Repository: dotnet/orleans Branch: main Commit: a0a2909dcee9 Files: 3194 Total size: 16.6 MB Directory structure: gitextract_hngr272l/ ├── .azure/ │ └── pipelines/ │ ├── build.yaml │ ├── github-mirror.yaml │ ├── nightly-main.yaml │ └── templates/ │ ├── build.yaml │ └── vars.yaml ├── .azuredevops/ │ └── dependabot.yml ├── .config/ │ ├── 1espt/ │ │ └── PipelineAutobaseliningConfig.yml │ ├── guardian/ │ │ └── .gdnbaselines │ └── tsaoptions.json ├── .devcontainer/ │ └── devcontainer.json ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── copilot-instructions.md │ ├── dependabot.yml │ ├── eventhubs-emulator/ │ │ └── Config.json │ ├── policies/ │ │ └── resourceManagement.yml │ └── workflows/ │ ├── ci.yml │ ├── codeql.yml │ ├── generate-api-diffs.yml │ └── locker.yml ├── .gitignore ├── Build.cmd ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── LICENSE ├── NuGet.Config ├── Orleans.slnx ├── Parallel-Tests.ps1 ├── README.md ├── SUPPORT.md ├── Test.cmd ├── TestAll.cmd ├── build.ps1 ├── common.ps1 ├── distributed-tests.yml ├── es-metadata.yml ├── global.json ├── playground/ │ ├── ActivationRebalancing/ │ │ ├── ActivationRebalancing.AppHost/ │ │ │ ├── ActivationRebalancing.AppHost.csproj │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── launchSettings.json │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ │ ├── ActivationRebalancing.Cluster/ │ │ │ ├── ActivationRebalancing.Cluster.csproj │ │ │ └── Program.cs │ │ └── ActivationRebalancing.Frontend/ │ │ ├── ActivationRebalancing.Frontend.csproj │ │ ├── Controllers/ │ │ │ └── StatsController.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ └── wwwroot/ │ │ ├── index.html │ │ └── worker.js │ ├── ActivationRepartitioning/ │ │ ├── ActivationRepartitioning.AppHost/ │ │ │ ├── ActivationRepartitioning.AppHost.csproj │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── launchSettings.json │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ │ └── ActivationRepartitioning.Frontend/ │ │ ├── ActivationRepartitioning.Frontend.csproj │ │ ├── Data/ │ │ │ └── ClusterDiagnosticsService.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ └── wwwroot/ │ │ ├── css/ │ │ │ ├── open-iconic/ │ │ │ │ ├── FONT-LICENSE │ │ │ │ ├── ICON-LICENSE │ │ │ │ ├── README.md │ │ │ │ └── font/ │ │ │ │ └── fonts/ │ │ │ │ └── open-iconic.otf │ │ │ └── site.css │ │ └── index.html │ ├── ActivationSheddingToy/ │ │ ├── ActivationSheddingToy.csproj │ │ ├── ActivationSheddingToyHostedService.cs │ │ └── Program.cs │ ├── ChaoticCluster/ │ │ ├── ChaoticCluster.AppHost/ │ │ │ ├── ChaoticCluster.AppHost.csproj │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── launchSettings.json │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ │ ├── ChaoticCluster.ServiceDefaults/ │ │ │ ├── ChaoticCluster.ServiceDefaults.csproj │ │ │ └── Extensions.cs │ │ └── ChaoticCluster.Silo/ │ │ ├── ChaoticCluster.Silo.csproj │ │ ├── Program.cs │ │ └── SiloBuilderConfigurator.cs │ ├── DashboardCohosted/ │ │ ├── DashboardCohosted.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── README.md │ ├── DashboardSeparateHost/ │ │ ├── DashboardSeparateHost.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── README.md │ └── Directory.Build.props ├── samples/ │ └── README.md ├── sign/ │ ├── NuGet.Config │ ├── Sign.proj │ ├── Sign.props │ ├── packages.config │ └── snk/ │ ├── 35MSSharedLib1024.snk │ ├── AspNetCore.snk │ ├── ECMA.snk │ ├── MSFT.snk │ ├── Open.snk │ └── SilverlightPlatformPublicKey.snk ├── spelling.dic ├── src/ │ ├── AWS/ │ │ ├── Orleans.Clustering.DynamoDB/ │ │ │ ├── AWSUtilsHostingExtensions.cs │ │ │ ├── DynamoDBClusteringProviderBuilder.cs │ │ │ ├── Membership/ │ │ │ │ ├── DynamoDBGatewayListProvider.cs │ │ │ │ ├── DynamoDBGatewayListProviderHelper.cs │ │ │ │ ├── DynamoDBMembershipHelper.cs │ │ │ │ ├── DynamoDBMembershipTable.cs │ │ │ │ └── SiloInstanceRecord.cs │ │ │ ├── Options/ │ │ │ │ ├── DynamoDBClusteringOptions.cs │ │ │ │ ├── DynamoDBClusteringSiloOptions.cs │ │ │ │ └── DynamoDBGatewayOptions.cs │ │ │ ├── Orleans.Clustering.DynamoDB.csproj │ │ │ └── README.md │ │ ├── Orleans.Persistence.DynamoDB/ │ │ │ ├── Hosting/ │ │ │ │ ├── DynamoDBGrainStorageProviderBuilder.cs │ │ │ │ ├── DynamoDBGrainStorageServiceCollectionExtensions.cs │ │ │ │ └── DynamoDBGrainStorageSiloBuilderExtensions.cs │ │ │ ├── Options/ │ │ │ │ └── DynamoDBStorageOptions.cs │ │ │ ├── Orleans.Persistence.DynamoDB.csproj │ │ │ ├── Provider/ │ │ │ │ └── DynamoDBGrainStorage.cs │ │ │ └── README.md │ │ ├── Orleans.Reminders.DynamoDB/ │ │ │ ├── DynamoDBRemindersProviderBuilder.cs │ │ │ ├── DynamoDBServiceCollectionReminderExtensions.cs │ │ │ ├── DynamoDBSiloBuilderReminderExtensions.cs │ │ │ ├── Orleans.Reminders.DynamoDB.csproj │ │ │ ├── README.md │ │ │ └── Reminders/ │ │ │ ├── DynamoDBReminderTable.cs │ │ │ ├── DynamoDbReminderServiceOptions.cs │ │ │ ├── DynamoDbReminderStorageOptions.cs │ │ │ └── DynamoDbReminderStorageOptionsExtensions.cs │ │ ├── Orleans.Streaming.SQS/ │ │ │ ├── Hosting/ │ │ │ │ ├── ClientBuilderExtensions.cs │ │ │ │ └── SiloBuilderExtensions.cs │ │ │ ├── Orleans.Streaming.SQS.csproj │ │ │ ├── README.md │ │ │ ├── Storage/ │ │ │ │ └── SQSStorage.cs │ │ │ └── Streams/ │ │ │ ├── SQSAdapter.cs │ │ │ ├── SQSAdapterFactory.cs │ │ │ ├── SQSAdapterReceiver.cs │ │ │ ├── SQSBatchContainer.cs │ │ │ ├── SQSStreamBuilder.cs │ │ │ ├── SQSStreamProviderUtils.cs │ │ │ └── SqsStreamOptions.cs │ │ └── Shared/ │ │ ├── AWSUtils.cs │ │ └── Storage/ │ │ ├── DynamoDBClientOptions.cs │ │ └── DynamoDBStorage.cs │ ├── AdoNet/ │ │ ├── Orleans.Clustering.AdoNet/ │ │ │ ├── AdoNetClusteringProviderBuilder.cs │ │ │ ├── AdoNetHostingExtensions.cs │ │ │ ├── Messaging/ │ │ │ │ ├── AdoNetClusteringTable.cs │ │ │ │ └── AdoNetGatewayListProvider.cs │ │ │ ├── Migrations/ │ │ │ │ ├── MySQL-Clustering-3.7.0.sql │ │ │ │ ├── Oracle-Clustering-3.7.0.sql │ │ │ │ ├── PostgreSQL-Clustering-3.6.0.sql │ │ │ │ ├── PostgreSQL-Clustering-3.7.0.sql │ │ │ │ └── SQLServer-Clustering-3.7.0.sql │ │ │ ├── MySQL-Clustering.sql │ │ │ ├── Options/ │ │ │ │ ├── AdoNetClusteringClientOptions.cs │ │ │ │ ├── AdoNetClusteringClientOptionsValidator.cs │ │ │ │ ├── AdoNetClusteringSiloOptions.cs │ │ │ │ └── AdoNetReminderTableOptionsValidator.cs │ │ │ ├── Oracle-Clustering.sql │ │ │ ├── Orleans.Clustering.AdoNet.csproj │ │ │ ├── PostgreSQL-Clustering.sql │ │ │ ├── README.md │ │ │ └── SQLServer-Clustering.sql │ │ ├── Orleans.GrainDirectory.AdoNet/ │ │ │ ├── AdoNetGrainDirectory.cs │ │ │ ├── AdoNetGrainDirectoryEntry.cs │ │ │ ├── AdoNetGrainDirectoryOptions.cs │ │ │ ├── GlobalSuppressions.cs │ │ │ ├── Hosting/ │ │ │ │ ├── AdoNetGrainDirectoryProviderBuilder.cs │ │ │ │ ├── AdoNetGrainDirectoryServiceCollectionExtensions.cs │ │ │ │ └── AdoNetGrainDirectorySiloBuilderExtensions.cs │ │ │ ├── MySQL-GrainDirectory.sql │ │ │ ├── Options/ │ │ │ │ └── AdoNetGrainDirectoryOptionsValidator.cs │ │ │ ├── Orleans.GrainDirectory.AdoNet.csproj │ │ │ ├── PostgreSQL-GrainDirectory.sql │ │ │ ├── Properties/ │ │ │ │ └── Usings.cs │ │ │ └── SQLServer-GrainDirectory.sql │ │ ├── Orleans.Persistence.AdoNet/ │ │ │ ├── AdoNetGrainStorageProviderBuilder.cs │ │ │ ├── Migrations/ │ │ │ │ └── PostgreSQL-Persistence-3.6.0.sql │ │ │ ├── MySQL-Persistence.sql │ │ │ ├── Options/ │ │ │ │ └── AdoNetGrainStorageOptions.cs │ │ │ ├── Oracle-Persistence.sql │ │ │ ├── Orleans.Persistence.AdoNet.csproj │ │ │ ├── PostgreSQL-Persistence.sql │ │ │ ├── README.md │ │ │ ├── SQLServer-Persistence.sql │ │ │ ├── Sqlite-Persistence.sql │ │ │ └── Storage/ │ │ │ └── Provider/ │ │ │ ├── AdoGrainKey.cs │ │ │ ├── AdoNetGrainStorage.cs │ │ │ ├── AdoNetGrainStorageServiceCollectionExtensions.cs │ │ │ ├── AdoNetGrainStorageSiloBuilderExtensions.cs │ │ │ ├── IHasher.cs │ │ │ ├── IStorageHashPicker.cs │ │ │ ├── JenkinsHash.cs │ │ │ ├── Orleans3CompatibleHasher.cs │ │ │ ├── Orleans3CompatibleStorageHashPicker.cs │ │ │ ├── Orleans3CompatibleStringKeyHasher.cs │ │ │ ├── OrleansDefaultHasher.cs │ │ │ ├── RelationalStorageProviderQueries.cs │ │ │ └── StorageHasherPicker.cs │ │ ├── Orleans.Reminders.AdoNet/ │ │ │ ├── AdoNetRemindersProviderBuilder.cs │ │ │ ├── Migrations/ │ │ │ │ └── PostgreSQL-Reminders-3.6.0.sql │ │ │ ├── MySQL-Reminders.sql │ │ │ ├── Oracle-Reminders.sql │ │ │ ├── Orleans.Reminders.AdoNet.csproj │ │ │ ├── PostgreSQL-Reminders.sql │ │ │ ├── README.md │ │ │ ├── ReminderService/ │ │ │ │ ├── AdoNetReminderTable.cs │ │ │ │ ├── AdoNetReminderTableOptions.cs │ │ │ │ └── AdoNetReminderTableOptionsValidator.cs │ │ │ ├── SQLServer-Reminders.sql │ │ │ └── SiloBuilderReminderExtensions.cs │ │ ├── Orleans.Streaming.AdoNet/ │ │ │ ├── AdoNetBatchContainer.cs │ │ │ ├── AdoNetQueueAdapter.cs │ │ │ ├── AdoNetQueueAdapterFactory.cs │ │ │ ├── AdoNetQueueAdapterReceiver.cs │ │ │ ├── AdoNetStreamConfirmation.cs │ │ │ ├── AdoNetStreamConfirmationAck.cs │ │ │ ├── AdoNetStreamDeadLetter.cs │ │ │ ├── AdoNetStreamFailureHandler.cs │ │ │ ├── AdoNetStreamMessage.cs │ │ │ ├── AdoNetStreamMessageAck.cs │ │ │ ├── AdoNetStreamOptions.cs │ │ │ ├── AdoNetStreamOptionsValidator.cs │ │ │ ├── AdoNetStreamQueueMapper.cs │ │ │ ├── Extensions.cs │ │ │ ├── GlobalSuppressions.cs │ │ │ ├── Hosting/ │ │ │ │ ├── ClusterClientAdoNetStreamConfigurator.cs │ │ │ │ ├── ClusterClientAdoNetStreamExtensions.cs │ │ │ │ ├── SiloAdoNetStreamConfigurator.cs │ │ │ │ └── SiloBuilderAdoNetStreamExtensions.cs │ │ │ ├── MySQL-Streaming.sql │ │ │ ├── Orleans.Streaming.AdoNet.csproj │ │ │ ├── PostgreSQL-Streaming.sql │ │ │ ├── Properties/ │ │ │ │ └── Usings.cs │ │ │ ├── README.md │ │ │ └── SQLServer-Streaming.sql │ │ └── Shared/ │ │ ├── MySQL-Main.sql │ │ ├── Oracle-Main.sql │ │ ├── PostgreSQL-Main.sql │ │ ├── SQLServer-Main.sql │ │ ├── Sqlite-Main.sql │ │ └── Storage/ │ │ ├── AdoNetFormatProvider.cs │ │ ├── AdoNetInvariants.cs │ │ ├── DbConnectionFactory.cs │ │ ├── DbConstantsStore.cs │ │ ├── DbExtensions.cs │ │ ├── DbStoredQueries.cs │ │ ├── ICommandInterceptor.cs │ │ ├── IRelationalStorage.cs │ │ ├── NoOpDatabaseCommandInterceptor.cs │ │ ├── OracleDatabaseCommandInterceptor.cs │ │ ├── OrleansRelationalDownloadStream.cs │ │ ├── RelationalOrleansQueries.cs │ │ ├── RelationalStorage.cs │ │ └── RelationalStorageExtensions.cs │ ├── Azure/ │ │ ├── Orleans.Clustering.AzureStorage/ │ │ │ ├── AzureBasedMembershipTable.cs │ │ │ ├── AzureGatewayListProvider.cs │ │ │ ├── AzureTableClusteringExtensions.cs │ │ │ ├── AzureTableStorageClusteringProviderBuilder.cs │ │ │ ├── Options/ │ │ │ │ ├── AzureStorageClusteringOptions.cs │ │ │ │ └── AzureStorageGatewayOptions.cs │ │ │ ├── Orleans.Clustering.AzureStorage.csproj │ │ │ ├── OrleansSiloInstanceManager.cs │ │ │ ├── README.md │ │ │ ├── SiloInstanceTableEntry.cs │ │ │ └── Utilities/ │ │ │ └── TableStorageErrorCode.cs │ │ ├── Orleans.Clustering.Cosmos/ │ │ │ ├── CosmosClusteringProviderBuilder.cs │ │ │ ├── HostingExtensions.cs │ │ │ ├── Membership/ │ │ │ │ ├── CosmosGatewayListProvider.cs │ │ │ │ └── CosmosMembershipTable.cs │ │ │ ├── Models/ │ │ │ │ ├── BaseClusterEntity.cs │ │ │ │ ├── ClusterVersionEntity.cs │ │ │ │ └── SiloEntity.cs │ │ │ ├── Options/ │ │ │ │ └── CosmosClusteringOptions.cs │ │ │ ├── Orleans.Clustering.Cosmos.csproj │ │ │ └── README.md │ │ ├── Orleans.DurableJobs.AzureStorage/ │ │ │ ├── AzureStorageJobShard.Log.cs │ │ │ ├── AzureStorageJobShard.cs │ │ │ ├── AzureStorageJobShardManager.cs │ │ │ ├── Hosting/ │ │ │ │ ├── AzureStorageDurableJobsExtensions.cs │ │ │ │ ├── AzureStorageJobShardOptions.cs │ │ │ │ └── AzureStorageJobShardOptionsValidator.cs │ │ │ ├── JobOperation.cs │ │ │ ├── NetstringJsonSerializer.cs │ │ │ ├── Orleans.DurableJobs.AzureStorage.csproj │ │ │ └── README.md │ │ ├── Orleans.GrainDirectory.AzureStorage/ │ │ │ ├── AzureTableGrainDirectory.cs │ │ │ ├── Hosting/ │ │ │ │ ├── AzureTableGrainDirectoryExtensions.cs │ │ │ │ ├── AzureTableGrainDirectoryServiceCollectionExtensions.cs │ │ │ │ └── AzureTableStorageGrainDirectoryProviderBuilder.cs │ │ │ ├── Options/ │ │ │ │ └── AzureTableGrainDirectoryOptions.cs │ │ │ ├── Orleans.GrainDirectory.AzureStorage.csproj │ │ │ └── README.md │ │ ├── Orleans.Journaling.AzureStorage/ │ │ │ ├── AzureAppendBlobLogStorage.cs │ │ │ ├── AzureAppendBlobStateMachineStorageOptions.cs │ │ │ ├── AzureAppendBlobStateMachineStorageProvider.cs │ │ │ ├── AzureBlobStorageGrainJournalingProviderBuilder.cs │ │ │ ├── AzureBlobStorageHostingExtensions.cs │ │ │ ├── DefaultBlobContainerFactory.cs │ │ │ ├── IBlobContainerFactory.cs │ │ │ ├── Orleans.Journaling.AzureStorage.csproj │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ └── README.md │ │ ├── Orleans.Persistence.AzureStorage/ │ │ │ ├── Hosting/ │ │ │ │ ├── AzureBlobGrainStorageServiceCollectionExtensions.cs │ │ │ │ ├── AzureBlobSiloBuilderExtensions.cs │ │ │ │ ├── AzureBlobStorageGrainStorageProviderBuilder.cs │ │ │ │ ├── AzureTableSiloBuilderExtensions.cs │ │ │ │ └── AzureTableStorageGrainStorageProviderBuilder.cs │ │ │ ├── Orleans.Persistence.AzureStorage.csproj │ │ │ ├── Providers/ │ │ │ │ ├── AzureProviderErrorCode.cs │ │ │ │ └── Storage/ │ │ │ │ ├── AzureBlobStorage.cs │ │ │ │ ├── AzureBlobStorageOptions.cs │ │ │ │ ├── AzureTableStorage.cs │ │ │ │ ├── AzureTableStorageOptions.cs │ │ │ │ └── IBlobContainerFactory.cs │ │ │ ├── README.md │ │ │ └── Storage/ │ │ │ ├── StorageExceptionExtensions.cs │ │ │ └── TableStorageUpdateConditionNotSatisfiedException.cs │ │ ├── Orleans.Persistence.Cosmos/ │ │ │ ├── CosmosConditionNotSatisfiedException.cs │ │ │ ├── CosmosGrainStorage.cs │ │ │ ├── CosmosStorageOptions.cs │ │ │ ├── HostingExtensions.cs │ │ │ ├── IPartitionKeyProvider.cs │ │ │ ├── Models/ │ │ │ │ └── GrainStateEntity.cs │ │ │ ├── Orleans.Persistence.Cosmos.csproj │ │ │ └── README.md │ │ ├── Orleans.Reminders.AzureStorage/ │ │ │ ├── AzureStorageReminderServiceCollectionExtensions.cs │ │ │ ├── AzureStorageReminderSiloBuilderReminderExtensions.cs │ │ │ ├── AzureTableStorageRemindersProviderBuilder.cs │ │ │ ├── Orleans.Reminders.AzureStorage.csproj │ │ │ ├── README.md │ │ │ ├── Storage/ │ │ │ │ ├── AzureBasedReminderTable.cs │ │ │ │ ├── AzureTableReminderStorageOptions.cs │ │ │ │ └── RemindersTableManager.cs │ │ │ └── Utilities/ │ │ │ └── AzureReminderErrorCode.cs │ │ ├── Orleans.Reminders.Cosmos/ │ │ │ ├── CosmosReminderTable.cs │ │ │ ├── CosmosReminderTableOptions.cs │ │ │ ├── HostingExtensions.cs │ │ │ ├── Models/ │ │ │ │ └── ReminderEntity.cs │ │ │ ├── Orleans.Reminders.Cosmos.csproj │ │ │ └── README.md │ │ ├── Orleans.Streaming.AzureStorage/ │ │ │ ├── Hosting/ │ │ │ │ ├── AzureQueueStreamProviderBuilder.cs │ │ │ │ ├── ClientBuilderExtensions.cs │ │ │ │ └── SiloBuilderExtensions.cs │ │ │ ├── Options/ │ │ │ │ └── AzureBlobLeaseProviderOptions.cs │ │ │ ├── Orleans.Streaming.AzureStorage.csproj │ │ │ ├── Providers/ │ │ │ │ ├── Lease/ │ │ │ │ │ └── AzureBlobLeaseProvider.cs │ │ │ │ └── Streams/ │ │ │ │ ├── AzureQueue/ │ │ │ │ │ ├── AzureQueueAdapter.cs │ │ │ │ │ ├── AzureQueueAdapterFactory.cs │ │ │ │ │ ├── AzureQueueAdapterReceiver.cs │ │ │ │ │ ├── AzureQueueBatchContainer.cs │ │ │ │ │ ├── AzureQueueBatchContainerV2.cs │ │ │ │ │ ├── AzureQueueStreamBuilder.cs │ │ │ │ │ ├── AzureQueueStreamOptions.cs │ │ │ │ │ ├── AzureQueueStreamProviderUtils.cs │ │ │ │ │ └── IAzureQueueDataAdapter.cs │ │ │ │ └── PersistentStreams/ │ │ │ │ ├── AzureTableStorageStreamFailureHandler.cs │ │ │ │ └── StreamDeliveryFailureEntity.cs │ │ │ ├── README.md │ │ │ ├── Storage/ │ │ │ │ └── AzureQueueDataManager.cs │ │ │ └── Utilities/ │ │ │ └── AzureQueueErrorCode.cs │ │ ├── Orleans.Streaming.EventHubs/ │ │ │ ├── Hosting/ │ │ │ │ ├── ClientBuilderExtensions.cs │ │ │ │ ├── DeveloperExtensions.cs │ │ │ │ └── SiloBuilderExtensions.cs │ │ │ ├── Orleans.Streaming.EventHubs.csproj │ │ │ ├── OrleansServiceBusErrorCode.cs │ │ │ ├── Providers/ │ │ │ │ ├── EventDataGeneratorStreamProvider/ │ │ │ │ │ ├── EventDataGeneratorAdapterFactory.cs │ │ │ │ │ ├── EventDataGeneratorStreamOptions.cs │ │ │ │ │ ├── EventHubPartitionDataGenerator.cs │ │ │ │ │ ├── EventHubPartitionGeneratorReceiver.cs │ │ │ │ │ ├── IEventDataGenerator.cs │ │ │ │ │ └── NoOpCheckpointer.cs │ │ │ │ └── Streams/ │ │ │ │ └── EventHub/ │ │ │ │ ├── CachePressureMonitors/ │ │ │ │ │ ├── AggregatedCachePressureMonitor.cs │ │ │ │ │ ├── AveragingCachePressureMonitor.cs │ │ │ │ │ ├── ICachePressureMonitor.cs │ │ │ │ │ └── SlowConsumingPressureMonitor.cs │ │ │ │ ├── EventDataExtensions.cs │ │ │ │ ├── EventDataGeneratorStreamConfigurator.cs │ │ │ │ ├── EventHubAdapterFactory.cs │ │ │ │ ├── EventHubAdapterReceiver.cs │ │ │ │ ├── EventHubBatchContainer.cs │ │ │ │ ├── EventHubCheckpointer.cs │ │ │ │ ├── EventHubCheckpointerOptions.cs │ │ │ │ ├── EventHubConstants.cs │ │ │ │ ├── EventHubDataAdapter.cs │ │ │ │ ├── EventHubMessage.cs │ │ │ │ ├── EventHubPartitionCheckpointEntity.cs │ │ │ │ ├── EventHubQueueCache.cs │ │ │ │ ├── EventHubQueueCacheFactory.cs │ │ │ │ ├── EventHubSequenceToken.cs │ │ │ │ ├── EventHubSequenceTokenV2.cs │ │ │ │ ├── EventHubStreamBuilder.cs │ │ │ │ ├── EventHubStreamOptions.cs │ │ │ │ ├── IEventHubDataAdapter.cs │ │ │ │ ├── IEventHubQueueCache.cs │ │ │ │ ├── IEventHubQueueCacheFactory.cs │ │ │ │ ├── IEventHubReceiver.cs │ │ │ │ └── StatisticMonitors/ │ │ │ │ ├── DefaultEventHubBlockPoolMonitor.cs │ │ │ │ ├── DefaultEventHubCacheMonitor.cs │ │ │ │ ├── DefaultEventHubReceiverMonitor.cs │ │ │ │ └── MonitorAggregationDimentions.cs │ │ │ └── README.md │ │ ├── Orleans.Transactions.AzureStorage/ │ │ │ ├── Hosting/ │ │ │ │ ├── AzureTableTransactionServicecollectionExtensions.cs │ │ │ │ └── AzureTableTransactionsSiloBuilderExtensions.cs │ │ │ ├── Orleans.Transactions.AzureStorage.csproj │ │ │ ├── README.md │ │ │ └── TransactionalState/ │ │ │ ├── AzureTableTransactionalStateOptions.cs │ │ │ ├── AzureTableTransactionalStateStorage.cs │ │ │ ├── AzureTableTransactionalStateStorageFactory.cs │ │ │ ├── KeyEntity.cs │ │ │ └── StateEntity.cs │ │ └── Shared/ │ │ ├── Cosmos/ │ │ │ ├── BaseEntity.cs │ │ │ ├── CosmosIdSanitizer.cs │ │ │ ├── CosmosOptions.cs │ │ │ ├── CosmosOptionsValidator.cs │ │ │ └── Usings.cs │ │ ├── Storage/ │ │ │ ├── AzureBlobUtils.cs │ │ │ ├── AzureStorageOperationOptions.cs │ │ │ ├── AzureStoragePolicyOptions.cs │ │ │ ├── AzureTableDataManager.cs │ │ │ ├── AzureTableDefaultPolicies.cs │ │ │ └── AzureTableUtils.cs │ │ └── Utilities/ │ │ └── ErrorCode.cs │ ├── Cassandra/ │ │ └── Orleans.Clustering.Cassandra/ │ │ ├── CassandraClusteringTable.cs │ │ ├── CassandraGatewayListProvider.cs │ │ ├── Hosting/ │ │ │ ├── CassandraClusteringOptions.cs │ │ │ └── CassandraMembershipHostingExtensions.cs │ │ ├── Orleans.Clustering.Cassandra.csproj │ │ └── OrleansQueries.cs │ ├── Dashboard/ │ │ ├── Orleans.Dashboard/ │ │ │ ├── .azdo/ │ │ │ │ └── .npmrc │ │ │ ├── Core/ │ │ │ │ ├── DashboardClient.cs │ │ │ │ ├── Extensions.cs │ │ │ │ ├── IDashboardClient.cs │ │ │ │ ├── IDashboardGrain.cs │ │ │ │ ├── IDashboardRemindersGrain.cs │ │ │ │ ├── ISiloGrainClient.cs │ │ │ │ ├── ISiloGrainProxy.cs │ │ │ │ └── ISiloGrainService.cs │ │ │ ├── DashboardHost.cs │ │ │ ├── DashboardOptions.cs │ │ │ ├── EmbeddedAssetProvider.cs │ │ │ ├── GrainProfilerOptions.cs │ │ │ ├── Implementation/ │ │ │ │ ├── DashboardLogger.cs │ │ │ │ ├── DashboardTelemetryExporter.cs │ │ │ │ ├── Details/ │ │ │ │ │ ├── MembershipTableSiloDetailsProvider.cs │ │ │ │ │ └── SiloStatusOracleSiloDetailsProvider.cs │ │ │ │ ├── GrainProfilerFilter.cs │ │ │ │ ├── Grains/ │ │ │ │ │ ├── DashboardGrain.cs │ │ │ │ │ ├── DashboardRemindersGrain.cs │ │ │ │ │ └── SiloGrainProxy.cs │ │ │ │ ├── Helpers/ │ │ │ │ │ └── GrainStateHelper.cs │ │ │ │ ├── SiloGrainClient.cs │ │ │ │ ├── SiloGrainService.cs │ │ │ │ └── TraceWriter.cs │ │ │ ├── Metrics/ │ │ │ │ ├── Details/ │ │ │ │ │ └── ISiloDetailsProvider.cs │ │ │ │ ├── GrainProfiler.cs │ │ │ │ ├── GrainProfilerExtensions.cs │ │ │ │ ├── History/ │ │ │ │ │ ├── GrainTraceEntryEqualityComparer.cs │ │ │ │ │ ├── HistoryEntry.cs │ │ │ │ │ ├── HistoryKey.cs │ │ │ │ │ ├── ITraceHistory.cs │ │ │ │ │ ├── RingBuffer.cs │ │ │ │ │ └── TraceHistory.cs │ │ │ │ ├── IGrainProfiler.cs │ │ │ │ └── TypeFormatting/ │ │ │ │ ├── ParseState.cs │ │ │ │ ├── Token.cs │ │ │ │ ├── TokenType.cs │ │ │ │ └── TypeFormatter.cs │ │ │ ├── Model/ │ │ │ │ ├── DashboardCounters.cs │ │ │ │ ├── GrainTraceEntry.cs │ │ │ │ ├── History/ │ │ │ │ │ ├── GrainMethodAggregate.cs │ │ │ │ │ └── TraceAggregate.cs │ │ │ │ ├── ReminderInfo.cs │ │ │ │ ├── ReminderResponse.cs │ │ │ │ ├── SiloDetails.cs │ │ │ │ ├── SiloGrainTraceEntry.cs │ │ │ │ ├── SimpleGrainStatisticCounter.cs │ │ │ │ └── StatCounter.cs │ │ │ ├── Orleans.Dashboard.Frontend.targets │ │ │ ├── Orleans.Dashboard.csproj │ │ │ ├── README.md │ │ │ ├── ServiceCollectionExtensions.cs │ │ │ └── TimeSpanConverter.cs │ │ ├── Orleans.Dashboard.Abstractions/ │ │ │ ├── NoProfilingAttribute.cs │ │ │ ├── Orleans.Dashboard.Abstractions.csproj │ │ │ └── README.md │ │ └── Orleans.Dashboard.App/ │ │ ├── .gitignore │ │ ├── .npmrc │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── alert.tsx │ │ │ │ ├── brand-header.tsx │ │ │ │ ├── checkbox-filter.tsx │ │ │ │ ├── counter-widget.tsx │ │ │ │ ├── display-grain-state.tsx │ │ │ │ ├── gauge-widget.tsx │ │ │ │ ├── grain-method-table.tsx │ │ │ │ ├── grain-table.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── menu.tsx │ │ │ │ ├── multi-series-chart-widget.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── panel.tsx │ │ │ │ ├── preferences.tsx │ │ │ │ ├── properties-widget.tsx │ │ │ │ ├── reminder-table.tsx │ │ │ │ ├── theme-buttons.tsx │ │ │ │ └── time-series-chart.tsx │ │ │ ├── custom.css │ │ │ ├── grains/ │ │ │ │ ├── grain-details.tsx │ │ │ │ ├── grain.tsx │ │ │ │ ├── grains.tsx │ │ │ │ └── silo-table.tsx │ │ │ ├── index.tsx │ │ │ ├── lib/ │ │ │ │ ├── http.ts │ │ │ │ ├── routie.ts │ │ │ │ ├── storage.ts │ │ │ │ └── typeName.ts │ │ │ ├── logstream/ │ │ │ │ └── log-stream.tsx │ │ │ ├── overview/ │ │ │ │ └── overview.tsx │ │ │ ├── reminders/ │ │ │ │ └── reminders.tsx │ │ │ ├── silos/ │ │ │ │ ├── host-table.tsx │ │ │ │ ├── silo-counters.tsx │ │ │ │ ├── silo-grid.tsx │ │ │ │ ├── silo-state-label.tsx │ │ │ │ ├── silo.tsx │ │ │ │ └── silos.tsx │ │ │ ├── skin-purple.css │ │ │ ├── styles.css │ │ │ ├── themes.css │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── Orleans.Analyzers/ │ │ ├── AbstractPropertiesCannotBeSerializedAnalyzer.cs │ │ ├── AliasClashAttributeAnalyzer.cs │ │ ├── AliasClashAttributeCodeFix.cs │ │ ├── AlwaysInterleaveDiagnosticAnalyzer.cs │ │ ├── AnalyzerReleases.Shipped.md │ │ ├── AnalyzerReleases.Unshipped.md │ │ ├── AtMostOneOrleansConstructorAnalyzer.cs │ │ ├── ConfigureAwaitAnalyzer.cs │ │ ├── ConfigureAwaitCodeFix.cs │ │ ├── Constants.cs │ │ ├── GenerateAliasAttributesAnalyzer.cs │ │ ├── GenerateAliasAttributesCodeFix.cs │ │ ├── GenerateGenerateSerializerAttributeAnalyzer.cs │ │ ├── GenerateSerializationAttributesAnalyzer.cs │ │ ├── GenerateSerializationAttributesCodeFix.cs │ │ ├── GrainInterfaceMethodReturnTypeDiagnosticAnalyzer.cs │ │ ├── GrainInterfacePropertyDiagnosticAnalyzer.cs │ │ ├── IdClashAttributeAnalyzer.cs │ │ ├── IdClashAttributeCodeFix.cs │ │ ├── IncorrectAttributeUseAnalyzer.cs │ │ ├── IncorrectAttributeUseCodeFix.cs │ │ ├── NoRefParamsDiagnosticAnalyzer.cs │ │ ├── Orleans.Analyzers.csproj │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── SerializationAttributesHelper.cs │ │ ├── SymbolHelpers.cs │ │ └── SyntaxHelpers.cs │ ├── Orleans.BroadcastChannel/ │ │ ├── BroadcastChannelConsumerExtension.cs │ │ ├── BroadcastChannelOptions.cs │ │ ├── BroadcastChannelProvider.cs │ │ ├── BroadcastChannelSubscription.cs │ │ ├── BroadcastChannelWriter.cs │ │ ├── ChannelId.cs │ │ ├── Hosting/ │ │ │ ├── BroadcastChannelProviderBuilder.cs │ │ │ └── ChannelHostingExtensions.cs │ │ ├── IdMapping/ │ │ │ ├── DefaultChannelIdMapper.cs │ │ │ └── IChannelIdMapper.cs │ │ ├── Orleans.BroadcastChannel.csproj │ │ └── SubscriberTable/ │ │ ├── ImplicitChannelSubscriberTable.cs │ │ └── Predicates/ │ │ ├── AllStreamNamespacesPredicate.cs │ │ ├── DefaultStreamNamespacePredicateProvider.cs │ │ ├── ExactMatchStreamNamespacePredicate.cs │ │ ├── IChannelNamespacePredicate.cs │ │ ├── ImplicitChannelSubscriptionAttribute.cs │ │ └── RegexChannelNamespacePredicate.cs │ ├── Orleans.Client/ │ │ ├── Orleans.Client.csproj │ │ └── README.md │ ├── Orleans.Clustering.Consul/ │ │ ├── ConsulBasedMembershipTable.cs │ │ ├── ConsulClusteringProviderBuilder.cs │ │ ├── ConsulGatewayListProvider.cs │ │ ├── ConsulUtilsHostingExtensions.cs │ │ ├── Options/ │ │ │ └── ConsulClusteringOptions.cs │ │ ├── Orleans.Clustering.Consul.csproj │ │ └── SerializableMembershipTypes.cs │ ├── Orleans.Clustering.ZooKeeper/ │ │ ├── MembershipSerializerSettings.cs │ │ ├── Options/ │ │ │ ├── ZooKeeperClusteringSiloOptions.cs │ │ │ └── ZooKeeperGatewayListProviderOptions.cs │ │ ├── Orleans.Clustering.ZooKeeper.csproj │ │ ├── ZooKeeperBasedMembershipTable.cs │ │ ├── ZooKeeperClusteringProviderBuilder.cs │ │ ├── ZooKeeperGatewayListProvider.cs │ │ └── ZooKeeperHostingExtensions.cs │ ├── Orleans.CodeGenerator/ │ │ ├── ActivatorGenerator.cs │ │ ├── AnalyzerReleases.Shipped.md │ │ ├── AnalyzerReleases.Unshipped.md │ │ ├── ApplicationPartAttributeGenerator.cs │ │ ├── CodeGenerator.cs │ │ ├── CopierGenerator.cs │ │ ├── Diagnostics/ │ │ │ ├── CanNotGenerateImplicitFieldIdsDiagnostic.cs │ │ │ ├── DiagnosticRuleId.cs │ │ │ ├── GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly_Diagnostic.cs │ │ │ ├── InaccessibleSerializableTypeDiagnostic.cs │ │ │ ├── InaccessibleSetterDiagnostic.cs │ │ │ ├── IncorrectProxyBaseClassSpecificationDiagnostic.cs │ │ │ ├── InvalidRpcMethodReturnTypeDiagnostic.cs │ │ │ ├── MultipleCancellationTokenParametersDiagnostic.cs │ │ │ ├── ReferenceAssemblyWithGenerateSerializerDiagnostic.cs │ │ │ ├── RpcInterfacePropertyDiagnostic.cs │ │ │ └── UnhandledCodeGenerationExceptionDiagnostic.cs │ │ ├── FieldIdAssignmentHelper.cs │ │ ├── Hashing/ │ │ │ ├── BitOperations.cs │ │ │ ├── HexConverter.cs │ │ │ ├── NonCryptographicHashAlgorithm.cs │ │ │ ├── XxHash32.State.cs │ │ │ └── XxHash32.cs │ │ ├── InvokableGenerator.cs │ │ ├── LibraryTypes.cs │ │ ├── MetadataGenerator.cs │ │ ├── Model/ │ │ │ ├── FieldDescription.cs │ │ │ ├── GenerateFieldIds.cs │ │ │ ├── GeneratedInvokableDescription.cs │ │ │ ├── GeneratedProxyDescription.cs │ │ │ ├── ICodecDescription.cs │ │ │ ├── IMemberDescription.cs │ │ │ ├── ISerializableTypeDescription.cs │ │ │ ├── InvokableMethodDescription.cs │ │ │ ├── InvokableMethodId.cs │ │ │ ├── InvokableMethodProxyBase.cs │ │ │ ├── InvokableMethodProxyBaseId.cs │ │ │ ├── MetadataModel.cs │ │ │ ├── MethodSignatureComparer.cs │ │ │ ├── PropertyDescription.cs │ │ │ ├── ProxyInterfaceDescription.cs │ │ │ ├── ProxyMethodDescription.cs │ │ │ ├── SerializableTypeDescription.cs │ │ │ └── WellKnownCodecDescription.cs │ │ ├── Orleans.CodeGenerator.csproj │ │ ├── OrleansGeneratorDiagnosticAnalysisException.cs │ │ ├── OrleansSourceGenerator.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── PropertyUtility.cs │ │ ├── ProxyGenerator.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── SerializerGenerator.cs │ │ ├── SyntaxGeneration/ │ │ │ ├── FSharpUtils.cs │ │ │ ├── Identifier.cs │ │ │ ├── StringExtensions.cs │ │ │ ├── SymbolExtensions.cs │ │ │ ├── SymbolSyntaxExtensions.cs │ │ │ └── SyntaxFactoryUtility.cs │ │ ├── build/ │ │ │ └── Microsoft.Orleans.CodeGenerator.props │ │ ├── buildMultiTargeting/ │ │ │ └── Microsoft.Orleans.CodeGenerator.props │ │ └── buildTransitive/ │ │ └── Microsoft.Orleans.CodeGenerator.props │ ├── Orleans.Connections.Security/ │ │ ├── Hosting/ │ │ │ ├── HostingExtensions.IClientBuilder.cs │ │ │ ├── HostingExtensions.ISiloBuilder.cs │ │ │ └── HostingExtensions.cs │ │ ├── Orleans.Connections.Security.csproj │ │ └── Security/ │ │ ├── CertificateLoader.cs │ │ ├── DuplexPipeStream.cs │ │ ├── DuplexPipeStreamAdapter.cs │ │ ├── ITlsApplicationProtocolFeature.cs │ │ ├── ITlsConnectionFeature.cs │ │ ├── ITlsHandshakeFeature.cs │ │ ├── MemoryPoolExtensions.cs │ │ ├── OrleansApplicationProtocol.cs │ │ ├── RemoteCertificateMode.cs │ │ ├── TlsClientAuthenticationOptions.cs │ │ ├── TlsClientConnectionMiddleware.cs │ │ ├── TlsConnectionFeature.cs │ │ ├── TlsDuplexPipe.cs │ │ ├── TlsOptions.cs │ │ ├── TlsServerAuthenticationOptions.cs │ │ └── TlsServerConnectionMiddleware.cs │ ├── Orleans.Core/ │ │ ├── Async/ │ │ │ ├── AsyncExecutorWithRetries.cs │ │ │ ├── AsyncLock.cs │ │ │ ├── AsyncSerialExecutor.cs │ │ │ ├── BatchWorker.cs │ │ │ ├── MultiTaskCompletionSource.cs │ │ │ └── TaskExtensions.cs │ │ ├── Caching/ │ │ │ ├── ConcurrentLruCache.cs │ │ │ └── Internal/ │ │ │ ├── CacheDebugView.cs │ │ │ ├── CapacityPartition.cs │ │ │ ├── Counter.cs │ │ │ ├── ICacheMetrics.cs │ │ │ ├── PaddedLong.cs │ │ │ ├── PaddedQueueCount.cs │ │ │ ├── Padding.cs │ │ │ ├── Striped64.cs │ │ │ └── TypeProps.cs │ │ ├── Cancellation/ │ │ │ └── IGrainCallCancellationExtension.cs │ │ ├── ClientObservers/ │ │ │ ├── ClientGatewayObserver.cs │ │ │ └── ClientObserver.cs │ │ ├── CodeGeneration/ │ │ │ ├── GrainInterfaceUtils.cs │ │ │ └── IGrainState.cs │ │ ├── Configuration/ │ │ │ ├── CollectionAgeLimitAttribute.cs │ │ │ ├── ConfigUtilities.cs │ │ │ ├── GrainTypeOptions.cs │ │ │ ├── NamedServiceConfigurator.cs │ │ │ ├── OptionLogger/ │ │ │ │ ├── DefaultOptionsFormatter.cs │ │ │ │ ├── IOptionFormatter.cs │ │ │ │ ├── IOptionsLogger.cs │ │ │ │ ├── OptionFormatterExtensionMethods.cs │ │ │ │ └── RedactMemberAttribute.cs │ │ │ ├── Options/ │ │ │ │ ├── ClientMessagingOptions.cs │ │ │ │ ├── ClusterMembershipOptions.cs │ │ │ │ ├── ClusterOptions.cs │ │ │ │ ├── GatewayOptions.cs │ │ │ │ ├── GrainVersioningOptions.cs │ │ │ │ ├── LoadSheddingOptions.cs │ │ │ │ ├── MessagingOptions.cs │ │ │ │ ├── StaticGatewayListProviderOptions.cs │ │ │ │ └── TypeManagementOptions.cs │ │ │ ├── OptionsOverrides.cs │ │ │ ├── ServiceCollectionExtensions.cs │ │ │ └── Validators/ │ │ │ ├── ClientClusteringValidator.cs │ │ │ ├── LoadSheddingValidator.cs │ │ │ └── SerializerConfigurationValidator.cs │ │ ├── Core/ │ │ │ ├── ClientBuilder.cs │ │ │ ├── ClientBuilderExtensions.cs │ │ │ ├── ClientBuilderGrainCallFilterExtensions.cs │ │ │ ├── ClusterClient.cs │ │ │ ├── DefaultClientServices.cs │ │ │ ├── GatewayCountChangedEventArgs.cs │ │ │ ├── GrainCallFilterServiceCollectionExtensions.cs │ │ │ ├── GrainFactory.cs │ │ │ ├── GrainInterfaceTypeToGrainTypeResolver.cs │ │ │ ├── GrainMethodInvoker.cs │ │ │ ├── IClientBuilder.cs │ │ │ ├── IClientConnectionRetryFilter.cs │ │ │ ├── IClusterClient.cs │ │ │ ├── IClusterConnectionStatusListener.cs │ │ │ ├── IClusterConnectionStatusObserver.cs │ │ │ ├── IInternalClusterClient.cs │ │ │ ├── IInternalGrainFactory.cs │ │ │ ├── InterfaceToImplementationMappingCache.cs │ │ │ └── InvalidSchedulingContextException.cs │ │ ├── Diagnostics/ │ │ │ ├── ActivityNames.cs │ │ │ ├── ActivityPropagationGrainCallFilter.cs │ │ │ ├── EventSourceEvents.cs │ │ │ ├── MessagingTrace.cs │ │ │ └── Metrics/ │ │ │ ├── Aggregators/ │ │ │ │ ├── AggregatorKey.cs │ │ │ │ ├── CounterAggregator.cs │ │ │ │ ├── CounterAggregatorGroup.cs │ │ │ │ ├── HistogramAggregator.cs │ │ │ │ ├── HistogramBucketAggregator.cs │ │ │ │ └── TagList.cs │ │ │ ├── ApplicationRequestInstruments.cs │ │ │ ├── CatalogInstruments.cs │ │ │ ├── ClientInstruments.cs │ │ │ ├── ConsistentRingInstruments.cs │ │ │ ├── DirectoryInstruments.cs │ │ │ ├── GatewayInstruments.cs │ │ │ ├── GrainInstruments.cs │ │ │ ├── InstrumentNames.cs │ │ │ ├── Instruments.cs │ │ │ ├── MessagingInstruments.cs │ │ │ ├── MessagingProcessingInstruments.cs │ │ │ ├── NetworkingInstruments.cs │ │ │ ├── OrleansInstruments.cs │ │ │ ├── ReminderInstruments.cs │ │ │ ├── SchedulerInstruments.cs │ │ │ ├── StorageInstruments.cs │ │ │ ├── StreamInstruments.cs │ │ │ └── WatchdogInstruments.cs │ │ ├── GlobalSuppressions.cs │ │ ├── GrainDirectory/ │ │ │ ├── IDhtGrainDirectory.cs │ │ │ └── IGrainLocator.cs │ │ ├── GrainReferences/ │ │ │ └── GrainReferenceActivator.cs │ │ ├── Hosting/ │ │ │ └── OrleansClientGenericHostExtensions.cs │ │ ├── IDs/ │ │ │ ├── GenericGrainInterfaceType.cs │ │ │ └── GenericGrainType.cs │ │ ├── Lifecycle/ │ │ │ ├── ClusterClientLifecycle.cs │ │ │ ├── ClusterClientLifecycleExtensions.cs │ │ │ ├── IClusterClientLifecycle.cs │ │ │ ├── LifecycleSubject.cs │ │ │ ├── MigrationContext.cs │ │ │ ├── ServiceLifecycle.cs │ │ │ ├── ServiceLifecycleNotificationStage.cs │ │ │ └── ServiceLifecycleStage.cs │ │ ├── Manifest/ │ │ │ ├── ClientClusterManifestProvider.cs │ │ │ ├── ClientManifestProvider.cs │ │ │ ├── GrainBindings.cs │ │ │ ├── GrainInterfaceTypeResolver.cs │ │ │ ├── GrainPropertiesResolver.cs │ │ │ ├── GrainTypeResolver.cs │ │ │ ├── GrainVersionManifest.cs │ │ │ ├── IClusterManifestProvider.cs │ │ │ ├── IClusterManifestSystemTarget.cs │ │ │ ├── ImplementedInterfaceProvider.cs │ │ │ └── TypeNameGrainPropertiesProvider.cs │ │ ├── Messaging/ │ │ │ ├── CachingIdSpanCodec.cs │ │ │ ├── CachingSiloAddressCodec.cs │ │ │ ├── ClientMessageCenter.cs │ │ │ ├── CorrelationId.cs │ │ │ ├── GatewayManager.cs │ │ │ ├── IGatewayListProvider.cs │ │ │ ├── IMessageCenter.cs │ │ │ ├── InvalidMessageFrameException.cs │ │ │ ├── Message.cs │ │ │ ├── MessageFactory.cs │ │ │ ├── MessageSerializer.cs │ │ │ ├── OverloadDetectionLogic.cs │ │ │ ├── PrefixingBufferWriter.cs │ │ │ ├── RejectionResponse.cs │ │ │ ├── StaticGatewayListProvider.cs │ │ │ ├── StaticGatewayListProviderBuilder.cs │ │ │ └── StatusResponse.cs │ │ ├── Networking/ │ │ │ ├── ClientConnectionOptions.cs │ │ │ ├── ClientOutboundConnection.cs │ │ │ ├── ClientOutboundConnectionFactory.cs │ │ │ ├── Connection.cs │ │ │ ├── ConnectionBuilderDelegates.cs │ │ │ ├── ConnectionFactory.cs │ │ │ ├── ConnectionFailedException.cs │ │ │ ├── ConnectionLogScope.cs │ │ │ ├── ConnectionManager.cs │ │ │ ├── ConnectionManagerLifecycleAdapter.cs │ │ │ ├── ConnectionOptions.cs │ │ │ ├── ConnectionPreamble.cs │ │ │ ├── ConnectionShared.cs │ │ │ ├── IUnderlyingTransportFeature.cs │ │ │ ├── NetworkProtocolVersion.cs │ │ │ ├── NetworkingTrace.cs │ │ │ ├── Shared/ │ │ │ │ ├── BufferExtensions.cs │ │ │ │ ├── CorrelationIdGenerator.cs │ │ │ │ ├── DuplexPipe.cs │ │ │ │ ├── IOQueue.cs │ │ │ │ ├── ISocketsTrace.cs │ │ │ │ ├── KestrelMemoryPool.cs │ │ │ │ ├── MemoryPoolBlock.cs │ │ │ │ ├── MemoryPoolSlab.cs │ │ │ │ ├── SharedMemoryPool.cs │ │ │ │ ├── SlabMemoryPool.cs │ │ │ │ ├── SocketAwaitableEventArgs.cs │ │ │ │ ├── SocketConnection.cs │ │ │ │ ├── SocketConnectionFactory.cs │ │ │ │ ├── SocketConnectionListener.cs │ │ │ │ ├── SocketConnectionListenerFactory.cs │ │ │ │ ├── SocketConnectionOptions.cs │ │ │ │ ├── SocketExtensions.cs │ │ │ │ ├── SocketReceiver.cs │ │ │ │ ├── SocketSchedulers.cs │ │ │ │ ├── SocketSender.cs │ │ │ │ ├── SocketSenderReceiverBase.cs │ │ │ │ ├── SocketsTrace.cs │ │ │ │ ├── TransportConnection.Features.cs │ │ │ │ └── TransportConnection.cs │ │ │ └── SocketDirection.cs │ │ ├── Orleans.Core.csproj │ │ ├── Placement/ │ │ │ ├── IPlacementContext.cs │ │ │ ├── IPlacementDirector.cs │ │ │ ├── IPlacementFilterDirector.cs │ │ │ ├── PlacementFilterExtensions.cs │ │ │ ├── PlacementTarget.cs │ │ │ ├── Rebalancing/ │ │ │ │ ├── IActivationRebalancer.cs │ │ │ │ ├── IActivationRebalancerMonitor.cs │ │ │ │ ├── IActivationRebalancerReportListener.cs │ │ │ │ ├── IActivationRebalancerWorker.cs │ │ │ │ ├── IFailedSessionBackoffProvider.cs │ │ │ │ └── RebalancingReport.cs │ │ │ └── Repartitioning/ │ │ │ ├── IActivationRepartitionerSystemTarget.cs │ │ │ ├── IImbalanceToleranceRule.cs │ │ │ └── IMessageStatisticsSink.cs │ │ ├── Providers/ │ │ │ ├── ClientProviderRuntime.cs │ │ │ ├── GrainStorageHelpers.cs │ │ │ ├── IControllable.cs │ │ │ ├── IGrainStorage.cs │ │ │ ├── IGrainStorageSerializer.cs │ │ │ ├── ILeaseProvider.cs │ │ │ ├── IMemoryStorageGrain.cs │ │ │ ├── IProviderRuntime.cs │ │ │ ├── IStorageProvider.cs │ │ │ ├── ProviderInitializationException.cs │ │ │ ├── ProviderStateManager.cs │ │ │ └── StorageSerializer/ │ │ │ ├── GrainStorageSerializer.cs │ │ │ ├── JsonGrainStorageSerializer.cs │ │ │ └── OrleansGrainStateSerializer.cs │ │ ├── README.md │ │ ├── Runtime/ │ │ │ ├── AsyncEnumerableGrainExtension.cs │ │ │ ├── CallbackData.cs │ │ │ ├── ClientGrainContext.cs │ │ │ ├── ClientLocalActivationStatusChecker.cs │ │ │ ├── ClusterConnectionStatusObserverAdaptor.cs │ │ │ ├── Constants.cs │ │ │ ├── GrainCancellationTokenRuntime.cs │ │ │ ├── GrainReferenceRuntime.cs │ │ │ ├── IHealthCheckable.cs │ │ │ ├── ILocalSiloDetails.cs │ │ │ ├── IRuntimeClient.cs │ │ │ ├── InvokableObjectManager.cs │ │ │ ├── LocalClientDetails.cs │ │ │ ├── MembershipTableSnapshot.cs │ │ │ ├── OutgoingCallInvoker.cs │ │ │ ├── OutsideRuntimeClient.cs │ │ │ ├── RequestContextExtensions.cs │ │ │ ├── RingRange.cs │ │ │ ├── RuntimeVersion.cs │ │ │ ├── SharedCallbackData.cs │ │ │ └── SiloStatus.cs │ │ ├── Serialization/ │ │ │ ├── OrleansJsonSerializationBinder.cs │ │ │ ├── OrleansJsonSerializer.cs │ │ │ ├── OrleansJsonSerializerOptions.cs │ │ │ └── OrleansJsonSerializerSettings.cs │ │ ├── Statistics/ │ │ │ ├── EnvironmentStatisticsProvider.cs │ │ │ ├── GrainCountStatistics.cs │ │ │ ├── GrainMetricsListener.cs │ │ │ ├── OldEnvironmentStatistics.cs │ │ │ ├── SiloRuntimeMetricsListener.cs │ │ │ └── SiloRuntimeStatistics.cs │ │ ├── SystemTargetInterfaces/ │ │ │ ├── IDeploymentLoadPublisher.cs │ │ │ ├── IManagementGrain.cs │ │ │ ├── IMembershipService.cs │ │ │ ├── IMembershipTable.cs │ │ │ └── ISiloControl.cs │ │ ├── Threading/ │ │ │ └── RecursiveInterlockedExchangeLock.cs │ │ ├── Timers/ │ │ │ ├── CoarseStopwatch.cs │ │ │ ├── NonCapturingTimer.cs │ │ │ ├── TimerManager.cs │ │ │ └── ValueStopwatch.cs │ │ ├── Utils/ │ │ │ ├── AsyncEnumerable.cs │ │ │ ├── ExecutionContextSuppressor.cs │ │ │ ├── Factory.cs │ │ │ ├── NamedOptionExtension.cs │ │ │ ├── ObserverManager.cs │ │ │ ├── RandomTimeSpan.cs │ │ │ ├── ReferenceEqualsComparer.cs │ │ │ ├── SetExtensions.cs │ │ │ ├── StandardExtensions.cs │ │ │ └── TypeConverterExtensions.cs │ │ ├── build/ │ │ │ └── Microsoft.Orleans.Core.targets │ │ ├── buildMultiTargeting/ │ │ │ └── Microsoft.Orleans.Core.targets │ │ └── buildTransitive/ │ │ └── Microsoft.Orleans.Core.targets │ ├── Orleans.Core.Abstractions/ │ │ ├── Cancellation/ │ │ │ ├── GrainCancellationToken.cs │ │ │ ├── GrainCancellationTokenSource.cs │ │ │ └── ICancellationSourcesExtension.cs │ │ ├── CodeGeneration/ │ │ │ ├── IOnDeserialized.cs │ │ │ ├── InvokeMethodOptions.cs │ │ │ └── VersionAttribute.cs │ │ ├── Concurrency/ │ │ │ └── GrainAttributeConcurrency.cs │ │ ├── Core/ │ │ │ ├── DeactivationReason.cs │ │ │ ├── Grain.cs │ │ │ ├── GrainExtensions.cs │ │ │ ├── IConfigurationValidator.cs │ │ │ ├── IGrain.cs │ │ │ ├── IGrainBase.cs │ │ │ ├── IGrainCallContext.cs │ │ │ ├── IGrainCallFilter.cs │ │ │ ├── IGrainContext.cs │ │ │ ├── IGrainFactory.cs │ │ │ ├── IGrainObserver.cs │ │ │ ├── ILocalActivationStatusChecker.cs │ │ │ ├── IStorage.cs │ │ │ ├── Immutable.cs │ │ │ └── Internal/ │ │ │ ├── ICallChainReentrantGrainContext.cs │ │ │ └── IGrainManagementExtension.cs │ │ ├── Diagnostics/ │ │ │ ├── ActivitySources.cs │ │ │ ├── ActivityTagKeys.cs │ │ │ └── OpenTelemetryHeaders.cs │ │ ├── Exceptions/ │ │ │ ├── ClientNotAvailableException.cs │ │ │ ├── GatewayTooBusyException.cs │ │ │ ├── GrainExtensionNotInstalledException.cs │ │ │ ├── LimitExceededException.cs │ │ │ ├── OrleansConfigurationException.cs │ │ │ ├── OrleansException.cs │ │ │ ├── OrleansLifecycleCanceledException.cs │ │ │ ├── OrleansMessageRejectionException.cs │ │ │ ├── SiloUnavailableException.cs │ │ │ └── WrappedException.cs │ │ ├── GrainDirectory/ │ │ │ ├── GrainDirectoryAttribute.cs │ │ │ └── IGrainDirectory.cs │ │ ├── IDs/ │ │ │ ├── ActivationId.cs │ │ │ ├── ClientGrainId.cs │ │ │ ├── GrainAddress.cs │ │ │ ├── GrainAddressCacheUpdate.cs │ │ │ ├── GrainId.cs │ │ │ ├── GrainIdKeyExtensions.cs │ │ │ ├── GrainInterfaceType.cs │ │ │ ├── GrainType.cs │ │ │ ├── GrainTypePrefix.cs │ │ │ ├── GuidId.cs │ │ │ ├── IdSpan.cs │ │ │ ├── IdSpanCodec.cs │ │ │ ├── Legacy/ │ │ │ │ ├── LegacyGrainId.cs │ │ │ │ └── UniqueKey.cs │ │ │ ├── ObserverGrainId.cs │ │ │ ├── SiloAddress.cs │ │ │ ├── SiloAddressCodec.cs │ │ │ ├── StableHash.cs │ │ │ └── SystemTargetGrainId.cs │ │ ├── Lifecycle/ │ │ │ ├── IGrainLifecycle.cs │ │ │ ├── ILifecycleObservable.cs │ │ │ ├── ILifecycleObserver.cs │ │ │ ├── ILifecycleParticipant.cs │ │ │ ├── ILifecycleSubject.cs │ │ │ └── LifecycleExtensions.cs │ │ ├── Logging/ │ │ │ ├── ErrorCodes.cs │ │ │ └── LogFormatter.cs │ │ ├── Manifest/ │ │ │ ├── ClusterManifest.cs │ │ │ ├── GrainInterfaceProperties.cs │ │ │ ├── GrainManifest.cs │ │ │ ├── GrainProperties.cs │ │ │ ├── IGrainTypeProvider.cs │ │ │ └── MajorMinorVersion.cs │ │ ├── Orleans.Core.Abstractions.csproj │ │ ├── Placement/ │ │ │ ├── ActivationCountBasedPlacement.cs │ │ │ ├── ClientObserversPlacement.cs │ │ │ ├── HashBasedPlacement.cs │ │ │ ├── PlacementAttribute.cs │ │ │ ├── PlacementFilterAttribute.cs │ │ │ ├── PlacementFilterStrategy.cs │ │ │ ├── PlacementStrategy.cs │ │ │ ├── PreferLocalPlacement.cs │ │ │ ├── RandomPlacement.cs │ │ │ ├── ResourceOptimizedPlacement.cs │ │ │ ├── SiloRoleBasedPlacement.cs │ │ │ ├── StatelessWorkerPlacement.cs │ │ │ └── SystemTargetPlacementStrategy.cs │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ ├── Providers/ │ │ │ ├── IProviderBuilder.cs │ │ │ ├── ProviderConstants.cs │ │ │ └── ProviderGrainAttributes.cs │ │ ├── README.md │ │ ├── Runtime/ │ │ │ ├── AsyncEnumerableRequest.cs │ │ │ ├── GrainContextComponentExtensions.cs │ │ │ ├── GrainLifecycleStage.cs │ │ │ ├── GrainReference.cs │ │ │ ├── GrainReferenceNotBoundException.cs │ │ │ ├── IAddressable.cs │ │ │ ├── IGrainCancellationTokenRuntime.cs │ │ │ ├── IGrainExtension.cs │ │ │ ├── IGrainReferenceRuntime.cs │ │ │ ├── IGrainRuntime.cs │ │ │ ├── IGrainTimer.cs │ │ │ ├── MembershipVersion.cs │ │ │ ├── RequestContext.cs │ │ │ └── RuntimeContext.cs │ │ ├── Services/ │ │ │ ├── IGrainService.cs │ │ │ └── IGrainServiceClient.cs │ │ ├── Statistics/ │ │ │ ├── EnvironmentStatisticExtensions.cs │ │ │ ├── IAppEnvironmentStatistics.cs │ │ │ ├── IEnvironmentStatisticsProvider.cs │ │ │ └── IHostEnvironmentStatistics.cs │ │ ├── SystemTargetInterfaces/ │ │ │ ├── ISystemTarget.cs │ │ │ ├── ISystemTargetBase.cs │ │ │ └── IVersionManager.cs │ │ ├── Timers/ │ │ │ ├── GrainTimerCreationOptions.cs │ │ │ └── ITimerRegistry.cs │ │ ├── Utils/ │ │ │ ├── Interner.cs │ │ │ ├── PublicOrleansTaskExtensions.cs │ │ │ ├── SpanFormattableIPEndPoint.cs │ │ │ └── Utils.cs │ │ ├── Versions/ │ │ │ ├── Compatibility/ │ │ │ │ ├── AllVersionsCompatible.cs │ │ │ │ ├── BackwardCompatible.cs │ │ │ │ ├── ICompatibilityDirector.cs │ │ │ │ └── StrictVersionCompatible.cs │ │ │ ├── IVersionStore.cs │ │ │ └── Selector/ │ │ │ ├── AllCompatibleVersions.cs │ │ │ ├── IVersionSelector.cs │ │ │ ├── LatestVersion.cs │ │ │ └── MinimumVersion.cs │ │ ├── build/ │ │ │ └── Microsoft.Orleans.Core.Abstractions.targets │ │ ├── buildMultiTargeting/ │ │ │ └── Microsoft.Orleans.Core.Abstractions.targets │ │ └── buildTransitive/ │ │ └── Microsoft.Orleans.Core.Abstractions.targets │ ├── Orleans.DurableJobs/ │ │ ├── DurableJob.cs │ │ ├── DurableJobRunResult.cs │ │ ├── Hosting/ │ │ │ ├── DurableJobsExtensions.cs │ │ │ └── DurableJobsOptions.cs │ │ ├── IDurableJobHandler.cs │ │ ├── IDurableJobReceiverExtension.cs │ │ ├── ILocalDurableJobManager.cs │ │ ├── InMemoryJobQueue.cs │ │ ├── InMemoryJobShard.cs │ │ ├── JobShard.cs │ │ ├── JobShardManager.cs │ │ ├── LocalDurableJobManager.Log.cs │ │ ├── LocalDurableJobManager.cs │ │ ├── Orleans.DurableJobs.csproj │ │ ├── README.md │ │ ├── ScheduleJobRequest.cs │ │ ├── ShardExecutor.Log.cs │ │ └── ShardExecutor.cs │ ├── Orleans.EventSourcing/ │ │ ├── Common/ │ │ │ ├── ConnectionIssues.cs │ │ │ ├── NotificationMessage.cs │ │ │ ├── PrimaryBasedLogViewAdaptor.cs │ │ │ ├── RecordedConnectionIssue.cs │ │ │ └── StringEncodedWriteVector.cs │ │ ├── CustomStorage/ │ │ │ ├── CustomStorageLogConsistencyOptions.cs │ │ │ ├── ICustomStorageInterface.cs │ │ │ ├── LogConsistencyProvider.cs │ │ │ └── LogViewAdaptor.cs │ │ ├── Hosting/ │ │ │ ├── CustomStorageSiloBuilderExtensions.cs │ │ │ ├── LogConsistencyProtocolSiloBuilderExtensions.cs │ │ │ ├── LogStorageSiloBuilderExtensions.cs │ │ │ └── StateStorageSiloBuilderExtensions.cs │ │ ├── JournaledGrain.cs │ │ ├── LogConsistency/ │ │ │ ├── ConnectionIssues.cs │ │ │ ├── IConnectionIssueListener.cs │ │ │ ├── ILogConsistencyDiagnostics.cs │ │ │ ├── ILogConsistencyProtocolGateway.cs │ │ │ ├── ILogConsistencyProtocolServices.cs │ │ │ ├── ILogViewAdaptor.cs │ │ │ ├── ILogViewAdaptorFactory.cs │ │ │ ├── ILogViewAdaptorHost.cs │ │ │ ├── IProtocolParticipant.cs │ │ │ ├── LogConsistentGrain.cs │ │ │ └── ProtocolServices.cs │ │ ├── LogStorage/ │ │ │ ├── DefaultAdaptorFactory.cs │ │ │ ├── LogConsistencyProvider.cs │ │ │ ├── LogStateWithMetaData.cs │ │ │ └── LogViewAdaptor.cs │ │ ├── Orleans.EventSourcing.csproj │ │ ├── README.md │ │ └── StateStorage/ │ │ ├── DefaultAdaptorFactory.cs │ │ ├── GrainStateWithMetaData.cs │ │ ├── LogConsistencyProvider.cs │ │ └── LogViewAdaptor.cs │ ├── Orleans.Hosting.Kubernetes/ │ │ ├── ConfigureKubernetesHostingOptions.cs │ │ ├── KubernetesClusterAgent.cs │ │ ├── KubernetesHostingExtensions.cs │ │ ├── KubernetesHostingOptions.cs │ │ ├── KubernetesHostingOptionsValidator.cs │ │ └── Orleans.Hosting.Kubernetes.csproj │ ├── Orleans.Journaling/ │ │ ├── DurableDictionary.cs │ │ ├── DurableGrain.cs │ │ ├── DurableList.cs │ │ ├── DurableNothing.cs │ │ ├── DurableQueue.cs │ │ ├── DurableSet.cs │ │ ├── DurableState.cs │ │ ├── DurableTaskCompletionSource.cs │ │ ├── DurableValue.cs │ │ ├── HostingExtensions.cs │ │ ├── IDurableStateMachine.cs │ │ ├── IStateMachineLogWriter.cs │ │ ├── IStateMachineManager.cs │ │ ├── IStateMachineStorage.cs │ │ ├── IStateMachineStorageProvider.cs │ │ ├── LogExtent.cs │ │ ├── LogExtentBuilder.ReadOnlyStream.cs │ │ ├── LogExtentBuilder.cs │ │ ├── Orleans.Journaling.csproj │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── StateMachineId.cs │ │ ├── StateMachineManager.cs │ │ ├── StateMachineManagerOptions.cs │ │ ├── StateMachineStorageWriter.cs │ │ └── VolatileStateMachineStorage.cs │ ├── Orleans.Persistence.Memory/ │ │ ├── Hosting/ │ │ │ ├── MemoryGrainStorageProviderBuilder.cs │ │ │ └── MemoryGrainStorageSiloBuilderExtensions.cs │ │ ├── Options/ │ │ │ └── MemoryGrainStorageOptions.cs │ │ ├── Orleans.Persistence.Memory.csproj │ │ └── Storage/ │ │ ├── MemoryStorage.cs │ │ ├── MemoryStorageEtagMismatchException.cs │ │ ├── MemoryStorageGrain.cs │ │ └── MemoryStorageWithLatency.cs │ ├── Orleans.Reminders/ │ │ ├── Constants/ │ │ │ ├── ReminderOptionsDefaults.cs │ │ │ └── RemindersConstants.cs │ │ ├── ErrorCodes.cs │ │ ├── GrainReminderExtensions.cs │ │ ├── Hosting/ │ │ │ ├── MemoryReminderTableBuilder.cs │ │ │ ├── SiloBuilderReminderExtensions.cs │ │ │ └── SiloBuilderReminderMemoryExtensions.cs │ │ ├── Options/ │ │ │ └── ReminderOptions.cs │ │ ├── Orleans.Reminders.csproj │ │ ├── ReminderService/ │ │ │ ├── GrainBasedReminderTable.cs │ │ │ ├── InMemoryReminderTable.cs │ │ │ ├── LocalReminderService.cs │ │ │ └── ReminderRegistry.cs │ │ ├── SystemTargetInterfaces/ │ │ │ ├── IReminderService.cs │ │ │ └── IReminderTable.cs │ │ └── Timers/ │ │ ├── IRemindable.cs │ │ └── IReminderRegistry.cs │ ├── Orleans.Reminders.Abstractions/ │ │ └── Orleans.Reminders.Abstractions.csproj │ ├── Orleans.Runtime/ │ │ ├── Activation/ │ │ │ ├── ActivationDataActivatorProvider.cs │ │ │ ├── ConfigureDefaultGrainActivator.cs │ │ │ ├── DefaultGrainActivator.cs │ │ │ ├── GrainContextAccessor.cs │ │ │ ├── IGrainActivator.cs │ │ │ └── IGrainContextActivator.cs │ │ ├── Cancellation/ │ │ │ ├── CancellationSourcesExtension.cs │ │ │ └── GrainCallCancellationManager.cs │ │ ├── Catalog/ │ │ │ ├── ActivationCollector.cs │ │ │ ├── ActivationData.cs │ │ │ ├── ActivationDirectory.cs │ │ │ ├── ActivationMigrationManager.cs │ │ │ ├── ActivationState.cs │ │ │ ├── ActivationWorkingSet.cs │ │ │ ├── Catalog.cs │ │ │ ├── GrainLifecycle.cs │ │ │ ├── GrainTypeSharedContext.cs │ │ │ ├── IActivationCollector.cs │ │ │ ├── ICatalog.cs │ │ │ ├── IncomingRequestMonitor.cs │ │ │ ├── LocalActivationStatusChecker.cs │ │ │ ├── StatelessWorkerGrainContext.cs │ │ │ ├── StreamResourceTestControl.cs │ │ │ └── SystemTargetShared.cs │ │ ├── Configuration/ │ │ │ ├── Options/ │ │ │ │ ├── ActivationCountBasedPlacementOptions.cs │ │ │ │ ├── ActivationRebalancerOptions.cs │ │ │ │ ├── ActivationRepartitionerOptions.cs │ │ │ │ ├── ConsistentRingOptions.cs │ │ │ │ ├── DeploymentLoadPublisherOptions.cs │ │ │ │ ├── GrainCollectionOptions.cs │ │ │ │ ├── GrainDirectoryOptions.cs │ │ │ │ ├── OptionsLogger.cs │ │ │ │ ├── ResourceOptimizedPlacementOptions.cs │ │ │ │ ├── SchedulingOptions.cs │ │ │ │ ├── SiloMessagingOptions.cs │ │ │ │ ├── SiloMessagingOptionsValidator.cs │ │ │ │ └── StatelessWorkerOptions.cs │ │ │ ├── SiloConnectionOptions.cs │ │ │ └── Validators/ │ │ │ ├── GrainCollectionOptionsValidator.cs │ │ │ └── SiloClusteringValidator.cs │ │ ├── ConsistentRing/ │ │ │ ├── ConsistentRingProvider.cs │ │ │ ├── IConsistentRingProvider.cs │ │ │ ├── SimpleConsistentRingProvider.cs │ │ │ └── VirtualBucketsRingProvider.cs │ │ ├── Core/ │ │ │ ├── FatalErrorHandler.cs │ │ │ ├── GrainRuntime.cs │ │ │ ├── HostedClient.cs │ │ │ ├── IFatalErrorHandler.cs │ │ │ ├── IHealthCheckParticipant.cs │ │ │ ├── InsideRuntimeClient.cs │ │ │ ├── InternalClusterClient.cs │ │ │ ├── InternalGrainRuntime.cs │ │ │ ├── ManagementGrain.cs │ │ │ ├── SystemStatus.cs │ │ │ └── SystemTarget.cs │ │ ├── Development/ │ │ │ ├── DevelopmentSiloBuilderExtensions.cs │ │ │ └── InMemoryLeaseProvider.cs │ │ ├── Facet/ │ │ │ ├── GrainConstructorArgumentFactory.cs │ │ │ ├── IAttributeToFactoryMapper.cs │ │ │ ├── IFacetMetadata.cs │ │ │ └── Persistent/ │ │ │ ├── IPersistentState.cs │ │ │ ├── IPersistentStateConfiguration.cs │ │ │ ├── IPersistentStateFactory.cs │ │ │ ├── PersistentStateAttribute.cs │ │ │ ├── PersistentStateAttributeMapper.cs │ │ │ └── PersistentStateStorageFactory.cs │ │ ├── GrainDirectory/ │ │ │ ├── CachedGrainLocator.cs │ │ │ ├── ClientDirectory.cs │ │ │ ├── ClientGrainLocator.cs │ │ │ ├── DhtGrainLocator.cs │ │ │ ├── DirectoryMembershipService.cs │ │ │ ├── DirectoryMembershipSnapshot.cs │ │ │ ├── DirectoryResult.cs │ │ │ ├── DistributedGrainDirectory.cs │ │ │ ├── GenericGrainDirectoryResolver.cs │ │ │ ├── GrainDirectoryCacheFactory.cs │ │ │ ├── GrainDirectoryHandoffManager.cs │ │ │ ├── GrainDirectoryPartition.Interface.cs │ │ │ ├── GrainDirectoryPartition.cs │ │ │ ├── GrainDirectoryPartitionSnapshot.cs │ │ │ ├── GrainDirectoryResolver.cs │ │ │ ├── GrainLocator.cs │ │ │ ├── GrainLocatorResolver.cs │ │ │ ├── IGrainDirectoryCache.cs │ │ │ ├── IGrainDirectoryPartition.cs │ │ │ ├── IGrainDirectoryResolver.cs │ │ │ ├── ILocalClientDirectory.cs │ │ │ ├── ILocalGrainDirectory.cs │ │ │ ├── IRemoteClientDirectory.cs │ │ │ ├── IRemoteGrainDirectory.cs │ │ │ ├── LocalGrainDirectory.cs │ │ │ ├── LocalGrainDirectoryPartition.cs │ │ │ ├── LruGrainDirectoryCache.cs │ │ │ ├── RemoteGrainDirectory.cs │ │ │ ├── RingRange.cs │ │ │ └── RingRangeCollection.cs │ │ ├── GrainTypeManager/ │ │ │ ├── ClusterManifestSystemTarget.cs │ │ │ └── ISiloManifestSystemTarget.cs │ │ ├── Hosting/ │ │ │ ├── ActivationRebalancerExtensions.cs │ │ │ ├── ActivationRepartitioningExtensions.cs │ │ │ ├── CoreHostingExtensions.cs │ │ │ ├── DefaultSiloServices.cs │ │ │ ├── DirectorySiloBuilderExtensions.cs │ │ │ ├── EndpointOptions.cs │ │ │ ├── EndpointOptionsExtensions.cs │ │ │ ├── EndpointOptionsProvider.cs │ │ │ ├── GrainCallFilterExtensions.cs │ │ │ ├── HostingGrainExtensions.cs │ │ │ ├── ISiloBuilder.cs │ │ │ ├── NamedService.cs │ │ │ ├── OrleansSiloGenericHostExtensions.cs │ │ │ ├── PlacementStrategyExtensions.cs │ │ │ ├── ProviderConfiguration/ │ │ │ │ └── DevelopmentClusteringProvider.cs │ │ │ ├── SiloBuilder.cs │ │ │ ├── SiloBuilderExtensions.cs │ │ │ ├── SiloBuilderStartupExtensions.cs │ │ │ ├── SiloHostedService.cs │ │ │ └── StorageProviderHostExtensions.cs │ │ ├── Lifecycle/ │ │ │ ├── ISiloLifecycle.cs │ │ │ ├── ISiloLifecycleSubject.cs │ │ │ ├── IStartupTask.cs │ │ │ └── SiloLifecycleSubject.cs │ │ ├── Manifest/ │ │ │ ├── ClusterManifestProvider.cs │ │ │ ├── GrainClassMap.cs │ │ │ └── SiloManifestProvider.cs │ │ ├── MembershipService/ │ │ │ ├── ClusterHealthMonitor.cs │ │ │ ├── ClusterMember.cs │ │ │ ├── ClusterMembershipService.cs │ │ │ ├── ClusterMembershipSnapshot.cs │ │ │ ├── ClusterMembershipUpdate.cs │ │ │ ├── DevelopmentClusterMembershipOptions.cs │ │ │ ├── DevelopmentClusterMembershipOptionsValidator.cs │ │ │ ├── IClusterMembershipService.cs │ │ │ ├── IMembershipGossiper.cs │ │ │ ├── IRemoteSiloProber.cs │ │ │ ├── ISiloStatusListener.cs │ │ │ ├── ISiloStatusOracle.cs │ │ │ ├── InMemoryMembershipTable.cs │ │ │ ├── LocalSiloHealthMonitor.cs │ │ │ ├── MembershipAgent.cs │ │ │ ├── MembershipGossiper.cs │ │ │ ├── MembershipSystemTarget.cs │ │ │ ├── MembershipTableCleanupAgent.cs │ │ │ ├── MembershipTableEntryExtensions.cs │ │ │ ├── MembershipTableManager.cs │ │ │ ├── MembershipTableSnapshotExtensions.cs │ │ │ ├── OrleansClusterConnectivityCheckFailedException.cs │ │ │ ├── OrleansMissingMembershipEntryException.cs │ │ │ ├── RemoteSiloProber.cs │ │ │ ├── SiloHealthMonitor.cs │ │ │ ├── SiloMetadata/ │ │ │ │ ├── ISiloMetadataCache.cs │ │ │ │ ├── ISiloMetadataClient.cs │ │ │ │ ├── ISiloMetadataSystemTarget.cs │ │ │ │ ├── SiloMetadaCache.cs │ │ │ │ ├── SiloMetadata.cs │ │ │ │ ├── SiloMetadataClient.cs │ │ │ │ ├── SiloMetadataHostingExtensions.cs │ │ │ │ └── SiloMetadataSystemTarget.cs │ │ │ ├── SiloStatusListenerManager.cs │ │ │ ├── SiloStatusOracle.cs │ │ │ └── SystemTargetBasedMembershipTable.cs │ │ ├── Messaging/ │ │ │ ├── Gateway.cs │ │ │ ├── IConnectedClientCollection.cs │ │ │ ├── MessageCenter.cs │ │ │ ├── OverloadDetector.cs │ │ │ └── RuntimeMessagingTrace.cs │ │ ├── Networking/ │ │ │ ├── ConnectionListener.cs │ │ │ ├── GatewayConnectionListener.cs │ │ │ ├── GatewayInboundConnection.cs │ │ │ ├── ProbeRequestMonitor.cs │ │ │ ├── SiloConnection.cs │ │ │ ├── SiloConnectionFactory.cs │ │ │ ├── SiloConnectionListener.cs │ │ │ └── SiloConnectionMaintainer.cs │ │ ├── Orleans.Runtime.csproj │ │ ├── Placement/ │ │ │ ├── ActivationCountPlacementDirector.cs │ │ │ ├── ClientObserverPlacementStrategyResolver.cs │ │ │ ├── ClientObserversPlacementDirector.cs │ │ │ ├── DeploymentLoadPublisher.cs │ │ │ ├── Filtering/ │ │ │ │ ├── PlacementFilterDirectorResolver.cs │ │ │ │ ├── PlacementFilterStrategyResolver.cs │ │ │ │ ├── PreferredMatchSiloMetadataPlacementFilterAttribute.cs │ │ │ │ ├── PreferredMatchSiloMetadataPlacementFilterDirector.cs │ │ │ │ ├── PreferredMatchSiloMetadataPlacementFilterStrategy.cs │ │ │ │ ├── RequiredMatchSiloMetadataPlacementFilterAttribute.cs │ │ │ │ ├── RequiredMatchSiloMetadataPlacementFilterDirector.cs │ │ │ │ └── RequiredMatchSiloMetadataPlacementFilterStrategy.cs │ │ │ ├── GrainMigratabilityChecker.cs │ │ │ ├── HashBasedPlacementDirector.cs │ │ │ ├── IPlacementStrategyResolver.cs │ │ │ ├── ISiloStatisticsChangeListener.cs │ │ │ ├── PlacementDirectorResolver.cs │ │ │ ├── PlacementService.cs │ │ │ ├── PlacementStrategyResolver.cs │ │ │ ├── PreferLocalPlacementDirector.cs │ │ │ ├── RandomPlacementDirector.cs │ │ │ ├── Rebalancing/ │ │ │ │ ├── ActivationRebalancerMonitor.cs │ │ │ │ ├── ActivationRebalancerWorker.Log.cs │ │ │ │ ├── ActivationRebalancerWorker.cs │ │ │ │ └── FailedSessionBackoffProvider.cs │ │ │ ├── Repartitioning/ │ │ │ │ ├── ActivationRepartitioner.Log.cs │ │ │ │ ├── ActivationRepartitioner.MessageSink.cs │ │ │ │ ├── ActivationRepartitioner.cs │ │ │ │ ├── BlockedBloomFilter.cs │ │ │ │ ├── FrequentItemCollection.cs │ │ │ │ ├── MaxHeap.cs │ │ │ │ ├── RebalancerCompatibleRule.cs │ │ │ │ └── RepartitionerMessageFilter.cs │ │ │ ├── ResourceOptimizedPlacementDirector.cs │ │ │ ├── SiloRoleBasedPlacementDirector.cs │ │ │ └── StatelessWorkerDirector.cs │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ ├── README.md │ │ ├── Scheduler/ │ │ │ ├── ActivationTaskScheduler.cs │ │ │ ├── ClosureWorkItem.cs │ │ │ ├── IWorkItem.cs │ │ │ ├── SchedulerExtensions.cs │ │ │ ├── TaskSchedulerUtils.cs │ │ │ ├── WorkItemBase.cs │ │ │ └── WorkItemGroup.cs │ │ ├── Services/ │ │ │ ├── GrainService.cs │ │ │ ├── GrainServiceClient.cs │ │ │ ├── GrainServiceFactory.cs │ │ │ └── GrainServicesSiloBuilderExtensions.cs │ │ ├── Silo/ │ │ │ ├── LocalSiloDetails.cs │ │ │ ├── Silo.cs │ │ │ ├── SiloControl.cs │ │ │ ├── SiloOptions.cs │ │ │ ├── SiloProviderRuntime.cs │ │ │ ├── TestHooks/ │ │ │ │ ├── ITestHooksSystemTarget.cs │ │ │ │ └── TestHooksSystemTarget.cs │ │ │ └── Watchdog.cs │ │ ├── Storage/ │ │ │ └── StateStorageBridge.cs │ │ ├── Timers/ │ │ │ ├── AsyncTimer.cs │ │ │ ├── AsyncTimerFactory.cs │ │ │ ├── GrainTimer.cs │ │ │ ├── IAsyncTimer.cs │ │ │ ├── IAsyncTimerFactory.cs │ │ │ ├── IGrainTimerRegistry.cs │ │ │ └── TimerRegistry.cs │ │ ├── Utilities/ │ │ │ ├── FactoryUtility.cs │ │ │ ├── OrleansDebuggerHelper.cs │ │ │ ├── SearchAlgorithms.cs │ │ │ └── StripedMpscBuffer.cs │ │ └── Versions/ │ │ ├── CachedVersionSelectorManager.cs │ │ ├── Compatibility/ │ │ │ ├── AllVersionsCompatibilityDirector.cs │ │ │ ├── BackwardCompatilityDirector.cs │ │ │ ├── CompatibilityDirectorManager.cs │ │ │ └── StrictVersionCompatibilityDirector.cs │ │ ├── GrainVersionStore.cs │ │ ├── Selector/ │ │ │ ├── AllCompatibleVersionsSelector.cs │ │ │ ├── LatestVersionDirector.cs │ │ │ ├── MinimumVersionSelector.cs │ │ │ └── VersionDirectorManager.cs │ │ ├── SingleWaiterAutoResetEvent.cs │ │ └── VersionStoreGrain.cs │ ├── Orleans.Sdk/ │ │ ├── Orleans.Sdk.csproj │ │ ├── README.md │ │ ├── build/ │ │ │ └── Microsoft.Orleans.Sdk.targets │ │ ├── buildMultiTargeting/ │ │ │ └── Microsoft.Orleans.Sdk.targets │ │ └── buildTransitive/ │ │ └── Microsoft.Orleans.Sdk.targets │ ├── Orleans.Serialization/ │ │ ├── Activators/ │ │ │ ├── DefaultActivator.cs │ │ │ └── IActivator.cs │ │ ├── Buffers/ │ │ │ ├── Adaptors/ │ │ │ │ ├── ArrayStreamBufferWriter.cs │ │ │ │ ├── BufferSliceReaderInput.cs │ │ │ │ ├── BufferWriterBox.cs │ │ │ │ ├── BufferWriterExtensions.cs │ │ │ │ ├── MemoryBufferWriter.cs │ │ │ │ ├── MemoryStreamBufferWriter.cs │ │ │ │ ├── PooledBufferStream.cs │ │ │ │ ├── PoolingStreamBufferWriter.cs │ │ │ │ └── SpanBufferWriter.cs │ │ │ ├── ArcBufferWriter.cs │ │ │ ├── BufferWriterExtensions.cs │ │ │ ├── PooledBuffer.cs │ │ │ ├── Reader.cs │ │ │ ├── Writer.FieldHeader.cs │ │ │ ├── Writer.TagDelimitedField.cs │ │ │ ├── Writer.VarInt.cs │ │ │ └── Writer.cs │ │ ├── Cloning/ │ │ │ └── IDeepCopier.cs │ │ ├── Codecs/ │ │ │ ├── ArrayCodec.cs │ │ │ ├── ArrayListCodec.cs │ │ │ ├── BigIntegerCodec.cs │ │ │ ├── BitVector32Codec.cs │ │ │ ├── ByteArrayCodec.cs │ │ │ ├── CollectionCodec.cs │ │ │ ├── CommonCodecTypeFilter.cs │ │ │ ├── CompareInfoCodec.cs │ │ │ ├── ConcurrentDictionaryCodec.cs │ │ │ ├── ConcurrentQueueCodec.cs │ │ │ ├── ConsumeFieldExtension.cs │ │ │ ├── CultureInfoCodec.cs │ │ │ ├── DateOnlyCodec.cs │ │ │ ├── DateTimeCodec.cs │ │ │ ├── DateTimeOffsetCodec.cs │ │ │ ├── DictionaryCodec.cs │ │ │ ├── Enum32BaseCodec.cs │ │ │ ├── FloatCodec.cs │ │ │ ├── GeneralizedReferenceTypeSurrogateCodec.cs │ │ │ ├── GeneralizedValueTypeSurrogateCodec.cs │ │ │ ├── GuidCodec.cs │ │ │ ├── HashSetCodec.cs │ │ │ ├── IFieldCodec.cs │ │ │ ├── IPAddressCodec.cs │ │ │ ├── IPEndPointCodec.cs │ │ │ ├── ImmutableArrayCodec.cs │ │ │ ├── ImmutableDictionaryCodec.cs │ │ │ ├── ImmutableHashSetCodec.cs │ │ │ ├── ImmutableListCodec.cs │ │ │ ├── ImmutableQueueCodec.cs │ │ │ ├── ImmutableSortedDictionaryCodec.cs │ │ │ ├── ImmutableSortedSetCodec.cs │ │ │ ├── ImmutableStackCodec.cs │ │ │ ├── IntegerCodec.cs │ │ │ ├── KeyValuePairCodec.cs │ │ │ ├── ListCodec.cs │ │ │ ├── MultiDimensionalArrayCodec.cs │ │ │ ├── NameValueCollectionCodec.cs │ │ │ ├── NullableCodec.cs │ │ │ ├── ObjectCodec.cs │ │ │ ├── QueueCodec.cs │ │ │ ├── ReadOnlyCollectionCodec.cs │ │ │ ├── ReadOnlyDictionaryCodec.cs │ │ │ ├── ReferenceCodec.cs │ │ │ ├── ReferenceTypeSurrogateCodec.cs │ │ │ ├── SkipFieldExtension.cs │ │ │ ├── SortedDictionaryCodec.cs │ │ │ ├── SortedListCodec.cs │ │ │ ├── SortedSetCodec.cs │ │ │ ├── StackCodec.cs │ │ │ ├── StringCodec.cs │ │ │ ├── TimeOnlyCodec.cs │ │ │ ├── TimeSpanCodec.cs │ │ │ ├── TupleCodec.cs │ │ │ ├── TypeSerializerCodec.cs │ │ │ ├── UnknownFieldMarker.cs │ │ │ ├── UriCodec.cs │ │ │ ├── ValueTupleCodec.cs │ │ │ ├── VersionCodec.cs │ │ │ ├── VoidCodec.cs │ │ │ └── WellKnownStringComparerCodec.cs │ │ ├── Configuration/ │ │ │ ├── DefaultTypeManifestProvider.cs │ │ │ ├── ITypeManifestProvider.cs │ │ │ ├── TypeManifestOptions.cs │ │ │ └── TypeManifestProviderAttribute.cs │ │ ├── Exceptions.cs │ │ ├── GeneratedCodeHelpers/ │ │ │ └── OrleansGeneratedCodeHelper.cs │ │ ├── Hosting/ │ │ │ ├── ISerializerBuilder.cs │ │ │ ├── ReferencedAssemblyProvider.cs │ │ │ ├── SerializerBuilderExtensions.cs │ │ │ ├── SerializerConfigurationAnalyzer.cs │ │ │ └── ServiceCollectionExtensions.cs │ │ ├── ISerializableSerializer/ │ │ │ ├── DotNetSerializableCodec.cs │ │ │ ├── ExceptionCodec.cs │ │ │ ├── ExceptionSerializationOptions.cs │ │ │ ├── SerializationCallbacksFactory.cs │ │ │ ├── SerializationConstructorFactory.cs │ │ │ ├── SerializationConstructorNotFoundException.cs │ │ │ ├── SerializationEntryCodec.cs │ │ │ ├── SerializationEntrySurrogate.cs │ │ │ ├── UnavailableExceptionFallbackException.cs │ │ │ ├── ValueTypeSerializer.cs │ │ │ └── ValueTypeSerializerFactory.cs │ │ ├── Invocation/ │ │ │ ├── IInvokable.cs │ │ │ ├── IResponseCompletionSource.cs │ │ │ ├── ITargetHolder.cs │ │ │ ├── Pools/ │ │ │ │ ├── ConcurrentObjectPool.cs │ │ │ │ ├── DefaultConcurrentObjectPoolPolicy.cs │ │ │ │ ├── InvokablePool.cs │ │ │ │ ├── ResponseCompletionSourcePool.cs │ │ │ │ └── ResponsePool.cs │ │ │ ├── Response.cs │ │ │ ├── ResponseCompletionSource.cs │ │ │ └── TargetHolderExtensions.cs │ │ ├── Orleans.Serialization.csproj │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ ├── README.md │ │ ├── Serializer.cs │ │ ├── Serializers/ │ │ │ ├── AbstractTypeSerializer.cs │ │ │ ├── CodecProvider.cs │ │ │ ├── ConcreteTypeSerializer.cs │ │ │ ├── IActivatorProvider.cs │ │ │ ├── IBaseCodec.cs │ │ │ ├── IBaseCodecProvider.cs │ │ │ ├── ICodecProvider.cs │ │ │ ├── ICodecSelector.cs │ │ │ ├── IFieldCodecProvider.cs │ │ │ ├── IGeneralizedCodec.cs │ │ │ ├── IValueSerializer.cs │ │ │ ├── IValueSerializerProvider.cs │ │ │ ├── SurrogateCodec.cs │ │ │ ├── ValueSerializer.cs │ │ │ └── ValueTypeSurrogateCodec.cs │ │ ├── Session/ │ │ │ ├── ReferencedObjectCollection.cs │ │ │ ├── ReferencedTypeCollection.cs │ │ │ ├── SerializerSession.cs │ │ │ ├── SerializerSessionPool.cs │ │ │ └── WellKnownTypeCollection.cs │ │ ├── TypeSystem/ │ │ │ ├── CachedTypeResolver.cs │ │ │ ├── CompoundTypeAliasTree.cs │ │ │ ├── DefaultTypeFilter.cs │ │ │ ├── ITypeConverter.cs │ │ │ ├── ITypeResolver.cs │ │ │ ├── QualifiedType.cs │ │ │ ├── RuntimeTypeNameFormatter.cs │ │ │ ├── RuntimeTypeNameParser.cs │ │ │ ├── RuntimeTypeNameRewriter.cs │ │ │ ├── TypeCodec.cs │ │ │ ├── TypeConverter.cs │ │ │ └── TypeSpec.cs │ │ ├── Utilities/ │ │ │ ├── BitOperations.cs │ │ │ ├── BitStreamFormatter.cs │ │ │ ├── FieldAccessor.cs │ │ │ ├── ReferenceEqualsComparer.cs │ │ │ ├── ServiceCollectionExtensions.cs │ │ │ └── VarIntReaderExtensions.cs │ │ └── WireProtocol/ │ │ ├── ExtendedWireType.cs │ │ ├── Field.cs │ │ ├── SchemaType.cs │ │ ├── Tag.cs │ │ └── WireType.cs │ ├── Orleans.Serialization.Abstractions/ │ │ ├── Annotations.cs │ │ ├── FrameworkPartAttribute.cs │ │ ├── GenerateFieldIds.cs │ │ ├── Orleans.Serialization.Abstractions.csproj │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ └── README.md │ ├── Orleans.Serialization.FSharp/ │ │ ├── FSharpCodecs.cs │ │ ├── Orleans.Serialization.FSharp.csproj │ │ └── README.md │ ├── Orleans.Serialization.MemoryPack/ │ │ ├── MemoryPackCodec.cs │ │ ├── MemoryPackCodecOptions.cs │ │ ├── Orleans.Serialization.MemoryPack.csproj │ │ ├── README.md │ │ └── SerializationHostingExtensions.cs │ ├── Orleans.Serialization.MessagePack/ │ │ ├── MessagePackCodec.cs │ │ ├── MessagePackCodecOptions.cs │ │ ├── Orleans.Serialization.MessagePack.csproj │ │ ├── README.md │ │ └── SerializationHostingExtensions.cs │ ├── Orleans.Serialization.NewtonsoftJson/ │ │ ├── NewtonsoftJsonCodec.cs │ │ ├── NewtonsoftJsonCodecOptions.cs │ │ ├── Orleans.Serialization.NewtonsoftJson.csproj │ │ ├── README.md │ │ └── SerializationHostingExtensions.cs │ ├── Orleans.Serialization.SystemTextJson/ │ │ ├── JsonCodec.cs │ │ ├── JsonCodecOptions.cs │ │ ├── Orleans.Serialization.SystemTextJson.csproj │ │ ├── README.md │ │ └── SerializationHostingExtensions.cs │ ├── Orleans.Serialization.TestKit/ │ │ ├── BufferTestHelper.cs │ │ ├── CopierTester.cs │ │ ├── FieldCodecTester.cs │ │ ├── IOutputBuffer.cs │ │ ├── Orleans.Serialization.TestKit.csproj │ │ ├── README.md │ │ ├── ReadOnlySequenceHelper.cs │ │ ├── TestBufferWriterStruct.cs │ │ ├── TestMultiSegmentBufferWriter.cs │ │ └── ValueTypeFieldCodecTester.cs │ ├── Orleans.Server/ │ │ ├── Orleans.Server.csproj │ │ └── README.md │ ├── Orleans.Streaming/ │ │ ├── Common/ │ │ │ ├── EventSequenceToken.cs │ │ │ ├── EventSequenceTokenV2.cs │ │ │ ├── Monitors/ │ │ │ │ ├── DefaultBlockPoolMonitor.cs │ │ │ │ ├── DefaultCacheMonitor.cs │ │ │ │ ├── DefaultQueueAdapterReceiverMonitor.cs │ │ │ │ ├── IBlockPoolMonitor.cs │ │ │ │ ├── ICacheMonitor.cs │ │ │ │ ├── IObjectPoolMonitor.cs │ │ │ │ ├── IQueueAdapterReceiverMonitor.cs │ │ │ │ └── MonitorAggregationDimensions.cs │ │ │ ├── PooledCache/ │ │ │ │ ├── CachedMessage.cs │ │ │ │ ├── CachedMessageBlock.cs │ │ │ │ ├── CachedMessagePool.cs │ │ │ │ ├── ChronologicalEvictionStrategy.cs │ │ │ │ ├── FixedSizeBuffer.cs │ │ │ │ ├── ICacheDataAdapter.cs │ │ │ │ ├── IEvictionStrategy.cs │ │ │ │ ├── IObjectPool.cs │ │ │ │ ├── ObjectPool.cs │ │ │ │ ├── PooledQueueCache.cs │ │ │ │ └── TimePurgePredicate.cs │ │ │ ├── RecoverableStreamConfigurator.cs │ │ │ ├── RecoverableStreamOptions.cs │ │ │ ├── SegmentBuilder.cs │ │ │ └── SimpleCache/ │ │ │ ├── SimpleQueueAdapterCache.cs │ │ │ ├── SimpleQueueCache.cs │ │ │ ├── SimpleQueueCacheCursor.cs │ │ │ └── SimpleQueueCacheOptions.cs │ │ ├── Core/ │ │ │ ├── DefaultStreamIdMapper.cs │ │ │ ├── IAsyncBatchObservable.cs │ │ │ ├── IAsyncBatchObserver.cs │ │ │ ├── IAsyncBatchProducer.cs │ │ │ ├── IAsyncObservable.cs │ │ │ ├── IAsyncObserver.cs │ │ │ ├── IAsyncStream.cs │ │ │ ├── IStreamIdMapper.cs │ │ │ ├── IStreamIdentity.cs │ │ │ ├── IStreamSubscriptionHandleFactory.cs │ │ │ ├── IStreamSubscriptionManager.cs │ │ │ ├── IStreamSubscriptionManagerAdmin.cs │ │ │ ├── IStreamSubscriptionManagerRetriever.cs │ │ │ ├── IStreamSubscriptionObserver.cs │ │ │ ├── ImplicitConsumerGrainExtensions.cs │ │ │ ├── StreamIdentity.cs │ │ │ ├── StreamSequenceToken.cs │ │ │ ├── StreamSubscription.cs │ │ │ ├── StreamSubscriptionHandle.cs │ │ │ ├── StreamSubscriptionManager.cs │ │ │ └── StreamSubscriptionManagerAdmin.cs │ │ ├── Extensions/ │ │ │ ├── AsyncBatchObservableExtensions.cs │ │ │ ├── AsyncObservableExtensions.cs │ │ │ ├── GenericAsyncObserver.cs │ │ │ ├── GenericBatchAsyncObserver.cs │ │ │ └── StreamSubscriptionHandleExtensions.cs │ │ ├── Filtering/ │ │ │ └── IStreamFilter.cs │ │ ├── Generator/ │ │ │ ├── GeneratorAdapterFactory.cs │ │ │ ├── GeneratorPooledCache.cs │ │ │ ├── Generators/ │ │ │ │ ├── GeneratedBatchContainer.cs │ │ │ │ ├── GeneratedEvent.cs │ │ │ │ ├── SimpleGenerator.cs │ │ │ │ └── SimpleGeneratorConfig.cs │ │ │ └── IStreamGenerator.cs │ │ ├── GrainStreamingExtensions.cs │ │ ├── Hosting/ │ │ │ ├── ClientBuilderStreamingExtensions.cs │ │ │ ├── ClusterClientPersistentStreamConfigurator.cs │ │ │ ├── SiloBuilderMemoryStreamExtensions.cs │ │ │ ├── SiloBuilderStreamingExtensions.cs │ │ │ └── StreamingServiceCollectionExtensions.cs │ │ ├── ISiloPersistentStreamConfigurator.cs │ │ ├── Internal/ │ │ │ ├── IInternalAsyncObservable.cs │ │ │ ├── IInternalStreamProvider.cs │ │ │ ├── IStreamControl.cs │ │ │ ├── IStreamGrainExtensions.cs │ │ │ ├── PeriodicAction.cs │ │ │ ├── StreamConsumer.cs │ │ │ ├── StreamConsumerExtension.cs │ │ │ ├── StreamDirectory.cs │ │ │ ├── StreamHandshakeToken.cs │ │ │ ├── StreamImpl.cs │ │ │ ├── StreamSubscriptionHandleImpl.cs │ │ │ └── StreamSubsriptionHandlerFactory.cs │ │ ├── InternalStreamId.cs │ │ ├── JsonConverters/ │ │ │ ├── StreamImplConverter.cs │ │ │ └── StreamingConverterConfigurator.cs │ │ ├── LoadShedQueueFlowController.cs │ │ ├── MemoryStreams/ │ │ │ ├── IMemoryStreamQueueGrain.cs │ │ │ ├── MemoryAdapterFactory.cs │ │ │ ├── MemoryAdapterReceiver.cs │ │ │ ├── MemoryBatchContainer.cs │ │ │ ├── MemoryMessageBody.cs │ │ │ ├── MemoryMessageBodySerializerFactory.cs │ │ │ ├── MemoryMessageData.cs │ │ │ ├── MemoryPooledCache.cs │ │ │ ├── MemoryStreamBuilder.cs │ │ │ ├── MemoryStreamProviderBuilder.cs │ │ │ └── MemoryStreamQueueGrain.cs │ │ ├── Orleans.Streaming.csproj │ │ ├── PersistentStreams/ │ │ │ ├── IDeploymentConfiguration.cs │ │ │ ├── IPersistentStreamPullingAgent.cs │ │ │ ├── IQueueDataAdapter.cs │ │ │ ├── IStreamFailureHandler.cs │ │ │ ├── IStreamQueueBalancer.cs │ │ │ ├── IStreamQueueCheckpointer.cs │ │ │ ├── NoOpStreamFailureHandler.cs │ │ │ ├── Options/ │ │ │ │ └── PersistentStreamProviderOptions.cs │ │ │ ├── PersistentStreamProducer.cs │ │ │ ├── PersistentStreamProvider.cs │ │ │ ├── PersistentStreamPullingAgent.cs │ │ │ ├── PersistentStreamPullingManager.cs │ │ │ ├── QueueStreamDataStructures.cs │ │ │ ├── StreamConsumerCollection.cs │ │ │ ├── StreamEventDeliveryFailureException.cs │ │ │ └── StreamPosition.cs │ │ ├── Predicates/ │ │ │ ├── AllStreamNamespacesPredicate.cs │ │ │ ├── ExactMatchStreamNamespacePredicate.cs │ │ │ ├── IStreamNamespacePredicate.cs │ │ │ ├── RegexStreamNamespacePredicate.cs │ │ │ └── StreamSubscriptionAttributes.cs │ │ ├── ProviderErrorCode.cs │ │ ├── Providers/ │ │ │ ├── ClientStreamingProviderRuntime.cs │ │ │ ├── IBackoffProviders.cs │ │ │ ├── IStreamProvider.cs │ │ │ ├── IStreamProviderRuntime.cs │ │ │ ├── ProviderStartException.cs │ │ │ ├── SiloStreamProviderRuntime.cs │ │ │ └── StreamProviderDirection.cs │ │ ├── PubSub/ │ │ │ ├── DefaultStreamNamespacePredicateProvider.cs │ │ │ ├── FaultedSubscriptionException.cs │ │ │ ├── GrainBasedPubSubRuntime.cs │ │ │ ├── IPubSubRendezvousGrain.cs │ │ │ ├── ImplicitStreamPubSub.cs │ │ │ ├── ImplicitStreamSubscriberTable.cs │ │ │ ├── PubSubPublisherState.cs │ │ │ ├── PubSubRendezvousGrain.cs │ │ │ ├── PubSubSubscriptionState.cs │ │ │ ├── StreamPubSubImpl.cs │ │ │ ├── StreamSubscriptionManagerExtensions.cs │ │ │ └── SubscriptionMarker.cs │ │ ├── QueueAdapters/ │ │ │ ├── AggregatedQueueFlowController.cs │ │ │ ├── BatchContainerBatch.cs │ │ │ ├── DataNotAvailableException.cs │ │ │ ├── HashRing.cs │ │ │ ├── HashRingBasedStreamQueueMapper.cs │ │ │ ├── HashRingStreamQueueMapperOptions.cs │ │ │ ├── IBatchContainer.cs │ │ │ ├── IBatchContainerBatch.cs │ │ │ ├── IConsistentRingStreamQueueMapper.cs │ │ │ ├── IQueueAdapter.cs │ │ │ ├── IQueueAdapterCache.cs │ │ │ ├── IQueueAdapterFactory.cs │ │ │ ├── IQueueAdapterReceiver.cs │ │ │ ├── IQueueCache.cs │ │ │ ├── IQueueCacheCursor.cs │ │ │ ├── IQueueFlowController.cs │ │ │ ├── IStreamQueueMapper.cs │ │ │ ├── QueueAdapterConstants.cs │ │ │ └── QueueCacheMissException.cs │ │ ├── QueueBalancer/ │ │ │ ├── BestFitBalancer.cs │ │ │ ├── ConsistentRingQueueBalancer.cs │ │ │ ├── DeploymentBasedQueueBalancer.cs │ │ │ ├── DeploymentBasedQueueBalancerOptions.cs │ │ │ ├── IResourceSelector.cs │ │ │ ├── LeaseBasedQueueBalancer.cs │ │ │ ├── LeaseBasedQueueBalancerOptions.cs │ │ │ ├── PersistentStreamConfiguratorExtension.cs │ │ │ ├── QueueBalancerBase.cs │ │ │ ├── RoundRobinSelector.cs │ │ │ └── StaticClusterDeploymentConfiguration.cs │ │ ├── QueueId.cs │ │ ├── SiloPersistentStreamConfigurator.cs │ │ ├── StreamConsumerGrainContextAction.cs │ │ └── StreamId.cs │ ├── Orleans.Streaming.Abstractions/ │ │ ├── Orleans.Streaming.Abstractions.csproj │ │ └── Properties/ │ │ └── AssemblyInfo.cs │ ├── Orleans.Streaming.NATS/ │ │ ├── Hosting/ │ │ │ ├── ClientBuilderExtensions.cs │ │ │ ├── NatsStreamConfigurator.cs │ │ │ └── SiloBuilderExtensions.cs │ │ ├── NatsOptions.cs │ │ ├── Orleans.Streaming.NATS.csproj │ │ ├── Providers/ │ │ │ ├── NatsAdapter.cs │ │ │ ├── NatsAdapterFactory.cs │ │ │ ├── NatsBatchContainer.cs │ │ │ ├── NatsConnectionManager.cs │ │ │ ├── NatsQueueAdapterReceiver.cs │ │ │ ├── NatsStreamConsumer.cs │ │ │ ├── NatsStreamMessage.cs │ │ │ └── StreamIdJsonConverter.cs │ │ └── README.md │ ├── Orleans.TestingHost/ │ │ ├── ClientExtensions.cs │ │ ├── ConfigureDistributedGrainDirectory.cs │ │ ├── IClientBuilderConfigurator.cs │ │ ├── IHostConfigurator.cs │ │ ├── IPortAllocator.cs │ │ ├── ISiloConfigurator.cs │ │ ├── InMemoryTransport/ │ │ │ ├── InMemoryTransportConnection.cs │ │ │ └── InMemoryTransportListenerFactory.cs │ │ ├── InProcTestCluster.cs │ │ ├── InProcTestClusterBuilder.cs │ │ ├── InProcTestClusterOptions.cs │ │ ├── InProcTestSiloSpecificOptions.cs │ │ ├── InProcess/ │ │ │ ├── InProcessGrainDirectory.cs │ │ │ └── InProcessMembershipTable.cs │ │ ├── InProcessSiloHandle.cs │ │ ├── Logging/ │ │ │ ├── FileLogger.cs │ │ │ └── FileLoggerProvider.cs │ │ ├── Orleans.TestingHost.csproj │ │ ├── SiloHandle.cs │ │ ├── StandaloneSiloHandle.cs │ │ ├── StandaloneSiloHost.cs │ │ ├── TestCluster.cs │ │ ├── TestClusterBuilder.cs │ │ ├── TestClusterExtensions.cs │ │ ├── TestClusterHostFactory.cs │ │ ├── TestClusterOptions.cs │ │ ├── TestClusterPortAllocator.cs │ │ ├── TestStorageProviders/ │ │ │ ├── FaultInjectionStorageProvider.cs │ │ │ ├── FaultInjectionStorageServiceCollectionExtensions.cs │ │ │ ├── FaultyMemoryStorage.cs │ │ │ ├── IStorageFaultGrain.cs │ │ │ ├── RandomlyInjectedStorageException.cs │ │ │ └── StorageFaultGrain.cs │ │ ├── UnixSocketTransport/ │ │ │ ├── UnixSocketConnectionExtensions.cs │ │ │ ├── UnixSocketConnectionFactory.cs │ │ │ ├── UnixSocketConnectionListener.cs │ │ │ ├── UnixSocketConnectionListenerFactory.cs │ │ │ └── UnixSocketConnectionOptions.cs │ │ └── Utils/ │ │ ├── AsyncResultHandle.cs │ │ ├── StorageEmulator.cs │ │ └── TestingUtils.cs │ ├── Orleans.Transactions/ │ │ ├── Abstractions/ │ │ │ ├── Extensions/ │ │ │ │ └── TransactionalStateExtensions.cs │ │ │ ├── INamedTransactionalStateStorageFactory.cs │ │ │ ├── ITransactionAgentStatistics.cs │ │ │ ├── ITransactionCommitter.cs │ │ │ ├── ITransactionCommitterConfiguration.cs │ │ │ ├── ITransactionCommitterFactory.cs │ │ │ ├── ITransactionDataCopier.cs │ │ │ ├── ITransactionManager.cs │ │ │ ├── ITransactionManagerExtension.cs │ │ │ ├── ITransactionalResource.cs │ │ │ ├── ITransactionalResourceExtension.cs │ │ │ ├── ITransactionalState.cs │ │ │ ├── ITransactionalStateConfiguration.cs │ │ │ ├── ITransactionalStateFactory.cs │ │ │ ├── ITransactionalStateStorage.cs │ │ │ ├── ITransactionalStateStorageFactory.cs │ │ │ ├── TransactionCommitterAttribute.cs │ │ │ └── TransactionalStateAttribute.cs │ │ ├── DisabledTransactionAgent.cs │ │ ├── DistributedTM/ │ │ │ ├── ContextResourceFactoryExtensions.cs │ │ │ ├── ParticipantId.cs │ │ │ ├── TransactionAgent.cs │ │ │ ├── TransactionAgentStatistics.cs │ │ │ ├── TransactionClient.cs │ │ │ ├── TransactionInfo.cs │ │ │ ├── TransactionManagerExtension.cs │ │ │ ├── TransactionOverloadDetector.cs │ │ │ ├── TransactionRecord.cs │ │ │ └── TransactionalResourceExtension.cs │ │ ├── ErrorCodes.cs │ │ ├── Hosting/ │ │ │ ├── ClientBuilderExtensions.cs │ │ │ ├── DefaultTransactionDataCopier.cs │ │ │ ├── SiloBuilderExtensions.cs │ │ │ ├── TransactionCommitterAttributeMapper.cs │ │ │ ├── TransactionalStateAttributeMapper.cs │ │ │ └── TransactionsServiceCollectionExtensions.cs │ │ ├── ITransactionAgent.cs │ │ ├── ITransactionClient.cs │ │ ├── Orleans.Transactions.csproj │ │ ├── OrleansTransactionException.cs │ │ ├── State/ │ │ │ ├── ActivationLifetime.cs │ │ │ ├── ConfirmationWorker.cs │ │ │ ├── IActivationLifetime.cs │ │ │ ├── NamedTransactionalStateStorageFactory.cs │ │ │ ├── ReaderWriterLock.cs │ │ │ ├── StorageBatch.cs │ │ │ ├── TransactionManager.cs │ │ │ ├── TransactionQueue.cs │ │ │ ├── TransactionalResource.cs │ │ │ ├── TransactionalState.cs │ │ │ ├── TransactionalStateFactory.cs │ │ │ ├── TransactionalStateOptions.cs │ │ │ └── TransactionalStateStorageProviderWrapper.cs │ │ ├── TOC/ │ │ │ ├── TocTransactionQueue.cs │ │ │ ├── TransactionCommitter.cs │ │ │ └── TransactionCommitterFactory.cs │ │ ├── TransactionAttribute.cs │ │ ├── TransactionContext.cs │ │ ├── TransactionalStatus.cs │ │ └── Utilities/ │ │ ├── CausalClock.cs │ │ ├── Clock.cs │ │ ├── CommitQueue.cs │ │ ├── IClock.cs │ │ └── PeriodicAction.cs │ ├── Orleans.Transactions.TestKit.Base/ │ │ ├── Consistency/ │ │ │ ├── ConsistencyTestGrain.cs │ │ │ ├── ConsistencyTestHarness.cs │ │ │ ├── ConsistencyTestOptions.cs │ │ │ ├── IConsistencyTestGrain.cs │ │ │ └── Observation.cs │ │ ├── FaultInjection/ │ │ │ ├── ControlledInjection/ │ │ │ │ ├── FaultInjectionAzureTableTransactionStateStorage.cs │ │ │ │ ├── FaultInjectionTransactionCoordinatorGrain.cs │ │ │ │ ├── FaultInjectionTransactionReource.cs │ │ │ │ ├── FaultInjectionTransactionState.cs │ │ │ │ ├── FaultInjectionTransactionStateAttribute.cs │ │ │ │ ├── HostingExtensions.cs │ │ │ │ ├── IControlledFaultInjector.cs │ │ │ │ ├── SimpleAzureStorageExceptionInjector.cs │ │ │ │ ├── SingleStateDeactivatingTransactionalGrain.cs │ │ │ │ └── TransactionFaultInjectionServiceCollectionExtensions.cs │ │ │ ├── ITransactionFaultInjector.cs │ │ │ └── RandomInjection/ │ │ │ └── RandomErrorInjector.cs │ │ ├── Grains/ │ │ │ ├── ITransactionAttributionGrain.cs │ │ │ ├── ITransactionCommitterTestGrain.cs │ │ │ ├── ITransactionCoordinatorGrain.cs │ │ │ ├── ITransactionTestGrain.cs │ │ │ ├── ITransactionalBitArrayGrain.cs │ │ │ ├── MultiStateTransactionalBitArrayGrain.cs │ │ │ ├── MultiStateTransactionalGrain.cs │ │ │ ├── RemoteCommitService.cs │ │ │ ├── TransactionAttributionGrain.cs │ │ │ ├── TransactionCommitterTestGrain.cs │ │ │ └── TransactionCoordinatorGrain.cs │ │ ├── ITestState.cs │ │ ├── Orleans.Transactions.TestKit.Base.csproj │ │ ├── TestRunners/ │ │ │ ├── ConsistencyTransactionTestRunner.cs │ │ │ ├── ControlledFaultInjectionTransactionTestRunner.cs │ │ │ ├── DisabledTransactionsTestRunner.cs │ │ │ ├── GoldenPathTransactionTestRunner.cs │ │ │ ├── GrainFaultTransactionTestRunner.cs │ │ │ ├── ScopedTransactionsTestRunner.cs │ │ │ ├── SkewedClock.cs │ │ │ ├── SkewedClockConfigurator.cs │ │ │ ├── TOCGoldenPathTestRunner.cs │ │ │ ├── TocFaultTransactionTestRunner.cs │ │ │ ├── TransactionConcurrencyTestRunner.cs │ │ │ ├── TransactionRecoveryTestsRunner.cs │ │ │ └── TransactionalStateStorageTestRunner.cs │ │ ├── TransactionTestConstants.cs │ │ └── TransactionTestRunnerBase.cs │ ├── Orleans.Transactions.TestKit.xUnit/ │ │ ├── ConsistencyTransactionTestRunner.cs │ │ ├── ControlledFaultInjectionTransactionTestRunner.cs │ │ ├── DisabledTransactionsTestRunner.cs │ │ ├── GoldenPathTransactionTestRunner.cs │ │ ├── GrainFaultTransactionTestRunner.cs │ │ ├── Orleans.Transactions.TestKit.xUnit.csproj │ │ ├── ScopedTransactionsTestRunnerxUnit.cs │ │ ├── TOCGoldenPathTestRunner.cs │ │ ├── TocFaultTransactionTestRunner.cs │ │ ├── TransactionConcurrencyTestRunner.cs │ │ ├── TransactionRecoveryTestsRunner.cs │ │ └── TransactionalStateStorageTestRunner.cs │ ├── Redis/ │ │ ├── Orleans.Clustering.Redis/ │ │ │ ├── Hosting/ │ │ │ │ ├── HostingExtensions.ICientBuilder.cs │ │ │ │ ├── HostingExtensions.ISiloBuilder.cs │ │ │ │ └── RedisClusteringProviderBuilder.cs │ │ │ ├── Orleans.Clustering.Redis.csproj │ │ │ ├── Providers/ │ │ │ │ ├── RedisClusteringOptions.cs │ │ │ │ └── RedisGatewayListProvider.cs │ │ │ ├── README.md │ │ │ └── Storage/ │ │ │ ├── JsonSettings.cs │ │ │ ├── RedisClusteringException.cs │ │ │ └── RedisMembershipTable.cs │ │ ├── Orleans.GrainDirectory.Redis/ │ │ │ ├── Hosting/ │ │ │ │ ├── RedisGrainDirectoryExtensions.cs │ │ │ │ └── RedisGrainDirectoryProviderBuilder.cs │ │ │ ├── Options/ │ │ │ │ └── RedisGrainDirectoryOptions.cs │ │ │ ├── Orleans.GrainDirectory.Redis.csproj │ │ │ ├── README.md │ │ │ └── RedisGrainDirectory.cs │ │ ├── Orleans.Persistence.Redis/ │ │ │ ├── Hosting/ │ │ │ │ ├── RedisGrainStorageProviderBuilder.cs │ │ │ │ ├── RedisGrainStorageServiceCollectionExtensions.cs │ │ │ │ └── RedisSiloBuilderExtensions.cs │ │ │ ├── Orleans.Persistence.Redis.csproj │ │ │ ├── Providers/ │ │ │ │ ├── RedisStorageOptions.cs │ │ │ │ └── RedisStorageOptionsValidator.cs │ │ │ ├── README.md │ │ │ └── Storage/ │ │ │ ├── RedisGrainStorage.cs │ │ │ ├── RedisGrainStorageFactory.cs │ │ │ └── RedisStorageException.cs │ │ └── Orleans.Reminders.Redis/ │ │ ├── Hosting/ │ │ │ ├── RedisRemindersProviderBuilder.cs │ │ │ └── SiloBuilderReminderExtensions.cs │ │ ├── Orleans.Reminders.Redis.csproj │ │ ├── Providers/ │ │ │ └── RedisReminderTableOptions.cs │ │ ├── README.md │ │ └── Storage/ │ │ ├── RedisReminderTable.cs │ │ └── RedisRemindersException.cs │ ├── Serializers/ │ │ └── Orleans.Serialization.Protobuf/ │ │ ├── ByteStringCodec.cs │ │ ├── ByteStringCopier.cs │ │ ├── MapFieldCodec.cs │ │ ├── MapFieldCopier.cs │ │ ├── Orleans.Serialization.Protobuf.csproj │ │ ├── ProtobufCodec.cs │ │ ├── README.md │ │ ├── RepeatedFieldCodec.cs │ │ ├── RepeatedFieldCopier.cs │ │ └── SerializationHostingExtensions.cs │ └── api/ │ ├── AWS/ │ │ ├── Orleans.Clustering.DynamoDB/ │ │ │ └── Orleans.Clustering.DynamoDB.cs │ │ ├── Orleans.Persistence.DynamoDB/ │ │ │ └── Orleans.Persistence.DynamoDB.cs │ │ ├── Orleans.Reminders.DynamoDB/ │ │ │ └── Orleans.Reminders.DynamoDB.cs │ │ └── Orleans.Streaming.SQS/ │ │ └── Orleans.Streaming.SQS.cs │ ├── AdoNet/ │ │ ├── Orleans.Clustering.AdoNet/ │ │ │ └── Orleans.Clustering.AdoNet.cs │ │ ├── Orleans.GrainDirectory.AdoNet/ │ │ │ └── Orleans.GrainDirectory.AdoNet.cs │ │ ├── Orleans.Persistence.AdoNet/ │ │ │ └── Orleans.Persistence.AdoNet.cs │ │ ├── Orleans.Reminders.AdoNet/ │ │ │ └── Orleans.Reminders.AdoNet.cs │ │ └── Orleans.Streaming.AdoNet/ │ │ └── Orleans.Streaming.AdoNet.cs │ ├── Azure/ │ │ ├── Orleans.Clustering.AzureStorage/ │ │ │ └── Orleans.Clustering.AzureStorage.cs │ │ ├── Orleans.Clustering.Cosmos/ │ │ │ └── Orleans.Clustering.Cosmos.cs │ │ ├── Orleans.GrainDirectory.AzureStorage/ │ │ │ └── Orleans.GrainDirectory.AzureStorage.cs │ │ ├── Orleans.Journaling.AzureStorage/ │ │ │ └── Orleans.Journaling.AzureStorage.cs │ │ ├── Orleans.Persistence.AzureStorage/ │ │ │ └── Orleans.Persistence.AzureStorage.cs │ │ ├── Orleans.Persistence.Cosmos/ │ │ │ └── Orleans.Persistence.Cosmos.cs │ │ ├── Orleans.Reminders.AzureStorage/ │ │ │ └── Orleans.Reminders.AzureStorage.cs │ │ ├── Orleans.Reminders.Cosmos/ │ │ │ └── Orleans.Reminders.Cosmos.cs │ │ ├── Orleans.Streaming.AzureStorage/ │ │ │ └── Orleans.Streaming.AzureStorage.cs │ │ ├── Orleans.Streaming.EventHubs/ │ │ │ └── Orleans.Streaming.EventHubs.cs │ │ └── Orleans.Transactions.AzureStorage/ │ │ └── Orleans.Transactions.AzureStorage.cs │ ├── Cassandra/ │ │ └── Orleans.Clustering.Cassandra/ │ │ └── Orleans.Clustering.Cassandra.cs │ ├── Orleans.BroadcastChannel/ │ │ └── Orleans.BroadcastChannel.cs │ ├── Orleans.Client/ │ │ └── Orleans.Client.cs │ ├── Orleans.Clustering.Consul/ │ │ └── Orleans.Clustering.Consul.cs │ ├── Orleans.Clustering.ZooKeeper/ │ │ └── Orleans.Clustering.ZooKeeper.cs │ ├── Orleans.Connections.Security/ │ │ └── Orleans.Connections.Security.cs │ ├── Orleans.Core/ │ │ └── Orleans.Core.cs │ ├── Orleans.Core.Abstractions/ │ │ └── Orleans.Core.Abstractions.cs │ ├── Orleans.EventSourcing/ │ │ └── Orleans.EventSourcing.cs │ ├── Orleans.Hosting.Kubernetes/ │ │ └── Orleans.Hosting.Kubernetes.cs │ ├── Orleans.Journaling/ │ │ └── Orleans.Journaling.cs │ ├── Orleans.Persistence.Memory/ │ │ └── Orleans.Persistence.Memory.cs │ ├── Orleans.Reminders/ │ │ └── Orleans.Reminders.cs │ ├── Orleans.Reminders.Abstractions/ │ │ └── Orleans.Reminders.Abstractions.cs │ ├── Orleans.Runtime/ │ │ └── Orleans.Runtime.cs │ ├── Orleans.Sdk/ │ │ └── Orleans.Sdk.cs │ ├── Orleans.Serialization/ │ │ └── Orleans.Serialization.cs │ ├── Orleans.Serialization.Abstractions/ │ │ └── Orleans.Serialization.Abstractions.cs │ ├── Orleans.Serialization.FSharp/ │ │ └── Orleans.Serialization.FSharp.cs │ ├── Orleans.Serialization.MemoryPack/ │ │ └── Orleans.Serialization.MemoryPack.cs │ ├── Orleans.Serialization.MessagePack/ │ │ └── Orleans.Serialization.MessagePack.cs │ ├── Orleans.Serialization.NewtonsoftJson/ │ │ └── Orleans.Serialization.NewtonsoftJson.cs │ ├── Orleans.Serialization.SystemTextJson/ │ │ └── Orleans.Serialization.SystemTextJson.cs │ ├── Orleans.Serialization.TestKit/ │ │ └── Orleans.Serialization.TestKit.cs │ ├── Orleans.Server/ │ │ └── Orleans.Server.cs │ ├── Orleans.Streaming/ │ │ └── Orleans.Streaming.cs │ ├── Orleans.Streaming.Abstractions/ │ │ └── Orleans.Streaming.Abstractions.cs │ ├── Orleans.TestingHost/ │ │ └── Orleans.TestingHost.cs │ ├── Orleans.Transactions/ │ │ └── Orleans.Transactions.cs │ ├── Orleans.Transactions.TestKit.Base/ │ │ └── Orleans.Transactions.TestKit.Base.cs │ ├── Orleans.Transactions.TestKit.xUnit/ │ │ └── Orleans.Transactions.TestKit.xUnit.cs │ ├── README.md │ ├── Redis/ │ │ ├── Orleans.Clustering.Redis/ │ │ │ └── Orleans.Clustering.Redis.cs │ │ ├── Orleans.GrainDirectory.Redis/ │ │ │ └── Orleans.GrainDirectory.Redis.cs │ │ ├── Orleans.Persistence.Redis/ │ │ │ └── Orleans.Persistence.Redis.cs │ │ └── Orleans.Reminders.Redis/ │ │ └── Orleans.Reminders.Redis.cs │ └── Serializers/ │ └── Orleans.Serialization.Protobuf/ │ └── Orleans.Serialization.Protobuf.cs └── test/ ├── Benchmarks/ │ ├── App.config │ ├── Benchmarks.csproj │ ├── Dashboard/ │ │ ├── DashboardGrainBenchmark.cs │ │ ├── Helper.cs │ │ ├── ManualTests.cs │ │ └── TestTraces.cs │ ├── GrainStorage/ │ │ └── GrainStorageBenchmark.cs │ ├── MapReduce/ │ │ ├── MapReduceBenchmark.cs │ │ └── MapReduceBenchmarkConfig.cs │ ├── Ping/ │ │ ├── ConcurrentLoadGenerator.cs │ │ ├── FanoutBenchmark.cs │ │ ├── PingBenchmark.cs │ │ └── StatelessWorkerBenchmark.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Serialization/ │ │ ├── Comparison/ │ │ │ ├── ArrayDeserializeBenchmark.cs │ │ │ ├── ArraySerializeBenchmark.cs │ │ │ ├── ClassDeserializeBenchmark.cs │ │ │ ├── ClassSerializeBenchmark.cs │ │ │ ├── CopierBenchmark.cs │ │ │ ├── StructDeserializeBenchmark.cs │ │ │ └── StructSerializeBenchmark.cs │ │ ├── ComplexTypeBenchmarks.cs │ │ ├── FieldHeaderBenchmarks.cs │ │ ├── MegaGraphBenchmark.cs │ │ ├── Models/ │ │ │ ├── ComplexClass.cs │ │ │ ├── IntClass.cs │ │ │ ├── IntStruct.cs │ │ │ ├── ProtoIntClass.cs │ │ │ ├── ProtoIntClass.proto │ │ │ ├── SimpleClass.cs │ │ │ ├── SimpleStruct.cs │ │ │ ├── Vector3.cs │ │ │ └── VirtualIntsClass.cs │ │ └── Utilities/ │ │ ├── BenchmarkConfig.cs │ │ ├── ClassSingleSegmentBuffer.cs │ │ ├── MethodResultColumn.cs │ │ ├── PayloadSizeColumnAttribute.cs │ │ └── SingleSegmentBuffer.cs │ ├── TopK/ │ │ ├── BloomFilterBenchmark.cs │ │ └── TopKBenchmark.cs │ ├── Transactions/ │ │ └── TransactionBenchmark.cs │ └── run_test.cmd ├── Benchmarks.AdoNet/ │ ├── Benchmarks.AdoNet.csproj │ ├── Program.cs │ ├── Properties/ │ │ └── Usings.cs │ └── Streaming/ │ ├── MessageDequeueingBenchmark.cs │ └── MessageQueueingBenchmark.cs ├── Directory.Build.props ├── Directory.Build.targets ├── DistributedTests/ │ ├── DistributedTests.Client/ │ │ ├── Commands/ │ │ │ ├── ChaosAgentCommand.cs │ │ │ ├── CounterCaptureCommand.cs │ │ │ └── ScenarioCommand.cs │ │ ├── DistributedTests.Client.csproj │ │ ├── LoadGeneratorScenario/ │ │ │ ├── ConcurrentLoadGenerator.cs │ │ │ ├── LoadGeneratorScenarioRunner.cs │ │ │ └── LoadGeneratorScenarios.cs │ │ └── Program.cs │ ├── DistributedTests.Common/ │ │ ├── DistributedTests.Common.csproj │ │ ├── GrainInterfaces/ │ │ │ ├── IPingGrain.cs │ │ │ ├── IStreamingGrains.cs │ │ │ └── ITreeGrain.cs │ │ ├── MessageChannel/ │ │ │ ├── Channels.cs │ │ │ └── Messages.cs │ │ ├── OptionHelper.cs │ │ └── TokenCredentialHelper.cs │ ├── DistributedTests.Grains/ │ │ ├── CounterReportingGrain.cs │ │ ├── DistributedTests.Grains.csproj │ │ ├── ImplicitSubscriberGrain.cs │ │ ├── PingGrain.cs │ │ └── TreeGrain.cs │ ├── DistributedTests.Server/ │ │ ├── Configurator/ │ │ │ ├── EventGeneratorStreamingSilo.cs │ │ │ ├── ISiloConfigurator.cs │ │ │ └── SimpleSilo.cs │ │ ├── DistributedTests.Server.csproj │ │ ├── Program.cs │ │ ├── ServerCommand.cs │ │ └── ServerRunner.cs │ ├── README.md │ └── secrets.json ├── Extensions/ │ ├── Orleans.AWS.Tests/ │ │ ├── App.config │ │ ├── CollectionFixtures.cs │ │ ├── LivenessTests.cs │ │ ├── MembershipTests/ │ │ │ ├── DynamoDBMembershipTableTest.cs │ │ │ └── SiloInstanceRecordTests.cs │ │ ├── Orleans.AWS.Tests.csproj │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Reminder/ │ │ │ └── DynamoDBRemindersTableTests.cs │ │ ├── StorageTests/ │ │ │ ├── AWSTestConstants.cs │ │ │ ├── DynamoDBStorageProviderTests.cs │ │ │ ├── DynamoDBStorageStressTests.cs │ │ │ ├── DynamoDBStorageTestFixture.cs │ │ │ ├── DynamoDBStorageTests.cs │ │ │ ├── PersistenceGrainTests_AWSDynamoDBStore.cs │ │ │ └── UnitTestDynamoDBStorage.cs │ │ └── Streaming/ │ │ ├── SQSAdapterTests.cs │ │ ├── SQSClientStreamTests.cs │ │ ├── SQSStreamTests.cs │ │ └── SQSSubscriptionMultiplicityTests.cs │ ├── Orleans.AdoNet.Tests/ │ │ ├── App.config │ │ ├── CollectionFixtures.cs │ │ ├── Fakes/ │ │ │ └── FakeHostApplicationLifetime.cs │ │ ├── GrainDirectory/ │ │ │ ├── AdoNetGrainDirectoryClusterTests.cs │ │ │ ├── AdoNetGrainDirectoryTests.cs │ │ │ └── RelationalOrleansQueriesTests.cs │ │ ├── LivenessTests.cs │ │ ├── MySqlMembershipTableTests.cs │ │ ├── Orleans.AdoNet.Tests.csproj │ │ ├── PackageReferences.cs │ │ ├── Persistence/ │ │ │ ├── PersistenceGrainTests_MySql.cs │ │ │ ├── PersistenceGrainTests_MySql_DeleteStateOnClear.cs │ │ │ ├── PersistenceGrainTests_Postgres.cs │ │ │ ├── PersistenceGrainTests_Postgres_DeleteStateOnClear.cs │ │ │ ├── PersistenceGrainTests_SqlServer.cs │ │ │ ├── PersistenceGrainTests_SqlServer_DeleteStateOnClear.cs │ │ │ ├── SqlitePersistenceGrainStorageFixture.cs │ │ │ └── SqlitePersistenceGrainStorageTests.cs │ │ ├── PostgreSqlMembershipTableTests.cs │ │ ├── Properties/ │ │ │ ├── AssemblyInfo.cs │ │ │ ├── GlobalSuppressions.cs │ │ │ ├── IsExternalInit.cs │ │ │ └── Usings.cs │ │ ├── RelationalUtilities/ │ │ │ ├── MySqlStorageForTesting.cs │ │ │ ├── PostgreSqlStorageForTesting.cs │ │ │ ├── RelationalStorageForTesting.cs │ │ │ └── SqlServerStorageForTesting.cs │ │ ├── Reminders/ │ │ │ ├── MySqlRemindersTableTests.cs │ │ │ ├── PostgreSqlRemindersTableTests.cs │ │ │ ├── ReminderTests_AdoNet_SqlServer.cs │ │ │ └── SqlServerRemindersTableTests.cs │ │ ├── SqlServerMembershipTableTests.cs │ │ ├── StorageTests/ │ │ │ ├── MySqlRelationalStoreTests.cs │ │ │ ├── PostgreSqlRelationalStoreTests.cs │ │ │ ├── Relational/ │ │ │ │ ├── AdotNetProviderFunctionalityTests.cs │ │ │ │ ├── CommonFixture.cs │ │ │ │ ├── ConstantHasher.cs │ │ │ │ ├── MySqlStorageDeleteOnClearTests.cs │ │ │ │ ├── MySqlStorageTests.cs │ │ │ │ ├── PostgreSqlStorageDeleteOnClearTests.cs │ │ │ │ ├── PostgreSqlStorageTests.cs │ │ │ │ ├── RelationalStorageTests.cs │ │ │ │ ├── SqlServerStorageDeleteOnClearTests.cs │ │ │ │ ├── SqlServerStorageTests.cs │ │ │ │ └── TestEnvironmentInvariant.cs │ │ │ ├── RelationalStoreTests.cs │ │ │ └── SqlServerRelationalStoreTests.cs │ │ └── Streaming/ │ │ ├── AdoNetBatchContainerTests.cs │ │ ├── AdoNetClientStreamTests.cs │ │ ├── AdoNetQueueAdapterFactoryTests.cs │ │ ├── AdoNetQueueAdapterReceiverTests.cs │ │ ├── AdoNetQueueAdapterTests.cs │ │ ├── AdoNetStreamFailureHandlerTests.cs │ │ ├── AdoNetStreamFilteringTests.cs │ │ ├── AdoNetStreamingTests.cs │ │ ├── AdoNetStreamsBatchingTests.cs │ │ ├── AdoNetSubscriptionMultiplicityTests.cs │ │ ├── AdoNetSubscriptionObserverWithImplicitSubscribingTests.cs │ │ └── RelationalOrleansQueriesTests.cs │ ├── Orleans.Azure.Tests/ │ │ ├── App.config │ │ ├── AzureGrainDirectoryTests.cs │ │ ├── AzureLivenessTests.cs │ │ ├── AzureMembershipTableTests.cs │ │ ├── AzureQueueDataManagerTests.cs │ │ ├── AzureRemindersTableTests.cs │ │ ├── AzureStorageBasicTests.cs │ │ ├── AzureStorageOperationOptionsExtensions.cs │ │ ├── AzureTableDataManagerStressTests.cs │ │ ├── AzureTableDataManagerTests.cs │ │ ├── AzureTableErrorCodeTests.cs │ │ ├── CollectionFixtures.cs │ │ ├── DurableJobs/ │ │ │ ├── AzureStorageBlobDurableJobsTests.cs │ │ │ ├── AzureStorageJobShardBatchingTests.cs │ │ │ ├── AzureStorageJobShardManagerTestFixture.cs │ │ │ ├── AzureStorageJobShardManagerTests.cs │ │ │ └── NetstringJsonSerializerTests.cs │ │ ├── GenericGrainsInAzureStorageTests.cs │ │ ├── GrainDirectory/ │ │ │ └── AzureMultipleGrainDirectoriesTests.cs │ │ ├── Lease/ │ │ │ ├── AzureBlobLeaseProviderTests.cs │ │ │ └── LeaseBasedQueueBalancerTests.cs │ │ ├── Orleans.Azure.Tests.csproj │ │ ├── Persistence/ │ │ │ ├── PersistenceGrainTests_AzureBlobStore.cs │ │ │ ├── PersistenceGrainTests_AzureBlobStore_Json.cs │ │ │ ├── PersistenceGrainTests_AzureStore.cs │ │ │ ├── PersistenceGrainTests_AzureTableGrainStorage.cs │ │ │ ├── PersistenceProviderTests.cs │ │ │ ├── PersistenceStateTests_AzureBlobStore.cs │ │ │ └── PersistenceStateTests_AzureTableGrainStorage.cs │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Reminder/ │ │ │ ├── ReminderTests_AzureTable.cs │ │ │ └── ReminderTests_Azure_Standalone.cs │ │ ├── SiloInstanceTableManagerTests.cs │ │ ├── StorageEmulatorUtilities.cs │ │ ├── Streaming/ │ │ │ ├── AQClientStreamTests.cs │ │ │ ├── AQProgrammaticSubscribeTest.cs │ │ │ ├── AQStreamFilteringTests.cs │ │ │ ├── AQStreamingTests.cs │ │ │ ├── AQStreamsBatchingTests.cs │ │ │ ├── AQSubscriptionMultiplicityTests.cs │ │ │ ├── AQSubscriptionObserverWithImplicitSubscribingTests.cs │ │ │ ├── AzureQueueAdapterTests.cs │ │ │ ├── AzureQueueStreamProviderBuilderTests.cs │ │ │ ├── DelayedQueueRebalancingTests.cs │ │ │ ├── HaloStreamSubscribeTests.cs │ │ │ ├── PullingAgentManagementTests.cs │ │ │ ├── SampleAzureQueueStreamingTests.cs │ │ │ ├── StreamReliabilityTests.cs │ │ │ └── TestAzureTableStorageStreamFailureHandler.cs │ │ ├── UnitTestAzureTableDataManager.cs │ │ └── Utilities/ │ │ └── AzureQueueUtilities.cs │ ├── Orleans.Clustering.Cassandra.Tests/ │ │ ├── Clustering/ │ │ │ ├── CassandraClusteringTableTests.cs │ │ │ ├── CassandraContainer.cs │ │ │ └── SiloAddressUtils.cs │ │ ├── Orleans.Clustering.Cassandra.Tests.csproj │ │ └── Utility/ │ │ └── TestExtensions.cs │ ├── Orleans.Clustering.Consul.Tests/ │ │ ├── CollectionFixtures.cs │ │ ├── ConsulClusteringOptionsTests.cs │ │ ├── ConsulMembershipTableTest.cs │ │ ├── ConsulTestUtils.cs │ │ ├── LivenessTests.cs │ │ ├── Orleans.Clustering.Consul.Tests.csproj │ │ └── Properties/ │ │ └── AssemblyInfo.cs │ ├── Orleans.Clustering.ZooKeeper.Tests/ │ │ ├── App.config │ │ ├── CollectionFixtures.cs │ │ ├── LivenessTests.cs │ │ ├── Orleans.Clustering.ZooKeeper.Tests.csproj │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── ZookeeperMembershipTableTests.cs │ │ └── ZookeeperTestUtils.cs │ ├── Orleans.Cosmos.Tests/ │ │ ├── CollectionFixtures.cs │ │ ├── CosmosMembershipTableTests.cs │ │ ├── CosmosOptionsExtensions.cs │ │ ├── CosmosTestUtils.cs │ │ ├── Orleans.Cosmos.Tests.csproj │ │ ├── PersistenceGrainTests_CosmosGrainStorage.cs │ │ ├── PersistenceProviderTests_Cosmos.cs │ │ ├── ReminderTests_Cosmos.cs │ │ ├── ReminderTests_Cosmos_Standalone.cs │ │ └── Usings.cs │ ├── Orleans.Redis.Tests/ │ │ ├── Clustering/ │ │ │ └── RedisMembershipTableTests.cs │ │ ├── CollectionFixtures.cs │ │ ├── GrainDirectory/ │ │ │ ├── RedisGrainDirectoryTests.cs │ │ │ └── RedisMultipleGrainDirectoriesTests.cs │ │ ├── Orleans.Redis.Tests.csproj │ │ ├── Persistence/ │ │ │ ├── GrainState.cs │ │ │ ├── RedisPersistenceGrainTests.cs │ │ │ ├── RedisPersistenceSetupTests.cs │ │ │ ├── RedisStorageTests.cs │ │ │ ├── RedisStorageTests_DeleteStateOnClear.cs │ │ │ └── RedisStorageTests_OrleansSerializer.cs │ │ ├── Reminders/ │ │ │ └── RedisReminderTableTests.cs │ │ └── Utility/ │ │ ├── CommonFixture.cs │ │ └── TestExtensions.cs │ ├── Orleans.Streaming.EventHubs.Tests/ │ │ ├── App.config │ │ ├── CollectionFixtures.cs │ │ ├── EventHubConfigurationExtensions.cs │ │ ├── EvictionStrategyTests/ │ │ │ ├── EHPurgeLogicTests.cs │ │ │ └── TestMocks.cs │ │ ├── Orleans.Streaming.EventHubs.Tests.csproj │ │ ├── PluggableQueueBalancerTests.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── SlowConsumingTests/ │ │ │ └── EHSlowConsumingTests.cs │ │ ├── StatisticMonitorTests/ │ │ │ ├── BlockPoolMonitorForTesting.cs │ │ │ ├── CacheMonitorForTesting.cs │ │ │ ├── EHStatisticMonitorTests.cs │ │ │ └── EventHubReceiverMonitorForTesting.cs │ │ ├── Streaming/ │ │ │ ├── EHBatchedSubscriptionMultiplicityTests.cs │ │ │ ├── EHClientStreamTests.cs │ │ │ ├── EHImplicitSubscriptionStreamRecoveryTests.cs │ │ │ ├── EHProgrammaticSubscribeTests.cs │ │ │ ├── EHStreamBatchingTests.cs │ │ │ ├── EHStreamCacheMissTests.cs │ │ │ ├── EHStreamPerPartitionTests.cs │ │ │ ├── EHStreamProviderCheckpointTests.cs │ │ │ ├── EHStreamingResumeTests.cs │ │ │ ├── EHSubscriptionMultiplicityTests.cs │ │ │ ├── EHSubscriptionObserverWithImplicitSubscribingTests.cs │ │ │ └── TimePurgePredicateTests.cs │ │ └── TestStreamProviders/ │ │ ├── EHStreamProviderForMonitorTests.cs │ │ ├── EHStreamProviderWithCreatedCacheList.cs │ │ ├── StreamPerPartitionEventHubStreamProvider.cs │ │ ├── TestAzureTableStorageStreamFailureHandler.cs │ │ └── TestEventHubStreamProvider.cs │ └── Orleans.Streaming.NATS.Tests/ │ ├── NatsAdapterTests.cs │ ├── NatsClientStreamTests.cs │ ├── NatsStreamTests.cs │ ├── NatsSubscriptionMultiplicityTests.cs │ ├── NatsTestConstants.cs │ └── Orleans.Streaming.NATS.Tests.csproj ├── Grains/ │ ├── BenchmarkGrainInterfaces/ │ │ ├── BenchmarkGrainInterfaces.csproj │ │ ├── GrainStorage/ │ │ │ └── IPersistentGrain.cs │ │ ├── MapReduce/ │ │ │ ├── DataflowGrainsInterfaces.cs │ │ │ └── GrainDataflowMessageStatus.cs │ │ ├── Ping/ │ │ │ ├── ILoadGrain.cs │ │ │ ├── IPingGrain.cs │ │ │ ├── ITreeGrain.cs │ │ │ └── MyType.cs │ │ └── Transaction/ │ │ ├── ILoadGrain.cs │ │ ├── ITransactionGrain.cs │ │ └── ITransactionRootGrain.cs │ ├── BenchmarkGrains/ │ │ ├── BenchmarkGrains.csproj │ │ ├── GrainStorage/ │ │ │ └── PersistentGrain.cs │ │ ├── MapReduce/ │ │ │ ├── BufferGrain.cs │ │ │ ├── DataflowGrain.cs │ │ │ ├── Processors.cs │ │ │ ├── TargetGrain.cs │ │ │ └── TransformGrain.cs │ │ ├── Ping/ │ │ │ ├── LoadGrain.cs │ │ │ ├── PingGrain.cs │ │ │ └── TreeGrain.cs │ │ └── Transaction/ │ │ ├── LoadGrain.cs │ │ ├── TransactionGrain.cs │ │ └── TransactionRootGrain.cs │ ├── TestFSharp/ │ │ ├── Grains.fs │ │ ├── TestFSharp.fsproj │ │ └── Types.fs │ ├── TestFSharpGrainInterfaces/ │ │ ├── FSharpInterfaces/ │ │ │ ├── IFSharpBaseInterface.fs │ │ │ ├── IFSharpParameters.fs │ │ │ └── TestFSharpInterfaces.fsproj │ │ ├── IFSharpParametersGrain.cs │ │ ├── IGeneratorTestDerivedFromFSharpInterfaceInExternalAssemblyGrain.cs │ │ └── TestFSharpGrainInterfaces.csproj │ ├── TestGrainInterfaces/ │ │ ├── AdoNet/ │ │ │ ├── ICustomerGrain.cs │ │ │ └── IDeviceGrain.cs │ │ ├── ClassNotReferencingOrleansTypeDto.cs │ │ ├── ClassReferencingOrleansTypeDto.cs │ │ ├── CodegenTestInterfaces.cs │ │ ├── CustomPlacement.cs │ │ ├── Directories/ │ │ │ ├── ICommonDirectoryGrain.cs │ │ │ ├── ICustomDirectoryGrain.cs │ │ │ └── IDefaultDirectoryGrain.cs │ │ ├── EventSourcing/ │ │ │ ├── IAccountGrain.cs │ │ │ ├── IChatGrain.cs │ │ │ ├── ICountersGrain.cs │ │ │ ├── IPersonGrain.cs │ │ │ ├── ISeatReservationGrain.cs │ │ │ └── XDocumentSurrogate.cs │ │ ├── GetGrainInterfaces.cs │ │ ├── GrainInterfaceHierarchyIGrains.cs │ │ ├── IActivateDeactivateTestGrain.cs │ │ ├── IActivateDeactivateWatcherGrain.cs │ │ ├── IActivationCancellationTestGrain.cs │ │ ├── IActivityGrain.cs │ │ ├── ICSharpBaseInterface.cs │ │ ├── ICancellationTestSystemTarget.cs │ │ ├── ICatalogTestGrain.cs │ │ ├── IChainedGrain.cs │ │ ├── ICircularStateTestGrain.cs │ │ ├── IClientAddressableTestConsumer.cs │ │ ├── IClusterTestGrains.cs │ │ ├── ICollectionTestGrain.cs │ │ ├── IConcurrentGrain.cs │ │ ├── IConsumerEventCountingGrain.cs │ │ ├── IDeadlockGrain.cs │ │ ├── IDurableJobGrain.cs │ │ ├── IEchoTaskGrain.cs │ │ ├── IErrorGrain.cs │ │ ├── IExceptionGrain.cs │ │ ├── IExtensionTestGrain.cs │ │ ├── IExternalTypeGrain.cs │ │ ├── IFaultableConsumerGrain.cs │ │ ├── IFilteredImplicitSubscriptionGrain.cs │ │ ├── IFilteredImplicitSubscriptionWithExtensionGrain.cs │ │ ├── IGeneratedEventCollectorGrain.cs │ │ ├── IGeneratedEventReporterGrain.cs │ │ ├── IGeneratorTestDerivedDerivedGrain.cs │ │ ├── IGeneratorTestDerivedFromCSharpInterfaceInExternalAssemblyGrain.cs │ │ ├── IGeneratorTestDerivedGrain1.cs │ │ ├── IGeneratorTestDerivedGrain2.cs │ │ ├── IGeneratorTestGrain.cs │ │ ├── IGenericInterfaces.cs │ │ ├── IGrainServiceTestGrain.cs │ │ ├── IImplicitSubscriptionCounterGrain.cs │ │ ├── IImplicitSubscriptionKeyTypeGrain.cs │ │ ├── IInitialStateGrain.cs │ │ ├── IKeyExtensionTestGrain.cs │ │ ├── ILivenessTestGrain.cs │ │ ├── ILogTestGrain.cs │ │ ├── ILongRunningObserver.cs │ │ ├── IMethodInterceptionGrain.cs │ │ ├── IMultifacetReader.cs │ │ ├── IMultifacetWriter.cs │ │ ├── IMultipleImplicitSubscriptionGrain.cs │ │ ├── IMultipleSubscriptionConsumerGrain.cs │ │ ├── INullStateGrain.cs │ │ ├── IObservableGrain.cs │ │ ├── IObserverGrain.cs │ │ ├── IPersistenceTestGrains.cs │ │ ├── IPlacementTestGrain.cs │ │ ├── IPolymorphicTestGrain.cs │ │ ├── IProducerEventCountingGrain.cs │ │ ├── IPromiseForwardGrain.cs │ │ ├── IProxyGrain.cs │ │ ├── IReentrancyCorrelationIdGrains.cs │ │ ├── IReentrancyGrain.cs │ │ ├── IReentrantStressTestGrain.cs │ │ ├── IReminderTestGrain.cs │ │ ├── IReminderTestGrain2.cs │ │ ├── IRequestContextTestGrain.cs │ │ ├── IRetryTestGrain.cs │ │ ├── ISampleStreamingGrain.cs │ │ ├── ISchedulerGrain.cs │ │ ├── ISiloRoleBasedPlacementGrain.cs │ │ ├── ISimpleDIGrain.cs │ │ ├── ISimpleGenericGrain.cs │ │ ├── ISimpleGrain.cs │ │ ├── ISimpleGrainWithAsyncMethods.cs │ │ ├── ISimpleObserverableGrain.cs │ │ ├── ISimplePersistentGrain.cs │ │ ├── IStatelessWorkerExceptionGrain.cs │ │ ├── IStatelessWorkerGrain.cs │ │ ├── IStatelessWorkerScalingGrain.cs │ │ ├── IStatelessWorkerStreamConsumerGrain.cs │ │ ├── IStatelessWorkerStreamProducerGrain.cs │ │ ├── IStatelessWorkerWithMayInterleaveGrain.cs │ │ ├── IStatsCollectorGrain.cs │ │ ├── IStreamBatchingTestConsumerGrain.cs │ │ ├── IStreamInterceptionGrain.cs │ │ ├── IStreamLifecycleTestGrains.cs │ │ ├── IStreamLifecycleTestInternalGrains.cs │ │ ├── IStreamReliabilityTestGrains.cs │ │ ├── IStreamingGrain.cs │ │ ├── IStreamingHistoryGrain.cs │ │ ├── IStreamingImmutabilityTestGrain.cs │ │ ├── IStreaming_ProducerGrain.cs │ │ ├── IStuckGrain.cs │ │ ├── ITestExtension.cs │ │ ├── ITestGrain.cs │ │ ├── ITimerGrain.cs │ │ ├── IValueTypeTestGrain.cs │ │ ├── IsExternalInit.cs │ │ ├── ProgramaticStreamSubscribe/ │ │ │ └── IPassive_ConsumerGrain.cs │ │ ├── RecursiveType.cs │ │ ├── RedStreamNamespacePredicate.cs │ │ ├── SerializerExclusions.cs │ │ ├── SerializerTestTypes.cs │ │ ├── SlowConsumingGrains/ │ │ │ └── ISlowConsumingGrain.cs │ │ ├── TestGrainInterfaces.csproj │ │ ├── TestTypeA.cs │ │ ├── UnitTestGrainInterfaces.cs │ │ └── VersionAwarePlacementDirector.cs │ ├── TestGrains/ │ │ ├── ActivateDeactivateWatcherGrain.cs │ │ ├── ActivationCancellationTestGrains.cs │ │ ├── ActivityGrain.cs │ │ ├── AdoNet/ │ │ │ ├── CustomerGrain.cs │ │ │ ├── DeviceGrain.cs │ │ │ ├── ICustomerState.cs │ │ │ └── IDeviceState.cs │ │ ├── AsyncSimpleGrain.cs │ │ ├── CatalogTestGrain.cs │ │ ├── ChainedGrain.cs │ │ ├── CircularStateTestGrain.cs │ │ ├── ConcreteGrainsWithGenericInterfaces.cs │ │ ├── ConcurrentGrain.cs │ │ ├── ConsumerEventCountingGrain.cs │ │ ├── CustomPlacementGrains.cs │ │ ├── DeadlockGrain.cs │ │ ├── DerivedServiceType.cs │ │ ├── Directories/ │ │ │ ├── CustomDirectoryGrain.cs │ │ │ └── DefaultDirectoryGrain.cs │ │ ├── DurableJobGrain.cs │ │ ├── EventSourcing/ │ │ │ ├── AccountGrain.cs │ │ │ ├── ChatEvents.cs │ │ │ ├── ChatFormat.cs │ │ │ ├── ChatGrain.cs │ │ │ ├── CountersGrain.cs │ │ │ ├── CountersGrainVariations.cs │ │ │ ├── PersonEvents.cs │ │ │ ├── PersonGrain.cs │ │ │ ├── PersonState.cs │ │ │ └── SeatReservationGrain.cs │ │ ├── ExceptionGrain.cs │ │ ├── ExternalTypeGrain.cs │ │ ├── FaultableConsumerGrain.cs │ │ ├── FilteredImplicitSubscriptionGrain.cs │ │ ├── FilteredImplicitSubscriptionWithExtensionGrain.cs │ │ ├── GeneratedEventCollectorGrain.cs │ │ ├── GeneratedEventReporterGrain.cs │ │ ├── GeneratedStreamTestConstants.cs │ │ ├── GeneratorTestDerivedDerivedGrain.cs │ │ ├── GeneratorTestDerivedFromCSharpInterfaceInExternalAssemblyGrain.cs │ │ ├── GeneratorTestDerivedFromFSharpInterfaceInExternalAssemblyGrain.cs │ │ ├── GeneratorTestDerivedGrain1.cs │ │ ├── GeneratorTestDerivedGrain2.cs │ │ ├── GeneratorTestGrain.cs │ │ ├── GenericGrains.cs │ │ ├── GetGrainGrains.cs │ │ ├── GrainInterfaceHierarchyGrains.cs │ │ ├── GrainService/ │ │ │ ├── GrainServiceTestGrain.cs │ │ │ └── ITestGrainService.cs │ │ ├── ImplicitStreamTestConstants.cs │ │ ├── ImplicitSubscriptionCounterGrain.cs │ │ ├── ImplicitSubscriptionWithKeyTypeGrain.cs │ │ ├── ImplicitSubscription_NonTransientError_RecoverableStream_CollectorGrain.cs │ │ ├── ImplicitSubscription_RecoverableStream_CollectorGrain.cs │ │ ├── ImplicitSubscription_TransientError_RecoverableStream_CollectorGrain.cs │ │ ├── InitialStateGrain.cs │ │ ├── KeyExtensionTestGrain.cs │ │ ├── LivenessTestGrain.cs │ │ ├── LogTestGrain.cs │ │ ├── LogTestGrainVariations.cs │ │ ├── MessageSerializationGrain.cs │ │ ├── MethodInterceptionGrain.cs │ │ ├── MultipleConstructorsSimpleGrain.cs │ │ ├── MultipleGenericParameterInterfaceImpl.cs │ │ ├── MultipleImplicitSubscriptionGrain.cs │ │ ├── MultipleSubscriptionConsumerGrain.cs │ │ ├── NoOpTestGrain.cs │ │ ├── NullStateGrain.cs │ │ ├── ObserverGrain.cs │ │ ├── ObserverWithCancellationGrain.cs │ │ ├── PolymorphicTestGrain.cs │ │ ├── ProducerEventCountingGrain.cs │ │ ├── ProgrammaticSubscribe/ │ │ │ ├── Passive_ConsumerGrain.cs │ │ │ ├── SubscribeGrain.cs │ │ │ └── TypedProducerGrain.cs │ │ ├── PromiseForwardGrain.cs │ │ ├── ProxyGrain.cs │ │ ├── ReentrancyCorrelationIdGrains.cs │ │ ├── ReentrantGrain.cs │ │ ├── ReminderTestGrain.cs │ │ ├── RequestContextTestGrain.cs │ │ ├── RetryTestGrain.cs │ │ ├── RoundtripSerializationGrain.cs │ │ ├── SampleStreamingGrain.cs │ │ ├── SchedulerGrain.cs │ │ ├── SerializationGenerationGrain.cs │ │ ├── ServiceType.cs │ │ ├── SiloRoleBasedPlacementGrain.cs │ │ ├── SimpleDIGrain.cs │ │ ├── SimpleGenericGrain.cs │ │ ├── SimpleGrain.cs │ │ ├── SimpleObserverableGrain.cs │ │ ├── SimplePersistentGrain.cs │ │ ├── SimpleStreams/ │ │ │ └── SimpleSubscriberGrain.cs │ │ ├── SlowConsumingGrains/ │ │ │ └── SlowConsumingGrain.cs │ │ ├── SpecializedSimpleGenericGrain.cs │ │ ├── StatelessWorkerExceptionGrain.cs │ │ ├── StatelessWorkerGrain.cs │ │ ├── StatelessWorkerScalingGrain.cs │ │ ├── StatelessWorkerStreamConsumerGrain.cs │ │ ├── StatelessWorkerStreamProducerGrain.cs │ │ ├── StatelessWorkerWithMayInterleaveGrain.cs │ │ ├── StatsCollectorGrain.cs │ │ ├── StreamBatchingTestConsumerGrain.cs │ │ ├── StreamCheckpoint.cs │ │ ├── StreamInterceptionGrain.cs │ │ ├── StreamingHistoryGrain.cs │ │ ├── StuckGrain.cs │ │ ├── TestGrains.csproj │ │ ├── TestPlacementStrategyFixedSiloDirector.cs │ │ ├── ValueTypeTestGrain.cs │ │ └── VersionAwarePlacementDirector.cs │ ├── TestInternalGrainInterfaces/ │ │ ├── ActivationGCTestGrainInterfaces.cs │ │ ├── IClientAddressableTestClientObject.cs │ │ ├── IClientAddressableTestGrain.cs │ │ ├── IClientAddressableTestProducer.cs │ │ ├── IClientAddressableTestRendezvousGrain.cs │ │ ├── IMultifacetFactoryTestGrain.cs │ │ ├── IMultifacetTestGrain.cs │ │ ├── IPlacementTestGrain.cs │ │ ├── ISerializerPresenceTest.cs │ │ ├── IStressTestGrain.cs │ │ └── TestInternalGrainInterfaces.csproj │ ├── TestInternalGrains/ │ │ ├── ActivateDeactivateTestGrain.cs │ │ ├── ActivationGCTestGrains.cs │ │ ├── ClientAddressableTestConsumerGrain.cs │ │ ├── ClientAddressableTestGrain.cs │ │ ├── ClientAddressableTestRendezvousGrain.cs │ │ ├── CollectionTestGrain.cs │ │ ├── EchoTaskGrain.cs │ │ ├── ErrorGrain.cs │ │ ├── ExtensionTestGrain.cs │ │ ├── HashBasedPlacementGrain.cs │ │ ├── InterlockedFlag.cs │ │ ├── MultifacetFactoryTestGrain.cs │ │ ├── MultifacetTestGrain.cs │ │ ├── ObservableGrain.cs │ │ ├── PersistenceTestGrains.cs │ │ ├── PersistentStateTestGrains.cs │ │ ├── PlacementTestGrain.cs │ │ ├── Properties/ │ │ │ └── IsExternalInit.cs │ │ ├── ReminderTestGrain2.cs │ │ ├── SerializationTestTypes.cs │ │ ├── SerializerPresenceTestGrain.cs │ │ ├── StreamLifecycleTestGrains.cs │ │ ├── StreamLifecycleTestInternalGrains.cs │ │ ├── StreamReliabilityTestGrains.cs │ │ ├── StreamTestsConstants.cs │ │ ├── StreamingGrain.cs │ │ ├── StreamingImmutabilityTestGrain.cs │ │ ├── StressTestGrain.cs │ │ ├── TestExtension.cs │ │ ├── TestGrain.cs │ │ ├── TestInternalGrains.csproj │ │ └── TimerGrain.cs │ ├── TestVersionGrains/ │ │ ├── IVersionTestGrain.cs │ │ ├── Program.cs │ │ ├── TestVersionGrains.csproj │ │ ├── VersionGrainsSiloBuilderConfigurator.cs │ │ └── VersionTestGrain.cs │ └── TestVersionGrains2/ │ └── TestVersionGrains2.csproj ├── Misc/ │ └── TestSerializerExternalModels/ │ ├── IsExternalInit.cs │ ├── Models.cs │ └── TestSerializerExternalModels.csproj ├── Orleans.Analyzers.Tests/ │ ├── AbstractPropertiesCannotBeSerializedAnalyzerTest.cs │ ├── AliasClashAttributeAnalyzerTest.cs │ ├── AlwaysInterleaveDiagnosticAnalyzerTests.cs │ ├── AssemblyInfo.cs │ ├── AtMostOneOrleansConstructorAnalyzerTest.cs │ ├── ConfigureAwaitAnalyzerTest.cs │ ├── DiagnosticAnalyzerTestBase.cs │ ├── GenerateAliasAttributesAnalyzerTest.cs │ ├── GenerateGenerateSerializerAttributeAnalyzerTest.cs │ ├── GenerateSerializationAttributesAnalyzerTest.cs │ ├── GrainInterfaceMethodReturnTypeDiagnosticAnalyzerTest.cs │ ├── GrainInterfacePropertyDiagnosticAnalyzerTest.cs │ ├── IdClashAttributeAnalyzerTest.cs │ ├── IncorrectAttributeUseAnalyzerTest.cs │ ├── NoRefParamsDiagnosticAnalyzerTest.cs │ └── Orleans.Analyzers.Tests.csproj ├── Orleans.CodeGenerator.Tests/ │ ├── Orleans.CodeGenerator.Tests.csproj │ ├── OrleansSourceGeneratorTests.cs │ └── snapshots/ │ ├── OrleansSourceGeneratorTests.TestAlias.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClass.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithDifferentAccessModifiers.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithFields.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithInheritance.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithInitOnlyProperty.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicClassWithoutNamespace.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicGrain.verified.cs │ ├── OrleansSourceGeneratorTests.TestBasicStruct.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassNestedTypes.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassPrimitiveTypes.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassPrimitiveTypesUsingFullName.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithConstructorParameters.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithFieldAndNoSetters.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithGenerateMethodSerializersAnnotation.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithGenerateSerializerAnnotation.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithNoPublicConstructors.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassWithParameterizedConstructor.verified.cs │ ├── OrleansSourceGeneratorTests.TestClassesWithGeneratedActivatorConstructorAnnotation.verified.cs │ ├── OrleansSourceGeneratorTests.TestCompoundTypeAlias.verified.cs │ ├── OrleansSourceGeneratorTests.TestGenericClass.verified.cs │ ├── OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.verified.cs │ ├── OrleansSourceGeneratorTests.TestGrainComplexGrain.verified.cs │ ├── OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithInvokableBaseType.verified.cs │ ├── OrleansSourceGeneratorTests.TestGrainMethodAnnotatedWithResponseTimeout.verified.cs │ ├── OrleansSourceGeneratorTests.TestGrainWithDifferentKeyTypes.verified.cs │ ├── OrleansSourceGeneratorTests.TestGrainWithMultipleInterfaces.verified.cs │ ├── OrleansSourceGeneratorTests.TestRecords.verified.cs │ ├── OrleansSourceGeneratorTests.TestWithOmitDefaultMemberValuesAnnotation.verified.cs │ ├── OrleansSourceGeneratorTests.TestWithSerializerTransparentAnnotation.verified.cs │ ├── OrleansSourceGeneratorTests.TestWithSuppressReferenceTrackingAttribute.verified.cs │ └── OrleansSourceGeneratorTests.TestWithUseActivatorAnnotation.verified.cs ├── Orleans.Connections.Security.Tests/ │ ├── CertificateCreator.cs │ ├── Orleans.Connections.Security.Tests.csproj │ └── TlsConnectionTests.cs ├── Orleans.Core.Tests/ │ ├── Async_AsyncExecutorWithRetriesTests.cs │ ├── Caching/ │ │ ├── ConcurrentLruSoakTests.cs │ │ └── ConcurrentLruTests.cs │ ├── ClientBuilderTests.cs │ ├── CollectionFixtures.cs │ ├── ConfigTests.cs │ ├── DebuggerHelperTests.cs │ ├── Directory/ │ │ ├── CachedGrainLocatorTests.cs │ │ ├── ClientDirectoryTests.cs │ │ ├── DhtGrainLocatorTests.cs │ │ ├── DirectoryMembershipSnapshotTests.cs │ │ ├── GrainDirectoryResolverTests.cs │ │ ├── GrainLocatorResolverTests.cs │ │ ├── MockClusterMembershipService.cs │ │ ├── MockLocalGrainDirectory.cs │ │ ├── RingRangeCollectionTests.cs │ │ └── RingRangeTests.cs │ ├── DurableJobs/ │ │ ├── DurableJobReceiverExtensionTests.cs │ │ ├── InMemoryJobQueueTests.cs │ │ ├── InMemoryJobShardManagerTests.cs │ │ └── ShardExecutorTests.cs │ ├── General/ │ │ ├── CounterAggregatorGroupTests.cs │ │ ├── HistogramAggregatorTests.cs │ │ ├── Identifiertests.cs │ │ ├── RequestContextTestsNonSiloRequired.cs │ │ ├── RingTests_Standalone.cs │ │ └── UtilsTests.cs │ ├── IdSpanTests.cs │ ├── Lease/ │ │ └── GoldenPathInMemoryLeaseProviderTests.cs │ ├── ManagementAgentTests.cs │ ├── Membership/ │ │ ├── ClusterHealthMonitorTests.cs │ │ ├── InMemoryMembershipTable.cs │ │ ├── MembershipAgentTests.cs │ │ ├── MembershipTableCleanupAgentTests.cs │ │ ├── MembershipTableManagerTests.cs │ │ ├── MembershipTableSnapshotTests.cs │ │ └── SiloHealthMonitorTests.cs │ ├── ObserverManagerTests.cs │ ├── Orleans.Core.Tests.csproj │ ├── OrleansRuntime/ │ │ ├── AsyncSerialExecutorTests.cs │ │ ├── ExceptionsTests.cs │ │ └── Streams/ │ │ ├── BestFitBalancerTests.cs │ │ ├── ResourceSelectorTestRunner.cs │ │ ├── RoundRobinSelectorTests.cs │ │ └── SubscriptionMarkerTests.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── ProviderErrorMessageTests.cs │ ├── Runtime/ │ │ ├── ActivationCollectorTests.cs │ │ └── ActivationCountPlacementDirectorTests.cs │ ├── RuntimeTypeNameFormatterTests.cs │ ├── SchedulerTests/ │ │ ├── OrleansTaskSchedulerAdvancedTests.cs │ │ ├── OrleansTaskSchedulerAdvancedTests_Set2.cs │ │ ├── OrleansTaskSchedulerBasicTests.cs │ │ └── STSchedulerLongTurnTest.cs │ ├── SchedulingHelper.cs │ ├── Serialization/ │ │ ├── BuiltInSerializerTests.cs │ │ ├── ExternalCodecTests.cs │ │ ├── MessageSerializerTests.cs │ │ ├── SerializationTests.DifferentTypes.cs │ │ └── SerializationTests.ImmutableCollections.cs │ ├── ServiceLifecycleTests.cs │ ├── SiloBuilderTests.cs │ └── Utilities/ │ ├── DelegateAsyncTimer.cs │ └── DelegateAsyncTimerFactory.cs ├── Orleans.Dashboard.Tests/ │ ├── Orleans.Dashboard.TestGrains/ │ │ ├── GenericGrain.cs │ │ ├── Orleans.Dashboard.TestGrains.csproj │ │ ├── TestCalls.cs │ │ ├── TestGenericGrain.cs │ │ ├── TestGrain.cs │ │ ├── TestGrainsHostedService.cs │ │ ├── TestMessageBasedGrain.cs │ │ ├── TestStateCompoundKeyGrain.cs │ │ ├── TestStateGrain.cs │ │ └── TestStateInMemoryGrain.cs │ └── Orleans.Dashboard.UnitTests/ │ ├── EmbeddedAssetTests.cs │ ├── GrainStateTests.cs │ ├── Orleans.Dashboard.UnitTests.csproj │ ├── RingBufferTests.cs │ ├── TraceHistoryTests.cs │ └── TypeFormatterTests.cs ├── Orleans.DefaultCluster.Tests/ │ ├── App.config │ ├── AsyncEnumerableGrainCallTests.cs │ ├── BasicActivationTests.cs │ ├── ClientAddressableTests.cs │ ├── CodeGenTests/ │ │ ├── CodeGeneratorTests_KnownAssemblyAttribute.cs │ │ ├── GeneratorGrainTest.cs │ │ └── IRuntimeCodeGenGrain.cs │ ├── CollectionFixtures.cs │ ├── ConcreteStateClassTests.cs │ ├── DeactivationTests.cs │ ├── EchoTaskGrainTests.cs │ ├── ErrorGrainTest.cs │ ├── ExternalTypesTests.cs │ ├── FSharpGrainTests.cs │ ├── GenericGrainTests.cs │ ├── GrainActivateDeactivateTests.cs │ ├── GrainFactoryTests.cs │ ├── GrainInterfaceHierarchyTests.cs │ ├── GrainReferenceCastTests.cs │ ├── GrainReferenceTest.cs │ ├── HostedClientTests.cs │ ├── InMemoryDurableJobsTests.cs │ ├── KeyExtensionTests.cs │ ├── LifecycleObserverCreationTests.cs │ ├── LocalActivationStatusCheckerTests.cs │ ├── LocalErrorGrain.cs │ ├── ManagementGrainTests.cs │ ├── MemoryStorageProviderTests.cs │ ├── Migration/ │ │ └── MigrationTests.cs │ ├── MultifacetGrainTest.cs │ ├── ObserverTests.cs │ ├── OneWayCallTests.cs │ ├── Orleans.DefaultCluster.Tests.csproj │ ├── PolymorphicInterfaceTest.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── ProviderTests.cs │ ├── ReminderTest.cs │ ├── RequestContextTest.cs │ ├── SerializationTests/ │ │ ├── JsonSerializerTests.cs │ │ ├── RoundTripSerializerTests.cs │ │ └── SerializationTests.cs │ ├── SiloRoleBasedPlacementDirectorTests.cs │ ├── SimpleGrainTests.cs │ ├── StatelessWorkerTests.cs │ └── TimerOrleansTest.cs ├── Orleans.DependencyInjection.Tests/ │ ├── App.config │ ├── Autofac/ │ │ └── DependencyInjectionGrainTestsUsingAutofac.cs │ ├── DefaultServiceProvider/ │ │ └── DependencyInjectionGrainTestsUsingDefaultServiceProvider.cs │ ├── DependencyInjectionGrainTestsRunner.cs │ ├── Orleans.DependencyInjection.Tests.csproj │ └── Properties/ │ └── AssemblyInfo.cs ├── Orleans.DurableJobs.Tests/ │ ├── DurableJobs/ │ │ ├── DurableJobTestsRunner.cs │ │ ├── IJobShardManagerTestFixture.cs │ │ ├── InMemoryJobShardManagerTestFixture.cs │ │ ├── InMemoryJobShardManagerTests.cs │ │ └── JobShardManagerTestsRunner.cs │ └── Orleans.DurableJobs.Tests.csproj ├── Orleans.EventSourcing.Tests/ │ ├── EventSourcingTests/ │ │ ├── AccountGrainTests.cs │ │ ├── ChatGrainTests.cs │ │ ├── CountersGrainPerfTests.cs │ │ ├── CountersGrainTests.cs │ │ ├── EventSourcingClusterFixture.cs │ │ ├── LogTestGrainClearTests.cs │ │ └── PersonGrainTests.cs │ └── Orleans.EventSourcing.Tests.csproj ├── Orleans.GrainDirectory.Tests/ │ ├── GrainDirectory/ │ │ ├── DistributedGrainDirectoryTests.cs │ │ └── GrainDirectoryResilienceTests.cs │ └── Orleans.GrainDirectory.Tests.csproj ├── Orleans.Journaling.Tests/ │ ├── DurableDictionaryTests.cs │ ├── DurableGrainTests.cs │ ├── DurableQueueTests.cs │ ├── DurableSetTests.cs │ ├── DurableValueTests.cs │ ├── Grains.cs │ ├── ITestDurableGrainInterface.cs │ ├── ITestMultiCollectionGrain.cs │ ├── IntegrationTestFixture.cs │ ├── JournalingAzureStorageTestConfiguration.cs │ ├── LogSegmentTests.cs │ ├── Orleans.Journaling.Tests.csproj │ ├── StateMachineManagerTests.cs │ ├── StateMachineTestBase.cs │ ├── TestDurableGrain.cs │ ├── TestMultiCollectionGrain.cs │ └── TestPerson.cs ├── Orleans.Placement.Tests/ │ ├── ActivationRebalancingTests/ │ │ ├── ControlRebalancerTests.cs │ │ ├── DynamicRebalancingTests.cs │ │ ├── RebalancerFixture.cs │ │ ├── RebalancingOptionsTests.cs │ │ ├── RebalancingTestBase.cs │ │ ├── StatePreservationRebalancingTests.cs │ │ └── StaticRebalancingTests.cs │ ├── ActivationRepartitioningTests/ │ │ ├── BlockedBloomFilterTests.cs │ │ ├── CustomToleranceTests.cs │ │ ├── DefaultToleranceTests.cs │ │ ├── FrequencyFilterTests.cs │ │ ├── FrequentEdgeCounterTests.cs │ │ ├── MaxHeapTests.cs │ │ ├── OptionsTests.cs │ │ ├── RepartitioningTestBase.cs │ │ └── TestMessageFilter.cs │ ├── General/ │ │ ├── ElasticPlacementTest.cs │ │ ├── GrainPlacementClusterChangeTests.cs │ │ ├── GrainPlacementTests.cs │ │ ├── LoadSheddingTest.cs │ │ └── ResourceOptimizedPlacementOptionsTests.cs │ ├── Orleans.Placement.Tests.csproj │ └── PlacementFilterTests/ │ ├── GrainPlacementFilterTests.cs │ ├── PreferredMatchSiloMetadataPlacementFilterDirectorTests.cs │ ├── RequiredMatchSiloMetadataPlacementFilterDirectorTests.cs │ ├── SiloMetadataPlacementFilterTests.cs │ ├── TestLocalSiloDetails.cs │ └── TestSiloMetadataCache.cs ├── Orleans.Runtime.Internal.Tests/ │ ├── ActivationsLifeCycleTests/ │ │ ├── ActivationCollectorTests.cs │ │ └── DeactivateOnIdleTests.cs │ ├── App.config │ ├── CancellationTests/ │ │ └── SystemTargetCancellationTokenTests.cs │ ├── CollectionFixtures.cs │ ├── ConcurrencyTests.cs │ ├── ConnectionStringFixture.cs │ ├── ErrorInjectionStorageProvider.cs │ ├── GatewaySelectionTest.cs │ ├── General/ │ │ ├── ConsistentRingProviderTests_Silo.cs │ │ └── RequestContextTest.cs │ ├── GeoClusterTests/ │ │ └── BasicLogTestGrainTests.cs │ ├── GrainDirectoryPartitionTests.cs │ ├── GrainLocatorActivationResiliencyTests.cs │ ├── GrainStateContainingGrainReferences.cs │ ├── LivenessTests/ │ │ └── ConsistentRingProviderTests.cs │ ├── MembershipTests/ │ │ ├── ClientIdPartitionDataRebuildTests.cs │ │ └── MembershipTableTestsBase.cs │ ├── MemoryGrainStorageTests.cs │ ├── MessageScheduling/ │ │ ├── AllowCallChainReentrancyTests.cs │ │ ├── CallChainReentrancyTestHelper.cs │ │ ├── DisabledCallChainReentrancyTestRunner.cs │ │ ├── DisabledCallChainReentrancyTests.cs │ │ └── ReentrancyTests.cs │ ├── Orleans.Runtime.Internal.Tests.csproj │ ├── OrleansRuntime/ │ │ └── StuckGrainTests.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── IsExternalInit.cs │ ├── ReadMe.md │ ├── RemindersTest/ │ │ └── ReminderTableTestsBase.cs │ ├── RetryHelper.cs │ ├── SiloMetadataTests/ │ │ └── SiloMetadataTests.cs │ ├── StorageTests/ │ │ ├── CommonStorageTests.cs │ │ ├── CommonStorageUtilities.cs │ │ ├── HierarchicalKeyStoreTests.cs │ │ ├── LocalStoreTests.cs │ │ ├── PersistenceGrainTests.cs │ │ ├── RandomUtilities.cs │ │ ├── Range.cs │ │ ├── StorageProviders/ │ │ │ ├── BaseJSONStorageProvider.cs │ │ │ ├── FileStorageProvider.cs │ │ │ └── IJSONStateDataManager.cs │ │ ├── SymbolSet.cs │ │ └── TestDataSets/ │ │ ├── GrainTypeGenerator.cs │ │ ├── StorageDataSet2CyrillicIdsAndGrainNames.cs │ │ ├── StorageDataSetGeneric.cs │ │ ├── StorageDataSetPlain.cs │ │ ├── StorageDateSetGenericHuge.cs │ │ ├── TestState1.cs │ │ └── TestStateGeneric1.cs │ ├── TestRunners/ │ │ └── GrainPersistenceTestRunner.cs │ ├── TestStoreGrainState.cs │ ├── TestUtils.cs │ ├── TimeoutTests.cs │ └── TimerTests/ │ ├── ReminderTests_Base.cs │ └── ReminderTests_TableGrain.cs ├── Orleans.Runtime.Tests/ │ ├── ActivationTracingTests.cs │ ├── ActivityPropagationTests.cs │ ├── App.config │ ├── CancellationTests/ │ │ ├── CancellationTokenTests.cs │ │ ├── GrainCancellationTokenTests.cs │ │ └── ObserverCancellationTokenTests.cs │ ├── ClientConnectionTests/ │ │ ├── ClientConnectionEventTests.cs │ │ ├── ClientConnectionRegisteredServiceEventTests.cs │ │ ├── ClusterClientTests.cs │ │ ├── GatewayConnectionTests.cs │ │ ├── InvalidPreambleConnectionTests.cs │ │ └── StallConnectionTests.cs │ ├── CollectionFixtures.cs │ ├── Directories/ │ │ ├── GrainDirectoryTests.cs │ │ └── MultipleGrainDirectoriesTests.cs │ ├── DuplicateActivationsTests.cs │ ├── ExceptionPropagationTests.cs │ ├── Forwarding/ │ │ └── ShutdownSiloTests.cs │ ├── GrainActivatorTests.cs │ ├── GrainCallFilterTests.cs │ ├── GrainLevelCallFilterTests.cs │ ├── GrainServiceTests/ │ │ ├── GrainServiceTests.cs │ │ └── TestGrainService.cs │ ├── HeterogeneousSilosTests/ │ │ ├── HeterogeneousTests.cs │ │ └── UpgradeTests/ │ │ ├── RuntimeStrategyChangeTests.cs │ │ ├── UpgradeTests.cs │ │ ├── UpgradeTestsBase.cs │ │ └── VersionPlacementTests.cs │ ├── JsonNodeGrainTests.cs │ ├── Lifecycle/ │ │ └── LifecycleTests.cs │ ├── LocalhostSiloTests.cs │ ├── LogFomatterTests.cs │ ├── ManagementGrainTests.cs │ ├── MembershipTests/ │ │ ├── LivenessTests.cs │ │ └── SilosStopTests.cs │ ├── MinimalReminderTests.cs │ ├── OneWayDeactivationTests.cs │ ├── Orleans.Runtime.Tests.csproj │ ├── Placement/ │ │ └── CustomPlacementTests.cs │ ├── Program.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── IsExternalInit.cs │ ├── ReadMe.md │ ├── StartupTaskTests.cs │ ├── StatelessWorkerActivationTests.cs │ ├── StorageFacet/ │ │ ├── Feature.Abstractions/ │ │ │ ├── ExampleStorageAttribute.cs │ │ │ └── IExampleStorage.cs │ │ ├── Feature.Implementations/ │ │ │ ├── BlobExampleStorage.cs │ │ │ └── TableExampleStorage.cs │ │ ├── Feature.Infrastructure/ │ │ │ ├── ExampleStorageAttributeMapper.cs │ │ │ ├── ExampleStorageExtensions.cs │ │ │ └── NamedExampleStorageFactory.cs │ │ ├── StorageFacetGrain.cs │ │ └── StorageFacetTests.cs │ ├── TestDirectoryCache.cs │ └── TransportTests/ │ ├── TransportTestsBase.cs │ └── UnixSocketTransportTests.cs ├── Orleans.Serialization.FSharp.Tests/ │ ├── Orleans.Serialization.FSharp.Tests.fsproj │ └── SerializationTests.fs ├── Orleans.Serialization.UnitTests/ │ ├── ArcBufferWriterTests.cs │ ├── Buffers/ │ │ └── Adaptors/ │ │ └── PooledBufferStreamTests.cs │ ├── BuiltInCodecTests.cs │ ├── ConverterTests.cs │ ├── GeneratedSerializerTests.cs │ ├── GenericBaseClassTest.cs │ ├── ISerializableTests.cs │ ├── InvokableTestInterfaces.cs │ ├── ManualVersionToleranceTests.cs │ ├── MemoryPackSerializerTests.cs │ ├── MessagePackSerializerTests.cs │ ├── Models.cs │ ├── NewtonsoftJsonCodecTests.cs │ ├── NumericsWideningAndNarrowingTests.cs │ ├── Orleans.Serialization.UnitTests.csproj │ ├── PolymorphismTests.cs │ ├── PooledBufferTests.cs │ ├── Properties/ │ │ ├── IsExternalInit.cs │ │ └── RequiredMemberAttribute.cs │ ├── ProtobufSerializerTests.cs │ ├── ReaderWriterTests.cs │ ├── RecordSerializationTests.cs │ ├── Request.cs │ ├── TypeEncodingTests.cs │ └── protobuf-model.proto ├── Orleans.Streaming.Tests/ │ ├── Orleans.Streaming.Tests.csproj │ ├── OrleansRuntime/ │ │ └── Streams/ │ │ ├── CachedMessageBlockTests.cs │ │ ├── FixedSizeBufferTests.cs │ │ ├── ObjectPoolTests.cs │ │ └── PooledQueueCacheTests.cs │ ├── StreamingTests/ │ │ ├── BroadcastChannels/ │ │ │ └── BroadcastChannelTests.cs │ │ ├── ClientStreamTestRunner.cs │ │ ├── ControllableStreamGeneratorProviderTests.cs │ │ ├── ControllableStreamProviderTests.cs │ │ ├── DeactivationTestRunner.cs │ │ ├── Filtering/ │ │ │ └── StreamFilteringTestsBase.cs │ │ ├── GeneratedStreamRecoveryTests.cs │ │ ├── ImplicitSubscriptionKeyTypeGrainTests.cs │ │ ├── ImplicitSubscritionRecoverableStreamTestRunner.cs │ │ ├── MemoryProgrammaticSubcribeTests.cs │ │ ├── MemoryStreamBatchingTests.cs │ │ ├── MemoryStreamCacheMissTests.cs │ │ ├── MemoryStreamProviderBatchedClientTests.cs │ │ ├── MemoryStreamProviderClientTests.cs │ │ ├── MemoryStreamResumeTests.cs │ │ ├── MultipleStreamsTestRunner.cs │ │ ├── PlugableQueueBalancerTests/ │ │ │ ├── LeaseBasedQueueBalancer.cs │ │ │ ├── LeaseManagerGrain.cs │ │ │ ├── PluggableQueueBalancerTestBase.cs │ │ │ └── PluggableQueueBalancerTestsWithMemoryStreamProvider.cs │ │ ├── ProgrammaticSubscribeTests/ │ │ │ ├── ProgrammaticSubscribeTestsRunner.cs │ │ │ └── SubscriptionObserverWithImplicitSubscribingTestRunner.cs │ │ ├── PubSubRendezvousGrainTests.cs │ │ ├── SampleStreamingTests.cs │ │ ├── SingleStreamTestRunner.cs │ │ ├── StatelessWorkersStreamTests.cs │ │ ├── StreamBatchingTestRunner.cs │ │ ├── StreamGeneratorProviderTests.cs │ │ ├── StreamProvidersTests.cs │ │ ├── StreamPubSubReliabilityTests.cs │ │ ├── StreamTestHelperClasses.cs │ │ ├── StreamTestUtils.cs │ │ ├── StreamingCacheMissTests.cs │ │ ├── StreamingResumeTests.cs │ │ ├── SubscriptionMultiplicityTestRunner.cs │ │ └── SystemTargetRouteTests.cs │ └── TestStreamProviders/ │ └── Controllable/ │ └── ControllableTestStreamProvider.cs ├── TestInfrastructure/ │ ├── Orleans.TestingHost.Tests/ │ │ ├── App.config │ │ ├── Grains/ │ │ │ └── SimpleGrain.cs │ │ ├── Orleans.TestingHost.Tests.csproj │ │ └── TestClusterTests.cs │ └── TestExtensions/ │ ├── BaseClusterFixture.cs │ ├── BaseInProcessTestClusterFixture.cs │ ├── DefaultClusterFixture.cs │ ├── HierarchicalKeyStore.cs │ ├── HostedTestClusterBase.cs │ ├── ILocalDataStore.cs │ ├── MockStorageProvider.cs │ ├── OrleansTestingBase.cs │ ├── Runners/ │ │ └── GoldenPathLeaseProviderTestRunner.cs │ ├── SerializationTestEnvironment.cs │ ├── SiloAddressUtils.cs │ ├── TestCategory.cs │ ├── TestClusterPerTest.cs │ ├── TestConstants.cs │ ├── TestDefaultConfiguration.cs │ ├── TestEnvironmentFixture.cs │ ├── TestExtensions.csproj │ ├── TestOutputHelperExtensions.cs │ ├── TestUtils.cs │ └── XunitLoggerProvider.cs ├── TesterInternal/ │ └── ActivationsLifeCycleTests/ │ └── ActivationCancellationTests.cs ├── Transactions/ │ ├── Orleans.Transactions.Azure.Test/ │ │ ├── AssemblyInfo.cs │ │ ├── AzureTransactionalStateStorageTests.cs │ │ ├── ConsistencySkewedClockTests.cs │ │ ├── ConsistencyTests.cs │ │ ├── FaultInjection/ │ │ │ ├── ControlledInjection/ │ │ │ │ └── TransactionFaultInjectionTests.cs │ │ │ └── RandomInjection/ │ │ │ └── ConsistencyFaultInjectionTests.cs │ │ ├── GoldenPathTests.cs │ │ ├── GrainFaultTests.cs │ │ ├── Orleans.Transactions.Azure.Test.csproj │ │ ├── SkewedClockGoldenPathTransactionTests.cs │ │ ├── TestFixture.cs │ │ ├── TocFaultTransactionTests.cs │ │ ├── TocGoldenPathTests.cs │ │ ├── TransactionConcurrencyTests.cs │ │ ├── TransactionRecoveryTests.cs │ │ └── TransactionScopeTests.cs │ └── Orleans.Transactions.Tests/ │ ├── AssemblyInfo.cs │ ├── Disabled/ │ │ └── DisabledTransactionsTests.cs │ ├── Memory/ │ │ ├── ConsistencySkewedClockTests.cs │ │ ├── ConsistencyTests.cs │ │ ├── GoldenPathTransactionMemoryTests.cs │ │ ├── GrainFaultTransactionMemoryTests.cs │ │ ├── MemoryTransactionsFixture.cs │ │ ├── SkewedClockGoldenPathTransactionMemoryTests.cs │ │ ├── TocFaultTransactionMemoryTests.cs │ │ ├── TocGoldenPathMemoryTests.cs │ │ ├── TransactionAttributionTest.cs │ │ └── TransactionConcurrencyTests.cs │ ├── Orleans.Transactions.Tests.csproj │ ├── Runners/ │ │ └── TransactionAttributionTestRunner.cs │ └── TransactionOverloadDetectorTests.cs └── xunit.runner.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .azure/pipelines/build.yaml ================================================ trigger: branches: include: - main - 3.x paths: exclude: - samples schedules: - cron: "0 0 * * *" displayName: 'Daily midnight build (including CodeQL)' branches: include: - main - 3.x always: true parameters: - name: build_configuration displayName: Build configuration type: string default: Release values: - Release - Debug - name: version_prefix displayName: Version prefix type: string default: 10.0.0 - name: include_suffix displayName: Append version suffix type: boolean default: true - name: version_suffix displayName: Version suffix type: string default: ci.$(Build.BuildNumber) - name: skip_test displayName: Skip tests type: boolean default: false - name: frameworks displayName: Frameworks type: object default: - net8.0 - net10.0 - name: tests_categories displayName: Test categories type: object default: - BVT - SlowBVT - Functional - name: runCodeQL3000 default: false displayName: Run CodeQL3000 tasks type: boolean variables: - template: templates/vars.yaml resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release extends: ${{ if eq(variables['System.TeamProject'], 'GitHub - PR Builds') }}: template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates ${{ else }}: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: settings: skipBuildTagsForGitHubPullRequests: true sdl: autobaseline: enableForGitHub: true pool: name: $(pool_name) image: $(pool_image) os: windows stages: - stage: build_test displayName: Build and Tests jobs: - template: /.azure/pipelines/templates/build.yaml@self parameters: build_configuration: ${{ parameters.build_configuration }} version_prefix: ${{ parameters.version_prefix }} include_suffix: ${{ parameters.include_suffix }} version_suffix: ${{ parameters.version_suffix }} codesign: false skip_test: ${{ parameters.skip_test }} publish_nightly: false publish_nuget: false frameworks: ${{ parameters.frameworks }} tests_categories: ${{ parameters.tests_categories }} runCodeQL3000: ${{ parameters.runCodeQL3000 }} ================================================ FILE: .azure/pipelines/github-mirror.yaml ================================================ trigger: branches: include: - '*' pr: none schedules: - cron: '0 */2 * * *' displayName: Scheduled sync branches: include: - main resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: name: orleans-build-hosted-pool image: orleans-build-image os: windows stages: - stage: clone displayName: Mirror GitHub repo jobs: - job: steps: - checkout: self displayName: Clone GitHub repo fetchDepth: 0 fetchTags: true - task: Powershell@2 displayName: Set git config inputs: targetType: 'inline' script: | git config --global user.email "${env:GIT_USEREMAIL}" git config --global user.name "${env:GIT_USERNAME}" - task: PowerShell@2 displayName: Push to remote repo inputs: targetType: 'inline' script: | foreach ($ref in ${env:REFS_MIRROR}.Split(' ')) { git config --add remote.origin.fetch "$ref" } git fetch --all git remote add --mirror=fetch mirror https://${Env:SYSTEM_ACCESSTOKEN}@${env:REMOTE_REPOSITORY} git push --force --all --progress mirror env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) ================================================ FILE: .azure/pipelines/nightly-main.yaml ================================================ trigger: none pr: none schedules: - cron: "0 0 * * *" displayName: Publish nightly packages branches: include: - main always: false parameters: - name: publish_nuget displayName: Publish to nuget.org type: boolean default: false - name: publish_nightly displayName: Publish to orleans-nightly type: boolean default: true - name: version_prefix displayName: Version prefix type: string default: 10.0.0 - name: version_suffix displayName: Version suffix type: string default: nightly.$(Build.BuildNumber) - name: include_suffix displayName: Append version suffix type: boolean default: true variables: - template: templates/vars.yaml resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release extends: ${{ if eq(variables['System.TeamProject'], 'GitHub - PR Builds') }}: template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates ${{ else }}: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: sdl: policheck: enabled: true tsa: enabled: true settings: skipBuildTagsForGitHubPullRequests: true pool: name: $(pool_name) image: $(pool_image) os: windows stages: - stage: build_test displayName: Build and Tests jobs: - template: /.azure/pipelines/templates/build.yaml@self parameters: build_configuration: Release version_prefix: ${{ parameters.version_prefix }} include_suffix: ${{ parameters.include_suffix }} version_suffix: ${{ parameters.version_suffix }} codesign: true publish_nightly: ${{ parameters.publish_nightly }} publish_nuget: ${{ parameters.publish_nuget }} skip_test: true ================================================ FILE: .azure/pipelines/templates/build.yaml ================================================ parameters: - name: build_configuration displayName: Build configuration type: string default: Release values: - Release - Debug - name: version_prefix displayName: Version prefix type: string default: 9.0.0 - name: include_suffix displayName: Append version suffix type: boolean default: true - name: version_suffix displayName: Version suffix type: string default: ci.$(Build.BuildNumber) - name: codesign displayName: Enable code signing type: boolean default: false - name: skip_test displayName: Skip tests type: boolean default: false - name: publish_nightly displayName: Publish to orleans-nightly type: boolean default: false - name: publish_nuget displayName: Publish to nuget.org type: boolean default: false - name: frameworks displayName: Frameworks type: object default: - net8.0 - net10.0 - name: tests_categories displayName: Test categories type: object default: - BVT - SlowBVT - Functional - name: runCodeQL3000 default: false displayName: Run CodeQL3000 tasks type: boolean jobs: # Approval needed for publishing to nuget.org - job: PreDeploymentApprovalJob displayName: Pre-Deployment Approval timeoutInMinutes: 2880 ${{ if and(eq(parameters.codesign, true), eq(parameters.publish_nuget, true)) }}: pool: server steps: - ${{ if and(eq(parameters.codesign, true), eq(parameters.publish_nuget, true)) }}: - task: ManualValidation@1 inputs: notifyUsers: ${{ variables.notifyUsers }} approvers: ${{ variables.approvers }} - ${{ if not(and(eq(parameters.codesign, true), eq(parameters.publish_nuget, true))) }}: - script: echo "Skipping pre-deployment approval" # Build, sign dlls, build nuget pkgs, then sign them - job: Build displayName: Build and create NuGet packages dependsOn: PreDeploymentApprovalJob variables: ${{ if eq(parameters.codesign, true) }}: microbuild_signing: true publishVstsFeed: 'public/orleans-nightly' ${{ else }}: microbuild_signing: false ${{ if ne(variables['System.TeamProject'], 'GitHub - PR Builds') }}: templateContext: outputs: - output: pipelineArtifact targetPath: '$(build.sourcesdirectory)/Artifacts/${{parameters.build_configuration}}' artifactName: nuget # Publish packages to nightly - ${{ if and(eq(parameters.codesign, true), eq(parameters.publish_nightly, true)) }}: - output: nuget useDotNetTask: false packageParentPath: $(Pipeline.Workspace) packagesToPush: $(build.sourcesdirectory)/Artifacts/${{parameters.build_configuration}}/**/*.nupkg nuGetFeedType: internal publishVstsFeed: $(publishVstsFeed) allowPackageConflicts: true - ${{ if and(eq(parameters.codesign, true), eq(parameters.publish_nuget, true)) }}: - output: nuget condition: succeeded() useDotNetTask: false packageParentPath: $(Pipeline.Workspace) packagesToPush: $(build.sourcesdirectory)/Artifacts/${{parameters.build_configuration}}/**/*.nupkg nuGetFeedType: external publishFeedCredentials: dotnet-orleans-nuget publishPackageMetadata: true allowPackageConflicts: true steps: - ${{ if eq(variables.microbuild_signing, true) }}: - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: signType: real zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca env: TeamName: Orleans MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' - checkout: self - task: NodeTool@0 displayName: 'Use Node.js' inputs: versionSpec: '20.x' - task: npmAuthenticate@0 displayName: 'Authenticate npm feeds' inputs: workingFile: '$(Build.SourcesDirectory)/src/Dashboard/Orleans.Dashboard/.azdo/.npmrc' - task: UseDotNet@2 displayName: 'Use .NET Core sdk' inputs: useGlobalJson: true - ${{ if eq(variables.runCodeQL3000, 'true') }}: - task: CodeQL3000Init@0 displayName: CodeQL Initialize # This task only tags a build if it actually does CodeQL3000 work. # Those tasks no-op while the analysis is considered up to date i.e. for runs w/in a few days of each other. - script: "echo ##vso[build.addbuildtag]CodeQL3000" displayName: 'Set CI CodeQL3000 tag' condition: ne(variables.CODEQL_DIST,'') - task: DotNetCoreCLI@2 displayName: Build inputs: command: build arguments: '$(build_flags) /bl:${{parameters.build_configuration}}-Build.binlog /p:Configuration=${{parameters.build_configuration}} $(solution)' env: VersionPrefix: ${{parameters.version_prefix}} ${{ if eq(parameters.include_suffix, true) }}: VersionSuffix: ${{parameters.version_suffix}} OfficialBuild: $(official_build) - ${{ if eq(variables.runCodeQL3000, 'true') }}: - task: CodeQL3000Finalize@0 displayName: CodeQL Finalize - task: CmdLine@2 displayName: Pack inputs: script: 'dotnet pack --no-build --no-restore $(build_flags) /bl:${{parameters.build_configuration}}-Pack.binlog /p:Configuration=${{parameters.build_configuration}} $(solution)' env: VersionPrefix: ${{parameters.version_prefix}} ${{ if eq(parameters.include_suffix, true) }}: VersionSuffix: ${{parameters.version_suffix}} OfficialBuild: $(official_build) # Signing - ${{ if eq(variables.microbuild_signing, true) }}: - task: NuGetCommand@2 displayName: "Install packages for signing" inputs: command: 'custom' arguments: 'install sign/packages.config -ConfigFile sign/Nuget.Config' - task: MSBuild@1 displayName: "Sign binaries and packages" inputs: solution: sign/sign.proj msbuildArguments: -t:sign -p:Configuration=${{parameters.build_configuration}} # Tests - ${{ if and(eq(parameters.skip_test, false), ne(variables.runCodeQL3000, 'true')) }}: - ${{ each category in parameters.tests_categories }}: - ${{ each framework in parameters.frameworks }}: - job: displayName: ${{category}} on ${{framework}} timeoutInMinutes: 120 dependsOn: Build templateContext: outputs: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/test_outputs_${{category}}_${{framework}}_$(Build.BuildId)' artifactName: 'test_outputs_${{category}}_${{framework}}_$(System.JobAttempt)' condition: succeededOrFailed() variables: # Transform "net8.0" to "8.0.x" for the second DotNetCoreCLI@2 testFrameworkToInstall: ${{format('{0}.x', replace(upper(framework), 'NET', ''))}} steps: - checkout: self - task: NodeTool@0 displayName: 'Use Node.js' inputs: versionSpec: '20.x' - task: npmAuthenticate@0 displayName: 'Authenticate npm feeds' inputs: workingFile: '$(Build.SourcesDirectory)/src/Dashboard/Orleans.Dashboard/.azdo/.npmrc' - task: UseDotNet@2 inputs: useGlobalJson: true displayName: 'Use .NET Core sdk' - task: DotNetCoreCLI@2 displayName: Build inputs: command: build arguments: '$(build_flags) /bl:${{parameters.build_configuration}}-Build.binlog /p:Configuration=${{parameters.build_configuration}} $(solution)' - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - task: AzureCLI@2 displayName: Azure Login env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) AZURE_CORE_USE_MSAL_HTTP_CACHE: "false" inputs: azureSubscription: 'dotnet-orleans-test' useGlobalConfig: true addSpnToEnvironment: true scriptType: pscore scriptLocation: inlineScript inlineScript: | # Extract TenantId and ServicePrincipalId from the connection Write-Host "##vso[task.setvariable variable=tenantId]$($env:tenantId)" Write-Host "##vso[task.setvariable variable=servicePrincipalId;issecret=true]$($env:servicePrincipalId)" # AzurePipelinesCredential expect the GUID of the connection, not the name, so let's get it here Write-Host "##vso[task.setvariable variable=serviceConnectionId]$($env:SERVICE_CONNECTION_ID)" Get-ChildItem env: - task: UseDotNet@2 # We need this step because the tested framework can be different from the build framework inputs: version: ${{variables.testFrameworkToInstall}} displayName: 'Install .NET Core sdk used in tests' - task: DotNetCoreCLI@2 displayName: Test env: ${{ if eq(variables['System.TeamProject'], 'internal') }}: AZURE_TENANT_ID: $(tenantId) AZURE_CLIENT_ID: $(servicePrincipalId) SERVICE_CONNECTION_ID: $(serviceConnectionId) SYSTEM_ACCESSTOKEN: $(System.AccessToken) inputs: command: 'test' testRunTitle: ${{category}} on ${{framework}} arguments: '--no-build --logger "trx;LogFilePrefix=testresults-${{framework}}-${{category}}" --framework ${{framework}} --configuration "${{parameters.build_configuration}}" --filter Category=${{category}} --blame-crash-dump-type full --blame-hang-timeout 10m --blame-hang-dump-type full -- -parallel none -noshadow' publishTestResults: false # Doesn't merge correctly, use the explicit PublishTestResults task instead - task: PublishTestResults@2 displayName: Publishing test results condition: succeededOrFailed() inputs: testResultsFormat: VSTest testResultsFiles: '**/testresults-*.trx' mergeTestResults: true testRunTitle: ${{category}} on ${{framework}} - task: CopyFiles@2 displayName: 'Copy test logs' condition: succeededOrFailed() inputs: Contents: '**\*.log' TargetFolder: '$(Build.ArtifactStagingDirectory)/test_outputs_${{category}}_${{framework}}_$(Build.BuildId)' OverWrite: true - task: CopyFiles@2 displayName: 'Copy crash dumps' condition: succeededOrFailed() inputs: Contents: '**\*.dmp' TargetFolder: '$(Build.ArtifactStagingDirectory)/test_outputs_${{category}}_${{framework}}_$(Build.BuildId)' OverWrite: true ================================================ FILE: .azure/pipelines/templates/vars.yaml ================================================ # It seems that variables must be defined in their own file when using templates variables: build_flags: ' /m /v:m' solution: 'Orleans.slnx' codesign_runtime: '2.1.x' GDN_SUPPRESS_FORKED_BUILD_WARNING: true # Avoid warning "Guardian is not supported for builds from forked GitHub repositories" MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' # Auto-injection is not necessary because the tasks are explicitly included where they're enabled. Codeql.SkipTaskAutoInjection: true ${{ if eq(variables['System.TeamProject'], 'GitHub - PR Builds') }}: pool_name: 'orleans-pr-hosted-pool' pool_image: 'orleans-build-image' official_build: false ${{ else }}: ${{ if eq(variables['System.TeamProject'], 'internal') }}: pool_name: 'NetCore1ESPool-Internal' pool_image: '1es-windows-2022' ${{ else }}: pool_name: 'orleans-build-hosted-pool' pool_image: 'orleans-build-image' official_build: true # Do not let CodeQL3000 Extension gate scan frequency. Codeql.Cadence: 0 # Enable CodeQL3000 unconditionally so it may be run on any branch. Codeql.Enabled: true # Ignore test and infrastructure code. Codeql.SourceRoot: src # CodeQL3000 needs this plumbed along as a variable to enable TSA. Don't use TSA in manual builds. Codeql.TSAEnabled: ${{ eq(variables['Build.Reason'], 'Schedule') }} # Default expects tsaoptions.json under SourceRoot. Codeql.TSAOptionsPath: '$(Build.SourcesDirectory)/.config/tsaoptions.json' # Do not slow builds down w/ the CodeQL3000 tasks unless this is a nightly build or it's requested. runCodeQL3000: ${{ or(eq(variables['Build.Reason'], 'Schedule'), and(eq(variables['Build.Reason'], 'Manual'), eq(parameters.runCodeQL3000, 'true'))) }} ================================================ FILE: .azuredevops/dependabot.yml ================================================ version: 2 # Disabling dependabot on Azure DevOps as this is a mirrored repo. Updates should go through github. enable-campaigned-updates: false enable-security-updates: false ================================================ FILE: .config/1espt/PipelineAutobaseliningConfig.yml ================================================ ## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. pipelines: 128: retail: source: credscan: lastModifiedDate: 2024-09-11 eslint: lastModifiedDate: 2024-09-11 armory: lastModifiedDate: 2024-09-11 binary: credscan: lastModifiedDate: 2024-10-16 binskim: lastModifiedDate: 2024-10-16 1152: retail: source: credscan: lastModifiedDate: 2024-07-30 eslint: lastModifiedDate: 2024-07-30 psscriptanalyzer: lastModifiedDate: 2024-07-30 armory: lastModifiedDate: 2024-07-30 binary: credscan: lastModifiedDate: 2024-07-30 binskim: lastModifiedDate: 2024-07-30 spotbugs: lastModifiedDate: 2024-07-30 1397: retail: source: credscan: lastModifiedDate: 2024-08-02 eslint: lastModifiedDate: 2024-08-02 psscriptanalyzer: lastModifiedDate: 2024-08-02 armory: lastModifiedDate: 2024-08-02 binary: credscan: lastModifiedDate: 2024-08-02 binskim: lastModifiedDate: 2024-08-02 spotbugs: lastModifiedDate: 2024-08-02 ================================================ FILE: .config/guardian/.gdnbaselines ================================================ { "properties": { "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines" }, "version": "1.0.0", "baselines": { "default": { "name": "default", "createdDate": "2024-08-02 00:42:13Z", "lastUpdatedDate": "2024-08-02 00:42:13Z" } }, "results": { "492bb9459285aa02a23f6a6779e5be2eb5abf65a32cc613280b7ed722cd4a98e": { "signature": "492bb9459285aa02a23f6a6779e5be2eb5abf65a32cc613280b7ed722cd4a98e", "alternativeSignatures": [], "target": "MicroBuild/Plugins/nuget.config", "line": 9, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0060", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "caaa03f03b1c7c073308d50dd596413120a75aa9f7188f1747597683f6e3b436": { "signature": "caaa03f03b1c7c073308d50dd596413120a75aa9f7188f1747597683f6e3b436", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/dlabnugetcert.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "37bf0118bbd656e6d30e7e3f731bac3e88caa0067868f32526cba611de7fb34a": { "signature": "37bf0118bbd656e6d30e7e3f731bac3e88caa0067868f32526cba611de7fb34a", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/dynamicsha1.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "0df13d5b0e02d27610c5d26fbf8ada8c29161046087a88e59971d1fdea20c88d": { "signature": "0df13d5b0e02d27610c5d26fbf8ada8c29161046087a88e59971d1fdea20c88d", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/dynamicsha2.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "709b4fdd6d0142712f9f69b3192563e149ccc7cb8762be291bbb567bb2d42d86": { "signature": "709b4fdd6d0142712f9f69b3192563e149ccc7cb8762be291bbb567bb2d42d86", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/testdlab.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "4eaff6af7a166277db7356b713f44571532010adcfa3b81e77d34370f054a692": { "signature": "4eaff6af7a166277db7356b713f44571532010adcfa3b81e77d34370f054a692", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/testdlabsha2.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "bf291646a1ae462fe07d1e4fe7cf682ececdbac480c9242a9fe60b5607842dbc": { "signature": "bf291646a1ae462fe07d1e4fe7cf682ececdbac480c9242a9fe60b5607842dbc", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/vsmsappx.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "f85ca33e9ffc2662390548a77947fa9c1c0a66aa194a61d381fdbce391769435": { "signature": "f85ca33e9ffc2662390548a77947fa9c1c0a66aa194a61d381fdbce391769435", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/WinBlue.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "f64a177dcdd266dfecc33e3a55c01c58ec6c097f3453a70cf7b9be0bb40ea30c": { "signature": "f64a177dcdd266dfecc33e3a55c01c58ec6c097f3453a70cf7b9be0bb40ea30c", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/WP223.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" }, "26564b328f56f22a3050e620db0d9bc693ba20720e04e99e76625da1bacc92b8": { "signature": "26564b328f56f22a3050e620db0d9bc693ba20720e04e99e76625da1bacc92b8", "alternativeSignatures": [], "target": "MicroBuild/Plugins/MicroBuild.Plugins.Signing.1.1.950/build/tools/MobileTools/7Sign/tcb.pfx", "line": 1, "memberOf": [ "default" ], "tool": "credscan", "ruleId": "CSCAN-GENERAL0020", "createdDate": "2024-08-02 00:42:13Z", "expirationDate": "2025-01-19 00:45:11Z", "justification": "This error is baselined with an expiration date of 180 days from 2024-08-02 00:45:11Z" } } } ================================================ FILE: .config/tsaoptions.json ================================================ { "areaPath": "DevDiv\\ASP.NET Core\\Orleans", "codebaseName": "Orleans", "instanceUrl": "https://devdiv.visualstudio.com/", "iterationPath": "DevDiv", "notificationAliases": [ "bpetit@microsoft.com", "reuben.bond@microsoft.com" ], "projectName": "DEVDIV", "repositoryName": "Orleans", "template": "TFSDEVDIV" } ================================================ FILE: .devcontainer/devcontainer.json ================================================ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/devcontainers/images/tree/main/src/dotnet { "name": "Orleans", "image": "mcr.microsoft.com/devcontainers/dotnet:latest", // Features to add to the dev container. More info: https://containers.dev/features "features": { "ghcr.io/devcontainers/features/docker-in-docker": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, "hostRequirements": { "cpus": 4, "memory": "8gb", "storage": "32gb" }, "customizations": { "vscode": { "extensions": [ "ms-dotnettools.csdevkit", "GitHub.copilot" ], "settings": { "remote.autoForwardPorts": true, "remote.autoForwardPortsSource": "hybrid", "remote.otherPortsAttributes": { "onAutoForward": "ignore" }, "dotnet.defaultSolution": "Orleans.slnx" } } }, "onCreateCommand": "dotnet restore", "postStartCommand": "dotnet dev-certs https --trust || true" } ================================================ FILE: .editorconfig ================================================ ; EditorConfig to support per-solution formatting. ; Use the EditorConfig VS add-in to make this work. ; http://editorconfig.org/ ; ; Here are some resources for what's supported for .NET/C# ; https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers ; https://learn.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference ; ; Be **careful** editing this because some of the rules don't support adding a severity level ; For instance if you change to `dotnet_sort_system_directives_first = true:suggestion` (adding `:warning`) ; then the rule will be silently ignored. ; This is the default for the codeline. root = true [*] indent_style = space charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true spelling_exclusion_path = spelling.dic # Verify settings [*.{received,verified}.{cs}] charset = "utf-8-bom" end_of_line = lf indent_size = unset indent_style = unset insert_final_newline = false tab_width = unset trim_trailing_whitespace = false [*.{cs,csx,cake}] indent_size = 4 dotnet_sort_system_directives_first = true # Don't use this. qualifier dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion # use int x = .. over Int32 dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion # use int.MaxValue over Int32.MaxValue dotnet_style_predefined_type_for_member_access = true:suggestion # Require var all the time. csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion # Disallow throw expressions. csharp_style_throw_expression = false:suggestion # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true # Namespace settings csharp_style_namespace_declarations = file_scoped # Brace settings csharp_prefer_braces = true # Prefer curly braces even for one line of code dotnet_sort_system_directives_first = true dotnet_style_coalesce_expression = true : suggestion dotnet_style_collection_initializer = true : suggestion dotnet_style_explicit_tuple_names = true : suggestion dotnet_style_null_propagation = true : suggestion dotnet_style_object_initializer = true : suggestion dotnet_style_parentheses_in_arithmetic_binary_operators =never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_binary_operators =never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_operators =never_if_unnecessary:suggestion dotnet_style_parentheses_in_relational_binary_operators =never_if_unnecessary:suggestion dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion dotnet_style_predefined_type_for_member_access = true : suggestion dotnet_style_prefer_auto_properties = true : suggestion dotnet_style_prefer_compound_assignment = true : suggestion dotnet_style_prefer_conditional_expression_over_assignment = true : suggestion dotnet_style_prefer_conditional_expression_over_return = true : suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true : suggestion dotnet_style_prefer_inferred_tuple_names = true : suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true : suggestion dotnet_style_qualification_for_event =false:suggestion dotnet_style_qualification_for_field =false:suggestion dotnet_style_qualification_for_method =false:suggestion dotnet_style_qualification_for_property =false:suggestion dotnet_style_require_accessibility_modifiers = always : suggestion # Naming Symbols # constant_fields - Define constant fields dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const # non_private_readonly_fields - Define public, internal and protected readonly fields dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly # static_readonly_fields - Define static and readonly fields dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly # private_readonly_fields - Define private readonly fields dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly # public_internal_fields - Define public and internal fields dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal dotnet_naming_symbols.public_internal_fields.applicable_kinds = field # private_protected_fields - Define private and protected fields dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected dotnet_naming_symbols.private_protected_fields.applicable_kinds = field # public_symbols - Define any public symbol dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate # parameters - Defines any parameter dotnet_naming_symbols.parameters.applicable_kinds = parameter # non_interface_types - Defines class, struct, enum and delegate types dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate # interface_types - Defines interfaces dotnet_naming_symbols.interface_types.applicable_kinds = interface # private_fields - Defines private fields dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private # Naming Styles # camel_case - Define the camelCase style dotnet_naming_style.camel_case.capitalization = camel_case # pascal_case - Define the Pascal_case style dotnet_naming_style.pascal_case.capitalization = pascal_case # first_upper - The first character must start with an upper-case character dotnet_naming_style.first_upper.capitalization = first_word_upper # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I # prefix_underscore_camel_case - Private fields must be prefixed with an '_' dotnet_naming_style.prefix_underscore_camel_case.capitalization = camel_case dotnet_naming_style.prefix_underscore_camel_case.required_prefix = _ # Naming Rules # Constant fields must be PascalCase dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case # Public, internal and protected readonly fields must be PascalCase dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = suggestion dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case # Static readonly fields must be PascalCase dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = suggestion dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case # Private readonly fields must be camelCase dotnet_naming_rule.private_readonly_fields_must_be_camel_case.severity = suggestion dotnet_naming_rule.private_readonly_fields_must_be_camel_case.symbols = private_readonly_fields dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = prefix_underscore_camel_case # Public and internal fields must be PascalCase dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = suggestion dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case # Private and protected fields must be camelCase dotnet_naming_rule.private_protected_fields_must_be_camel_case.severity = suggestion dotnet_naming_rule.private_protected_fields_must_be_camel_case.symbols = private_protected_fields dotnet_naming_rule.private_protected_fields_must_be_camel_case.style = camel_case # Public members must be capitalized dotnet_naming_rule.public_members_must_be_capitalized.severity = suggestion dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper # Parameters must be camelCase dotnet_naming_rule.parameters_must_be_camel_case.severity = suggestion dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case # Class, struct, enum and delegates must be PascalCase dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = suggestion dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case # Interfaces must be PascalCase and start with an 'I' dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = suggestion dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i # Private fields must begin with an '_' dotnet_naming_rule.private_members_with_underscore.symbols = private_fields dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore_camel_case dotnet_naming_rule.private_members_with_underscore.severity = suggestion # Indentation Options csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_labels = flush_left csharp_indent_switch_labels = true # Style Options csharp_style_conditional_delegate_call = true : suggestion csharp_style_deconstructed_variable_declaration = true : suggestion csharp_style_expression_bodied_accessors = true : suggestion csharp_style_expression_bodied_constructors = false : silent csharp_style_expression_bodied_indexers = true : suggestion csharp_style_expression_bodied_methods = when_on_single_line : suggestion csharp_style_expression_bodied_operators = when_on_single_line : suggestion csharp_style_expression_bodied_properties = true : suggestion csharp_style_expression_bodied_lambdas = true : suggestion csharp_style_expression_bodied_local_functions = when_on_single_line : suggestion csharp_style_inlined_variable_declaration = true : suggestion csharp_style_pattern_local_over_anonymous_function = true : suggestion csharp_style_pattern_matching_over_as_with_null_check = true : suggestion csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion csharp_style_throw_expression = true : suggestion csharp_style_var_elsewhere = true : suggestion csharp_style_var_for_built_in_types = true : suggestion csharp_style_var_when_type_is_apparent = true : suggestion csharp_style_namespace_declarations = file_scoped # New Line Options csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true # Spacing Options csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = do_not_ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Wrapping Options csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true # Blocks csharp_prefer_braces = true:none # Expressions csharp_prefer_simple_default_expression = true:suggestion [*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 [*.json] indent_size = 2 [*.{ps1,psm1}] indent_size = 4 [*.sh] indent_size = 4 end_of_line = lf [*.{razor,cshtml}] charset = utf-8-bom [*.{cs,vb}] # SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time dotnet_diagnostic.SYSLIB1054.severity = suggestion # CA1018: Mark attributes with AttributeUsageAttribute dotnet_diagnostic.CA1018.severity = suggestion # CA1047: Do not declare protected member in sealed type dotnet_diagnostic.CA1047.severity = suggestion # CA1305: Specify IFormatProvider dotnet_diagnostic.CA1305.severity = suggestion # CA1507: Use nameof to express symbol names dotnet_diagnostic.CA1507.severity = suggestion # CA1510: Use ArgumentNullException throw helper dotnet_diagnostic.CA1510.severity = suggestion # CA1511: Use ArgumentException throw helper dotnet_diagnostic.CA1511.severity = suggestion # CA1512: Use ArgumentOutOfRangeException throw helper dotnet_diagnostic.CA1512.severity = suggestion # CA1513: Use ObjectDisposedException throw helper dotnet_diagnostic.CA1513.severity = suggestion # CA1725: Parameter names should match base declaration dotnet_diagnostic.CA1725.severity = suggestion # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = suggestion # CA1805: Do not initialize unnecessarily dotnet_diagnostic.CA1805.severity = suggestion # CA1810: Do not initialize unnecessarily dotnet_diagnostic.CA1810.severity = suggestion # CA1821: Remove empty Finalizers dotnet_diagnostic.CA1821.severity = suggestion # CA1822: Make member static dotnet_diagnostic.CA1822.severity = suggestion dotnet_code_quality.CA1822.api_surface = private, internal # CA1823: Avoid unused private fields dotnet_diagnostic.CA1823.severity = suggestion # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = suggestion # CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly dotnet_diagnostic.CA1826.severity = suggestion # CA1827: Do not use Count() or LongCount() when Any() can be used dotnet_diagnostic.CA1827.severity = suggestion # CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used dotnet_diagnostic.CA1828.severity = suggestion # CA1829: Use Length/Count property instead of Count() when available dotnet_diagnostic.CA1829.severity = suggestion # CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder dotnet_diagnostic.CA1830.severity = suggestion # CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1831.severity = suggestion # CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1832.severity = suggestion # CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1833.severity = suggestion # CA1834: Consider using 'StringBuilder.Append(char)' when applicable dotnet_diagnostic.CA1834.severity = suggestion # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' dotnet_diagnostic.CA1835.severity = suggestion # CA1836: Prefer IsEmpty over Count dotnet_diagnostic.CA1836.severity = suggestion # CA1837: Use 'Environment.ProcessId' dotnet_diagnostic.CA1837.severity = suggestion # CA1838: Avoid 'StringBuilder' parameters for P/Invokes dotnet_diagnostic.CA1838.severity = suggestion # CA1839: Use 'Environment.ProcessPath' dotnet_diagnostic.CA1839.severity = suggestion # CA1840: Use 'Environment.CurrentManagedThreadId' dotnet_diagnostic.CA1840.severity = suggestion # CA1841: Prefer Dictionary.Contains methods dotnet_diagnostic.CA1841.severity = suggestion # CA1842: Do not use 'WhenAll' with a single task dotnet_diagnostic.CA1842.severity = suggestion # CA1843: Do not use 'WaitAll' with a single task dotnet_diagnostic.CA1843.severity = suggestion # CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' dotnet_diagnostic.CA1844.severity = suggestion # CA1845: Use span-based 'string.Concat' dotnet_diagnostic.CA1845.severity = suggestion # CA1846: Prefer AsSpan over Substring dotnet_diagnostic.CA1846.severity = suggestion # CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = suggestion # CA1852: Seal internal types dotnet_diagnostic.CA1852.severity = suggestion # CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method dotnet_diagnostic.CA1854.severity = suggestion # CA1855: Prefer 'Clear' over 'Fill' dotnet_diagnostic.CA1855.severity = suggestion # CA1856: Incorrect usage of ConstantExpected attribute dotnet_diagnostic.CA1856.severity = error # CA1857: A constant is expected for the parameter dotnet_diagnostic.CA1857.severity = suggestion # CA1858: Use 'StartsWith' instead of 'IndexOf' dotnet_diagnostic.CA1858.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = silent # CA2008: Do not create tasks without passing a TaskScheduler dotnet_diagnostic.CA2008.severity = suggestion # CA2009: Do not call ToImmutableCollection on an ImmutableCollection value dotnet_diagnostic.CA2009.severity = suggestion # CA2011: Avoid infinite recursion dotnet_diagnostic.CA2011.severity = suggestion # CA2012: Use ValueTask correctly dotnet_diagnostic.CA2012.severity = suggestion # CA2013: Do not use ReferenceEquals with value types dotnet_diagnostic.CA2013.severity = suggestion # CA2014: Do not use stackalloc in loops. dotnet_diagnostic.CA2014.severity = suggestion # CA2016: Forward the 'CancellationToken' parameter to methods that take one dotnet_diagnostic.CA2016.severity = suggestion # CA2200: Rethrow to preserve stack details dotnet_diagnostic.CA2200.severity = suggestion # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = suggestion # CA2208: Instantiate argument exceptions correctly dotnet_diagnostic.CA2208.severity = suggestion # CA2245: Do not assign a property to itself dotnet_diagnostic.CA2245.severity = suggestion # CA2246: Assigning symbol and its member in the same statement dotnet_diagnostic.CA2246.severity = suggestion # CA2249: Use string.Contains instead of string.IndexOf to improve readability. dotnet_diagnostic.CA2249.severity = suggestion # IDE0005: Remove unnecessary usings dotnet_diagnostic.IDE0005.severity = suggestion # IDE0011: Curly braces to surround blocks of code dotnet_diagnostic.IDE0011.severity = suggestion # IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) dotnet_diagnostic.IDE0020.severity = suggestion # IDE0029: Use coalesce expression (non-nullable types) dotnet_diagnostic.IDE0029.severity = suggestion # IDE0030: Use coalesce expression (nullable types) dotnet_diagnostic.IDE0030.severity = suggestion # IDE0031: Use null propagation dotnet_diagnostic.IDE0031.severity = suggestion # IDE0035: Remove unreachable code dotnet_diagnostic.IDE0035.severity = suggestion # IDE0036: Order modifiers csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion dotnet_diagnostic.IDE0036.severity = suggestion # IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) dotnet_diagnostic.IDE0038.severity = suggestion # IDE0043: Format string contains invalid placeholder dotnet_diagnostic.IDE0043.severity = suggestion # IDE0044: Make field readonly dotnet_diagnostic.IDE0044.severity = suggestion # IDE0051: Remove unused private members dotnet_diagnostic.IDE0051.severity = suggestion # IDE0055: All formatting rules dotnet_diagnostic.IDE0055.severity = suggestion # IDE0059: Unnecessary assignment to a value dotnet_diagnostic.IDE0059.severity = suggestion # IDE0060: Remove unused parameter dotnet_code_quality_unused_parameters = non_public dotnet_diagnostic.IDE0060.severity = suggestion # IDE0062: Make local function static dotnet_diagnostic.IDE0062.severity = suggestion # IDE0073: File header #dotnet_diagnostic.IDE0073.severity = silent #file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. # IDE1006: Required naming style dotnet_diagnostic.IDE1006.severity = suggestion # IDE0161: Convert to file-scoped namespace dotnet_diagnostic.IDE0161.severity = suggestion # IDE0200: Lambda expression can be removed dotnet_diagnostic.IDE0200.severity = suggestion # IDE2000: Disallow multiple blank lines dotnet_style_allow_multiple_blank_lines_experimental = false dotnet_diagnostic.IDE2000.severity = suggestion # CA1018: Mark attributes with AttributeUsageAttribute dotnet_diagnostic.CA1018.severity = suggestion # CA1507: Use nameof to express symbol names dotnet_diagnostic.CA1507.severity = suggestion # CA1510: Use ArgumentNullException throw helper dotnet_diagnostic.CA1510.severity = suggestion # CA1511: Use ArgumentException throw helper dotnet_diagnostic.CA1511.severity = suggestion # CA1512: Use ArgumentOutOfRangeException throw helper dotnet_diagnostic.CA1512.severity = suggestion # CA1513: Use ObjectDisposedException throw helper dotnet_diagnostic.CA1513.severity = suggestion # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = suggestion # CA1805: Do not initialize unnecessarily dotnet_diagnostic.CA1805.severity = suggestion # CA1810: Do not initialize unnecessarily dotnet_diagnostic.CA1810.severity = suggestion # CA1822: Make member static dotnet_diagnostic.CA1822.severity = suggestion # CA1823: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = suggestion # CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly dotnet_diagnostic.CA1826.severity = suggestion # CA1827: Do not use Count() or LongCount() when Any() can be used dotnet_diagnostic.CA1827.severity = suggestion # CA1829: Use Length/Count property instead of Count() when available dotnet_diagnostic.CA1829.severity = suggestion # CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1831.severity = suggestion # CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1832.severity = suggestion # CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate dotnet_diagnostic.CA1833.severity = suggestion # CA1834: Consider using 'StringBuilder.Append(char)' when applicable dotnet_diagnostic.CA1834.severity = suggestion # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' dotnet_diagnostic.CA1835.severity = suggestion # CA1837: Use 'Environment.ProcessId' dotnet_diagnostic.CA1837.severity = suggestion # CA1838: Avoid 'StringBuilder' parameters for P/Invokes dotnet_diagnostic.CA1838.severity = suggestion # CA1841: Prefer Dictionary.Contains methods dotnet_diagnostic.CA1841.severity = suggestion # CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' dotnet_diagnostic.CA1844.severity = suggestion # CA1845: Use span-based 'string.Concat' dotnet_diagnostic.CA1845.severity = suggestion # CA1846: Prefer AsSpan over Substring dotnet_diagnostic.CA1846.severity = suggestion # CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = suggestion # CA1852: Seal internal types dotnet_diagnostic.CA1852.severity = suggestion # CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method dotnet_diagnostic.CA1854.severity = suggestion # CA1855: Prefer 'Clear' over 'Fill' dotnet_diagnostic.CA1855.severity = suggestion # CA1856: Incorrect usage of ConstantExpected attribute dotnet_diagnostic.CA1856.severity = suggestion # CA1857: A constant is expected for the parameter dotnet_diagnostic.CA1857.severity = suggestion # CA1858: Use 'StartsWith' instead of 'IndexOf' dotnet_diagnostic.CA1858.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = suggestion # CA2008: Do not create tasks without passing a TaskScheduler dotnet_diagnostic.CA2008.severity = suggestion # CA2012: Use ValueTask correctly dotnet_diagnostic.CA2012.severity = suggestion # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = suggestion # CA2249: Use string.Contains instead of string.IndexOf to improve readability. dotnet_diagnostic.CA2249.severity = suggestion # IDE0005: Remove unnecessary usings dotnet_diagnostic.IDE0005.severity = suggestion # IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) dotnet_diagnostic.IDE0020.severity = suggestion # IDE0029: Use coalesce expression (non-nullable types) dotnet_diagnostic.IDE0029.severity = suggestion # IDE0030: Use coalesce expression (nullable types) dotnet_diagnostic.IDE0030.severity = suggestion # IDE0031: Use null propagation dotnet_diagnostic.IDE0031.severity = suggestion # IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) dotnet_diagnostic.IDE0038.severity = suggestion # IDE0044: Make field readonly dotnet_diagnostic.IDE0044.severity = suggestion # IDE0051: Remove unused private members dotnet_diagnostic.IDE0051.severity = suggestion # IDE0059: Unnecessary assignment to a value dotnet_diagnostic.IDE0059.severity = suggestion # IDE0060: Remove unused parameters dotnet_diagnostic.IDE0060.severity = suggestion # IDE0062: Make local function static dotnet_diagnostic.IDE0062.severity = suggestion # IDE0200: Lambda expression can be removed dotnet_diagnostic.IDE0200.severity = suggestion # CA2016: Forward the 'CancellationToken' parameter to methods that take one dotnet_diagnostic.CA2016.severity = suggestion # RS0041: Public members should not use oblivious types dotnet_diagnostic.RS0041.severity = suggestion # RS0026: Do not add multiple public overloads with optional parameters dotnet_diagnostic.RS0026.severity = suggestion # RS0027: API with optional parameter(s) should have the most parameters amongst its public overloads dotnet_diagnostic.RS0027.severity = suggestion ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain # Verify.Xunit *.verified.cs text eol=lf working-tree-encoding=UTF-8 ================================================ FILE: .github/copilot-instructions.md ================================================ ## General * Make only high confidence suggestions when reviewing code changes. * Always use the latest version C#, currently C# 13 features. * Never change global.json unless explicitly asked to. * Orleans is a distributed actor framework for .NET - understand the grain-based programming model when making changes. ## Build and Test ### Building the Project * Use `dotnet build` to build the solution (Orleans.slnx). * The solution uses .NET SDK 9.0.306 as specified in global.json. * Build scripts are available: `Build.cmd` (Windows) or `build.ps1` (PowerShell). * Debug builds include a date suffix in version numbers. ### Running Tests * Use `dotnet test` to run tests. * Test.cmd provides a convenient way to run all tests on Windows. * Tests are organized by category: BVT, SlowBVT, Functional, and provider-specific categories. * Some tests require external dependencies (Redis, Cassandra, Azure, AWS, etc.) - check the CI workflow for setup examples. * Use `--filter` to run specific test categories, e.g., `dotnet test --filter "Category=BVT"`. ## Formatting * Apply code-formatting style defined in `.editorconfig`. * Prefer file-scoped namespace declarations and single-line using directives. * Insert a newline before the opening curly brace of any code block (e.g., after `if`, `for`, `while`, `foreach`, `using`, `try`, etc.). * Ensure that the final return statement of a method is on its own line. * Use pattern matching and switch expressions wherever possible. * Use `nameof` instead of string literals when referring to member names. * Ensure that XML doc comments are created for any public APIs. When applicable, include `` and `` documentation in the comments. ### Nullable Reference Types * Declare variables non-nullable, and check for `null` at entry points. * Always use `is null` or `is not null` instead of `== null` or `!= null`. * Trust the C# null annotations and don't add null checks when the type system says a value cannot be null. ## Testing * We use xUnit SDK v3 for tests. * Do not emit "Act", "Arrange" or "Assert" comments. * Use NSubstitute for mocking in tests. * Copy existing style in nearby files for test method names and capitalization. * Tests are located in the `test/` directory, organized by functionality (e.g., DefaultCluster.Tests, NonSilo.Tests, Extensions/). ## Repository Structure * **src/** - Core Orleans runtime, serialization, client, hosting, and provider implementations. * Orleans.Core - Core runtime abstractions and implementations * Orleans.Serialization - High-performance serialization framework * Orleans.Client - Client-side grain communication * Orleans.Runtime - Server-side runtime implementation * Orleans.Hosting.Kubernetes - Kubernetes hosting support * Provider subdirectories: AWS/, Azure/, AdoNet/, Cassandra/, Redis/ for various storage and clustering providers * **test/** - All test projects, mirroring the src/ structure. * **samples/** - Example applications demonstrating Orleans usage. * **playground/** - Experimental code and development workspace. ## Common Patterns * Grains are the fundamental building blocks - they have stable identity, behavior, and state. * Grain interfaces inherit from IGrain or IGrainWithGuidKey/IGrainWithStringKey/etc. * Use async/await consistently - Orleans is built on asynchronous patterns. * Follow the Virtual Actor Model - grains are automatically activated/deactivated by the runtime. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "dotnet-sdk" directory: "/" schedule: interval: "weekly" day: "wednesday" ignore: - dependency-name: "*" update-types: - "version-update:semver-major" ================================================ FILE: .github/eventhubs-emulator/Config.json ================================================ { "UserConfig": { "NamespaceConfig": [ { "Type": "EventHub", "Name": "emulatorNs1", "Entities": [ { "Name": "ehorleanstest", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest2", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest3", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest4", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest5", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest6", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest7", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest8", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] }, { "Name": "ehorleanstest9", "PartitionCount": "4", "ConsumerGroups": [ { "Name": "orleansnightly" } ] } ] } ], "LoggingConfig": { "Type": "File" } } } ================================================ FILE: .github/policies/resourceManagement.yml ================================================ id: name: GitOps.PullRequestIssueManagement description: GitOps.PullRequestIssueManagement primitive owner: resource: repository disabled: false where: configuration: resourceManagementConfiguration: scheduledSearches: - description: Close stale issues frequencies: - hourly: hour: 6 filters: - isIssue - isOpen - hasLabel: label: 'Needs: author feedback' - hasLabel: label: 'Status: no recent activity' - noActivitySince: days: 3 actions: - closeIssue - description: Add no recent activity label to issues frequencies: - hourly: hour: 6 filters: - isIssue - isOpen - hasLabel: label: 'Needs: author feedback' - noActivitySince: days: 4 - isNotLabeledWith: label: 'Status: no recent activity' actions: - addLabel: label: 'Status: no recent activity' - addReply: reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**. - description: Close duplicate issues frequencies: - hourly: hour: 6 filters: - isIssue - isOpen - hasLabel: label: duplicate actions: - addReply: reply: This issue has been marked as duplicate. It will be closed for housekeeping purposes. - closeIssue - description: Close stale pull requests frequencies: - hourly: hour: 6 filters: - isPullRequest - isOpen - hasLabel: label: 'Needs: author feedback' - hasLabel: label: 'Status: no recent activity' - noActivitySince: days: 7 actions: - closeIssue - description: Add no recent activity label to pull requests frequencies: - hourly: hour: 6 filters: - isPullRequest - isOpen - hasLabel: label: 'Needs: author feedback' - noActivitySince: days: 7 - isNotLabeledWith: label: 'Status: no recent activity' actions: - addLabel: label: 'Status: no recent activity' - addReply: reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. - description: Close Answered questions frequencies: - hourly: hour: 6 filters: - isIssue - isOpen - hasLabel: label: ':heavy_check_mark: Resolution: Answered' actions: - addReply: reply: Thanks for contacting us. We believe that the question you've raised has been answered. If you still feel a need to continue the discussion, feel free to reopen the issue and add your comments. - addLabel: label: 'Status: Resolved' - closeIssue - description: Close by Design frequencies: - hourly: hour: 6 filters: - isOpen - isIssue - hasLabel: label: ':heavy_check_mark: Resolution: By Design' actions: - addReply: reply: Thank you for your feedback. We're closing this issue as the behavior discussed is by design. - addLabel: label: 'Status: Resolved' - closeIssue - description: Close duplicates frequencies: - hourly: hour: 6 filters: - isOpen - isIssue - hasLabel: label: ':heavy_check_mark: Resolution: Duplicate' actions: - addReply: reply: >+ This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. - addLabel: label: 'Status: Resolved' - closeIssue - description: 'Label issues as Stale ' frequencies: - daily: time: 16:0 filters: - isIssue - isOpen - hasNoLabel - isNotAssigned - isPartOfMilestone: milestone: Triage - created: before: 365 actions: - addLabel: label: stale - addReply: reply: 'We are marking this issue as stale due to the lack of activity in the past six months. If there is no further activity within two weeks, this issue will be closed. You can always create a new issue based on the guidelines provided in our pinned announcement. ' - description: Mark old issues in the backlog as stale frequencies: - daily: time: 17:0 filters: - isOpen - hasNoLabel - isNotAssigned - isPartOfMilestone: milestone: Backlog - created: before: 365 - isIssue actions: - addLabel: label: stale - addReply: reply: 'We are marking this issue as stale due to the lack of activity in the past six months. If there is no further activity within two weeks, this issue will be closed. You can always create a new issue based on the guidelines provided in our pinned announcement. ' - description: Close Stale Issues frequencies: - daily: time: 15:0 - daily: time: 18:0 filters: - isIssue - isOpen - hasLabel: label: stale - noActivitySince: days: 10 actions: - addReply: reply: 'This issue has been marked stale for the past 30 and is being closed due to lack of activity. ' - closeIssue eventResponderTasks: - if: - payloadType: Pull_Request - isAction: action: Opened then: - addCodeFlowLink description: Add a CodeFlow link to new pull requests onFailure: onSuccess: ================================================ FILE: .github/workflows/ci.yml ================================================ name: .NET CI on: workflow_dispatch: push: branches: - main pull_request: branches: - main merge_group: types: - checks_requested env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true jobs: build: name: Build runs-on: ${{ matrix.os }} continue-on-error: true strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20.x' - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Build run: dotnet build -bl - uses: actions/upload-artifact@v4 with: name: build_log_${{ matrix.os }} retention-days: 1 path: | **/*.binlog test-redis: name: Redis provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["Redis"] framework: ["net8.0", "net10.0"] services: redis: image: redis ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: ORLEANSREDISCONNECTIONSTRING: "localhost:6379,ssl=False,abortConnect=False" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-cassandra: name: Cassandra provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["Cassandra"] dbversion: ["4.0", "4.1", "5.0"] framework: ["net8.0", "net10.0"] steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Clustering)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.dbversion }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: CASSANDRAVERSION: ${{ matrix.dbversion }} - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.dbversion }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-postgres: name: PostgreSQL provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["PostgreSql"] framework: ["net8.0", "net10.0"] services: postgres: image: postgres env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="False positive")] POSTGRES_PASSWORD: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="False positive")] ORLEANSPOSTGRESCONNECTIONSTRING: "Server=127.0.0.1;Port=5432;Pooling=false;User Id=postgres;Password=postgres;SSL Mode=Disable" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-mariadb: name: MariaDB/MySQL provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["MySql"] framework: ["net8.0", "net10.0"] services: mariadb: image: mariadb:10.6 ports: - 3306:3306 env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="False positive")] MARIADB_ROOT_PASSWORD: "mariadb" steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSMYSQLCONNECTIONSTRING: "Server=127.0.0.1;Port=3306;UId=root;Pwd=mariadb;" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-sqlserver: name: Microsoft SQL Server provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["SqlServer"] framework: ["net8.0", "net10.0"] services: mssql: image: mcr.microsoft.com/mssql/server:latest ports: - 1433:1433 env: ACCEPT_EULA: "Y" MSSQL_PID: "Developer" # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="False positive")] SA_PASSWORD: "yourWeak(!)Password" steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSMSSQLCONNECTIONSTRING: "Server=127.0.0.1,1433;User Id=SA;Password=yourWeak(!)Password;TrustServerCertificate=True;" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-azure-storage: name: Azure Storage provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: framework: ["net8.0", "net10.0"] provider: ["AzureStorage"] steps: - uses: actions/checkout@v4 - name: Start Azurite run: | docker run -d --name azurite \ -p 10000:10000 \ -p 10001:10001 \ -p 10002:10002 \ mcr.microsoft.com/azure-storage/azurite:latest \ azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck - name: Wait for Azurite run: | echo "Waiting for Azurite to be ready..." timeout 60 bash -c 'until nc -z localhost 10000 && nc -z localhost 10001 && nc -z localhost 10002; do sleep 1; done' echo "Azurite is ready" - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --framework ${{ matrix.framework }} --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] # [SuppressMessage("Microsoft.Security", "CSCAN0090:ConfigFile", Justification="Not a secret")] # [SuppressMessage("Microsoft.Security", "CSCAN0220:DefaultPasswordContexts", Justification="Not a secret")] ORLEANSDATACONNECTIONSTRING: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;" - name: Clean up Azurite if: always() run: docker rm -f azurite - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-azure-eventhubs: name: Azure Event Hubs provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: framework: ["net8.0", "net10.0"] provider: ["EventHub"] steps: - uses: actions/checkout@v4 - name: Start Azurite run: | docker run -d --name azurite \ -p 10000:10000 \ -p 10001:10001 \ -p 10002:10002 \ mcr.microsoft.com/azure-storage/azurite:latest \ azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck - name: Wait for Azurite run: | echo "Waiting for Azurite to be ready..." timeout 60 bash -c 'until nc -z localhost 10000 && nc -z localhost 10001 && nc -z localhost 10002; do sleep 1; done' echo "Azurite is ready" - name: Start Event Hubs emulator run: | docker run -d --name eventhubs-emulator \ -v ${{ github.workspace }}/.github/eventhubs-emulator/Config.json:/Eventhubs_Emulator/ConfigFiles/Config.json \ -p 5672:5672 \ -e BLOB_SERVER=host.docker.internal \ -e METADATA_SERVER=host.docker.internal \ -e ACCEPT_EULA=Y \ --add-host=host.docker.internal:host-gateway \ mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest - name: Wait for Event Hubs emulator run: | echo "Waiting for Event Hubs emulator to be ready..." timeout 60 bash -c 'until nc -z localhost 5672; do sleep 1; done' echo "Event Hubs emulator is ready" - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional|Category=Streaming)" --framework ${{ matrix.framework }} --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] # [SuppressMessage("Microsoft.Security", "CSCAN0090:ConfigFile", Justification="Not a secret")] # [SuppressMessage("Microsoft.Security", "CSCAN0220:DefaultPasswordContexts", Justification="Not a secret")] ORLEANSEVENTHUBCONNECTIONSTRING: "Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;" # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSDATACONNECTIONSTRING: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;" - name: Clean up Event Hubs emulator if: always() run: docker rm -f eventhubs-emulator - name: Clean up Azurite if: always() run: docker rm -f azurite - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-azure-cosmosdb: if: ${{ false }} name: Azure Cosmos DB provider tests runs-on: windows-latest continue-on-error: true strategy: matrix: framework: ["net8.0", "net10.0"] provider: ["Cosmos"] steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json # - name: Install emulator certificate # run: | # sleep 90s # mkdir /tmp/emulatorcerts # sudo sh -c "curl -k https://127.0.0.1:${{ job.services.cosmosdb-emulator.ports[8081] }}/_explorer/emulator.pem > /tmp/emulatorcert.crt" # cat /tmp/emulatorcert.crt # awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > "emulatorcert." c ".crt"}' < /tmp/emulatorcert.crt # sudo cp emulatorcert.*.crt /usr/local/share/ca-certificates/ # sudo update-ca-certificates - name: Start Azure Cosmos DB emulator run: | Write-Host "Launching Azure Cosmos DB Emulator" Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" Start-CosmosDbEmulator -NoUI -Consistency Strong -PartitionCount 2 -DefaultPartitionCount 2 $IPAddress = "127.0.0.1" #(Get-NetIPAddress -AddressFamily IPV4 -AddressState Preferred -PrefixOrigin Manual | Select-Object IPAddress -First 1).IPAddress ?? "127.0.0.1" Add-Content -Path $env:GITHUB_ENV -Value "ORLEANSCOSMOSDBACCOUNTENDPOINT=https://$($IPAddress):8081/" - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] #ORLEANSCOSMOSDBACCOUNTENDPOINT: "https://127.0.0.1:8081/" # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSCOSMOSDBACCOUNTKEY: "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-consul: name: Consul provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["Consul"] framework: ["net8.0", "net10.0"] steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-zookeeper: name: ZooKeeper provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["ZooKeeper"] framework: ["net8.0", "net10.0"] services: zookeeper: image: zookeeper:3.9 ports: - 2181:2181 steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: ORLEANSZOOKEEPERCONNECTIONSTRING: "localhost:2181" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-dynamodb: name: AWS DynamoDB provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["DynamoDB"] framework: ["net8.0", "net10.0"] services: dynamodb: image: amazon/dynamodb-local:latest ports: - 8000:8000 env: # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] AWS_ACCESS_KEY_ID: root # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] AWS_SECRET_ACCESS_KEY: pass AWS_REGION: us-east-1 steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow env: ORLEANSDYNAMODBSERVICE: "http://127.0.0.1:8000" # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSDYNAMODBACCESSKEY: "root" # [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Not a secret")] ORLEANSDYNAMODBSECRETKEY: "pass" - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-nats: name: NATS stream provider tests runs-on: ubuntu-latest continue-on-error: true strategy: matrix: provider: ["NATS"] framework: ["net8.0", "net10.0"] # services: # nats: # image: nats:latest # ports: # - 4222:4222 # - 8222:8222 # env: # HTTP_PORT: 8222 steps: - name: Start NATS run: docker run -d --name nats -p 4222:4222 -p 8222:8222 nats:latest --js --http-port=8222 - name: Wait for NATS run: sleep 5 - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.provider }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow - name: Clean up container if: always() run: docker rm -f nats - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test: name: Test runs-on: ${{ matrix.os }} continue-on-error: true strategy: matrix: suite: [BVT, SlowBVT, Functional] os: [ubuntu-latest, windows-latest, macos-latest] framework: ["net8.0", "net10.0"] steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20.x' - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Build run: dotnet build - name: Test run: dotnet test --framework ${{ matrix.framework }} --filter "Category=${{ matrix.suite }}&Category!=Consul" --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.suite }}_${{ matrix.framework }}.trx" -- -parallel none -noshadow - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.suite }}_${{ matrix.os }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* test-codegenerator: name: Test Code Generator runs-on: ${{ matrix.os }} continue-on-error: true strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] framework: ["net8.0", "net10.0"] steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Test Code Generator run: dotnet test test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj --framework ${{ matrix.framework }} --blame-hang-timeout 10m --blame-crash-dump-type full --blame-hang-dump-type full --logger "trx;LogFileName=test_results_${{ matrix.framework }}.trx" -- -parallel none -noshadow - name: Archive Test Results if: always() uses: actions/upload-artifact@v4 with: name: test_output_${{ github.job }}_${{ matrix.os }}_${{ matrix.framework }} retention-days: 1 path: | **/TestResults/* **/logs/* ================================================ FILE: .github/workflows/codeql.yml ================================================ name: CodeQL on: push: branches: - main pull_request: branches: - main merge_group: types: - checks_requested schedule: - cron: '0 6 * * 1' # Run weekly on Monday at 6:00 UTC jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest timeout-minutes: 360 permissions: security-events: write packages: read actions: read contents: read strategy: fail-fast: false matrix: include: - language: csharp build-mode: none - language: javascript-typescript build-mode: none - language: actions build-mode: none steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" ================================================ FILE: .github/workflows/generate-api-diffs.yml ================================================ name: Generate API Diffs on: workflow_dispatch: schedule: - cron: '0 16 * * *' # 8am PST (16:00 UTC) permissions: contents: write pull-requests: write jobs: generate-and-pr: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: Restore and build run: | set +e # Find all csproj files excluding specific paths find src -name '*.csproj' | egrep -v 'Orleans.Analyzers|Orleans.CodeGenerator' | while read proj; do export CI=false && dotnet build "$proj" -f net8.0 --configuration Release --no-incremental /t:"Build;GenAPIGenerateReferenceAssemblySource" done continue-on-error: true - name: Create or update pull request uses: dotnet/actions-create-pull-request@e8d799aa1f8b17f324f9513832811b0a62f1e0b1 with: token: ${{ secrets.GITHUB_TOKEN }} branch: update-api-diffs base: main title: "[Automated] Update API Surface Area" body: "Auto-generated update to the API surface to compare current surface vs latest release. This should only be merged once this surface area ships in a new release." ================================================ FILE: .github/workflows/locker.yml ================================================ name: Locker - Lock stale issues and PRs on: schedule: - cron: '0 9 * * *' # Once per day, early morning PT workflow_dispatch: # Manual triggering through the GitHub UI, API, or CLI inputs: daysSinceClose: required: true default: "30" daysSinceUpdate: required: true default: "30" permissions: issues: write pull-requests: write jobs: main: runs-on: ubuntu-latest steps: - name: Checkout Actions uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions ref: cd16cd2aad6ba2da74bb6c6f7293adddd579a90e # locker action commit sha - name: Install Actions run: npm install --production --prefix ./actions - name: Run Locker uses: ./actions/locker with: daysSinceClose: ${{ fromJson(inputs.daysSinceClose || 30) }} daysSinceUpdate: ${{ fromJson(inputs.daysSinceUpdate || 30) }} ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # Tool Runtime Dir /[Tt]ools/ /src/[Bb]ootstrap/ # Orleans code-gen files orleans.codegen.cs orleans.codegen.fs orleans.codegen.vb src/SDK/VSIX/ # User-specific files *.suo *.user *.sln.docstates .vscode/ .vs/ .idea/ # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ Binaries/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ Artifacts/ # Roslyn cache directories *.ide/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* #NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # If using the old MSBuild-Integrated Package Restore, uncomment this: #!**/packages/repositories.config # Auto-downloaded nuget.exe **/[Nn]u[Gg]et.exe *.lock.json *.nuget.props *.nuget.targets # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ patch.diff # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Temporary working files **/pingme.txt .vs/ src/SetupTestScriptOutput.txt \.DS_Store # Binary MSBuild Log files *.binlog # VS Code .ionide/ # Verify.Xunit *.received.* # code coverage *.cobertura.xml ================================================ FILE: Build.cmd ================================================ powershell -NoProfile -ExecutionPolicy unrestricted -Command "./build.ps1 %1" ================================================ FILE: CODE-OF-CONDUCT.md ================================================ # Code of Conduct This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Orleans Some notes and guidelines for developers who want to contribute to Orleans. ## Contributing to this project Here are some pointers for anyone looking for mini-features and work items that would make a positive contribution to Orleans. These are just a few ideas, so if you think of something else that would be useful, then spin up a [discussion thread](https://github.com/dotnet/orleans/issues) on GitHub to discuss the proposal, and go for it. * **[Orleans GitHub Repository](https://github.com/dotnet/orleans)** Pull requests are always welcome. * **[Intern and Student Projects](https://docs.microsoft.com/dotnet/orleans/resources/student-projects)** Some suggestions for possible intern / student projects. * **[Documentation Guidelines](https://docs.microsoft.com/contribute/dotnet/dotnet-contribute)** A style guide for writing documentation for this site. ## Code contributions This project uses the same contribution process as the other **[.NET projects](https://github.com/dotnet)** on GitHub. * **[.NET Project Contribution Guidelines](https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md)** Guidelines and workflow for contributing to .NET projects on GitHub. * **[.NET CLA](https://cla.dotnetfoundation.org/)** Contribution License Agreement for .NET projects on GitHub. * **[.NET Framework Design Guidelines](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/framework-design-guidelines-digest.md)** Some basic API design rules, coding standards, and style guide for .NET Framework APIs. ## Coding Standards and Conventions We try not to be too OCD about coding style wars, but in case of disputes we do fall back to the core principles in the two ".NET Coding Standards" books used by the other .NET OSS projects on GitHub: * [C# Coding Style Guide](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) * [.NET Framework Design Guidelines](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/framework-design-guidelines-digest.md) There are lots of other useful documents on the [.NET](https://github.com/dotnet/runtime/tree/main/docs#coding-guidelines) documentation sites which are worth reading, although most experienced C# developers will probably have picked up many of those best-practices by osmosis, particularly around performance and memory management. ## Source code organization Orleans has not religiously followed a "One Class Per File" rule, but instead we have tried to use pragmatic judgment to maximize the change of "code understand-ability" for developers on the team. If lots of small-ish classes share a "common theme" and/or are always dealt with together, then it is OK to place those into one source code file in most cases. See for example the various "log consumer" classes were originally placed in single source file, as they represented a single unit of code comprehension. As a corollary, it is much easier to find the source code for a class if it is in a file with the same name as the class [similar to Java file naming rules], so there is a tension and value judgment here between code find-ability and minimizing / constraining the number of projects in a solution and files within a project [which both have direct impact on the Visual Studio "Opening" and "Building" times for large projects]. Code search tools in VS and ReSharper definitely help here. ## Dependencies and Inter-Project References One topic that we are very strict about is around dependency references between components and sub-systems. ### Component / Project References References between projects in a solution must always use "**Project References**" rather than "_DLL References_" to ensure that component build relationships are known to the build tools. **Right**: ```xml {BC1BD60C-E7D8-4452-A21C-290AEC8E2E74} Orleans ``` _Wrong_: ```xml ..\Orleans\bin\Debug\Orleans.dll ``` In order to help ensure we keep inter-project references clean, then on the build servers [and local `Build.cmd` script] we deliberately use side-by-side input `.\src` and output `.\Binaries` directories rather than the more normal in-place build directory structure (eg. `[PROJ]\bin\Release`) used by VS on local dev machines. ### Unified component versions We use the same unified versions of external component throughout the Orleans code base, and so should never need to add `bindingRedirect` entries in `App.config` files. Also, in general it should almost never be necessary to have `Private=True` elements in Orleans project files, except to override a conflict with a Windows / VS "system" component. Some package management tools can occasionally get confused when making version changes, and sometimes think that we are using multiple versions of the same assembly within a solution, which of course we never do. We long for the day when package management tools for .NET can make version changes transactionally. Until then, it is occasionally necessary to "fix" the misguided actions of some .NET package management tools by hand-editing the .csproj files (they are just XML text files) back to sanity and/or using the "Discard Edited Line" functions that most good Git tools such as [Atlassian SourceTree](https://www.sourcetreeapp.com/) provide. Using "sort" references and unified component versions avoids creating brittle links between Orleans run-time and/or external components, and has proved highly effective in the last several years at reducing stress levels for the Orleans Team during important deployment milestones. :) ================================================ FILE: Directory.Build.props ================================================ $(MSBuildThisFileDirectory) Microsoft Microsoft Orleans © Microsoft Corporation. All rights reserved. MIT https://github.com/dotnet/orleans logo_128.png Orleans Cloud-Computing Actor-Model Actors Distributed-Systems C# .NET https://github.com/dotnet/orleans $(RepositoryUrl) $(RepositoryUrl) git preview strict 3 preview true snupkg embedded true false false true true true enable $(NoWarn);1591 true true true 10.0.0.0 10.0.0 dev $(VersionSuffix)-$(VersionDateSuffix) $(SourceRoot)/Artifacts/$(Configuration) $(SourceRoot)/Artifacts/DistributedTests <developer build> $(BUILD_SOURCEVERSION) $(GIT_COMMIT) Not found $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory).git')) $([System.IO.File]::ReadAllText('$(DotGitDir)/HEAD').Trim()) $(DotGitDir)/$(HeadFileContent.Substring(5)) $([System.IO.File]::ReadAllText('$(RefPath)').Trim()) $(HeadFileContent) ================================================ FILE: Directory.Build.targets ================================================ $(Version). Commit Hash: $(GitHeadSha) ================================================ FILE: Directory.Packages.props ================================================ true true ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) .NET Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NuGet.Config ================================================  ================================================ FILE: Orleans.slnx ================================================ ================================================ FILE: Parallel-Tests.ps1 ================================================ param( [string[]] $directories, [string] $testFilter = $null) . .\common.ps1 Install-Dotnet $maxDegreeOfParallelism = [math]::min($env:NUMBER_OF_PROCESSORS, 4) Write-Host "Max Job Parallelism = $maxDegreeOfParallelism" $failed = $false if( [Console]::InputEncoding -is [Text.UTF8Encoding] -and [Console]::InputEncoding.GetPreamble().Length -ne 0 ) { Write-Host Setting [Console]::InputEncoding [Console]::InputEncoding = New-Object Text.UTF8Encoding $false } else { Write-Host Not changing [Console]::InputEncoding } if ([string]::IsNullOrWhiteSpace($testFilter)) { $testFilter = $env:TEST_FILTERS; } if ([string]::IsNullOrWhiteSpace($testFilter)) { $testFilter = "Category=BVT|Category=SlowBVT"; } Write-Host "Test filters: `"$testFilter`""; function Receive-CompletedJobs { $succeeded = $true foreach($job in (Get-Job | Where-Object { $_.State -ne 'Running' })) { Receive-Job $job -AutoRemoveJob -Wait | Write-Host if ($job.State -eq 'Failed') { $succeeded = $false Write-Host -ForegroundColor Red 'Failed: ' $job.Name '('$job.State')' } Write-Host '' } return $succeeded } $ExecuteCmd = { param([string] $args1, [string] $path) Set-Location -Path "$path" $cmdline = "& dotnet " + $args1 Invoke-Expression $cmdline; $cmdExitCode = $LASTEXITCODE; if ($cmdExitCode -ne 0) { Throw "Error when running tests. Command: `"$cmdline`". Exit Code: $cmdExitCode" } else { Write-Host "Tests completed. Command: `"$cmdline`"" } } foreach ($d in $directories) { $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) if ($running.Count -ge $maxDegreeOfParallelism) { $running | Wait-Job -Any | Out-Null } if (-not (Receive-CompletedJobs)) { $failed = $true } if (-not $testFilter.StartsWith('"')) { $testFilter = "`"$testFilter"; } if (-not $testFilter.EndsWith('"')) { $testFilter = "$testFilter`""; } $jobName = $([System.IO.Path]::GetFileName($d)) $cmdLine = 'test --blame-hang-timeout 10m --no-build --configuration "' + $env:BuildConfiguration + '" --filter ' + $testFilter + ' --logger "trx" -- -parallel none -noshadow' Write-Host $jobName dotnet $cmdLine Start-Job $ExecuteCmd -ArgumentList @($cmdLine, "$d") -Name $jobName | Out-Null Write-Host '' } # Wait for all jobs to complete and results ready to be received Wait-Job * | Out-Null if (-not (Receive-CompletedJobs)) { $failed = $true } if ($failed) { Write-Host 'Test run failed' Exit 1 } ================================================ FILE: README.md ================================================

Orleans logo

[![NuGet](https://img.shields.io/nuget/v/Microsoft.Orleans.Core.svg?style=flat)](http://www.nuget.org/profiles/Orleans) [![Follow on Twitter](https://img.shields.io/twitter/follow/msftorleans.svg?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=msftorleans) [![Discord](https://discordapp.com/api/guilds/333727978460676096/widget.png?style=banner2)](https://aka.ms/orleans-discord) ### Orleans is a cross-platform framework for building robust, scalable distributed applications Orleans builds on the developer productivity of .NET and brings it to the world of distributed applications, such as cloud services. Orleans scales from a single on-premises server to globally distributed, highly-available applications in the cloud. Orleans takes familiar concepts like objects, interfaces, async/await, and try/catch and extends them to multi-server environments. As such, it helps developers experienced with single-server applications transition to building resilient, scalable cloud services and other distributed applications. For this reason, Orleans has often been referred to as "Distributed .NET". It was created by [Microsoft Research](http://research.microsoft.com/projects/orleans/) and introduced the [Virtual Actor Model](https://www.microsoft.com/en-us/research/publication/orleans-distributed-virtual-actors-for-programmability-and-scalability/) as a novel approach to building a new generation of distributed systems for the Cloud era. The core contribution of Orleans is its programming model which tames the complexity inherent to highly-parallel distributed systems without restricting capabilities or imposing onerous constraints on the developer. ## Grains ![A grain is composed of a stable identity, behavior, and state](assets/grain_formulation.svg) The fundamental building block in any Orleans application is a *grain*. Grains are entities comprising user-defined identity, behavior, and state. Grain identities are user-defined keys which make Grains always available for invocation. Grains can be invoked by other grains or by external clients such as Web frontends, via strongly-typed communication interfaces (contracts). Each grain is an instance of a class which implements one or more of these interfaces. Grains can have volatile and/or persistent state that can be stored in any storage system. As such, grains implicitly partition application state, enabling automatic scalability and simplifying recovery from failures. Grain state is kept in memory while the grain is active, leading to lower latency and less load on data stores.

A diagram showing the managed lifecycle of a grain

Instantiation of grains is automatically performed on demand by the Orleans runtime. Grains which are not used for a while are automatically removed from memory to free up resources. This is possible because of their stable identity, which allows invoking grains whether they are already loaded into memory or not. This also allows for transparent recovery from failure because the caller does not need to know on which server a grain is instantiated on at any point in time. Grains have a managed lifecycle, with the Orleans runtime responsible for activating/deactivating, and placing/locating grains as needed. This allows the developer to write code as if all grains were always in-memory. Taken together, the stable identity, statefulness, and managed lifecycle of Grains are core factors that make systems built on Orleans scalable, performant, & reliable without forcing developers to write complex distributed systems code. ### Example: IoT cloud backend Consider a cloud backend for an [Internet of Things](https://en.wikipedia.org/wiki/Internet_of_things) system. This application needs to process incoming device data, filter, aggregate, and process this information, and enable sending commands to devices. In Orleans, it is natural to model each device with a grain which becomes a *digital twin* of the physical device it corresponds to. These grains keep the latest device data in memory, so that they can be quickly queried and processed without the need to communicate with the physical device directly. By observing streams of time-series data from the device, the grain can detect changes in conditions, such as measurements exceeding a threshold, and trigger an action. A simple thermostat could be modeled as follows: ```csharp public interface IThermostat : IGrainWithStringKey { Task> OnUpdate(ThermostatStatus update); } ``` Events arriving from the thermostat from a Web frontend can be sent to its grain by invoking the `OnUpdate` method which optionally returns a command back to the device. ```csharp var thermostat = client.GetGrain(id); return await thermostat.OnUpdate(update); ``` The same thermostat grain can implement a separate interface for control systems to interact with: ```csharp public interface IThermostatControl : IGrainWithStringKey { Task GetStatus(); Task UpdateConfiguration(ThermostatConfiguration config); } ``` These two interfaces (`IThermostat` and `IThermostatControl`) are implemented by a single implementation class: ```csharp public class ThermostatGrain : Grain, IThermostat, IThermostatControl { private ThermostatStatus _status; private List _commands; public Task> OnUpdate(ThermostatStatus status) { _status = status; var result = _commands; _commands = new List(); return Task.FromResult(result); } public Task GetStatus() => Task.FromResult(_status); public Task UpdateConfiguration(ThermostatConfiguration config) { _commands.Add(new ConfigUpdateCommand(config)); return Task.CompletedTask; } } ``` The `Grain` class above does not persist its state. A more thorough example demonstrating state persistence is available in the docs, for more information see [Microsoft Orleans: Grain Persistence](https://docs.microsoft.com/dotnet/orleans/grains/grain-persistence). ## Orleans runtime The Orleans runtime is what implements the programming model for applications. The main component of the runtime is the *silo*, which is responsible for hosting grains. Typically, a group of silos run as a cluster for scalability and fault-tolerance. When run as a cluster, silos coordinate with each other to distribute work, detect and recover from failures. The runtime enables grains hosted in the cluster to communicate with each other as if they are within a single process. In addition to the core programming model, the silo provides grains with a set of runtime services, such as timers, reminders (persistent timers), persistence, transactions, streams, and more. See the [features section](#features) below for more detail. Web frontends and other external clients call grains in the cluster using the client library which automatically manages network communication. Clients can also be co-hosted in the same process with silos for simplicity. Orleans is compatible with .NET Standard 2.0 and above, running on Windows, Linux, and macOS, in full .NET Framework or .NET Core. ## Features Orleans is a feature-rich framework. It provides a set of services that enable the development of distributed systems. The following sections describe the features of Orleans. ### Persistence Orleans provides a simple persistence model which ensures that state is available to a grain before requests are processed and that consistency is maintained. Grains can have multiple named persistent data objects, for example, one called "profile" for a user's profile and one called "inventory" for their inventory. This state can be stored in any storage system. For example, profile data may be stored in one database and inventory in another. While a grain is running, this state is kept in memory so that read requests can be served without accessing storage. When the grain updates its state, a `state.WriteStateAsync()` call ensures that the backing store is updated for durability and consistency. For more information see [Microsoft Orleans: Grain Persistence](https://docs.microsoft.com/dotnet/orleans/grains/grain-persistence). ### Distributed ACID transactions In addition to the simple persistence model described above, grains can have *transactional state*. Multiple grains can participate in [ACID](https://en.wikipedia.org/wiki/ACID) transactions together regardless of where their state is ultimately stored. Transactions in Orleans are distributed and decentralized (there is no central transaction manager or transaction coordinator) and have [serializable isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels). For more information, see the [Microsoft Orleans: Transactions](https://docs.microsoft.com/dotnet/orleans/grains/transactions). ### Streams Streams help developers to process series of data items in near-real time. Streams in Orleans are *managed*: streams do not need to be created or registered before a grain or client publishes to a stream or subscribes to a stream. This allows for greater decoupling of stream producers and consumers from each other and from the infrastructure. Stream processing is reliable: grains can store checkpoints (cursors) and reset to a stored checkpoint during activation or at any point afterwards. Streams supports batch delivery of messages to consumers to improve efficiency and recovery performance. Streams are backed by queueing services such as Azure Event Hubs, Amazon Kinesis, and others. An arbitrary number of streams can be multiplexed onto a smaller number of queues and the responsibility for processing these queues is balanced evenly across the cluster. ### Timers & reminders Reminders are a durable scheduling mechanism for grains. They can be used to ensure that some action is completed at a future point even if the grain is not currently activated at that time. Timers are the non-durable counterpart to reminders and can be used for high-frequency events which do not require reliability. For more information, see [Microsoft Orleans: Timers and reminders](https://docs.microsoft.com/dotnet/orleans/grains/timers-and-reminders). ### Flexible grain placement When a grain is activated in Orleans, the runtime decides which server (silo) to activate that grain on. This is called grain placement. The placement process in Orleans is fully configurable: developers can choose from a set of out-of-the-box placement policies such as random, prefer-local, and load-based, or custom logic can be configured. This allows for full flexibility in deciding where grains are created. For example, grains can be placed on a server close to resources which they need to operate on or other grains which they communicate with. ### Grain versioning & heterogeneous clusters Application code evolves over time and upgrading live, production systems in a manner which safely accounts for these changes can be challenging, particularly in stateful systems. Grain interfaces in Orleans can be optionally versioned. The cluster maintains a mapping of which grain implementations are available on which silos in the cluster and the versions of those implementations. This version information is used by the runtime in conjunction with placement strategies to make placement decisions when routing calls to grains. In addition to safe update of versioned grains, this also enables heterogeneous clusters, where different silos have different sets of grain implementations available. For more information, see [Microsoft Orleans: Grain interface versioning](https://docs.microsoft.com/dotnet/orleans/grains/grain-versioning/grain-versioning). ### Elastic scalability & fault tolerance Orleans is designed to scale elastically. When a silo joins a cluster it is able to accept new activations and when a silo leaves the cluster (either because of scale down or a machine failure) the grains which were activated on that silo will be re-activated on remaining silos as needed. An Orleans cluster can be scaled down to a single silo. The same properties which enable elastic scalability also enable fault tolerance: the cluster automatically detects and quickly recovers from failures. ### Run anywhere Orleans runs anywhere that .NET Core or .NET Framework are supported. This includes hosting on Linux, Windows, and macOS and deploying to Kubernetes, virtual or physical machines, on premises or in the cloud, and PaaS services such as Azure Container Apps, Azure App Service, and Azure Kubernetes Service. ### Stateless workers Stateless workers are specially marked grains which do not have any associated state and can be activated on multiple silos simultaneously. This enables increased parallelism for stateless functions. For more information, see [Microsoft Orleans: Stateless worker grains](https://docs.microsoft.com/dotnet/orleans/grains/stateless-worker-grains) documentation. ### Grain call filters Logic which is common to many grains can be expressed as [an interceptor, or Grain call filter](https://docs.microsoft.com/dotnet/orleans/grains/interceptors). Orleans supports filters for both incoming and outgoing calls. Some common use-cases of filters are: authorization, logging and telemetry, and error handling. ### Request context Metadata and other information can be passed along a series of requests using [request context](https://docs.microsoft.com/dotnet/orleans/grains/request-context). Request context can be used for holding distributed tracing information or any other user-defined values. ## Documentation The official documentation for Microsoft Orleans is available at . ## [Samples](./samples/#readme) A variety of samples are available in the official [.NET Samples Browser](https://docs.microsoft.com/samples/browse/?terms=orleans). ## Get started Please see the [getting started tutorial](https://docs.microsoft.com/dotnet/orleans/tutorials-and-samples/tutorial-1). ### Building On Windows, run the `build.cmd` script to build the NuGet packages locally, then reference the required NuGet packages from `/Artifacts/Release/*`. You can run `Test.cmd` to run all BVT tests, and `TestAll.cmd` to also run Functional tests. On Linux and macOS, run `dotnet build` to build Orleans. ## Official builds The latest stable, production-quality release is located [here](https://github.com/dotnet/orleans/releases/latest). Nightly builds are published to [a NuGet feed](https://pkgs.dev.azure.com/dnceng/public/_packaging/orleans-nightly/nuget/v3/index.json). These builds pass all functional tests, but are not thoroughly tested as the stable builds or pre-release builds published to NuGet.
Using the nightly build packages in your project To use nightly builds in your project, add the MyGet feed using either of the following methods: 1. Changing the .csproj file to include this section: ```xml $(RestoreSources); https://pkgs.dev.azure.com/dnceng/public/_packaging/orleans-nightly/nuget/v3/index.json ``` or 1. Creating a `NuGet.config` file in the solution directory with the following contents: ```xml ```
## Community [![Discord](https://discordapp.com/api/guilds/333727978460676096/widget.png?style=banner4)](https://aka.ms/orleans-discord) * Ask questions by [opening an issue on GitHub](https://github.com/dotnet/orleans/issues) or on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=orleans) * [Chat on Discord](https://aka.ms/orleans-discord) * Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements. * [OrleansContrib - GitHub organization for community add-ons to Orleans](https://github.com/OrleansContrib/) Various community projects, including Monitoring, Design Patterns, Storage Providers, etc. * Guidelines for developers wanting to [contribute code changes to Orleans](CONTRIBUTING.md). * You are also encouraged to report bugs or start a technical discussion by starting a new [thread](https://github.com/dotnet/orleans/issues) on GitHub. ## License This project is licensed under the [MIT license](LICENSE). ## Quick links * [Microsoft Research project home](http://research.microsoft.com/projects/orleans/) * Technical Report: [Distributed Virtual Actors for Programmability and Scalability](http://research.microsoft.com/apps/pubs/default.aspx?id=210931) * [Microsoft Orleans: Documentation](https://docs.microsoft.com/dotnet/orleans/) * [Contributing](CONTRIBUTING.md) ================================================ FILE: SUPPORT.md ================================================ # Support ## How to file issues and get help This project uses GitHub [Issues](https://github.com/dotnet/orleans/issues) to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a [new Issue](https://github.com/dotnet/orleans/issues/new). ## Community The Orleans community enjoys a lively discussion on our [Discord server](https://aka.ms/orleans-discord), and in the Issues customers file here on GitHub. If you would like to engage with our active, friendly community, use one of the links below to find someone who can help. [![Discord](https://discordapp.com/api/guilds/333727978460676096/widget.png?style=banner4)](https://aka.ms/orleans-discord) * Ask questions by [opening an issue on GitHub](https://github.com/dotnet/orleans/issues) or on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=orleans) * [Chat on Discord](https://aka.ms/orleans-discord) * Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements. * [OrleansContrib - GitHub organization for community add-ons to Orleans](https://github.com/OrleansContrib/) Various community projects, including Monitoring, Design Patterns, Storage Providers, etc. * Guidelines for developers wanting to [contribute code changes to Orleans](CONTRIBUTING.md). * You are also encouraged to report bugs or start a technical discussion by starting a new [thread](https://github.com/dotnet/orleans/issues) on GitHub. ## Microsoft Support Policy Orleans is officially supported by Microsoft. Customers with support contracts can create support tickets via https://aka.ms/orleanssupport. Orleans is listed under ASP.NET Core: ![image](https://github.com/dotnet/orleans/assets/203839/30b02291-06a1-4364-9e1c-9a06209c386d) ================================================ FILE: Test.cmd ================================================ @if not defined _echo @echo off setlocal if not defined BuildConfiguration SET BuildConfiguration=Debug SET CMDHOME=%~dp0 @REM Remove trailing backslash \ set CMDHOME=%CMDHOME:~0,-1% :: Clear the 'Platform' env variable for this session, as it's a per-project setting within the build, and :: misleading value (such as 'MCD' in HP PCs) may lead to build breakage (issue: #69). set Platform= :: Disable multilevel lookup https://github.com/dotnet/core-setup/blob/main/Documentation/design-docs/multilevel-sharedfx-lookup.md set DOTNET_MULTILEVEL_LOOKUP=0 pushd "%CMDHOME%" @cd SET TestResultDir=%CMDHOME%\Binaries\%BuildConfiguration%\TestResults if not exist %TestResultDir% md %TestResultDir% SET _Directory=bin\%BuildConfiguration%\net461\win10-x64 set TESTS=^ '%CMDHOME%\test\Extensions\TesterAzureUtils',^ '%CMDHOME%\test\TesterInternal',^ '%CMDHOME%\test\Tester',^ '%CMDHOME%\test\DefaultCluster.Tests',^ '%CMDHOME%\test\NonSilo.Tests',^ '%CMDHOME%\test\Extensions\AWSUtils.Tests',^ '%CMDHOME%\test\Extensions\Consul.Tests',^ '%CMDHOME%\test\Extensions\ServiceBus.Tests',^ '%CMDHOME%\test\Extensions\TesterAdoNet',^ '%CMDHOME%\test\Extensions\TesterZooKeeperUtils',^ '%CMDHOME%\test\Transactions\Orleans.Transactions.Tests',^ '%CMDHOME%\test\Transactions\Orleans.Transactions.Azure.Test',^ '%CMDHOME%\test\TestInfrastructure\Orleans.TestingHost.Tests',^ '%CMDHOME%\test\DependencyInjection.Tests',^ '%CMDHOME%\test\Orleans.Connections.Security.Tests',^ '%CMDHOME%\test\Analyzers.Tests'" @Echo Test assemblies = %TESTS% PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& ./Parallel-Tests.ps1 -directories %TESTS% -dotnet '%_dotnet%'" set testresult=%errorlevel% popd endlocal&set testresult=%testresult% exit /B %testresult% ================================================ FILE: TestAll.cmd ================================================ @setlocal @ECHO off SET CMDHOME=%~dp0 @REM Remove trailing backslash \ set CMDHOME=%CMDHOME:~0,-1% @REM Due to more of Windows .cmd script parameter passing quirks, we can't pass this value as cmdline argument, @REM so we need to pass it in through the back door as environment variable, scoped by setlocal set TEST_FILTERS=Category=BVT^|Category=SlowBVT^|Category=Functional @REM Note: We transfer _complete_ control to the Test.cmd script here because we don't use CALL. "%CMDHOME%\Test.cmd" %1 @REM Note: Execution will NOT return here, and the exit code returned to the caller will be whatever the other script returned. ================================================ FILE: build.ps1 ================================================ # -------------------- # Orleans build script # -------------------- . ./common.ps1 $scriptDir = Split-Path $script:MyInvocation.MyCommand.Path $solution = Join-Path $scriptDir "Orleans.slnx" # Define build flags & config if ($null -eq $env:BUILD_FLAGS) { $env:BUILD_FLAGS = "/m /v:m" } if ($null -eq $env:BuildConfiguration) { $env:BuildConfiguration = "Debug" } # Clear the 'Platform' env variable for this session, as it's a per-project setting within the build, and # misleading value (such as 'MCD' in HP PCs) may lead to build breakage (issue: #69). $Platform = $null # Disable multilevel lookup https://github.com/dotnet/core-setup/blob/main/Documentation/design-docs/multilevel-sharedfx-lookup.md $DOTNET_MULTILEVEL_LOOKUP = 0 $BuildProperties = "/p:Configuration=$env:BuildConfiguration"; # Set DateTime suffix for debug builds if ($env:BuildConfiguration -eq "Debug") { $dateSuffix = Get-Date -Format "yyyyMMddHHmm" $BuildProperties = $BuildProperties + " /p:VersionDateSuffix=$dateSuffix" } Write-Output "===== Building $solution =====" Install-Dotnet if ($args[0] -ne "Pack") { Write-Output "Build $env:BuildConfiguration ==============================" Invoke-Dotnet -Command "restore" -Arguments "$env:BUILD_FLAGS /bl:${env:BuildConfiguration}-Restore.binlog ${BuildProperties} `"$solution`"" Invoke-Dotnet -Command "build" -Arguments "$env:BUILD_FLAGS /bl:${env:BuildConfiguration}-Build.binlog ${BuildProperties} `"$solution`"" } Write-Output "Package $env:BuildConfiguration ============================" Invoke-Dotnet -Command "pack" -Arguments "--no-build --no-restore $BUILD_FLAGS /bl:${env:BuildConfiguration}-Pack.binlog ${BuildProperties} `"$solution`"" Write-Output "===== Build succeeded for $solution =====" ================================================ FILE: common.ps1 ================================================ function Install-Dotnet { $installDotnet = $true try { # Check that the dotnet.exe command exists Get-Command dotnet.exe 2>&1 | Out-Null # Read the version from global.json $globalJson = Get-Content .\global.json | ConvertFrom-Json $requiredVersion = $globalJson.sdk.version # Check versions already installed $localVersions = dotnet --list-sdks |% { $_.Split(" ")[0] } Write-Output "Required SDK: $requiredVersion" Write-Output "Installed SDKs: $localversions" # If the required version is not installed, we will call the installation script $installDotnet = !$localVersions.Contains($globalJson.sdk.version) } catch { Write-Output "dotnet not found" # do nothing, we will install dotnet } if ($installDotnet) { Write-Output "Installing dotnet ${requiredVersion}" New-Item -ItemType Directory -Force -Path .\Tools | Out-Null [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest ` -Uri "https://dot.net/v1/dotnet-install.ps1" ` -OutFile ".\Tools\dotnet-install.ps1" & .\Tools\dotnet-install.ps1 -InstallDir Tools\dotnetcli\ -JSonFile .\global.json } } function Invoke-Dotnet { param ( $Command, $Arguments ) $cmdArgs = @() $cmdArgs = $cmdArgs + $Command $cmdArgs = $cmdArgs + ($Arguments -split "\s+") Write-Output "dotnet $cmdArgs" & dotnet $cmdArgs if ($LASTEXITCODE -ne 0) { Write-Error "===== Build FAILED -- $Command with error $LASTEXITCODE - CANNOT CONTINUE =====" Exit $LASTEXITCODE } } ================================================ FILE: distributed-tests.yml ================================================ variables: clusterId: '{{ "now" | date: "%s" }}' serviceId: '{{ "now" | date: "%s" }}' framework: net8.0 jobs: server: source: localFolder: Artifacts/DistributedTests/DistributedTests.Server/{{framework}} executable: DistributedTests.Server.exe readyStateText: Orleans Silo started. framework: net8.0 arguments: "{{configurator}} --clusterId {{clusterId}} --serviceId {{serviceId}} --azureQueueUri {{azureQueueUri}} --azureTableUri {{azureTableUri}} {{configuratorOptions}}" onConfigure: - if (job.endpoints.Count > 0) { job.endpoints.RemoveRange(job.variables.instances, job.endpoints.Count - job.variables.instances); } client: source: localFolder: Artifacts/DistributedTests/DistributedTests.Client/{{framework}} executable: DistributedTests.Client.exe waitForExit: true framework: net8.0 arguments: "{{command}} --clusterId {{clusterId}} --serviceId {{serviceId}} --azureQueueUri {{azureQueueUri}} --azureTableUri {{azureTableUri}} {{commandOptions}}" onConfigure: - if (job.endpoints.Count > 0) { job.endpoints.Reverse(); job.endpoints.RemoveRange(job.variables.instances, job.endpoints.Count - job.variables.instances); } scenarios: ping: server: job: server variables: instances: 10 configurator: SimpleSilo client: job: client variables: command: ping instances: 1 numWorkers: 250 blocksPerWorker: 0 requestsPerBlock: 500 duration: 120 commandOptions: "--numWorkers {{numWorkers}} --blocksPerWorker {{blocksPerWorker}} --requestsPerBlock {{requestsPerBlock}} --duration {{duration}}" fanout: server: job: server variables: instances: 10 configurator: SimpleSilo client: job: client variables: command: fan-out instances: 1 numWorkers: 1 blocksPerWorker: 0 requestsPerBlock: 50 duration: 240 commandOptions: "--numWorkers {{numWorkers}} --blocksPerWorker {{blocksPerWorker}} --requestsPerBlock {{requestsPerBlock}} --duration {{duration}}" streaming: server: job: server variables: instances: 10 configurator: EventGeneratorStreamingSilo duration: 300 type: ExplicitGrainBasedAndImplicit streamsPerQueue: 1000 queueCount: 8 wait: 20 configuratorOptions: "--type {{type}} --streamsPerQueue {{streamsPerQueue}} --queueCount {{queueCount}} --wait {{wait}} --duration {{duration}}" client: job: client variables: command: counter instances: 1 commandOptions: "requests errors" reliability: server: job: server variables: instances: 10 configurator: SimpleSilo chaosagent: job: client waitForExit: false variables: command: chaosagent instances: 1 wait: 30 serversPerRound: 1 rounds: 4 roundDelay: 30 graceful: false restart: true commandOptions: "--wait {{wait}} --serversPerRound {{serversPerRound}} --rounds {{rounds}} --roundDelay {{roundDelay}} --graceful {{graceful}} --restart {{restart}}" client: job: client waitForExit: true variables: command: ping instances: 1 numWorkers: 250 blocksPerWorker: 0 requestsPerBlock: 500 duration: 180 commandOptions: "--numWorkers {{numWorkers}} --blocksPerWorker {{blocksPerWorker}} --requestsPerBlock {{requestsPerBlock}} --duration {{duration}}" rolling: server: job: server variables: instances: 10 configurator: SimpleSilo chaosagent: job: client waitForExit: false variables: command: chaosagent instances: 1 wait: 30 serversPerRound: 1 rounds: 4 roundDelay: 30 graceful: true restart: true commandOptions: "--wait {{wait}} --serversPerRound {{serversPerRound}} --rounds {{rounds}} --roundDelay {{roundDelay}} --graceful {{graceful}} --restart {{restart}}" client: job: client waitForExit: true variables: command: ping instances: 1 numWorkers: 250 blocksPerWorker: 0 requestsPerBlock: 500 duration: 180 commandOptions: "--numWorkers {{numWorkers}} --blocksPerWorker {{blocksPerWorker}} --requestsPerBlock {{requestsPerBlock}} --duration {{duration}}" counters: - provider: Microsoft.Orleans values: - name: app-requests measurement: orleans-counter/requests-per-second description: Request rate - name: activation-count measurement: orleans-counter/grain-activation-count description: Total number of grains results: # Microsoft.Orleans counters - name: orleans-counter/requests-per-second measurement: orleans-counter/requests-per-second description: Request rate format: "n0" aggregate: max reduce: max - name: orleans-counter/requests-per-second/95 measurement: orleans-counter/requests-per-second description: Request rate format: "n0" aggregate: percentile95 reduce: max - name: activation-count measurement: orleans-counter/grain-activation-count description: Active grains profiles: local: variables: azureQueueUri: "http://127.0.0.1:10001" azureTableUri: "http://127.0.0.1:10002" jobs: server: endpoints: - http://localhost:5010 variables: instances: 1 client: endpoints: - http://localhost:5010 chaosagent: endpoints: - http://localhost:5010 ================================================ FILE: es-metadata.yml ================================================ schemaVersion: 0.0.1 isProduction: true accountableOwners: service: 29f743e3-3a8f-4955-89e1-dcd58905919a routing: defaultAreaPath: org: devdiv path: DevDiv\ASP.NET Core ================================================ FILE: global.json ================================================ { "sdk": { "rollForward": "major", "version": "10.0.103" } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.AppHost/ActivationRebalancing.AppHost.csproj ================================================ Exe net10.0 enable enable true 6e52d7b6-4f1f-4e1c-abef-df2fe839e7f2 ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.AppHost/Program.cs ================================================ using Aspire.Hosting; var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("orleans-redis"); var orleans = builder.AddOrleans("cluster") .WithClustering(redis); var backend = builder.AddProject("backend") .WithReference(orleans) .WaitFor(redis) .WithReplicas(5); builder.AddProject("frontend") .WithReference(orleans.AsClient()) .WaitFor(backend) .WithReplicas(1); builder.Build().Run(); ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.AppHost/Properties/launchSettings.json ================================================ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:17267;http://localhost:15094", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21125", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22078" } }, "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:15094", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19108", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20187" } } } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.AppHost/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.AppHost/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Aspire.Hosting.Dcp": "Warning" } } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Cluster/ActivationRebalancing.Cluster.csproj ================================================ Exe net10.0 enable true ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Cluster/Program.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Orleans.Configuration; var builder = Host.CreateApplicationBuilder(args); builder.AddKeyedRedisClient("orleans-redis"); builder.Logging.AddFilter("Orleans.Runtime.Placement.Rebalancing", LogLevel.Trace); #pragma warning disable ORLEANSEXP002 builder.UseOrleans(builder => builder .Configure(o => { o.CollectionQuantum = TimeSpan.FromSeconds(15); }) .Configure(o => { o.LocalSiloPreferenceMargin = 0; }) .Configure(o => { o.RebalancerDueTime = TimeSpan.FromSeconds(5); o.SessionCyclePeriod = TimeSpan.FromSeconds(5); // uncomment these below, if you want higher migration rate //o.CycleNumberWeight = 1; //o.SiloNumberWeight = 0; }) .AddActivationRebalancer()); #pragma warning restore ORLEANSEXP002 builder.Services.AddHostedService(); var app = builder.Build(); await app.RunAsync(); internal class LoadDriverBackgroundService(IGrainFactory client) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { for (var i = 0; i < 5 * Random.Shared.Next(1, 1000); i++) { await client.GetGrain(Guid.NewGuid()).Ping(); } await Task.Delay(Random.Shared.Next(500, 1_000), stoppingToken); } } } public interface IRebalancingTestGrain : IGrainWithGuidKey { Task Ping(); } [CollectionAgeLimit(Minutes = 0.5)] public class RebalancingTestGrain : Grain, IRebalancingTestGrain { public Task Ping() => Task.CompletedTask; } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/ActivationRebalancing.Frontend.csproj ================================================ net10.0 enable enable true ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/Controllers/StatsController.cs ================================================ using Microsoft.AspNetCore.Mvc; using Orleans.Runtime; using Orleans; namespace ActivationRebalancing.Frontend.Controllers; [ApiController] [Route("api/[controller]")] public class StatsController(IClusterClient clusterClient) : ControllerBase { [HttpGet("silos")] public async Task GetStats() { var grainStats = await clusterClient .GetGrain(0) .GetDetailedGrainStatistics(); var siloData = grainStats.GroupBy(stat => stat.SiloAddress) .Select(g => new SiloData(g.Key.ToString(), g.Count())) .ToList(); if (siloData.Count == 4) { siloData = [.. siloData, new SiloData("x", 0)]; } if (siloData.Count > 5) { throw new NotSupportedException("The frontend cant support more than 6 silos"); } return Ok(siloData); } } public record SiloData(string Host, int Activations); ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/Program.cs ================================================ using Orleans.Hosting; var builder = WebApplication.CreateBuilder(args); builder.AddKeyedRedisClient("orleans-redis"); builder.UseOrleansClient(); builder.Services.AddControllers(); var app = builder.Build(); var options = new DefaultFilesOptions(); options.DefaultFileNames.Clear(); options.DefaultFileNames.Add("index.html"); app.UseDefaultFiles(options); app.UseStaticFiles(); app.MapControllers(); app.Run(); ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/Properties/launchSettings.json ================================================ { "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "index.html", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } ================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/wwwroot/index.html ================================================ Orleans Activation Load Balancing

Orleans Activation Load Balancing

Real-time monitoring and visualization of grain activation distribution across silos

Timeline
Current Distribution
Silo 1
Silo 2
Silo 3
Silo 4
Silo 5
================================================ FILE: playground/ActivationRebalancing/ActivationRebalancing.Frontend/wwwroot/worker.js ================================================ self.onmessage = function (e) { try { const data = e.data; const totalActivations = data.reduce((sum, d) => sum + d.activations, 0); const densityMatrix = Array.from({ length: 20 }, () => Array(20).fill('white')); data.forEach((d, i) => { const numCells = Math.round(d.activations / totalActivations * 400); const color = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'][i % 5]; let cellsFilled = 0; const maxFillWhiteCellAttempts = 500; for (let attempt = 0; attempt < maxFillWhiteCellAttempts && cellsFilled < numCells; attempt++) { const x = Math.floor(Math.random() * 20); const y = Math.floor(Math.random() * 20); if (densityMatrix[y][x] === 'white') { densityMatrix[y][x] = color; cellsFilled++; } } }); postMessage({ densityMatrix }); } catch (error) { postMessage({ error: error.message }); } }; ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.AppHost/ActivationRepartitioning.AppHost.csproj ================================================  Exe net10.0 enable enable true 6a521b87-2bf9-4af8-b7c7-4947536e1d50 ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.AppHost/Program.cs ================================================ using Aspire.Hosting; using Projects; var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("orleans-redis"); var orleans = builder.AddOrleans("cluster") .WithClustering(redis); builder.AddProject("frontend") .WithReference(orleans) .WaitFor(redis) .WithReplicas(5); builder.Build().Run(); ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.AppHost/Properties/launchSettings.json ================================================ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:17234;http://localhost:15087", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21284", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22143" } }, "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:15087", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19030", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20232" } } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.AppHost/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.AppHost/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Aspire.Hosting.Dcp": "Warning" } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/ActivationRepartitioning.Frontend.csproj ================================================ net10.0 enable enable true ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Data/ClusterDiagnosticsService.cs ================================================ using System.Diagnostics; using System.Runtime.InteropServices; using Orleans.Core.Internal; namespace ActivationRepartitioning.Frontend.Data; public class ClusterDiagnosticsService(IGrainFactory grainFactory) { private readonly Dictionary _hostKeys = []; private readonly Dictionary _hostDetails = []; private readonly Dictionary _grainDetails = []; // Grain to host id private readonly Dictionary _edges = []; private readonly IManagementGrain _managementGrain = grainFactory.GetGrain(0); private readonly record struct GrainDetails(int GrainKey, int HostKey); private readonly record struct HostDetails(int HostKey, int ActivationCount); private int _version; public async ValueTask GetGrainCallFrequencies() { var loaderGrain = grainFactory.GetGrain("root"); var loaderGrainType = loaderGrain.GetGrainId().Type; var resetCount = await loaderGrain.GetResetCount(); if (resetCount > _version) { _version = resetCount; await ResetAsync(); } lock (this) { _edges.Clear(); } var maxEdgeValue = 0; var maxActivationCount = 0; var silos = (await _managementGrain.GetHosts(onlyActive: true)).Keys.Order(); foreach (var silo in silos) { var hostKey = GetHostVertex(silo); var activationCount = 0; foreach (var activation in await _managementGrain.GetDetailedGrainStatistics(hostsIds: [silo])) { if (activation.GrainId.Type.Equals(loaderGrainType)) continue; if (activation.GrainId.IsSystemTarget()) continue; lock (this) { var details = GetGrainVertex(activation.GrainId, hostKey); _grainDetails[activation.GrainId] = new(details.GrainKey, hostKey); ++activationCount; } } lock (this) { maxActivationCount = Math.Max(maxActivationCount, activationCount); _hostDetails[silo] = new(hostKey, activationCount); } } foreach (var edge in await _managementGrain.GetGrainCallFrequencies()) { if (edge.TargetGrain.IsSystemTarget() || edge.SourceGrain.IsSystemTarget()) continue; lock (this) { var sourceHostId = GetHostVertex(edge.SourceHost); var targetHostId = GetHostVertex(edge.TargetHost); var sourceVertex = GetGrainVertex(edge.SourceGrain, sourceHostId); var targetVertex = GetGrainVertex(edge.TargetGrain, targetHostId); maxEdgeValue = Math.Max(maxEdgeValue, (int)edge.CallCount); UpdateEdge(new(sourceVertex.GrainKey, targetVertex.GrainKey), edge.CallCount); } } lock (this) { var grainIds = new List(_grainDetails.Count); CollectionsMarshal.SetCount(grainIds, _grainDetails.Count); foreach ((var grainId, var (grainKey, hostKey)) in _grainDetails) { grainIds[grainKey] = new(grainId.ToString(), grainId.Key.ToString()!, hostKey, 1.0); } var hostIds = new List(_hostKeys.Count); CollectionsMarshal.SetCount(hostIds, _hostKeys.Count); foreach ((var hostId, var key) in _hostKeys) { if (_hostDetails.TryGetValue(hostId, out var details)) { hostIds[key] = new(hostId.ToString(), details.ActivationCount); } } var edges = new List(); foreach (var edge in _edges) { edges.Add(new(edge.Key.Source, edge.Key.Target, edge.Value)); } return new(grainIds, hostIds, edges, maxEdgeValue, maxActivationCount); } } internal async ValueTask ResetAsync() { var fanoutType = grainFactory.GetGrain(0, "0").GetGrainId().Type; foreach (var activation in await _managementGrain.GetDetailedGrainStatistics()) { if (!activation.GrainId.Type.Equals(fanoutType)) continue; await grainFactory.GetGrain(activation.GrainId).DeactivateOnIdle(); } Reset(); } internal void Reset() { lock (this) { _hostKeys.Clear(); _hostDetails.Clear(); _grainDetails.Clear(); _edges.Clear(); } } private GrainDetails GetGrainVertex(GrainId grainId, int hostKey) { lock (this) { ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(_grainDetails, grainId, out var exists); if (!exists) { key = new(_grainDetails.Count - 1, hostKey); } return key; } } private int GetHostVertex(SiloAddress silo) { lock (this) { ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(_hostKeys, silo, out var exists); if (!exists) { key = _hostKeys.Count - 1; } return key; } } private void UpdateEdge(Key key, ulong increment) { lock (this) { ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_edges, key, out var exists); count += increment; } } } public record class CallGraph(List GrainIds, List HostIds, List Edges, int MaxEdgeValue, int MaxActivationCount); public record struct HostNode(string Name, int ActivationCount); public record struct GraphNode(string Name, string Key, int Host, double Weight); public record struct Key(int Source, int Target); public record struct GraphEdge(int Source, int Target, double Weight); ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Program.cs ================================================ using ActivationRepartitioning.Frontend.Data; using Microsoft.AspNetCore.Mvc; using Orleans.Configuration; using Orleans.Placement.Repartitioning; var builder = WebApplication.CreateBuilder(args); builder.AddKeyedRedisClient("orleans-redis"); builder.UseOrleans(orleans => { #pragma warning disable ORLEANSEXP001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. orleans.AddActivationRepartitioner(); orleans.Configure(o => { o.MinRoundPeriod = TimeSpan.FromSeconds(5); o.MaxRoundPeriod = TimeSpan.FromSeconds(15); o.RecoveryPeriod = TimeSpan.FromSeconds(5); o.AnchoringFilterEnabled = false; }); #pragma warning restore ORLEANSEXP001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning disable ORLEANSEXP002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. orleans.AddActivationRebalancer(); orleans.Configure(o => { o.RebalancerDueTime = TimeSpan.FromSeconds(10); o.SessionCyclePeriod = TimeSpan.FromSeconds(5); o.CycleNumberWeight = 0.7; }); #pragma warning restore ORLEANSEXP002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. }); // Add services to the container. builder.Services.AddSingleton(); var app = builder.Build(); var clusterDiagnosticsService = app.Services.GetRequiredService(); app.MapGet("/data.json", ([FromServices] ClusterDiagnosticsService clusterDiagnosticsService) => clusterDiagnosticsService.GetGrainCallFrequencies()); app.MapPost("/reset", async ([FromServices] IGrainFactory grainFactory) => { await grainFactory.GetGrain("root").Reset(); }); app.MapPost("/add", async ([FromServices] IGrainFactory grainFactory) => { await grainFactory.GetGrain("root").AddForest(); }); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); await app.StartAsync(); var loadGrain = app.Services.GetRequiredService().GetGrain("root"); for (var i = 0; i < 5; i++) { for (var j = 0; j < 3; j++) { await loadGrain.AddForest(); } await Task.Delay(TimeSpan.FromSeconds(2)); } await app.WaitForShutdownAsync(); public interface ILoaderGrain : IGrainWithStringKey { ValueTask AddForest(); ValueTask Reset(); ValueTask GetResetCount(); } public class LoaderGrain : Grain, ILoaderGrain { private int _numForests = 0; private int _resetCount; public async ValueTask AddForest() { var forest = _numForests++; var loadGrain = GrainFactory.GetGrain(0, forest.ToString()); await loadGrain.Ping(); } public async ValueTask Reset() { ++_resetCount; _numForests = 0; await ServiceProvider.GetRequiredService().ResetAsync(); await GrainFactory.GetGrain(0).ResetGrainCallFrequencies(); } public ValueTask GetResetCount() => new(_resetCount); } public interface IFanOutGrain : IGrainWithIntegerCompoundKey { public ValueTask Ping(); } public class FanOutGrain : Grain, IFanOutGrain { public const int FanOutFactor = 4; public const int MaxLevel = 2; private readonly List _children; public FanOutGrain() { var id = this.GetPrimaryKeyLong(out var forest); if (forest is null) { throw new InvalidOperationException("FanOutGrain must be created with a forest identifier (i.e, a grain key extension)."); } var level = id == 0 ? 0 : (int)Math.Log(id, FanOutFactor); var numChildren = level < MaxLevel ? FanOutFactor : 0; _children = new List(numChildren); var childBase = (id + 1) * FanOutFactor; for (var i = 1; i <= numChildren; i++) { var child = GrainFactory.GetGrain(childBase + i, forest); _children.Add(child); } this.RegisterGrainTimer(() => Ping().AsTask(), TimeSpan.FromSeconds(0.5), TimeSpan.FromSeconds(0.5)); } public async ValueTask Ping() { var tasks = new List(_children.Count); foreach (var child in _children) { tasks.Add(child.Ping()); } // Wait for the tasks to complete. foreach (var task in tasks) { await task; } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:14770", "sslPort": 44343 } }, "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:5022", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:7000;http://localhost:5022", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/appsettings.Development.json ================================================ { "DetailedErrors": true, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/wwwroot/css/open-iconic/FONT-LICENSE ================================================ SIL OPEN FONT LICENSE Version 1.1 Copyright (c) 2014 Waybury PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/wwwroot/css/open-iconic/ICON-LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Waybury Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/wwwroot/css/open-iconic/README.md ================================================ [Open Iconic v1.1.1](https://github.com/iconic/open-iconic) =========== ### Open Iconic is the open source sibling of [Iconic](https://github.com/iconic/open-iconic). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](https://github.com/iconic/open-iconic) ## What's in Open Iconic? * 223 icons designed to be legible down to 8 pixels * Super-light SVG files - 61.8 for the entire set * SVG sprite—the modern replacement for icon fonts * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. ## Getting Started #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](https://github.com/iconic/open-iconic) and [Reference](https://github.com/iconic/open-iconic) sections. ### General Usage #### Using Open Iconic's SVGs We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). ``` icon name ``` #### Using Open Iconic's SVG Sprite Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* ``` ``` Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. ``` .icon { width: 16px; height: 16px; } ``` Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. ``` .icon-account-login { fill: #f00; } ``` To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). #### Using Open Iconic's Icon Font... ##### …with Bootstrap You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` ``` ``` ``` ``` ##### …with Foundation You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` ``` ``` ``` ``` ##### …on its own You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` ``` ``` ``` ``` ## License ### Icons All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). ### Fonts All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/wwwroot/css/site.css ================================================ @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } h1:focus { outline: none; } a, .btn-link { color: #0071c1; } .btn-primary { color: #fff; background-color: #1b6ec2; border-color: #1861ac; } .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } .content { padding-top: 1.1rem; } .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; } .validation-message { color: red; } #blazor-error-ui { background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); display: none; left: 0; padding: 0.6rem 1.25rem 0.7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; } #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; color: white; } .blazor-error-boundary::after { content: "An error has occurred." } ================================================ FILE: playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/wwwroot/index.html ================================================ Orleans Activation Rebalancing
================================================ FILE: playground/ActivationSheddingToy/ActivationSheddingToy.csproj ================================================ Exe net10.0 enable enable true true ActivationSheddingToy ActivationSheddingToy ================================================ FILE: playground/ActivationSheddingToy/ActivationSheddingToyHostedService.cs ================================================ using Microsoft.Extensions.Hosting; using Orleans; using Orleans.Runtime; using Orleans.Statistics; internal sealed class ActivationSheddingToyHostedService(IGrainFactory grainFactory, IEnvironmentStatisticsProvider environmentStatisticsProvider) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var i = 0; var tasks = new List(); var stats = new List<(int GrainCount, long UsageBytes)>(); while (!stoppingToken.IsCancellationRequested) { while (tasks.Count < 10_000) { var grain = grainFactory.GetGrain(++i); tasks.Add(grain.Ping()); } await Task.WhenAll(tasks); tasks.Clear(); if (i % 100_000 == 0) { var activationCount = await grainFactory.GetGrain(0).GetTotalActivationCount(); var envStats = environmentStatisticsProvider.GetEnvironmentStatistics(); while (envStats.MemoryUsagePercentage > 80) { Console.WriteLine($"Memory usage is high ({envStats.MemoryUsagePercentage:N2}%) with {activationCount} activations."); GC.Collect(); await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); envStats = environmentStatisticsProvider.GetEnvironmentStatistics(); activationCount = await grainFactory.GetGrain(0).GetTotalActivationCount(); } PrintUsage(i, stats, envStats, activationCount); } if (stats.Count == 50) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); while (await timer.WaitForNextTickAsync(stoppingToken)) { var activationCount = await grainFactory.GetGrain(0).GetTotalActivationCount(); var envStats = environmentStatisticsProvider.GetEnvironmentStatistics(); PrintUsage(i, stats, envStats, activationCount); if (stats.Count == 10) { break; } } break; } } PlotAndPrintStats(stats); } private void PrintUsage(int i, List<(int GrainCount, long UsageBytes)> stats, EnvironmentStatistics envStats, int activationCount) { GC.Collect(); var usage = GC.GetTotalMemory(forceFullCollection: true); stats.Add((i, usage)); Console.WriteLine($"{i:N0} active grains. {usage:N0}bytes used. {usage / i:N0}bytes/grain (approx)"); Console.WriteLine($"\t{envStats}"); Console.WriteLine($"\tActivations: {activationCount}"); } private static void PlotAndPrintStats(List<(int GrainCount, long UsageBytes)> stats) { if (stats.Count < 2) { Console.WriteLine("Not enough data points to plot or calculate marginal usage."); return; } // Calculate and print marginal memory usage per grain Console.WriteLine("Marginal memory usage per grain (bytes) between points:"); for (int j = 1; j < stats.Count; j++) { int deltaGrains = stats[j].GrainCount - stats[j - 1].GrainCount; long deltaBytes = stats[j].UsageBytes - stats[j - 1].UsageBytes; double marginal = deltaGrains != 0 ? (double)deltaBytes / deltaGrains : double.NaN; Console.WriteLine($"{stats[j - 1].GrainCount:N0} -> {stats[j].GrainCount:N0}: {marginal:N2} bytes/grain"); } } } internal sealed class ActivationSheddingToyGrain : Grain, IActivationSheddingToyGrain { private byte[] _buffer = new byte[100_000]; public Task Ping() { _ = _buffer; return Task.CompletedTask; } } internal interface IActivationSheddingToyGrain : IGrainWithIntegerKey { Task Ping(); } ================================================ FILE: playground/ActivationSheddingToy/Program.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(orleans => { orleans.UseLocalhostClustering(); #pragma warning disable ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. orleans.AddDistributedGrainDirectory(); #pragma warning restore ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. }); builder.Services.Configure(options => { options.EnableActivationSheddingOnMemoryPressure = true; options.MemoryUsageLimitPercentage = 80; options.MemoryUsageTargetPercentage = 50; }); builder.Services.AddHostedService(); await builder.Build().RunAsync(); ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.AppHost/ChaoticCluster.AppHost.csproj ================================================ Exe net10.0 enable enable true 8cceaca4-1c1f-473f-ac3a-6f220c8791cf ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.AppHost/Program.cs ================================================ using Projects; var builder = DistributedApplication.CreateBuilder(args); builder.AddProject("silo"); builder.Build().Run(); ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.AppHost/Properties/launchSettings.json ================================================ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:17213;http://localhost:15139", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21045", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22043" } }, "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:15139", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19150", "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20085" } } } } ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.AppHost/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.AppHost/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Aspire.Hosting.Dcp": "Warning" } } } ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.ServiceDefaults/ChaoticCluster.ServiceDefaults.csproj ================================================ net10.0 enable enable true ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.ServiceDefaults/Extensions.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; namespace Microsoft.Extensions.Hosting; // Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. // This project should be referenced by each service project in your solution. // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults public static class Extensions { public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) { builder.ConfigureOpenTelemetry(); builder.AddDefaultHealthChecks(); builder.Services.AddServiceDiscovery(); builder.Services.ConfigureHttpClientDefaults(http => { // Turn on resilience by default http.AddStandardResilienceHandler(); // Turn on service discovery by default http.AddServiceDiscovery(); }); // Uncomment the following to restrict the allowed schemes for service discovery. // builder.Services.Configure(options => // { // options.AllowedSchemes = ["https"]; // }); return builder; } public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) { builder.Logging.AddOpenTelemetry(logging => { logging.IncludeFormattedMessage = true; logging.IncludeScopes = true; }); builder.Services.AddOpenTelemetry() .WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() .AddMeter("System.Runtime") .AddMeter("Microsoft.Orleans"); }); builder.AddOpenTelemetryExporters(); return builder; } private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) { var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); if (useOtlpExporter) { builder.Services.AddOpenTelemetry().UseOtlpExporter(); } // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) //{ // builder.Services.AddOpenTelemetry() // .UseAzureMonitor(); //} return builder; } public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) { builder.Services.AddHealthChecks() // Add a default liveness check to ensure app is responsive .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); return builder; } public static WebApplication MapDefaultEndpoints(this WebApplication app) { // Adding health checks endpoints to applications in non-development environments has security implications. // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. if (app.Environment.IsDevelopment()) { // All health checks must pass for app to be considered ready to accept traffic after starting app.MapHealthChecks("/health"); // Only health checks tagged with the "live" tag must pass for app to be considered alive app.MapHealthChecks("/alive", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") }); } return app; } } ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.Silo/ChaoticCluster.Silo.csproj ================================================ Exe net10.0 enable enable true true ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.Silo/Program.cs ================================================ using System.Diagnostics; using ChaoticCluster.Silo; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Orleans.TestingHost; var builder = Host.CreateApplicationBuilder(args); builder.AddServiceDefaults(); // Configure OTel using var app = builder.Build(); await app.StartAsync(); var testClusterBuilder = new InProcessTestClusterBuilder(1); testClusterBuilder.ConfigureSilo((options, siloBuilder) => new SiloBuilderConfigurator().Configure(siloBuilder)); testClusterBuilder.ConfigureSiloHost((options, hostBuilder) => { foreach (var provider in app.Services.GetServices()) { hostBuilder.Logging.AddProvider(provider); } }); testClusterBuilder.ConfigureClientHost(hostBuilder => { foreach (var provider in app.Services.GetServices()) { hostBuilder.Logging.AddProvider(provider); } }); var testCluster = testClusterBuilder.Build(); await testCluster.DeployAsync(); var log = testCluster.Client.ServiceProvider.GetRequiredService>(); log.LogInformation($"ServiceId: {testCluster.Options.ServiceId}"); log.LogInformation($"ClusterId: {testCluster.Options.ClusterId}"); var cts = new CancellationTokenSource(TimeSpan.FromMinutes(15)); var reconfigurationTimer = Stopwatch.StartNew(); var upperLimit = 10; var lowerLimit = 1; // Membership is kept on the primary, so we can't go below 1 var target = upperLimit; var idBase = 0L; var client = testCluster.Silos[0].ServiceProvider.GetRequiredService(); const int CallsPerIteration = 100; const int MaxGrains = 524_288; // 2**19; var loadTask = Task.Run(async () => { while (!cts.IsCancellationRequested) { var time = Stopwatch.StartNew(); var tasks = Enumerable.Range(0, CallsPerIteration).Select(i => client.GetGrain((idBase + i) % MaxGrains).Ping().AsTask()).ToList(); var workTask = Task.WhenAll(tasks); using var delayCancellation = new CancellationTokenSource(); var delay = TimeSpan.FromMilliseconds(90_000); var delayTask = Task.Delay(delay, delayCancellation.Token); await Task.WhenAny(workTask, delayTask); try { await workTask; } catch (SiloUnavailableException sue) { log.LogInformation(sue, "Swallowed transient exception."); } catch (OrleansMessageRejectionException omre) { log.LogInformation(omre, "Swallowed rejection."); } catch (Exception exception) { log.LogError(exception, "Unhandled exception."); throw; } delayCancellation.Cancel(); idBase += CallsPerIteration; } }); var chaosTask = Task.Run(async () => { var clusterOperation = Task.CompletedTask; while (!cts.IsCancellationRequested) { try { var remaining = TimeSpan.FromSeconds(10) - reconfigurationTimer.Elapsed; if (remaining <= TimeSpan.Zero) { reconfigurationTimer.Restart(); await clusterOperation; clusterOperation = Task.Run(async () => { var currentCount = testCluster.Silos.Count; if (currentCount > target) { // Stop or kill a random silo, but not the primary (since that hosts cluster membership) var victim = testCluster.Silos[Random.Shared.Next(1, testCluster.Silos.Count - 1)]; if (currentCount % 2 == 0) { log.LogInformation($"Stopping '{victim.SiloAddress}'."); await testCluster.StopSiloAsync(victim); log.LogInformation($"Stopped '{victim.SiloAddress}'."); } else { log.LogInformation($"Killing '{victim.SiloAddress}'."); await testCluster.KillSiloAsync(victim); log.LogInformation($"Killed '{victim.SiloAddress}'."); } } else if (currentCount < target) { log.LogInformation("Starting new silo."); var result = await testCluster.StartAdditionalSiloAsync(); log.LogInformation($"Started '{result.SiloAddress}'."); } if (currentCount <= lowerLimit) { target = upperLimit; } else if (currentCount >= upperLimit) { target = lowerLimit; } }); } else { await Task.Delay(remaining); } } catch (Exception exception) { log.LogInformation(exception, "Ignoring chaos exception."); } } }); await await Task.WhenAny(loadTask, chaosTask); cts.Cancel(); await Task.WhenAll(loadTask, chaosTask); await testCluster.StopAllSilosAsync(); await testCluster.DisposeAsync(); await app.StopAsync(); ================================================ FILE: playground/ChaoticCluster/ChaoticCluster.Silo/SiloBuilderConfigurator.cs ================================================ using Orleans.TestingHost; namespace ChaoticCluster.Silo; class SiloBuilderConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder siloBuilder) { #pragma warning disable ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. siloBuilder.AddDistributedGrainDirectory(); #pragma warning restore ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } } internal interface IMyTestGrain : IGrainWithIntegerKey { ValueTask Ping(); } [CollectionAgeLimit(Minutes = 1.01)] internal class MyTestGrain : Grain, IMyTestGrain { public ValueTask Ping() => default; } ================================================ FILE: playground/DashboardCohosted/DashboardCohosted.csproj ================================================ Exe net8.0 false true ================================================ FILE: playground/DashboardCohosted/Program.cs ================================================ using Orleans.Dashboard; var builder = WebApplication.CreateBuilder(args); // Configure Orleans builder.UseOrleans(siloBuilder => { siloBuilder.UseLocalhostClustering(); siloBuilder.UseInMemoryReminderService(); siloBuilder.AddMemoryGrainStorageAsDefault(); // Add the dashboard siloBuilder.AddDashboard(); }); var app = builder.Build(); // Map dashboard endpoints at the root app.MapOrleansDashboard(); app.Run(); ================================================ FILE: playground/DashboardCohosted/Properties/launchSettings.json ================================================ { "profiles": { "DashboardCohosted": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:55355;http://localhost:55356" } } } ================================================ FILE: playground/DashboardCohosted/README.md ================================================ # Orleans Dashboard - Cohosted Example This example demonstrates how to cohost the Orleans Dashboard within the same web application that runs your Orleans silo. ## Overview This is the simplest way to add the Orleans Dashboard to your application. The dashboard is hosted within the same process as your silo, using ASP.NET Core minimal APIs. ## Key Features - **Single Process**: Both Orleans and the dashboard run in the same web application - **Minimal Configuration**: Simple setup with just a few lines of code - **ASP.NET Core Integration**: Uses `WebApplication.CreateBuilder()` and minimal APIs ## How It Works The example shows: 1. Creating a web application with `WebApplication.CreateBuilder()` 2. Configuring Orleans using `builder.UseOrleans()` 3. Adding the dashboard with `siloBuilder.AddDashboard()` 4. Mapping dashboard endpoints with `app.MapOrleansDashboard()` ## Running the Example ```bash dotnet run ``` Once running, open your browser to: - **Dashboard**: https://localhost:55355/ (or http://localhost:55356/) ## Code Walkthrough ```csharp var builder = WebApplication.CreateBuilder(args); // Configure Orleans builder.UseOrleans(siloBuilder => { siloBuilder.UseLocalhostClustering(); siloBuilder.UseInMemoryReminderService(); siloBuilder.AddMemoryGrainStorageAsDefault(); // Add the dashboard siloBuilder.AddDashboard(); }); var app = builder.Build(); // Map dashboard endpoints app.MapOrleansDashboard(); app.Run(); ``` ## When to Use This Approach Use this cohosted approach when: - You want the simplest setup - You're building a web application that also hosts Orleans - You want dashboard access on the same port as your web app - You're running a single silo or don't need a centralized dashboard ## Customization ### Custom Route Prefix ```csharp app.MapOrleansDashboard(routePrefix: "/dashboard"); ``` ### Add Authentication ```csharp app.MapOrleansDashboard().RequireAuthorization(); ``` ### Configure Update Interval ```csharp siloBuilder.AddDashboard(options => { options.CounterUpdateIntervalMs = 2000; // Update every 2 seconds }); ``` ## Important Notes > [!WARNING] > The Orleans Dashboard is designed for **development and testing scenarios only**. It is not recommended for production deployments as it can have a significant performance impact on your cluster. ## Related Examples - **DashboardSeparateHost**: Shows how to host the dashboard in a separate application - **DashboardToy**: Advanced example with .NET Aspire integration ## Learn More - [Orleans Dashboard Documentation](../../../src/Dashboard/Orleans.Dashboard/README.md) - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) ================================================ FILE: playground/DashboardSeparateHost/DashboardSeparateHost.csproj ================================================ Exe net8.0 false true ================================================ FILE: playground/DashboardSeparateHost/Program.cs ================================================ using Orleans.Configuration; using Orleans.Dashboard; using Orleans.Runtime.MembershipService.SiloMetadata; using System.Net; using TestGrains; // // In this sample we integrate the Dashboard Minimal APIs into the client application. // var siloHostBuilder = Host.CreateApplicationBuilder(args); siloHostBuilder.UseOrleans(builder => { builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(IPAddress.Loopback, 11111)); builder.UseInMemoryReminderService(); builder.AddMemoryGrainStorageAsDefault(); builder.ConfigureEndpoints(IPAddress.Loopback, 11111, 30000); builder.AddDashboard(); }); siloHostBuilder.Services.AddSingleton(); using var siloHost = siloHostBuilder.Build(); await siloHost.StartAsync(); await Task.Delay(1000); // Create a WebApplication for hosting the dashboard var dashboardBuilder = WebApplication.CreateBuilder(args); // Configure Orleans client dashboardBuilder.UseOrleansClient(clientBuilder => { clientBuilder.UseStaticClustering(options => options.Gateways.Add(new IPEndPoint(IPAddress.Loopback, 30000).ToGatewayUri())); // Add dashboard services clientBuilder.AddDashboard(); }); var dashboardApp = dashboardBuilder.Build(); // Map dashboard endpoints dashboardApp.MapOrleansDashboard(); await dashboardApp.RunAsync(); await siloHost.StopAsync(); ================================================ FILE: playground/DashboardSeparateHost/Properties/launchSettings.json ================================================ { "profiles": { "DashboardSeparateHost": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:55355;http://localhost:55356" } } } ================================================ FILE: playground/DashboardSeparateHost/README.md ================================================ # Orleans Dashboard - Separate Host Example This example demonstrates how to host the Orleans Dashboard in a separate web application that connects to an Orleans cluster as a client. ## Overview This approach separates the dashboard web service from your silos. ## Key Features - **Separate Process**: Dashboard web service runs independently from Orleans silos - **Client Connection**: Connects to the Orleans cluster as an Orleans client - **Standalone Silo**: Includes a separate silo host for demonstration - **Test Grains**: Includes test grains to generate activity for the dashboard ## How It Works The example consists of two components: 1. **Silo Host**: A standalone Orleans silo that runs independently 2. **Dashboard Web App**: A web application that connects as an Orleans client and hosts the dashboard ## Running the Example ```bash dotnet run ``` The application will: 1. Start an Orleans silo on ports 11111 (silo) and 30000 (gateway) 2. Start the dashboard web application 3. Generate test grain activity automatically Once running, open your browser to the default ports (typically https://localhost:5001 or http://localhost:5000). ## Code Walkthrough ### Silo Host ```csharp var siloHost = Host.CreateDefaultBuilder(args) .UseOrleans((_, builder) => { builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(IPAddress.Loopback, 11111)); builder.ConfigureEndpoints(IPAddress.Loopback, 11111, 30000); builder.AddDashboard(); // ... other configuration }) .Build(); ``` ### Dashboard Client ```csharp var dashboardBuilder = WebApplication.CreateBuilder(args); // Configure Orleans client dashboardBuilder.UseOrleansClient(clientBuilder => { clientBuilder.UseStaticClustering(options => options.Gateways.Add(new IPEndPoint(IPAddress.Loopback, 30000).ToGatewayUri())); // Add dashboard services clientBuilder.AddDashboard(); }); var app = dashboardBuilder.Build(); // Map dashboard endpoints app.MapOrleansDashboard(); await app.RunAsync(); ``` ## Architecture ``` ┌─────────────────┐ │ Silo Host │ │ (Port 11111) │ │ Gateway: 30000 │ └────────▲────────┘ │ │ Orleans Client Connection │ ┌────────┴────────┐ │ Dashboard │ │ Web App │ │ (Port 5000) │ └─────────────────┘ ``` ## Customization ### Connect to Remote Cluster ```csharp dashboardBuilder.UseOrleansClient(clientBuilder => { clientBuilder.UseStaticClustering(options => { options.Gateways.Add(new IPEndPoint(IPAddress.Parse("10.0.0.1"), 30000).ToGatewayUri()); options.Gateways.Add(new IPEndPoint(IPAddress.Parse("10.0.0.2"), 30000).ToGatewayUri()); }); clientBuilder.AddDashboard(); }); ``` ### Custom Route Prefix ```csharp app.MapOrleansDashboard(routePrefix: "/dashboard"); ``` ### Add Authentication ```csharp app.MapOrleansDashboard().RequireAuthorization(); ``` ## Important Notes > [!WARNING] > The Orleans Dashboard is designed for **development and testing scenarios only**. It is not recommended for production deployments as it can have a significant performance impact on your cluster. ## Related Examples - **DashboardCohosted**: Shows how to host the dashboard within a silo - **DashboardToy**: Advanced example with .NET Aspire integration ## Learn More - [Orleans Dashboard Documentation](../../../src/Dashboard/Orleans.Dashboard/README.md) - [Orleans Client Configuration](https://learn.microsoft.com/dotnet/orleans/host/configuration-guide/client-configuration) - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) ================================================ FILE: playground/Directory.Build.props ================================================ <_ParentDirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildPropsFile)')) false false false false true ================================================ FILE: samples/README.md ================================================ # Orleans Samples > [!IMPORTANT] > 📢 This collection of samples has been moved to the official [`dotnet/samples` repository](https://github.com/dotnet/samples/tree/main/orleans) and is part of the [Samples browser experience](https://learn.microsoft.com/en-us/samples/browse/?expanded=dotnet&products=dotnet-orleans). - :octocat: [dotnet/samples](https://github.com/dotnet/samples/tree/main/orleans) - :eyes: [Samples browser](https://learn.microsoft.com/samples/browse/?expanded=dotnet&products=dotnet-orleans) ## [Hello, World!](https://learn.microsoft.com/samples/dotnet/samples/orleans-hello-world-sample-app)

A *Hello, World!* application which demonstrates how to create and use your first grains. ### Demonstrates - How to get started with Orleans - How to define and implement grain interface - How to get a reference to a grain and call a grain ## [Adventure](https://learn.microsoft.com/samples/dotnet/samples/orleans-text-adventure-game)

Before there were graphical user interfaces, before the era of game consoles and massive-multiplayer games, there were VT100 terminals and there was [Colossal Cave Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), [Zork](https://en.wikipedia.org/wiki/Zork), and [Microsoft Adventure](https://en.wikipedia.org/wiki/Microsoft_Adventure). Possibly lame by today's standards, back then it was a magical world of monsters, chirping birds, and things you could pick up. It's the inspiration for this sample. ### Demonstrates - How to structure an application (in this case, a game) using grains - How to connect an external client to an Orleans cluster (`ClientBuilder`) ## [Chirper](https://learn.microsoft.com/samples/dotnet/samples/orleans-chirper-social-media-sample-app)

A social network pub/sub system, with short text messages being sent between users. Publishers send out short *"Chirp"* messages (not to be confused with *"Tweets"*, for a variety of legal reasons) to any other users that are following them. ### Demonstrates - How to build a simplified social media / social network application using Orleans - How to store state within a grain using grain persistence (`IPersistentState`) - Grains which implement multiple grain interfaces - Reentrant grains, which allow for multiple grain calls to be executed concurrently, in a single-threaded, interleaving fashion - Using a *grain observer* (`IGrainObserver`) to receive push notifications from grains ## [GPS Tracker](https://learn.microsoft.com/samples/dotnet/samples/orleans-gps-device-tracker-sample)

A service for tracking GPS-equipped [IoT](https://en.wikipedia.org/wiki/Internet_of_Things) devices on a map. Device locations are updated in near-real-time using SignalR and hence this sample demonstrates one approach to integrating Orleans with SignalR. The device updates originate from a *device gateway*, which is implemented using a separate process which connects to the main service and simulates a number of devices moving in a pseudorandom fashion around an area of San Francisco. ### Demonstrates - How to use Orleans to build an [Internet of Things](https://en.wikipedia.org/wiki/Internet_of_Things) application - How Orleans can be co-hosted and integrated with [ASP.NET Core SignalR](https://docs.microsoft.com/aspnet/core/signalr/introduction) - How to broadcast real-time updates from a grain to a set of clients using Orleans and SignalR ## [HanBaoBao](https://github.com/ReubenBond/hanbaobao-web)

An English-Mandarin dictionary Web application demonstrating deployment to Kubernetes, fan-out grain calls, and request throttling. ### Demonstrates - How to build a realistic application using Orleans - How to deploy an Orleans-based application to Kubernetes - How to integrate Orleans with ASP.NET Core and a [*Single-page Application*](https://en.wikipedia.org/wiki/Single-page_application) JavaScript framework ([Vue.js](https://vuejs.org/)) - How to implement leaky-bucket request throttling - How to load and query data from a database - How to cache results lazily and temporarily - How to fan-out requests to many grains and collect the results ## [Presence Service](https://learn.microsoft.com/samples/dotnet/samples/orleans-gaming-presence-service-sample)

A gaming presence service, similar to one of the Orleans-based services built for [Halo](https://www.xbox.com/games/halo). A presence service tracks players and game sessions in near-real-time. ### Demonstrates - A simplified version of a real-world use of Orleans - Using a *grain observer* (`IGrainObserver`) to receive push notifications from grains ## [Tic Tac Toe](https://learn.microsoft.com/samples/dotnet/samples/orleans-tictactoe-web-based-game)

A Web-based [Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) game using ASP.NET MVC, JavaScript, and Orleans. ### Demonstrates - How to build an online game using Orleans - How to build a basic game lobby system - How to access Orleans grains from an ASP.NET Core MVC application ## [Voting](https://learn.microsoft.com/samples/dotnet/samples/orleans-voting-sample-app-on-kubernetes)

A Web application for voting on a set of choices. This sample demonstrates deployment to Kubernetes. The application uses [.NET Generic Host](https://docs.microsoft.com/dotnet/core/extensions/generic-host) to co-host [ASP.NET Core](https://docs.microsoft.com/aspnet/core) and Orleans as well as the [Orleans Dashboard](https://www.nuget.org/packages/Microsoft.Orleans.Dashboard) together in the same process.

### Demonstrates - How to deploy an Orleans-based application to Kubernetes - How to configure the [Orleans Dashboard](https://www.nuget.org/packages/Microsoft.Orleans.Dashboard) ## [Chat Room](https://learn.microsoft.com/samples/dotnet/samples/orleans-chat-room-sample)

A terminal-based chat application built using [Orleans Streams](https://docs.microsoft.com/dotnet/orleans/streaming). ### Demonstrates - How to build a chat application using Orleans - How to use [Orleans Streams](https://docs.microsoft.com/dotnet/orleans/streaming) ## [Bank Account](https://learn.microsoft.com/samples/dotnet/samples/orleans-bank-account-acid-transactions)

Simulates bank accounts, using ACID transactions to transfer random amounts between a set of accounts. ### Demonstrates - How to use Orleans Transactions to safely perform operations involving multiple stateful grains with ACID guarantees and serializable isolation. ## [Blazor Server](https://learn.microsoft.com/samples/dotnet/samples/orleans-aspnet-core-blazor-server-sample) and [Blazor WebAssembly](https://learn.microsoft.com/samples/dotnet/samples/orleans-aspnet-core-blazor-wasm-sample)

These two Blazor samples are based on the [Blazor introductory tutorials](https://dotnet.microsoft.com/learn/aspnet/blazor-tutorial/intro), adapted for use with Orleans. The [Blazor WebAssembly](./Blazor/BlazorWasm/#readme) sample uses the [Blazor WebAssembly hosting model](https://docs.microsoft.com/aspnet/core/blazor/hosting-models#blazor-webassembly). The [Blazor Server](./Blazor/BlazorServer/#readme) sample uses the [Blazor Server hosting model](https://docs.microsoft.com/aspnet/core/blazor/hosting-models#blazor-server). They include an interactive counter, a TODO list, and a Weather service. ### Demonstrates - How to integrate ASP.NET Core Blazor Server with Orleans - How to integrate ASP.NET Core Blazor WebAssembly (WASM) with Orleans ## [Stocks](https://learn.microsoft.com/samples/dotnet/samples/orleans-stocks-sample-app)

A stock price application which fetches prices from a remote service using an HTTP call and caches prices temporarily in a grain. A [`BackgroundService`](https://docs.microsoft.com/aspnet/core/fundamentals/host/hosted-services#backgroundservice-base-class) periodically polls for updates stock prices from various `StockGrain` grains which correspond to a set of stock symbols. ### Demonstrates - How to use Orleans from within a [`BackgroundService`](https://docs.microsoft.com/aspnet/core/fundamentals/host/hosted-services#backgroundservice-base-class). - How to use timers within a grain - How to make external service calls using .NET's `HttpClient` and cache the results within a grain. ## [Transport Layer Security](https://learn.microsoft.com/samples/dotnet/samples/orleans-transport-layer-security-tls)

A *Hello, World!* application configured to use mutual [*Transport Layer Security*](https://en.wikipedia.org/wiki/Transport_Layer_Security) to secure network communication between every server. ### Demonstrates - How to configure mutual-TLS (mTLS) authentication for Orleans ## [General Examples - Road to Orleans](https://github.com/PiotrJustyna/road-to-orleans/) A compiled list of examples varying in difficulty. ### Demonstrates - How to develop Orleans-based applications ## [Visual Basic Hello World](https://github.com/dotnet/samples/tree/main/orleans/VBHelloWorld/README.md) A *Hello, World!* application using Visual Basic. ### Demonstrates - How to develop Orleans-based applications using Visual Basic ## [F# Hello World](https://github.com/dotnet/samples/tree/main/orleans/FSharpHelloWorld/README.md) A *Hello, World!* application using F#. ### Demonstrates - How to develop Orleans-based applications using F# ## [F# Hello World written in F# end to end](https://github.com/PiotrJustyna/road-to-orleans/tree/main/5a#readme) In-memory clustering example where everything is written in F#: - Clustered Silos - Concurrent Clients - Grains - Interfaces ### Demonstrates - How to develop Orleans-based applications using F# end to end ## [F# Reminder](https://github.com/PiotrJustyna/road-to-orleans/tree/main/1b#readme) - How to use grain reminders in an F# grain ### Demonstrates - How to develop a reminder grain in F# ## [F# Grain Service](https://github.com/PiotrJustyna/road-to-orleans/tree/main/1c#readme) - How to use grain service from other grains in F# ### Demonstrates - How to develop grain service and grain service client in F# ## [Streaming: Pub/Sub Streams over Azure Event Hubs](https://learn.microsoft.com/samples/dotnet/samples/orleans-streaming-pubsub-with-azure-event-hub) An application using Orleans Streams with [Azure Event Hubs](https://azure.microsoft.com/services/event-hubs/) as the provider and implicit subscribers. ### Demonstrates - How to use [Orleans Streams](https://docs.microsoft.com/dotnet/orleans/streaming) - How to use the `[ImplicitStreamSubscription(namespace)]` attribute to implicitly subscribe a grain to the stream with the corresponding id - How to configure Orleans Streams for use with [Azure Event Hubs](https://azure.microsoft.com/services/event-hubs/) ## [Streaming: Custom Data Adapter](https://learn.microsoft.com/samples/dotnet/samples/orleans-streaming-custom-data-adapter) An application using Orleans Streams with a non-Orleans publisher pushing to a stream which a grain consumes via a *custom data adapter* which tells Orleans how to interpret stream messages. ### Demonstrates - How to use [Orleans Streams](https://docs.microsoft.com/dotnet/orleans/streaming) - How to use the `[ImplicitStreamSubscription(namespace)]` attribute to implicitly subscribe a grain to the stream with the corresponding id - How to configure Orleans Streams for use with [Azure Event Hubs](https://azure.microsoft.com/services/event-hubs/) - How to consume stream messages published by non-Orleans publishers by providing a custom `EventHubDataAdapter` implementation (a custom data adapter) ================================================ FILE: sign/NuGet.Config ================================================  ================================================ FILE: sign/Sign.proj ================================================ $([MSBuild]::NormalizeDirectory('$(NuGetPackageRoot)')) $([MSBuild]::NormalizeDirectory('$(NUGET_PACKAGES)')) $([MSBuild]::NormalizeDirectory('$(UserProfile)', '.nuget', 'packages')) $([MSBuild]::NormalizeDirectory('$(HOME)', '.nuget', 'packages')) $(MSBuildProjectDirectory)\..\bin\tmp\ $(MSBuildProjectDirectory)\..\bin\logs\ <_DryRun>false <_TestSign>false <_DesktopMSBuildRequired>false <_DesktopMSBuildRequired>true false <_DesktopMSBuildPath>$(_VSInstallDir)\MSBuild\Current\Bin\msbuild.exe <_DesktopMSBuildPath Condition="!Exists('$(_DesktopMSBuildPath)')">$(_VSInstallDir)\MSBuild\15.0\Bin\msbuild.exe ================================================ FILE: sign/Sign.props ================================================ true true true MicrosoftDotNet500 false false true true false Signing 16 128 ================================================ FILE: sign/packages.config ================================================ ================================================ FILE: spelling.dic ================================================ Parsable stackalloc Impl addr ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/AWSUtilsHostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Clustering.DynamoDB; using Orleans.Configuration; using Orleans.Messaging; using System; using Microsoft.Extensions.Options; namespace Orleans.Hosting { public static class AwsUtilsHostingExtensions { /// /// Configures the silo to use DynamoDB for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseDynamoDBClustering( this ISiloBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configures the silo to use DynamoDB for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseDynamoDBClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } /// /// Configures the client to use DynamoDB for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseDynamoDBClustering( this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configures the client to use DynamoDB for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseDynamoDBClustering( this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/DynamoDBClusteringProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("DynamoDB", "Clustering", "Silo", typeof(DynamoDBClusteringProviderBuilder))] [assembly: RegisterProvider("DynamoDB", "Clustering", "Client", typeof(DynamoDBClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class DynamoDBClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseDynamoDBClustering(options => { var accessKey = configurationSection[nameof(options.AccessKey)]; if (!string.IsNullOrEmpty(accessKey)) { options.AccessKey = accessKey; } var secretKey = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(secretKey)) { options.SecretKey = secretKey; } var region = configurationSection[nameof(options.Service)] ?? configurationSection["Region"]; if (!string.IsNullOrEmpty(region)) { options.Service = region; } var token = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(token)) { options.Token = token; } var profileName = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(profileName)) { options.ProfileName = profileName; } var tableName = configurationSection[nameof(options.TableName)]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } if (int.TryParse(configurationSection[nameof(options.ReadCapacityUnits)], out var rcu)) { options.ReadCapacityUnits = rcu; } if (int.TryParse(configurationSection[nameof(options.WriteCapacityUnits)], out var wcu)) { options.WriteCapacityUnits = wcu; } if (bool.TryParse(configurationSection[nameof(options.UseProvisionedThroughput)], out var upt)) { options.UseProvisionedThroughput = upt; } if (bool.TryParse(configurationSection[nameof(options.CreateIfNotExists)], out var cine)) { options.CreateIfNotExists = cine; } if (bool.TryParse(configurationSection[nameof(options.UpdateIfExists)], out var uie)) { options.UpdateIfExists = uie; } }); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseDynamoDBClustering(options => { var accessKey = configurationSection[nameof(options.AccessKey)]; if (!string.IsNullOrEmpty(accessKey)) { options.AccessKey = accessKey; } var secretKey = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(secretKey)) { options.SecretKey = secretKey; } var region = configurationSection[nameof(options.Service)] ?? configurationSection["Region"]; if (!string.IsNullOrEmpty(region)) { options.Service = region; } var token = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(token)) { options.Token = token; } var profileName = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(profileName)) { options.ProfileName = profileName; } var tableName = configurationSection[nameof(options.TableName)]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } if (int.TryParse(configurationSection[nameof(options.ReadCapacityUnits)], out var rcu)) { options.ReadCapacityUnits = rcu; } if (int.TryParse(configurationSection[nameof(options.WriteCapacityUnits)], out var wcu)) { options.WriteCapacityUnits = wcu; } if (bool.TryParse(configurationSection[nameof(options.UseProvisionedThroughput)], out var upt)) { options.UseProvisionedThroughput = upt; } if (bool.TryParse(configurationSection[nameof(options.CreateIfNotExists)], out var cine)) { options.CreateIfNotExists = cine; } if (bool.TryParse(configurationSection[nameof(options.UpdateIfExists)], out var uie)) { options.UpdateIfExists = uie; } }); } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Membership/DynamoDBGatewayListProvider.cs ================================================ using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Runtime; using Orleans.Runtime.MembershipService; using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; namespace Orleans.Clustering.DynamoDB { internal class DynamoDBGatewayListProvider : IGatewayListProvider { private DynamoDBStorage storage; private readonly string clusterId; private readonly string INSTANCE_STATUS_ACTIVE = ((int)SiloStatus.Active).ToString(); private readonly ILogger logger; private readonly DynamoDBGatewayOptions options; public DynamoDBGatewayListProvider( ILogger logger, IOptions options, IOptions clusterOptions, IOptions gatewayOptions) { this.logger = logger; this.options = options.Value; this.clusterId = clusterOptions.Value.ClusterId; this.MaxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; } public Task InitializeGatewayListProvider() { this.storage = new DynamoDBStorage( this.logger, this.options.Service, this.options.AccessKey, this.options.SecretKey, this.options.Token, this.options.ProfileName, this.options.ReadCapacityUnits, this.options.WriteCapacityUnits, this.options.UseProvisionedThroughput, this.options.CreateIfNotExists, this.options.UpdateIfExists); return this.storage.InitializeTable(this.options.TableName, new List { new KeySchemaElement { AttributeName = SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME, KeyType = KeyType.HASH }, new KeySchemaElement { AttributeName = SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME, KeyType = KeyType.RANGE } }, new List { new AttributeDefinition { AttributeName = SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME, AttributeType = ScalarAttributeType.S }, new AttributeDefinition { AttributeName = SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME, AttributeType = ScalarAttributeType.S } }); } public async Task> GetGateways() { var expressionValues = new Dictionary { { $":{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, { $":{SiloInstanceRecord.STATUS_PROPERTY_NAME}", new AttributeValue { N = INSTANCE_STATUS_ACTIVE } }, { $":{SiloInstanceRecord.PROXY_PORT_PROPERTY_NAME}", new AttributeValue { N = "0"} } }; var expression = $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME} = :{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME} " + $"AND {SiloInstanceRecord.STATUS_PROPERTY_NAME} = :{SiloInstanceRecord.STATUS_PROPERTY_NAME} " + $"AND {SiloInstanceRecord.PROXY_PORT_PROPERTY_NAME} > :{SiloInstanceRecord.PROXY_PORT_PROPERTY_NAME}"; var records = await storage.ScanAsync(this.options.TableName, expressionValues, expression, gateway => { return SiloAddress.New( IPAddress.Parse(gateway[SiloInstanceRecord.ADDRESS_PROPERTY_NAME].S), int.Parse(gateway[SiloInstanceRecord.PROXY_PORT_PROPERTY_NAME].N), int.Parse(gateway[SiloInstanceRecord.GENERATION_PROPERTY_NAME].N)).ToGatewayUri(); }); return records; } public TimeSpan MaxStaleness { get; } public bool IsUpdatable { get { return true; } } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Membership/DynamoDBGatewayListProviderHelper.cs ================================================ using System; using System.Linq; using Orleans.Configuration; namespace Orleans.Clustering.DynamoDB { /// public class DynamoDBGatewayListProviderHelper { private const string AccessKeyPropertyName = "AccessKey"; private const string SecretKeyPropertyName = "SecretKey"; private const string ServicePropertyName = "Service"; private const string ReadCapacityUnitsPropertyName = "ReadCapacityUnits"; private const string WriteCapacityUnitsPropertyName = "WriteCapacityUnits"; /// /// Parse data connection string to fill in fields in /// /// /// internal static void ParseDataConnectionString(string dataConnectionString, DynamoDBGatewayOptions options) { var parameters = dataConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var serviceConfig = Array.Find(parameters, p => p.Contains(ServicePropertyName)); if (!string.IsNullOrWhiteSpace(serviceConfig)) { var value = serviceConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.Service = value[1]; } var secretKeyConfig = Array.Find(parameters, p => p.Contains(SecretKeyPropertyName)); if (!string.IsNullOrWhiteSpace(secretKeyConfig)) { var value = secretKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.SecretKey = value[1]; } var accessKeyConfig = Array.Find(parameters, p => p.Contains(AccessKeyPropertyName)); if (!string.IsNullOrWhiteSpace(accessKeyConfig)) { var value = accessKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.AccessKey = value[1]; } var readCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(ReadCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(readCapacityUnitsConfig)) { var value = readCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.ReadCapacityUnits = int.Parse(value[1]); } var writeCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(WriteCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(writeCapacityUnitsConfig)) { var value = writeCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.WriteCapacityUnits = int.Parse(value[1]); } } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Membership/DynamoDBMembershipHelper.cs ================================================ using Orleans.Configuration; using System; using System.Linq; namespace Orleans.Clustering.DynamoDB { /// public class DynamoDBMembershipHelper { private const string AccessKeyPropertyName = "AccessKey"; private const string SecretKeyPropertyName = "SecretKey"; private const string ServicePropertyName = "Service"; private const string TableNamePropertyName = "TableName"; private const string ReadCapacityUnitsPropertyName = "ReadCapacityUnits"; private const string WriteCapacityUnitsPropertyName = "WriteCapacityUnits"; /// /// Parse data connection string to fill in fields in /// /// /// public static void ParseDataConnectionString(string dataConnectionString, DynamoDBClusteringOptions options) { var parameters = dataConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var serviceConfig = Array.Find(parameters, p => p.Contains(ServicePropertyName)); if (!string.IsNullOrWhiteSpace(serviceConfig)) { var value = serviceConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.Service = value[1]; } var tableNameConfig = Array.Find(parameters, p => p.Contains(TableNamePropertyName)); if (!string.IsNullOrWhiteSpace(tableNameConfig)) { var value = tableNameConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.TableName = value[1]; } var secretKeyConfig = Array.Find(parameters, p => p.Contains(SecretKeyPropertyName)); if (!string.IsNullOrWhiteSpace(secretKeyConfig)) { var value = secretKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.SecretKey = value[1]; } var accessKeyConfig = Array.Find(parameters, p => p.Contains(AccessKeyPropertyName)); if (!string.IsNullOrWhiteSpace(accessKeyConfig)) { var value = accessKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.AccessKey = value[1]; } var readCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(ReadCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(readCapacityUnitsConfig)) { var value = readCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.ReadCapacityUnits = int.Parse(value[1]); } var writeCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(WriteCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(writeCapacityUnitsConfig)) { var value = writeCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.WriteCapacityUnits = int.Parse(value[1]); } } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Membership/DynamoDBMembershipTable.cs ================================================ using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Runtime.MembershipService; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; namespace Orleans.Clustering.DynamoDB { internal partial class DynamoDBMembershipTable : IMembershipTable { private static readonly TableVersion NotFoundTableVersion = new TableVersion(0, "0"); private const string CURRENT_ETAG_ALIAS = ":currentETag"; private const int MAX_BATCH_SIZE = 25; private readonly ILogger logger; private DynamoDBStorage storage; private readonly DynamoDBClusteringOptions options; private readonly string clusterId; public DynamoDBMembershipTable( ILoggerFactory loggerFactory, IOptions clusteringOptions, IOptions clusterOptions) { logger = loggerFactory.CreateLogger(); this.options = clusteringOptions.Value; this.clusterId = clusterOptions.Value.ClusterId; } public async Task InitializeMembershipTable(bool tryInitTableVersion) { this.storage = new DynamoDBStorage( this.logger, this.options.Service, this.options.AccessKey, this.options.SecretKey, this.options.Token, this.options.ProfileName, this.options.ReadCapacityUnits, this.options.WriteCapacityUnits, this.options.UseProvisionedThroughput, this.options.CreateIfNotExists, this.options.UpdateIfExists); LogInformationInitializingMembershipTable(); await storage.InitializeTable(this.options.TableName, new List { new KeySchemaElement { AttributeName = SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME, KeyType = KeyType.HASH }, new KeySchemaElement { AttributeName = SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME, KeyType = KeyType.RANGE } }, new List { new AttributeDefinition { AttributeName = SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME, AttributeType = ScalarAttributeType.S }, new AttributeDefinition { AttributeName = SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME, AttributeType = ScalarAttributeType.S } }); // even if I am not the one who created the table, // try to insert an initial table version if it is not already there, // so we always have a first table version row, before this silo starts working. if (tryInitTableVersion) { // ignore return value, since we don't care if I inserted it or not, as long as it is in there. bool created = await TryCreateTableVersionEntryAsync(); if (created) LogInformationCreatedNewTableVersionRow(); } } private async Task TryCreateTableVersionEntryAsync() { var keys = new Dictionary { { $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, { $"{SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME}", new AttributeValue(SiloInstanceRecord.TABLE_VERSION_ROW) } }; var versionRow = await storage.ReadSingleEntryAsync(this.options.TableName, keys, fields => new SiloInstanceRecord(fields)); if (versionRow != null) { return false; } if (!TryCreateTableVersionRecord(0, null, out var entry)) { return false; } var notExistConditionExpression = $"attribute_not_exists({SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}) AND attribute_not_exists({SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME})"; try { await storage.PutEntryAsync(this.options.TableName, entry.GetFields(true), notExistConditionExpression); } catch (ConditionalCheckFailedException) { return false; } return true; } private bool TryCreateTableVersionRecord(int version, string etag, out SiloInstanceRecord entry) { int etagInt; if (etag is null) { etagInt = 0; } else { if (!int.TryParse(etag, out etagInt)) { entry = default; return false; } } entry = new SiloInstanceRecord { DeploymentId = clusterId, SiloIdentity = SiloInstanceRecord.TABLE_VERSION_ROW, MembershipVersion = version, ETag = etagInt }; return true; } public async Task DeleteMembershipTableEntries(string clusterId) { try { var keys = new Dictionary { { $":{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(clusterId) } }; var records = await storage.QueryAsync(this.options.TableName, keys, $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME} = :{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", item => new SiloInstanceRecord(item)); var toDelete = new List>(); foreach (var record in records.results) { toDelete.Add(record.GetKeys()); } List tasks = new List(); foreach (var batch in toDelete.BatchIEnumerable(MAX_BATCH_SIZE)) { tasks.Add(storage.DeleteEntriesAsync(this.options.TableName, batch)); } await Task.WhenAll(tasks); } catch (Exception exc) { LogErrorUnableToDeleteMembershipRecords(exc, this.options.TableName, clusterId); throw; } } public async Task ReadRow(SiloAddress siloAddress) { try { var siloEntryKeys = new Dictionary { { $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, { $"{SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME}", new AttributeValue(SiloInstanceRecord.ConstructSiloIdentity(siloAddress)) } }; var versionEntryKeys = new Dictionary { { $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, { $"{SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME}", new AttributeValue(SiloInstanceRecord.TABLE_VERSION_ROW) } }; var entries = await storage.GetEntriesTxAsync(this.options.TableName, new[] {siloEntryKeys, versionEntryKeys}, fields => new SiloInstanceRecord(fields)); MembershipTableData data = Convert(entries.ToList()); LogTraceReadMyEntry(siloAddress, data); return data; } catch (Exception exc) { LogWarningIntermediateErrorReadingSiloEntry(exc, siloAddress, this.options.TableName); throw; } } public async Task ReadAll() { try { //first read just the version row so that we can check for version consistency var versionEntryKeys = new Dictionary { { $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, { $"{SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME}", new AttributeValue(SiloInstanceRecord.TABLE_VERSION_ROW) } }; var versionRow = await this.storage.ReadSingleEntryAsync(this.options.TableName, versionEntryKeys, fields => new SiloInstanceRecord(fields)); if (versionRow == null) { throw new KeyNotFoundException("No version row found for membership table"); } var keys = new Dictionary { { $":{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) } }; var records = await this.storage.QueryAllAsync(this.options.TableName, keys, $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME} = :{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", item => new SiloInstanceRecord(item)); if (records.Exists(record => record.MembershipVersion > versionRow.MembershipVersion)) { LogWarningFoundInconsistencyReadingAllSiloEntries(); //not expecting this to hit often, but if it does, should put in a limit return await this.ReadAll(); } MembershipTableData data = Convert(records); LogTraceReadAllTable(data); return data; } catch (Exception exc) { LogWarningIntermediateErrorReadingAllSiloEntries(exc, options.TableName); throw; } } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { try { LogDebugInsertRow(entry); var tableEntry = Convert(entry, tableVersion); if (!TryCreateTableVersionRecord(tableVersion.Version, tableVersion.VersionEtag, out var versionEntry)) { LogWarningInsertFailedInvalidETag(entry, tableVersion.VersionEtag); return false; } versionEntry.ETag++; bool result; try { var notExistConditionExpression = $"attribute_not_exists({SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}) AND attribute_not_exists({SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME})"; var tableEntryInsert = new Put { Item = tableEntry.GetFields(true), ConditionExpression = notExistConditionExpression, TableName = this.options.TableName }; var conditionalValues = new Dictionary { { CURRENT_ETAG_ALIAS, new AttributeValue { N = tableVersion.VersionEtag } } }; var etagConditionalExpression = $"{SiloInstanceRecord.ETAG_PROPERTY_NAME} = {CURRENT_ETAG_ALIAS}"; var versionEntryUpdate = new Update { TableName = this.options.TableName, Key = versionEntry.GetKeys(), ConditionExpression = etagConditionalExpression }; (versionEntryUpdate.UpdateExpression, versionEntryUpdate.ExpressionAttributeValues) = this.storage.ConvertUpdate(versionEntry.GetFields(), conditionalValues); await this.storage.WriteTxAsync(new[] {tableEntryInsert}, new[] {versionEntryUpdate}); result = true; } catch (TransactionCanceledException canceledException) { if (canceledException.Message.Contains("ConditionalCheckFailed")) //not a good way to check for this currently { result = false; LogWarningInsertFailedDueToContention(entry); } else { throw; } } return result; } catch (Exception exc) { LogWarningIntermediateErrorInsertingEntry(exc, entry, this.options.TableName); throw; } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { try { LogDebugUpdateRow(entry, etag); var siloEntry = Convert(entry, tableVersion); if (!int.TryParse(etag, out var currentEtag)) { LogWarningUpdateFailedInvalidETag(entry, etag); return false; } siloEntry.ETag = currentEtag + 1; if (!TryCreateTableVersionRecord(tableVersion.Version, tableVersion.VersionEtag, out var versionEntry)) { LogWarningUpdateFailedInvalidETag(entry, tableVersion.VersionEtag); return false; } versionEntry.ETag++; bool result; try { var etagConditionalExpression = $"{SiloInstanceRecord.ETAG_PROPERTY_NAME} = {CURRENT_ETAG_ALIAS}"; var siloConditionalValues = new Dictionary { { CURRENT_ETAG_ALIAS, new AttributeValue { N = etag } } }; var siloEntryUpdate = new Update { TableName = this.options.TableName, Key = siloEntry.GetKeys(), ConditionExpression = etagConditionalExpression }; (siloEntryUpdate.UpdateExpression, siloEntryUpdate.ExpressionAttributeValues) = this.storage.ConvertUpdate(siloEntry.GetFields(), siloConditionalValues); var versionConditionalValues = new Dictionary { { CURRENT_ETAG_ALIAS, new AttributeValue { N = tableVersion.VersionEtag } } }; var versionEntryUpdate = new Update { TableName = this.options.TableName, Key = versionEntry.GetKeys(), ConditionExpression = etagConditionalExpression }; (versionEntryUpdate.UpdateExpression, versionEntryUpdate.ExpressionAttributeValues) = this.storage.ConvertUpdate(versionEntry.GetFields(), versionConditionalValues); await this.storage.WriteTxAsync(updates: new[] {siloEntryUpdate, versionEntryUpdate}); result = true; } catch (TransactionCanceledException canceledException) { if (canceledException.Message.Contains("ConditionalCheckFailed")) //not a good way to check for this currently { result = false; LogWarningUpdateFailedDueToContention(canceledException, entry, etag); } else { throw; } } return result; } catch (Exception exc) { LogWarningIntermediateErrorUpdatingEntry(exc, entry, this.options.TableName); throw; } } public async Task UpdateIAmAlive(MembershipEntry entry) { try { LogDebugMergeEntry(entry); var siloEntry = ConvertPartial(entry); var fields = new Dictionary { { SiloInstanceRecord.I_AM_ALIVE_TIME_PROPERTY_NAME, new AttributeValue(siloEntry.IAmAliveTime) } }; var expression = $"attribute_exists({SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}) AND attribute_exists({SiloInstanceRecord.SILO_IDENTITY_PROPERTY_NAME})"; await this.storage.UpsertEntryAsync(this.options.TableName, siloEntry.GetKeys(),fields, expression); } catch (Exception exc) { LogWarningIntermediateErrorUpdatingIAmAlive(exc, entry, this.options.TableName); throw; } } private MembershipTableData Convert(List entries) { try { var memEntries = new List>(); var tableVersion = NotFoundTableVersion; foreach (var tableEntry in entries) { if (tableEntry.SiloIdentity == SiloInstanceRecord.TABLE_VERSION_ROW) { tableVersion = new TableVersion(tableEntry.MembershipVersion, tableEntry.ETag.ToString(CultureInfo.InvariantCulture)); } else { try { MembershipEntry membershipEntry = Parse(tableEntry); memEntries.Add(new Tuple(membershipEntry, tableEntry.ETag.ToString(CultureInfo.InvariantCulture))); } catch (Exception exc) { LogErrorIntermediateErrorParsingSiloInstanceTableEntry(exc, tableEntry); } } } var data = new MembershipTableData(memEntries, tableVersion); return data; } catch (Exception exc) { LogErrorIntermediateErrorParsingSiloInstanceTableEntries(exc, entries); throw; } } private static MembershipEntry Parse(SiloInstanceRecord tableEntry) { var parse = new MembershipEntry { HostName = tableEntry.HostName, Status = (SiloStatus)tableEntry.Status }; parse.ProxyPort = tableEntry.ProxyPort; parse.SiloAddress = SiloAddress.New(IPAddress.Parse(tableEntry.Address), tableEntry.Port, tableEntry.Generation); if (!string.IsNullOrEmpty(tableEntry.SiloName)) { parse.SiloName = tableEntry.SiloName; } parse.StartTime = !string.IsNullOrEmpty(tableEntry.StartTime) ? LogFormatter.ParseDate(tableEntry.StartTime) : default; parse.IAmAliveTime = !string.IsNullOrEmpty(tableEntry.IAmAliveTime) ? LogFormatter.ParseDate(tableEntry.IAmAliveTime) : default; var suspectingSilos = new List(); var suspectingTimes = new List(); if (!string.IsNullOrEmpty(tableEntry.SuspectingSilos)) { string[] silos = tableEntry.SuspectingSilos.Split('|'); foreach (string silo in silos) { suspectingSilos.Add(SiloAddress.FromParsableString(silo)); } } if (!string.IsNullOrEmpty(tableEntry.SuspectingTimes)) { string[] times = tableEntry.SuspectingTimes.Split('|'); foreach (string time in times) suspectingTimes.Add(LogFormatter.ParseDate(time)); } if (suspectingSilos.Count != suspectingTimes.Count) throw new OrleansException($"SuspectingSilos.Length of {suspectingSilos.Count} as read from Azure table is not equal to SuspectingTimes.Length of {suspectingTimes.Count}"); for (int i = 0; i < suspectingSilos.Count; i++) parse.AddSuspector(suspectingSilos[i], suspectingTimes[i]); return parse; } private SiloInstanceRecord Convert(MembershipEntry memEntry, TableVersion tableVersion) { var tableEntry = new SiloInstanceRecord { DeploymentId = this.clusterId, Address = memEntry.SiloAddress.Endpoint.Address.ToString(), Port = memEntry.SiloAddress.Endpoint.Port, Generation = memEntry.SiloAddress.Generation, HostName = memEntry.HostName, Status = (int)memEntry.Status, ProxyPort = memEntry.ProxyPort, SiloName = memEntry.SiloName, StartTime = LogFormatter.PrintDate(memEntry.StartTime), IAmAliveTime = LogFormatter.PrintDate(memEntry.IAmAliveTime), SiloIdentity = SiloInstanceRecord.ConstructSiloIdentity(memEntry.SiloAddress), MembershipVersion = tableVersion.Version }; if (memEntry.SuspectTimes != null) { var siloList = new StringBuilder(); var timeList = new StringBuilder(); bool first = true; foreach (var tuple in memEntry.SuspectTimes) { if (!first) { siloList.Append('|'); timeList.Append('|'); } siloList.Append(tuple.Item1.ToParsableString()); timeList.Append(LogFormatter.PrintDate(tuple.Item2)); first = false; } tableEntry.SuspectingSilos = siloList.ToString(); tableEntry.SuspectingTimes = timeList.ToString(); } else { tableEntry.SuspectingSilos = string.Empty; tableEntry.SuspectingTimes = string.Empty; } return tableEntry; } private SiloInstanceRecord ConvertPartial(MembershipEntry memEntry) { return new SiloInstanceRecord { DeploymentId = this.clusterId, IAmAliveTime = LogFormatter.PrintDate(memEntry.IAmAliveTime), SiloIdentity = SiloInstanceRecord.ConstructSiloIdentity(memEntry.SiloAddress) }; } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { try { var keys = new Dictionary { { $":{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}", new AttributeValue(this.clusterId) }, }; var filter = $"{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME} = :{SiloInstanceRecord.DEPLOYMENT_ID_PROPERTY_NAME}"; var records = await this.storage.QueryAllAsync(this.options.TableName, keys, filter, item => new SiloInstanceRecord(item)); var defunctRecordKeys = records.Where(r => SiloIsDefunct(r, beforeDate)).Select(r => r.GetKeys()); var tasks = new List(); foreach (var batch in defunctRecordKeys.BatchIEnumerable(MAX_BATCH_SIZE)) { tasks.Add(this.storage.DeleteEntriesAsync(this.options.TableName, batch)); } await Task.WhenAll(tasks); } catch (Exception exc) { LogErrorUnableToCleanUpDefunctMembershipRecords(exc, this.options.TableName, this.clusterId); throw; } } private static bool SiloIsDefunct(SiloInstanceRecord silo, DateTimeOffset beforeDate) { return DateTimeOffset.TryParse(silo.IAmAliveTime, out var iAmAliveTime) && iAmAliveTime < beforeDate && silo.Status != (int)SiloStatus.Active; } [LoggerMessage( Level = LogLevel.Information, Message = "Initializing AWS DynamoDB Membership Table" )] private partial void LogInformationInitializingMembershipTable(); [LoggerMessage( Level = LogLevel.Information, Message = "Created new table version row." )] private partial void LogInformationCreatedNewTableVersionRow(); [LoggerMessage( Level = LogLevel.Error, Message = "Unable to delete membership records on table {TableName} for ClusterId {ClusterId}" )] private partial void LogErrorUnableToDeleteMembershipRecords(Exception exception, string tableName, string clusterId); [LoggerMessage( Level = LogLevel.Trace, Message = "Read my entry {SiloAddress} Table: {TableData}" )] private partial void LogTraceReadMyEntry(SiloAddress siloAddress, MembershipTableData tableData); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error reading silo entry for key {SiloAddress} from the table {TableName}" )] private partial void LogWarningIntermediateErrorReadingSiloEntry(Exception exception, SiloAddress siloAddress, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "Found an inconsistency while reading all silo entries" )] private partial void LogWarningFoundInconsistencyReadingAllSiloEntries(); [LoggerMessage( Level = LogLevel.Trace, Message = "ReadAll Table {Table}" )] private partial void LogTraceReadAllTable(MembershipTableData table); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error reading all silo entries {TableName}." )] private partial void LogWarningIntermediateErrorReadingAllSiloEntries(Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "InsertRow entry = {Entry}" )] private partial void LogDebugInsertRow(MembershipEntry entry); [LoggerMessage( Level = LogLevel.Warning, Message = "Insert failed. Invalid ETag value. Will retry. Entry {Entry}, eTag {ETag}" )] private partial void LogWarningInsertFailedInvalidETag(MembershipEntry entry, string etag); [LoggerMessage( Level = LogLevel.Warning, Message = "Insert failed due to contention on the table. Will retry. Entry {Entry}" )] private partial void LogWarningInsertFailedDueToContention(MembershipEntry entry); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error inserting entry {Entry} to the table {TableName}." )] private partial void LogWarningIntermediateErrorInsertingEntry(Exception exception, MembershipEntry entry, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "UpdateRow entry = {Entry}, etag = {Etag}" )] private partial void LogDebugUpdateRow(MembershipEntry entry, string etag); [LoggerMessage( Level = LogLevel.Warning, Message = "Update failed. Invalid ETag value. Will retry. Entry {Entry}, eTag {ETag}" )] private partial void LogWarningUpdateFailedInvalidETag(MembershipEntry entry, string etag); [LoggerMessage( Level = LogLevel.Warning, Message = "Update failed due to contention on the table. Will retry. Entry {Entry}, eTag {ETag}" )] private partial void LogWarningUpdateFailedDueToContention(Exception exception, MembershipEntry entry, string etag); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error updating entry {Entry} to the table {TableName}." )] private partial void LogWarningIntermediateErrorUpdatingEntry(Exception exception, MembershipEntry entry, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "Merge entry = {Entry}" )] private partial void LogDebugMergeEntry(MembershipEntry entry); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error updating IAmAlive field for entry {Entry} to the table {TableName}." )] private partial void LogWarningIntermediateErrorUpdatingIAmAlive(Exception exception, MembershipEntry entry, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Intermediate error parsing SiloInstanceTableEntry to MembershipTableData: {TableEntry}. Ignoring this entry." )] private partial void LogErrorIntermediateErrorParsingSiloInstanceTableEntry(Exception exception, SiloInstanceRecord tableEntry); [LoggerMessage( Level = LogLevel.Error, Message = "Intermediate error parsing SiloInstanceTableEntry to MembershipTableData: {Entries}." )] private partial void LogErrorIntermediateErrorParsingSiloInstanceTableEntries(Exception exception, IEnumerable entries); [LoggerMessage( Level = LogLevel.Error, Message = "Unable to clean up defunct membership records on table {TableName} for ClusterId {ClusterId}" )] private partial void LogErrorUnableToCleanUpDefunctMembershipRecords(Exception exception, string tableName, string clusterId); } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Membership/SiloInstanceRecord.cs ================================================ using Amazon.DynamoDBv2.Model; using System; using System.Collections.Generic; using System.Net; using System.Text; namespace Orleans.Runtime.MembershipService { internal class SiloInstanceRecord { public const string DEPLOYMENT_ID_PROPERTY_NAME = "DeploymentId"; public const string SILO_IDENTITY_PROPERTY_NAME = "SiloIdentity"; public const string ETAG_PROPERTY_NAME = "ETag"; public const string ADDRESS_PROPERTY_NAME = "Address"; public const string PORT_PROPERTY_NAME = "Port"; public const string GENERATION_PROPERTY_NAME = "Generation"; public const string HOSTNAME_PROPERTY_NAME = "HostName"; public const string STATUS_PROPERTY_NAME = "SiloStatus"; public const string PROXY_PORT_PROPERTY_NAME = "ProxyPort"; public const string SILO_NAME_PROPERTY_NAME = "SiloName"; public const string INSTANCE_NAME_PROPERTY_NAME = "InstanceName"; public const string SUSPECTING_SILOS_PROPERTY_NAME = "SuspectingSilos"; public const string SUSPECTING_TIMES_PROPERTY_NAME = "SuspectingTimes"; public const string START_TIME_PROPERTY_NAME = "StartTime"; public const string I_AM_ALIVE_TIME_PROPERTY_NAME = "IAmAliveTime"; internal const char Seperator = '-'; internal const string TABLE_VERSION_ROW = "VersionRow"; // Range key for version row. public const string MEMBERSHIP_VERSION_PROPERTY_NAME = "MembershipVersion"; public string DeploymentId { get; set; } public string SiloIdentity { get; set; } public string Address { get; set; } public int Port { get; set; } public int Generation { get; set; } public string HostName { get; set; } public int Status { get; set; } public int ProxyPort { get; set; } public string SiloName { get; set; } public string SuspectingSilos { get; set; } public string SuspectingTimes { get; set; } public string StartTime { get; set; } public string IAmAliveTime { get; set; } public int ETag { get; set; } public int MembershipVersion { get; set; } public SiloInstanceRecord() { } public SiloInstanceRecord(Dictionary fields) { if (fields.TryGetValue(DEPLOYMENT_ID_PROPERTY_NAME, out var deploymentId)) DeploymentId = deploymentId.S; if (fields.TryGetValue(SILO_IDENTITY_PROPERTY_NAME, out var siloIdentity)) SiloIdentity = siloIdentity.S; if (fields.TryGetValue(ADDRESS_PROPERTY_NAME, out var address)) Address = address.S; if (fields.TryGetValue(PORT_PROPERTY_NAME, out var sPort) && int.TryParse(sPort.N, out var port)) Port = port; if (fields.TryGetValue(GENERATION_PROPERTY_NAME, out var sGeneration) && int.TryParse(sGeneration.N, out var generation)) Generation = generation; if (fields.TryGetValue(HOSTNAME_PROPERTY_NAME, out var hostName)) HostName = hostName.S; if (fields.TryGetValue(STATUS_PROPERTY_NAME, out var sStatus) && int.TryParse(sStatus.N, out var status)) Status = status; if (fields.TryGetValue(PROXY_PORT_PROPERTY_NAME, out var sProxyPort) && int.TryParse(sProxyPort.N, out var proxyPort)) ProxyPort = proxyPort; if (fields.TryGetValue(SILO_NAME_PROPERTY_NAME, out var siloName)) SiloName = siloName.S; if (fields.TryGetValue(SUSPECTING_SILOS_PROPERTY_NAME, out var suspectingSilos)) SuspectingSilos = suspectingSilos.S; if (fields.TryGetValue(SUSPECTING_TIMES_PROPERTY_NAME, out var suspectingTimes)) SuspectingTimes = suspectingTimes.S; if (fields.TryGetValue(START_TIME_PROPERTY_NAME, out var startTime)) StartTime = startTime.S; if (fields.TryGetValue(I_AM_ALIVE_TIME_PROPERTY_NAME, out var aliveTime)) IAmAliveTime = aliveTime.S; if (fields.TryGetValue(ETAG_PROPERTY_NAME, out var sETag) && int.TryParse(sETag.N, out var etag)) ETag = etag; if (fields.TryGetValue(MEMBERSHIP_VERSION_PROPERTY_NAME, out var value) && int.TryParse(value.N, out var version)) MembershipVersion = version; } internal static SiloAddress UnpackRowKey(string rowKey) { try { int idx1 = rowKey.IndexOf(Seperator); int idx2 = rowKey.LastIndexOf(Seperator); ReadOnlySpan rowKeySpan = rowKey.AsSpan(); ReadOnlySpan addressStr = rowKeySpan[..idx1]; ReadOnlySpan portStr = rowKeySpan.Slice(idx1 + 1, idx2 - idx1 - 1); ReadOnlySpan genStr = rowKeySpan[(idx2 + 1)..]; IPAddress address = IPAddress.Parse(addressStr); int port = int.Parse(portStr); int generation = int.Parse(genStr); return SiloAddress.New(address, port, generation); } catch (Exception exc) { throw new AggregateException("Error from UnpackRowKey", exc); } } public override string ToString() { var sb = new StringBuilder(); sb.Append("OrleansSilo ["); sb.Append(" Deployment=").Append(DeploymentId); sb.Append(" LocalEndpoint=").Append(Address); sb.Append(" LocalPort=").Append(Port); sb.Append(" Generation=").Append(Generation); sb.Append(" Host=").Append(HostName); sb.Append(" Status=").Append(Status); sb.Append(" ProxyPort=").Append(ProxyPort); sb.Append(" SiloName=").Append(SiloName); if (!string.IsNullOrEmpty(SuspectingSilos)) sb.Append(" SuspectingSilos=").Append(SuspectingSilos); if (!string.IsNullOrEmpty(SuspectingTimes)) sb.Append(" SuspectingTimes=").Append(SuspectingTimes); sb.Append(" StartTime=").Append(StartTime); sb.Append(" IAmAliveTime=").Append(IAmAliveTime); sb.Append("]"); return sb.ToString(); } public static string ConstructSiloIdentity(SiloAddress silo) { return string.Format("{0}-{1}-{2}", silo.Endpoint.Address, silo.Endpoint.Port, silo.Generation); } public Dictionary GetKeys() { var keys = new Dictionary(); keys.Add(DEPLOYMENT_ID_PROPERTY_NAME, new AttributeValue(DeploymentId)); keys.Add(SILO_IDENTITY_PROPERTY_NAME, new AttributeValue(SiloIdentity)); return keys; } public Dictionary GetFields(bool includeKeys = false) { var fields = new Dictionary(); if (includeKeys) { fields.Add(DEPLOYMENT_ID_PROPERTY_NAME, new AttributeValue(DeploymentId)); fields.Add(SILO_IDENTITY_PROPERTY_NAME, new AttributeValue(SiloIdentity)); } if (!string.IsNullOrWhiteSpace(Address)) fields.Add(ADDRESS_PROPERTY_NAME, new AttributeValue(Address)); fields.Add(PORT_PROPERTY_NAME, new AttributeValue { N = Port.ToString() }); fields.Add(GENERATION_PROPERTY_NAME, new AttributeValue { N = Generation.ToString() }); if (!string.IsNullOrWhiteSpace(HostName)) fields.Add(HOSTNAME_PROPERTY_NAME, new AttributeValue(HostName)); fields.Add(STATUS_PROPERTY_NAME, new AttributeValue { N = Status.ToString() }); fields.Add(PROXY_PORT_PROPERTY_NAME, new AttributeValue { N = ProxyPort.ToString() }); if (!string.IsNullOrWhiteSpace(SiloName)) fields.Add(SILO_NAME_PROPERTY_NAME, new AttributeValue(SiloName)); if (!string.IsNullOrWhiteSpace(SuspectingSilos)) fields.Add(SUSPECTING_SILOS_PROPERTY_NAME, new AttributeValue(SuspectingSilos)); if (!string.IsNullOrWhiteSpace(SuspectingTimes)) fields.Add(SUSPECTING_TIMES_PROPERTY_NAME, new AttributeValue(SuspectingTimes)); if (!string.IsNullOrWhiteSpace(StartTime)) fields.Add(START_TIME_PROPERTY_NAME, new AttributeValue(StartTime)); if (!string.IsNullOrWhiteSpace(IAmAliveTime)) fields.Add(I_AM_ALIVE_TIME_PROPERTY_NAME, new AttributeValue(IAmAliveTime)); fields.Add(MEMBERSHIP_VERSION_PROPERTY_NAME, new AttributeValue { N = MembershipVersion.ToString() }); fields.Add(ETAG_PROPERTY_NAME, new AttributeValue { N = ETag.ToString() }); return fields; } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Options/DynamoDBClusteringOptions.cs ================================================ using Orleans.Clustering.DynamoDB; namespace Orleans.Configuration { public class DynamoDBClusteringOptions : DynamoDBClientOptions { /// /// Read capacity unit for DynamoDB storage /// public int ReadCapacityUnits { get; set; } = DynamoDBStorage.DefaultReadCapacityUnits; /// /// Write capacity unit for DynamoDB storage /// public int WriteCapacityUnits { get; set; } = DynamoDBStorage.DefaultWriteCapacityUnits; /// /// Use Provisioned Throughput for tables /// public bool UseProvisionedThroughput { get; set; } = true; /// /// Create the table if it doesn't exist /// public bool CreateIfNotExists { get; set; } = true; /// /// Update the table if it exists /// public bool UpdateIfExists { get; set; } = true; /// /// DynamoDB table name. /// Defaults to 'OrleansSilos'. /// public string TableName { get; set; } = "OrleansSilos"; } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Options/DynamoDBClusteringSiloOptions.cs ================================================ namespace Orleans.Configuration { public class DynamoDBClusteringSiloOptions { /// /// Connection string for DynamoDB Storage /// [RedactConnectionString] public string ConnectionString { get; set; } } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Options/DynamoDBGatewayOptions.cs ================================================ using Orleans.Clustering.DynamoDB; namespace Orleans.Configuration { public class DynamoDBGatewayOptions : DynamoDBClientOptions { /// /// Read capacity unit for DynamoDB storage /// public int ReadCapacityUnits { get; set; } = DynamoDBStorage.DefaultReadCapacityUnits; /// /// Write capacity unit for DynamoDB storage /// public int WriteCapacityUnits { get; set; } = DynamoDBStorage.DefaultWriteCapacityUnits; /// /// Use Provisioned Throughput for tables /// public bool UseProvisionedThroughput { get; set; } = true; /// /// Create the table if it doesn't exist /// public bool CreateIfNotExists { get; set; } = true; /// /// Update the table if it exists /// public bool UpdateIfExists { get; set; } = true; /// /// DynamoDB table name. /// Defaults to 'OrleansSilos'. /// public string TableName { get; set; } = "OrleansSilos"; } } ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/Orleans.Clustering.DynamoDB.csproj ================================================ README.md Microsoft.Orleans.Clustering.DynamoDB Microsoft Orleans AWS DynamoDB Clustering Provider Microsoft Orleans clustering provider backed by AWS DynamoDB $(PackageTags) AWS DynamoDB $(DefaultTargetFrameworks) true Orleans.Clustering.DynamoDB Orleans.Clustering.DynamoDB $(DefineConstants);CLUSTERING_DYNAMODB ================================================ FILE: src/AWS/Orleans.Clustering.DynamoDB/README.md ================================================ # Microsoft Orleans Clustering for DynamoDB ## Introduction Microsoft Orleans Clustering for DynamoDB provides cluster membership functionality for Microsoft Orleans using Amazon's DynamoDB. This allows Orleans silos to coordinate and form a cluster using DynamoDB as the backing store. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Clustering.DynamoDB ``` ## Example - Configuring DynamoDB Membership ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace ExampleGrains; // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } // Implement the grain interface public class HelloGrain : Grain, IHelloGrain { public Task SayHello(string greeting) { return Task.FromResult($"Hello, {greeting}!"); } } var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder // Configure DynamoDB clustering .UseDynamoDBClustering(options => { options.AccessKey = "YOUR_AWS_ACCESS_KEY"; options.SecretKey = "YOUR_AWS_SECRET_KEY"; options.Region = "us-east-1"; options.TableName = "OrleansClusteringTable"; options.CreateIfNotExists = true; }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("DynamoDB"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Configuration Guide](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/) - [Orleans Clustering](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) - [AWS SDK for .NET Documentation](https://docs.aws.amazon.com/sdk-for-net/index.html) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Hosting/DynamoDBGrainStorageProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Storage; [assembly: RegisterProvider("DynamoDB", "GrainStorage", "Silo", typeof(DynamoDBGrainStorageProviderBuilder))] namespace Orleans.Hosting; internal sealed class DynamoDBGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddDynamoDBGrainStorage( name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var accessKey = configurationSection[nameof(options.AccessKey)]; if (!string.IsNullOrEmpty(accessKey)) { options.AccessKey = accessKey; } var secretKey = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(secretKey)) { options.SecretKey = secretKey; } var region = configurationSection[nameof(options.Service)] ?? configurationSection["Region"]; if (!string.IsNullOrEmpty(region)) { options.Service = region; } var token = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(token)) { options.Token = token; } var profileName = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(profileName)) { options.ProfileName = profileName; } var serviceId = configurationSection[nameof(options.ServiceId)]; if (!string.IsNullOrEmpty(serviceId)) { options.ServiceId = serviceId; } var tableName = configurationSection[nameof(options.TableName)]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } if (int.TryParse(configurationSection[nameof(options.ReadCapacityUnits)], out var rcu)) { options.ReadCapacityUnits = rcu; } if (int.TryParse(configurationSection[nameof(options.WriteCapacityUnits)], out var wcu)) { options.WriteCapacityUnits = wcu; } if (bool.TryParse(configurationSection[nameof(options.UseProvisionedThroughput)], out var upt)) { options.UseProvisionedThroughput = upt; } if (bool.TryParse(configurationSection[nameof(options.CreateIfNotExists)], out var cine)) { options.CreateIfNotExists = cine; } if (bool.TryParse(configurationSection[nameof(options.UpdateIfExists)], out var uie)) { options.UpdateIfExists = uie; } if (bool.TryParse(configurationSection[nameof(options.DeleteStateOnClear)], out var dsoc)) { options.DeleteStateOnClear = dsoc; } if (TimeSpan.TryParse(configurationSection[nameof(options.TimeToLive)], out var ttl)) { options.TimeToLive = ttl; } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } })); } } ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Hosting/DynamoDBGrainStorageServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Storage; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; using Orleans.Runtime.Hosting; namespace Orleans.Hosting { /// /// extensions. /// public static class DynamoDBGrainStorageServiceCollectionExtensions { /// /// Configure silo to use AWS DynamoDB storage as the default grain storage. /// public static IServiceCollection AddDynamoDBGrainStorageAsDefault(this IServiceCollection services, Action configureOptions) { return services.AddDynamoDBGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use AWS DynamoDB storage for grain storage. /// public static IServiceCollection AddDynamoDBGrainStorage(this IServiceCollection services, string name, Action configureOptions) { return services.AddDynamoDBGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use AWS DynamoDB storage as the default grain storage. /// public static IServiceCollection AddDynamoDBGrainStorageAsDefault(this IServiceCollection services, Action> configureOptions = null) { return services.AddDynamoDBGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AWS DynamoDB storage for grain storage. /// public static IServiceCollection AddDynamoDBGrainStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient(sp => new DynamoDBGrainStorageOptionsValidator(sp.GetRequiredService>().Get(name), name)); services.ConfigureNamedOptionForLogging(name); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); return services.AddGrainStorage(name, DynamoDBGrainStorageFactory.Create); } } } ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Hosting/DynamoDBGrainStorageSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; namespace Orleans.Hosting { public static class DynamoDBGrainStorageSiloBuilderExtensions { /// /// Configure silo to use AWS DynamoDB storage as the default grain storage. /// public static ISiloBuilder AddDynamoDBGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddDynamoDBGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AWS DynamoDB storage for grain storage. /// public static ISiloBuilder AddDynamoDBGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddDynamoDBGrainStorage(name, configureOptions)); } /// /// Configure silo to use AWS DynamoDB storage as the default grain storage. /// public static ISiloBuilder AddDynamoDBGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddDynamoDBGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AWS DynamoDB storage for grain storage. /// public static ISiloBuilder AddDynamoDBGrainStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder.ConfigureServices(services => services.AddDynamoDBGrainStorage(name, configureOptions)); } } } ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Options/DynamoDBStorageOptions.cs ================================================ using System; using Orleans.Persistence.DynamoDB; using Orleans.Runtime; using Orleans.Storage; namespace Orleans.Configuration { public class DynamoDBStorageOptions : DynamoDBClientOptions, IStorageProviderSerializerOptions { /// /// Gets or sets a unique identifier for this service, which should survive deployment and redeployment. /// public string ServiceId { get; set; } = string.Empty; /// /// Use Provisioned Throughput for tables /// public bool UseProvisionedThroughput { get; set; } = true; /// /// Create the table if it doesn't exist /// public bool CreateIfNotExists { get; set; } = true; /// /// Update the table if it exists /// public bool UpdateIfExists { get; set; } = true; /// /// Read capacity unit for DynamoDB storage /// public int ReadCapacityUnits { get; set; } = DynamoDBStorage.DefaultReadCapacityUnits; /// /// Write capacity unit for DynamoDB storage /// public int WriteCapacityUnits { get; set; } = DynamoDBStorage.DefaultWriteCapacityUnits; /// /// DynamoDB table name. /// Defaults to 'OrleansGrainState'. /// public string TableName { get; set; } = "OrleansGrainState"; /// /// Indicates if grain data should be deleted or reset to defaults when a grain clears it's state. /// public bool DeleteStateOnClear { get; set; } = false; /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// /// Specifies a time span in which the item would be expired in the future /// every StateWrite will increase the TTL of the grain /// public TimeSpan? TimeToLive { get; set; } public IGrainStorageSerializer GrainStorageSerializer { get; set; } } /// /// Configuration validator for DynamoDBStorageOptions /// public class DynamoDBGrainStorageOptionsValidator : IConfigurationValidator { private readonly DynamoDBStorageOptions options; private readonly string name; /// /// Constructor /// /// The option to be validated. /// The option name to be validated. public DynamoDBGrainStorageOptionsValidator(DynamoDBStorageOptions options, string name) { this.options = options; this.name = name; } public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.TableName)) throw new OrleansConfigurationException( $"Configuration for DynamoDBGrainStorage {this.name} is invalid. {nameof(this.options.TableName)} is not valid."); if (this.options.UseProvisionedThroughput) { if (this.options.ReadCapacityUnits == 0) throw new OrleansConfigurationException( $"Configuration for DynamoDBGrainStorage {this.name} is invalid. {nameof(this.options.ReadCapacityUnits)} is not valid."); if (this.options.WriteCapacityUnits == 0) throw new OrleansConfigurationException( $"Configuration for DynamoDBGrainStorage {this.name} is invalid. {nameof(this.options.WriteCapacityUnits)} is not valid."); } } } } ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Orleans.Persistence.DynamoDB.csproj ================================================ README.md Microsoft.Orleans.Persistence.DynamoDB Microsoft Orleans AWS DynamoDB Persistence Provider Microsoft Orleans persistence providers backed by AWS DynamoDB $(PackageTags) AWS DynamoDB $(DefaultTargetFrameworks) true Orleans.Persistence.DynamoDB Orleans.Persistence.DynamoDB $(DefineConstants);PERSISTENCE_DYNAMODB ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/Provider/DynamoDBGrainStorage.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Orleans.Configuration; using Orleans.Persistence.DynamoDB; using Orleans.Runtime; using Orleans.Serialization.Serializers; namespace Orleans.Storage { /// /// Dynamo DB storage Provider. /// Persist Grain State in a DynamoDB table either in Json or Binary format. /// public partial class DynamoDBGrainStorage : IGrainStorage, ILifecycleParticipant { private const int MAX_DATA_SIZE = 400 * 1024; private const string GRAIN_REFERENCE_PROPERTY_NAME = "GrainReference"; private const string BINARY_STATE_PROPERTY_NAME = "GrainState"; private const string GRAIN_TYPE_PROPERTY_NAME = "GrainType"; private const string ETAG_PROPERTY_NAME = "ETag"; private const string GRAIN_TTL_PROPERTY_NAME = "GrainTtl"; private const string CURRENT_ETAG_ALIAS = ":currentETag"; private readonly DynamoDBStorageOptions options; private readonly IActivatorProvider _activatorProvider; private readonly ILogger logger; private readonly string name; private DynamoDBStorage storage; /// /// Default Constructor /// public DynamoDBGrainStorage( string name, DynamoDBStorageOptions options, IActivatorProvider activatorProvider, ILogger logger) { this.name = name; this.logger = logger; this.options = options; _activatorProvider = activatorProvider; } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.name), this.options.InitStage, Init, Close); } /// Initialization function for this storage provider. public async Task Init(CancellationToken ct) { var stopWatch = Stopwatch.StartNew(); try { var initMsg = string.Format("Init: Name={0} ServiceId={1} Table={2} DeleteStateOnClear={3}", this.name, this.options.ServiceId, this.options.TableName, this.options.DeleteStateOnClear); LogInformationInitializingDynamoDBGrainStorage(logger, this.name, initMsg); this.storage = new DynamoDBStorage( this.logger, this.options.Service, this.options.AccessKey, this.options.SecretKey, this.options.Token, this.options.ProfileName, this.options.ReadCapacityUnits, this.options.WriteCapacityUnits, this.options.UseProvisionedThroughput, this.options.CreateIfNotExists, this.options.UpdateIfExists); await storage.InitializeTable(this.options.TableName, new List { new KeySchemaElement { AttributeName = GRAIN_REFERENCE_PROPERTY_NAME, KeyType = KeyType.HASH }, new KeySchemaElement { AttributeName = GRAIN_TYPE_PROPERTY_NAME, KeyType = KeyType.RANGE } }, new List { new AttributeDefinition { AttributeName = GRAIN_REFERENCE_PROPERTY_NAME, AttributeType = ScalarAttributeType.S }, new AttributeDefinition { AttributeName = GRAIN_TYPE_PROPERTY_NAME, AttributeType = ScalarAttributeType.S } }, secondaryIndexes: null, ttlAttributeName: this.options.TimeToLive.HasValue ? GRAIN_TTL_PROPERTY_NAME : null); stopWatch.Stop(); LogInformationProviderInitialized(logger, this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds); } catch (Exception exc) { stopWatch.Stop(); LogErrorProviderInitFailed(logger, this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds, exc); throw; } } /// Shutdown this storage provider. public Task Close(CancellationToken ct) => Task.CompletedTask; /// Read state data function for this storage provider. /// public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (this.storage == null) throw new ArgumentException("GrainState-Table property not initialized"); string partitionKey = GetKeyString(grainId); LogTraceReadingGrainState(logger, grainType, partitionKey, grainId, this.options.TableName); string rowKey = AWSUtils.ValidateDynamoDBRowKey(grainType); var record = await this.storage.ReadSingleEntryAsync(this.options.TableName, new Dictionary { { GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue(partitionKey) }, { GRAIN_TYPE_PROPERTY_NAME, new AttributeValue(rowKey) } }, (fields) => { return new GrainStateRecord { GrainType = fields[GRAIN_TYPE_PROPERTY_NAME].S, GrainReference = fields[GRAIN_REFERENCE_PROPERTY_NAME].S, ETag = int.Parse(fields[ETAG_PROPERTY_NAME].N), State = fields.TryGetValue(BINARY_STATE_PROPERTY_NAME, out var propertyName) ? propertyName.B?.ToArray() : null, }; }).ConfigureAwait(false); if (record != null) { var loadedState = ConvertFromStorageFormat(record); grainState.RecordExists = loadedState != null; grainState.State = loadedState ?? CreateInstance(); grainState.ETag = record.ETag.ToString(); } else { ResetGrainState(grainState); } } /// Write state data function for this storage provider. /// public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (this.storage == null) throw new ArgumentException("GrainState-Table property not initialized"); string partitionKey = GetKeyString(grainId); string rowKey = AWSUtils.ValidateDynamoDBRowKey(grainType); var record = new GrainStateRecord { GrainReference = partitionKey, GrainType = rowKey }; try { ConvertToStorageFormat(grainState.State, record); await WriteStateInternal(grainState, record); } catch (ConditionalCheckFailedException exc) { throw new InconsistentStateException($"Inconsistent grain state: {exc}"); } catch (Exception exc) { LogErrorWritingGrainState(logger, exc, grainType, grainId, grainState.ETag, this.options.TableName); throw; } } private async Task WriteStateInternal(IGrainState grainState, GrainStateRecord record, bool clear = false) { var fields = new Dictionary(); if (this.options.TimeToLive.HasValue) { fields.Add(GRAIN_TTL_PROPERTY_NAME, new AttributeValue { N = ((DateTimeOffset)DateTime.UtcNow.Add(this.options.TimeToLive.Value)).ToUnixTimeSeconds().ToString() }); } if (record.State != null && record.State.Length > 0) { fields.Add(BINARY_STATE_PROPERTY_NAME, new AttributeValue { B = new MemoryStream(record.State) }); } else { fields.Add(BINARY_STATE_PROPERTY_NAME, new AttributeValue { NULL = true }); } int newEtag = 0; if (clear) { fields.Add(GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue(record.GrainReference)); fields.Add(GRAIN_TYPE_PROPERTY_NAME, new AttributeValue(record.GrainType)); int currentEtag; int.TryParse(grainState.ETag, out currentEtag); newEtag = currentEtag; newEtag++; fields.Add(ETAG_PROPERTY_NAME, new AttributeValue { N = newEtag.ToString() }); await this.storage.PutEntryAsync(this.options.TableName, fields).ConfigureAwait(false); } else if (string.IsNullOrWhiteSpace(grainState.ETag)) { fields.Add(GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue(record.GrainReference)); fields.Add(GRAIN_TYPE_PROPERTY_NAME, new AttributeValue(record.GrainType)); fields.Add(ETAG_PROPERTY_NAME, new AttributeValue { N = "0" }); var expression = $"attribute_not_exists({GRAIN_REFERENCE_PROPERTY_NAME}) AND attribute_not_exists({GRAIN_TYPE_PROPERTY_NAME})"; await this.storage.PutEntryAsync(this.options.TableName, fields, expression).ConfigureAwait(false); } else { var keys = new Dictionary(); keys.Add(GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue(record.GrainReference)); keys.Add(GRAIN_TYPE_PROPERTY_NAME, new AttributeValue(record.GrainType)); int currentEtag; int.TryParse(grainState.ETag, out currentEtag); newEtag = currentEtag; newEtag++; fields.Add(ETAG_PROPERTY_NAME, new AttributeValue { N = newEtag.ToString() }); var conditionalValues = new Dictionary { { CURRENT_ETAG_ALIAS, new AttributeValue { N = currentEtag.ToString() } } }; var expression = $"{ETAG_PROPERTY_NAME} = {CURRENT_ETAG_ALIAS}"; await this.storage.UpsertEntryAsync(this.options.TableName, keys, fields, expression, conditionalValues).ConfigureAwait(false); } grainState.ETag = newEtag.ToString(); grainState.RecordExists = !clear; } /// Clear / Delete state data function for this storage provider. /// /// If the DeleteStateOnClear is set to true then the table row /// for this grain will be deleted / removed, otherwise the table row will be /// cleared by overwriting with default / null values. /// /// public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (this.storage == null) throw new ArgumentException("GrainState-Table property not initialized"); string partitionKey = GetKeyString(grainId); LogTraceClearingGrainState(logger, grainType, partitionKey, grainId, grainState.ETag, this.options.DeleteStateOnClear, this.options.TableName); string rowKey = AWSUtils.ValidateDynamoDBRowKey(grainType); var record = new GrainStateRecord { GrainReference = partitionKey, ETag = string.IsNullOrWhiteSpace(grainState.ETag) ? 0 : int.Parse(grainState.ETag), GrainType = rowKey }; var operation = "Clearing"; try { if (this.options.DeleteStateOnClear) { operation = "Deleting"; var keys = new Dictionary(); keys.Add(GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue(record.GrainReference)); keys.Add(GRAIN_TYPE_PROPERTY_NAME, new AttributeValue(record.GrainType)); await this.storage.DeleteEntryAsync(this.options.TableName, keys).ConfigureAwait(false); ResetGrainState(grainState); } else { await WriteStateInternal(grainState, record, true); grainState.State = CreateInstance(); grainState.RecordExists = false; } } catch (Exception exc) { LogErrorClearingGrainState(logger, exc, operation, grainType, grainId, grainState.ETag, this.options.TableName); throw; } } internal class GrainStateRecord { public string GrainReference { get; set; } = ""; public string GrainType { get; set; } = ""; public byte[] State { get; set; } public int ETag { get; set; } } private string GetKeyString(GrainId grainId) { var key = $"{options.ServiceId}_{grainId}"; return AWSUtils.ValidateDynamoDBPartitionKey(key); } internal T ConvertFromStorageFormat(GrainStateRecord entity) { T dataValue = default; try { if (entity.State is { Length: > 0 }) dataValue = this.options.GrainStorageSerializer.Deserialize(entity.State); } catch (Exception exc) { var sb = new StringBuilder(); sb.AppendFormat("Unable to convert from storage format GrainStateEntity.Data={0}", entity.State); if (dataValue != null) { sb.Append($"Data Value={dataValue} Type={dataValue.GetType()}"); } var message = sb.ToString(); LogError(logger, message); throw new AggregateException(message, exc); } return dataValue; } internal void ConvertToStorageFormat(object grainState, GrainStateRecord entity) { int dataSize; // Convert to binary format entity.State = this.options.GrainStorageSerializer.Serialize(grainState).ToArray(); dataSize = BINARY_STATE_PROPERTY_NAME.Length + entity.State.Length; LogTraceWritingBinaryData(logger, dataSize, entity.GrainReference, entity.GrainType); var pkSize = GRAIN_REFERENCE_PROPERTY_NAME.Length + entity.GrainReference.Length; var rkSize = GRAIN_TYPE_PROPERTY_NAME.Length + entity.GrainType.Length; var versionSize = ETAG_PROPERTY_NAME.Length + entity.ETag.ToString().Length; if ((pkSize + rkSize + versionSize + dataSize) > MAX_DATA_SIZE) { var msg = $"Data too large to write to DynamoDB table. Size={dataSize} MaxSize={MAX_DATA_SIZE}"; throw new ArgumentOutOfRangeException("GrainState.Size", msg); } } private void ResetGrainState(IGrainState grainState) { grainState.RecordExists = false; grainState.ETag = null; grainState.State = CreateInstance(); } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( Level = LogLevel.Information, Message = "AWS DynamoDB Grain Storage {Name} is initializing: {InitMsg}" )] private static partial void LogInformationInitializingDynamoDBGrainStorage(ILogger logger, string name, string initMsg); [LoggerMessage( Level = LogLevel.Information, Message = "Initializing provider {Name} of type {Type} in stage {Stage} took {ElapsedMilliseconds} Milliseconds." )] private static partial void LogInformationProviderInitialized(ILogger logger, string name, string type, int stage, long elapsedMilliseconds); [LoggerMessage( EventId = (int)ErrorCode.Provider_ErrorFromInit, Level = LogLevel.Error, Message = "Initialization failed for provider {Name} of type {Type} in stage {Stage} in {ElapsedMilliseconds} Milliseconds." )] private static partial void LogErrorProviderInitFailed(ILogger logger, string name, string type, int stage, long elapsedMilliseconds, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Reading: GrainType={GrainType} Pk={PartitionKey} GrainId={GrainId} from Table={TableName}" )] private static partial void LogTraceReadingGrainState(ILogger logger, string grainType, string partitionKey, GrainId grainId, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Error Writing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to Table={TableName}" )] private static partial void LogErrorWritingGrainState(ILogger logger, Exception exception, string grainType, GrainId grainId, string eTag, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Clearing: GrainType={GrainType} Pk={PartitionKey} GrainId={GrainId} ETag={ETag} DeleteStateOnClear={DeleteStateOnClear} from Table={TableName}" )] private static partial void LogTraceClearingGrainState(ILogger logger, string grainType, string partitionKey, GrainId grainId, string eTag, bool deleteStateOnClear, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Error {Operation}: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from Table={TableName}" )] private static partial void LogErrorClearingGrainState(ILogger logger, Exception exception, string operation, string grainType, GrainId grainId, string eTag, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "{Message}" )] private static partial void LogError(ILogger logger, string message); [LoggerMessage( Level = LogLevel.Trace, Message = "Writing binary data size = {DataSize} for grain id = Partition={Partition} / Row={Row}" )] private static partial void LogTraceWritingBinaryData(ILogger logger, int dataSize, string partition, string row); } public static class DynamoDBGrainStorageFactory { public static DynamoDBGrainStorage Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); return ActivatorUtilities.CreateInstance(services, optionsMonitor.Get(name), name); } } } ================================================ FILE: src/AWS/Orleans.Persistence.DynamoDB/README.md ================================================ # Microsoft Orleans Persistence for DynamoDB ## Introduction Microsoft Orleans Persistence for DynamoDB provides grain persistence for Microsoft Orleans using Amazon's DynamoDB. This allows your grains to persist their state in DynamoDB and reload it when they are reactivated. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Persistence.DynamoDB ``` ## Example - Configuring DynamoDB Persistence ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure DynamoDB as grain storage .AddDynamoDBGrainStorage( name: "dynamoStore", configureOptions: options => { options.AccessKey = "YOUR_AWS_ACCESS_KEY"; options.SecretKey = "YOUR_AWS_SECRET_KEY"; options.Region = "us-east-1"; options.TableName = "OrleansGrainState"; options.CreateIfNotExists = true; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Grain Storage in a Grain ```csharp // Define grain state class public class MyGrainState { public string Data { get; set; } public int Version { get; set; } } // Grain implementation that uses the DynamoDB storage public class MyGrain : Grain, IMyGrain, IGrainWithStringKey { private readonly IPersistentState _state; public MyGrain([PersistentState("state", "dynamoStore")] IPersistentState state) { _state = state; } public async Task SetData(string data) { _state.State.Data = data; _state.State.Version++; await _state.WriteStateAsync(); } public Task GetData() { return Task.FromResult(_state.State.Data); } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) - [AWS SDK for .NET Documentation](https://docs.aws.amazon.com/sdk-for-net/index.html) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/DynamoDBRemindersProviderBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("DynamoDB", "Reminders", "Silo", typeof(DynamoDBRemindersProviderBuilder))] namespace Orleans.Hosting; internal sealed class DynamoDBRemindersProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseDynamoDBReminderService(options => { var accessKey = configurationSection[nameof(options.AccessKey)]; if (!string.IsNullOrEmpty(accessKey)) { options.AccessKey = accessKey; } var secretKey = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(secretKey)) { options.SecretKey = secretKey; } var region = configurationSection[nameof(options.Service)] ?? configurationSection["Region"]; if (!string.IsNullOrEmpty(region)) { options.Service = region; } var token = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(token)) { options.Token = token; } var profileName = configurationSection[nameof(options.SecretKey)]; if (!string.IsNullOrEmpty(profileName)) { options.ProfileName = profileName; } var tableName = configurationSection[nameof(options.TableName)]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } if (int.TryParse(configurationSection[nameof(options.ReadCapacityUnits)], out var rcu)) { options.ReadCapacityUnits = rcu; } if (int.TryParse(configurationSection[nameof(options.WriteCapacityUnits)], out var wcu)) { options.WriteCapacityUnits = wcu; } if (bool.TryParse(configurationSection[nameof(options.UseProvisionedThroughput)], out var upt)) { options.UseProvisionedThroughput = upt; } if (bool.TryParse(configurationSection[nameof(options.CreateIfNotExists)], out var cine)) { options.CreateIfNotExists = cine; } if (bool.TryParse(configurationSection[nameof(options.UpdateIfExists)], out var uie)) { options.UpdateIfExists = uie; } }); } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/DynamoDBServiceCollectionReminderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Reminders.DynamoDB; using System; namespace Orleans.Hosting { /// /// extensions. /// public static class DynamoDBServiceCollectionReminderExtensions { /// /// Adds reminder storage backed by Amazon DynamoDB. /// /// /// The service collection. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static IServiceCollection UseDynamoDBReminderService(this IServiceCollection services, Action configure) { services.AddReminders(); services.AddSingleton(); services.Configure(configure); services.ConfigureFormatter(); return services; } } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/DynamoDBSiloBuilderReminderExtensions.cs ================================================ using Orleans.Configuration; using System; namespace Orleans.Hosting { /// /// Silo host builder extensions. /// public static class DynamoDBSiloBuilderReminderExtensions { /// /// Adds reminder storage backed by Amazon DynamoDB. /// /// /// The builder. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseDynamoDBReminderService(this ISiloBuilder builder, Action configure) { builder.ConfigureServices(services => services.UseDynamoDBReminderService(configure)); return builder; } } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/Orleans.Reminders.DynamoDB.csproj ================================================ README.md Microsoft.Orleans.Reminders.DynamoDB Microsoft Orleans AWS DynamoDB Reminders Provider Microsoft Orleans reminders provider backed by AWS DynamoDB $(PackageTags) AWS DynamoDB $(DefaultTargetFrameworks) true Orleans.Reminders.DynamoDB Orleans.Reminders.DynamoDB $(DefineConstants);REMINDERS_DYNAMODB ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/README.md ================================================ # Microsoft Orleans Reminders for DynamoDB ## Introduction Microsoft Orleans Reminders for DynamoDB provides persistence for Orleans reminders using Amazon's DynamoDB. This allows your Orleans applications to schedule persistent reminders that will be triggered even after silo restarts or grain deactivation. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Reminders.DynamoDB ``` ## Example - Configuring DynamoDB Reminders ```csharp using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure DynamoDB as reminder storage .UseDynamoDBReminderService(options => { options.AccessKey = "YOUR_AWS_ACCESS_KEY"; options.SecretKey = "YOUR_AWS_SECRET_KEY"; options.Region = "us-east-1"; options.TableName = "OrleansReminders"; options.CreateIfNotExists = true; }); }); // Run the host var host = builder.Build(); await host.StartAsync(); // Get a reference to the grain var reminderGrain = host.Services.GetRequiredService() .GetGrain("my-reminder-grain"); // Start the reminder await reminderGrain.StartReminder("ExampleReminder"); Console.WriteLine("Reminder started!"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Using Reminders in a Grain ```csharp using System; using System.Threading.Tasks; using Orleans; using Orleans.Runtime; namespace ReminderExample; public interface IReminderGrain : IGrainWithStringKey { Task StartReminder(string reminderName); Task StopReminder(); } public class ReminderGrain : Grain, IReminderGrain, IRemindable { private string _reminderName = "MyReminder"; public async Task StartReminder(string reminderName) { _reminderName = reminderName; // Register a persistent reminder await RegisterOrUpdateReminder( reminderName, TimeSpan.FromMinutes(2), // Time to delay before the first tick (must be > 1 minute) TimeSpan.FromMinutes(5)); // Period of the reminder (must be > 1 minute) } public async Task StopReminder() { // Find and unregister the reminder var reminder = await GetReminder(_reminderName); if (reminder != null) { await UnregisterReminder(reminder); } } public Task ReceiveReminder(string reminderName, TickStatus status) { // This method is called when the reminder ticks Console.WriteLine($"Reminder {reminderName} triggered at {DateTime.UtcNow}. Status: {status}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Reminders and Timers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) - [Reminder Services](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/reminder-services) - [AWS SDK for .NET Documentation](https://docs.aws.amazon.com/sdk-for-net/index.html) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/Reminders/DynamoDBReminderTable.cs ================================================ using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Reminders.DynamoDB { /// /// Implementation for IReminderTable using DynamoDB as underlying storage. /// internal sealed partial class DynamoDBReminderTable : IReminderTable { private const string GRAIN_REFERENCE_PROPERTY_NAME = "GrainReference"; private const string REMINDER_NAME_PROPERTY_NAME = "ReminderName"; private const string SERVICE_ID_PROPERTY_NAME = "ServiceId"; private const string START_TIME_PROPERTY_NAME = "StartTime"; private const string PERIOD_PROPERTY_NAME = "Period"; private const string GRAIN_HASH_PROPERTY_NAME = "GrainHash"; private const string REMINDER_ID_PROPERTY_NAME = "ReminderId"; private const string ETAG_PROPERTY_NAME = "ETag"; private const string CURRENT_ETAG_ALIAS = ":currentETag"; private const string SERVICE_ID_GRAIN_HASH_INDEX = "ServiceIdIndex"; private const string SERVICE_ID_GRAIN_REFERENCE_INDEX = "ServiceIdGrainReferenceIndex"; private readonly ILogger logger; private readonly DynamoDBReminderStorageOptions options; private readonly string serviceId; private DynamoDBStorage storage; /// Initializes a new instance of the class. /// logger factory to use /// /// public DynamoDBReminderTable( ILoggerFactory loggerFactory, IOptions clusterOptions, IOptions storageOptions) { this.logger = loggerFactory.CreateLogger(); this.serviceId = clusterOptions.Value.ServiceId; this.options = storageOptions.Value; } /// Initialize current instance with specific global configuration and logger public Task Init() { this.storage = new DynamoDBStorage( this.logger, this.options.Service, this.options.AccessKey, this.options.SecretKey, this.options.Token, this.options.ProfileName, this.options.ReadCapacityUnits, this.options.WriteCapacityUnits, this.options.UseProvisionedThroughput, this.options.CreateIfNotExists, this.options.UpdateIfExists); LogInformationInitializingDynamoDBRemindersTable(logger); var serviceIdGrainHashGlobalSecondaryIndex = new GlobalSecondaryIndex { IndexName = SERVICE_ID_GRAIN_HASH_INDEX, Projection = new Projection { ProjectionType = ProjectionType.ALL }, KeySchema = new List { new KeySchemaElement { AttributeName = SERVICE_ID_PROPERTY_NAME, KeyType = KeyType.HASH}, new KeySchemaElement { AttributeName = GRAIN_HASH_PROPERTY_NAME, KeyType = KeyType.RANGE } } }; var serviceIdGrainReferenceGlobalSecondaryIndex = new GlobalSecondaryIndex { IndexName = SERVICE_ID_GRAIN_REFERENCE_INDEX, Projection = new Projection { ProjectionType = ProjectionType.ALL }, KeySchema = new List { new KeySchemaElement { AttributeName = SERVICE_ID_PROPERTY_NAME, KeyType = KeyType.HASH}, new KeySchemaElement { AttributeName = GRAIN_REFERENCE_PROPERTY_NAME, KeyType = KeyType.RANGE } } }; return this.storage.InitializeTable(this.options.TableName, new List { new KeySchemaElement { AttributeName = REMINDER_ID_PROPERTY_NAME, KeyType = KeyType.HASH }, new KeySchemaElement { AttributeName = GRAIN_HASH_PROPERTY_NAME, KeyType = KeyType.RANGE } }, new List { new AttributeDefinition { AttributeName = REMINDER_ID_PROPERTY_NAME, AttributeType = ScalarAttributeType.S }, new AttributeDefinition { AttributeName = GRAIN_HASH_PROPERTY_NAME, AttributeType = ScalarAttributeType.N }, new AttributeDefinition { AttributeName = SERVICE_ID_PROPERTY_NAME, AttributeType = ScalarAttributeType.S }, new AttributeDefinition { AttributeName = GRAIN_REFERENCE_PROPERTY_NAME, AttributeType = ScalarAttributeType.S } }, new List { serviceIdGrainHashGlobalSecondaryIndex, serviceIdGrainReferenceGlobalSecondaryIndex }); } /// /// Reads a reminder for a grain reference by reminder name. /// Read a row from the reminder table /// /// grain ref to locate the row /// reminder name to locate the row /// Return the ReminderTableData if the rows were read successfully public async Task ReadRow(GrainId grainId, string reminderName) { var reminderId = ConstructReminderId(this.serviceId, grainId, reminderName); var keys = new Dictionary { { $"{REMINDER_ID_PROPERTY_NAME}", new AttributeValue(reminderId) }, { $"{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = grainId.GetUniformHashCode().ToString() } } }; try { return await this.storage.ReadSingleEntryAsync(this.options.TableName, keys, this.Resolve).ConfigureAwait(false); } catch (Exception exc) { LogWarningReadReminderEntry(logger, exc, new(keys), this.options.TableName); throw; } } /// /// Read one row from the reminder table /// /// grain ref to locate the row /// Return the ReminderTableData if the rows were read successfully public async Task ReadRows(GrainId grainId) { var expressionValues = new Dictionary { { $":{SERVICE_ID_PROPERTY_NAME}", new AttributeValue(this.serviceId) }, { $":{GRAIN_REFERENCE_PROPERTY_NAME}", new AttributeValue(grainId.ToString()) } }; try { var expression = $"{SERVICE_ID_PROPERTY_NAME} = :{SERVICE_ID_PROPERTY_NAME} AND {GRAIN_REFERENCE_PROPERTY_NAME} = :{GRAIN_REFERENCE_PROPERTY_NAME}"; var records = await this.storage.QueryAllAsync(this.options.TableName, expressionValues, expression, this.Resolve, SERVICE_ID_GRAIN_REFERENCE_INDEX, consistentRead: false).ConfigureAwait(false); return new ReminderTableData(records); } catch (Exception exc) { LogWarningReadReminderEntries(logger, exc, new(expressionValues), this.options.TableName); throw; } } /// /// Reads reminder table data for a given hash range. /// /// /// /// Return the RemiderTableData if the rows were read successfully public async Task ReadRows(uint begin, uint end) { Dictionary expressionValues = null; try { string expression = string.Empty; List records; if (begin < end) { expressionValues = new Dictionary { { $":{SERVICE_ID_PROPERTY_NAME}", new AttributeValue(this.serviceId) }, { $":Begin{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = (begin + 1).ToString() } }, { $":End{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = end.ToString() } } }; expression = $"{SERVICE_ID_PROPERTY_NAME} = :{SERVICE_ID_PROPERTY_NAME} AND {GRAIN_HASH_PROPERTY_NAME} BETWEEN :Begin{GRAIN_HASH_PROPERTY_NAME} AND :End{GRAIN_HASH_PROPERTY_NAME}"; records = await this.storage.QueryAllAsync(this.options.TableName, expressionValues, expression, this.Resolve, SERVICE_ID_GRAIN_HASH_INDEX, consistentRead: false).ConfigureAwait(false); } else { expressionValues = new Dictionary { { $":{SERVICE_ID_PROPERTY_NAME}", new AttributeValue(this.serviceId) }, { $":End{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = end.ToString() } } }; expression = $"{SERVICE_ID_PROPERTY_NAME} = :{SERVICE_ID_PROPERTY_NAME} AND {GRAIN_HASH_PROPERTY_NAME} <= :End{GRAIN_HASH_PROPERTY_NAME}"; records = await this.storage.QueryAllAsync(this.options.TableName, expressionValues, expression, this.Resolve, SERVICE_ID_GRAIN_HASH_INDEX, consistentRead: false).ConfigureAwait(false); expressionValues = new Dictionary { { $":{SERVICE_ID_PROPERTY_NAME}", new AttributeValue(this.serviceId) }, { $":Begin{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = begin.ToString() } } }; expression = $"{SERVICE_ID_PROPERTY_NAME} = :{SERVICE_ID_PROPERTY_NAME} AND {GRAIN_HASH_PROPERTY_NAME} > :Begin{GRAIN_HASH_PROPERTY_NAME}"; records.AddRange(await this.storage.QueryAllAsync(this.options.TableName, expressionValues, expression, this.Resolve, SERVICE_ID_GRAIN_HASH_INDEX, consistentRead: false).ConfigureAwait(false)); } return new ReminderTableData(records); } catch (Exception exc) { LogWarningReadReminderEntryRange(logger, exc, new(expressionValues), this.options.TableName); throw; } } private ReminderEntry Resolve(Dictionary item) { return new ReminderEntry { ETag = item[ETAG_PROPERTY_NAME].N, GrainId = GrainId.Parse(item[GRAIN_REFERENCE_PROPERTY_NAME].S), Period = TimeSpan.Parse(item[PERIOD_PROPERTY_NAME].S), ReminderName = item[REMINDER_NAME_PROPERTY_NAME].S, StartAt = DateTime.Parse(item[START_TIME_PROPERTY_NAME].S) }; } /// /// Remove one row from the reminder table /// /// specific grain ref to locate the row /// reminder name to locate the row /// e tag /// Return true if the row was removed public async Task RemoveRow(GrainId grainId, string reminderName, string eTag) { var reminderId = ConstructReminderId(this.serviceId, grainId, reminderName); var keys = new Dictionary { { $"{REMINDER_ID_PROPERTY_NAME}", new AttributeValue(reminderId) }, { $"{GRAIN_HASH_PROPERTY_NAME}", new AttributeValue { N = grainId.GetUniformHashCode().ToString() } } }; try { var conditionalValues = new Dictionary { { CURRENT_ETAG_ALIAS, new AttributeValue { N = eTag } } }; var expression = $"{ETAG_PROPERTY_NAME} = {CURRENT_ETAG_ALIAS}"; await this.storage.DeleteEntryAsync(this.options.TableName, keys, expression, conditionalValues).ConfigureAwait(false); return true; } catch (ConditionalCheckFailedException) { return false; } } /// /// Test hook to clear reminder table data. /// /// public async Task TestOnlyClearTable() { var expressionValues = new Dictionary { { $":{SERVICE_ID_PROPERTY_NAME}", new AttributeValue(this.serviceId) } }; try { var expression = $"{SERVICE_ID_PROPERTY_NAME} = :{SERVICE_ID_PROPERTY_NAME}"; var records = await this.storage.ScanAsync(this.options.TableName, expressionValues, expression, item => new Dictionary { { REMINDER_ID_PROPERTY_NAME, item[REMINDER_ID_PROPERTY_NAME] }, { GRAIN_HASH_PROPERTY_NAME, item[GRAIN_HASH_PROPERTY_NAME] } }).ConfigureAwait(false); if (records.Count <= 25) { await this.storage.DeleteEntriesAsync(this.options.TableName, records); } else { List tasks = new List(); foreach (var batch in records.BatchIEnumerable(25)) { tasks.Add(this.storage.DeleteEntriesAsync(this.options.TableName, batch)); } await Task.WhenAll(tasks); } } catch (Exception exc) { LogWarningRemoveReminderEntries(logger, exc, new(expressionValues), this.options.TableName); throw; } } /// /// Async method to put an entry into the reminder table /// /// The entry to put /// Return the entry ETag if entry was upsert successfully public async Task UpsertRow(ReminderEntry entry) { var reminderId = ConstructReminderId(this.serviceId, entry.GrainId, entry.ReminderName); var fields = new Dictionary { { REMINDER_ID_PROPERTY_NAME, new AttributeValue(reminderId) }, { GRAIN_HASH_PROPERTY_NAME, new AttributeValue { N = entry.GrainId.GetUniformHashCode().ToString() } }, { SERVICE_ID_PROPERTY_NAME, new AttributeValue(this.serviceId) }, { GRAIN_REFERENCE_PROPERTY_NAME, new AttributeValue( entry.GrainId.ToString()) }, { PERIOD_PROPERTY_NAME, new AttributeValue(entry.Period.ToString()) }, { START_TIME_PROPERTY_NAME, new AttributeValue(entry.StartAt.ToString()) }, { REMINDER_NAME_PROPERTY_NAME, new AttributeValue(entry.ReminderName) }, { ETAG_PROPERTY_NAME, new AttributeValue { N = Random.Shared.Next().ToString() } } }; try { LogDebugUpsertRow(logger, entry, entry.ETag); await this.storage.PutEntryAsync(this.options.TableName, fields); entry.ETag = fields[ETAG_PROPERTY_NAME].N; return entry.ETag; } catch (Exception exc) { LogWarningUpdateReminderEntry(logger, exc, entry, options.TableName); throw; } } private static string ConstructReminderId(string serviceId, GrainId grainId, string reminderName) => $"{serviceId}_{grainId}_{reminderName}"; [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Information, Message = "Initializing AWS DynamoDB Reminders Table" )] private static partial void LogInformationInitializingDynamoDBRemindersTable(ILogger logger); private readonly struct DictionaryLogRecord(Dictionary keys) { public override string ToString() => Utils.DictionaryToString(keys); } [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Warning, Message = "Intermediate error reading reminder entry {Keys} from table {TableName}." )] private static partial void LogWarningReadReminderEntry(ILogger logger, Exception exception, DictionaryLogRecord keys, string tableName); [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Warning, Message = "Intermediate error reading reminder entry {Entries} from table {TableName}." )] private static partial void LogWarningReadReminderEntries(ILogger logger, Exception exception, DictionaryLogRecord entries, string tableName); [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Warning, Message = "Intermediate error reading reminder entry {ExpressionValues} from table {TableName}." )] private static partial void LogWarningReadReminderEntryRange(ILogger logger, Exception exception, DictionaryLogRecord expressionValues, string tableName); [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Warning, Message = "Intermediate error removing reminder entries {Entries} from table {TableName}." )] private static partial void LogWarningRemoveReminderEntries(ILogger logger, Exception exception, DictionaryLogRecord entries, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "UpsertRow entry = {Entry}, etag = {ETag}" )] private static partial void LogDebugUpsertRow(ILogger logger, ReminderEntry entry, string eTag); [LoggerMessage( EventId = (int)ErrorCode.ReminderServiceBase, Level = LogLevel.Warning, Message = "Intermediate error updating entry {Entry} to the table {TableName}." )] private static partial void LogWarningUpdateReminderEntry(ILogger logger, Exception exception, ReminderEntry entry, string tableName); } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/Reminders/DynamoDbReminderServiceOptions.cs ================================================ namespace Orleans.Configuration { /// /// Configuration for Amazon DynamoDB reminder storage. /// public class DynamoDBReminderTableOptions { /// /// Gets or sets the connection string. /// [RedactConnectionString] public string ConnectionString { get; set; } } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/Reminders/DynamoDbReminderStorageOptions.cs ================================================ using Orleans.Reminders.DynamoDB; namespace Orleans.Configuration { /// /// Configuration for Amazon DynamoDB reminder storage. /// public class DynamoDBReminderStorageOptions : DynamoDBClientOptions { /// /// Read capacity unit for DynamoDB storage /// public int ReadCapacityUnits { get; set; } = DynamoDBStorage.DefaultReadCapacityUnits; /// /// Write capacity unit for DynamoDB storage /// public int WriteCapacityUnits { get; set; } = DynamoDBStorage.DefaultWriteCapacityUnits; /// /// Use Provisioned Throughput for tables /// public bool UseProvisionedThroughput { get; set; } = true; /// /// Create the table if it doesn't exist /// public bool CreateIfNotExists { get; set; } = true; /// /// Update the table if it exists /// public bool UpdateIfExists { get; set; } = true; /// /// DynamoDB table name. /// Defaults to 'OrleansReminders'. /// public string TableName { get; set; } = "OrleansReminders"; } } ================================================ FILE: src/AWS/Orleans.Reminders.DynamoDB/Reminders/DynamoDbReminderStorageOptionsExtensions.cs ================================================ using System; using System.Linq; namespace Orleans.Configuration { /// /// Configuration for Amazon DynamoDB reminder storage. /// public static class DynamoDBReminderStorageOptionsExtensions { private const string AccessKeyPropertyName = "AccessKey"; private const string SecretKeyPropertyName = "SecretKey"; private const string ServicePropertyName = "Service"; private const string ReadCapacityUnitsPropertyName = "ReadCapacityUnits"; private const string WriteCapacityUnitsPropertyName = "WriteCapacityUnits"; private const string UseProvisionedThroughputPropertyName = "UseProvisionedThroughput"; private const string CreateIfNotExistsPropertyName = "CreateIfNotExists"; private const string UpdateIfExistsPropertyName = "UpdateIfExists"; /// /// Configures this instance using the provided connection string. /// public static void ParseConnectionString(this DynamoDBReminderStorageOptions options, string connectionString) { var parameters = connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var serviceConfig = Array.Find(parameters, p => p.Contains(ServicePropertyName)); if (!string.IsNullOrWhiteSpace(serviceConfig)) { var value = serviceConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.Service = value[1]; } var secretKeyConfig = Array.Find(parameters, p => p.Contains(SecretKeyPropertyName)); if (!string.IsNullOrWhiteSpace(secretKeyConfig)) { var value = secretKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.SecretKey = value[1]; } var accessKeyConfig = Array.Find(parameters, p => p.Contains(AccessKeyPropertyName)); if (!string.IsNullOrWhiteSpace(accessKeyConfig)) { var value = accessKeyConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.AccessKey = value[1]; } var readCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(ReadCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(readCapacityUnitsConfig)) { var value = readCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.ReadCapacityUnits = int.Parse(value[1]); } var writeCapacityUnitsConfig = Array.Find(parameters, p => p.Contains(WriteCapacityUnitsPropertyName)); if (!string.IsNullOrWhiteSpace(writeCapacityUnitsConfig)) { var value = writeCapacityUnitsConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.WriteCapacityUnits = int.Parse(value[1]); } var useProvisionedThroughputConfig = Array.Find(parameters, p => p.Contains(UseProvisionedThroughputPropertyName)); if (!string.IsNullOrWhiteSpace(useProvisionedThroughputConfig)) { var value = useProvisionedThroughputConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.UseProvisionedThroughput = bool.Parse(value[1]); } var createIfNotExistsPropertyNameConfig = Array.Find(parameters, p => p.Contains(CreateIfNotExistsPropertyName)); if (!string.IsNullOrWhiteSpace(createIfNotExistsPropertyNameConfig)) { var value = createIfNotExistsPropertyNameConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.CreateIfNotExists = bool.Parse(value[1]); } var updateIfExistsPropertyNameConfig = Array.Find(parameters, p => p.Contains(UpdateIfExistsPropertyName)); if (!string.IsNullOrWhiteSpace(updateIfExistsPropertyNameConfig)) { var value = updateIfExistsPropertyNameConfig.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) options.UpdateIfExists = bool.Parse(value[1]); } } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Hosting/ClientBuilderExtensions.cs ================================================ using System; using Orleans.Configuration; namespace Orleans.Hosting { public static class ClientBuilderExtensions { /// /// Configure cluster client to use SQS persistent streams with default settings /// public static IClientBuilder AddSqsStreams(this IClientBuilder builder, string name, Action configureOptions) { builder.AddSqsStreams(name, b=> b.ConfigureSqs(ob=>ob.Configure(configureOptions))); return builder; } /// /// Configure cluster client to use SQS persistent streams. /// public static IClientBuilder AddSqsStreams(this IClientBuilder builder, string name, Action configure) { var configurator = new ClusterClientSqsStreamConfigurator(name, builder); configure?.Invoke(configurator); return builder; } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Hosting/SiloBuilderExtensions.cs ================================================ using System; using Orleans.Configuration; namespace Orleans.Hosting { public static class SiloBuilderExtensions { /// /// Configure silo to use SQS persistent streams. /// public static ISiloBuilder AddSqsStreams(this ISiloBuilder builder, string name, Action configureOptions) { builder.AddSqsStreams(name, b => b.ConfigureSqs(ob => ob.Configure(configureOptions))); return builder; } /// /// Configure silo to use SQS persistent streams. /// public static ISiloBuilder AddSqsStreams(this ISiloBuilder builder, string name, Action configure) { var configurator = new SiloSqsStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure?.Invoke(configurator); return builder; } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Orleans.Streaming.SQS.csproj ================================================ README.md Microsoft.Orleans.Streaming.SQS Microsoft Orleans AWS SQS Streaming Provider Microsoft Orleans streaming provider backed by AWS SQS $(PackageTags) AWS SQS $(DefaultTargetFrameworks) $(DefineConstants);STREAMING_SQS true ================================================ FILE: src/AWS/Orleans.Streaming.SQS/README.md ================================================ # Microsoft Orleans Streaming for Amazon SQS ## Introduction Microsoft Orleans Streaming for Amazon SQS provides a stream provider implementation for Orleans using Amazon Simple Queue Service (SQS). This allows for publishing and subscribing to streams of events with SQS as the underlying messaging infrastructure. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Streaming.SQS ``` ## Example - Configuring SQS Streaming ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Streams; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure SQS as a stream provider .AddSqsStreams( name: "SQSStreamProvider", configureOptions: options => { options.AccessKey = "YOUR_AWS_ACCESS_KEY"; options.SecretKey = "YOUR_AWS_SECRET_KEY"; options.Region = "us-east-1"; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using SQS Streams in a Grain ```csharp using System; using System.Threading; using System.Threading.Tasks; using Orleans; using Orleans.Streams; // Producer grain public class ProducerGrain : Grain, IProducerGrain { private IAsyncStream _stream; public override Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("SQSStreamProvider"); _stream = streamProvider.GetStream(Guid.NewGuid(), "MyStreamNamespace"); return base.OnActivateAsync(cancellationToken); } public async Task SendMessage(string message) { // Send a message to the stream await _stream.OnNextAsync(message); } } // Consumer grain public class ConsumerGrain : Grain, IConsumerGrain, IAsyncObserver { private StreamSubscriptionHandle _subscription; public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("SQSStreamProvider"); var stream = streamProvider.GetStream(this.GetPrimaryKey(), "MyStreamNamespace"); // Subscribe to the stream _subscription = await stream.SubscribeAsync(this); await base.OnActivateAsync(cancellationToken); } public Task OnNextAsync(string item, StreamSequenceToken token = null) { Console.WriteLine($"Received message: {item}"); return Task.CompletedTask; } public Task OnCompletedAsync() { Console.WriteLine("Stream completed"); return Task.CompletedTask; } public Task OnErrorAsync(Exception ex) { Console.WriteLine($"Stream error: {ex.Message}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Streams](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/index) - [Stream Providers](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/stream-providers) - [AWS SDK for .NET Documentation](https://docs.aws.amazon.com/sdk-for-net/index.html) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Storage/SQSStorage.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Amazon.Runtime; using Amazon.SQS; using Amazon.SQS.Model; using Microsoft.Extensions.Logging; using Orleans; using Orleans.Streaming.SQS; using SQSMessage = Amazon.SQS.Model.Message; namespace OrleansAWSUtils.Storage { /// /// Wrapper/Helper class around AWS SQS queue service /// internal partial class SQSStorage { /// /// Maximum number of messages allowed by SQS to peak per request /// public const int MAX_NUMBER_OF_MESSAGE_TO_PEEK = 10; private const string AccessKeyPropertyName = "AccessKey"; private const string SecretKeyPropertyName = "SecretKey"; private const string ServicePropertyName = "Service"; private readonly ILogger Logger; private string accessKey; private string secretKey; private string service; private string queueUrl; private AmazonSQSClient sqsClient; /// /// The queue Name /// public string QueueName { get; private set; } /// /// Default Ctor /// /// logger factory to use /// The name of the queue /// The connection string /// The service ID public SQSStorage(ILoggerFactory loggerFactory, string queueName, string connectionString, string serviceId = "") { QueueName = string.IsNullOrWhiteSpace(serviceId) ? queueName : $"{serviceId}-{queueName}"; ParseDataConnectionString(connectionString); Logger = loggerFactory.CreateLogger(); CreateClient(); } private void ParseDataConnectionString(string dataConnectionString) { var parameters = dataConnectionString.Split(';', StringSplitOptions.RemoveEmptyEntries); var serviceConfig = Array.Find(parameters, p => p.Contains(ServicePropertyName)); if (!string.IsNullOrWhiteSpace(serviceConfig)) { var value = serviceConfig.Split('=', StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) service = value[1]; } var secretKeyConfig = Array.Find(parameters, p => p.Contains(SecretKeyPropertyName)); if (!string.IsNullOrWhiteSpace(secretKeyConfig)) { var value = secretKeyConfig.Split('=', StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) secretKey = value[1]; } var accessKeyConfig = Array.Find(parameters, p => p.Contains(AccessKeyPropertyName)); if (!string.IsNullOrWhiteSpace(accessKeyConfig)) { var value = accessKeyConfig.Split('=', StringSplitOptions.RemoveEmptyEntries); if (value.Length == 2 && !string.IsNullOrWhiteSpace(value[1])) accessKey = value[1]; } } private void CreateClient() { if (service.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || service.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { // Local SQS instance (for testing) var credentials = new BasicAWSCredentials("dummy", "dummyKey"); sqsClient = new AmazonSQSClient(credentials, new AmazonSQSConfig { ServiceURL = service }); } else if (!string.IsNullOrEmpty(accessKey) && !string.IsNullOrEmpty(secretKey)) { // AWS SQS instance (auth via explicit credentials) var credentials = new BasicAWSCredentials(accessKey, secretKey); sqsClient = new AmazonSQSClient(credentials, new AmazonSQSConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(service) }); } else { // AWS SQS instance (implicit auth - EC2 IAM Roles etc) sqsClient = new AmazonSQSClient(new AmazonSQSConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(service) }); } } private async Task GetQueueUrl() { try { var response = await sqsClient.GetQueueUrlAsync(QueueName); if (!string.IsNullOrWhiteSpace(response.QueueUrl)) queueUrl = response.QueueUrl; return queueUrl; } catch (QueueDoesNotExistException) { return null; } } /// /// Initialize SQSStorage by creating or connecting to an existent queue /// /// public async Task InitQueueAsync() { try { if (string.IsNullOrWhiteSpace(await GetQueueUrl())) { var response = await sqsClient.CreateQueueAsync(QueueName); queueUrl = response.QueueUrl; } } catch (Exception exc) { ReportErrorAndRethrow(exc, "InitQueueAsync"); } } /// /// Delete the queue /// /// public async Task DeleteQueue() { try { if (string.IsNullOrWhiteSpace(queueUrl)) throw new InvalidOperationException("Queue not initialized"); await sqsClient.DeleteQueueAsync(queueUrl); } catch (Exception exc) { ReportErrorAndRethrow(exc, "DeleteQueue"); } } /// /// Add a message to the SQS queue /// /// Message request /// public async Task AddMessage(SendMessageRequest message) { try { if (string.IsNullOrWhiteSpace(queueUrl)) throw new InvalidOperationException("Queue not initialized"); message.QueueUrl = queueUrl; await sqsClient.SendMessageAsync(message); } catch (Exception exc) { ReportErrorAndRethrow(exc, "AddMessage"); } } /// /// Get Messages from SQS Queue. /// /// The number of messages to peak. Min 1 and max 10 /// Collection with messages from the queue public async Task> GetMessages(int count = 1) { try { if (string.IsNullOrWhiteSpace(queueUrl)) throw new InvalidOperationException("Queue not initialized"); if (count < 1) throw new ArgumentOutOfRangeException(nameof(count)); var request = new ReceiveMessageRequest { QueueUrl = queueUrl, MaxNumberOfMessages = count <= MAX_NUMBER_OF_MESSAGE_TO_PEEK ? count : MAX_NUMBER_OF_MESSAGE_TO_PEEK }; var response = await sqsClient.ReceiveMessageAsync(request); return response.Messages; } catch (Exception exc) { ReportErrorAndRethrow(exc, "GetMessages"); } return null; } /// /// Delete a message from SQS queue /// /// The message to be deleted /// public async Task DeleteMessage(SQSMessage message) { try { if (message == null) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrWhiteSpace(message.ReceiptHandle)) throw new ArgumentNullException(nameof(message.ReceiptHandle)); if (string.IsNullOrWhiteSpace(queueUrl)) throw new InvalidOperationException("Queue not initialized"); await sqsClient.DeleteMessageAsync( new DeleteMessageRequest { QueueUrl = queueUrl, ReceiptHandle = message.ReceiptHandle }); } catch (Exception exc) { ReportErrorAndRethrow(exc, "DeleteMessage"); } } private void ReportErrorAndRethrow(Exception exc, string operation) { LogErrorSQSOperation(exc, operation, QueueName); throw new AggregateException($"Error doing {operation} for SQS queue {QueueName}", exc); } [LoggerMessage( EventId = (int)ErrorCode.StreamProviderManagerBase, Level = LogLevel.Error, Message = "Error doing {Operation} for SQS queue {QueueName}" )] private partial void LogErrorSQSOperation(Exception exception, string operation, string queueName); } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSAdapter.cs ================================================ using Orleans.Streams; using OrleansAWSUtils.Storage; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Serialization; namespace OrleansAWSUtils.Streams { internal class SQSAdapter : IQueueAdapter { protected readonly string ServiceId; private readonly Serializer serializer; protected readonly string DataConnectionString; private readonly IConsistentRingStreamQueueMapper streamQueueMapper; protected readonly ConcurrentDictionary Queues = new ConcurrentDictionary(); private readonly ILoggerFactory loggerFactory; public string Name { get; private set; } public bool IsRewindable { get { return false; } } public StreamProviderDirection Direction { get { return StreamProviderDirection.ReadWrite; } } public SQSAdapter(Serializer serializer, IConsistentRingStreamQueueMapper streamQueueMapper, ILoggerFactory loggerFactory, string dataConnectionString, string serviceId, string providerName) { if (string.IsNullOrEmpty(dataConnectionString)) throw new ArgumentNullException(nameof(dataConnectionString)); if (string.IsNullOrEmpty(serviceId)) throw new ArgumentNullException(nameof(serviceId)); this.loggerFactory = loggerFactory; this.serializer = serializer; DataConnectionString = dataConnectionString; this.ServiceId = serviceId; Name = providerName; this.streamQueueMapper = streamQueueMapper; } public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { return SQSAdapterReceiver.Create(this.serializer, this.loggerFactory, queueId, DataConnectionString, this.ServiceId); } public async Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { if (token != null) { throw new ArgumentException("SQSStream stream provider currently does not support non-null StreamSequenceToken.", nameof(token)); } var queueId = streamQueueMapper.GetQueueForStream(streamId); SQSStorage queue; if (!Queues.TryGetValue(queueId, out queue)) { var tmpQueue = new SQSStorage(this.loggerFactory, queueId.ToString(), DataConnectionString, this.ServiceId); await tmpQueue.InitQueueAsync(); queue = Queues.GetOrAdd(queueId, tmpQueue); } var msg = SQSBatchContainer.ToSQSMessage(this.serializer, streamId, events, requestContext); await queue.AddMessage(msg); } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSAdapterFactory.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Providers.Streams.Common; using Orleans.Streams; using Orleans.Configuration; using Orleans; using Orleans.Configuration.Overrides; using Orleans.Serialization; namespace OrleansAWSUtils.Streams { /// Factory class for Azure Queue based stream provider. public class SQSAdapterFactory : IQueueAdapterFactory { private readonly string providerName; private readonly SqsOptions sqsOptions; private readonly ClusterOptions clusterOptions; private readonly Serializer serializer; private readonly ILoggerFactory loggerFactory; private readonly HashRingBasedStreamQueueMapper streamQueueMapper; private readonly IQueueAdapterCache adapterCache; /// /// Application level failure handler override. /// protected Func> StreamFailureHandlerFactory { private get; set; } public SQSAdapterFactory( string name, SqsOptions sqsOptions, HashRingStreamQueueMapperOptions queueMapperOptions, SimpleQueueCacheOptions cacheOptions, IOptions clusterOptions, Orleans.Serialization.Serializer serializer, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(serializer); this.providerName = name; this.sqsOptions = sqsOptions; this.clusterOptions = clusterOptions.Value; this.serializer = serializer.GetSerializer(); this.loggerFactory = loggerFactory; streamQueueMapper = new HashRingBasedStreamQueueMapper(queueMapperOptions, this.providerName); adapterCache = new SimpleQueueAdapterCache(cacheOptions, this.providerName, this.loggerFactory); } /// Init the factory. public virtual void Init() { if (StreamFailureHandlerFactory == null) { StreamFailureHandlerFactory = qid => Task.FromResult(new NoOpStreamDeliveryFailureHandler()); } } /// Creates the Azure Queue based adapter. public virtual Task CreateAdapter() { var adapter = new SQSAdapter(this.serializer, this.streamQueueMapper, this.loggerFactory, this.sqsOptions.ConnectionString, this.clusterOptions.ServiceId, this.providerName); return Task.FromResult(adapter); } /// Creates the adapter cache. public virtual IQueueAdapterCache GetQueueAdapterCache() { return adapterCache; } /// Creates the factory stream queue mapper. public IStreamQueueMapper GetStreamQueueMapper() { return streamQueueMapper; } /// /// Creates a delivery failure handler for the specified queue. /// /// /// public Task GetDeliveryFailureHandler(QueueId queueId) { return StreamFailureHandlerFactory(queueId); } public static SQSAdapterFactory Create(IServiceProvider services, string name) { var sqsOptions = services.GetOptionsByName(name); var cacheOptions = services.GetOptionsByName(name); var queueMapperOptions = services.GetOptionsByName(name); IOptions clusterOptions = services.GetProviderClusterOptions(name); var factory = ActivatorUtilities.CreateInstance(services, name, sqsOptions, cacheOptions, queueMapperOptions, clusterOptions); factory.Init(); return factory; } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSAdapterReceiver.cs ================================================ using Orleans; using Orleans.Streams; using OrleansAWSUtils.Storage; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Serialization; using SQSMessage = Amazon.SQS.Model.Message; namespace OrleansAWSUtils.Streams { /// /// Receives batches of messages from a single partition of a message queue. /// internal partial class SQSAdapterReceiver : IQueueAdapterReceiver { private SQSStorage queue; private long lastReadMessage; private Task outstandingTask; private readonly ILogger logger; private readonly Serializer serializer; public QueueId Id { get; private set; } public static IQueueAdapterReceiver Create(Serializer serializer, ILoggerFactory loggerFactory, QueueId queueId, string dataConnectionString, string serviceId) { if (queueId.IsDefault) throw new ArgumentNullException(nameof(queueId)); if (string.IsNullOrEmpty(dataConnectionString)) throw new ArgumentNullException(nameof(dataConnectionString)); if (string.IsNullOrEmpty(serviceId)) throw new ArgumentNullException(nameof(serviceId)); var queue = new SQSStorage(loggerFactory, queueId.ToString(), dataConnectionString, serviceId); return new SQSAdapterReceiver(serializer, loggerFactory, queueId, queue); } private SQSAdapterReceiver(Serializer serializer, ILoggerFactory loggerFactory, QueueId queueId, SQSStorage queue) { if (queueId.IsDefault) throw new ArgumentNullException(nameof(queueId)); if (queue == null) throw new ArgumentNullException(nameof(queue)); Id = queueId; this.queue = queue; logger = loggerFactory.CreateLogger(); this.serializer = serializer; } public Task Initialize(TimeSpan timeout) { if (queue != null) // check in case we already shut it down. { return queue.InitQueueAsync(); } return Task.CompletedTask; } public async Task Shutdown(TimeSpan timeout) { try { // await the last storage operation, so after we shutdown and stop this receiver we don't get async operation completions from pending storage operations. if (outstandingTask != null) await outstandingTask; } finally { // remember that we shut down so we never try to read from the queue again. queue = null; } } public async Task> GetQueueMessagesAsync(int maxCount) { try { var queueRef = queue; // store direct ref, in case we are somehow asked to shutdown while we are receiving. if (queueRef == null) return new List(); int count = maxCount < 0 || maxCount == QueueAdapterConstants.UNLIMITED_GET_QUEUE_MSG ? SQSStorage.MAX_NUMBER_OF_MESSAGE_TO_PEEK : Math.Min(maxCount, SQSStorage.MAX_NUMBER_OF_MESSAGE_TO_PEEK); var task = queueRef.GetMessages(count); outstandingTask = task; IEnumerable messages = await task; if (messages == null || !messages.Any()) return Array.Empty(); List messageBatch = messages .Select(msg => (IBatchContainer)SQSBatchContainer.FromSQSMessage(this.serializer, msg, lastReadMessage++)).ToList(); return messageBatch; } finally { outstandingTask = null; } } public async Task MessagesDeliveredAsync(IList messages) { try { var queueRef = queue; // store direct ref, in case we are somehow asked to shutdown while we are receiving. if (messages.Count == 0 || queueRef == null) return; List cloudQueueMessages = messages.Cast().Select(b => b.Message).ToList(); outstandingTask = Task.WhenAll(cloudQueueMessages.Select(queueRef.DeleteMessage)); try { await outstandingTask; } catch (Exception exc) { LogWarningDeleteMessageException(logger, exc, Id); } } finally { outstandingTask = null; } } [LoggerMessage( Level = LogLevel.Warning, Message = "Exception upon DeleteMessage on queue {Id}. Ignoring." )] private static partial void LogWarningDeleteMessageException(ILogger logger, Exception exception, QueueId id); } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSBatchContainer.cs ================================================ using Amazon.SQS.Model; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; using System; using System.Collections.Generic; using System.Linq; using SQSMessage = Amazon.SQS.Model.Message; namespace OrleansAWSUtils.Streams { [Serializable] [Orleans.GenerateSerializer] internal class SQSBatchContainer : IBatchContainer { [JsonProperty] [Orleans.Id(0)] private EventSequenceTokenV2 sequenceToken; [JsonProperty] [Orleans.Id(1)] private readonly List events; [JsonProperty] [Orleans.Id(2)] private readonly Dictionary requestContext; [NonSerialized] // Need to store reference to the original SQS Message to be able to delete it later on. // Don't need to serialize it, since we are never interested in sending it to stream consumers. internal SQSMessage Message; [Orleans.Id(3)] public StreamId StreamId { get; private set; } public StreamSequenceToken SequenceToken { get { return sequenceToken; } } [JsonConstructor] private SQSBatchContainer( StreamId streamId, List events, Dictionary requestContext, EventSequenceTokenV2 sequenceToken) : this(streamId, events, requestContext) { this.sequenceToken = sequenceToken; } private SQSBatchContainer(StreamId streamId, List events, Dictionary requestContext) { if (events == null) throw new ArgumentNullException(nameof(events), "Message contains no events"); StreamId = streamId; this.events = events; this.requestContext = requestContext; } public IEnumerable> GetEvents() { return events.OfType().Select((e, i) => Tuple.Create(e, sequenceToken.CreateSequenceTokenForEvent(i))); } internal static SendMessageRequest ToSQSMessage( Serializer serializer, StreamId streamId, IEnumerable events, Dictionary requestContext) { var sqsBatchMessage = new SQSBatchContainer(streamId, events.Cast().ToList(), requestContext); var rawBytes = serializer.SerializeToArray(sqsBatchMessage); var payload = new JObject { { "payload", JToken.FromObject(rawBytes) } }; return new SendMessageRequest { MessageBody = payload.ToString() }; } internal static SQSBatchContainer FromSQSMessage(Serializer serializer, SQSMessage msg, long sequenceId) { var json = JObject.Parse(msg.Body); var sqsBatch = serializer.Deserialize(json["payload"].ToObject()); sqsBatch.Message = msg; sqsBatch.sequenceToken = new EventSequenceTokenV2(sequenceId); return sqsBatch; } public bool ImportRequestContext() { if (requestContext != null) { RequestContextExtensions.Import(requestContext); return true; } return false; } public override string ToString() { return string.Format("[SQSBatchContainer:Stream={0},#Items={1}]", StreamId, events.Count); } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSStreamBuilder.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using OrleansAWSUtils.Streams; namespace Orleans.Hosting { public class SiloSqsStreamConfigurator : SiloPersistentStreamConfigurator { public SiloSqsStreamConfigurator(string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, SQSAdapterFactory.Create) { this.ConfigureDelegate(services => { services.ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name); }); } public SiloSqsStreamConfigurator ConfigureSqs(Action> configureOptions) { this.Configure(configureOptions); return this; } public SiloSqsStreamConfigurator ConfigureCache(int cacheSize = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE) { this.Configure(ob => ob.Configure(options => options.CacheSize = cacheSize)); return this; } public SiloSqsStreamConfigurator ConfigurePartitioning(int numOfparitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = numOfparitions)); return this; } } public class ClusterClientSqsStreamConfigurator : ClusterClientPersistentStreamConfigurator { public ClusterClientSqsStreamConfigurator(string name, IClientBuilder builder) : base(name, builder, SQSAdapterFactory.Create) { builder .ConfigureServices(services => { services.ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name); }); } public ClusterClientSqsStreamConfigurator ConfigureSqs(Action> configureOptions) { this.Configure(configureOptions); return this; } public ClusterClientSqsStreamConfigurator ConfigurePartitioning(int numOfparitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = numOfparitions)); return this; } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SQSStreamProviderUtils.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Streams; using OrleansAWSUtils.Storage; using Orleans.Configuration; namespace OrleansAWSUtils.Streams { /// /// SQS utility functions /// public class SQSStreamProviderUtils { /// /// Async method to delete all used queues, for specific provider and clusterId /// /// Task object for this async method public static async Task DeleteAllUsedQueues(string providerName, string clusterId, string storageConnectionString, ILoggerFactory loggerFactory) { if (clusterId != null) { var queueMapper = new HashRingBasedStreamQueueMapper(new HashRingStreamQueueMapperOptions(), providerName); List allQueues = queueMapper.GetAllQueues().ToList(); var deleteTasks = new List(); foreach (var queueId in allQueues) { var manager = new SQSStorage(loggerFactory, queueId.ToString(), storageConnectionString, clusterId); manager.InitQueueAsync().Wait(); deleteTasks.Add(manager.DeleteQueue()); } await Task.WhenAll(deleteTasks); } } } } ================================================ FILE: src/AWS/Orleans.Streaming.SQS/Streams/SqsStreamOptions.cs ================================================  namespace Orleans.Configuration { public class SqsOptions { [Redact] public string ConnectionString { get; set; } } } ================================================ FILE: src/AWS/Shared/AWSUtils.cs ================================================ using Amazon; using System; #if CLUSTERING_DYNAMODB namespace Orleans.Clustering.DynamoDB #elif PERSISTENCE_DYNAMODB namespace Orleans.Persistence.DynamoDB #elif REMINDERS_DYNAMODB namespace Orleans.Reminders.DynamoDB #elif STREAMING_SQS namespace Orleans.Streaming.SQS #elif AWSUTILS_TESTS namespace Orleans.AWSUtils.Tests #elif TRANSACTIONS_DYNAMODB namespace Orleans.Transactions.DynamoDB #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Some basic utilities methods for AWS SDK /// internal static class AWSUtils { internal static RegionEndpoint GetRegionEndpoint(string zone = "") { // // Keep the order from RegionEndpoint so it is easier to maintain. // us-west-2 is the default // return RegionEndpoint.GetBySystemName(zone) ?? RegionEndpoint.USWest2; } /// /// Validate DynamoDB PartitionKey. /// /// /// public static string ValidateDynamoDBPartitionKey(string key) { if (key.Length >= 2048) throw new ArgumentException(string.Format("Key length {0} is too long to be an DynamoDB partition key. Key={1}", key.Length, key)); return key; } /// /// Validate DynamoDB RowKey. /// /// /// public static string ValidateDynamoDBRowKey(string key) { if (key.Length >= 1024) throw new ArgumentException(string.Format("Key length {0} is too long to be an DynamoDB row key. Key={1}", key.Length, key)); return key; } } } ================================================ FILE: src/AWS/Shared/Storage/DynamoDBClientOptions.cs ================================================ #if CLUSTERING_DYNAMODB namespace Orleans.Clustering.DynamoDB #elif PERSISTENCE_DYNAMODB namespace Orleans.Persistence.DynamoDB #elif REMINDERS_DYNAMODB namespace Orleans.Reminders.DynamoDB #else // No default namespace intentionally to cause compile errors if something is not defined #endif { public class DynamoDBClientOptions { /// /// AccessKey string for DynamoDB Storage /// [Redact] public string AccessKey { get; set; } /// /// Secret key for DynamoDB storage /// [Redact] public string SecretKey { get; set; } /// /// DynamoDB region name, such as "us-west-2" /// public string Service { get; set; } /// /// Token for DynamoDB storage /// public string Token { get; set; } /// /// AWS profile name. /// public string ProfileName { get; set; } } } ================================================ FILE: src/AWS/Shared/Storage/DynamoDBStorage.cs ================================================ using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Amazon.Runtime; using Microsoft.Extensions.Logging; using Orleans.Runtime; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using Amazon.Runtime.CredentialManagement; #if CLUSTERING_DYNAMODB namespace Orleans.Clustering.DynamoDB #elif PERSISTENCE_DYNAMODB namespace Orleans.Persistence.DynamoDB #elif REMINDERS_DYNAMODB namespace Orleans.Reminders.DynamoDB #elif AWSUTILS_TESTS namespace Orleans.AWSUtils.Tests #elif TRANSACTIONS_DYNAMODB namespace Orleans.Transactions.DynamoDB #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Wrapper around AWS DynamoDB SDK. /// internal partial class DynamoDBStorage { private readonly string _accessKey; private readonly string _token; private readonly string _profileName; /// Secret key for this dynamoDB table protected string secretKey; private readonly string _service; public const int DefaultReadCapacityUnits = 10; public const int DefaultWriteCapacityUnits = 5; private readonly ProvisionedThroughput _provisionedThroughput; private readonly bool _createIfNotExists; private readonly bool _updateIfExists; private readonly bool _useProvisionedThroughput; private readonly ReadOnlyCollection _updateTableValidTableStatuses = new ReadOnlyCollection(new List() { TableStatus.CREATING, TableStatus.UPDATING, TableStatus.ACTIVE }); private AmazonDynamoDBClient _ddbClient; private readonly ILogger _logger; /// /// Create a DynamoDBStorage instance /// /// /// /// /// /// /// /// /// /// /// /// public DynamoDBStorage( ILogger logger, string service, string accessKey = "", string secretKey = "", string token = "", string profileName = "", int readCapacityUnits = DefaultReadCapacityUnits, int writeCapacityUnits = DefaultWriteCapacityUnits, bool useProvisionedThroughput = true, bool createIfNotExists = true, bool updateIfExists = true) { if (service == null) throw new ArgumentNullException(nameof(service)); this._accessKey = accessKey; this.secretKey = secretKey; this._token = token; this._profileName = profileName; this._service = service; this._useProvisionedThroughput = useProvisionedThroughput; this._provisionedThroughput = this._useProvisionedThroughput ? new ProvisionedThroughput(readCapacityUnits, writeCapacityUnits) : null; this._createIfNotExists = createIfNotExists; this._updateIfExists = updateIfExists; _logger = logger; CreateClient(); } /// /// Create a DynamoDB table if it doesn't exist /// /// The name of the table /// The keys definitions /// The attributes used on the key definition /// (optional) The secondary index definitions /// (optional) The name of the item attribute that indicates the item TTL (if null, ttl won't be enabled) /// public async Task InitializeTable(string tableName, List keys, List attributes, List secondaryIndexes = null, string ttlAttributeName = null) { if (!this._createIfNotExists && !this._updateIfExists) { LogInformationTableNotCreatedOrUpdated(_logger, tableName); return; } try { TableDescription tableDescription = await GetTableDescription(tableName); await (tableDescription == null ? CreateTableAsync(tableName, keys, attributes, secondaryIndexes, ttlAttributeName) : UpdateTableAsync(tableDescription, attributes, secondaryIndexes, ttlAttributeName)); } catch (Exception exc) { LogErrorCouldNotInitializeTable(_logger, exc, tableName); throw; } } private void CreateClient() { if (this._service.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || this._service.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { // Local DynamoDB instance (for testing) var credentials = new BasicAWSCredentials("dummy", "dummyKey"); this._ddbClient = new AmazonDynamoDBClient(credentials, new AmazonDynamoDBConfig { ServiceURL = this._service }); } else if (!string.IsNullOrEmpty(this._accessKey) && !string.IsNullOrEmpty(this.secretKey) && !string.IsNullOrEmpty(this._token)) { // AWS DynamoDB instance (auth via explicit credentials and token) var credentials = new SessionAWSCredentials(this._accessKey, this.secretKey, this._token); this._ddbClient = new AmazonDynamoDBClient(credentials, new AmazonDynamoDBConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(this._service) }); } else if (!string.IsNullOrEmpty(this._accessKey) && !string.IsNullOrEmpty(this.secretKey)) { // AWS DynamoDB instance (auth via explicit credentials) var credentials = new BasicAWSCredentials(this._accessKey, this.secretKey); this._ddbClient = new AmazonDynamoDBClient(credentials, new AmazonDynamoDBConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(this._service) }); } else if (!string.IsNullOrEmpty(this._profileName)) { // AWS DynamoDB instance (auth via explicit credentials and token found in a named profile) var chain = new CredentialProfileStoreChain(); if (chain.TryGetAWSCredentials(this._profileName, out var credentials)) { this._ddbClient = new AmazonDynamoDBClient( credentials, new AmazonDynamoDBConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(this._service) }); } else { throw new InvalidOperationException( $"AWS named profile '{this._profileName}' provided, but credentials could not be retrieved"); } } else { // AWS DynamoDB instance (implicit auth - EC2 IAM Roles etc) this._ddbClient = new AmazonDynamoDBClient(new AmazonDynamoDBConfig { RegionEndpoint = AWSUtils.GetRegionEndpoint(this._service) }); } } private async Task GetTableDescription(string tableName) { try { var description = await _ddbClient.DescribeTableAsync(tableName); if (description.Table != null) return description.Table; } catch (ResourceNotFoundException) { return null; } return null; } private async ValueTask CreateTableAsync(string tableName, List keys, List attributes, List secondaryIndexes = null, string ttlAttributeName = null) { if (!_createIfNotExists) { LogWarningTableNotCreated(_logger, tableName); return; } var request = new CreateTableRequest { TableName = tableName, AttributeDefinitions = attributes, KeySchema = keys, BillingMode = this._useProvisionedThroughput ? BillingMode.PROVISIONED : BillingMode.PAY_PER_REQUEST, ProvisionedThroughput = _provisionedThroughput }; if (secondaryIndexes != null && secondaryIndexes.Count > 0) { if (this._useProvisionedThroughput) { secondaryIndexes.ForEach(i => { i.ProvisionedThroughput = _provisionedThroughput; }); } request.GlobalSecondaryIndexes = secondaryIndexes; } try { try { await _ddbClient.CreateTableAsync(request); } catch (ResourceInUseException) { // The table has already been created. } TableDescription tableDescription = await TableWaitOnStatusAsync(tableName, TableStatus.CREATING, TableStatus.ACTIVE); tableDescription = await TableUpdateTtlAsync(tableDescription, ttlAttributeName); } catch (Exception exc) { LogErrorCouldNotCreateTable(_logger, exc, tableName); throw; } } private async ValueTask UpdateTableAsync(TableDescription tableDescription, List attributes, List secondaryIndexes = null, string ttlAttributeName = null) { if (!this._updateIfExists) { LogWarningTableNotUpdated(_logger, tableDescription.TableName); return; } if (!_updateTableValidTableStatuses.Contains(tableDescription.TableStatus)) { throw new InvalidOperationException($"Table {tableDescription.TableName} has a status of {tableDescription.TableStatus} and can't be updated automatically."); } if (tableDescription.TableStatus == TableStatus.CREATING || tableDescription.TableStatus == TableStatus.UPDATING) { tableDescription = await TableWaitOnStatusAsync(tableDescription.TableName, tableDescription.TableStatus, TableStatus.ACTIVE); } var request = new UpdateTableRequest { TableName = tableDescription.TableName, AttributeDefinitions = attributes, BillingMode = this._useProvisionedThroughput ? BillingMode.PROVISIONED : BillingMode.PAY_PER_REQUEST, ProvisionedThroughput = _provisionedThroughput, GlobalSecondaryIndexUpdates = this._useProvisionedThroughput ? tableDescription.GlobalSecondaryIndexes?.Select(gsi => new GlobalSecondaryIndexUpdate { Update = new UpdateGlobalSecondaryIndexAction { IndexName = gsi.IndexName, ProvisionedThroughput = _provisionedThroughput } }).ToList() : null }; try { if ((request.ProvisionedThroughput?.ReadCapacityUnits ?? 0) != tableDescription.ProvisionedThroughput?.ReadCapacityUnits // PROVISIONED Throughput read capacity change || (request.ProvisionedThroughput?.WriteCapacityUnits ?? 0) != tableDescription.ProvisionedThroughput?.WriteCapacityUnits // PROVISIONED Throughput write capacity change || (tableDescription.ProvisionedThroughput?.ReadCapacityUnits != 0 && tableDescription.ProvisionedThroughput?.WriteCapacityUnits != 0 && this._useProvisionedThroughput == false /* from PROVISIONED to PAY_PER_REQUEST */)) { await _ddbClient.UpdateTableAsync(request); tableDescription = await TableWaitOnStatusAsync(tableDescription.TableName, TableStatus.UPDATING, TableStatus.ACTIVE); } tableDescription = await TableUpdateTtlAsync(tableDescription, ttlAttributeName); // Wait for all table indexes to become ACTIVE. // We can only have one GSI in CREATING state at one time. // We also wait for all indexes to finish UPDATING as the table is not ready to receive queries from Orleans until all indexes are created. List globalSecondaryIndexes = tableDescription.GlobalSecondaryIndexes; if (globalSecondaryIndexes != null) { foreach (var globalSecondaryIndex in globalSecondaryIndexes) { if (globalSecondaryIndex.IndexStatus == IndexStatus.CREATING || globalSecondaryIndex.IndexStatus == IndexStatus.UPDATING) { tableDescription = await TableIndexWaitOnStatusAsync(tableDescription.TableName, globalSecondaryIndex.IndexName, globalSecondaryIndex.IndexStatus, IndexStatus.ACTIVE); } } } var existingGlobalSecondaryIndexes = tableDescription.GlobalSecondaryIndexes?.Select(globalSecondaryIndex => globalSecondaryIndex.IndexName).ToArray() ?? Array.Empty(); var secondaryIndexesToCreate = (secondaryIndexes ?? Enumerable.Empty()).Where(secondaryIndex => !existingGlobalSecondaryIndexes.Contains(secondaryIndex.IndexName)); foreach (var secondaryIndex in secondaryIndexesToCreate) { await TableCreateSecondaryIndex(tableDescription.TableName, attributes, secondaryIndex); } } catch (Exception exc) { LogErrorCouldNotUpdateTable(_logger, exc, tableDescription.TableName); throw; } } private async Task TableCreateSecondaryIndex(string tableName, List attributes, GlobalSecondaryIndex secondaryIndex) { await _ddbClient.UpdateTableAsync(new UpdateTableRequest { TableName = tableName, GlobalSecondaryIndexUpdates = new List { new GlobalSecondaryIndexUpdate { Create = new CreateGlobalSecondaryIndexAction() { IndexName = secondaryIndex.IndexName, Projection = secondaryIndex.Projection, ProvisionedThroughput = _provisionedThroughput, KeySchema = secondaryIndex.KeySchema } } }, AttributeDefinitions = attributes }); // Adding a GSI to a table is an eventually consistent operation and we might miss the table UPDATING status if we query the table status imediatelly after the table update call. // Creating a GSI takes significantly longer than 1 second and therefore this delay does not add time to the total duration of this method. await Task.Delay(1000); // When adding a GSI, the table briefly changes its status to UPDATING. The GSI creation process usually takes longer. // For this reason, we will wait for both the table and the index to become ACTIVE before marking the operation as complete. await TableWaitOnStatusAsync(tableName, TableStatus.UPDATING, TableStatus.ACTIVE); await TableIndexWaitOnStatusAsync(tableName, secondaryIndex.IndexName, IndexStatus.CREATING, IndexStatus.ACTIVE); } private async ValueTask TableUpdateTtlAsync(TableDescription tableDescription, string ttlAttributeName) { var describeTimeToLive = (await _ddbClient.DescribeTimeToLiveAsync(tableDescription.TableName)).TimeToLiveDescription; // We can only handle updates to the table TTL from DISABLED to ENABLED. // This is because updating the TTL attribute requires (1) disabling the table TTL and (2) re-enabling it with the new TTL attribute. // As per the below details page for this API: "It can take up to one hour for the change to fully process. Any additional UpdateTimeToLive calls for the same table during this one hour duration result in a ValidationException." // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTimeToLive.html // However if the TTL is already set on the correct attribute, the attribute value is updated every time a record is written, thus the configuration is already correct and warning doesn't need to be logged. if (describeTimeToLive.TimeToLiveStatus != TimeToLiveStatus.DISABLED && describeTimeToLive.AttributeName != ttlAttributeName) { LogWarningTtlNotDisabled(_logger, tableDescription.TableName); return tableDescription; } if (string.IsNullOrEmpty(ttlAttributeName) || describeTimeToLive.AttributeName == ttlAttributeName) { return tableDescription; } try { await _ddbClient.UpdateTimeToLiveAsync(new UpdateTimeToLiveRequest { TableName = tableDescription.TableName, TimeToLiveSpecification = new TimeToLiveSpecification { AttributeName = ttlAttributeName, Enabled = true } }); return await TableWaitOnStatusAsync(tableDescription.TableName, TableStatus.UPDATING, TableStatus.ACTIVE); } catch (AmazonDynamoDBException ddbEx) { // We need to swallow this exception as there is no API exposed to determine if the below issue will occur before calling UpdateTimeToLive(Async) // "Time to live has been modified multiple times within a fixed interval". // We can arrive at this situation if the TTL feature was recently disabled on the target table. LogErrorUpdateTtlException(_logger, ddbEx, tableDescription.TableName, ttlAttributeName); return tableDescription; } } private async Task TableWaitOnStatusAsync(string tableName, TableStatus whileStatus, TableStatus desiredStatus, int delay = 2000) { TableDescription ret = null; do { if (ret != null) { await Task.Delay(delay); } ret = await GetTableDescription(tableName); } while (ret.TableStatus == whileStatus); if (ret.TableStatus != desiredStatus) { throw new InvalidOperationException($"Table {tableName} has failed to reach the desired status of {desiredStatus}"); } return ret; } private async Task TableIndexWaitOnStatusAsync(string tableName, string indexName, IndexStatus whileStatus, IndexStatus desiredStatus = null, int delay = 2000) { TableDescription ret; GlobalSecondaryIndexDescription index = null; do { if (index != null) { await Task.Delay(delay); } ret = await GetTableDescription(tableName); index = ret.GlobalSecondaryIndexes?.Find(index => index.IndexName == indexName); } while (index != null && index.IndexStatus == whileStatus); if (desiredStatus != null && (index == null || index.IndexStatus != desiredStatus)) { throw new InvalidOperationException($"Index {indexName} in table {tableName} has failed to reach the desired status of {desiredStatus}"); } return ret; } /// /// Delete a table from DynamoDB /// /// The name of the table to delete /// public Task DeleTableAsync(string tableName) { try { return _ddbClient.DeleteTableAsync(new DeleteTableRequest { TableName = tableName }); } catch (Exception exc) { LogErrorCouldNotDeleteTable(_logger, exc, tableName); throw; } } /// /// Create or Replace an entry in a DynamoDB Table /// /// The name of the table to put an entry /// The fields/attributes to add or replace in the table /// Optional conditional expression /// Optional field/attribute values used in the conditional expression /// public Task PutEntryAsync(string tableName, Dictionary fields, string conditionExpression = "", Dictionary conditionValues = null) { LogTraceCreatingTableEntry(_logger, tableName, new(fields)); try { var request = new PutItemRequest(tableName, fields, ReturnValue.NONE); if (!string.IsNullOrWhiteSpace(conditionExpression)) request.ConditionExpression = conditionExpression; if (conditionValues != null && conditionValues.Keys.Count > 0) request.ExpressionAttributeValues = conditionValues; return _ddbClient.PutItemAsync(request); } catch (Exception exc) { LogErrorUnableToCreateItem(_logger, exc, tableName); throw; } } /// /// Create or update an entry in a DynamoDB Table /// /// The name of the table to upsert an entry /// The table entry keys for the entry /// The fields/attributes to add or updated in the table /// Optional conditional expression /// Optional field/attribute values used in the conditional expression /// Additional expression that will be added in the end of the upsert expression /// Additional field/attribute that will be used in the extraExpression /// The fields dictionary item values will be updated with the values returned from DynamoDB /// public async Task UpsertEntryAsync(string tableName, Dictionary keys, Dictionary fields, string conditionExpression = "", Dictionary conditionValues = null, string extraExpression = "", Dictionary extraExpressionValues = null) { LogTraceUpsertingEntry(_logger, new(fields), new(keys), tableName); try { var request = new UpdateItemRequest { TableName = tableName, Key = keys, ReturnValues = ReturnValue.UPDATED_NEW }; (request.UpdateExpression, request.ExpressionAttributeValues) = ConvertUpdate(fields, conditionValues, extraExpression, extraExpressionValues); if (!string.IsNullOrWhiteSpace(conditionExpression)) request.ConditionExpression = conditionExpression; var result = await _ddbClient.UpdateItemAsync(request); foreach (var key in result.Attributes.Keys) { if (fields.ContainsKey(key)) { fields[key] = result.Attributes[key]; } else { fields.Add(key, result.Attributes[key]); } } } catch (Exception exc) { LogWarningIntermediateUpsert(_logger, exc, tableName); throw; } } public (string updateExpression, Dictionary expressionAttributeValues) ConvertUpdate(Dictionary fields, Dictionary conditionValues = null, string extraExpression = "", Dictionary extraExpressionValues = null) { var expressionAttributeValues = new Dictionary(); var updateExpression = new StringBuilder(); foreach (var field in fields.Keys) { var valueKey = ":" + field; expressionAttributeValues.Add(valueKey, fields[field]); updateExpression.Append($" {field} = {valueKey},"); } updateExpression.Insert(0, "SET"); if (string.IsNullOrWhiteSpace(extraExpression)) { updateExpression.Remove(updateExpression.Length - 1, 1); } else { updateExpression.Append($" {extraExpression}"); if (extraExpressionValues != null && extraExpressionValues.Count > 0) { foreach (var key in extraExpressionValues.Keys) { expressionAttributeValues.Add(key, extraExpressionValues[key]); } } } if (conditionValues != null && conditionValues.Keys.Count > 0) { foreach (var item in conditionValues) { expressionAttributeValues.Add(item.Key, item.Value); } } return (updateExpression.ToString(), expressionAttributeValues); } /// /// Delete an entry from a DynamoDB table /// /// The name of the table to delete an entry /// The table entry keys for the entry to be deleted /// Optional conditional expression /// Optional field/attribute values used in the conditional expression /// public Task DeleteEntryAsync(string tableName, Dictionary keys, string conditionExpression = "", Dictionary conditionValues = null) { LogTraceDeletingTableEntry(_logger, tableName, new(keys)); try { var request = new DeleteItemRequest { TableName = tableName, Key = keys }; if (!string.IsNullOrWhiteSpace(conditionExpression)) request.ConditionExpression = conditionExpression; if (conditionValues != null && conditionValues.Keys.Count > 0) request.ExpressionAttributeValues = conditionValues; return _ddbClient.DeleteItemAsync(request); } catch (Exception exc) { LogWarningIntermediateDelete(_logger, exc, tableName); throw; } } /// /// Delete multiple entries from a DynamoDB table (Batch delete) /// /// The name of the table to delete entries /// List of key values for each entry that must be deleted in the batch /// public Task DeleteEntriesAsync(string tableName, IReadOnlyCollection> toDelete) { LogTraceDeletingTableEntries(_logger, tableName); if (toDelete == null) throw new ArgumentNullException(nameof(toDelete)); if (toDelete.Count == 0) return Task.CompletedTask; try { var request = new BatchWriteItemRequest(); request.RequestItems = new Dictionary>(); var batch = new List(); foreach (var keys in toDelete) { var writeRequest = new WriteRequest(); writeRequest.DeleteRequest = new DeleteRequest(); writeRequest.DeleteRequest.Key = keys; batch.Add(writeRequest); } request.RequestItems.Add(tableName, batch); return _ddbClient.BatchWriteItemAsync(request); } catch (Exception exc) { LogWarningIntermediateDeleteEntries(_logger, exc, tableName); throw; } } /// /// Read an entry from a DynamoDB table /// /// The result type /// The name of the table to search for the entry /// The table entry keys to search for /// Function that will be called to translate the returned fields into a concrete type. This Function is only called if the result is != null /// The object translated by the resolver function public async Task ReadSingleEntryAsync(string tableName, Dictionary keys, Func, TResult> resolver) where TResult : class { try { var request = new GetItemRequest { TableName = tableName, Key = keys, ConsistentRead = true }; var response = await _ddbClient.GetItemAsync(request); if (response.IsItemSet) { return resolver(response.Item); } else { return null; } } catch (Exception) { LogDebugUnableToFindTableEntry(_logger, new(keys)); throw; } } /// /// Query for multiple entries in a DynamoDB table by filtering its keys /// /// The result type /// The name of the table to search for the entries /// The table entry keys to search for /// the expression that will filter the keys /// Function that will be called to translate the returned fields into a concrete type. This Function is only called if the result is != null and will be called for each entry that match the query and added to the results list /// In case a secondary index is used in the keyConditionExpression /// In case an index is used, show if the seek order is ascending (true) or descending (false) /// The primary key of the first item that this operation will evaluate. Use the value that was returned for LastEvaluatedKey in the previous operation /// Determines the read consistency model. Note that if a GSI is used, this must be false. /// The collection containing a list of objects translated by the resolver function and the LastEvaluatedKey for paged results public async Task<(List results, Dictionary lastEvaluatedKey)> QueryAsync(string tableName, Dictionary keys, string keyConditionExpression, Func, TResult> resolver, string indexName = "", bool scanIndexForward = true, Dictionary lastEvaluatedKey = null, bool consistentRead = true) where TResult : class { try { var request = new QueryRequest { TableName = tableName, ExpressionAttributeValues = keys, ConsistentRead = consistentRead, KeyConditionExpression = keyConditionExpression, Select = Select.ALL_ATTRIBUTES }; if (lastEvaluatedKey != null && lastEvaluatedKey.Count > 0) { request.ExclusiveStartKey = lastEvaluatedKey; } if (!string.IsNullOrWhiteSpace(indexName)) { request.ScanIndexForward = scanIndexForward; request.IndexName = indexName; } var response = await _ddbClient.QueryAsync(request); var resultList = new List(); foreach (var item in response.Items) { resultList.Add(resolver(item)); } return (resultList, response.LastEvaluatedKey); } catch (Exception) { LogDebugUnableToFindTableEntry(_logger, new(keys)); throw; } } /// /// Query for multiple entries in a DynamoDB table by filtering its keys /// /// The result type /// The name of the table to search for the entries /// The table entry keys to search for /// the expression that will filter the keys /// Function that will be called to translate the returned fields into a concrete type. This Function is only called if the result is != null and will be called for each entry that match the query and added to the results list /// In case a secondary index is used in the keyConditionExpression /// In case an index is used, show if the seek order is ascending (true) or descending (false) /// Determines the read consistency model. Note that if a GSI is used, this must be false. /// The collection containing a list of objects translated by the resolver function public async Task> QueryAllAsync(string tableName, Dictionary keys, string keyConditionExpression, Func, TResult> resolver, string indexName = "", bool scanIndexForward = true, bool consistentRead = true) where TResult : class { List resultList = null; Dictionary lastEvaluatedKey = null; do { List results; (results, lastEvaluatedKey) = await QueryAsync(tableName, keys, keyConditionExpression, resolver, indexName, scanIndexForward, lastEvaluatedKey, consistentRead); if (resultList == null) { resultList = results; } else { resultList.AddRange(results); } } while (lastEvaluatedKey != null && lastEvaluatedKey.Count != 0); return resultList; } /// /// Scan a DynamoDB table by querying the entry fields. /// /// The result type /// The name of the table to search for the entries /// The attributes used on the expression /// The filter expression /// Function that will be called to translate the returned fields into a concrete type. This Function is only called if the result is != null and will be called for each entry that match the query and added to the results list /// The collection containing a list of objects translated by the resolver function public async Task> ScanAsync(string tableName, Dictionary attributes, string expression, Func, TResult> resolver) where TResult : class { // From the Amazon documentation: // "A single Scan operation will read up to the maximum number of items set // (if using the Limit parameter) or a maximum of 1 MB of data and then apply // any filtering to the results using FilterExpression." // https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/DynamoDBv2/MDynamoDBScanAsyncStringDictionary!String,%20Condition!CancellationToken.html try { var resultList = new List(); Dictionary exclusiveStartKey = null; while (true) { var request = new ScanRequest { TableName = tableName, ConsistentRead = true, FilterExpression = expression, ExpressionAttributeValues = attributes, Select = Select.ALL_ATTRIBUTES }; if (exclusiveStartKey is not null && exclusiveStartKey.Count > 0) { request.ExclusiveStartKey = exclusiveStartKey; } var response = await _ddbClient.ScanAsync(request); if (response.Items != null) { foreach (var item in response.Items) { resultList.Add(resolver(item)); } } if (response.LastEvaluatedKey == null || response.LastEvaluatedKey.Count == 0) { break; } else { exclusiveStartKey = response.LastEvaluatedKey; } } return resultList; } catch (Exception exc) { LogWarningFailedToReadTable(_logger, exc, tableName); throw new OrleansException($"Failed to read table {tableName}: {exc.Message}", exc); } } /// /// Crete or replace multiple entries in a DynamoDB table (Batch put) /// /// The name of the table to search for the entry /// List of key values for each entry that must be created or replaced in the batch /// public Task PutEntriesAsync(string tableName, IReadOnlyCollection> toCreate) { LogTracePutEntries(_logger, tableName); if (toCreate == null) throw new ArgumentNullException(nameof(toCreate)); if (toCreate.Count == 0) return Task.CompletedTask; try { var request = new BatchWriteItemRequest(); request.RequestItems = new Dictionary>(); var batch = new List(); foreach (var item in toCreate) { var writeRequest = new WriteRequest(); writeRequest.PutRequest = new PutRequest(); writeRequest.PutRequest.Item = item; batch.Add(writeRequest); } request.RequestItems.Add(tableName, batch); return _ddbClient.BatchWriteItemAsync(request); } catch (Exception exc) { LogWarningIntermediateBulkInsert(_logger, exc, tableName); throw; } } /// /// Transactionally reads entries from a DynamoDB table /// /// The result type /// The name of the table to search for the entry /// The table entry keys to search for /// Function that will be called to translate the returned fields into a concrete type. This Function is only called if the result is != null /// The object translated by the resolver function public async Task> GetEntriesTxAsync(string tableName, IEnumerable> keys, Func, TResult> resolver) where TResult : class { try { var request = new TransactGetItemsRequest { TransactItems = keys.Select(key => new TransactGetItem { Get = new Get { TableName = tableName, Key = key } }).ToList() }; var response = await _ddbClient.TransactGetItemsAsync(request); return response.Responses.Where(r => r?.Item?.Count > 0).Select(r => resolver(r.Item)); } catch (Exception) { LogDebugUnableToFindTableEntries(_logger, new(keys)); throw; } } /// /// Transactionally performs write requests /// /// Any puts to be performed /// Any updated to be performed /// Any deletes to be performed /// Any condition checks to be performed /// public Task WriteTxAsync(IEnumerable puts = null, IEnumerable updates = null, IEnumerable deletes = null, IEnumerable conditionChecks = null) { try { var transactItems = new List(); if (puts != null) { transactItems.AddRange(puts.Select(p => new TransactWriteItem { Put = p })); } if (updates != null) { transactItems.AddRange(updates.Select(u => new TransactWriteItem { Update = u })); } if (deletes != null) { transactItems.AddRange(deletes.Select(d => new TransactWriteItem { Delete = d })); } if (conditionChecks != null) { transactItems.AddRange(conditionChecks.Select(c => new TransactWriteItem { ConditionCheck = c })); } var request = new TransactWriteItemsRequest { TransactItems = transactItems }; return _ddbClient.TransactWriteItemsAsync(request); } catch (Exception exc) { LogDebugUnableToWrite(_logger, exc); throw; } } private readonly struct DictionaryLogRecord(Dictionary dictionary) { public override string ToString() => Utils.DictionaryToString(dictionary); } [LoggerMessage( Level = LogLevel.Information, Message = "The config values for 'createIfNotExists' and 'updateIfExists' are false. The table '{TableName}' will not be created or updated." )] private static partial void LogInformationTableNotCreatedOrUpdated(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Could not initialize connection to storage table {TableName}" )] private static partial void LogErrorCouldNotInitializeTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "The config value 'createIfNotExists' is false. The table '{TableName}' does not exist and it will not get created." )] private static partial void LogWarningTableNotCreated(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Could not create table {TableName}" )] private static partial void LogErrorCouldNotCreateTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "The config value 'updateIfExists' is false. The table structure for table '{TableName}' will not be updated." )] private static partial void LogWarningTableNotUpdated(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Could not update table {TableName}" )] private static partial void LogErrorCouldNotUpdateTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "TTL is not DISABLED. Cannot update table TTL for table {TableName}. Please update manually." )] private static partial void LogWarningTtlNotDisabled(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Exception occured while updating table {TableName} TTL attribute to {TtlAttributeName}. Please update manually." )] private static partial void LogErrorUpdateTtlException(ILogger logger, Exception exception, string tableName, string ttlAttributeName); [LoggerMessage( Level = LogLevel.Error, Message = "Could not delete table {TableName}" )] private static partial void LogErrorCouldNotDeleteTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Creating {TableName} table entry: {TableEntry}" )] private static partial void LogTraceCreatingTableEntry(ILogger logger, string tableName, DictionaryLogRecord tableEntry); [LoggerMessage( Level = LogLevel.Error, Message = "Unable to create item to table {TableName}" )] private static partial void LogErrorUnableToCreateItem(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Upserting entry {Entry} with key(s) {Keys} into table {TableName}" )] private static partial void LogTraceUpsertingEntry(ILogger logger, DictionaryLogRecord entry, DictionaryLogRecord keys, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error upserting to the table {TableName}" )] private static partial void LogWarningIntermediateUpsert(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Deleting table {TableName} entry with key(s) {Keys}" )] private static partial void LogTraceDeletingTableEntry(ILogger logger, string tableName, DictionaryLogRecord keys); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error deleting entry from the table {TableName}." )] private static partial void LogWarningIntermediateDelete(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Deleting {TableName} table entries" )] private static partial void LogTraceDeletingTableEntries(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error deleting entries from the table {TableName}." )] private static partial void LogWarningIntermediateDeleteEntries(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Put entries {TableName} table" )] private static partial void LogTracePutEntries(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error bulk inserting entries to table {TableName}." )] private static partial void LogWarningIntermediateBulkInsert(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "Unable to find table entry for Keys = {Keys}" )] private static partial void LogDebugUnableToFindTableEntry(ILogger logger, DictionaryLogRecord keys); private readonly struct DictionariesLogRecord(IEnumerable> keys) { public override string ToString() => Utils.EnumerableToString(keys, d => Utils.DictionaryToString(d)); } [LoggerMessage( Level = LogLevel.Debug, Message = "Unable to find table entry for Keys = {Keys}" )] private static partial void LogDebugUnableToFindTableEntries(ILogger logger, DictionariesLogRecord keys); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to read table {TableName}" )] private static partial void LogWarningFailedToReadTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "Unable to write" )] private static partial void LogDebugUnableToWrite(ILogger logger, Exception exception); } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/AdoNetClusteringProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AdoNet", "Clustering", "Silo", typeof(AdoNetClusteringProviderBuilder))] [assembly: RegisterProvider("AdoNet", "Clustering", "Client", typeof(AdoNetClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class AdoNetClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAdoNetClustering((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var invariant = configurationSection[nameof(options.Invariant)]; if (!string.IsNullOrEmpty(invariant)) { options.Invariant = invariant; } var connectionString = configurationSection[nameof(options.ConnectionString)]; var connectionName = configurationSection["ConnectionName"]; if (string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(connectionName)) { connectionString = services.GetRequiredService().GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConnectionString = connectionString; } })); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAdoNetClustering((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var invariant = configurationSection[nameof(options.Invariant)]; if (!string.IsNullOrEmpty(invariant)) { options.Invariant = invariant; } var connectionString = configurationSection[nameof(options.ConnectionString)]; var connectionName = configurationSection["ConnectionName"]; if (string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(connectionName)) { connectionString = services.GetRequiredService().GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConnectionString = connectionString; } })); } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/AdoNetHostingExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Messaging; using Orleans.Runtime.Membership; using Orleans.Runtime.MembershipService; using Orleans.Configuration; namespace Orleans.Hosting { /// /// Extensions for configuring ADO.NET for clustering. /// public static class AdoNetHostingExtensions { /// /// Configures this silo to use ADO.NET for clustering. Instructions on configuring your database are available at . /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder UseAdoNetClustering( this ISiloBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); services.AddSingleton(); }); } /// /// Configures this silo to use ADO.NET for clustering. Instructions on configuring your database are available at . /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder UseAdoNetClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); services.AddSingleton(); }); } /// /// Configures this client to use ADO.NET for clustering. Instructions on configuring your database are available at . /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// /// /// Instructions on configuring your database are available at . /// public static IClientBuilder UseAdoNetClustering( this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); services.AddSingleton(); }); } /// /// Configures this client to use ADO.NET for clustering. Instructions on configuring your database are available at . /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// /// /// Instructions on configuring your database are available at . /// public static IClientBuilder UseAdoNetClustering( this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); services.AddSingleton(); }); } } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Messaging/AdoNetClusteringTable.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Clustering.AdoNet.Storage; using Orleans.Configuration; namespace Orleans.Runtime.MembershipService { public partial class AdoNetClusteringTable : IMembershipTable { private readonly string clusterId; private readonly IServiceProvider serviceProvider; private readonly ILogger logger; private RelationalOrleansQueries orleansQueries; private readonly AdoNetClusteringSiloOptions clusteringTableOptions; public AdoNetClusteringTable( IServiceProvider serviceProvider, IOptions clusterOptions, IOptions clusteringOptions, ILogger logger) { this.serviceProvider = serviceProvider; this.logger = logger; this.clusteringTableOptions = clusteringOptions.Value; this.clusterId = clusterOptions.Value.ClusterId; } public async Task InitializeMembershipTable(bool tryInitTableVersion) { LogTraceInitializeMembershipTable(); //This initializes all of Orleans operational queries from the database using a well known view //and assumes the database with appropriate definitions exists already. orleansQueries = await RelationalOrleansQueries.CreateInstance( clusteringTableOptions.Invariant, clusteringTableOptions.ConnectionString); // even if I am not the one who created the table, // try to insert an initial table version if it is not already there, // so we always have a first table version row, before this silo starts working. if (tryInitTableVersion) { var wasCreated = await InitTableAsync(); if (wasCreated) { LogInfoCreatedNewTableVersionRow(); } } } public async Task ReadRow(SiloAddress key) { LogTraceReadRow(key); try { return await orleansQueries.MembershipReadRowAsync(this.clusterId, key); } catch (Exception ex) { LogDebugReadRowFailed(ex); throw; } } public async Task ReadAll() { LogTraceReadAll(); try { return await orleansQueries.MembershipReadAllAsync(this.clusterId); } catch (Exception ex) { LogDebugReadAllFailed(ex); throw; } } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { LogTraceInsertRow(entry, tableVersion); //The "tableVersion" parameter should always exist when inserting a row as Init should //have been called and membership version created and read. This is an optimization to //not to go through all the way to database to fail a conditional check on etag (which does //exist for the sake of robustness) as mandated by Orleans membership protocol. //Likewise, no update can be done without membership entry. if (entry == null) { LogDebugInsertRowAbortedNullEntry(); throw new ArgumentNullException(nameof(entry)); } if (tableVersion is null) { LogDebugInsertRowAbortedNullTableVersion(); throw new ArgumentNullException(nameof(tableVersion)); } try { return await orleansQueries.InsertMembershipRowAsync(this.clusterId, entry, tableVersion.VersionEtag); } catch (Exception ex) { LogDebugInsertRowFailed(ex); throw; } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { LogTraceUpdateRow(entry, etag, tableVersion); //The "tableVersion" parameter should always exist when updating a row as Init should //have been called and membership version created and read. This is an optimization to //not to go through all the way to database to fail a conditional check (which does //exist for the sake of robustness) as mandated by Orleans membership protocol. //Likewise, no update can be done without membership entry or an etag. if (entry == null) { LogDebugUpdateRowAbortedNullEntry(); throw new ArgumentNullException(nameof(entry)); } if (tableVersion is null) { LogDebugUpdateRowAbortedNullTableVersion(); throw new ArgumentNullException(nameof(tableVersion)); } try { return await orleansQueries.UpdateMembershipRowAsync(this.clusterId, entry, tableVersion.VersionEtag); } catch (Exception ex) { LogDebugUpdateRowFailed(ex); throw; } } public async Task UpdateIAmAlive(MembershipEntry entry) { LogTraceUpdateIAmAlive(entry); if (entry == null) { LogDebugUpdateIAmAliveAbortedNullEntry(); throw new ArgumentNullException(nameof(entry)); } try { await orleansQueries.UpdateIAmAliveTimeAsync(this.clusterId, entry.SiloAddress, entry.IAmAliveTime); } catch (Exception ex) { LogDebugUpdateIAmAliveFailed(ex); throw; } } public async Task DeleteMembershipTableEntries(string clusterId) { LogTraceDeleteMembershipTableEntries(clusterId); try { await orleansQueries.DeleteMembershipTableEntriesAsync(clusterId); } catch (Exception ex) { LogDebugDeleteMembershipTableEntriesFailed(ex); throw; } } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { LogTraceCleanupDefunctSiloEntries(beforeDate, clusterId); try { await orleansQueries.CleanupDefunctSiloEntriesAsync(beforeDate, this.clusterId); } catch (Exception ex) { LogDebugCleanupDefunctSiloEntriesFailed(ex); throw; } } private async Task InitTableAsync() { try { return await orleansQueries.InsertMembershipVersionRowAsync(this.clusterId); } catch (Exception ex) { LogTraceInsertSiloMembershipVersionFailed(ex); throw; } } [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(InitializeMembershipTable)} called." )] private partial void LogTraceInitializeMembershipTable(); [LoggerMessage( Level = LogLevel.Information, Message = "Created new table version row." )] private partial void LogInfoCreatedNewTableVersionRow(); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(ReadRow)} called with key: {{Key}}." )] private partial void LogTraceReadRow(SiloAddress key); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(ReadRow)} failed" )] private partial void LogDebugReadRowFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(ReadAll)} called." )] private partial void LogTraceReadAll(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(ReadAll)} failed" )] private partial void LogDebugReadAllFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(InsertRow)} called with entry {{Entry}} and tableVersion {{TableVersion}}." )] private partial void LogTraceInsertRow(MembershipEntry entry, TableVersion tableVersion); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(InsertRow)} aborted due to null check. MembershipEntry is null." )] private partial void LogDebugInsertRowAbortedNullEntry(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(InsertRow)} aborted due to null check. TableVersion is null " )] private partial void LogDebugInsertRowAbortedNullTableVersion(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(InsertRow)} failed" )] private partial void LogDebugInsertRowFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(IMembershipTable)}.{nameof(UpdateRow)} called with entry {{Entry}}, etag {{ETag}} and tableVersion {{TableVersion}}." )] private partial void LogTraceUpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(UpdateRow)} aborted due to null check. MembershipEntry is null." )] private partial void LogDebugUpdateRowAbortedNullEntry(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(UpdateRow)} aborted due to null check. TableVersion is null" )] private partial void LogDebugUpdateRowAbortedNullTableVersion(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(UpdateRow)} failed" )] private partial void LogDebugUpdateRowFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(IMembershipTable)}.{nameof(UpdateIAmAlive)} called with entry {{Entry}}." )] private partial void LogTraceUpdateIAmAlive(MembershipEntry entry); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(UpdateIAmAlive)} aborted due to null check. MembershipEntry is null." )] private partial void LogDebugUpdateIAmAliveAbortedNullEntry(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(UpdateIAmAlive)} failed" )] private partial void LogDebugUpdateIAmAliveFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(IMembershipTable)}.{nameof(DeleteMembershipTableEntries)} called with clusterId {{ClusterId}}." )] private partial void LogTraceDeleteMembershipTableEntries(string clusterId); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(DeleteMembershipTableEntries)} failed" )] private partial void LogDebugDeleteMembershipTableEntriesFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(IMembershipTable)}.{nameof(CleanupDefunctSiloEntries)} called with beforeDate {{beforeDate}} and clusterId {{ClusterId}}." )] private partial void LogTraceCleanupDefunctSiloEntries(DateTimeOffset beforeDate, string clusterId); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetClusteringTable)}.{nameof(CleanupDefunctSiloEntries)} failed" )] private partial void LogDebugCleanupDefunctSiloEntriesFailed(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Insert silo membership version failed" )] private partial void LogTraceInsertSiloMembershipVersionFailed(Exception exception); } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Messaging/AdoNetGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Clustering.AdoNet.Storage; using Orleans.Messaging; using Orleans.Configuration; namespace Orleans.Runtime.Membership { public partial class AdoNetGatewayListProvider : IGatewayListProvider { private readonly ILogger _logger; private readonly string _clusterId; private readonly AdoNetClusteringClientOptions _options; private RelationalOrleansQueries _orleansQueries; private readonly IServiceProvider _serviceProvider; private readonly TimeSpan _maxStaleness; public AdoNetGatewayListProvider( ILogger logger, IServiceProvider serviceProvider, IOptions options, IOptions gatewayOptions, IOptions clusterOptions) { this._logger = logger; this._serviceProvider = serviceProvider; this._options = options.Value; this._clusterId = clusterOptions.Value.ClusterId; this._maxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; } public TimeSpan MaxStaleness { get { return this._maxStaleness; } } public bool IsUpdatable { get { return true; } } public async Task InitializeGatewayListProvider() { LogTraceInitializeGatewayListProvider(); _orleansQueries = await RelationalOrleansQueries.CreateInstance(_options.Invariant, _options.ConnectionString); } public async Task> GetGateways() { LogTraceGetGateways(); try { return await _orleansQueries.ActiveGatewaysAsync(this._clusterId); } catch (Exception ex) { LogDebugGatewaysFailed(ex); throw; } } [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetGatewayListProvider)}.{nameof(InitializeGatewayListProvider)} called." )] private partial void LogTraceInitializeGatewayListProvider(); [LoggerMessage( Level = LogLevel.Trace, Message = $"{nameof(AdoNetGatewayListProvider)}.{nameof(GetGateways)} called." )] private partial void LogTraceGetGateways(); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AdoNetGatewayListProvider)}.{nameof(GetGateways)} failed" )] private partial void LogDebugGatewaysFailed(Exception exception); } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Migrations/MySQL-Clustering-3.7.0.sql ================================================ INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'CleanupDefunctSiloEntriesKey',' DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND IAmAliveTime < @IAmAliveTime AND Status !=3; '); ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Migrations/Oracle-Clustering-3.7.0.sql ================================================ INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteMembershipTableEntriesKey',' BEGIN DELETE FROM OrleansMembershipTable WHERE DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL AND IAmAliveTime < :IAmAliveTime AND Status != 3; END; '); / ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Migrations/PostgreSQL-Clustering-3.6.0.sql ================================================ -- Run this migration for upgrading the PostgreSQL clustering table and routines for deployments created before 3.6.0 BEGIN; -- Change date type ALTER TABLE OrleansMembershipVersionTable ALTER COLUMN Timestamp TYPE TIMESTAMPTZ(3) USING Timestamp AT TIME ZONE 'UTC'; ALTER TABLE OrleansMembershipTable ALTER COLUMN StartTime TYPE TIMESTAMPTZ(3) USING StartTime AT TIME ZONE 'UTC', ALTER COLUMN IAmAliveTime TYPE TIMESTAMPTZ(3) USING IAmAliveTime AT TIME ZONE 'UTC'; -- Recreate routines CREATE OR REPLACE FUNCTION update_i_am_alive_time( deployment_id OrleansMembershipTable.DeploymentId%TYPE, address_arg OrleansMembershipTable.Address%TYPE, port_arg OrleansMembershipTable.Port%TYPE, generation_arg OrleansMembershipTable.Generation%TYPE, i_am_alive_time OrleansMembershipTable.IAmAliveTime%TYPE) RETURNS void AS $func$ BEGIN -- This is expected to never fail by Orleans, so return value -- is not needed nor is it checked. UPDATE OrleansMembershipTable as d SET IAmAliveTime = i_am_alive_time WHERE d.DeploymentId = deployment_id AND deployment_id IS NOT NULL AND d.Address = address_arg AND address_arg IS NOT NULL AND d.Port = port_arg AND port_arg IS NOT NULL AND d.Generation = generation_arg AND generation_arg IS NOT NULL; END $func$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION insert_membership( DeploymentIdArg OrleansMembershipTable.DeploymentId%TYPE, AddressArg OrleansMembershipTable.Address%TYPE, PortArg OrleansMembershipTable.Port%TYPE, GenerationArg OrleansMembershipTable.Generation%TYPE, SiloNameArg OrleansMembershipTable.SiloName%TYPE, HostNameArg OrleansMembershipTable.HostName%TYPE, StatusArg OrleansMembershipTable.Status%TYPE, ProxyPortArg OrleansMembershipTable.ProxyPort%TYPE, StartTimeArg OrleansMembershipTable.StartTime%TYPE, IAmAliveTimeArg OrleansMembershipTable.IAmAliveTime%TYPE, VersionArg OrleansMembershipVersionTable.Version%TYPE) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN BEGIN INSERT INTO OrleansMembershipTable ( DeploymentId, Address, Port, Generation, SiloName, HostName, Status, ProxyPort, StartTime, IAmAliveTime ) SELECT DeploymentIdArg, AddressArg, PortArg, GenerationArg, SiloNameArg, HostNameArg, StatusArg, ProxyPortArg, StartTimeArg, IAmAliveTimeArg ON CONFLICT (DeploymentId, Address, Port, Generation) DO NOTHING; GET DIAGNOSTICS RowCountVar = ROW_COUNT; UPDATE OrleansMembershipVersionTable SET Timestamp = now(), Version = Version + 1 WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Version = VersionArg AND VersionArg IS NOT NULL AND RowCountVar > 0; GET DIAGNOSTICS RowCountVar = ROW_COUNT; ASSERT RowCountVar <> 0, 'no rows affected, rollback'; RETURN QUERY SELECT RowCountVar; EXCEPTION WHEN assert_failure THEN RETURN QUERY SELECT RowCountVar; END; END $func$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION update_membership( DeploymentIdArg OrleansMembershipTable.DeploymentId%TYPE, AddressArg OrleansMembershipTable.Address%TYPE, PortArg OrleansMembershipTable.Port%TYPE, GenerationArg OrleansMembershipTable.Generation%TYPE, StatusArg OrleansMembershipTable.Status%TYPE, SuspectTimesArg OrleansMembershipTable.SuspectTimes%TYPE, IAmAliveTimeArg OrleansMembershipTable.IAmAliveTime%TYPE, VersionArg OrleansMembershipVersionTable.Version%TYPE ) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN BEGIN UPDATE OrleansMembershipVersionTable SET Timestamp = now(), Version = Version + 1 WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Version = VersionArg AND VersionArg IS NOT NULL; GET DIAGNOSTICS RowCountVar = ROW_COUNT; UPDATE OrleansMembershipTable SET Status = StatusArg, SuspectTimes = SuspectTimesArg, IAmAliveTime = IAmAliveTimeArg WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Address = AddressArg AND AddressArg IS NOT NULL AND Port = PortArg AND PortArg IS NOT NULL AND Generation = GenerationArg AND GenerationArg IS NOT NULL AND RowCountVar > 0; GET DIAGNOSTICS RowCountVar = ROW_COUNT; ASSERT RowCountVar <> 0, 'no rows affected, rollback'; RETURN QUERY SELECT RowCountVar; EXCEPTION WHEN assert_failure THEN RETURN QUERY SELECT RowCountVar; END; END $func$ LANGUAGE plpgsql; COMMIT; ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Migrations/PostgreSQL-Clustering-3.7.0.sql ================================================ INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'CleanupDefunctSiloEntriesKey',' DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND IAmAliveTime < @IAmAliveTime AND Status != 3; '); ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Migrations/SQLServer-Clustering-3.7.0.sql ================================================ INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'CleanupDefunctSiloEntriesKey', 'DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND IAmAliveTime < @IAmAliveTime AND Status != 3; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'CleanupDefunctSiloEntriesKey' ); ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/MySQL-Clustering.sql ================================================ -- For each deployment, there will be only one (active) membership version table version column which will be updated periodically. CREATE TABLE OrleansMembershipVersionTable ( DeploymentId NVARCHAR(150) NOT NULL, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, Version INT NOT NULL DEFAULT 0, CONSTRAINT PK_OrleansMembershipVersionTable_DeploymentId PRIMARY KEY(DeploymentId) ); -- Every silo instance has a row in the membership table. CREATE TABLE OrleansMembershipTable ( DeploymentId NVARCHAR(150) NOT NULL, Address VARCHAR(45) NOT NULL, Port INT NOT NULL, Generation INT NOT NULL, SiloName NVARCHAR(150) NOT NULL, HostName NVARCHAR(150) NOT NULL, Status INT NOT NULL, ProxyPort INT NULL, SuspectTimes VARCHAR(8000) NULL, StartTime DATETIME NOT NULL, IAmAliveTime DATETIME NOT NULL, CONSTRAINT PK_MembershipTable_DeploymentId PRIMARY KEY(DeploymentId, Address, Port, Generation), CONSTRAINT FK_MembershipTable_MembershipVersionTable_DeploymentId FOREIGN KEY (DeploymentId) REFERENCES OrleansMembershipVersionTable (DeploymentId) ); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateIAmAlivetimeKey',' -- This is expected to never fail by Orleans, so return value -- is not needed nor is it checked. UPDATE OrleansMembershipTable SET IAmAliveTime = @IAmAliveTime WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipVersionKey',' INSERT INTO OrleansMembershipVersionTable ( DeploymentId ) SELECT * FROM ( SELECT @DeploymentId ) AS TMP WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipVersionTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL ); SELECT ROW_COUNT(); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipKey',' call InsertMembershipKey(@DeploymentId, @Address, @Port, @Generation, @Version, @SiloName, @HostName, @Status, @ProxyPort, @StartTime, @IAmAliveTime);' ); DELIMITER $$ CREATE PROCEDURE InsertMembershipKey( in _DeploymentId NVARCHAR(150), in _Address VARCHAR(45), in _Port INT, in _Generation INT, in _Version INT, in _SiloName NVARCHAR(150), in _HostName NVARCHAR(150), in _Status INT, in _ProxyPort INT, in _StartTime DATETIME, in _IAmAliveTime DATETIME ) BEGIN DECLARE _ROWCOUNT INT; START TRANSACTION; INSERT INTO OrleansMembershipTable ( DeploymentId, Address, Port, Generation, SiloName, HostName, Status, ProxyPort, StartTime, IAmAliveTime ) SELECT * FROM ( SELECT _DeploymentId, _Address, _Port, _Generation, _SiloName, _HostName, _Status, _ProxyPort, _StartTime, _IAmAliveTime) AS TMP WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipTable WHERE DeploymentId = _DeploymentId AND _DeploymentId IS NOT NULL AND Address = _Address AND _Address IS NOT NULL AND Port = _Port AND _Port IS NOT NULL AND Generation = _Generation AND _Generation IS NOT NULL ); UPDATE OrleansMembershipVersionTable SET Version = Version + 1 WHERE DeploymentId = _DeploymentId AND _DeploymentId IS NOT NULL AND Version = _Version AND _Version IS NOT NULL AND ROW_COUNT() > 0; SET _ROWCOUNT = ROW_COUNT(); IF _ROWCOUNT = 0 THEN ROLLBACK; ELSE COMMIT; END IF; SELECT _ROWCOUNT; END$$ DELIMITER ; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateMembershipKey',' START TRANSACTION; UPDATE OrleansMembershipVersionTable SET Version = Version + 1 WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Version = @Version AND @Version IS NOT NULL; UPDATE OrleansMembershipTable SET Status = @Status, SuspectTimes = @SuspectTimes, IAmAliveTime = @IAmAliveTime WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL AND ROW_COUNT() > 0; SELECT ROW_COUNT(); COMMIT; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'GatewaysQueryKey',' SELECT Address, ProxyPort, Generation FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Status = @Status AND @Status IS NOT NULL AND ProxyPort > 0; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadRowKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v -- This ensures the version table will returned even if there is no matching membership row. LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadAllKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteMembershipTableEntriesKey',' DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; DELETE FROM OrleansMembershipVersionTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Options/AdoNetClusteringClientOptions.cs ================================================ namespace Orleans.Configuration { public class AdoNetClusteringClientOptions { /// /// Connection string for Sql /// [Redact] public string ConnectionString { get; set; } /// /// The invariant name of the connector for gatewayProvider's database. /// public string Invariant { get; set; } } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Options/AdoNetClusteringClientOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Runtime.MembershipService; namespace Orleans.Configuration { /// /// Validates configuration. /// public class AdoNetClusteringClientOptionsValidator : IConfigurationValidator { private readonly AdoNetClusteringClientOptions options; public AdoNetClusteringClientOptionsValidator(IOptions options) { this.options = options.Value; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetClusteringClientOptions)} values for {nameof(AdoNetClusteringTable)}. {nameof(options.Invariant)} is required."); } if (string.IsNullOrWhiteSpace(this.options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetClusteringClientOptions)} values for {nameof(AdoNetClusteringTable)}. {nameof(options.ConnectionString)} is required."); } } } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Options/AdoNetClusteringSiloOptions.cs ================================================ namespace Orleans.Configuration { /// /// Options for ADO.NET clustering /// public class AdoNetClusteringSiloOptions { /// /// Connection string for AdoNet Storage /// [Redact] public string ConnectionString { get; set; } /// /// The invariant name of the connector for membership's database. /// public string Invariant { get; set; } } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Options/AdoNetReminderTableOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Runtime.MembershipService; namespace Orleans.Configuration { /// /// Validates configuration. /// public class AdoNetClusteringSiloOptionsValidator : IConfigurationValidator { private readonly AdoNetClusteringSiloOptions options; public AdoNetClusteringSiloOptionsValidator(IOptions options) { this.options = options.Value; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetClusteringSiloOptions)} values for {nameof(AdoNetClusteringTable)}. {nameof(options.Invariant)} is required."); } if (string.IsNullOrWhiteSpace(this.options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetClusteringSiloOptions)} values for {nameof(AdoNetClusteringTable)}. {nameof(options.ConnectionString)} is required."); } } } } ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Oracle-Clustering.sql ================================================ -- For each deployment, there will be only one (active) membership version table version column which will be updated periodically. CREATE TABLE "ORLEANSMEMBERSHIPVERSIONTABLE" ( "DEPLOYMENTID" NVARCHAR2(150) NOT NULL ENABLE, "TIMESTAMP" TIMESTAMP (6) DEFAULT sys_extract_utc(systimestamp) NOT NULL ENABLE, "VERSION" NUMBER(*,0) DEFAULT 0, CONSTRAINT "ORLEANSMEMBERSHIPVERSIONTA_PK" PRIMARY KEY ("DEPLOYMENTID") ); / -- Every silo instance has a row in the membership table. CREATE TABLE "ORLEANSMEMBERSHIPTABLE" ( "DEPLOYMENTID" NVARCHAR2(150) NOT NULL ENABLE, "ADDRESS" VARCHAR2(45 BYTE) NOT NULL ENABLE, "PORT" NUMBER(*,0) NOT NULL ENABLE, "GENERATION" NUMBER(*,0) NOT NULL ENABLE, "SILONAME" NVARCHAR2(150) NOT NULL ENABLE, "HOSTNAME" NVARCHAR2(150) NOT NULL ENABLE, "STATUS" NUMBER(*,0) NOT NULL ENABLE, "PROXYPORT" NUMBER(*,0), "SUSPECTTIMES" VARCHAR2(4000 BYTE), "STARTTIME" TIMESTAMP (6) NOT NULL ENABLE, "IAMALIVETIME" TIMESTAMP (6) NOT NULL ENABLE, CONSTRAINT "ORLEANSMEMBERSHIPTABLE_PK" PRIMARY KEY ("DEPLOYMENTID", "ADDRESS", "PORT", "GENERATION"), CONSTRAINT "ORLEANSMEMBERSHIPTABLE_FK1" FOREIGN KEY ("DEPLOYMENTID") REFERENCES "ORLEANSMEMBERSHIPVERSIONTABLE" ("DEPLOYMENTID") ENABLE ); / CREATE OR REPLACE FUNCTION InsertMembership(PARAM_DEPLOYMENTID IN NVARCHAR2, PARAM_IAMALIVETIME IN TIMESTAMP, PARAM_SILONAME IN NVARCHAR2, PARAM_HOSTNAME IN NVARCHAR2, PARAM_ADDRESS IN VARCHAR2, PARAM_PORT IN NUMBER, PARAM_GENERATION IN NUMBER, PARAM_STARTTIME IN TIMESTAMP, PARAM_STATUS IN NUMBER, PARAM_PROXYPORT IN NUMBER, PARAM_VERSION IN NUMBER) RETURN NUMBER IS rowcount NUMBER; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO OrleansMembershipTable ( DeploymentId, Address, Port, Generation, SiloName, HostName, Status, ProxyPort, StartTime, IAmAliveTime ) SELECT PARAM_DEPLOYMENTID, PARAM_ADDRESS, PARAM_PORT, PARAM_GENERATION, PARAM_SILONAME, PARAM_HOSTNAME, PARAM_STATUS, PARAM_PROXYPORT, PARAM_STARTTIME, PARAM_IAMALIVETIME FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipTable WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL AND Address = PARAM_ADDRESS AND PARAM_ADDRESS IS NOT NULL AND Port = PARAM_PORT AND PARAM_PORT IS NOT NULL AND Generation = PARAM_GENERATION AND PARAM_GENERATION IS NOT NULL ); rowcount := SQL%ROWCOUNT; UPDATE OrleansMembershipVersionTable SET Timestamp = sys_extract_utc(systimestamp), Version = Version + 1 WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL AND Version = PARAM_VERSION AND PARAM_VERSION IS NOT NULL AND rowcount > 0; rowcount := SQL%ROWCOUNT; IF rowcount = 0 THEN ROLLBACK; ELSE COMMIT; END IF; IF rowcount > 0 THEN RETURN(1); ELSE RETURN(0); END IF; END; / CREATE OR REPLACE FUNCTION UpdateMembership(PARAM_DEPLOYMENTID IN NVARCHAR2, PARAM_ADDRESS IN VARCHAR2, PARAM_PORT IN NUMBER, PARAM_GENERATION IN NUMBER, PARAM_IAMALIVETIME IN TIMESTAMP, PARAM_STATUS IN NUMBER, PARAM_SUSPECTTIMES IN VARCHAR2, PARAM_VERSION IN NUMBER ) RETURN NUMBER IS rowcount NUMBER; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN UPDATE OrleansMembershipVersionTable SET Timestamp = sys_extract_utc(systimestamp), Version = Version + 1 WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL AND Version = PARAM_VERSION AND PARAM_VERSION IS NOT NULL; rowcount := SQL%ROWCOUNT; UPDATE OrleansMembershipTable SET Status = PARAM_STATUS, SuspectTimes = PARAM_SUSPECTTIMES, IAmAliveTime = PARAM_IAMALIVETIME WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL AND Address = PARAM_ADDRESS AND PARAM_ADDRESS IS NOT NULL AND Port = PARAM_PORT AND PARAM_PORT IS NOT NULL AND Generation = PARAM_GENERATION AND PARAM_GENERATION IS NOT NULL AND rowcount > 0; rowcount := SQL%ROWCOUNT; COMMIT; RETURN(rowcount); END; / CREATE OR REPLACE FUNCTION InsertMembershipVersion(PARAM_DEPLOYMENTID IN NVARCHAR2) RETURN NUMBER IS rowcount NUMBER; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO OrleansMembershipVersionTable ( DeploymentId ) SELECT PARAM_DEPLOYMENTID FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipVersionTable WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL ); rowCount := SQL%ROWCOUNT; COMMIT; RETURN(rowCount); END; / CREATE OR REPLACE FUNCTION UpdateIAmAlivetime(PARAM_DEPLOYMENTID IN NVARCHAR2, PARAM_ADDRESS in VARCHAR2, PARAM_PORT IN NUMBER, PARAM_GENERATION IN NUMBER, PARAM_IAMALIVE IN TIMESTAMP) RETURN NUMBER IS rowcount NUMBER; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN UPDATE OrleansMembershipTable SET IAmAliveTime = PARAM_IAMALIVE WHERE DeploymentId = PARAM_DEPLOYMENTID AND PARAM_DEPLOYMENTID IS NOT NULL AND Address = PARAM_ADDRESS AND PARAM_ADDRESS IS NOT NULL AND Port = PARAM_PORT AND PARAM_PORT IS NOT NULL AND Generation = PARAM_GENERATION AND PARAM_GENERATION IS NOT NULL; COMMIT; RETURN(0); END; / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateIAmAlivetimeKey',' SELECT UpdateIAmAlivetime(:DeploymentId, :Address, :Port, :Generation, :IAmAliveTime) AS RESULT FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipVersionKey',' SELECT InsertMembershipVersion(:DeploymentId) AS RESULT FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipKey',' SELECT INSERTMEMBERSHIP(:DeploymentId,:IAmAliveTime,:SiloName,:Hostname,:Address,:Port,:Generation,:StartTime,:Status,:ProxyPort,:Version) FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateMembershipKey',' SELECT UpdateMembership(:DeploymentId, :Address, :Port, :Generation, :IAmAliveTime, :Status, :SuspectTimes, :Version) AS RESULT FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadRowKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId AND Address = :Address AND :Address IS NOT NULL AND Port = :Port AND :Port IS NOT NULL AND Generation = :Generation AND :Generation IS NOT NULL WHERE v.DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadAllKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId WHERE v.DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteMembershipTableEntriesKey',' BEGIN DELETE FROM OrleansMembershipTable WHERE DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL; DELETE FROM OrleansMembershipVersionTable WHERE DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL; END; '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'GatewaysQueryKey',' SELECT Address, ProxyPort, Generation FROM OrleansMembershipTable WHERE DeploymentId = :DeploymentId AND :DeploymentId IS NOT NULL AND Status = :Status AND :Status IS NOT NULL AND ProxyPort > 0 '); / COMMIT; ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/Orleans.Clustering.AdoNet.csproj ================================================ Microsoft.Orleans.Clustering.AdoNet Microsoft Orleans ADO.NET Clustering Provider Microsoft Orleans clustering provider backed by ADO.NET $(PackageTags) ADO.NET SQL MySQL PostgreSQL Oracle $(DefaultTargetFrameworks) true README.md Orleans.Clustering.AdoNet Orleans.Clustering.AdoNet $(DefineConstants);CLUSTERING_ADONET ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/PostgreSQL-Clustering.sql ================================================ -- For each deployment, there will be only one (active) membership version table version column which will be updated periodically. CREATE TABLE OrleansMembershipVersionTable ( DeploymentId varchar(150) NOT NULL, Timestamp timestamptz(3) NOT NULL DEFAULT now(), Version integer NOT NULL DEFAULT 0, CONSTRAINT PK_OrleansMembershipVersionTable_DeploymentId PRIMARY KEY(DeploymentId) ); -- Every silo instance has a row in the membership table. CREATE TABLE OrleansMembershipTable ( DeploymentId varchar(150) NOT NULL, Address varchar(45) NOT NULL, Port integer NOT NULL, Generation integer NOT NULL, SiloName varchar(150) NOT NULL, HostName varchar(150) NOT NULL, Status integer NOT NULL, ProxyPort integer NULL, SuspectTimes varchar(8000) NULL, StartTime timestamptz(3) NOT NULL, IAmAliveTime timestamptz(3) NOT NULL, CONSTRAINT PK_MembershipTable_DeploymentId PRIMARY KEY(DeploymentId, Address, Port, Generation), CONSTRAINT FK_MembershipTable_MembershipVersionTable_DeploymentId FOREIGN KEY (DeploymentId) REFERENCES OrleansMembershipVersionTable (DeploymentId) ); CREATE FUNCTION update_i_am_alive_time( deployment_id OrleansMembershipTable.DeploymentId%TYPE, address_arg OrleansMembershipTable.Address%TYPE, port_arg OrleansMembershipTable.Port%TYPE, generation_arg OrleansMembershipTable.Generation%TYPE, i_am_alive_time OrleansMembershipTable.IAmAliveTime%TYPE) RETURNS void AS $func$ BEGIN -- This is expected to never fail by Orleans, so return value -- is not needed nor is it checked. UPDATE OrleansMembershipTable as d SET IAmAliveTime = i_am_alive_time WHERE d.DeploymentId = deployment_id AND deployment_id IS NOT NULL AND d.Address = address_arg AND address_arg IS NOT NULL AND d.Port = port_arg AND port_arg IS NOT NULL AND d.Generation = generation_arg AND generation_arg IS NOT NULL; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateIAmAlivetimeKey',' -- This is expected to never fail by Orleans, so return value -- is not needed nor is it checked. SELECT * from update_i_am_alive_time( @DeploymentId, @Address, @Port, @Generation, @IAmAliveTime ); '); CREATE FUNCTION insert_membership_version( DeploymentIdArg OrleansMembershipTable.DeploymentId%TYPE ) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN BEGIN INSERT INTO OrleansMembershipVersionTable ( DeploymentId ) SELECT DeploymentIdArg ON CONFLICT (DeploymentId) DO NOTHING; GET DIAGNOSTICS RowCountVar = ROW_COUNT; ASSERT RowCountVar <> 0, 'no rows affected, rollback'; RETURN QUERY SELECT RowCountVar; EXCEPTION WHEN assert_failure THEN RETURN QUERY SELECT RowCountVar; END; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipVersionKey',' SELECT * FROM insert_membership_version( @DeploymentId ); '); CREATE FUNCTION insert_membership( DeploymentIdArg OrleansMembershipTable.DeploymentId%TYPE, AddressArg OrleansMembershipTable.Address%TYPE, PortArg OrleansMembershipTable.Port%TYPE, GenerationArg OrleansMembershipTable.Generation%TYPE, SiloNameArg OrleansMembershipTable.SiloName%TYPE, HostNameArg OrleansMembershipTable.HostName%TYPE, StatusArg OrleansMembershipTable.Status%TYPE, ProxyPortArg OrleansMembershipTable.ProxyPort%TYPE, StartTimeArg OrleansMembershipTable.StartTime%TYPE, IAmAliveTimeArg OrleansMembershipTable.IAmAliveTime%TYPE, VersionArg OrleansMembershipVersionTable.Version%TYPE) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN BEGIN INSERT INTO OrleansMembershipTable ( DeploymentId, Address, Port, Generation, SiloName, HostName, Status, ProxyPort, StartTime, IAmAliveTime ) SELECT DeploymentIdArg, AddressArg, PortArg, GenerationArg, SiloNameArg, HostNameArg, StatusArg, ProxyPortArg, StartTimeArg, IAmAliveTimeArg ON CONFLICT (DeploymentId, Address, Port, Generation) DO NOTHING; GET DIAGNOSTICS RowCountVar = ROW_COUNT; UPDATE OrleansMembershipVersionTable SET Timestamp = now(), Version = Version + 1 WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Version = VersionArg AND VersionArg IS NOT NULL AND RowCountVar > 0; GET DIAGNOSTICS RowCountVar = ROW_COUNT; ASSERT RowCountVar <> 0, 'no rows affected, rollback'; RETURN QUERY SELECT RowCountVar; EXCEPTION WHEN assert_failure THEN RETURN QUERY SELECT RowCountVar; END; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'InsertMembershipKey',' SELECT * FROM insert_membership( @DeploymentId, @Address, @Port, @Generation, @SiloName, @HostName, @Status, @ProxyPort, @StartTime, @IAmAliveTime, @Version ); '); CREATE FUNCTION update_membership( DeploymentIdArg OrleansMembershipTable.DeploymentId%TYPE, AddressArg OrleansMembershipTable.Address%TYPE, PortArg OrleansMembershipTable.Port%TYPE, GenerationArg OrleansMembershipTable.Generation%TYPE, StatusArg OrleansMembershipTable.Status%TYPE, SuspectTimesArg OrleansMembershipTable.SuspectTimes%TYPE, IAmAliveTimeArg OrleansMembershipTable.IAmAliveTime%TYPE, VersionArg OrleansMembershipVersionTable.Version%TYPE ) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN BEGIN UPDATE OrleansMembershipVersionTable SET Timestamp = now(), Version = Version + 1 WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Version = VersionArg AND VersionArg IS NOT NULL; GET DIAGNOSTICS RowCountVar = ROW_COUNT; UPDATE OrleansMembershipTable SET Status = StatusArg, SuspectTimes = SuspectTimesArg, IAmAliveTime = IAmAliveTimeArg WHERE DeploymentId = DeploymentIdArg AND DeploymentIdArg IS NOT NULL AND Address = AddressArg AND AddressArg IS NOT NULL AND Port = PortArg AND PortArg IS NOT NULL AND Generation = GenerationArg AND GenerationArg IS NOT NULL AND RowCountVar > 0; GET DIAGNOSTICS RowCountVar = ROW_COUNT; ASSERT RowCountVar <> 0, 'no rows affected, rollback'; RETURN QUERY SELECT RowCountVar; EXCEPTION WHEN assert_failure THEN RETURN QUERY SELECT RowCountVar; END; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpdateMembershipKey',' SELECT * FROM update_membership( @DeploymentId, @Address, @Port, @Generation, @Status, @SuspectTimes, @IAmAliveTime, @Version ); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadRowKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v -- This ensures the version table will returned even if there is no matching membership row. LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'MembershipReadAllKey',' SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteMembershipTableEntriesKey',' DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; DELETE FROM OrleansMembershipVersionTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'GatewaysQueryKey',' SELECT Address, ProxyPort, Generation FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Status = @Status AND @Status IS NOT NULL AND ProxyPort > 0; '); ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/README.md ================================================ # Microsoft Orleans Clustering Provider for ADO.NET ## Introduction Microsoft Orleans Clustering Provider for ADO.NET allows Orleans silos to organize themselves as a cluster using relational databases through ADO.NET. This provider enables silos to discover each other, maintain cluster membership, and detect and handle failures. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Clustering.AdoNet ``` You will also need to install the appropriate database driver package for your database system: - SQL Server: `Microsoft.Data.SqlClient` - MySQL: `MySql.Data` or `MySqlConnector` - PostgreSQL: `Npgsql` - Oracle: `Oracle.ManagedDataAccess.Core` - SQLite: `Microsoft.Data.Sqlite` ## Example - Configuring ADO.NET Clustering ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } // Implement the grain interface public class HelloGrain : Grain, IHelloGrain { public Task SayHello(string greeting) { return Task.FromResult($"Hello, {greeting}!"); } } var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder // Configure ADO.NET for clustering .UseAdoNetClustering(options => { options.Invariant = "System.Data.SqlClient"; // Or other providers like "MySql.Data.MySqlClient", "Npgsql", etc. options.ConnectionString = "Server=localhost;Database=OrleansCluster;User Id=myUsername;******;"; }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("World"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Configuring Client to Connect to Cluster ```csharp using Microsoft.Extensions.Hosting; using Orleans; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } var clientBuilder = Host.CreateApplicationBuilder(args) .UseOrleansClient(clientBuilder => { clientBuilder // Configure the client to use ADO.NET for clustering .UseAdoNetClustering(options => { options.Invariant = "Microsoft.Data.SqlClient"; // Or other providers like "MySql.Data.MySqlClient", "Npgsql", etc. options.ConnectionString = "Server=localhost;Database=OrleansCluster;User Id=myUsername;******;"; }); }); var host = clientBuilder.Build(); await host.StartAsync(); var client = host.Services.GetRequiredService(); // Get a reference to a grain and call it var grain = client.GetGrain("user123"); var response = await grain.SayHello("World"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Database Setup Before using the ADO.NET clustering provider, you need to set up the necessary database tables. Scripts for different database systems are available in the Orleans source repository: namespace ExampleGrains; - [SQL Server Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Clustering.AdoNet/SQLServer-Clustering.sql) - [MySQL Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Clustering.AdoNet/MySQL-Clustering.sql) - [PostgreSQL Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Clustering.AdoNet/PostgreSQL-Clustering.sql) - [Oracle Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Clustering.AdoNet/Oracle-Clustering.sql) ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Clustering providers](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) - [Relational Database Provider](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/relational-storage-providers) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AdoNet/Orleans.Clustering.AdoNet/SQLServer-Clustering.sql ================================================ -- For each deployment, there will be only one (active) membership version table version column which will be updated periodically. IF OBJECT_ID(N'[OrleansMembershipVersionTable]', 'U') IS NULL CREATE TABLE OrleansMembershipVersionTable ( DeploymentId NVARCHAR(150) NOT NULL, Timestamp DATETIME2(3) NOT NULL DEFAULT GETUTCDATE(), Version INT NOT NULL DEFAULT 0, CONSTRAINT PK_OrleansMembershipVersionTable_DeploymentId PRIMARY KEY(DeploymentId) ); -- Every silo instance has a row in the membership table. IF OBJECT_ID(N'[OrleansMembershipTable]', 'U') IS NULL CREATE TABLE OrleansMembershipTable ( DeploymentId NVARCHAR(150) NOT NULL, Address VARCHAR(45) NOT NULL, Port INT NOT NULL, Generation INT NOT NULL, SiloName NVARCHAR(150) NOT NULL, HostName NVARCHAR(150) NOT NULL, Status INT NOT NULL, ProxyPort INT NULL, SuspectTimes VARCHAR(8000) NULL, StartTime DATETIME2(3) NOT NULL, IAmAliveTime DATETIME2(3) NOT NULL, CONSTRAINT PK_MembershipTable_DeploymentId PRIMARY KEY(DeploymentId, Address, Port, Generation), CONSTRAINT FK_MembershipTable_MembershipVersionTable_DeploymentId FOREIGN KEY (DeploymentId) REFERENCES OrleansMembershipVersionTable (DeploymentId) ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'UpdateIAmAlivetimeKey', '-- This is expected to never fail by Orleans, so return value -- is not needed nor is it checked. SET NOCOUNT ON; UPDATE OrleansMembershipTable SET IAmAliveTime = @IAmAliveTime WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'UpdateIAmAlivetimeKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'InsertMembershipVersionKey', 'SET NOCOUNT ON; INSERT INTO OrleansMembershipVersionTable ( DeploymentId ) SELECT @DeploymentId WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipVersionTable WITH(HOLDLOCK, XLOCK, ROWLOCK) WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL ); SELECT @@ROWCOUNT; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'InsertMembershipVersionKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'InsertMembershipKey', 'SET XACT_ABORT, NOCOUNT ON; DECLARE @ROWCOUNT AS INT; BEGIN TRANSACTION; INSERT INTO OrleansMembershipTable ( DeploymentId, Address, Port, Generation, SiloName, HostName, Status, ProxyPort, StartTime, IAmAliveTime ) SELECT @DeploymentId, @Address, @Port, @Generation, @SiloName, @HostName, @Status, @ProxyPort, @StartTime, @IAmAliveTime WHERE NOT EXISTS ( SELECT 1 FROM OrleansMembershipTable WITH(HOLDLOCK, XLOCK, ROWLOCK) WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL ); UPDATE OrleansMembershipVersionTable SET Timestamp = GETUTCDATE(), Version = Version + 1 WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Version = @Version AND @Version IS NOT NULL AND @@ROWCOUNT > 0; SET @ROWCOUNT = @@ROWCOUNT; IF @ROWCOUNT = 0 ROLLBACK TRANSACTION ELSE COMMIT TRANSACTION SELECT @ROWCOUNT; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'InsertMembershipKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'UpdateMembershipKey', 'SET XACT_ABORT, NOCOUNT ON; BEGIN TRANSACTION; UPDATE OrleansMembershipVersionTable SET Timestamp = GETUTCDATE(), Version = Version + 1 WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Version = @Version AND @Version IS NOT NULL; UPDATE OrleansMembershipTable SET Status = @Status, SuspectTimes = @SuspectTimes, IAmAliveTime = @IAmAliveTime WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL AND @@ROWCOUNT > 0; SELECT @@ROWCOUNT; COMMIT TRANSACTION; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'UpdateMembershipKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'GatewaysQueryKey', 'SELECT Address, ProxyPort, Generation FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL AND Status = @Status AND @Status IS NOT NULL AND ProxyPort > 0; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'GatewaysQueryKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'MembershipReadRowKey', 'SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v -- This ensures the version table will returned even if there is no matching membership row. LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId AND Address = @Address AND @Address IS NOT NULL AND Port = @Port AND @Port IS NOT NULL AND Generation = @Generation AND @Generation IS NOT NULL WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'MembershipReadRowKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'MembershipReadAllKey', 'SELECT v.DeploymentId, m.Address, m.Port, m.Generation, m.SiloName, m.HostName, m.Status, m.ProxyPort, m.SuspectTimes, m.StartTime, m.IAmAliveTime, v.Version FROM OrleansMembershipVersionTable v LEFT OUTER JOIN OrleansMembershipTable m ON v.DeploymentId = m.DeploymentId WHERE v.DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'MembershipReadAllKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'DeleteMembershipTableEntriesKey', 'DELETE FROM OrleansMembershipTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; DELETE FROM OrleansMembershipVersionTable WHERE DeploymentId = @DeploymentId AND @DeploymentId IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'DeleteMembershipTableEntriesKey' ); ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/AdoNetGrainDirectory.cs ================================================ namespace Orleans.GrainDirectory.AdoNet; internal sealed partial class AdoNetGrainDirectory(string name, AdoNetGrainDirectoryOptions options, ILogger logger, IOptions clusterOptions, IHostApplicationLifetime lifetime) : IGrainDirectory { private readonly ILogger _logger = logger; private readonly string _clusterId = clusterOptions.Value.ClusterId; private RelationalOrleansQueries? _queries; /// /// Looks up a grain activation. /// /// The grain identifier. /// The grain address if found or null if not found. public async Task Lookup(GrainId grainId) { try { var queries = await GetQueriesAsync(); var entry = await queries .LookupGrainActivationAsync(_clusterId, name, grainId.ToString()) .WaitAsync(lifetime.ApplicationStopping); return entry?.ToGrainAddress(); } catch (Exception ex) { LogFailedToLookup(ex, _clusterId, grainId); throw; } } /// /// Registers a new grain activation. /// /// The grain address. /// The new or current grain address. public async Task Register(GrainAddress address) { ArgumentNullException.ThrowIfNull(address); ArgumentNullException.ThrowIfNull(address.SiloAddress); try { var queries = await GetQueriesAsync(); // this call is expected to register a new entry or return the existing one if found in a thread safe manner var entry = await queries .RegisterGrainActivationAsync(_clusterId, name, address.GrainId.ToString(), address.SiloAddress.ToParsableString(), address.ActivationId.ToParsableString()) .WaitAsync(lifetime.ApplicationStopping); LogRegistered(_clusterId, address.GrainId, address.SiloAddress, address.ActivationId); return entry.ToGrainAddress(); } catch (Exception ex) { LogFailedToRegister(ex, _clusterId, address.GrainId, address.SiloAddress, address.ActivationId); throw; } } /// /// Unregister an existing grain activation. /// /// The grain address. public async Task Unregister(GrainAddress address) { ArgumentNullException.ThrowIfNull(address); ArgumentNullException.ThrowIfNull(address.SiloAddress); try { var queries = await GetQueriesAsync(); var count = await queries .UnregisterGrainActivationAsync(_clusterId, name, address.GrainId.ToString(), address.ActivationId.ToParsableString()) .WaitAsync(lifetime.ApplicationStopping); if (count > 0) { LogUnregistered(_clusterId, address.GrainId, address.SiloAddress, address.ActivationId); } } catch (Exception ex) { LogFailedToUnregister(ex, _clusterId, address.GrainId, address.SiloAddress, address.ActivationId); throw; } } /// /// Unregisters all grain activations in the specified set of silos. /// /// The set of silos. public async Task UnregisterSilos(List siloAddresses) { ArgumentNullException.ThrowIfNull(siloAddresses); if (siloAddresses.Count == 0) { return; } try { var queries = await GetQueriesAsync(); var count = await queries .UnregisterGrainActivationsAsync(_clusterId, name, GetSilosAddressesAsString(siloAddresses)) .WaitAsync(lifetime.ApplicationStopping); if (count > 0) { LogUnregisteredSilos(count, _clusterId, siloAddresses); } } catch (Exception ex) { LogFailedToUnregisterSilos(ex, _clusterId, siloAddresses); throw; } static string GetSilosAddressesAsString(IEnumerable siloAddresses) => string.Join('|', siloAddresses.Select(x => x.ToParsableString())); } /// /// Unfortunate implementation detail to account for lack of async lifetime. /// Ideally this concern will be moved upstream so this won't be needed. /// private readonly SemaphoreSlim _semaphore = new(1); /// /// Ensures queries are loaded only once while allowing for recovery if the load fails. /// private ValueTask GetQueriesAsync() { // attempt fast path return _queries is not null ? new(_queries) : new(CoreAsync()); // slow path async Task CoreAsync() { await _semaphore.WaitAsync(lifetime.ApplicationStopping); try { // attempt fast path again if (_queries is not null) { return _queries; } // slow path - the member variable will only be set if the call succeeds return _queries = await RelationalOrleansQueries .CreateInstance(options.Invariant, options.ConnectionString) .WaitAsync(lifetime.ApplicationStopping); } finally { _semaphore.Release(); } } } #region Logging [LoggerMessage(1, LogLevel.Error, "Failed to lookup({ClusterId}, {GrainId})")] private partial void LogFailedToLookup(Exception ex, string clusterId, GrainId grainId); [LoggerMessage(2, LogLevel.Debug, "Registered ({ClusterId}, {GrainId}, {SiloAddress}, {ActivationId})")] private partial void LogRegistered(string clusterId, GrainId grainId, SiloAddress siloAddress, ActivationId activationId); [LoggerMessage(3, LogLevel.Error, "Failed to register ({ClusterId}, {GrainId}, {SiloAddress}, {ActivationId})")] private partial void LogFailedToRegister(Exception ex, string clusterId, GrainId grainId, SiloAddress siloAddress, ActivationId activationId); [LoggerMessage(4, LogLevel.Debug, "Unregistered ({ClusterId}, {GrainId}, {SiloAddress}, {ActivationId})")] private partial void LogUnregistered(string clusterId, GrainId grainId, SiloAddress siloAddress, ActivationId activationId); [LoggerMessage(5, LogLevel.Error, "Failed to unregister ({ClusterId}, {GrainId}, {SiloAddress}, {ActivationId})")] private partial void LogFailedToUnregister(Exception ex, string clusterId, GrainId grainId, SiloAddress siloAddress, ActivationId activationId); [LoggerMessage(6, LogLevel.Debug, "Unregistered {Count} activations from silos {SiloAddresses} in cluster {ClusterId}")] private partial void LogUnregisteredSilos(int count, string clusterId, IEnumerable siloAddresses); [LoggerMessage(7, LogLevel.Error, "Failed to unregister silos {SiloAddresses} in cluster {ClusterId}")] private partial void LogFailedToUnregisterSilos(Exception ex, string clusterId, IEnumerable siloAddresses); #endregion Logging } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/AdoNetGrainDirectoryEntry.cs ================================================ namespace Orleans.GrainDirectory.AdoNet; /// /// The model that represents a grain activation in an ADONET grain directory. /// internal sealed record AdoNetGrainDirectoryEntry( string ClusterId, string ProviderId, string GrainId, string SiloAddress, string ActivationId) { public AdoNetGrainDirectoryEntry() : this("", "", "", "", "") { } public GrainAddress ToGrainAddress() => new() { GrainId = Runtime.GrainId.Parse(GrainId, CultureInfo.InvariantCulture), SiloAddress = Runtime.SiloAddress.FromParsableString(SiloAddress), ActivationId = Runtime.ActivationId.FromParsableString(ActivationId) }; public static AdoNetGrainDirectoryEntry FromGrainAddress(string clusterId, string providerId, GrainAddress address) { ArgumentNullException.ThrowIfNull(clusterId); ArgumentNullException.ThrowIfNull(address); ArgumentNullException.ThrowIfNull(address.SiloAddress); return new AdoNetGrainDirectoryEntry( clusterId, providerId, address.GrainId.ToString(), address.SiloAddress.ToParsableString(), address.ActivationId.ToParsableString()); } } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/AdoNetGrainDirectoryOptions.cs ================================================ namespace Orleans.GrainDirectory.AdoNet; /// /// Options for the ADO.NET Grain Directory. /// public class AdoNetGrainDirectoryOptions { /// /// Gets or sets the ADO.NET invariant. /// [Required] public string Invariant { get; set; } = ""; /// /// Gets or sets the connection string. /// [Redact] [Required] public string ConnectionString { get; set; } = ""; } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/GlobalSuppressions.cs ================================================ // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "N/A", Scope = "namespace", Target = "~N:Orleans.Configuration")] [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "N/A", Scope = "namespace", Target = "~N:Orleans.Hosting")] ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Hosting/AdoNetGrainDirectoryProviderBuilder.cs ================================================ using static System.String; [assembly: RegisterProvider("AdoNet", "GrainDirectory", "Silo", typeof(AdoNetGrainDirectoryProviderBuilder))] namespace Orleans.Hosting; internal sealed class AdoNetGrainDirectoryProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string? name, IConfigurationSection configurationSection) { builder.AddAdoNetGrainDirectory(name ?? "Default", optionsBuilder => optionsBuilder.Configure((options, services) => { var invariant = configurationSection["Invariant"]; if (!IsNullOrWhiteSpace(invariant)) { options.Invariant = invariant; } var connectionString = configurationSection["ConnectionString"]; var connectionName = configurationSection["ConnectionName"]; if (!IsNullOrWhiteSpace(connectionString)) { options.ConnectionString = connectionString; } else if (!IsNullOrWhiteSpace(connectionName)) { connectionString = services.GetRequiredService().GetConnectionString(connectionName); if (!IsNullOrWhiteSpace(connectionString)) { options.ConnectionString = connectionString; } } })); } } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Hosting/AdoNetGrainDirectoryServiceCollectionExtensions.cs ================================================ using Orleans.Runtime.Hosting; namespace Orleans.Hosting; /// /// extensions. /// internal static class AdoNetGrainDirectoryServiceCollectionExtensions { internal static IServiceCollection AddAdoNetGrainDirectory( this IServiceCollection services, string name, Action> configureOptions) { configureOptions.Invoke(services.AddOptions(name)); return services .AddTransient(sp => new AdoNetGrainDirectoryOptionsValidator(sp.GetRequiredService>().Get(name), name)) .ConfigureNamedOptionForLogging(name) .AddGrainDirectory(name, (sp, name) => { var options = sp.GetOptionsByName(name); return ActivatorUtilities.CreateInstance(sp, name, options); }); } } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Hosting/AdoNetGrainDirectorySiloBuilderExtensions.cs ================================================ namespace Orleans.Hosting; public static class AdoNetGrainDirectorySiloBuilderExtensions { public static ISiloBuilder UseAdoNetGrainDirectoryAsDefault( this ISiloBuilder builder, Action configureOptions) => builder.UseAdoNetGrainDirectoryAsDefault(ob => ob.Configure(configureOptions)); public static ISiloBuilder UseAdoNetGrainDirectoryAsDefault( this ISiloBuilder builder, Action> configureOptions) => builder.ConfigureServices(services => services.AddAdoNetGrainDirectory(GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY, configureOptions)); public static ISiloBuilder AddAdoNetGrainDirectory( this ISiloBuilder builder, string name, Action configureOptions) => builder.AddAdoNetGrainDirectory(name, ob => ob.Configure(configureOptions)); public static ISiloBuilder AddAdoNetGrainDirectory( this ISiloBuilder builder, string name, Action> configureOptions) => builder.ConfigureServices(services => services.AddAdoNetGrainDirectory(name, configureOptions)); } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/MySQL-GrainDirectory.sql ================================================ /* Orleans Grain Directory. This table stores the location of all grains in the cluster. NOTE: The combination of ClusterId, ProviderId, and GrainId forms the primary key for the OrleansGrainDirectory table. Together, these columns reach the maximum allowed key size for MariaDB/MySQL indexes (768 bytes). Care should be taken not to increase the length of these columns, as it may exceed MariaDB/MySQL's key size limitation. */ CREATE TABLE OrleansGrainDirectory ( /* Identifies the cluster instance */ ClusterId VARCHAR(150) NOT NULL, /* Identifies the grain directory provider */ ProviderId VARCHAR(150) NOT NULL, /* Holds the grain id in text form */ GrainId VARCHAR(468) NOT NULL, /* Holds the silo address where the grain is located */ SiloAddress VARCHAR(100) NOT NULL, /* Holds the activation id in the silo where it is located */ ActivationId VARCHAR(100) NOT NULL, /* Holds the time at which the grain was added to the directory */ CreatedOn DATETIME(3) NOT NULL, /* Primary key ensures uniqueness of grain identity */ PRIMARY KEY (ClusterId, ProviderId, GrainId) ); DELIMITER $$ CREATE PROCEDURE RegisterGrainActivation ( IN _ClusterId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _GrainId NVARCHAR(468), IN _SiloAddress NVARCHAR(100), IN _ActivationId NVARCHAR(100) ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; INSERT INTO OrleansGrainDirectory ( ClusterId, ProviderId, GrainId, SiloAddress, ActivationId, CreatedOn ) VALUES ( _ClusterId, _ProviderId, _GrainId, _SiloAddress, _ActivationId, UTC_TIMESTAMP(3) ) ON DUPLICATE KEY UPDATE ClusterId = ClusterId; -- Return the current registration SELECT ClusterId, ProviderId, GrainId, SiloAddress, ActivationId FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND GrainId = _GrainId; COMMIT; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'RegisterGrainActivationKey', 'CALL RegisterGrainActivation(@ClusterId, @ProviderId, @GrainId, @SiloAddress, @ActivationId);'; DELIMITER $$ CREATE PROCEDURE UnregisterGrainActivation ( IN _ClusterId VARCHAR(150), IN _ProviderId VARCHAR(150), IN _GrainId VARCHAR(468), IN _ActivationId VARCHAR(100) ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; DELETE FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND GrainId = _GrainId AND ActivationId = _ActivationId; SELECT ROW_COUNT() AS DeletedRows; COMMIT; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationKey', 'CALL UnregisterGrainActivation(@ClusterId, @ProviderId, @GrainId, @ActivationId);'; DELIMITER $$ CREATE PROCEDURE LookupGrainActivation ( IN _ClusterId VARCHAR(150), IN _ProviderId VARCHAR(150), IN _GrainId VARCHAR(468) ) BEGIN SELECT ClusterId, ProviderId, GrainId, SiloAddress, ActivationId FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND GrainId = _GrainId; END DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'LookupGrainActivationKey', 'CALL LookupGrainActivation(@ClusterId, @ProviderId, @GrainId);'; DELIMITER $$ CREATE PROCEDURE UnregisterGrainActivations ( IN _ClusterId VARCHAR(150), IN _ProviderId VARCHAR(150), IN _SiloAddresses TEXT ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; -- Parse silo addresses into temporary table for batch operation CREATE TEMPORARY TABLE TempSiloAddresses ( SiloAddress VARCHAR(100) NOT NULL, Level INT NOT NULL ); INSERT INTO TempSiloAddresses (SiloAddress, Level) WITH RECURSIVE SiloAddressesCTE AS ( SELECT SUBSTRING_INDEX(_SiloAddresses, '|', 1) AS Value, SUBSTRING(_SiloAddresses, CHAR_LENGTH(SUBSTRING_INDEX(_SiloAddresses, '|', 1)) + 2, CHAR_LENGTH(_SiloAddresses)) AS Others, 1 AS Level UNION ALL SELECT SUBSTRING_INDEX(Others, '|', 1) AS Value, SUBSTRING(Others, CHAR_LENGTH(SUBSTRING_INDEX(Others, '|', 1)) + 2, CHAR_LENGTH(Others)) AS Others, Level + 1 FROM SiloAddressesCTE WHERE Others != '' ) SELECT Value, Level FROM SiloAddressesCTE; DELETE FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND SiloAddress IN (SELECT SiloAddress FROM TempSiloAddresses); SELECT ROW_COUNT() AS DeletedRows; DROP TEMPORARY TABLE TempSiloAddresses; COMMIT; END DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationsKey', 'CALL UnregisterGrainActivations(@ClusterId, @ProviderId, @SiloAddresses);'; DELIMITER ; ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Options/AdoNetGrainDirectoryOptionsValidator.cs ================================================ using static System.String; namespace Orleans.Configuration; /// /// Validates configuration. /// public class AdoNetGrainDirectoryOptionsValidator(AdoNetGrainDirectoryOptions options, string name) : IConfigurationValidator { /// public void ValidateConfiguration() { if (options is null) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainDirectoryOptions)} values for {nameof(AdoNetGrainDirectory)}|{name}. {nameof(options)} is required."); } if (IsNullOrWhiteSpace(options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainDirectoryOptions)} values for {nameof(AdoNetGrainDirectory)}|{name}. {nameof(options.Invariant)} is required."); } if (IsNullOrWhiteSpace(options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainDirectoryOptions)} values for {nameof(AdoNetGrainDirectory)}|{name}. {nameof(options.ConnectionString)} is required."); } } } ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Orleans.GrainDirectory.AdoNet.csproj ================================================  Microsoft.Orleans.GrainDirectory.AdoNet Microsoft Orleans ADO.NET Grain Directory Provider Microsoft Orleans grain directory provider backed by ADO.NET $(PackageTags) ADO.NET SQL $(DefaultTargetFrameworks) true alpha.1 Enable Enable CA2007 Orleans.GrainDirectory.AdoNet Orleans.GrainDirectory.AdoNet $(DefineConstants);GRAINDIRECTORY_ADONET ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/PostgreSQL-GrainDirectory.sql ================================================ /* Orleans Grain Directory. This table stores the location of all grains in the cluster. NOTE: The combination of ClusterId, ProviderId, and GrainId forms the primary key for the OrleansGrainDirectory table. Together, these columns reach the maximum allowed key size for PostgreSQL indexes (2704 bytes). Care should be taken not to increase the length of these columns, as it may exceed PostgreSQL's key size limitation. */ CREATE TABLE OrleansGrainDirectory ( /* Identifies the cluster instance */ ClusterId VARCHAR(150) NOT NULL, /* Identifies the grain directory provider */ ProviderId VARCHAR(150) NOT NULL, /* Holds the grain id in text form */ GrainId VARCHAR(2404) NOT NULL, /* Holds the silo address where the grain is located */ SiloAddress VARCHAR(100) NOT NULL, /* Holds the activation id in the silo where it is located */ ActivationId VARCHAR(100) NOT NULL, /* Holds the time at which the grain was added to the directory */ CreatedOn TIMESTAMPTZ NOT NULL, /* Identifies a unique grain activation */ CONSTRAINT PK_OrleansGrainDirectory PRIMARY KEY ( ClusterId, ProviderId, GrainId ) ); /* Registers a new grain activation */ CREATE OR REPLACE FUNCTION RegisterGrainActivation( _ClusterId VARCHAR(150), _ProviderId VARCHAR(150), _GrainId VARCHAR(600), _SiloAddress VARCHAR(100), _ActivationId VARCHAR(100) ) RETURNS TABLE ( ClusterId VARCHAR(150), ProviderId VARCHAR(150), GrainId VARCHAR(600), SiloAddress VARCHAR(100), ActivationId VARCHAR(100) ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Now TIMESTAMPTZ := NOW(); BEGIN RETURN QUERY INSERT INTO OrleansGrainDirectory ( ClusterId, ProviderId, GrainId, SiloAddress, ActivationId, CreatedOn ) SELECT _ClusterId, _ProviderId, _GrainId, _SiloAddress, _ActivationId, _Now ON CONFLICT (ClusterId, ProviderId, GrainId) DO UPDATE SET ClusterId = _ClusterId, ProviderId = _ProviderId, GrainId = _GrainId RETURNING ClusterId, ProviderId, GrainId, SiloAddress, ActivationId; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'RegisterGrainActivationKey', 'START TRANSACTION; SELECT * FROM RegisterGrainActivation (@ClusterId, @ProviderId, @GrainId, @SiloAddress, @ActivationId); COMMIT;' ; /* Unregisters an existing grain activation */ CREATE OR REPLACE FUNCTION UnregisterGrainActivation( _ClusterId VARCHAR(150), _ProviderId VARCHAR(150), _GrainId VARCHAR(600), _ActivationId VARCHAR(100) ) RETURNS INT LANGUAGE plpgsql AS $$ DECLARE _RowCount INT; BEGIN DELETE FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND GrainId = _GrainId AND ActivationId = _ActivationId; GET DIAGNOSTICS _RowCount = ROW_COUNT; RETURN _RowCount; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationKey', 'SELECT * FROM UnregisterGrainActivation (@ClusterId, @ProviderId, @GrainId, @ActivationId)' ; /* Looks up an existing grain activation */ CREATE OR REPLACE FUNCTION LookupGrainActivation( _ClusterId VARCHAR(150), _ProviderId VARCHAR(150), _GrainId VARCHAR(600) ) RETURNS TABLE ( ClusterId VARCHAR(150), ProviderId VARCHAR(150), GrainId VARCHAR(600), SiloAddress VARCHAR(100), ActivationId VARCHAR(100) ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN BEGIN RETURN QUERY SELECT ClusterId, ProviderId, GrainId, SiloAddress, ActivationId FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND GrainId = _GrainId; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'LookupGrainActivationKey', 'SELECT * FROM LookupGrainActivation(@ClusterId, @ProviderId, @GrainId)' ; /* Unregisters all grain activations in the specified silos */ CREATE OR REPLACE FUNCTION UnregisterGrainActivations( _ClusterId VARCHAR(150), _ProviderId VARCHAR(150), _SiloAddresses TEXT ) RETURNS INT LANGUAGE plpgsql AS $$ DECLARE _RowCount INT; BEGIN DELETE FROM OrleansGrainDirectory WHERE ClusterId = _ClusterId AND ProviderId = _ProviderId AND SiloAddress = ANY (string_to_array(_SiloAddresses, '|')); GET DIAGNOSTICS _RowCount = ROW_COUNT; RETURN _RowCount; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationsKey', 'SELECT * FROM UnregisterGrainActivations (@ClusterId, @ProviderId, @SiloAddresses)' ; ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/Properties/Usings.cs ================================================ global using System; global using System.Collections.Generic; global using System.ComponentModel.DataAnnotations; global using System.Globalization; global using System.Linq; global using System.Threading; global using System.Threading.Tasks; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Orleans.Configuration; global using Orleans.GrainDirectory; global using Orleans.GrainDirectory.AdoNet; global using Orleans.GrainDirectory.AdoNet.Storage; global using Orleans.Providers; global using Orleans.Runtime; ================================================ FILE: src/AdoNet/Orleans.GrainDirectory.AdoNet/SQLServer-GrainDirectory.sql ================================================ /* Orleans Grain Directory. This table stores the location of all grains in the cluster. NOTE: The combination of ClusterId, ProviderId, and GrainId forms the primary key for the OrleansGrainDirectory table. Together, these columns reach the maximum allowed key size for SQL Server indexes (900 bytes). Care should be taken not to increase the length of these columns, as it may exceed SQL Server's key size limitation. */ CREATE TABLE OrleansGrainDirectory ( /* Identifies the cluster instance */ ClusterId VARCHAR(150) NOT NULL, /* Identifies the grain directory provider */ ProviderId VARCHAR(150) NOT NULL, /* Holds the grain id in text form */ GrainId VARCHAR(600) NOT NULL, /* Holds the silo address where the grain is located */ SiloAddress VARCHAR(100) NOT NULL, /* Holds the activation id in the silo where it is located */ ActivationId VARCHAR(100) NOT NULL, /* Holds the time at which the grain was added to the directory */ CreatedOn DATETIMEOFFSET(3) NOT NULL, /* Identifies a unique grain activation */ CONSTRAINT PK_OrleansGrainDirectory PRIMARY KEY CLUSTERED ( ClusterId ASC, ProviderId ASC, GrainId ASC ) ) GO /* Registers a new grain activation */ CREATE PROCEDURE RegisterGrainActivation @ClusterId VARCHAR(150), @ProviderId VARCHAR(150), @GrainId VARCHAR(600), @SiloAddress VARCHAR(100), @ActivationId VARCHAR(100) AS SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Now DATETIMEOFFSET(3) = CAST(SYSUTCDATETIME() AS DATETIMEOFFSET(3)); BEGIN TRANSACTION; /* Get the existing entry if it exists. */ /* This also places a lock on the range upfront in case we need to add a new entry. */ /* This lock is required to prevent deadlocks. */ DECLARE @Exists INT = ( SELECT 1 FROM OrleansGrainDirectory WITH (UPDLOCK, HOLDLOCK) WHERE ClusterId = @ClusterId AND ProviderId = @ProviderId AND GrainId = @GrainId ); IF @Exists = 1 BEGIN /* If the entry already exists, we can return it. */ SELECT ClusterId, ProviderId, GrainId, SiloAddress, ActivationId FROM OrleansGrainDirectory WHERE ClusterId = @ClusterId AND ProviderId = @ProviderId AND GrainId = @GrainId; END ELSE BEGIN /* If the entry does not yet exist, we will add it now. */ INSERT INTO OrleansGrainDirectory ( ClusterId, ProviderId, GrainId, SiloAddress, ActivationId, CreatedOn ) OUTPUT INSERTED.ClusterId, INSERTED.ProviderId, INSERTED.GrainId, INSERTED.SiloAddress, INSERTED.ActivationId VALUES ( @ClusterId, @ProviderId, @GrainId, @SiloAddress, @ActivationId, @Now ); END COMMIT; GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'RegisterGrainActivationKey', 'EXECUTE RegisterGrainActivation @ClusterId = @ClusterId, @ProviderId = @ProviderId, @GrainId = @GrainId, @SiloAddress = @SiloAddress, @ActivationId = @ActivationId' GO /* Unregisters an existing grain activation */ CREATE PROCEDURE UnregisterGrainActivation @ClusterId VARCHAR(150), @ProviderId VARCHAR(150), @GrainId VARCHAR(600), @ActivationId VARCHAR(100) AS SET NOCOUNT ON; SET XACT_ABORT ON; /* Delete the entry if it exists. */ /* This places a lock on the hash index pages upfront to prevent deadlocks. */ DELETE OrleansGrainDirectory FROM OrleansGrainDirectory WHERE ClusterId = @ClusterId AND ProviderId = @ProviderId AND GrainId = @GrainId AND ActivationId = @ActivationId; SELECT @@ROWCOUNT; GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationKey', 'EXECUTE UnregisterGrainActivation @ClusterId = @ClusterId, @ProviderId = @ProviderId, @GrainId = @GrainId, @ActivationId = @ActivationId' GO /* Looks up an existing grain activation */ CREATE PROCEDURE LookupGrainActivation @ClusterId VARCHAR(150), @ProviderId VARCHAR(150), @GrainId VARCHAR(600) AS SET NOCOUNT ON; SET XACT_ABORT ON; /* Get the existing entry if it exists. */ /* This also places a lock on the hash index pages upfront to prevent deadlocks with registration. */ SELECT ClusterId, ProviderId, GrainId, SiloAddress, ActivationId FROM OrleansGrainDirectory WHERE ClusterId = @ClusterId AND ProviderId = @ProviderId AND GrainId = @GrainId; GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'LookupGrainActivationKey', 'EXECUTE LookupGrainActivation @ClusterId = @ClusterId, @ProviderId = @ProviderId, @GrainId = @GrainId' GO /* Unregisters all grain activations in the specified silos */ CREATE PROCEDURE UnregisterGrainActivations @ClusterId VARCHAR(150), @ProviderId VARCHAR(150), @SiloAddresses VARCHAR(MAX) AS SET NOCOUNT ON; SET XACT_ABORT ON; BEGIN TRANSACTION; /* Delete the entries if they exist. */ /* This places a exclusive lock on the entire table to prevent deadlocks with registration. */ DELETE OrleansGrainDirectory FROM OrleansGrainDirectory WITH (TABLOCKX) WHERE ClusterId = @ClusterId AND ProviderId = @ProviderId AND SiloAddress IN (SELECT Value FROM STRING_SPLIT(@SiloAddresses, '|')); SELECT @@ROWCOUNT; COMMIT TRANSACTION; GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'UnregisterGrainActivationsKey', 'EXECUTE UnregisterGrainActivations @ClusterId = @ClusterId, @ProviderId = @ProviderId, @SiloAddresses = @SiloAddresses' GO ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/AdoNetGrainStorageProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Storage; [assembly: RegisterProvider("AdoNet", "GrainStorage", "Silo", typeof(AdoNetGrainStorageProviderBuilder))] namespace Orleans.Hosting; internal sealed class AdoNetGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAdoNetGrainStorage(name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var invariant = configurationSection[nameof(options.Invariant)]; if (!string.IsNullOrEmpty(invariant)) { options.Invariant = invariant; } var connectionString = configurationSection[nameof(options.ConnectionString)]; var connectionName = configurationSection["ConnectionName"]; if (string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(connectionName)) { connectionString = services.GetRequiredService().GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConnectionString = connectionString; } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } })); } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Migrations/PostgreSQL-Persistence-3.6.0.sql ================================================ -- Run this migration for upgrading the PostgreSQL persistence table and routines for deployments created before 3.6.0 BEGIN; -- Change date type ALTER TABLE OrleansStorage ALTER COLUMN modifiedon TYPE TIMESTAMPTZ USING modifiedon AT TIME ZONE 'UTC'; -- Recreate routines CREATE OR REPLACE FUNCTION writetostorage( _grainidhash integer, _grainidn0 bigint, _grainidn1 bigint, _graintypehash integer, _graintypestring character varying, _grainidextensionstring character varying, _serviceid character varying, _grainstateversion integer, _payloadbinary bytea) RETURNS TABLE(newgrainstateversion integer) LANGUAGE 'plpgsql' AS $function$ DECLARE _newGrainStateVersion integer := _GrainStateVersion; RowCountVar integer := 0; BEGIN -- Grain state is not null, so the state must have been read from the storage before. -- Let's try to update it. -- -- When Orleans is running in normal, non-split state, there will -- be only one grain with the given ID and type combination only. This -- grain saves states mostly serially if Orleans guarantees are upheld. Even -- if not, the updates should work correctly due to version number. -- -- In split brain situations there can be a situation where there are two or more -- grains with the given ID and type combination. When they try to INSERT -- concurrently, the table needs to be locked pessimistically before one of -- the grains gets @GrainStateVersion = 1 in return and the other grains will fail -- to update storage. The following arrangement is made to reduce locking in normal operation. -- -- If the version number explicitly returned is still the same, Orleans interprets it so the update did not succeed -- and throws an InconsistentStateException. -- -- See further information at https://learn.microsoft.com/dotnet/orleans/grains/grain-persistence. IF _GrainStateVersion IS NOT NULL THEN UPDATE OrleansStorage SET PayloadBinary = _PayloadBinary, ModifiedOn = (now() at time zone 'utc'), Version = Version + 1 WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = _GrainStateVersion AND _GrainStateVersion IS NOT NULL; GET DIAGNOSTICS RowCountVar = ROW_COUNT; IF RowCountVar > 0 THEN _newGrainStateVersion := _GrainStateVersion + 1; END IF; END IF; -- The grain state has not been read. The following locks rather pessimistically -- to ensure only one INSERT succeeds. IF _GrainStateVersion IS NULL THEN INSERT INTO OrleansStorage ( GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version ) SELECT _GrainIdHash, _GrainIdN0, _GrainIdN1, _GrainTypeHash, _GrainTypeString, _GrainIdExtensionString, _ServiceId, _PayloadBinary, now(), 1 WHERE NOT EXISTS ( -- There should not be any version of this grain state. SELECT 1 FROM OrleansStorage WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL ); GET DIAGNOSTICS RowCountVar = ROW_COUNT; IF RowCountVar > 0 THEN _newGrainStateVersion := 1; END IF; END IF; RETURN QUERY SELECT _newGrainStateVersion AS NewGrainStateVersion; END $function$; COMMIT; ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/MySQL-Persistence.sql ================================================ -- The design criteria for this table are: -- -- 1. It can contain arbitrary content serialized as binary, XML or JSON. These formats -- are supported to allow one to take advantage of in-storage processing capabilities for -- these types if required. This should not incur extra cost on storage. -- -- 2. The table design should scale with the idea of tens or hundreds (or even more) types -- of grains that may operate with even hundreds of thousands of grain IDs within each -- type of a grain. -- -- 3. The table and its associated operations should remain stable. There should not be -- structural reason for unexpected delays in operations. It should be possible to also -- insert data reasonably fast without resource contention. -- -- 4. For reasons in 2. and 3., the index should be as narrow as possible so it fits well in -- memory and should it require maintenance, isn't resource intensive. For this -- reason the index is narrow by design (ideally non-clustered). Currently the entity -- is recognized in the storage by the grain type and its ID, which are unique in Orleans silo. -- The ID is the grain ID bytes (if string type UTF-8 bytes) and possible extension key as UTF-8 -- bytes concatenated with the ID and then hashed. -- -- Reason for hashing: Database engines usually limit the length of the column sizes, which -- would artificially limit the length of IDs or types. Even when within limitations, the -- index would be thick and consume more memory. -- -- In the current setup the ID and the type are hashed into two INT type instances, which -- are made a compound index. When there are no collisions, the index can quickly locate -- the unique row. Along with the hashed index values, the NVARCHAR(nnn) values are also -- stored and they are used to prune hash collisions down to only one result row. -- -- 5. The design leads to duplication in the storage. It is reasonable to assume there will -- a low number of services with a given service ID operational at any given time. Or that -- compared to the number of grain IDs, there are a fairly low number of different types of -- grain. The catch is that were these data separated to another table, it would make INSERT -- and UPDATE operations complicated and would require joins, temporary variables and additional -- indexes or some combinations of them to make it work. It looks like fitting strategy -- could be to use table compression. -- -- 6. For the aforementioned reasons, grain state DELETE will set NULL to the data fields -- and updates the Version number normally. This should alleviate the need for index or -- statistics maintenance with the loss of some bytes of storage space. The table can be scrubbed -- in a separate maintenance operation. -- -- 7. In the storage operations queries the columns need to be in the exact same order -- since the storage table operations support optionally streaming. CREATE TABLE OrleansStorage ( -- These are for the book keeping. Orleans calculates -- these hashes (see RelationalStorageProvide implementation), -- which are signed 32 bit integers mapped to the *Hash fields. -- The mapping is done in the code. The -- *String columns contain the corresponding clear name fields. -- -- If there are duplicates, they are resolved by using GrainIdN0, -- GrainIdN1, GrainIdExtensionString and GrainTypeString fields. -- It is assumed these would be rarely needed. GrainIdHash INT NOT NULL, GrainIdN0 BIGINT NOT NULL, GrainIdN1 BIGINT NOT NULL, GrainTypeHash INT NOT NULL, GrainTypeString NVARCHAR(512) NOT NULL, GrainIdExtensionString NVARCHAR(512) NULL, ServiceId NVARCHAR(150) NOT NULL, -- Payload PayloadBinary BLOB NULL, -- Informational field, no other use. ModifiedOn DATETIME NOT NULL, -- The version of the stored payload. Version INT NULL -- The following would in principle be the primary key, but it would be too thick -- to be indexed, so the values are hashed and only collisions will be solved -- by using the fields. That is, after the indexed queries have pinpointed the right -- rows down to [0, n] relevant ones, n being the number of collided value pairs. ) ROW_FORMAT = COMPRESSED KEY_BLOCK_SIZE = 16; ALTER TABLE OrleansStorage ADD INDEX IX_OrleansStorage (GrainIdHash, GrainTypeHash); DELIMITER $$ CREATE PROCEDURE ClearStorage ( in _GrainIdHash INT, in _GrainIdN0 BIGINT, in _GrainIdN1 BIGINT, in _GrainTypeHash INT, in _GrainTypeString NVARCHAR(512), in _GrainIdExtensionString NVARCHAR(512), in _ServiceId NVARCHAR(150), in _GrainStateVersion INT ) BEGIN DECLARE _newGrainStateVersion INT; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; DECLARE EXIT HANDLER FOR SQLWARNING BEGIN ROLLBACK; RESIGNAL; END; SET _newGrainStateVersion = _GrainStateVersion; -- Default level is REPEATABLE READ and may cause Gap Lock issues SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; UPDATE OrleansStorage SET PayloadBinary = NULL, Version = Version + 1 WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = _GrainStateVersion AND _GrainStateVersion IS NOT NULL LIMIT 1; IF ROW_COUNT() > 0 THEN SET _newGrainStateVersion = _GrainStateVersion + 1; END IF; SELECT _newGrainStateVersion AS NewGrainStateVersion; COMMIT; END$$ CREATE PROCEDURE DeleteStorage ( in _GrainIdHash INT, in _GrainIdN0 BIGINT, in _GrainIdN1 BIGINT, in _GrainTypeHash INT, in _GrainTypeString NVARCHAR(512), in _GrainIdExtensionString NVARCHAR(512), in _ServiceId NVARCHAR(150), in _GrainStateVersion INT ) BEGIN DECLARE _newGrainStateVersion INT; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; DECLARE EXIT HANDLER FOR SQLWARNING BEGIN ROLLBACK; RESIGNAL; END; SET _newGrainStateVersion = _GrainStateVersion; -- Default level is REPEATABLE READ and may cause Gap Lock issues SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; DELETE FROM OrleansStorage WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = _GrainStateVersion AND _GrainStateVersion IS NOT NULL LIMIT 1; IF ROW_COUNT() > 0 THEN SET _newGrainStateVersion = _GrainStateVersion + 1; END IF; SELECT _newGrainStateVersion AS NewGrainStateVersion; COMMIT; END$$ DELIMITER $$ CREATE PROCEDURE WriteToStorage ( in _GrainIdHash INT, in _GrainIdN0 BIGINT, in _GrainIdN1 BIGINT, in _GrainTypeHash INT, in _GrainTypeString NVARCHAR(512), in _GrainIdExtensionString NVARCHAR(512), in _ServiceId NVARCHAR(150), in _GrainStateVersion INT, in _PayloadBinary BLOB ) BEGIN DECLARE _newGrainStateVersion INT; DECLARE _rowCount INT; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; DECLARE EXIT HANDLER FOR SQLWARNING BEGIN ROLLBACK; RESIGNAL; END; SET _newGrainStateVersion = _GrainStateVersion; -- Default level is REPEATABLE READ and may cause Gap Lock issues SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; -- Grain state is not null, so the state must have been read from the storage before. -- Let's try to update it. -- -- When Orleans is running in normal, non-split state, there will -- be only one grain with the given ID and type combination only. This -- grain saves states mostly serially if Orleans guarantees are upheld. Even -- if not, the updates should work correctly due to version number. -- -- In split brain situations there can be a situation where there are two or more -- grains with the given ID and type combination. When they try to INSERT -- concurrently, the table needs to be locked pessimistically before one of -- the grains gets @GrainStateVersion = 1 in return and the other grains will fail -- to update storage. The following arrangement is made to reduce locking in normal operation. -- -- If the version number explicitly returned is still the same, Orleans interprets it so the update did not succeed -- and throws an InconsistentStateException. -- -- See further information at https://learn.microsoft.com/dotnet/orleans/grains/grain-persistence. IF _GrainStateVersion IS NOT NULL THEN UPDATE OrleansStorage SET PayloadBinary = _PayloadBinary, ModifiedOn = UTC_TIMESTAMP(), Version = Version + 1 WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = _GrainStateVersion AND _GrainStateVersion IS NOT NULL LIMIT 1; IF ROW_COUNT() > 0 THEN SET _newGrainStateVersion = _GrainStateVersion + 1; SET _GrainStateVersion = _newGrainStateVersion; END IF; END IF; -- The grain state has not been read. The following locks rather pessimistically -- to ensure only on INSERT succeeds. IF _GrainStateVersion IS NULL THEN INSERT INTO OrleansStorage ( GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version ) SELECT * FROM ( SELECT _GrainIdHash, _GrainIdN0, _GrainIdN1, _GrainTypeHash, _GrainTypeString, _GrainIdExtensionString, _ServiceId, _PayloadBinary, UTC_TIMESTAMP(), 1) AS TMP WHERE NOT EXISTS ( -- There should not be any version of this grain state. SELECT 1 FROM OrleansStorage WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL ) LIMIT 1; IF ROW_COUNT() > 0 THEN SET _newGrainStateVersion = 1; END IF; END IF; SELECT _newGrainStateVersion AS NewGrainStateVersion; COMMIT; END$$ DELIMITER ; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadFromStorageKey', 'SELECT PayloadBinary, UTC_TIMESTAMP(), Version FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND GrainIdN0 = @GrainIdN0 AND @GrainIdN0 IS NOT NULL AND GrainIdN1 = @GrainIdN1 AND @GrainIdN1 IS NOT NULL AND GrainTypeString = @GrainTypeString AND GrainTypeString IS NOT NULL AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL LIMIT 1;' ); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'WriteToStorageKey',' call WriteToStorage(@GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @GrainStateVersion, @PayloadBinary);' ); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ClearStorageKey',' call ClearStorage(@GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @GrainStateVersion);' ); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteStorageKey',' call DeleteStorage(@GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @GrainStateVersion);' ); ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Options/AdoNetGrainStorageOptions.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Persistence.AdoNet.Storage; using Orleans.Runtime; using Orleans.Storage; namespace Orleans.Configuration { /// /// Options for ADO.NET grain storage. /// public class AdoNetGrainStorageOptions : IStorageProviderSerializerOptions { /// /// Connection string for AdoNet storage. /// [Redact] public string ConnectionString { get; set; } /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; /// /// Default init stage in silo lifecycle. /// public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// /// The default ADO.NET invariant used for storage if none is given. /// public const string DEFAULT_ADONET_INVARIANT = AdoNetInvariants.InvariantNameSqlServer; /// /// The invariant name for storage. /// public string Invariant { get; set; } = DEFAULT_ADONET_INVARIANT; /// public IGrainStorageSerializer GrainStorageSerializer { get; set; } /// /// Gets or sets the hasher picker to use for this storage provider. /// public IStorageHasherPicker HashPicker { get; set; } /// /// Sets legacy Orleans v3-compatible hash picker to use for this storage provider. Invoke this method if you need to run /// Orleans v7+ silo against existing Orleans v3-initialized database and keep existing grain state. /// public void UseOrleans3CompatibleHasher() { // content-aware hashing with different pickers, unable to use standard StorageHasherPicker this.HashPicker = new Orleans3CompatibleStorageHashPicker(); } /// /// Delete record row from db when state is cleared. /// public bool DeleteStateOnClear { get; set; } } /// /// ConfigurationValidator for AdoNetGrainStorageOptions /// public class AdoNetGrainStorageOptionsValidator : IConfigurationValidator { private readonly AdoNetGrainStorageOptions options; private readonly string name; /// /// Constructor /// /// The option to be validated. /// The name of the option to be validated. public AdoNetGrainStorageOptionsValidator(AdoNetGrainStorageOptions configurationOptions, string name) { this.options = configurationOptions ?? throw new OrleansConfigurationException($"Invalid AdoNetGrainStorageOptions for AdoNetGrainStorage {name}. Options is required."); this.name = name; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainStorageOptions)} values for {nameof(AdoNetGrainStorage)} \"{name}\". {nameof(options.Invariant)} is required."); } if (string.IsNullOrWhiteSpace(this.options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainStorageOptions)} values for {nameof(AdoNetGrainStorage)} \"{name}\". {nameof(options.ConnectionString)} is required."); } if (this.options.HashPicker == null) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetGrainStorageOptions)} values for {nameof(AdoNetGrainStorage)} {name}. {nameof(options.HashPicker)} is required."); } } } /// /// Provides default configuration HashPicker for AdoNetGrainStorageOptions. /// public class DefaultAdoNetGrainStorageOptionsHashPickerConfigurator : IPostConfigureOptions { public void PostConfigure(string name, AdoNetGrainStorageOptions options) { // preserving explicitly configured HashPicker if (options.HashPicker != null) return; // set default IHashPicker if not configured yet options.HashPicker = new StorageHasherPicker(new[] { new OrleansDefaultHasher() }); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Oracle-Persistence.sql ================================================ -- The design criteria for this table are: -- -- 1. It can contain arbitrary content serialized as binary, XML or JSON. These formats -- are supported to allow one to take advantage of in-storage processing capabilities for -- these types if required. This should not incur extra cost on storage. -- -- 2. The table design should scale with the idea of tens or hundreds (or even more) types -- of grains that may operate with even hundreds of thousands of grain IDs within each -- type of a grain. -- -- 3. The table and its associated operations should remain stable. There should not be -- structural reason for unexpected delays in operations. It should be possible to also -- insert data reasonably fast without resource contention. -- -- 4. For reasons in 2. and 3., the index should be as narrow as possible so it fits well in -- memory and should it require maintenance, isn't resource intensive. For this -- reason the index is narrow by design (ideally non-clustered). Currently the entity -- is recognized in the storage by the grain type and its ID, which are unique in Orleans silo. -- The ID is the grain ID bytes (if string type UTF-8 bytes) and possible extension key as UTF-8 -- bytes concatenated with the ID and then hashed. -- -- Reason for hashing: Database engines usually limit the length of the column sizes, which -- would artificially limit the length of IDs or types. Even when within limitations, the -- index would be thick and consume more memory. -- -- In the current setup the ID and the type are hashed into two INT type instances, which -- are made a compound index. When there are no collisions, the index can quickly locate -- the unique row. Along with the hashed index values, the NVARCHAR(nnn) values are also -- stored and they are used to prune hash collisions down to only one result row. -- -- 5. The design leads to duplication in the storage. It is reasonable to assume there will -- a low number of services with a given service ID operational at any given time. Or that -- compared to the number of grain IDs, there are a fairly low number of different types of -- grain. The catch is that were these data separated to another table, it would make INSERT -- and UPDATE operations complicated and would require joins, temporary variables and additional -- indexes or some combinations of them to make it work. It looks like fitting strategy -- could be to use table compression. -- -- 6. For the aforementioned reasons, grain state DELETE will set NULL to the data fields -- and updates the Version number normally. This should alleviate the need for index or -- statistics maintenance with the loss of some bytes of storage space. The table can be scrubbed -- in a separate maintenance operation. -- -- 7. In the storage operations queries the columns need to be in the exact same order -- since the storage table operations support optionally streaming. CREATE TABLE "ORLEANSSTORAGE" ( -- These are for the book keeping. Orleans calculates -- these hashes (see RelationalStorageProvide implementation), -- which are signed 32 bit integers mapped to the *Hash fields. -- The mapping is done in the code. The -- *String columns contain the corresponding clear name fields. -- -- If there are duplicates, they are resolved by using GrainIdN0, -- GrainIdN1, GrainIdExtensionString and GrainTypeString fields. -- It is assumed these would be rarely needed. "GRAINIDHASH" NUMBER(*,0) NOT NULL ENABLE, "GRAINIDN0" NUMBER(19,0) NOT NULL ENABLE, "GRAINIDN1" NUMBER(19,0) NOT NULL ENABLE, "GRAINTYPEHASH" NUMBER(*,0) NOT NULL ENABLE, "GRAINTYPESTRING" NVARCHAR2(512) NOT NULL ENABLE, "GRAINIDEXTENSIONSTRING" NVARCHAR2(512), "SERVICEID" NVARCHAR2(150) NOT NULL ENABLE, -- Payload "PAYLOADBINARY" BLOB, -- Informational field, no other use. "MODIFIEDON" TIMESTAMP (6) NOT NULL ENABLE, -- The version of the stored payload. "VERSION" NUMBER(*,0) -- The following would in principle be the primary key, but it would be too thick -- to be indexed, so the values are hashed and only collisions will be solved -- by using the fields. That is, after the indexed queries have pinpointed the right -- rows down to [0, n] relevant ones, n being the number of collided value pairs. ); CREATE INDEX "IX_ORLEANSSTORAGE" ON "ORLEANSSTORAGE" ("GRAINIDHASH", "GRAINTYPEHASH") PARALLEL COMPRESS; / CREATE OR REPLACE FUNCTION WriteToStorage(PARAM_GRAINIDHASH IN NUMBER, PARAM_GRAINIDN0 IN NUMBER, PARAM_GRAINIDN1 IN NUMBER, PARAM_GRAINTYPEHASH IN NUMBER, PARAM_GRAINTYPESTRING IN NVARCHAR2, PARAM_GRAINIDEXTENSIONSTRING IN NVARCHAR2, PARAM_SERVICEID IN VARCHAR2, PARAM_GRAINSTATEVERSION IN NUMBER, PARAM_PAYLOADBINARY IN BLOB) RETURN NUMBER IS rowcount NUMBER; newGrainStateVersion NUMBER := PARAM_GRAINSTATEVERSION; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN -- When Orleans is running in normal, non-split state, there will -- be only one grain with the given ID and type combination only. This -- grain saves states mostly serially if Orleans guarantees are upheld. Even -- if not, the updates should work correctly due to version number. -- -- In split brain situations there can be a situation where there are two or more -- grains with the given ID and type combination. When they try to INSERT -- concurrently, the table needs to be locked pessimistically before one of -- the grains gets @GrainStateVersion = 1 in return and the other grains will fail -- to update storage. The following arrangement is made to reduce locking in normal operation. -- -- If the version number explicitly returned is still the same, Orleans interprets it so the update did not succeed -- and throws an InconsistentStateException. -- -- See further information at https://learn.microsoft.com/dotnet/orleans/grains/grain-persistence. -- If the @GrainStateVersion is not zero, this branch assumes it exists in this database. -- The NULL value is supplied by Orleans when the state is new. IF newGrainStateVersion IS NOT NULL THEN UPDATE OrleansStorage SET PayloadBinary = PARAM_PAYLOADBINARY, ModifiedOn = sys_extract_utc(systimestamp), Version = Version + 1 WHERE GrainIdHash = PARAM_GRAINIDHASH AND PARAM_GRAINIDHASH IS NOT NULL AND GrainTypeHash = PARAM_GRAINTYPEHASH AND PARAM_GRAINTYPEHASH IS NOT NULL AND (GrainIdN0 = PARAM_GRAINIDN0 OR PARAM_GRAINIDN0 IS NULL) AND (GrainIdN1 = PARAM_GRAINIDN1 OR PARAM_GRAINIDN1 IS NULL) AND (GrainTypeString = PARAM_GRAINTYPESTRING OR PARAM_GRAINTYPESTRING IS NULL) AND ((PARAM_GRAINIDEXTENSIONSTRING IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = PARAM_GRAINIDEXTENSIONSTRING) OR PARAM_GRAINIDEXTENSIONSTRING IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL AND Version IS NOT NULL AND Version = PARAM_GRAINSTATEVERSION AND PARAM_GRAINSTATEVERSION IS NOT NULL RETURNING Version INTO newGrainStateVersion; rowcount := SQL%ROWCOUNT; IF rowcount = 1 THEN COMMIT; RETURN(newGrainStateVersion); END IF; END IF; -- The grain state has not been read. The following locks rather pessimistically -- to ensure only one INSERT succeeds. IF PARAM_GRAINSTATEVERSION IS NULL THEN INSERT INTO OrleansStorage ( GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version ) SELECT PARAM_GRAINIDHASH, PARAM_GRAINIDN0, PARAM_GRAINIDN1, PARAM_GRAINTYPEHASH, PARAM_GRAINTYPESTRING, PARAM_GRAINIDEXTENSIONSTRING, PARAM_SERVICEID, PARAM_PAYLOADBINARY, sys_extract_utc(systimestamp), 1 FROM DUAL WHERE NOT EXISTS ( -- There should not be any version of this grain state. SELECT 1 FROM OrleansStorage WHERE GrainIdHash = PARAM_GRAINIDHASH AND PARAM_GRAINIDHASH IS NOT NULL AND GrainTypeHash = PARAM_GRAINTYPEHASH AND PARAM_GRAINTYPEHASH IS NOT NULL AND (GrainIdN0 = PARAM_GRAINIDN0 OR PARAM_GRAINIDN0 IS NULL) AND (GrainIdN1 = PARAM_GRAINIDN1 OR PARAM_GRAINIDN1 IS NULL) AND (GrainTypeString = PARAM_GRAINTYPESTRING OR PARAM_GRAINTYPESTRING IS NULL) AND ((PARAM_GRAINIDEXTENSIONSTRING IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = PARAM_GRAINIDEXTENSIONSTRING) OR PARAM_GRAINIDEXTENSIONSTRING IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL ); rowCount := SQL%ROWCOUNT; IF rowCount > 0 THEN newGrainStateVersion := 1; END IF; END IF; COMMIT; RETURN(newGrainStateVersion); END; / CREATE OR REPLACE FUNCTION ClearStorage(PARAM_GRAINIDHASH IN NUMBER, PARAM_GRAINIDN0 IN NUMBER, PARAM_GRAINIDN1 IN NUMBER, PARAM_GRAINTYPEHASH IN NUMBER, PARAM_GRAINTYPESTRING IN NVARCHAR2, PARAM_GRAINIDEXTENSIONSTRING IN NVARCHAR2, PARAM_SERVICEID IN VARCHAR2, PARAM_GRAINSTATEVERSION IN NUMBER) RETURN NUMBER IS rowcount NUMBER; newGrainStateVersion NUMBER := PARAM_GRAINSTATEVERSION; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN UPDATE OrleansStorage SET PayloadBinary = NULL, ModifiedOn = sys_extract_utc(systimestamp), Version = Version + 1 WHERE GrainIdHash = PARAM_GRAINIDHASH AND PARAM_GRAINIDHASH IS NOT NULL AND GrainTypeHash = PARAM_GRAINTYPEHASH AND PARAM_GRAINTYPEHASH IS NOT NULL AND (GrainIdN0 = PARAM_GRAINIDN0 OR PARAM_GRAINIDN0 IS NULL) AND (GrainIdN1 = PARAM_GRAINIDN1 OR PARAM_GRAINIDN1 IS NULL) AND (GrainTypeString = PARAM_GRAINTYPESTRING OR PARAM_GRAINTYPESTRING IS NULL) AND ((PARAM_GRAINIDEXTENSIONSTRING IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = PARAM_GRAINIDEXTENSIONSTRING) OR PARAM_GRAINIDEXTENSIONSTRING IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL AND Version IS NOT NULL AND Version = PARAM_GRAINSTATEVERSION AND PARAM_GRAINSTATEVERSION IS NOT NULL RETURNING Version INTO newGrainStateVersion; COMMIT; RETURN(newGrainStateVersion); END; / CREATE OR REPLACE FUNCTION DeleteStorage(PARAM_GRAINIDHASH IN NUMBER, PARAM_GRAINIDN0 IN NUMBER, PARAM_GRAINIDN1 IN NUMBER, PARAM_GRAINTYPEHASH IN NUMBER, PARAM_GRAINTYPESTRING IN NVARCHAR2, PARAM_GRAINIDEXTENSIONSTRING IN NVARCHAR2, PARAM_SERVICEID IN VARCHAR2, PARAM_GRAINSTATEVERSION IN NUMBER) RETURN NUMBER IS rowcount NUMBER; newGrainStateVersion NUMBER := PARAM_GRAINSTATEVERSION; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN DELETE FROM OrleansStorage WHERE GrainIdHash = PARAM_GRAINIDHASH AND PARAM_GRAINIDHASH IS NOT NULL AND GrainTypeHash = PARAM_GRAINTYPEHASH AND PARAM_GRAINTYPEHASH IS NOT NULL AND (GrainIdN0 = PARAM_GRAINIDN0 OR PARAM_GRAINIDN0 IS NULL) AND (GrainIdN1 = PARAM_GRAINIDN1 OR PARAM_GRAINIDN1 IS NULL) AND (GrainTypeString = PARAM_GRAINTYPESTRING OR PARAM_GRAINTYPESTRING IS NULL) AND ((PARAM_GRAINIDEXTENSIONSTRING IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = PARAM_GRAINIDEXTENSIONSTRING) OR PARAM_GRAINIDEXTENSIONSTRING IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL AND Version IS NOT NULL AND Version = PARAM_GRAINSTATEVERSION AND PARAM_GRAINSTATEVERSION IS NOT NULL; rowCount := SQL%ROWCOUNT; IF rowCount > 0 THEN newGrainStateVersion := PARAM_GRAINSTATEVERSION + 1; END IF; COMMIT; RETURN(newGrainStateVersion); END; / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'WriteToStorageKey',' SELECT WriteToStorage(:GrainIdHash, :GrainIdN0, :GrainIdN1, :GrainTypeHash, :GrainTypeString, :GrainIdExtensionString, :ServiceId, :GrainStateVersion, :PayloadBinary) AS NewGrainStateVersion FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ClearStorageKey', 'SELECT ClearStorage(:GrainIdHash, :GrainIdN0, :GrainIdN1, :GrainTypeHash, :GrainTypeString, :GrainIdExtensionString, :ServiceId, :GrainStateVersion) AS Version FROM DUAL' ); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteStorageKey', 'SELECT DeleteStorage(:GrainIdHash, :GrainIdN0, :GrainIdN1, :GrainTypeHash, :GrainTypeString, :GrainIdExtensionString, :ServiceId, :GrainStateVersion) AS Version FROM DUAL' ); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadFromStorageKey', ' SELECT PayloadBinary, Version FROM OrleansStorage WHERE GrainIdHash = :GrainIdHash AND :GrainIdHash IS NOT NULL AND (GrainIdN0 = :GrainIdN0 OR :GrainIdN0 IS NULL) AND (GrainIdN1 = :GrainIdN1 OR :GrainIdN1 IS NULL) AND GrainTypeHash = :GrainTypeHash AND :GrainTypeHash IS NOT NULL AND (GrainTypeString = :GrainTypeString OR :GrainTypeString IS NULL) AND ((:GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = :GrainIdExtensionString) OR :GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = :ServiceId AND :ServiceId IS NOT NULL' ); / COMMIT; ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Orleans.Persistence.AdoNet.csproj ================================================ Microsoft.Orleans.Persistence.AdoNet Microsoft Orleans ADO.NET Persistence Provider Microsoft Orleans persistence providers backed by ADO.NET $(PackageTags) ADO.NET SQL MySQL PostgreSQL Oracle $(DefaultTargetFrameworks) true README.md Orleans.Persistence.AdoNet Orleans.Persistence.AdoNet $(DefineConstants);PERSISTENCE_ADONET ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/PostgreSQL-Persistence.sql ================================================ CREATE TABLE OrleansStorage ( grainidhash integer NOT NULL, grainidn0 bigint NOT NULL, grainidn1 bigint NOT NULL, graintypehash integer NOT NULL, graintypestring character varying(512) NOT NULL, grainidextensionstring character varying(512) , serviceid character varying(150) NOT NULL, payloadbinary bytea, modifiedon timestamp without time zone NOT NULL, version integer ); CREATE INDEX ix_orleansstorage ON orleansstorage USING btree (grainidhash, graintypehash); CREATE OR REPLACE FUNCTION writetostorage( _grainidhash integer, _grainidn0 bigint, _grainidn1 bigint, _graintypehash integer, _graintypestring character varying, _grainidextensionstring character varying, _serviceid character varying, _grainstateversion integer, _payloadbinary bytea) RETURNS TABLE(newgrainstateversion integer) LANGUAGE 'plpgsql' AS $function$ DECLARE _newGrainStateVersion integer := _GrainStateVersion; RowCountVar integer := 0; BEGIN -- Grain state is not null, so the state must have been read from the storage before. -- Let's try to update it. -- -- When Orleans is running in normal, non-split state, there will -- be only one grain with the given ID and type combination only. This -- grain saves states mostly serially if Orleans guarantees are upheld. Even -- if not, the updates should work correctly due to version number. -- -- In split brain situations there can be a situation where there are two or more -- grains with the given ID and type combination. When they try to INSERT -- concurrently, the table needs to be locked pessimistically before one of -- the grains gets @GrainStateVersion = 1 in return and the other grains will fail -- to update storage. The following arrangement is made to reduce locking in normal operation. -- -- If the version number explicitly returned is still the same, Orleans interprets it so the update did not succeed -- and throws an InconsistentStateException. -- -- See further information at https://learn.microsoft.com/dotnet/orleans/grains/grain-persistence. IF _GrainStateVersion IS NOT NULL THEN UPDATE OrleansStorage SET PayloadBinary = _PayloadBinary, ModifiedOn = (now() at time zone 'utc'), Version = Version + 1 WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = _GrainStateVersion AND _GrainStateVersion IS NOT NULL; GET DIAGNOSTICS RowCountVar = ROW_COUNT; IF RowCountVar > 0 THEN _newGrainStateVersion := _GrainStateVersion + 1; END IF; END IF; -- The grain state has not been read. The following locks rather pessimistically -- to ensure only one INSERT succeeds. IF _GrainStateVersion IS NULL THEN INSERT INTO OrleansStorage ( GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version ) SELECT _GrainIdHash, _GrainIdN0, _GrainIdN1, _GrainTypeHash, _GrainTypeString, _GrainIdExtensionString, _ServiceId, _PayloadBinary, (now() at time zone 'utc'), 1 WHERE NOT EXISTS ( -- There should not be any version of this grain state. SELECT 1 FROM OrleansStorage WHERE GrainIdHash = _GrainIdHash AND _GrainIdHash IS NOT NULL AND GrainTypeHash = _GrainTypeHash AND _GrainTypeHash IS NOT NULL AND GrainIdN0 = _GrainIdN0 AND _GrainIdN0 IS NOT NULL AND GrainIdN1 = _GrainIdN1 AND _GrainIdN1 IS NOT NULL AND GrainTypeString = _GrainTypeString AND _GrainTypeString IS NOT NULL AND ((_GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = _GrainIdExtensionString) OR _GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = _ServiceId AND _ServiceId IS NOT NULL ); GET DIAGNOSTICS RowCountVar = ROW_COUNT; IF RowCountVar > 0 THEN _newGrainStateVersion := 1; END IF; END IF; RETURN QUERY SELECT _newGrainStateVersion AS NewGrainStateVersion; END $function$; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'WriteToStorageKey',' select * from WriteToStorage(@GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @GrainStateVersion, @PayloadBinary); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadFromStorageKey',' SELECT PayloadBinary, (now() at time zone ''utc''), Version FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND GrainIdN0 = @GrainIdN0 AND @GrainIdN0 IS NOT NULL AND GrainIdN1 = @GrainIdN1 AND @GrainIdN1 IS NOT NULL AND GrainTypeString = @GrainTypeString AND GrainTypeString IS NOT NULL AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ClearStorageKey',' UPDATE OrleansStorage SET PayloadBinary = NULL, Version = Version + 1 WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND GrainIdN0 = @GrainIdN0 AND @GrainIdN0 IS NOT NULL AND GrainIdN1 = @GrainIdN1 AND @GrainIdN1 IS NOT NULL AND GrainTypeString = @GrainTypeString AND @GrainTypeString IS NOT NULL AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL Returning Version as NewGrainStateVersion '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteStorageKey',' DELETE FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND GrainIdN0 = @GrainIdN0 AND @GrainIdN0 IS NOT NULL AND GrainIdN1 = @GrainIdN1 AND @GrainIdN1 IS NOT NULL AND GrainTypeString = @GrainTypeString AND @GrainTypeString IS NOT NULL AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL Returning Version + 1 as NewGrainStateVersion '); ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/README.md ================================================ # Microsoft Orleans Persistence for ADO.NET ## Introduction Microsoft Orleans Persistence for ADO.NET provides grain persistence for Microsoft Orleans using relational databases through ADO.NET. This provider allows your grains to persist their state in various relational databases including SQL Server, MySQL, PostgreSQL, and Oracle. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Persistence.AdoNet ``` You will also need to install the appropriate database driver package for your database system: - SQL Server: `Microsoft.Data.SqlClient` - MySQL: `MySql.Data` or `MySqlConnector` - PostgreSQL: `Npgsql` - Oracle: `Oracle.ManagedDataAccess.Core` - SQLite: `Microsoft.Data.Sqlite` ## Example - Configuring ADO.NET Persistence ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure ADO.NET as grain storage .AddAdoNetGrainStorage( name: "AdoNetStore", configureOptions: options => { options.Invariant = "Microsoft.Data.SqlClient"; // Or other providers like "MySql.Data.MySqlClient", "Npgsql", etc. options.ConnectionString = "Server=localhost;Database=OrleansStorage;User Id=myUsername;******;"; // Optional: Configure custom queries }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Grain Storage in a Grain ```csharp using System; using System.Threading.Tasks; using Orleans; using Orleans.Runtime; // Define grain state class public class MyGrainState { public string Data { get; set; } public int Version { get; set; } } // Grain implementation that uses the ADO.NET storage public class MyGrain : Grain, IMyGrain, IGrainWithStringKey { private readonly IPersistentState _state; public MyGrain([PersistentState("state", "AdoNetStore")] IPersistentState state) { _state = state; } public async Task SetData(string data) { _state.State.Data = data; _state.State.Version++; await _state.WriteStateAsync(); } public Task GetData() { return Task.FromResult(_state.State.Data); } } ``` ## Database Setup Before using the ADO.NET provider, you need to set up the necessary database tables. Scripts for different database systems are available in the Orleans source repository: - [SQL Server Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Persistence.AdoNet/SQLServer-Persistence.sql) - [MySQL Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Persistence.AdoNet/MySQL-Persistence.sql) - [PostgreSQL Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Persistence.AdoNet/PostgreSQL-Persistence.sql) - [Oracle Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Persistence.AdoNet/Oracle-Persistence.sql) - [SQLite Scripts](https://github.com/dotnet/orleans/tree/main/src/AdoNet/Orleans.Persistence.AdoNet/Sqlite-Persistence.sql) ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) - [Relational Database Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence/relational-storage) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/SQLServer-Persistence.sql ================================================ -- The design criteria for this table are: -- -- 1. It can contain arbitrary content serialized as binary, XML or JSON. These formats -- are supported to allow one to take advantage of in-storage processing capabilities for -- these types if required. This should not incur extra cost on storage. -- -- 2. The table design should scale with the idea of tens or hundreds (or even more) types -- of grains that may operate with even hundreds of thousands of grain IDs within each -- type of a grain. -- -- 3. The table and its associated operations should remain stable. There should not be -- structural reason for unexpected delays in operations. It should be possible to also -- insert data reasonably fast without resource contention. -- -- 4. For reasons in 2. and 3., the index should be as narrow as possible so it fits well in -- memory and should it require maintenance, isn't resource intensive. For this -- reason the index is narrow by design (ideally non-clustered). Currently the entity -- is recognized in the storage by the grain type and its ID, which are unique in Orleans silo. -- The ID is the grain ID bytes (if string type UTF-8 bytes) and possible extension key as UTF-8 -- bytes concatenated with the ID and then hashed. -- -- Reason for hashing: Database engines usually limit the length of the column sizes, which -- would artificially limit the length of IDs or types. Even when within limitations, the -- index would be thick and consume more memory. -- -- In the current setup the ID and the type are hashed into two INT type instances, which -- are made a compound index. When there are no collisions, the index can quickly locate -- the unique row. Along with the hashed index values, the NVARCHAR(nnn) values are also -- stored and they are used to prune hash collisions down to only one result row. -- -- 5. The design leads to duplication in the storage. It is reasonable to assume there will -- a low number of services with a given service ID operational at any given time. Or that -- compared to the number of grain IDs, there are a fairly low number of different types of -- grain. The catch is that were these data separated to another table, it would make INSERT -- and UPDATE operations complicated and would require joins, temporary variables and additional -- indexes or some combinations of them to make it work. It looks like fitting strategy -- could be to use table compression. -- -- 6. For the aforementioned reasons, grain state DELETE will set NULL to the data fields -- and updates the Version number normally. This should alleviate the need for index or -- statistics maintenance with the loss of some bytes of storage space. The table can be scrubbed -- in a separate maintenance operation. -- -- 7. In the storage operations queries the columns need to be in the exact same order -- since the storage table operations support optionally streaming. IF OBJECT_ID(N'[OrleansStorage]', 'U') IS NULL CREATE TABLE OrleansStorage ( -- These are for the book keeping. Orleans calculates -- these hashes (see RelationalStorageProvide implementation), -- which are signed 32 bit integers mapped to the *Hash fields. -- The mapping is done in the code. The -- *String columns contain the corresponding clear name fields. -- -- If there are duplicates, they are resolved by using GrainIdN0, -- GrainIdN1, GrainIdExtensionString and GrainTypeString fields. -- It is assumed these would be rarely needed. GrainIdHash INT NOT NULL, GrainIdN0 BIGINT NOT NULL, GrainIdN1 BIGINT NOT NULL, GrainTypeHash INT NOT NULL, GrainTypeString NVARCHAR(512) NOT NULL, GrainIdExtensionString NVARCHAR(512) NULL, ServiceId NVARCHAR(150) NOT NULL, -- Payload PayloadBinary VARBINARY(MAX) NULL, -- Informational field, no other use. ModifiedOn DATETIME2(3) NOT NULL, -- The version of the stored payload. Version INT NULL -- The following would in principle be the primary key, but it would be too thick -- to be indexed, so the values are hashed and only collisions will be solved -- by using the fields. That is, after the indexed queries have pinpointed the right -- rows down to [0, n] relevant ones, n being the number of collided value pairs. ); IF NOT EXISTS(SELECT * FROM sys.indexes WHERE name = 'IX_OrleansStorage' AND object_id = OBJECT_ID('OrleansStorage')) BEGIN CREATE NONCLUSTERED INDEX IX_OrleansStorage ON OrleansStorage(GrainIdHash, GrainTypeHash); END -- This ensures lock escalation will not lock the whole table, which can potentially be enormous. -- See more information at https://www.littlekendra.com/2016/02/04/why-rowlock-hints-can-make-queries-slower-and-blocking-worse-in-sql-server/. ALTER TABLE OrleansStorage SET(LOCK_ESCALATION = DISABLE); -- A feature with ID is compression. If it is supported, it is used for OrleansStorage table. This is an Enterprise feature. -- This consumes more processor cycles, but should save on space on GrainIdString, GrainTypeString and ServiceId, which -- contain mainly the same values. Also the payloads will be compressed. IF EXISTS (SELECT 1 FROM sys.dm_db_persisted_sku_features WHERE feature_id = 100) BEGIN ALTER TABLE OrleansStorage REBUILD PARTITION = ALL WITH(DATA_COMPRESSION = PAGE); END INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'WriteToStorageKey', '-- When Orleans is running in normal, non-split state, there will -- be only one grain with the given ID and type combination only. This -- grain saves states mostly serially if Orleans guarantees are upheld. Even -- if not, the updates should work correctly due to version number. -- -- In split brain situations there can be a situation where there are two or more -- grains with the given ID and type combination. When they try to INSERT -- concurrently, the table needs to be locked pessimistically before one of -- the grains gets @GrainStateVersion = 1 in return and the other grains will fail -- to update storage. The following arrangement is made to reduce locking in normal operation. -- -- If the version number explicitly returned is still the same, Orleans interprets it so the update did not succeed -- and throws an InconsistentStateException. -- -- See further information at https://learn.microsoft.com/dotnet/orleans/grains/grain-persistence. BEGIN TRANSACTION; SET XACT_ABORT, NOCOUNT ON; DECLARE @NewGrainStateVersion AS INT = @GrainStateVersion; -- If the @GrainStateVersion is not zero, this branch assumes it exists in this database. -- The NULL value is supplied by Orleans when the state is new. IF @GrainStateVersion IS NOT NULL BEGIN UPDATE OrleansStorage SET PayloadBinary = @PayloadBinary, ModifiedOn = GETUTCDATE(), Version = Version + 1, @NewGrainStateVersion = Version + 1, @GrainStateVersion = Version + 1 WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL) AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL) AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL) AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN)); END -- The grain state has not been read. The following locks rather pessimistically -- to ensure only one INSERT succeeds. IF @GrainStateVersion IS NULL BEGIN INSERT INTO OrleansStorage ( GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version ) SELECT @GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @PayloadBinary, GETUTCDATE(), 1 WHERE NOT EXISTS ( -- There should not be any version of this grain state. SELECT 1 FROM OrleansStorage WITH(XLOCK, ROWLOCK, HOLDLOCK, INDEX(IX_OrleansStorage)) WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL) AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL) AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL) AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL ) OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN)); IF @@ROWCOUNT > 0 BEGIN SET @NewGrainStateVersion = 1; END END SELECT @NewGrainStateVersion AS NewGrainStateVersion; COMMIT TRANSACTION;' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'WriteToStorageKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ClearStorageKey', 'BEGIN TRANSACTION; SET XACT_ABORT, NOCOUNT ON; DECLARE @NewGrainStateVersion AS INT = @GrainStateVersion; UPDATE OrleansStorage SET PayloadBinary = NULL, ModifiedOn = GETUTCDATE(), Version = Version + 1, @NewGrainStateVersion = Version + 1 WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL) AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL) AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL) AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN)); SELECT @NewGrainStateVersion; COMMIT TRANSACTION;' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ClearStorageKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ReadFromStorageKey', '-- The application code will deserialize the relevant result. Not that the query optimizer -- estimates the result of rows based on its knowledge on the index. It does not know there -- will be only one row returned. Forcing the optimizer to process the first found row quickly -- creates an estimate for a one-row result and makes a difference on multi-million row tables. -- Also the optimizer is instructed to always use the same plan via index using the OPTIMIZE -- FOR UNKNOWN flags. These hints are only available in SQL Server 2008 and later. They -- should guarantee the execution time is robustly basically the same from query-to-query. SELECT PayloadBinary, Version FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL) AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL) AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL) AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN));' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ReadFromStorageKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'DeleteStorageKey', 'BEGIN TRANSACTION; SET XACT_ABORT, NOCOUNT ON; DELETE FROM OrleansStorage OUTPUT DELETED.Version + 1 WHERE GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL) AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL) AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL) AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL) AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN)); COMMIT TRANSACTION;' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'DeleteStorageKey' ); ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Sqlite-Persistence.sql ================================================ -- The design criteria for this table are: -- -- 1. It can contain arbitrary content serialized as binary, XML or JSON. These formats -- are supported to allow one to take advantage of in-storage processing capabilities for -- these types if required. This should not incur extra cost on storage. -- -- 2. The table design should scale with the idea of tens or hundreds (or even more) types -- of grains that may operate with even hundreds of thousands of grain IDs within each -- type of a grain. -- -- 3. The table and its associated operations should remain stable. There should not be -- structural reason for unexpected delays in operations. It should be possible to also -- insert data reasonably fast without resource contention. -- -- 4. For reasons in 2. and 3., the index should be as narrow as possible so it fits well in -- memory and should it require maintenance, isn't resource intensive. For this -- reason the index is narrow by design (ideally non-clustered). Currently the entity -- is recognized in the storage by the grain type and its ID, which are unique in Orleans silo. -- The ID is the grain ID bytes (if string type UTF-8 bytes) and possible extension key as UTF-8 -- bytes concatenated with the ID and then hashed. -- -- Reason for hashing: Database engines usually limit the length of the column sizes, which -- would artificially limit the length of IDs or types. Even when within limitations, the -- index would be thick and consume more memory. -- -- In the current setup the ID and the type are hashed into two INT type instances, which -- are made a compound index. When there are no collisions, the index can quickly locate -- the unique row. Along with the hashed index values, the NVARCHAR(nnn) values are also -- stored and they are used to prune hash collisions down to only one result row. -- -- 5. The design leads to duplication in the storage. It is reasonable to assume there will -- a low number of services with a given service ID operational at any given time. Or that -- compared to the number of grain IDs, there are a fairly low number of different types of -- grain. The catch is that were these data separated to another table, it would make INSERT -- and UPDATE operations complicated and would require joins, temporary variables and additional -- indexes or some combinations of them to make it work. It looks like fitting strategy -- could be to use table compression. -- -- 6. For the aforementioned reasons, grain state DELETE will set NULL to the data fields -- and updates the Version number normally. This should alleviate the need for index or -- statistics maintenance with the loss of some bytes of storage space. The table can be scrubbed -- in a separate maintenance operation. -- -- 7. In the storage operations queries the columns need to be in the exact same order -- since the storage table operations support optionally streaming. CREATE TABLE OrleansStorage ( -- These are for the book keeping. Orleans calculates -- these hashes (see RelationalStorageProvide implementation), -- which are signed 32 bit integers mapped to the *Hash fields. -- The mapping is done in the code. The -- *String columns contain the corresponding clear name fields. -- -- If there are duplicates, they are resolved by using GrainIdN0, -- GrainIdN1, GrainIdExtensionString and GrainTypeString fields. -- It is assumed these would be rarely needed. GrainIdHash INT NOT NULL, GrainIdN0 BIGINT NOT NULL, GrainIdN1 BIGINT NOT NULL, GrainTypeHash INT NOT NULL, GrainTypeString NVARCHAR(512) NOT NULL, GrainIdExtensionString NVARCHAR(512) NULL, ServiceId NVARCHAR(150) NOT NULL, -- Payload PayloadBinary BLOB NULL, -- Informational field, no other use. ModifiedOn DATETIME NOT NULL, -- The version of the stored payload. Version INT NULL -- The following would in principle be the primary key, but it would be too thick -- to be indexed, so the values are hashed and only collisions will be solved -- by using the fields. That is, after the indexed queries have pinpointed the right -- rows down to [0, n] relevant ones, n being the number of collided value pairs. ); CREATE INDEX IX_OrleansStorage ON OrleansStorage(GrainIdHash, GrainTypeHash); -- Updates an existing grain state with optimistic concurrency control or inserts it if it does not exist. INSERT INTO OrleansQuery (QueryKey, QueryText) VALUES ('WriteToStorageKey', ' BEGIN TRANSACTION; CREATE TEMP TABLE IF NOT EXISTS OrleansStorageWriteState ( TotalChangesBefore INT NOT NULL ); DELETE FROM OrleansStorageWriteState; INSERT INTO OrleansStorageWriteState (TotalChangesBefore) VALUES (total_changes() + 1); UPDATE OrleansStorage SET PayloadBinary = @PayloadBinary, ModifiedOn = datetime(''now''), Version = Version + 1 WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId AND Version = @GrainStateVersion; INSERT INTO OrleansStorage (GrainIdHash, GrainIdN0, GrainIdN1, GrainTypeHash, GrainTypeString, GrainIdExtensionString, ServiceId, PayloadBinary, ModifiedOn, Version) SELECT @GrainIdHash, @GrainIdN0, @GrainIdN1, @GrainTypeHash, @GrainTypeString, @GrainIdExtensionString, @ServiceId, @PayloadBinary, datetime(''now''), 1 WHERE changes() = 0 AND @GrainStateVersion IS NULL AND NOT EXISTS ( SELECT 1 FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId ); SELECT Version AS NewGrainStateVersion FROM OrleansStorage WHERE total_changes() > (SELECT TotalChangesBefore FROM OrleansStorageWriteState LIMIT 1) AND GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId; SELECT @GrainStateVersion AS NewGrainStateVersion WHERE total_changes() = (SELECT TotalChangesBefore FROM OrleansStorageWriteState LIMIT 1) AND @GrainStateVersion IS NOT NULL; COMMIT; '); -- Retrieves the binary payload and the current version of a specific grain state. INSERT INTO OrleansQuery (QueryKey, QueryText) VALUES ('ReadFromStorageKey', ' SELECT PayloadBinary, Version AS Version FROM OrleansStorage WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId LIMIT 1; '); -- Clears the grain state by setting the payload to null and incrementing the version for consistency. INSERT INTO OrleansQuery (QueryKey, QueryText) VALUES ('ClearStorageKey', ' UPDATE OrleansStorage SET PayloadBinary = NULL, ModifiedOn = datetime(''now''), Version = Version + 1 WHERE GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId AND Version = @GrainStateVersion; SELECT Version AS NewGrainStateVersion FROM OrleansStorage WHERE changes() > 0 AND GrainIdHash = @GrainIdHash AND GrainTypeHash = @GrainTypeHash AND GrainIdN0 = @GrainIdN0 AND GrainIdN1 = @GrainIdN1 AND GrainTypeString = @GrainTypeString AND (GrainIdExtensionString = @GrainIdExtensionString OR (GrainIdExtensionString IS NULL AND @GrainIdExtensionString IS NULL)) AND ServiceId = @ServiceId; SELECT @GrainStateVersion AS NewGrainStateVersion WHERE changes() = 0 AND @GrainStateVersion IS NOT NULL; '); ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/AdoGrainKey.cs ================================================ using System; using System.Globalization; using System.Text; namespace Orleans.Storage { /// /// This is an internal helper class that collects grain key information /// so that's easier to manage during database operations. /// internal class AdoGrainKey { public long N0Key { get; } public long N1Key { get; } public string StringKey { get; } public bool IsLongKey { get; } public bool IsGuidKey { get; } public bool IsStringKey { get; } public AdoGrainKey(long key, string keyExtension) { N0Key = 0; N1Key = key; StringKey = keyExtension; IsLongKey = true; IsGuidKey = false; IsStringKey = false; } public AdoGrainKey(Guid key, string keyExtension) { var guidKeyBytes = key.ToByteArray(); N0Key = BitConverter.ToInt64(guidKeyBytes, 0); N1Key = BitConverter.ToInt64(guidKeyBytes, 8); StringKey = keyExtension; IsLongKey = false; IsGuidKey = true; IsStringKey = false; } public AdoGrainKey(string key) { StringKey = key; N0Key = 0; N1Key = 0; IsLongKey = false; IsGuidKey = false; IsStringKey = true; } public byte[] GetHashBytes() { byte[] bytes = null; if(IsLongKey) { bytes = BitConverter.GetBytes(N1Key); } else if(IsGuidKey) { bytes = ToGuidKey(N0Key, N1Key).ToByteArray(); } if(bytes != null && StringKey != null) { int oldLen = bytes.Length; var stringBytes = Encoding.UTF8.GetBytes(StringKey); Array.Resize(ref bytes, bytes.Length + stringBytes.Length); Array.Copy(stringBytes, 0, bytes, oldLen, stringBytes.Length); } if(bytes == null) { bytes = Encoding.UTF8.GetBytes(StringKey); } if(BitConverter.IsLittleEndian) { Array.Reverse(bytes); } return bytes; } public override string ToString() { string primaryKey; string keyExtension = null; if(IsLongKey) { primaryKey = N1Key.ToString(CultureInfo.InvariantCulture); keyExtension = StringKey; } else if(IsGuidKey) { primaryKey = ToGuidKey(N0Key, N1Key).ToString(); keyExtension = StringKey; } else { primaryKey = StringKey; } const string GrainIdAndExtensionSeparator = "#"; return string.Format($"{primaryKey}{(keyExtension != null ? GrainIdAndExtensionSeparator + keyExtension : string.Empty)}"); } private static Guid ToGuidKey(long n0Key, long n1Key) { return new Guid((uint)(n0Key & 0xffffffff), (ushort)(n0Key >> 32), (ushort)(n0Key >> 48), (byte)n1Key, (byte)(n1Key >> 8), (byte)(n1Key >> 16), (byte)(n1Key >> 24), (byte)(n1Key >> 32), (byte)(n1Key >> 40), (byte)(n1Key >> 48), (byte)(n1Key >> 56)); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/AdoNetGrainStorage.cs ================================================ using Orleans.Persistence.AdoNet.Storage; using Orleans.Providers; using Orleans.Runtime; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration.Overrides; using Orleans.Configuration; using Orleans.Runtime.Configuration; using Orleans.Serialization.Serializers; namespace Orleans.Storage { /// /// Logging codes used by . /// /// These are taken from Orleans.Providers.ProviderErrorCode and Orleans.Providers.AzureProviderErrorCode. internal enum RelationalStorageProviderCodes { //These is from Orleans.Providers.ProviderErrorCode and Orleans.Providers.AzureProviderErrorCode. ProvidersBase = 200000, RelationalProviderBase = ProvidersBase + 400, RelationalProviderDeleteError = RelationalProviderBase + 8, RelationalProviderInitProvider = RelationalProviderBase + 9, RelationalProviderNoDeserializer = RelationalProviderBase + 10, RelationalProviderNoStateFound = RelationalProviderBase + 11, RelationalProviderClearing = RelationalProviderBase + 12, RelationalProviderCleared = RelationalProviderBase + 13, RelationalProviderReading = RelationalProviderBase + 14, RelationalProviderRead = RelationalProviderBase + 15, RelationalProviderReadError = RelationalProviderBase + 16, RelationalProviderWriting = RelationalProviderBase + 17, RelationalProviderWrote = RelationalProviderBase + 18, RelationalProviderWriteError = RelationalProviderBase + 19 } public static class AdoNetGrainStorageFactory { public static AdoNetGrainStorage Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); var clusterOptions = services.GetProviderClusterOptions(name); return ActivatorUtilities.CreateInstance(services, Options.Create(optionsMonitor.Get(name)), name, clusterOptions); } } /// /// A storage provider for writing grain state data to relational storage. /// /// /// /// Configuration is provided through . /// /// /// Required configuration: ConnectionString - The database connection string. /// /// /// Optional configuration: Invariant - The ADO.NET provider invariant name (defaults to Microsoft.Data.SqlClient). /// /// [DebuggerDisplay("Name = {Name}, ConnectionString = {Storage.ConnectionString}")] public partial class AdoNetGrainStorage : IGrainStorage, ILifecycleParticipant { public IGrainStorageSerializer Serializer { get; set; } /// /// Tag for BinaryFormatSerializer /// public const string BinaryFormatSerializerTag = "BinaryFormatSerializer"; /// /// Tag for JsonFormatSerializer /// public const string JsonFormatSerializerTag = "JsonFormatSerializer"; /// /// Tag for XmlFormatSerializer /// public const string XmlFormatSerializerTag = "XmlFormatSerializer"; /// /// The Service ID for which this relational provider is used. /// private readonly string serviceId; private readonly IActivatorProvider _activatorProvider; private readonly ILogger logger; /// /// The storage used for back-end operations. /// private IRelationalStorage Storage { get; set; } /// /// These chars are delimiters when used to extract a class base type from a class /// that is either or . /// . /// private static char[] BaseClassExtractionSplitDelimeters { get; } = new[] { '[', ']' }; /// /// The default query to initialize this structure from the Orleans database. /// public const string DefaultInitializationQuery = "SELECT QueryKey, QueryText FROM OrleansQuery WHERE QueryKey = 'WriteToStorageKey' OR QueryKey = 'ReadFromStorageKey' OR QueryKey = 'ClearStorageKey' OR QueryKey = 'DeleteStorageKey'"; /// /// The queries currently used. When this is updated, the new queries will take effect immediately. /// public RelationalStorageProviderQueries CurrentOperationalQueries { get; set; } /// /// The hash generator used to hash natural keys, grain ID and grain type to a more narrow index. /// public IStorageHasherPicker HashPicker { get; set; } private readonly AdoNetGrainStorageOptions options; private readonly string name; public AdoNetGrainStorage( IActivatorProvider activatorProvider, ILogger logger, IOptions options, IOptions clusterOptions, string name) { this.options = options.Value; this.name = name; _activatorProvider = activatorProvider; this.logger = logger; this.serviceId = clusterOptions.Value.ServiceId; this.Serializer = options.Value.GrainStorageSerializer; this.HashPicker = options.Value.HashPicker ?? new StorageHasherPicker(new[] { new OrleansDefaultHasher() }); } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.name), this.options.InitStage, Init, Close); } /// Clear state data function for this storage provider. /// . public async Task ClearStateAsync(string grainType, GrainId grainReference, IGrainState grainState) { //It assumed these parameters are always valid. If not, an exception will be thrown, //even if not as clear as when using explicitly checked parameters. var grainId = GrainIdAndExtensionAsString(grainReference); var baseGrainType = ExtractBaseClass(grainType); LogTraceClearingGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); if (!grainState.RecordExists) { await ReadStateAsync(grainType, grainReference, grainState).ConfigureAwait(false); if (!grainState.RecordExists) { return; } } string storageVersion = null; try { var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); var queryText = options.DeleteStateOnClear ? CurrentOperationalQueries.DeleteState : CurrentOperationalQueries.ClearState; // Backward compatibility checks in Init should handle this case and throw prior to reaching this state. if (queryText is null) { throw new UnreachableException($"QueryText is null for {nameof(options.DeleteStateOnClear)}={options.DeleteStateOnClear}"); } var clearRecord = (await Storage.ReadAsync(queryText, command => { command.AddParameter("GrainIdHash", grainIdHash); command.AddParameter("GrainIdN0", grainId.N0Key); command.AddParameter("GrainIdN1", grainId.N1Key); command.AddParameter("GrainTypeHash", grainTypeHash); command.AddParameter("GrainTypeString", baseGrainType); command.AddParameter("GrainIdExtensionString", grainId.StringKey); command.AddParameter("ServiceId", serviceId); command.AddParameter("GrainStateVersion", !string.IsNullOrWhiteSpace(grainState.ETag) ? int.Parse(grainState.ETag, CultureInfo.InvariantCulture) : default(int?)); }, (selector, resultSetCount, token) => Task.FromResult(selector.GetValue(0).ToString()), cancellationToken: CancellationToken.None).ConfigureAwait(false)); storageVersion = clearRecord.SingleOrDefault(); } catch (Exception ex) { LogErrorClearingGrainState(ex, serviceId, name, baseGrainType, grainId, grainState.ETag); throw; } const string OperationString = "ClearState"; var inconsistentStateException = CheckVersionInconsistency(OperationString, serviceId, this.name, storageVersion, grainState.ETag, baseGrainType, grainId.ToString()); if (inconsistentStateException != null) { throw inconsistentStateException; } // When delete on clear is set, ETag should be null since the record was deleted. // The DB script returns deleted record version + 1 as storageVersion for the CheckVersionInconsistency check above. //No errors found, the version of the state held by the grain can be updated and also the state. grainState.ETag = options.DeleteStateOnClear ? null : storageVersion; grainState.RecordExists = false; grainState.State = CreateInstance(); LogTraceClearedGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); } /// Read state data function for this storage provider. /// . public async Task ReadStateAsync(string grainType, GrainId grainReference, IGrainState grainState) { //It assumed these parameters are always valid. If not, an exception will be thrown, even if not as clear //as with explicitly checked parameters. var grainId = GrainIdAndExtensionAsString(grainReference); var baseGrainType = ExtractBaseClass(grainType); LogTraceReadingGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); try { var commandBehavior = CommandBehavior.Default; var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); var readRecords = (await Storage.ReadAsync( CurrentOperationalQueries.ReadFromStorage, command => { command.AddParameter("GrainIdHash", grainIdHash); command.AddParameter("GrainIdN0", grainId.N0Key); command.AddParameter("GrainIdN1", grainId.N1Key); command.AddParameter("GrainTypeHash", grainTypeHash); command.AddParameter("GrainTypeString", baseGrainType); command.AddParameter("GrainIdExtensionString", grainId.StringKey); command.AddParameter("ServiceId", serviceId); }, (selector, resultSetCount, token) => { object storageState = null; int? version; byte[] payload; payload = selector.GetValueOrDefault("PayloadBinary"); if (payload != null) { storageState = Serializer.Deserialize(new BinaryData(payload)); } version = selector.GetNullableInt32("Version"); var result = Tuple.Create(storageState, version?.ToString(CultureInfo.InvariantCulture)); return Task.FromResult(result); }, commandBehavior, CancellationToken.None).ConfigureAwait(false)).SingleOrDefault(); T state = readRecords != null ? (T)readRecords.Item1 : default; string etag = readRecords != null ? readRecords.Item2 : null; bool recordExists = readRecords != null; if (state == null) { LogTraceNullGrainStateRead(serviceId, name, baseGrainType, grainId, grainState.ETag); state = CreateInstance(); } grainState.State = state; grainState.ETag = etag; grainState.RecordExists = recordExists; LogTraceReadGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); } catch (Exception ex) { LogErrorReadingGrainState(ex, serviceId, name, baseGrainType, grainId, grainState.ETag); throw; } } /// Write state data function for this storage provider. /// public async Task WriteStateAsync(string grainType, GrainId grainReference, IGrainState grainState) { //It assumed these parameters are always valid. If not, an exception will be thrown, even if not as clear //as with explicitly checked parameters. var data = grainState.State; var grainId = GrainIdAndExtensionAsString(grainReference); var baseGrainType = ExtractBaseClass(grainType); LogTraceWritingGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); string storageVersion = null; try { var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); var writeRecord = await Storage.ReadAsync(CurrentOperationalQueries.WriteToStorage, command => { var serialized = this.Serializer.Serialize(grainState.State); command.AddParameter("GrainIdHash", grainIdHash); command.AddParameter("GrainIdN0", grainId.N0Key); command.AddParameter("GrainIdN1", grainId.N1Key); command.AddParameter("GrainTypeHash", grainTypeHash); command.AddParameter("GrainTypeString", baseGrainType); command.AddParameter("GrainIdExtensionString", grainId.StringKey); command.AddParameter("ServiceId", serviceId); command.AddParameter("GrainStateVersion", !string.IsNullOrWhiteSpace(grainState.ETag) ? int.Parse(grainState.ETag, CultureInfo.InvariantCulture) : default(int?)); command.AddParameter("PayloadBinary", serialized.ToArray()); }, (selector, resultSetCount, token) => { return Task.FromResult(selector.GetNullableInt32("NewGrainStateVersion").ToString()); }, cancellationToken: CancellationToken.None).ConfigureAwait(false); storageVersion = writeRecord.SingleOrDefault(); } catch (Exception ex) { LogErrorWritingGrainState(ex, serviceId, name, baseGrainType, grainId, grainState.ETag); throw; } const string OperationString = "WriteState"; var inconsistentStateException = CheckVersionInconsistency(OperationString, serviceId, this.name, storageVersion, grainState.ETag, baseGrainType, grainId.ToString()); if (inconsistentStateException != null) { throw inconsistentStateException; } //No errors found, the version of the state held by the grain can be updated. grainState.ETag = storageVersion; grainState.RecordExists = true; LogTraceWroteGrainState(serviceId, name, baseGrainType, grainId, grainState.ETag); } /// Initialization function for this storage provider. private async Task Init(CancellationToken cancellationToken) { Storage = RelationalStorage.CreateInstance(options.Invariant, options.ConnectionString); var queries = await Storage.ReadAsync(DefaultInitializationQuery, command => { }, (selector, resultSetCount, token) => { return Task.FromResult(Tuple.Create(selector.GetValue("QueryKey"), selector.GetValue("QueryText"))); }).ConfigureAwait(false); // This check is for backward compatibility: // 1. Some AdoNet storage invariants may not support delete on clear. // 2. AdoNet invariant supports delete on clear but updated persistence scripts have not been deployed to management db. var deleteStateQuery = queries.SingleOrDefault(i => i.Item1 == "DeleteStorageKey")?.Item2; if (options.DeleteStateOnClear && deleteStateQuery is null) { throw new ArgumentException($"{nameof(options.DeleteStateOnClear)}=true is not supported. Use {nameof(options.DeleteStateOnClear)}=false instead or check persistence scripts."); } CurrentOperationalQueries = new RelationalStorageProviderQueries( queries.Single(i => i.Item1 == "WriteToStorageKey").Item2, queries.Single(i => i.Item1 == "ReadFromStorageKey").Item2, queries.Single(i => i.Item1 == "ClearStorageKey").Item2, deleteStateQuery); LogInfoInitializedStorageProvider( serviceId, name, Storage.InvariantName, new(Storage.ConnectionString)); } /// /// Close this provider /// private Task Close(CancellationToken token) { return Task.CompletedTask; } /// /// Checks for version inconsistency as defined in the database scripts. /// /// Service Id. /// The name of this storage provider. /// The operation attempted. /// The version from storage. /// The grain version. /// Grain type without generics information. /// The grain ID. /// An exception for throwing or null if no violation was detected. /// This means that the version was not updated in the database or the version storage version was something else than null /// when the grain version was null, meaning effectively a double activation and save. private static InconsistentStateException CheckVersionInconsistency(string operation, string serviceId, string providerName, string storageVersion, string grainVersion, string normalizedGrainType, string grainId) { //If these are the same, it means no row was inserted or updated in the storage. //Effectively it means the UPDATE or INSERT conditions failed due to ETag violation. //Also if grainState.ETag storageVersion is null and storage comes back as null, //it means two grains were activated an the other one succeeded in writing its state. // //NOTE: the storage could return also the new and old ETag (Version), but currently it doesn't. if (storageVersion == grainVersion || storageVersion == string.Empty || (storageVersion is null && grainVersion is not null)) { //TODO: Note that this error message should be canonical across back-ends. return new InconsistentStateException($"Version conflict ({operation}): ServiceId={serviceId} ProviderName={providerName} GrainType={normalizedGrainType} GrainId={grainId} ETag={grainVersion}."); } return null; } /// /// Extracts a grain ID as a string and appends the key extension with '#' infix is present. /// /// The grain ID as a string. /// This likely should exist in Orleans core in more optimized form. private static AdoGrainKey GrainIdAndExtensionAsString(GrainId grainId) { string keyExt; if (grainId.TryGetGuidKey(out var guid, out keyExt)) { return new AdoGrainKey(guid, keyExt); } if (grainId.TryGetIntegerKey(out var integer, out keyExt)) { return new AdoGrainKey(integer, keyExt); } return new AdoGrainKey(grainId.Key.ToString()); } /// /// Extracts a base class from a string that is either or /// or returns the one given as a parameter if no type is given. /// /// The base class name to give. /// The extracted base class or the one given as a parameter if it didn't have a generic part. private static string ExtractBaseClass(string typeName) { var genericPosition = typeName.IndexOf("`", StringComparison.OrdinalIgnoreCase); if (genericPosition != -1) { //The following relies the generic argument list to be in form as described //at https://msdn.microsoft.com/en-us/library/w3f99sx1.aspx. var split = typeName.Split(BaseClassExtractionSplitDelimeters, StringSplitOptions.RemoveEmptyEntries); var stripped = new Queue(split.Where(i => i.Length > 1 && i[0] != ',').Select(WithoutAssemblyVersion)); return ReformatClassName(stripped); } return typeName; string WithoutAssemblyVersion(string input) { var asmNameIndex = input.IndexOf(','); if (asmNameIndex >= 0) { var asmVersionIndex = input.IndexOf(',', asmNameIndex + 1); if (asmVersionIndex >= 0) return input[..asmVersionIndex]; return input[..asmNameIndex]; } return input; } string ReformatClassName(Queue segments) { var simpleTypeName = segments.Dequeue(); var arity = GetGenericArity(simpleTypeName); if (arity <= 0) return simpleTypeName; var args = new List(arity); for (var i = 0; i < arity; i++) { args.Add(ReformatClassName(segments)); } return $"{simpleTypeName}[{string.Join(",", args.Select(arg => $"[{arg}]"))}]"; } int GetGenericArity(string input) { var arityIndex = input.IndexOf("`", StringComparison.OrdinalIgnoreCase); if (arityIndex != -1) { return int.Parse(input.AsSpan()[(arityIndex + 1)..]); } return 0; } } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderClearing, Level = LogLevel.Trace, Message = "Clearing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceClearingGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderDeleteError, Level = LogLevel.Error, Message = "Error clearing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogErrorClearingGrainState(Exception exception, string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderCleared, Level = LogLevel.Trace, Message = "Cleared grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceClearedGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderReading, Level = LogLevel.Trace, Message = "Reading grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceReadingGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderNoStateFound, Level = LogLevel.Trace, Message = "Null grain state read (default will be instantiated): ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceNullGrainStateRead(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderRead, Level = LogLevel.Trace, Message = "Read grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceReadGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderReadError, Level = LogLevel.Error, Message = "Error reading grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogErrorReadingGrainState(Exception exception, string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderWriting, Level = LogLevel.Trace, Message = "Writing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceWritingGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderWriteError, Level = LogLevel.Error, Message = "Error writing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogErrorWritingGrainState(Exception exception, string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderWrote, Level = LogLevel.Trace, Message = "Wrote grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}." )] private partial void LogTraceWroteGrainState(string serviceId, string name, string baseGrainType, AdoGrainKey grainId, string etag); private readonly struct ConnectionStringLogRecord(string connectionString) { public override string ToString() => ConfigUtilities.RedactConnectionStringInfo(connectionString); } [LoggerMessage( EventId = (int)RelationalStorageProviderCodes.RelationalProviderInitProvider, Level = LogLevel.Information, Message = "Initialized storage provider: ServiceId={ServiceId} ProviderName={Name} Invariant={InvariantName} ConnectionString={ConnectionString}." )] private partial void LogInfoInitializedStorageProvider(string serviceId, string name, string invariantName, ConnectionStringLogRecord connectionString); } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/AdoNetGrainStorageServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Hosting; using Orleans.Storage; namespace Orleans.Hosting { /// /// extensions. /// public static class AdoNetGrainStorageServiceCollectionExtensions { /// /// Configure silo to use AdoNet grain storage as the default grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static IServiceCollection AddAdoNetGrainStorage(this IServiceCollection services, Action configureOptions) { return services.AddAdoNetGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use AdoNet grain storage for grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static IServiceCollection AddAdoNetGrainStorage(this IServiceCollection services, string name, Action configureOptions) { return services.AddAdoNetGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use AdoNet grain storage as the default grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static IServiceCollection AddAdoNetGrainStorageAsDefault(this IServiceCollection services, Action> configureOptions = null) { return services.AddAdoNetGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AdoNet grain storage for grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static IServiceCollection AddAdoNetGrainStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.ConfigureNamedOptionForLogging(name); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); services.AddTransient, DefaultAdoNetGrainStorageOptionsHashPickerConfigurator>(); services.AddTransient(sp => new AdoNetGrainStorageOptionsValidator(sp.GetRequiredService>().Get(name), name)); return services.AddGrainStorage(name, AdoNetGrainStorageFactory.Create); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/AdoNetGrainStorageSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; namespace Orleans.Hosting { public static class AdoNetGrainStorageSiloBuilderExtensions { /// /// Configure silo to use AdoNet grain storage as the default grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder AddAdoNetGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddAdoNetGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AdoNet grain storage for grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder AddAdoNetGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddAdoNetGrainStorage(name, configureOptions)); } /// /// Configure silo to use AdoNet grain storage as the default grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder AddAdoNetGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddAdoNetGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use AdoNet grain storage for grain storage. Instructions on configuring your database are available at . /// /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder AddAdoNetGrainStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder.ConfigureServices(services => services.AddAdoNetGrainStorage(name, configureOptions)); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/IHasher.cs ================================================ namespace Orleans.Storage { /// /// An interface for all the hashing operations currently in Orleans Storage operations. /// /// Implement this to provide a hasher for database key with specific properties. /// As for an example: collision resistance on out-of-control ID providers. public interface IHasher { /// /// Description of the hashing functionality. /// string Description { get; } /// /// The hash. /// /// The data to hash. /// The given bytes hashed. int Hash(byte[] data); } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/IStorageHashPicker.cs ================================================ using Orleans.Runtime; using System.Collections.Generic; namespace Orleans.Storage { /// /// A picker to choose from provided hash functions. Provides agility to update or change hashing functionality for both built-in and custom operations. /// /// The returned hash needs to be thread safe or a unique instance. public interface IStorageHasherPicker { /// /// The hash functions saved to this picker. /// ICollection HashProviders { get; } /// Picks a hasher using the given parameters. /// The ID of the current service. /// The requesting storage provider. /// The type of grain. /// The grain ID. /// The grain state. /// An optional tag parameter that might be used by the storage parameter for "out-of-band" contracts. /// A serializer or null if not match was found. IHasher PickHasher(string serviceId, string storageProviderInstanceName, string grainType, GrainId grainId, IGrainState grainState, string tag = null); } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/JenkinsHash.cs ================================================ using System; namespace Orleans.Storage { // Based on the version in http://home.comcast.net/~bretm/hash/7.html, which is based on that // in http://burtleburtle.net/bob/hash/evahash.html. // Note that we only use the version that takes three ulongs, which was written by the Orleans team. // implementation restored from Orleans v3.7.2: https://github.com/dotnet/orleans/blob/b24e446abfd883f0e4ed614f5267eaa3331548dc/src/Orleans.Core.Abstractions/IDs/JenkinsHash.cs, // trimmed and slightly optimized internal static class JenkinsHash { private static void Mix(ref uint aa, ref uint bb, ref uint cc) { uint a = aa; uint b = bb; uint c = cc; a -= b; a -= c; a ^= (c >> 13); b -= c; b -= a; b ^= (a << 8); c -= a; c -= b; c ^= (b >> 13); a -= b; a -= c; a ^= (c >> 12); b -= c; b -= a; b ^= (a << 16); c -= a; c -= b; c ^= (b >> 5); a -= b; a -= c; a ^= (c >> 3); b -= c; b -= a; b ^= (a << 10); c -= a; c -= b; c ^= (b >> 15); aa = a; bb = b; cc = c; } // This is the reference implementation of the Jenkins hash. public static uint ComputeHash(ReadOnlySpan data) { int len = data.Length; uint a = 0x9e3779b9; uint b = a; uint c = 0; int i = 0; while (i <= len - 12) { a += (uint)data[i++] | ((uint)data[i++] << 8) | ((uint)data[i++] << 16) | ((uint)data[i++] << 24); b += (uint)data[i++] | ((uint)data[i++] << 8) | ((uint)data[i++] << 16) | ((uint)data[i++] << 24); c += (uint)data[i++] | ((uint)data[i++] << 8) | ((uint)data[i++] << 16) | ((uint)data[i++] << 24); Mix(ref a, ref b, ref c); } c += (uint)len; if (i < len) a += data[i++]; if (i < len) a += (uint)data[i++] << 8; if (i < len) a += (uint)data[i++] << 16; if (i < len) a += (uint)data[i++] << 24; if (i < len) b += (uint)data[i++]; if (i < len) b += (uint)data[i++] << 8; if (i < len) b += (uint)data[i++] << 16; if (i < len) b += (uint)data[i++] << 24; if (i < len) c += (uint)data[i++] << 8; if (i < len) c += (uint)data[i++] << 16; if (i < len) c += (uint)data[i++] << 24; Mix(ref a, ref b, ref c); return c; } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleHasher.cs ================================================ using System; namespace Orleans.Storage { /// /// Orleans v3-compatible hasher implementation for non-string-only grain key ids. /// internal class Orleans3CompatibleHasher : IHasher { /// /// /// public string Description { get; } = $"Orleans v3 hash function ({nameof(JenkinsHash)})."; /// /// . /// public int Hash(byte[] data) => Hash(data.AsSpan()); /// /// . /// public int Hash(ReadOnlySpan data) { // implementation restored from Orleans v3.7.2: https://github.com/dotnet/orleans/blob/b24e446abfd883f0e4ed614f5267eaa3331548dc/src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/OrleansDefaultHasher.cs return unchecked((int)JenkinsHash.ComputeHash(data)); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleStorageHashPicker.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Storage { /// /// Orleans v3-compatible hash picker implementation for Orleans v3 -> v7+ migration scenarios. /// public class Orleans3CompatibleStorageHashPicker : IStorageHasherPicker { private readonly Orleans3CompatibleHasher _nonStringHasher; /// /// . /// public ICollection HashProviders { get; } /// /// A constructor. /// public Orleans3CompatibleStorageHashPicker() { _nonStringHasher = new(); HashProviders = [_nonStringHasher]; } /// /// . /// public IHasher PickHasher( string serviceId, string storageProviderInstanceName, string grainType, GrainId grainId, IGrainState grainState, string tag = null) { // string-only grain keys had special behaviour in Orleans v3 if (grainId.TryGetIntegerKey(out _, out _) || grainId.TryGetGuidKey(out _, out _)) return _nonStringHasher; // unable to cache hasher instances: content-aware behaviour, see hasher implementation for details return new Orleans3CompatibleStringKeyHasher(_nonStringHasher, grainType); } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/Orleans3CompatibleStringKeyHasher.cs ================================================ using System; using System.Buffers; using System.Text; namespace Orleans.Storage { /// /// Orleans v3-compatible hasher implementation for string-only grain key ids. /// internal class Orleans3CompatibleStringKeyHasher : IHasher { private readonly Orleans3CompatibleHasher _innerHasher; private readonly string _grainType; public Orleans3CompatibleStringKeyHasher(Orleans3CompatibleHasher innerHasher, string grainType) { _innerHasher = innerHasher; _grainType = grainType; } /// /// /// public string Description { get; } = $"Orleans v3 hash function ({nameof(JenkinsHash)})."; /// /// . /// public int Hash(byte[] data) { // Orleans v3 treats string-only keys as integer keys with extension (AdoGrainKey.IsLongKey == true), // so data must be extended for string-only grain keys. // But AdoNetGrainStorage implementation also uses such code: // ... // var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); // var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); // ... // PickHasher parameters are the same for both calls so we need to analyze data content to distinguish these cases. // It doesn't word if string key is equal to grain type name, but we consider this edge case to be negligibly rare. if (IsGrainTypeName(data)) return _innerHasher.Hash(data); var extendedLength = data.Length + 8; const int maxOnStack = 256; byte[] rentedBuffer = null; // assuming code below never throws, so calling ArrayPool.Return without try/finally block for JIT optimization var buffer = extendedLength > maxOnStack ? (rentedBuffer = ArrayPool.Shared.Rent(extendedLength)).AsSpan() : stackalloc byte[maxOnStack]; buffer = buffer[..extendedLength]; data.AsSpan().CopyTo(buffer); // buffer may contain arbitrary data, setting zeros in 'extension' segment buffer[data.Length..].Clear(); var hash = _innerHasher.Hash(buffer); if (rentedBuffer is not null) ArrayPool.Shared.Return(rentedBuffer); return hash; } private bool IsGrainTypeName(byte[] data) { // at least 1 byte per char if (data.Length < _grainType.Length) return false; var grainTypeByteCount = Encoding.UTF8.GetByteCount(_grainType); if (grainTypeByteCount != data.Length) return false; const int maxOnStack = 256; byte[] rentedBuffer = null; // assuming code below never throws, so calling ArrayPool.Return without try/finally block for JIT optimization var buffer = grainTypeByteCount > maxOnStack ? (rentedBuffer = ArrayPool.Shared.Rent(grainTypeByteCount)).AsSpan() : stackalloc byte[maxOnStack]; buffer = buffer[..grainTypeByteCount]; var bytesWritten = Encoding.UTF8.GetBytes(_grainType, buffer); var isGrainType = buffer[..bytesWritten].SequenceEqual(data); if (rentedBuffer is not null) ArrayPool.Shared.Return(rentedBuffer); return isGrainType; } } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/OrleansDefaultHasher.cs ================================================ namespace Orleans.Storage { /// /// A default implementation uses the same hash as Orleans in grains placement. /// public sealed class OrleansDefaultHasher: IHasher { /// /// /// public string Description => $"The default Orleans hash function ({nameof(StableHash)})."; /// /// . /// public int Hash(byte[] data) => (int)StableHash.ComputeHash(data); } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/RelationalStorageProviderQueries.cs ================================================ namespace Orleans.Storage; /// /// A container class for the queries currently used by the . /// /// This is provided as a separate entity in order to make these dynamically updatable. public class RelationalStorageProviderQueries { /// /// The clause to write to the storage. /// public string WriteToStorage { get; } /// /// The clause to read from the storage. /// public string ReadFromStorage { get; set; } /// /// The clause to clear the storage. /// public string ClearState { get; set; } /// /// The clause to delete the row from storage when clearing state. /// public string DeleteState { get; set; } /// /// Constructor. /// /// The clause to write to a storage. /// The clause to read from a storage. /// The clause to clear the storage. /// The clause to delete the row from storage. May be for backward compatibility. public RelationalStorageProviderQueries(string writeToStorage, string readFromStorage, string clearState, string deleteState) { ArgumentNullException.ThrowIfNull(writeToStorage); ArgumentNullException.ThrowIfNull(readFromStorage); ArgumentNullException.ThrowIfNull(clearState); // No null check on deleteState for backward compatibility. WriteToStorage = writeToStorage; ReadFromStorage = readFromStorage; ClearState = clearState; DeleteState = deleteState; } } ================================================ FILE: src/AdoNet/Orleans.Persistence.AdoNet/Storage/Provider/StorageHasherPicker.cs ================================================ using Orleans.Runtime; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace Orleans.Storage { /// /// . /// public class StorageHasherPicker: IStorageHasherPicker { /// /// . /// public ICollection HashProviders { get; } /// /// A constructor. /// /// The hash providers this picker uses. public StorageHasherPicker(IEnumerable hashProviders) { if(hashProviders == null) { throw new ArgumentNullException(nameof(hashProviders)); } HashProviders = new Collection(new List(hashProviders)); } /// /// . /// public IHasher PickHasher(string serviceId, string storageProviderInstanceName, string grainType, GrainId grainId, IGrainState grainState, string tag = null) { return HashProviders.FirstOrDefault(); } } } ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/AdoNetRemindersProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AdoNet", "Reminders", "Silo", typeof(AdoNetRemindersProviderBuilder))] namespace Orleans.Hosting; internal sealed class AdoNetRemindersProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAdoNetReminderService((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var invariant = configurationSection[nameof(options.Invariant)]; if (!string.IsNullOrEmpty(invariant)) { options.Invariant = invariant; } var connectionString = configurationSection[nameof(options.ConnectionString)]; var connectionName = configurationSection["ConnectionName"]; if (string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(connectionName)) { connectionString = services.GetRequiredService().GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConnectionString = connectionString; } })); } } ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/Migrations/PostgreSQL-Reminders-3.6.0.sql ================================================ -- Run this migration for upgrading the PostgreSQL reminder table and routines for deployments created before 3.6.0 BEGIN; -- Change date type ALTER TABLE OrleansRemindersTable ALTER COLUMN StartTime TYPE TIMESTAMPTZ(3) USING StartTime AT TIME ZONE 'UTC'; -- Recreate routines CREATE OR REPLACE FUNCTION upsert_reminder_row( ServiceIdArg OrleansRemindersTable.ServiceId%TYPE, GrainIdArg OrleansRemindersTable.GrainId%TYPE, ReminderNameArg OrleansRemindersTable.ReminderName%TYPE, StartTimeArg OrleansRemindersTable.StartTime%TYPE, PeriodArg OrleansRemindersTable.Period%TYPE, GrainHashArg OrleansRemindersTable.GrainHash%TYPE ) RETURNS TABLE(version integer) AS $func$ DECLARE VersionVar int := 0; BEGIN INSERT INTO OrleansRemindersTable ( ServiceId, GrainId, ReminderName, StartTime, Period, GrainHash, Version ) SELECT ServiceIdArg, GrainIdArg, ReminderNameArg, StartTimeArg, PeriodArg, GrainHashArg, 0 ON CONFLICT (ServiceId, GrainId, ReminderName) DO UPDATE SET StartTime = excluded.StartTime, Period = excluded.Period, GrainHash = excluded.GrainHash, Version = OrleansRemindersTable.Version + 1 RETURNING OrleansRemindersTable.Version INTO STRICT VersionVar; RETURN QUERY SELECT VersionVar AS versionr; END $func$ LANGUAGE plpgsql; COMMIT; ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/MySQL-Reminders.sql ================================================ -- Orleans Reminders table - https://learn.microsoft.com/dotnet/orleans/grains/timers-and-reminders CREATE TABLE OrleansRemindersTable ( ServiceId NVARCHAR(150) NOT NULL, GrainId VARCHAR(150) NOT NULL, ReminderName NVARCHAR(150) NOT NULL, StartTime DATETIME NOT NULL, Period BIGINT NOT NULL, GrainHash INT NOT NULL, Version INT NOT NULL, CONSTRAINT PK_RemindersTable_ServiceId_GrainId_ReminderName PRIMARY KEY(ServiceId, GrainId, ReminderName) ); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpsertReminderRowKey',' INSERT INTO OrleansRemindersTable ( ServiceId, GrainId, ReminderName, StartTime, Period, GrainHash, Version ) VALUES ( @ServiceId, @GrainId, @ReminderName, @StartTime, @Period, @GrainHash, last_insert_id(0) ) ON DUPLICATE KEY UPDATE StartTime = @StartTime, Period = @Period, GrainHash = @GrainHash, Version = last_insert_id(Version+1); SELECT last_insert_id() AS Version; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowsKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows1Key',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainHash > @BeginHash AND @BeginHash IS NOT NULL AND GrainHash <= @EndHash AND @EndHash IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows2Key',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND ((GrainHash > @BeginHash AND @BeginHash IS NOT NULL) OR (GrainHash <= @EndHash AND @EndHash IS NOT NULL)); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowKey',' DELETE FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL AND Version = @Version AND @Version IS NOT NULL; SELECT ROW_COUNT(); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowsKey',' DELETE FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL; '); ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/Oracle-Reminders.sql ================================================ -- Orleans Reminders table - https://learn.microsoft.com/dotnet/orleans/grains/timers-and-reminders CREATE TABLE "ORLEANSREMINDERSTABLE" ( "SERVICEID" NVARCHAR2(150) NOT NULL ENABLE, "GRAINID" VARCHAR2(150) NOT NULL, "REMINDERNAME" NVARCHAR2(150) NOT NULL, "STARTTIME" TIMESTAMP(6) NOT NULL ENABLE, "PERIOD" NUMBER(19,0) NULL, "GRAINHASH" INT NOT NULL, "VERSION" INT NOT NULL, CONSTRAINT PK_REMINDERSTABLE PRIMARY KEY(SERVICEID, GRAINID, REMINDERNAME) ); / CREATE OR REPLACE FUNCTION UpsertReminderRow(PARAM_SERVICEID IN NVARCHAR2, PARAM_GRAINHASH IN INT, PARAM_GRAINID IN VARCHAR2, PARAM_REMINDERNAME IN NVARCHAR2, PARAM_STARTTIME IN TIMESTAMP, PARAM_PERIOD IN NUMBER) RETURN NUMBER IS rowcount NUMBER; currentVersion NUMBER := 0; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN MERGE INTO OrleansRemindersTable ort USING ( SELECT PARAM_SERVICEID as SERVICEID, PARAM_GRAINID as GRAINID, PARAM_REMINDERNAME as REMINDERNAME, PARAM_STARTTIME as STARTTIME, PARAM_PERIOD as PERIOD, PARAM_GRAINHASH GRAINHASH FROM dual ) n_ort ON (ort.ServiceId = n_ort.SERVICEID AND ort.GrainId = n_ort.GRAINID AND ort.ReminderName = n_ort.REMINDERNAME ) WHEN MATCHED THEN UPDATE SET ort.StartTime = n_ort.STARTTIME, ort.Period = n_ort.PERIOD, ort.GrainHash = n_ort.GRAINHASH, ort.Version = ort.Version+1 WHEN NOT MATCHED THEN INSERT (ort.ServiceId, ort.GrainId, ort.ReminderName, ort.StartTime, ort.Period, ort.GrainHash, ort.Version) VALUES (n_ort.SERVICEID, n_ort.GRAINID, n_ort.REMINDERNAME, n_ort.STARTTIME, n_ort.PERIOD, n_ort.GRAINHASH, 0); SELECT Version INTO currentVersion FROM OrleansRemindersTable WHERE ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL AND GrainId = PARAM_GRAINID AND PARAM_GRAINID IS NOT NULL AND ReminderName = PARAM_REMINDERNAME AND PARAM_REMINDERNAME IS NOT NULL; COMMIT; RETURN(currentVersion); END; / CREATE OR REPLACE FUNCTION DeleteReminderRow(PARAM_SERVICEID IN NVARCHAR2, PARAM_GRAINID IN VARCHAR2, PARAM_REMINDERNAME IN NVARCHAR2, PARAM_VERSION IN NUMBER) RETURN NUMBER IS rowcount NUMBER; PRAGMA AUTONOMOUS_TRANSACTION; BEGIN DELETE FROM OrleansRemindersTable WHERE ServiceId = PARAM_SERVICEID AND PARAM_SERVICEID IS NOT NULL AND GrainId = PARAM_GRAINID AND PARAM_GRAINID IS NOT NULL AND ReminderName = PARAM_REMINDERNAME AND PARAM_REMINDERNAME IS NOT NULL AND Version = PARAM_VERSION AND PARAM_VERSION IS NOT NULL; rowcount := SQL%ROWCOUNT; COMMIT; RETURN(rowcount); END; / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpsertReminderRowKey',' SELECT UpsertReminderRow(:ServiceId, :GrainHash, :GrainId, :ReminderName, :StartTime, :Period) AS Version FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowsKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = :ServiceId AND :ServiceId IS NOT NULL AND GrainId = :GrainId AND :GrainId IS NOT NULL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = :ServiceId AND :ServiceId IS NOT NULL AND GrainId = :GrainId AND :GrainId IS NOT NULL AND ReminderName = :ReminderName AND :ReminderName IS NOT NULL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows1Key',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = :ServiceId AND :ServiceId IS NOT NULL AND GrainHash > :BeginHash AND :BeginHash IS NOT NULL AND GrainHash <= :EndHash AND :EndHash IS NOT NULL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows2Key',' SELECT GrainId, ReminderName, StartTime, Period,Version FROM OrleansRemindersTable WHERE ServiceId = :ServiceId AND :ServiceId IS NOT NULL AND ((GrainHash > :BeginHash AND :BeginHash IS NOT NULL) OR (GrainHash <= :EndHash AND :EndHash IS NOT NULL)) '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowKey',' SELECT DeleteReminderRow(:ServiceId, :GrainId, :ReminderName, :Version) AS RESULT FROM DUAL '); / INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowsKey',' DELETE FROM OrleansRemindersTable WHERE ServiceId = :ServiceId AND :ServiceId IS NOT NULL '); / COMMIT; ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/Orleans.Reminders.AdoNet.csproj ================================================ README.md Microsoft.Orleans.Reminders.AdoNet Microsoft Orleans ADO.NET Reminders Provider Microsoft Orleans reminders provider backed by ADO.NET $(PackageTags) ADO.NET SQL MySQL PostgreSQL Oracle $(DefaultTargetFrameworks) true Orleans.Reminders.AdoNet Orleans.Reminders.AdoNet $(DefineConstants);REMINDERS_ADONET ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/PostgreSQL-Reminders.sql ================================================ -- Orleans Reminders table - https://learn.microsoft.com/dotnet/orleans/grains/timers-and-reminders CREATE TABLE OrleansRemindersTable ( ServiceId varchar(150) NOT NULL, GrainId varchar(150) NOT NULL, ReminderName varchar(150) NOT NULL, StartTime timestamptz(3) NOT NULL, Period bigint NOT NULL, GrainHash integer NOT NULL, Version integer NOT NULL, CONSTRAINT PK_RemindersTable_ServiceId_GrainId_ReminderName PRIMARY KEY(ServiceId, GrainId, ReminderName) ); CREATE FUNCTION upsert_reminder_row( ServiceIdArg OrleansRemindersTable.ServiceId%TYPE, GrainIdArg OrleansRemindersTable.GrainId%TYPE, ReminderNameArg OrleansRemindersTable.ReminderName%TYPE, StartTimeArg OrleansRemindersTable.StartTime%TYPE, PeriodArg OrleansRemindersTable.Period%TYPE, GrainHashArg OrleansRemindersTable.GrainHash%TYPE ) RETURNS TABLE(version integer) AS $func$ DECLARE VersionVar int := 0; BEGIN INSERT INTO OrleansRemindersTable ( ServiceId, GrainId, ReminderName, StartTime, Period, GrainHash, Version ) SELECT ServiceIdArg, GrainIdArg, ReminderNameArg, StartTimeArg, PeriodArg, GrainHashArg, 0 ON CONFLICT (ServiceId, GrainId, ReminderName) DO UPDATE SET StartTime = excluded.StartTime, Period = excluded.Period, GrainHash = excluded.GrainHash, Version = OrleansRemindersTable.Version + 1 RETURNING OrleansRemindersTable.Version INTO STRICT VersionVar; RETURN QUERY SELECT VersionVar AS versionr; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'UpsertReminderRowKey',' SELECT * FROM upsert_reminder_row( @ServiceId, @GrainId, @ReminderName, @StartTime, @Period, @GrainHash ); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowsKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadReminderRowKey',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows1Key',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainHash > @BeginHash AND @BeginHash IS NOT NULL AND GrainHash <= @EndHash AND @EndHash IS NOT NULL; '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'ReadRangeRows2Key',' SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND ((GrainHash > @BeginHash AND @BeginHash IS NOT NULL) OR (GrainHash <= @EndHash AND @EndHash IS NOT NULL)); '); CREATE FUNCTION delete_reminder_row( ServiceIdArg OrleansRemindersTable.ServiceId%TYPE, GrainIdArg OrleansRemindersTable.GrainId%TYPE, ReminderNameArg OrleansRemindersTable.ReminderName%TYPE, VersionArg OrleansRemindersTable.Version%TYPE ) RETURNS TABLE(row_count integer) AS $func$ DECLARE RowCountVar int := 0; BEGIN DELETE FROM OrleansRemindersTable WHERE ServiceId = ServiceIdArg AND ServiceIdArg IS NOT NULL AND GrainId = GrainIdArg AND GrainIdArg IS NOT NULL AND ReminderName = ReminderNameArg AND ReminderNameArg IS NOT NULL AND Version = VersionArg AND VersionArg IS NOT NULL; GET DIAGNOSTICS RowCountVar = ROW_COUNT; RETURN QUERY SELECT RowCountVar; END $func$ LANGUAGE plpgsql; INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowKey',' SELECT * FROM delete_reminder_row( @ServiceId, @GrainId, @ReminderName, @Version ); '); INSERT INTO OrleansQuery(QueryKey, QueryText) VALUES ( 'DeleteReminderRowsKey',' DELETE FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL; '); ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/README.md ================================================ # Microsoft Orleans Reminders for ADO.NET ## Introduction Microsoft Orleans Reminders for ADO.NET provides persistence for Orleans reminders using ADO.NET-compatible databases (SQL Server, MySQL, PostgreSQL, etc.). This allows your Orleans applications to schedule persistent reminders that will be triggered even after silo restarts or grain deactivation. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Reminders.AdoNet ``` You will also need to install the appropriate ADO.NET provider for your database: ```shell # For SQL Server dotnet add package Microsoft.Data.SqlClient # For MySQL dotnet add package MySql.Data # For PostgreSQL dotnet add package Npgsql ``` ## Example - Configuring ADO.NET Reminders ```csharp using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Hosting; using Example; // Create a host builder var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure ADO.NET as reminder storage .UseAdoNetReminderService(options => { options.Invariant = "Microsoft.Data.SqlClient"; // For SQL Server options.ConnectionString = "Server=localhost;Database=OrleansReminders;User ID=orleans;******;"; }); }); // Build and start the host var host = builder.Build(); await host.StartAsync(); // Get a grain reference and use it var grain = host.Services.GetRequiredService().GetGrain("my-reminder-grain"); await grain.StartReminder("DailyReport"); Console.WriteLine("Reminder started successfully!"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Using Reminders in a Grain ```csharp using System; using System.Threading.Tasks; using Orleans; using Orleans.Runtime; namespace Example; public interface IReminderGrain : IGrainWithStringKey { Task StartReminder(string reminderName); Task StopReminder(); } public class ReminderGrain : Grain, IReminderGrain, IRemindable { private string _reminderName = "MyReminder"; public async Task StartReminder(string reminderName) { _reminderName = reminderName; // Register a persistent reminder await RegisterOrUpdateReminder( reminderName, TimeSpan.FromMinutes(2), // Time to delay before the first tick (must be > 1 minute) TimeSpan.FromMinutes(5)); // Period of the reminder (must be > 1 minute) } public async Task StopReminder() { // Find and unregister the reminder var reminder = await GetReminder(_reminderName); if (reminder != null) { await UnregisterReminder(reminder); } } public Task ReceiveReminder(string reminderName, TickStatus status) { // This method is called when the reminder ticks Console.WriteLine($"Reminder {reminderName} triggered at {DateTime.UtcNow}. Status: {status}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Reminders and Timers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) - [Reminder Services](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/reminder-services) - [ADO.NET Database Setup](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/adonet-configuration) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/ReminderService/AdoNetReminderTable.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Reminders.AdoNet.Storage; namespace Orleans.Runtime.ReminderService { internal sealed class AdoNetReminderTable : IReminderTable { private readonly AdoNetReminderTableOptions options; private readonly string serviceId; private RelationalOrleansQueries orleansQueries; public AdoNetReminderTable( IOptions clusterOptions, IOptions storageOptions) { this.serviceId = clusterOptions.Value.ServiceId; this.options = storageOptions.Value; } public async Task Init() { this.orleansQueries = await RelationalOrleansQueries.CreateInstance(this.options.Invariant, this.options.ConnectionString); } public Task ReadRows(GrainId grainId) { return this.orleansQueries.ReadReminderRowsAsync(this.serviceId, grainId); } public Task ReadRows(uint beginHash, uint endHash) { return this.orleansQueries.ReadReminderRowsAsync(this.serviceId, beginHash, endHash); } public Task ReadRow(GrainId grainId, string reminderName) { return this.orleansQueries.ReadReminderRowAsync(this.serviceId, grainId, reminderName); } public Task UpsertRow(ReminderEntry entry) { if (entry.StartAt.Kind is DateTimeKind.Unspecified) { entry.StartAt = new DateTime(entry.StartAt.Ticks, DateTimeKind.Utc); } return this.orleansQueries.UpsertReminderRowAsync(this.serviceId, entry.GrainId, entry.ReminderName, entry.StartAt, entry.Period); } public Task RemoveRow(GrainId grainId, string reminderName, string eTag) { return this.orleansQueries.DeleteReminderRowAsync(this.serviceId, grainId, reminderName, eTag); } public Task TestOnlyClearTable() { return this.orleansQueries.DeleteReminderRowsAsync(this.serviceId); } } } ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/ReminderService/AdoNetReminderTableOptions.cs ================================================ namespace Orleans.Configuration { /// /// Options for ADO.NET reminder storage. /// public class AdoNetReminderTableOptions { /// /// Gets or sets the ADO.NET invariant. /// public string Invariant { get; set; } /// /// Gets or sets the connection string. /// [Redact] public string ConnectionString { get; set; } } } ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/ReminderService/AdoNetReminderTableOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Runtime.ReminderService; namespace Orleans.Configuration { /// /// Validates configuration. /// public class AdoNetReminderTableOptionsValidator : IConfigurationValidator { private readonly AdoNetReminderTableOptions options; public AdoNetReminderTableOptionsValidator(IOptions options) { this.options = options.Value; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetReminderTableOptions)} values for {nameof(AdoNetReminderTable)}. {nameof(options.Invariant)} is required."); } if (string.IsNullOrWhiteSpace(this.options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetReminderTableOptions)} values for {nameof(AdoNetReminderTable)}. {nameof(options.ConnectionString)} is required."); } } } } ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/SQLServer-Reminders.sql ================================================ -- Orleans Reminders table - https://learn.microsoft.com/dotnet/orleans/grains/timers-and-reminders IF OBJECT_ID(N'[OrleansRemindersTable]', 'U') IS NULL CREATE TABLE OrleansRemindersTable ( ServiceId NVARCHAR(150) NOT NULL, GrainId VARCHAR(150) NOT NULL, ReminderName NVARCHAR(150) NOT NULL, StartTime DATETIME2(3) NOT NULL, Period BIGINT NOT NULL, GrainHash INT NOT NULL, Version INT NOT NULL, CONSTRAINT PK_RemindersTable_ServiceId_GrainId_ReminderName PRIMARY KEY(ServiceId, GrainId, ReminderName) ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'UpsertReminderRowKey', 'DECLARE @Version AS INT = 0; SET XACT_ABORT, NOCOUNT ON; BEGIN TRANSACTION; UPDATE OrleansRemindersTable WITH(UPDLOCK, ROWLOCK, HOLDLOCK) SET StartTime = @StartTime, Period = @Period, GrainHash = @GrainHash, @Version = Version = Version + 1 WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; INSERT INTO OrleansRemindersTable ( ServiceId, GrainId, ReminderName, StartTime, Period, GrainHash, Version ) SELECT @ServiceId, @GrainId, @ReminderName, @StartTime, @Period, @GrainHash, 0 WHERE @@ROWCOUNT=0; SELECT @Version AS Version; COMMIT TRANSACTION; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'UpsertReminderRowKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ReadReminderRowsKey', 'SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ReadReminderRowsKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ReadReminderRowKey', 'SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ReadReminderRowKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ReadRangeRows1Key', 'SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainHash > @BeginHash AND @BeginHash IS NOT NULL AND GrainHash <= @EndHash AND @EndHash IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ReadRangeRows1Key' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'ReadRangeRows2Key', 'SELECT GrainId, ReminderName, StartTime, Period, Version FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND ((GrainHash > @BeginHash AND @BeginHash IS NOT NULL) OR (GrainHash <= @EndHash AND @EndHash IS NOT NULL)); ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'ReadRangeRows2Key' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'DeleteReminderRowKey', 'DELETE FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL AND GrainId = @GrainId AND @GrainId IS NOT NULL AND ReminderName = @ReminderName AND @ReminderName IS NOT NULL AND Version = @Version AND @Version IS NOT NULL; SELECT @@ROWCOUNT; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'DeleteReminderRowKey' ); INSERT INTO OrleansQuery(QueryKey, QueryText) SELECT 'DeleteReminderRowsKey', 'DELETE FROM OrleansRemindersTable WHERE ServiceId = @ServiceId AND @ServiceId IS NOT NULL; ' WHERE NOT EXISTS ( SELECT 1 FROM OrleansQuery oqt WHERE oqt.[QueryKey] = 'DeleteReminderRowsKey' ); ================================================ FILE: src/AdoNet/Orleans.Reminders.AdoNet/SiloBuilderReminderExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Runtime.ReminderService; namespace Orleans.Hosting { /// /// Silo host builder extensions. /// public static class SiloBuilderReminderExtensions { /// Adds reminder storage using ADO.NET. Instructions on configuring your database are available at . /// The builder. /// Configuration delegate. /// The provided , for chaining. /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder UseAdoNetReminderService( this ISiloBuilder builder, Action configureOptions) { return builder.UseAdoNetReminderService(ob => ob.Configure(configureOptions)); } /// Adds reminder storage using ADO.NET. Instructions on configuring your database are available at . /// The builder. /// Configuration delegate. /// The provided , for chaining. /// /// Instructions on configuring your database are available at . /// public static ISiloBuilder UseAdoNetReminderService( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices(services => services.UseAdoNetReminderService(configureOptions)); } /// Adds reminder storage using ADO.NET. Instructions on configuring your database are available at . /// The service collection. /// Configuration delegate. /// The provided , for chaining. /// /// Instructions on configuring your database are available at . /// public static IServiceCollection UseAdoNetReminderService(this IServiceCollection services, Action> configureOptions) { services.AddReminders(); services.AddSingleton(); services.ConfigureFormatter(); services.AddSingleton(); configureOptions(services.AddOptions()); return services; } } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetBatchContainer.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// The implementation for the ADONET provider. /// /// /// 1. This class only supports binary serialization as performance and data size is the priority for database storage. /// 2. Though the is supported here, it is not yet used, as the ADO.NET provider is not rewindable. /// [GenerateSerializer] [Alias("Orleans.Streaming.AdoNet.AdoNetBatchContainer")] internal class AdoNetBatchContainer : IBatchContainer { public AdoNetBatchContainer(StreamId streamId, List events, Dictionary requestContext) { ArgumentNullException.ThrowIfNull(events); StreamId = streamId; Events = events; RequestContext = requestContext; } #region Serialized State [Id(0)] public StreamId StreamId { get; } [Id(1)] public List Events { get; } [Id(2)] public Dictionary RequestContext { get; } [Id(3)] public EventSequenceTokenV2 SequenceToken { get; internal set; } = null!; /// /// Holds the receipt for message confirmation. /// [Id(4)] public int Dequeued { get; internal set; } #endregion Serialized State #region Interface StreamSequenceToken IBatchContainer.SequenceToken => SequenceToken; public IEnumerable> GetEvents() { return SequenceToken is null ? throw new InvalidOperationException($"Cannot get events from a half-baked {nameof(AdoNetBatchContainer)}") : Events .OfType() .Select((e, i) => Tuple.Create(e, SequenceToken.CreateSequenceTokenForEvent(i))); } public bool ImportRequestContext() { if (RequestContext is not null) { RequestContextExtensions.Import(RequestContext); return true; } return false; } #endregion Interface #region Conversion /// /// Creates a new from the specified . /// public static AdoNetBatchContainer FromMessage(Serializer serializer, AdoNetStreamMessage message) { ArgumentNullException.ThrowIfNull(serializer); ArgumentNullException.ThrowIfNull(message); var container = serializer.Deserialize(message.Payload); container.SequenceToken = new(message.MessageId); container.Dequeued = message.Dequeued; return container; } /// /// Converts the specified to a message payload. /// public static byte[] ToMessagePayload(Serializer serializer, StreamId streamId, List events, Dictionary requestContext) { ArgumentNullException.ThrowIfNull(serializer); ArgumentNullException.ThrowIfNull(events); var container = new AdoNetBatchContainer(streamId, events, requestContext); var payload = serializer.SerializeToArray(container); return payload; } #endregion Conversion public override string ToString() => $"[{nameof(AdoNetBatchContainer)}:Stream={StreamId},#Items={Events.Count}]"; } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetQueueAdapter.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// Stream queue storage adapter for ADO.NET providers. /// internal partial class AdoNetQueueAdapter(string name, AdoNetStreamOptions streamOptions, ClusterOptions clusterOptions, SimpleQueueCacheOptions cacheOptions, AdoNetStreamQueueMapper mapper, RelationalOrleansQueries queries, Serializer serializer, ILogger logger, IServiceProvider serviceProvider) : IQueueAdapter { private readonly ILogger _logger = logger; /// /// Maps to the ProviderId in the database. /// public string Name { get; } = name; /// /// The ADO.NET provider is not yet rewindable. /// public bool IsRewindable => false; /// /// The ADO.NET provider works both ways. /// public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { // map the queue id var adoNetQueueId = mapper.GetAdoNetQueueId(queueId); // create the receiver return ReceiverFactory(serviceProvider, [Name, adoNetQueueId, streamOptions, clusterOptions, cacheOptions, queries]); } public async Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { // the ADO.NET provider is not rewindable so we do not support user supplied tokens if (token is not null) { throw new ArgumentException($"{nameof(AdoNetQueueAdapter)} does not support a user supplied {nameof(StreamSequenceToken)}."); } // map the Orleans stream id to the corresponding queue id var queueId = mapper.GetAdoNetQueueId(streamId); // create the payload from the events var payload = AdoNetBatchContainer.ToMessagePayload(serializer, streamId, events.Cast().ToList(), requestContext); // we can enqueue the message now try { await queries.QueueStreamMessageAsync(clusterOptions.ServiceId, Name, queueId, payload, streamOptions.ExpiryTimeout.TotalSecondsCeiling()); } catch (Exception ex) { LogFailedToQueueStreamMessage(ex, clusterOptions.ServiceId, Name, queueId); throw; } } /// /// The receiver factory. /// private static readonly ObjectFactory ReceiverFactory = ActivatorUtilities.CreateFactory([typeof(string), typeof(string), typeof(AdoNetStreamOptions), typeof(ClusterOptions), typeof(SimpleQueueCacheOptions), typeof(RelationalOrleansQueries)]); #region Logging [LoggerMessage(1, LogLevel.Error, "Failed to queue stream message with ({ServiceId}, {ProviderId}, {QueueId})")] private partial void LogFailedToQueueStreamMessage(Exception ex, string serviceId, string providerId, string queueId); #endregion Logging } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetQueueAdapterFactory.cs ================================================ using System.Threading; using Microsoft.Extensions.Hosting; namespace Orleans.Streaming.AdoNet; internal class AdoNetQueueAdapterFactory : IQueueAdapterFactory { public AdoNetQueueAdapterFactory(string name, AdoNetStreamOptions streamOptions, ClusterOptions clusterOptions, SimpleQueueCacheOptions cacheOptions, HashRingStreamQueueMapperOptions hashOptions, ILoggerFactory loggerFactory, IHostApplicationLifetime lifetime, IServiceProvider serviceProvider) { _name = name; _streamOptions = streamOptions; _clusterOptions = clusterOptions; _cacheOptions = cacheOptions; _lifetime = lifetime; _serviceProvider = serviceProvider; _streamQueueMapper = new HashRingBasedStreamQueueMapper(hashOptions, name); _cache = new SimpleQueueAdapterCache(cacheOptions, name, loggerFactory); _adoNetQueueMapper = new AdoNetStreamQueueMapper(_streamQueueMapper); } private readonly string _name; private readonly AdoNetStreamOptions _streamOptions; private readonly ClusterOptions _clusterOptions; private readonly SimpleQueueCacheOptions _cacheOptions; private readonly IHostApplicationLifetime _lifetime; private readonly IServiceProvider _serviceProvider; private readonly HashRingBasedStreamQueueMapper _streamQueueMapper; private readonly SimpleQueueAdapterCache _cache; private readonly AdoNetStreamQueueMapper _adoNetQueueMapper; private RelationalOrleansQueries _queries; /// /// Unfortunate implementation detail to account for lack of async lifetime. /// Ideally this concern will be moved upstream so this won't be needed. /// private readonly SemaphoreSlim _semaphore = new(1); /// /// Ensures queries are loaded only once while allowing for recovery if the load fails. /// private ValueTask GetQueriesAsync() { // attempt fast path return _queries is not null ? new(_queries) : new(CoreAsync()); // slow path async Task CoreAsync() { await _semaphore.WaitAsync(_streamOptions.InitializationTimeout, _lifetime.ApplicationStopping); try { // attempt fast path again if (_queries is not null) { return _queries; } // slow path - the member variable will only be set if the call succeeds return _queries = await RelationalOrleansQueries .CreateInstance(_streamOptions.Invariant, _streamOptions.ConnectionString) .WaitAsync(_streamOptions.InitializationTimeout); } finally { _semaphore.Release(); } } } public async Task CreateAdapter() { var queries = await GetQueriesAsync(); return AdapterFactory(_serviceProvider, [_name, _streamOptions, _clusterOptions, _cacheOptions, _adoNetQueueMapper, queries]); } public async Task GetDeliveryFailureHandler(QueueId queueId) { var queries = await GetQueriesAsync(); return HandlerFactory(_serviceProvider, [false, _streamOptions, _clusterOptions, _adoNetQueueMapper, queries]); } public IQueueAdapterCache GetQueueAdapterCache() => _cache; public IStreamQueueMapper GetStreamQueueMapper() => _streamQueueMapper; /// /// Used by the silo and client configurators as an entry point to set up a stream. /// public static IQueueAdapterFactory Create(IServiceProvider serviceProvider, string name) { ArgumentNullException.ThrowIfNull(serviceProvider); ArgumentNullException.ThrowIfNull(name); var streamOptions = serviceProvider.GetOptionsByName(name); var clusterOptions = serviceProvider.GetProviderClusterOptions(name).Value; var cacheOptions = serviceProvider.GetOptionsByName(name); var hashOptions = serviceProvider.GetOptionsByName(name); return QueueAdapterFactoryFactory(serviceProvider, [name, streamOptions, clusterOptions, cacheOptions, hashOptions]); } /// /// Factory of instances. /// private static readonly ObjectFactory QueueAdapterFactoryFactory = ActivatorUtilities.CreateFactory([typeof(string), typeof(AdoNetStreamOptions), typeof(ClusterOptions), typeof(SimpleQueueCacheOptions), typeof(HashRingStreamQueueMapperOptions)]); /// /// Factory of instances. /// private static readonly ObjectFactory AdapterFactory = ActivatorUtilities.CreateFactory([typeof(string), typeof(AdoNetStreamOptions), typeof(ClusterOptions), typeof(SimpleQueueCacheOptions), typeof(AdoNetStreamQueueMapper), typeof(RelationalOrleansQueries)]); /// /// Factory of instances. /// private static readonly ObjectFactory HandlerFactory = ActivatorUtilities.CreateFactory([typeof(bool), typeof(AdoNetStreamOptions), typeof(ClusterOptions), typeof(AdoNetStreamQueueMapper), typeof(RelationalOrleansQueries)]); } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetQueueAdapterReceiver.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// Receives message batches from an individual queue of an ADO.NET provider. /// internal partial class AdoNetQueueAdapterReceiver(string providerId, string queueId, AdoNetStreamOptions streamOptions, ClusterOptions clusterOptions, SimpleQueueCacheOptions cacheOptions, RelationalOrleansQueries queries, Serializer serializer, ILogger logger) : IQueueAdapterReceiver { private readonly ILogger _logger = logger; /// /// Flags that no further work should be attempted. /// private bool _shutdown; /// /// Helps shutdown wait for any outstanding storage operation. /// private Task _outstandingTask; /// /// This receiver does not require initialization. /// public Task Initialize(TimeSpan timeout) => Task.CompletedTask; /// /// Waits for any outstanding work before shutting down. /// public async Task Shutdown(TimeSpan timeout) { // disable any further attempts to access storage _shutdown = true; // wait for any outstanding storage operation to complete. var outstandingTask = _outstandingTask; if (outstandingTask is not null) { try { await outstandingTask.WaitAsync(timeout); } catch (Exception ex) { LogShutdownFault(ex, clusterOptions.ServiceId, providerId, queueId); } } } /// public async Task> GetQueueMessagesAsync(int maxCount) { // if shutdown has been called then we refuse further requests gracefully if (_shutdown) { return []; } // cap max count as appropriate maxCount = Math.Min(maxCount, cacheOptions.CacheSize); try { // grab a message batch from storage while pinning the task so shutdown can wait for it var task = queries.GetStreamMessagesAsync( clusterOptions.ServiceId, providerId, queueId, maxCount, streamOptions.MaxAttempts, streamOptions.VisibilityTimeout.TotalSecondsCeiling(), streamOptions.DeadLetterEvictionTimeout.TotalSecondsCeiling(), streamOptions.EvictionInterval.TotalSecondsCeiling(), streamOptions.EvictionBatchSize); _outstandingTask = task; var messages = await task; // convert the messages into standard batch containers return messages.Select(x => AdoNetBatchContainer.FromMessage(serializer, x)).Cast().ToList(); } catch (Exception ex) { LogDequeueFailed(ex, clusterOptions.ServiceId, providerId, queueId); throw; } finally { _outstandingTask = null; } } /// public async Task MessagesDeliveredAsync(IList messages) { // skip work if there are no messages to deliver if (messages.Count == 0) { return; } // get the identifiers for the messages to confirm var items = messages.Cast().Select(x => new AdoNetStreamConfirmation(x.SequenceToken.SequenceNumber, x.Dequeued)).ToList(); try { // execute the confirmation while pinning the task so shutdown can wait for it var task = queries.ConfirmStreamMessagesAsync(clusterOptions.ServiceId, providerId, queueId, items); _outstandingTask = task; try { await task; } catch (Exception ex) { LogConfirmationFailed(ex, clusterOptions.ClusterId, providerId, queueId, items); throw; } } finally { _outstandingTask = null; } } #region Logging [LoggerMessage(1, LogLevel.Error, "Failed to get messages from ({ServiceId}, {ProviderId}, {QueueId})")] private partial void LogDequeueFailed(Exception exception, string serviceId, string providerId, string queueId); [LoggerMessage(2, LogLevel.Error, "Failed to confirm messages for ({ServiceId}, {ProviderId}, {QueueId}, {@Items})")] private partial void LogConfirmationFailed(Exception exception, string serviceId, string providerId, string queueId, List items); [LoggerMessage(3, LogLevel.Warning, "Handled fault while shutting down receiver for ({ServiceId}, {ProviderId}, {QueueId})")] private partial void LogShutdownFault(Exception exception, string serviceId, string providerId, string queueId); #endregion Logging } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamConfirmation.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// The model that represents a message that can be confirmed. /// internal record AdoNetStreamConfirmation( long MessageId, int Dequeued) { public AdoNetStreamConfirmation() : this(0, 0) { } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamConfirmationAck.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// The model that represents a message that was successfully confirmed. /// internal record AdoNetStreamConfirmationAck( string ServiceId, string ProviderId, string QueueId, long MessageId) { public AdoNetStreamConfirmationAck() : this("", "", "", 0) { } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamDeadLetter.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// The model that represents a dead letter in an ADONET streaming provider. /// internal record AdoNetStreamDeadLetter( string ServiceId, string ProviderId, string QueueId, long MessageId, int Dequeued, DateTime VisibleOn, DateTime ExpiresOn, DateTime CreatedOn, DateTime ModifiedOn, DateTime DeadOn, DateTime RemoveOn, byte[] Payload) { public AdoNetStreamDeadLetter() : this("", "", "", 0, 0, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, []) { } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamFailureHandler.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// An that attempts to move the message to dead letters. /// internal partial class AdoNetStreamFailureHandler(bool faultOnFailure, AdoNetStreamOptions streamOptions, ClusterOptions clusterOptions, AdoNetStreamQueueMapper mapper, RelationalOrleansQueries queries, ILogger logger) : IStreamFailureHandler { private readonly ILogger _logger = logger; /// /// Gets a value indicating whether the subscription should fault when there is an error. /// public bool ShouldFaultSubsriptionOnError { get; } = faultOnFailure; /// /// Attempts to move the message to dead letters on delivery failure. /// public Task OnDeliveryFailure(GuidId subscriptionId, string streamProviderName, StreamId streamIdentity, StreamSequenceToken sequenceToken) => OnFailureAsync(streamProviderName, streamIdentity, sequenceToken); /// /// Attempts to move the message to dead letters on delivery failure. /// public Task OnSubscriptionFailure(GuidId subscriptionId, string streamProviderName, StreamId streamIdentity, StreamSequenceToken sequenceToken) => OnFailureAsync(streamProviderName, streamIdentity, sequenceToken); /// /// Attempts to move the message to dead letters on delivery failure. /// private async Task OnFailureAsync(string streamProviderName, StreamId streamIdentity, StreamSequenceToken sequenceToken) { ArgumentNullException.ThrowIfNull(streamProviderName); ArgumentNullException.ThrowIfNull(sequenceToken); var queueId = mapper.GetAdoNetQueueId(streamIdentity); try { await queries.FailStreamMessageAsync(clusterOptions.ServiceId, streamProviderName, queueId, sequenceToken.SequenceNumber, streamOptions.MaxAttempts, streamOptions.DeadLetterEvictionTimeout.TotalSecondsCeiling()); LogMovedMessage(clusterOptions.ServiceId, streamProviderName, queueId, sequenceToken.SequenceNumber); } catch (Exception ex) { LogFailedToMoveMessage(ex, clusterOptions.ServiceId, streamProviderName, queueId, sequenceToken.SequenceNumber); throw; } } #region Logging [LoggerMessage(1, LogLevel.Warning, "Moved failed delivery to dead letters: ({ServiceId}, {ProviderId}, {QueueId}, {MessageId})")] private partial void LogMovedMessage(string serviceId, string providerId, string queueId, long messageId); [LoggerMessage(2, LogLevel.Error, "Failed to move failed delivery to dead letters: ({ServiceId}, {ProviderId}, {QueueId}, {MessageId}")] private partial void LogFailedToMoveMessage(Exception ex, string serviceId, string providerId, string queueId, long messageId); #endregion Logging } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamMessage.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// The model that represents a stored message in an ADONET streaming provider. /// internal record AdoNetStreamMessage( string ServiceId, string ProviderId, string QueueId, long MessageId, int Dequeued, DateTime VisibleOn, DateTime ExpiresOn, DateTime CreatedOn, DateTime ModifiedOn, byte[] Payload) { public AdoNetStreamMessage() : this("", "", "", 0, 0, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, []) { } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamMessageAck.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// Represents an acknowledgement from storage that a message was enqueued. /// This is used to surface any generated identifiers for testing. /// internal record AdoNetStreamMessageAck( string ServiceId, string ProviderId, string QueueId, long MessageId) { public AdoNetStreamMessageAck() : this("", "", "", 0) { } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamOptions.cs ================================================ namespace Orleans.Configuration; /// /// Options for ADO.NET Streaming. /// public class AdoNetStreamOptions { /// /// Gets or sets the ADO.NET invariant. /// public string Invariant { get; set; } /// /// Gets or sets the connection string. /// [Redact] public string ConnectionString { get; set; } /// /// The maximum number of attempts to deliver a message. /// The message is eventually moved to dead letters if these many attempts are made without success. /// public int MaxAttempts { get; set; } = 5; /// /// The timeout until a message is allowed to be dequeued again if not yet confirmed. /// public TimeSpan VisibilityTimeout { get; set; } = TimeSpan.FromMinutes(1); /// /// The expiry timeout until a message is considered expired and moved to dead letters regardless of attempts. /// The message is only moved if the current attempt is also past its visibility timeout. /// public TimeSpan ExpiryTimeout { get; set; } = TimeSpan.FromMinutes(10); /// /// The removal timeout until a failed message is deleted from the dead letters table. /// public TimeSpan DeadLetterEvictionTimeout { get; set; } = TimeSpan.FromDays(7); /// /// The period of time between eviction activities. /// These include moving expired messages to dead letters and removing dead letters after their own lifetime. /// This period is cluster wide and will not change with the number of silos. /// public TimeSpan EvictionInterval { get; set; } = TimeSpan.FromSeconds(10); /// /// The maximum number of messages affected by an eviction batch. /// public int EvictionBatchSize { get; set; } = 1000; /// /// A safety timeout for underlying database initialization. /// public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30); } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamOptionsValidator.cs ================================================ using static System.String; namespace Orleans.Configuration; /// /// Validates configuration. /// public class AdoNetStreamOptionsValidator(AdoNetStreamOptions options, string name) : IConfigurationValidator { /// public void ValidateConfiguration() { if (IsNullOrWhiteSpace(options.Invariant)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.Invariant)} is required."); } if (IsNullOrWhiteSpace(options.ConnectionString)) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.ConnectionString)} is required."); } if (options.MaxAttempts < 0) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.MaxAttempts)} must be greater than zero."); } if (options.VisibilityTimeout < TimeSpan.Zero) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.VisibilityTimeout)} must be greater than zero."); } if (options.EvictionInterval < TimeSpan.Zero) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.EvictionInterval)} must be greater than zero."); } if (options.ExpiryTimeout < TimeSpan.Zero) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.ExpiryTimeout)} must be greater than zero."); } if (options.DeadLetterEvictionTimeout < TimeSpan.Zero) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.DeadLetterEvictionTimeout)} must be greater than zero."); } if (options.EvictionBatchSize < 0) { throw new OrleansConfigurationException($"Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Streaming Provider '{name}': {nameof(options.EvictionBatchSize)} must be greater than zero."); } } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/AdoNetStreamQueueMapper.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// Maps Orleans streams and queues identifiers to ADO.NET queue identifiers. /// internal class AdoNetStreamQueueMapper(IConsistentRingStreamQueueMapper mapper) { /// /// Caches the lookup of Orleans stream identifiers to ADO.NET queue identifiers. /// private readonly ConcurrentDictionary _byStreamLookup = new(); /// /// Caches the lookup of Orleans queue identifiers to ADO.NET queue identifiers. /// private readonly ConcurrentDictionary _byQueueLookup = new(); /// /// Caches the mapping factory of Orleans stream identifiers to ADO.NET queue identifiers. /// private readonly Func _fromStreamFactory = (StreamId streamId) => mapper.GetQueueForStream(streamId).ToString(); /// /// Cache the mapping factory of Orleans queue identifiers to ADO.NET queue identifiers. /// private readonly Func _fromQueueFactory = (QueueId queueId) => queueId.ToString(); /// /// Gets the ADO.NET QueueId for the specified Orleans StreamId. /// public string GetAdoNetQueueId(StreamId streamId) => _byStreamLookup.GetOrAdd(streamId, _fromStreamFactory); /// /// Gets the ADO.NET QueueId for the specified Orleans QueueId. /// public string GetAdoNetQueueId(QueueId queueId) => _byQueueLookup.GetOrAdd(queueId, _fromQueueFactory); /// /// Gets all ADO.NET QueueIds in the current space. /// public IEnumerable GetAllAdoNetQueueIds() { foreach (var queueId in mapper.GetAllQueues()) { yield return _byQueueLookup.GetOrAdd(queueId, _fromQueueFactory); } } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Extensions.cs ================================================ namespace Orleans.Streaming.AdoNet; /// /// Internal syntax sugar. /// internal static class Extensions { /// public static int Int32Ceiling(this double value) => (int)Math.Ceiling(value); /// /// Rounds up the specified time span to the nearest upper second and returns the total number of seconds as an integer. /// public static int TotalSecondsCeiling(this TimeSpan value) => value.TotalSeconds.Int32Ceiling(); /// /// Rounds up the specified time span to the nearest upper second and returns the total number of seconds as an integer. /// public static TimeSpan SecondsCeiling(this TimeSpan value) => TimeSpan.FromSeconds(value.TotalSecondsCeiling()); } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/GlobalSuppressions.cs ================================================ // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. [assembly: SuppressMessage("Style", "IDE0053:Use expression body for lambda expression", Justification = "N/A")] ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Hosting/ClusterClientAdoNetStreamConfigurator.cs ================================================ using Orleans.Streaming.AdoNet; namespace Orleans.Hosting; /// /// Helps set up an individual stream provider on a silo. /// public class ClusterClientAdoNetStreamConfigurator : ClusterClientPersistentStreamConfigurator { public ClusterClientAdoNetStreamConfigurator(string name, IClientBuilder clientBuilder) : base(name, clientBuilder, AdoNetQueueAdapterFactory.Create) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(clientBuilder); clientBuilder.ConfigureServices(services => { services .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new AdoNetStreamOptionsValidator(sp.GetOptionsByName(name), name)); }); // in a typical i/o bound shared database there is little benefit to more than one queue per provider // however multiple queues are fully supported if the user wants to fine tune throughput for their own system ConfigurePartitioning(1); } public ClusterClientAdoNetStreamConfigurator ConfigureAdoNet(Action> configureOptions) { ArgumentNullException.ThrowIfNull(configureOptions); this.Configure(configureOptions); return this; } public ClusterClientAdoNetStreamConfigurator ConfigureCache(int cacheSize = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE) { this.Configure(ob => ob.Configure(options => options.CacheSize = cacheSize)); return this; } public ClusterClientAdoNetStreamConfigurator ConfigurePartitioning(int partitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = partitions)); return this; } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Hosting/ClusterClientAdoNetStreamExtensions.cs ================================================ namespace Orleans.Hosting; /// /// Allows configuration of individual ADO.NET streams in a cluster client. /// public static class ClusterClientAdoNetStreamExtensions { /// /// Configure cluster client to use ADO.NET persistent streams with default settings. /// public static IClientBuilder AddAdoNetStreams(this IClientBuilder builder, string name, Action configureOptions) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configureOptions); return builder.AddAdoNetStreams(name, b => { b.ConfigureAdoNet(ob => ob.Configure(configureOptions)); }); } /// /// Configure cluster client to use ADO.NET persistent streams. /// public static IClientBuilder AddAdoNetStreams(this IClientBuilder builder, string name, Action configure) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configure); var configurator = new ClusterClientAdoNetStreamConfigurator(name, builder); configure.Invoke(configurator); return builder; } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Hosting/SiloAdoNetStreamConfigurator.cs ================================================ using Orleans.Streaming.AdoNet; namespace Orleans.Hosting; /// /// Helps set up an individual stream provider on a silo. /// public class SiloAdoNetStreamConfigurator : SiloPersistentStreamConfigurator { public SiloAdoNetStreamConfigurator(string name, Action> configureDelegate) : base(name, configureDelegate, AdoNetQueueAdapterFactory.Create) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configureDelegate); ConfigureDelegate(services => { services .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new AdoNetStreamOptionsValidator(sp.GetOptionsByName(name), name)); }); // in a typical i/o bound shared database there is little benefit to more than one queue per provider // however multiple queues are fully supported if the user wants to fine tune throughput for their own system ConfigurePartitioning(1); } public SiloAdoNetStreamConfigurator ConfigureAdoNet(Action> configureOptions) { ArgumentNullException.ThrowIfNull(configureOptions); this.Configure(configureOptions); return this; } public SiloAdoNetStreamConfigurator ConfigureCache(int cacheSize = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE) { this.Configure(ob => ob.Configure(options => options.CacheSize = cacheSize)); return this; } public SiloAdoNetStreamConfigurator ConfigurePartitioning(int partitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = partitions)); return this; } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Hosting/SiloBuilderAdoNetStreamExtensions.cs ================================================ namespace Orleans.Hosting; /// /// Allows configuration of individual ADO.NET streams in a silo. /// public static class SiloBuilderAdoNetStreamExtensions { /// /// Configure silo to use ADO.NET persistent streams. /// public static ISiloBuilder AddAdoNetStreams(this ISiloBuilder builder, string name, Action configureOptions) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configureOptions); return builder.AddAdoNetStreams(name, b => { b.ConfigureAdoNet(ob => ob.Configure(configureOptions)); }); } /// /// Configure silo to use ADO.NET persistent streams. /// public static ISiloBuilder AddAdoNetStreams(this ISiloBuilder builder, string name, Action configure) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configure); var configurator = new SiloAdoNetStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure.Invoke(configurator); return builder; } } ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/MySQL-Streaming.sql ================================================ CREATE TABLE OrleansStreamMessageSequence ( MessageId BIGINT NOT NULL ); INSERT INTO OrleansStreamMessageSequence SELECT 0 WHERE NOT EXISTS (SELECT * FROM OrleansStreamMessageSequence); DELIMITER $$ CREATE TABLE OrleansStreamMessage ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider*/ QueueId NVARCHAR(150) NOT NULL, /* The unique ascending number of the queued message */ MessageId BIGINT NOT NULL, /* The number of times the event was dequeued */ Dequeued INT NOT NULL, /* The UTC time at which the event will become visible */ VisibleOn DATETIME(6) NOT NULL, /* The UTC time at which the event will expire */ ExpiresOn DATETIME(6) NOT NULL, /* The UTC time at which the event was created - troubleshooting only */ CreatedOn DATETIME(6) NOT NULL, /* The UTC time at which the event was updated - troubleshooting only */ ModifiedOn DATETIME(6) NOT NULL, /* The arbitrarily large payload of the event */ Payload LONGBLOB NOT NULL, /* This PK supports the various ordered scanning queries. */ PRIMARY KEY (ServiceId, ProviderId, QueueId, MessageId) ); DELIMITER $$ CREATE TABLE OrleansStreamDeadLetter ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider*/ QueueId NVARCHAR(150) NOT NULL, /* The unique ascending number of the queued message */ MessageId BIGINT NOT NULL, /* The number of times the event was dequeued */ Dequeued INT NOT NULL, /* The UTC time at which the event will become visible */ VisibleOn DATETIME(6) NOT NULL, /* The UTC time at which the event will expire */ ExpiresOn DATETIME(6) NOT NULL, /* The UTC time at which the event was created - troubleshooting only */ CreatedOn DATETIME(6) NOT NULL, /* The UTC time at which the event was updated - troubleshooting only */ ModifiedOn DATETIME(6) NOT NULL, /* The UTC time at which the event was given up on - troubleshooting only */ DeadOn DATETIME(6) NOT NULL, /* The UTC time at which the event is scheduled to be removed from dead letters */ RemoveOn DATETIME(6) NOT NULL, /* The arbitrarily large payload of the event */ Payload LONGBLOB NULL, /* This PK supports the various ordered scanning queries. */ PRIMARY KEY (ServiceId, ProviderId, QueueId, MessageId) ); DELIMITER $$ CREATE TABLE OrleansStreamControl ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider */ QueueId NVARCHAR(150) NOT NULL, /* The next due schedule for messages to be evicted */ EvictOn DATETIME(6) NOT NULL, /* Each row represents a flat configuration object for an individual queue */ PRIMARY KEY (ServiceId, ProviderId, QueueId) ); DELIMITER $$ CREATE PROCEDURE QueueStreamMessage ( IN _ServiceId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _QueueId NVARCHAR(150), IN _Payload LONGBLOB, IN _ExpiryTimeout INT ) BEGIN DECLARE _Now DATETIME(6) DEFAULT UTC_TIMESTAMP(6); DECLARE _ExpiresOn DATETIME(6) DEFAULT DATE_ADD(_Now, INTERVAL _ExpiryTimeout SECOND); DECLARE _MessageId BIGINT; UPDATE OrleansStreamMessageSequence SET MessageId = LAST_INSERT_ID(MessageId + 1); SET _MessageId = LAST_INSERT_ID(); INSERT INTO OrleansStreamMessage ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, Payload ) VALUES ( _ServiceId, _ProviderId, _QueueId, _MessageId, 0, _Now, _ExpiresOn, _Now, _Now, _Payload ); SELECT _ServiceId AS ServiceId, _ProviderId AS ProviderId, _QueueId AS QueueId, _MessageId AS MessageId; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'QueueStreamMessageKey', 'CALL QueueStreamMessage(@ServiceId, @ProviderId, @QueueId, @Payload, @ExpiryTimeout)' DELIMITER $$ CREATE PROCEDURE GetStreamMessages ( IN _ServiceId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _QueueId NVARCHAR(150), IN _MaxCount INT, IN _MaxAttempts INT, IN _VisibilityTimeout INT, IN _RemovalTimeout INT, IN _EvictionInterval INT, IN _EvictionBatchSize INT ) BEGIN DECLARE _Now DATETIME(6) DEFAULT UTC_TIMESTAMP(6); DECLARE _VisibleOn DATETIME(6) DEFAULT DATE_ADD(_Now, INTERVAL _VisibilityTimeout SECOND); DECLARE _NextEvictOn TIMESTAMP(6) DEFAULT DATE_ADD(_Now, INTERVAL _EvictionInterval SECOND); DECLARE _EvictOn DATETIME(6); DECLARE _Count INT; -- get the next eviction schedule SET _EvictOn = ( SELECT EvictOn FROM OrleansStreamControl WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId ); -- initialize the control row as necessary IF _EvictOn IS NULL THEN -- race to initialize the control row INSERT OrleansStreamControl ( ServiceId, ProviderId, QueueId, EvictOn ) VALUES ( _ServiceId, _ProviderId, _QueueId, _NextEvictOn ) ON DUPLICATE KEY UPDATE EvictOn = EvictOn; -- read the winning update SET _EvictOn = ( SELECT EvictOn FROM OrleansStreamControl WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId ); END IF; IF _EvictOn <= _Now THEN -- race to update the control row UPDATE OrleansStreamControl SET EvictOn = _NextEvictOn WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND EvictOn <= _Now; -- if we won the race then we also run eviction IF ROW_COUNT() > 0 THEN CALL EvictStreamMessages(_ServiceId, _ProviderId, _QueueId, _MaxAttempts, _RemovalTimeout, _EvictionBatchSize); CALL EvictStreamDeadLetters(_ServiceId, _ProviderId, _QueueId, _EvictionBatchSize); END IF; END IF; START TRANSACTION; /* elect the batch of messages to dequeue and lock them in order */ CREATE TEMPORARY TABLE _Batch AS SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND Dequeued < _MaxAttempts AND VisibleOn <= _Now AND ExpiresOn > _Now ORDER BY ServiceId, ProviderId, QueueId, MessageId LIMIT _MaxCount FOR UPDATE SKIP LOCKED; /* update the message batch */ UPDATE OrleansStreamMessage AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId SET M.Dequeued = M.Dequeued + 1, M.VisibleOn = _VisibleOn, M.ModifiedOn = _Now; /* return the updated batch */ SELECT M.ServiceId, M.ProviderId, M.QueueId, M.MessageId, M.Dequeued, M.VisibleOn, M.ExpiresOn, M.CreatedOn, M.ModifiedOn, M.Payload FROM OrleansStreamMessage AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; DROP TEMPORARY TABLE _Batch; COMMIT; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'GetStreamMessagesKey', 'CALL GetStreamMessages(@ServiceId, @ProviderId, @QueueId, @MaxCount, @MaxAttempts, @VisibilityTimeout, @RemovalTimeout, @EvictionInterval, @EvictionBatchSize)'; DELIMITER $$ CREATE PROCEDURE ConfirmStreamMessages ( IN _ServiceId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _QueueId NVARCHAR(150), IN _Items LONGTEXT ) BEGIN DECLARE _Delimiter1 NVARCHAR(1) DEFAULT '|'; DECLARE _Delimiter2 NVARCHAR(1) DEFAULT ':'; DECLARE _Value LONGTEXT; DECLARE _MessageId BIGINT; DECLARE _Dequeued INT; SET _Items = CONCAT(_Items, _Delimiter1); /* parse the message identifiers to be deleted */ DROP TEMPORARY TABLE IF EXISTS _ItemsTable; CREATE TEMPORARY TABLE _ItemsTable ( ServiceId NVARCHAR(150) NOT NULL, ProviderId NVARCHAR(150) NOT NULL, QueueId NVARCHAR(150) NOT NULL, MessageId BIGINT NOT NULL, Dequeued INT NOT NULL, PRIMARY KEY (ServiceId, ProviderId, QueueId, MessageId) ); WHILE LOCATE(_Delimiter1, _Items) > 0 DO SET _Value = SUBSTRING_INDEX(_Items, _Delimiter1, 1); SET _MessageId = CAST(SUBSTRING_INDEX(_Value, _Delimiter2, 1) AS UNSIGNED); SET _Dequeued = CAST(SUBSTRING_INDEX(_Value, _Delimiter2, -1) AS UNSIGNED); INSERT INTO _ItemsTable ( ServiceId, ProviderId, QueueId, MessageId, Dequeued ) VALUES ( _ServiceId, _ProviderId, _QueueId, _MessageId, _Dequeued ); SET _Items = SUBSTRING(_Items, LOCATE(_Delimiter1, _Items) + 1); END WHILE; START TRANSACTION; /* elect the batch of messages to confirm and lock them in order */ CREATE TEMPORARY TABLE _Batch AS SELECT M.ServiceId, M.ProviderId, M.QueueId, M.MessageId FROM OrleansStreamMessage AS M INNER JOIN _ItemsTable AS I ON M.ServiceId = I.ServiceId AND M.ProviderId = I.ProviderId AND M.QueueId = I.QueueId AND M.MessageId = I.MessageId AND M.Dequeued = I.Dequeued ORDER BY M.ServiceId, M.ProviderId, M.QueueId, M.MessageId FOR UPDATE; /* delete the elected batch */ DELETE M FROM OrleansStreamMessage AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; /* return the ack */ SELECT ServiceId, ProviderId, QueueId, MessageId FROM _Batch; DROP TEMPORARY TABLE _Batch; DROP TEMPORARY TABLE _ItemsTable; COMMIT; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'ConfirmStreamMessagesKey', 'CALL ConfirmStreamMessages(@ServiceId, @ProviderId, @QueueId, @Items)'; DELIMITER $$ CREATE PROCEDURE FailStreamMessage ( IN _ServiceId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _QueueId NVARCHAR(150), IN _MessageId BIGINT, IN _MaxAttempts INT, IN _RemovalTimeout INT ) BEGIN DECLARE _Now DATETIME(6) DEFAULT UTC_TIMESTAMP(6); DECLARE _RemoveOn DATETIME(6) DEFAULT DATE_ADD(_Now, INTERVAL _RemovalTimeout SECOND); /* if the message can still be dequeued then attempt to mark it visible again */ UPDATE OrleansStreamMessage SET VisibleOn = _Now, ModifiedOn = _Now WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND MessageId = _MessageId AND Dequeued < _MaxAttempts; IF ROW_COUNT() = 0 THEN START TRANSACTION; /* otherwise attempt to move the message to dead letters */ CREATE TEMPORARY TABLE Deleted AS SELECT * FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND MessageId = _MessageId; DELETE FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND MessageId = _MessageId; INSERT INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ) SELECT ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, _Now AS DeadOn, _RemoveOn AS RemoveOn, Payload FROM Deleted; COMMIT; END IF; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'FailStreamMessageKey', 'CALL FailStreamMessage(@ServiceId, @ProviderId, @QueueId, @MessageId, @MaxAttempts, @RemovalTimeout)' DELIMITER $$ CREATE PROCEDURE EvictStreamMessages ( IN _ServiceId NVARCHAR(150), IN _ProviderId NVARCHAR(150), IN _QueueId NVARCHAR(150), IN _BatchSize INT, IN _MaxAttempts INT, IN _RemovalTimeout INT ) BEGIN DECLARE _Now DATETIME(6) DEFAULT UTC_TIMESTAMP(); DECLARE _RemoveOn DATETIME(6) DEFAULT DATE_ADD(_Now, INTERVAL _RemovalTimeout SECOND); START TRANSACTION; /* elect the batch of messages to move and lock them in order */ CREATE TEMPORARY TABLE _Batch AS SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND ( -- a message is no longer dequeueable if the last attempt timed out (Dequeued >= _MaxAttempts AND VisibleOn <= _Now) OR -- a message is no longer dequeueable if it has expired regardless (ExpiresOn <= _Now) ) ORDER BY ServiceId, ProviderId, QueueId, MessageId LIMIT _BatchSize FOR UPDATE SKIP LOCKED; /* copy the messages to dead letters */ INSERT INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ) SELECT M.ServiceId, M.ProviderId, M.QueueId, M.MessageId, M.Dequeued, M.VisibleOn, M.ExpiresOn, M.CreatedOn, M.ModifiedOn, _Now, _RemoveOn, M.Payload FROM OrleansStreamMessage AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; /* delete elected messages from the source now */ DELETE M FROM OrleansStreamMessage AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; DROP TEMPORARY TABLE _Batch; COMMIT; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamMessagesKey', 'CALL EvictStreamMessages(@ServiceId, @ProviderId, @QueueId, @BatchSize, @MaxAttempts, @RemovalTimeout)' ; DELIMITER $$ CREATE PROCEDURE EvictStreamDeadLetters ( _ServiceId NVARCHAR(150), _ProviderId NVARCHAR(150), _QueueId NVARCHAR(150), _BatchSize INT ) BEGIN DECLARE _Now DATETIME(6) DEFAULT UTC_TIMESTAMP(); /* elect the batch of messages to remove */ CREATE TEMPORARY TABLE _Batch AS SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamDeadLetter WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND RemoveOn <= _Now ORDER BY ServiceId, ProviderId, QueueId, MessageId LIMIT _BatchSize FOR UPDATE SKIP LOCKED; /* now delete the locked messages */ DELETE M FROM OrleansStreamDeadLetter AS M INNER JOIN _Batch AS B ON M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; DROP TEMPORARY TABLE _Batch; END; DELIMITER $$ INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamDeadLettersKey', 'CALL EvictStreamDeadLetters(@ServiceId, @ProviderId, @QueueId, @BatchSize)' ; DELIMITER $$ ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Orleans.Streaming.AdoNet.csproj ================================================ README.md Microsoft.Orleans.Streaming.AdoNet Microsoft Orleans ADO.NET Streaming Provider Microsoft Orleans streaming provider backed by ADO.NET $(PackageTags) ADO.NET SQL $(DefaultTargetFrameworks) true alpha.1 Orleans.Streaming.AdoNet Orleans.Streaming.AdoNet $(DefineConstants);STREAMING_ADONET ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/PostgreSQL-Streaming.sql ================================================ CREATE SEQUENCE OrleansStreamMessageSequence AS BIGINT START WITH 1 INCREMENT BY 1 NO MAXVALUE NO CYCLE; CREATE TABLE OrleansStreamMessage ( ServiceId VARCHAR(150) NOT NULL, ProviderId VARCHAR(150) NOT NULL, QueueId VARCHAR(150) NOT NULL, MessageId BIGINT NOT NULL, Dequeued INT NOT NULL, VisibleOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, ExpiresOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, CreatedOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, ModifiedOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, Payload BYTEA NOT NULL, CONSTRAINT PK_OrleansStreamMessage PRIMARY KEY ( ServiceId, ProviderId, QueueId, MessageId ) ); CREATE TABLE OrleansStreamDeadLetter ( ServiceId VARCHAR(150) NOT NULL, ProviderId VARCHAR(150) NOT NULL, QueueId VARCHAR(150) NOT NULL, MessageId BIGINT NOT NULL, Dequeued INT NOT NULL, VisibleOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, ExpiresOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, CreatedOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, ModifiedOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, DeadOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, RemoveOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, Payload BYTEA, CONSTRAINT PK_OrleansStreamDeadLetter PRIMARY KEY ( ServiceId, ProviderId, QueueId, MessageId ) ); CREATE TABLE OrleansStreamControl ( ServiceId VARCHAR(150) NOT NULL, ProviderId VARCHAR(150) NOT NULL, QueueId VARCHAR(150) NOT NULL, EvictOn TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL, CONSTRAINT PK_OrleansStreamControl PRIMARY KEY ( ServiceId, ProviderId, QueueId ) ); CREATE OR REPLACE FUNCTION QueueStreamMessage ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _Payload BYTEA, _ExpiryTimeout INT ) RETURNS TABLE ( ServiceId VARCHAR(150), ProviderId VARCHAR(150), QueueId VARCHAR(150), MessageId BIGINT ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _MessageId BIGINT := nextval('OrleansStreamMessageSequence'); _Now TIMESTAMP(6) WITHOUT TIME ZONE := CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; _ExpiresOn TIMESTAMP(6) WITHOUT TIME ZONE := _Now + INTERVAL '1 SECOND' * _ExpiryTimeout; BEGIN RETURN QUERY INSERT INTO OrleansStreamMessage ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, Payload ) VALUES ( _ServiceId, _ProviderId, _QueueId, _MessageId, 0, _Now, _ExpiresOn, _Now, _Now, _Payload ) RETURNING ServiceId, ProviderId, QueueId, MessageId; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'QueueStreamMessageKey', 'SELECT * FROM QueueStreamMessage(@ServiceId, @ProviderId, @QueueId, @Payload, @ExpiryTimeout)' ; CREATE OR REPLACE FUNCTION GetStreamMessages ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _MaxCount INT, _MaxAttempts INT, _VisibilityTimeout INT, _RemovalTimeout INT, _EvictionInterval INT, _EvictionBatchSize INT ) RETURNS TABLE ( ServiceId VARCHAR(150), ProviderId VARCHAR(150), QueueId VARCHAR(150), MessageId BIGINT, Dequeued INT, VisibleOn TIMESTAMP(6) WITHOUT TIME ZONE, ExpiresOn TIMESTAMP(6) WITHOUT TIME ZONE, CreatedOn TIMESTAMP(6) WITHOUT TIME ZONE, ModifiedOn TIMESTAMP(6) WITHOUT TIME ZONE, Payload BYTEA ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Now TIMESTAMP(6) WITHOUT TIME ZONE := CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; _VisibleOn TIMESTAMP(6) WITHOUT TIME ZONE := _Now + INTERVAL '1 SECOND' * _VisibilityTimeout; _EvictOn TIMESTAMP(6) WITHOUT TIME ZONE; _NextEvictOn TIMESTAMP(6) WITHOUT TIME ZONE := _Now + INTERVAL '1 SECOND' * _EvictionInterval; BEGIN /* get the next eviction schedule */ SELECT EvictOn INTO _EvictOn FROM OrleansStreamControl WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId; /* initialize the control row if necessary */ IF _EvictOn IS NULL THEN /* initialize with a past date so eviction runs immediately */ INSERT INTO OrleansStreamControl ( ServiceId, ProviderId, QueueId, EvictOn ) VALUES ( _ServiceId, _ProviderId, _QueueId, _Now - INTERVAL '1 SECOND' ) ON CONFLICT (ServiceId, ProviderId, QueueId) DO NOTHING; /* get the next eviction schedule again */ SELECT EvictOn INTO _EvictOn FROM OrleansStreamControl WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId; END IF; /* evict messages if necessary */ IF _EvictOn <= _Now THEN /* race to set the next schedule */ UPDATE OrleansStreamControl SET EvictOn = _NextEvictOn WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND EvictOn <= _Now; /* if we won the race then we also run the due eviction */ IF (FOUND) THEN CALL EvictStreamMessages(_ServiceId, _ProviderId, _QueueId, _EvictionBatchSize, _MaxAttempts, _RemovalTimeout); CALL EvictStreamDeadLetters(_ServiceId, _ProviderId, _QueueId, _EvictionBatchSize); END IF; END IF; RETURN QUERY WITH Batch AS ( /* elect the next batch of visible messages */ SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND Dequeued < _MaxAttempts AND VisibleOn <= _Now AND ExpiresOn > _Now /* the criteria below helps prevent deadlocks while improving queue-like throughput */ ORDER BY ServiceId, ProviderId, QueueId, MessageId FOR UPDATE LIMIT _MaxCount ) UPDATE OrleansStreamMessage AS M SET Dequeued = Dequeued + 1, VisibleOn = _VisibleOn, ModifiedOn = _Now FROM Batch AS B WHERE M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId RETURNING M.ServiceId, M.ProviderId, M.QueueId, M.MessageId, M.Dequeued, M.VisibleOn, M.ExpiresOn, M.CreatedOn, M.ModifiedOn, M.Payload; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'GetStreamMessagesKey', 'SELECT * FROM GetStreamMessages(@ServiceId, @ProviderId, @QueueId, @MaxCount, @MaxAttempts, @VisibilityTimeout, @RemovalTimeout, @EvictionInterval, @EvictionBatchSize)' ; CREATE OR REPLACE FUNCTION ConfirmStreamMessages ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _Items TEXT ) RETURNS TABLE ( ServiceId VARCHAR(150), ProviderId VARCHAR(150), QueueId VARCHAR(150), MessageId BIGINT ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Count INT; BEGIN CREATE TEMP TABLE _ItemsTable ( MessageId BIGINT PRIMARY KEY NOT NULL, Dequeued INT NOT NULL ) ON COMMIT DROP; INSERT INTO _ItemsTable ( MessageId, Dequeued ) SELECT CAST(split_part(Value, ':', 1) AS BIGINT) AS MessageId, CAST(split_part(Value, ':', 2) AS INT) AS Dequeued FROM UNNEST(string_to_array(_Items, '|')) AS Value; RETURN QUERY WITH Batch AS ( SELECT M.* FROM OrleansStreamMessage AS M INNER JOIN _ItemsTable AS I ON I.MessageId = M.MessageId AND I.Dequeued = M.Dequeued WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId /* the criteria below helps prevent deadlocks */ ORDER BY ServiceId, ProviderId, QueueId, MessageId FOR UPDATE ) DELETE FROM OrleansStreamMessage AS M USING Batch AS B WHERE M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId RETURNING M.ServiceId, M.ProviderId, M.QueueId, M.MessageId; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'ConfirmStreamMessagesKey', 'SELECT * FROM ConfirmStreamMessages(@ServiceId, @ProviderId, @QueueId, @Items)' ; CREATE OR REPLACE PROCEDURE FailStreamMessage ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _MessageId BIGINT, _MaxAttempts INT, _RemovalTimeout INT ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Now TIMESTAMP(6) WITHOUT TIME ZONE := CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; _RemoveOn TIMESTAMP(6) WITHOUT TIME ZONE := _Now + INTERVAL '1 SECOND' * _RemovalTimeout; BEGIN /* if the message can still be dequeued then attempt to mark it visible again */ UPDATE OrleansStreamMessage SET VisibleOn = _Now, ModifiedOn = _Now WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND MessageId = _MessageId AND Dequeued < _MaxAttempts; IF FOUND THEN RETURN; END IF; /* otherwise attempt to move the message to dead letters */ WITH Deleted AS ( DELETE FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND MessageId = _MessageId RETURNING ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, Payload ) INSERT INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ) SELECT ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, _Now AS DeadOn, _RemoveOn AS RemoveOn, Payload FROM Deleted; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'FailStreamMessageKey', 'CALL FailStreamMessage(@ServiceId, @ProviderId, @QueueId, @MessageId, @MaxAttempts, @RemovalTimeout)' ; CREATE OR REPLACE PROCEDURE EvictStreamMessages ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _BatchSize INT, _MaxAttempts INT, _RemovalTimeout INT ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Now TIMESTAMP(6) WITHOUT TIME ZONE := CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; _RemoveOn TIMESTAMP(6) WITHOUT TIME ZONE := _Now + INTERVAL '1 second' * _RemovalTimeout; BEGIN /* elect the next batch of messages to evict */ WITH Batch AS ( SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamMessage WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId -- the message was given the opportunity to complete AND VisibleOn <= _Now AND ( -- the message was dequeued too many times Dequeued >= _MaxAttempts OR -- the message expired ExpiresOn <= _Now ) /* the criteria below helps prevent deadlocks while improving queue-like throughput */ ORDER BY ServiceId, ProviderId, QueueId, MessageId FOR UPDATE LIMIT _BatchSize ), /* delete the messages locked in the batch */ Deleted AS ( DELETE FROM OrleansStreamMessage AS M USING Batch AS B WHERE M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId RETURNING M.ServiceId, M.ProviderId, M.QueueId, M.MessageId, M.Dequeued, M.VisibleOn, M.ExpiresOn, M.CreatedOn, M.ModifiedOn, M.Payload ) /* copy the deleted messages to the dead-letter table */ INSERT INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ) SELECT ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, _Now, _RemoveOn, Payload FROM Deleted AS D; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamMessagesKey', 'CALL EvictStreamMessages(@ServiceId, @ProviderId, @QueueId, @BatchSize, @MaxAttempts, @RemovalTimeout)' ; CREATE OR REPLACE PROCEDURE EvictStreamDeadLetters ( _ServiceId VARCHAR(150), _ProviderId VARCHAR(150), _QueueId VARCHAR(150), _BatchSize INT ) LANGUAGE plpgsql AS $$ #VARIABLE_CONFLICT USE_COLUMN DECLARE _Now TIMESTAMP(6) WITHOUT TIME ZONE := CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; BEGIN /* elect the next batch of dead letters to evict */ WITH Batch AS ( SELECT ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamDeadLetter WHERE ServiceId = _ServiceId AND ProviderId = _ProviderId AND QueueId = _QueueId AND RemoveOn <= _Now /* the criteria below helps prevent deadlocks while improving queue-like throughput */ ORDER BY ServiceId, ProviderId, QueueId, MessageId FOR UPDATE LIMIT _BatchSize ) DELETE FROM OrleansStreamDeadLetter AS M USING Batch AS B WHERE M.ServiceId = B.ServiceId AND M.ProviderId = B.ProviderId AND M.QueueId = B.QueueId AND M.MessageId = B.MessageId; END; $$; INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamDeadLettersKey', 'CALL EvictStreamDeadLetters(@ServiceId, @ProviderId, @QueueId, @BatchSize)' ; ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/Properties/Usings.cs ================================================ global using System; global using System.Collections.Concurrent; global using System.Collections.Generic; global using System.Diagnostics.CodeAnalysis; global using System.Linq; global using System.Threading.Tasks; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Orleans.Configuration; global using Orleans.Configuration.Overrides; global using Orleans.Hosting; global using Orleans.Providers.Streams.Common; global using Orleans.Runtime; global using Orleans.Serialization; global using Orleans.Streaming.AdoNet.Storage; global using Orleans.Streams; ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/README.md ================================================ # Microsoft Orleans Streaming for ADO.NET ## Introduction Microsoft Orleans Streaming for ADO.NET provides a stream provider implementation for Orleans using ADO.NET-compatible databases (SQL Server, MySQL, PostgreSQL, etc.). This allows for publishing and subscribing to streams of events with relational databases as the underlying infrastructure. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Streaming.AdoNet ``` You will also need to install the appropriate ADO.NET provider for your database: ```shell # For SQL Server dotnet add package Microsoft.Data.SqlClient # For MySQL dotnet add package MySql.Data # For PostgreSQL dotnet add package Npgsql ``` ## Example - Configuring ADO.NET Streaming ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Streams; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure ADO.NET as a stream provider .AddAdoNetStreams( name: "AdoNetStreamProvider", configureOptions: options => { options.Invariant = "Microsoft.Data.SqlClient"; // For SQL Server options.ConnectionString = "Server=localhost;Database=OrleansStreaming;User ID=orleans;******;"; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using ADO.NET Streams in a Grain ```csharp // Producer grain public class ProducerGrain : Grain, IProducerGrain { private IAsyncStream _stream; public override Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("AdoNetStreamProvider"); _stream = streamProvider.GetStream(Guid.NewGuid(), "MyStreamNamespace"); return base.OnActivateAsync(cancellationToken); } public async Task SendMessage(string message) { // Send a message to the stream await _stream.OnNextAsync(message); } } // Consumer grain public class ConsumerGrain : Grain, IConsumerGrain, IAsyncObserver { private StreamSubscriptionHandle _subscription; public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("AdoNetStreamProvider"); var stream = streamProvider.GetStream(this.GetPrimaryKey(), "MyStreamNamespace"); // Subscribe to the stream _subscription = await stream.SubscribeAsync(this); await base.OnActivateAsync(cancellationToken); } public Task OnNextAsync(string item, StreamSequenceToken token = null) { Console.WriteLine($"Received message: {item}"); return Task.CompletedTask; } public Task OnCompletedAsync() { Console.WriteLine("Stream completed"); return Task.CompletedTask; } public Task OnErrorAsync(Exception ex) { Console.WriteLine($"Stream error: {ex.Message}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Streams](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/index) - [Stream Providers](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/stream-providers) - [ADO.NET Database Setup](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/adonet-configuration) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/AdoNet/Orleans.Streaming.AdoNet/SQLServer-Streaming.sql ================================================ /* Orleans Stream Message Sequence. This sequence reduces contention on generation of [MessageId] values vs an identity column. The CACHE parameter can be increased to further reduce contention. */ CREATE SEQUENCE OrleansStreamMessageSequence AS BIGINT START WITH 1 INCREMENT BY 1 NO MAXVALUE NO CYCLE CACHE 1000; GO /* Orleans Streaming Message Queue. This table stores queued messages awaiting processing by Orleans. The demands for this table are as follows: 1. The table will see inserts only at the tail, as new rows are added. 2. The table will be polled with high frequency to reserve the first batch of rows that matches a well-known criteria ("visible" and "not expired" and "under max attempts"). 3. The table will see rows being removed at the head as messages are confirmed. 4. The table will see rows being removed at the head as expired messages are moved to dead letters. 5. Due to the above queries touching more than one row at a time, there is a possibility of deadlocks. 6. A few faulted or poisoned messages can linger for some time at the head before being moved to dead letters. 7. The table will occasionaly become empty or at least sparse as the cluster succeeds to catch up to all messages. While [1-6] all cause page fragmentation over time, [7] self resolves this degradation by allowing sql server to eventually remove all pages. Therefore the design attempts to optimise for [2] while assuming the resulting degradation eventually resolves itself. The design also attempts to minimize the possibility of deadlocks at the expense of higher locking contention. This happens by forcing all queries to touch data in the exact same order of the clustered index. This induces ordered resource lock acquisition while avoiding the cost of ordering itself. */ CREATE TABLE OrleansStreamMessage ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider*/ QueueId NVARCHAR(150) NOT NULL, /* The unique ascending number of the queued message */ MessageId BIGINT NOT NULL, /* The number of times the event was dequeued */ Dequeued INT NOT NULL, /* The UTC time at which the event will become visible */ VisibleOn DATETIME2(7) NOT NULL, /* The UTC time at which the event will expire */ ExpiresOn DATETIME2(7) NOT NULL, /* The UTC time at which the event was created - troubleshooting only */ CreatedOn DATETIME2(7) NOT NULL, /* The UTC time at which the event was updated - troubleshooting only */ ModifiedOn DATETIME2(7) NOT NULL, /* The arbitrarily large payload of the event */ Payload VARBINARY(MAX) NOT NULL, /* This Clustered PK supports the various ordered scanning queries. */ CONSTRAINT PK_OrleansStreamMessage PRIMARY KEY CLUSTERED ( ServiceId ASC, ProviderId ASC, QueueId ASC, MessageId ASC ) ); GO /* Orleans Streaming Dead Letters. This table holds events that could not be processed within the allowed number of attempts or that have expired. */ CREATE TABLE OrleansStreamDeadLetter ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider*/ QueueId NVARCHAR(150) NOT NULL, /* The unique ascending number of the queued message */ MessageId BIGINT NOT NULL, /* The number of times the event was dequeued */ Dequeued INT NOT NULL, /* The UTC time at which the event will become visible */ VisibleOn DATETIME2(7) NOT NULL, /* The UTC time at which the event will expire */ ExpiresOn DATETIME2(7) NOT NULL, /* The UTC time at which the event was created - troubleshooting only */ CreatedOn DATETIME2(7) NOT NULL, /* The UTC time at which the event was updated - troubleshooting only */ ModifiedOn DATETIME2(7) NOT NULL, /* The UTC time at which the event was given up on - troubleshooting only */ DeadOn DATETIME2(7) NOT NULL, /* The UTC time at which the event is scheduled to be removed from dead letters */ RemoveOn DATETIME2(7) NOT NULL, /* The arbitrarily large payload of the event */ Payload VARBINARY(MAX) NULL, /* This Clustered PK supports the various ordered scanning queries. */ /* Its main purpose is to help partition the update row locks as to minimize dequeing contention. */ CONSTRAINT PK_OrleansStreamDeadLetter PRIMARY KEY CLUSTERED ( ServiceId ASC, ProviderId ASC, QueueId ASC, MessageId ASC ) ); GO /* Orleans Streaming Control Table. This table holds schedule variables to help providers self manage their own work. */ CREATE TABLE OrleansStreamControl ( /* Identifies the application */ ServiceId NVARCHAR(150) NOT NULL, /* Identifies the provider within the application */ ProviderId NVARCHAR(150) NOT NULL, /* Identifies the individual queue shard as configured in the provider */ QueueId NVARCHAR(150) NOT NULL, /* The next due schedule for messages to be evicted */ EvictOn DATETIME2(7) NOT NULL, /* Each row represents a flat configuration object for an individual queue */ CONSTRAINT PK_OrleansStreamControl PRIMARY KEY CLUSTERED ( ServiceId ASC, ProviderId ASC, QueueId ASC ) ); GO /* Queues a message to the Orleans Streaming Message Queue */ CREATE PROCEDURE QueueStreamMessage @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @Payload VARBINARY(MAX), @ExpiryTimeout INT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @MessageId BIGINT = NEXT VALUE FOR OrleansStreamMessageSequence; DECLARE @Now DATETIME2(7) = SYSUTCDATETIME(); DECLARE @ExpiresOn DATETIME2(7) = DATEADD(SECOND, @ExpiryTimeout, @Now); INSERT INTO OrleansStreamMessage ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, Payload ) OUTPUT Inserted.ServiceId, Inserted.ProviderId, Inserted.QueueId, Inserted.MessageId VALUES ( @ServiceId, @ProviderId, @QueueId, @MessageId, 0, @Now, @ExpiresOn, @Now, @Now, @Payload ); END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'QueueStreamMessageKey', 'EXECUTE QueueStreamMessage @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @Payload = @Payload, @ExpiryTimeout = @ExpiryTimeout' GO /* Gets message batches from the Orleans Streaming Message Queue */ /* Also opportunistically performs eviction activities when they are due */ CREATE PROCEDURE GetStreamMessages @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @MaxCount INT, @MaxAttempts INT, @VisibilityTimeout INT, @RemovalTimeout INT, @EvictionInterval INT, @EvictionBatchSize INT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Now DATETIME2(7) = SYSUTCDATETIME(); DECLARE @VisibleOn DATETIME2(7) = DATEADD(SECOND, @VisibilityTimeout, @Now); /* lightweight check to see if an eviction activity is due */ DECLARE @EvictOn DATETIME2(7) = ( SELECT EvictOn FROM OrleansStreamControl WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId ); /* escalate to a eviction attempt only if an activity is due */ IF @EvictOn IS NULL OR @EvictOn < @Now BEGIN /* attempt to win a race to update the schedule */ /* this will also initialize the table if necessary */ WITH Candidate AS ( SELECT ServiceId = @ServiceId, ProviderId = @ProviderId, QueueId = @QueueId, Now = @Now, EvictOn = DATEADD(SECOND, @EvictionInterval, @Now) ) MERGE OrleansStreamControl WITH (UPDLOCK, HOLDLOCK) AS T USING Candidate AS S ON T.ServiceId = S.ServiceId AND T.ProviderId = S.ProviderId AND T.QueueId = S.QueueId WHEN MATCHED AND T.EvictOn < S.Now THEN UPDATE SET T.EvictOn = S.EvictOn WHEN NOT MATCHED BY TARGET THEN INSERT ( ServiceId, ProviderId, QueueId, EvictOn ) VALUES ( ServiceId, ProviderId, QueueId, EvictOn ); /* if the above statement won the race then we also get to run the eviction */ /* other concurrent queries will continue running as normal until the next due time */ IF (@@ROWCOUNT > 0) BEGIN /* evict messages */ EXECUTE EvictStreamMessages @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @MaxAttempts = @MaxAttempts, @RemovalTimeout = @RemovalTimeout, @BatchSize = @EvictionBatchSize /* evict dead letters */ EXECUTE EvictStreamDeadLetters @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @BatchSize = @EvictionBatchSize; END; END; /* update messages in the exact same order as the clustered index to avoid deadlocks with other queries */ WITH Batch AS ( SELECT TOP (@MaxCount) ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, Payload FROM OrleansStreamMessage WITH (UPDLOCK) WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId AND Dequeued < @MaxAttempts AND VisibleOn <= @Now AND ExpiresOn > @Now ORDER BY ServiceId, ProviderId, QueueId, MessageId ) UPDATE Batch SET Dequeued += 1, VisibleOn = @VisibleOn, ModifiedOn = @Now OUTPUT Inserted.ServiceId, Inserted.ProviderId, Inserted.QueueId, Inserted.MessageId, Inserted.Dequeued, Inserted.VisibleOn, Inserted.ExpiresOn, Inserted.CreatedOn, Inserted.ModifiedOn, Inserted.Payload FROM Batch; END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'GetStreamMessagesKey', 'EXECUTE GetStreamMessages @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @MaxCount = @MaxCount, @MaxAttempts = @MaxAttempts, @VisibilityTimeout = @VisibilityTimeout, @RemovalTimeout = @RemovalTimeout, @EvictionInterval = @EvictionInterval, @EvictionBatchSize = @EvictionBatchSize' GO /* Confirms delivery of a stream message. */ CREATE PROCEDURE ConfirmStreamMessages @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @Items NVARCHAR(MAX) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; /* parse the message identifiers to be deleted */ DECLARE @ItemsTable TABLE ( MessageId BIGINT PRIMARY KEY NOT NULL, Dequeued INT NOT NULL ); WITH Items AS ( SELECT Value FROM STRING_SPLIT(@Items, '|') ) INSERT INTO @ItemsTable ( MessageId, Dequeued ) SELECT CAST(SUBSTRING(Value, 1, CHARINDEX(':', Value, 1) - 1) AS BIGINT) AS MessageId, CAST(SUBSTRING(Value, CHARINDEX(':', Value, 1) + 1, LEN(Value)) AS INT) AS Dequeued FROM Items; /* count the number of messages to delete so we can use order by in the next query */ DECLARE @Count INT = (SELECT COUNT(*) FROM @ItemsTable); /* delete messages in the exact same order as the clustered index to avoid deadlocks with other queries */ WITH Batch AS ( SELECT TOP (@Count) * FROM OrleansStreamMessage AS M WITH (UPDLOCK, HOLDLOCK) WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId AND EXISTS ( SELECT * FROM @ItemsTable AS I WHERE I.MessageId = M.MessageId AND I.Dequeued = M.Dequeued ) ORDER BY ServiceId, ProviderId, QueueId, MessageId ) DELETE FROM Batch OUTPUT Deleted.ServiceId, Deleted.ProviderId, Deleted.QueueId, Deleted.MessageId; END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'ConfirmStreamMessagesKey', 'EXECUTE ConfirmStreamMessages @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @Items = @Items' GO /* Applies delivery failure rules to the specified message. */ /* If the message has been dequeued too many times, we move it to the dead letter table. */ /* If the message has expired, we move to the dead letter table. */ /* If the message is still eligible for delivery, it is made visible again. */ CREATE PROCEDURE FailStreamMessage @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @MessageId BIGINT, @MaxAttempts INT, @RemovalTimeout INT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Now DATETIME2(7) = SYSUTCDATETIME(); DECLARE @RemoveOn DATETIME2(7) = DATEADD(SECOND, @RemovalTimeout, @Now); /* if the message can still be dequeued then attempt to mark it visible again */ UPDATE OrleansStreamMessage SET VisibleOn = @Now, ModifiedOn = @Now WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId AND MessageId = @MessageId AND Dequeued < @MaxAttempts; IF @@ROWCOUNT > 0 RETURN; /* otherwise attempt to move the message to dead letters */ DELETE FROM OrleansStreamMessage OUTPUT Deleted.ServiceId, Deleted.ProviderId, Deleted.QueueId, Deleted.MessageId, Deleted.Dequeued, Deleted.VisibleOn, Deleted.ExpiresOn, Deleted.CreatedOn, Deleted.ModifiedOn, @Now AS DeadOn, @RemoveOn AS RemoveOn, Deleted.Payload INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ) WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId AND MessageId = @MessageId; END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'FailStreamMessageKey', 'EXECUTE FailStreamMessage @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @MessageId = @MessageId, @MaxAttempts = @MaxAttempts, @RemovalTimeout = @RemovalTimeout' GO /* Moves non-delivered messages from the message table to the dead letter table for human troubleshooting. */ CREATE PROCEDURE EvictStreamMessages @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @BatchSize INT, @MaxAttempts INT, @RemovalTimeout INT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Now DATETIME2(7) = SYSUTCDATETIME(); DECLARE @RemoveOn DATETIME2(7) = DATEADD(SECOND, @RemovalTimeout, @Now); /* delete messages in the exact same order as the clustered index to avoid deadlocks with other queries */ WITH Batch AS ( SELECT TOP (@BatchSize) ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn = @Now, RemoveOn = @RemoveOn, Payload FROM OrleansStreamMessage WITH (UPDLOCK) WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId -- the message was given the opportunity to complete AND VisibleOn <= @Now AND ( -- the message was dequeued too many times Dequeued >= @MaxAttempts OR -- the message expired ExpiresOn <= @Now ) ORDER BY ServiceId, ProviderId, QueueId, MessageId ) DELETE FROM Batch OUTPUT Deleted.ServiceId, Deleted.ProviderId, Deleted.QueueId, Deleted.MessageId, Deleted.Dequeued, Deleted.VisibleOn, Deleted.ExpiresOn, Deleted.CreatedOn, Deleted.ModifiedOn, Deleted.DeadOn, Deleted.RemoveOn, Deleted.Payload INTO OrleansStreamDeadLetter ( ServiceId, ProviderId, QueueId, MessageId, Dequeued, VisibleOn, ExpiresOn, CreatedOn, ModifiedOn, DeadOn, RemoveOn, Payload ); END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamMessagesKey', 'EXECUTE EvictStreamMessages @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @BatchSize = @BatchSize, @MaxAttempts = @MaxAttempts, @RemovalTimeout = @RemovalTimeout' GO /* Removes messages from the dead letters table. */ CREATE PROCEDURE EvictStreamDeadLetters @ServiceId NVARCHAR(150), @ProviderId NVARCHAR(150), @QueueId NVARCHAR(150), @BatchSize INT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Now DATETIME2(7) = SYSUTCDATETIME(); /* delete messages in the exact same order as the clustered index to avoid deadlocks with other queries */ WITH Batch AS ( SELECT TOP (@BatchSize) ServiceId, ProviderId, QueueId, MessageId FROM OrleansStreamDeadLetter WITH (UPDLOCK) WHERE ServiceId = @ServiceId AND ProviderId = @ProviderId AND QueueId = @QueueId AND RemoveOn <= @Now ORDER BY ServiceId, ProviderId, QueueId, MessageId ) DELETE FROM Batch; END GO INSERT INTO OrleansQuery ( QueryKey, QueryText ) SELECT 'EvictStreamDeadLettersKey', 'EXECUTE EvictStreamDeadLetters @ServiceId = @ServiceId, @ProviderId = @ProviderId, @QueueId = @QueueId, @BatchSize = @BatchSize' GO ================================================ FILE: src/AdoNet/Shared/MySQL-Main.sql ================================================ /* Implementation notes: 1) The general idea is that data is read and written through Orleans specific queries. Orleans operates on column names and types when reading and on parameter names and types when writing. 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract is maintained. 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting by virtue of uniform naming across concrete implementations. 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation is not important as long as it represents a unique version. In this implementation we use integers for versioning 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. Orleans handles exception as a failure and will retry. 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: https://learn.microsoft.com/dotnet/orleans/implementation/cluster-management https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs */ -- This table defines Orleans operational queries. Orleans uses these to manage its operations, -- these are the only queries Orleans issues to the database. -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. CREATE TABLE OrleansQuery ( QueryKey VARCHAR(64) NOT NULL, QueryText VARCHAR(8000) NOT NULL, CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) ); ================================================ FILE: src/AdoNet/Shared/Oracle-Main.sql ================================================ /* Implementation notes: 1) The general idea is that data is read and written through Orleans specific queries. Orleans operates on column names and types when reading and on parameter names and types when writing. 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract is maintained. 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting by virtue of uniform naming across concrete implementations. 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation is not important as long as it represents a unique version. In this implementation we use integers for versioning 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. Orleans handles exception as a failure and will retry. 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: https://learn.microsoft.com/dotnet/orleans/implementation/cluster-management https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs */ -- This table defines Orleans operational queries. Orleans uses these to manage its operations, -- these are the only queries Orleans issues to the database. -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. CREATE TABLE "ORLEANSQUERY" ( "QUERYKEY" VARCHAR2(64 BYTE) NOT NULL ENABLE, "QUERYTEXT" VARCHAR2(4000 BYTE), CONSTRAINT "ORLEANSQUERY_PK" PRIMARY KEY ("QUERYKEY") ); / COMMIT; -- Oracle specific implementation note: -- Some OrleansQueries are implemented as functions and differ from the scripts of other databases. -- The main reason for this is the fact, that oracle doesn't support returning variables from queries -- directly. So in the case that a variable value is needed as output of a OrleansQuery (e.g. version) -- a function is used. ================================================ FILE: src/AdoNet/Shared/PostgreSQL-Main.sql ================================================ -- requires Postgres 9.5 (or perhaps higher) /* Implementation notes: 1) The general idea is that data is read and written through Orleans specific queries. Orleans operates on column names and types when reading and on parameter names and types when writing. 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract is maintained. 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting by virtue of uniform naming across concrete implementations. 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation is not important as long as it represents a unique version. In this implementation we use integers for versioning 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. Orleans handles exception as a failure and will retry. 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: https://learn.microsoft.com/dotnet/orleans/implementation/cluster-management https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs */ -- This table defines Orleans operational queries. Orleans uses these to manage its operations, -- these are the only queries Orleans issues to the database. -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. CREATE TABLE OrleansQuery ( QueryKey varchar(64) NOT NULL, QueryText varchar(8000) NOT NULL, CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) ); ================================================ FILE: src/AdoNet/Shared/SQLServer-Main.sql ================================================ /* Implementation notes: 1) The general idea is that data is read and written through Orleans specific queries. Orleans operates on column names and types when reading and on parameter names and types when writing. 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract is maintained. 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting by virtue of uniform naming across concrete implementations. 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation is not important as long as it represents a unique version. In this implementation we use integers for versioning 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. Orleans handles exception as a failure and will retry. 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: https://learn.microsoft.com/dotnet/orleans/implementation/cluster-management https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs */ -- These settings improves throughput of the database by reducing locking by better separating readers from writers. -- SQL Server 2012 and newer can refer to itself as CURRENT. Older ones need a workaround. DECLARE @current NVARCHAR(256); DECLARE @snapshotSettings NVARCHAR(612); SELECT @current = N'[' + (SELECT DB_NAME()) + N']'; SET @snapshotSettings = N'ALTER DATABASE ' + @current + N' SET READ_COMMITTED_SNAPSHOT ON; ALTER DATABASE ' + @current + N' SET ALLOW_SNAPSHOT_ISOLATION ON;'; EXECUTE sp_executesql @snapshotSettings; -- This table defines Orleans operational queries. Orleans uses these to manage its operations, -- these are the only queries Orleans issues to the database. -- These can be redefined (e.g. to provide non-destructive updates) provided the stated interface principles hold. IF OBJECT_ID(N'[OrleansQuery]', 'U') IS NULL CREATE TABLE OrleansQuery ( QueryKey VARCHAR(64) NOT NULL, QueryText VARCHAR(8000) NOT NULL, CONSTRAINT OrleansQuery_Key PRIMARY KEY(QueryKey) ); ================================================ FILE: src/AdoNet/Shared/Sqlite-Main.sql ================================================ /* Implementation notes: 1) The general idea is that data is read and written through Orleans specific queries. Orleans operates on column names and types when reading and on parameter names and types when writing. 2) The implementations *must* preserve input and output names and types. Orleans uses these parameters to reads query results by name and type. Vendor and deployment specific tuning is allowed and contributions are encouraged as long as the interface contract is maintained. 3) The implementation across vendor specific scripts *should* preserve the constraint names. This simplifies troubleshooting by virtue of uniform naming across concrete implementations. 5) ETag for Orleans is an opaque column that represents a unique version. The type of its actual implementation is not important as long as it represents a unique version. In this implementation we use integers for versioning 6) For the sake of being explicit and removing ambiguity, Orleans expects some queries to return either TRUE as >0 value or FALSE as =0 value. That is, affected rows or such does not matter. If an error is raised or an exception is thrown the query *must* ensure the entire transaction is rolled back and may either return FALSE or propagate the exception. Orleans handles exception as a failure and will retry. 7) The implementation follows the Extended Orleans membership protocol. For more information, see at: https://learn.microsoft.com/dotnet/orleans/implementation/cluster-management https://github.com/dotnet/orleans/blob/main/src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs */ -- These settings improves throughput of the database by reducing locking by better separating readers from writers. -- SQL Server 2012 and newer can refer to itself as CURRENT. Older ones need a workaround. CREATE TABLE OrleansQuery ( QueryKey TEXT PRIMARY KEY, QueryText TEXT NOT NULL ); ================================================ FILE: src/AdoNet/Shared/Storage/AdoNetFormatProvider.cs ================================================ using System; using System.Globalization; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Formats .NET types appropriately for database consumption in non-parameterized queries. /// internal class AdoNetFormatProvider: IFormatProvider { private readonly AdoNetFormatter formatter = new AdoNetFormatter(); /// /// Returns an instance of the formatter /// /// Requested format type /// public object GetFormat(Type formatType) { return formatType == typeof(ICustomFormatter) ? formatter : null; } private class AdoNetFormatter: ICustomFormatter { public string Format(string format, object arg, IFormatProvider formatProvider) { //This null check applies also to Nullable when T does not have value defined. if(arg == null) { return "NULL"; } if(arg is string) { return "N'" + ((string)arg).Replace("'", "''", StringComparison.Ordinal) + "'"; } if(arg is DateTime) { return "'" + ((DateTime)arg).ToString("O") + "'"; } if(arg is DateTimeOffset) { return "'" + ((DateTimeOffset)arg).ToString("O") + "'"; } if(arg is IFormattable) { return ((IFormattable)arg).ToString(format, CultureInfo.InvariantCulture); } return arg.ToString(); } } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/AdoNetInvariants.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// A holder for well known, vendor specific connector class invariant names. /// internal static class AdoNetInvariants { /// /// A list of the supported invariants. /// /// The invariant names here do not match the namespaces as is often the convention. /// Current exception is MySQL Connector library that uses the same invariant as MySQL compared /// to the official Oracle distribution. public static ICollection Invariants { get; } = new Collection(new List(new[] { InvariantNameMySql, InvariantNameOracleDatabase, InvariantNamePostgreSql, InvariantNameSqlLite, InvariantNameSqlServer, InvariantNameMySqlConnector })); /// /// Microsoft SQL Server invariant name string. /// public const string InvariantNameSqlServer = "Microsoft.Data.SqlClient"; /// /// Oracle Database server invariant name string. /// public const string InvariantNameOracleDatabase = "Oracle.DataAccess.Client"; /// /// SQLite invariant name string. /// public const string InvariantNameSqlLite = "System.Data.SQLite"; /// /// MySql invariant name string. /// public const string InvariantNameMySql = "MySql.Data.MySqlClient"; /// /// PostgreSQL invariant name string. /// public const string InvariantNamePostgreSql = "Npgsql"; /// /// Dotnet core Microsoft SQL Server invariant name string. /// [Obsolete("Use InvariantNameSqlServer instead.")] public const string InvariantNameSqlServerDotnetCore = InvariantNameSqlServer; /// /// An open source implementation of the MySQL connector library. /// public const string InvariantNameMySqlConnector = "MySql.Data.MySqlConnector"; } } ================================================ FILE: src/AdoNet/Shared/Storage/DbConnectionFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Common; using System.Linq; using System.Reflection; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// This class caches the references to all loaded factories internal static class DbConnectionFactory { private static readonly ConcurrentDictionary factoryCache = new ConcurrentDictionary(); private static readonly Dictionary>> providerFactoryTypeMap = new Dictionary>> { { AdoNetInvariants.InvariantNameSqlServer, new List>{ new Tuple("Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient.SqlClientFactory") } }, { AdoNetInvariants.InvariantNameMySql, new List>{ new Tuple("MySql.Data", "MySql.Data.MySqlClient.MySqlClientFactory") } }, { AdoNetInvariants.InvariantNameOracleDatabase, new List>{ new Tuple("Oracle.ManagedDataAccess", "Oracle.ManagedDataAccess.Client.OracleClientFactory") } }, { AdoNetInvariants.InvariantNamePostgreSql, new List>{ new Tuple("Npgsql", "Npgsql.NpgsqlFactory") } }, { AdoNetInvariants.InvariantNameSqlLite, new List>{ new Tuple("Microsoft.Data.Sqlite", "Microsoft.Data.Sqlite.SqliteFactory") } }, { AdoNetInvariants.InvariantNameMySqlConnector, new List>{ new Tuple("MySqlConnector", "MySqlConnector.MySqlConnectorFactory") , new Tuple("MySqlConnector", "MySql.Data.MySqlClient.MySqlClientFactory") } }, }; private static CachedFactory GetFactory(string invariantName) { if (string.IsNullOrWhiteSpace(invariantName)) { throw new ArgumentNullException(nameof(invariantName)); } List> providerFactoryDefinitions; if (!providerFactoryTypeMap.TryGetValue(invariantName, out providerFactoryDefinitions) || providerFactoryDefinitions.Count == 0) throw new InvalidOperationException($"Database provider factory with '{invariantName}' invariant name not supported."); List exceptions = null; foreach (var providerFactoryDefinition in providerFactoryDefinitions) { Assembly asm = null; try { var asmName = new AssemblyName(providerFactoryDefinition.Item1); asm = Assembly.Load(asmName); } catch (Exception exc) { AddException(new InvalidOperationException($"Unable to find and/or load a candidate assembly '{providerFactoryDefinition.Item1}' for '{invariantName}' invariant name.", exc)); continue; } if (asm == null) { AddException(new InvalidOperationException($"Can't find database provider factory with '{invariantName}' invariant name. Please make sure that your ADO.Net provider package library is deployed with your application.")); continue; } var providerFactoryType = asm.GetType(providerFactoryDefinition.Item2); if (providerFactoryType == null) { AddException(new InvalidOperationException($"Unable to load type '{providerFactoryDefinition.Item2}' for '{invariantName}' invariant name.")); continue; } var prop = providerFactoryType.GetFields().SingleOrDefault(p => string.Equals(p.Name, "Instance", StringComparison.OrdinalIgnoreCase) && p.IsStatic); if (prop == null) { AddException(new InvalidOperationException($"Invalid provider type '{providerFactoryDefinition.Item2}' for '{invariantName}' invariant name.")); continue; } var factory = (DbProviderFactory)prop.GetValue(null); return new CachedFactory(factory, providerFactoryType.Name, "", providerFactoryType.AssemblyQualifiedName); } throw new AggregateException(exceptions); void AddException(Exception ex) { if (exceptions == null) { exceptions = new List(); } exceptions.Add(ex); } } public static DbConnection CreateConnection(string invariantName, string connectionString) { if (string.IsNullOrWhiteSpace(invariantName)) { throw new ArgumentNullException(nameof(invariantName)); } if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentNullException(nameof(connectionString)); } var factory = factoryCache.GetOrAdd(invariantName, GetFactory).Factory; var connection = factory.CreateConnection(); if (connection == null) { throw new InvalidOperationException($"Database provider factory: '{invariantName}' did not return a connection object."); } connection.ConnectionString = connectionString; return connection; } private class CachedFactory { public CachedFactory(DbProviderFactory factory, string factoryName, string factoryDescription, string factoryAssemblyQualifiedNameKey) { Factory = factory; FactoryName = factoryName; FactoryDescription = factoryDescription; FactoryAssemblyQualifiedNameKey = factoryAssemblyQualifiedNameKey; } /// /// The factory to provide vendor specific functionality. /// /// For more about ConnectionPool /// and issues with using this factory. Take these notes into account when considering robustness of Orleans! public readonly DbProviderFactory Factory; /// /// The name of the loaded factory, set by a database connector vendor. /// public readonly string FactoryName; /// /// The description of the loaded factory, set by a database connector vendor. /// public readonly string FactoryDescription; /// /// The description of the loaded factory, set by a database connector vendor. /// public readonly string FactoryAssemblyQualifiedNameKey; } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/DbConstantsStore.cs ================================================ using System.Collections.Generic; #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { internal static class DbConstantsStore { private static readonly Dictionary invariantNameToConsts = new Dictionary { { AdoNetInvariants.InvariantNameSqlServer, new DbConstants(startEscapeIndicator: '[', endEscapeIndicator: ']', unionAllSelectTemplate: " UNION ALL SELECT ", isSynchronousAdoNetImplementation: false, supportsStreamNatively: true, supportsCommandCancellation: true, commandInterceptor: NoOpCommandInterceptor.Instance) }, {AdoNetInvariants.InvariantNameMySql, new DbConstants( startEscapeIndicator: '`', endEscapeIndicator: '`', unionAllSelectTemplate: " UNION ALL SELECT ", isSynchronousAdoNetImplementation: true, supportsStreamNatively: false, supportsCommandCancellation: false, commandInterceptor: NoOpCommandInterceptor.Instance) }, {AdoNetInvariants.InvariantNamePostgreSql, new DbConstants( startEscapeIndicator: '"', endEscapeIndicator: '"', unionAllSelectTemplate: " UNION ALL SELECT ", isSynchronousAdoNetImplementation: false, supportsStreamNatively: true, supportsCommandCancellation: true, // See https://dev.mysql.com/doc/connector-net/en/connector-net-ref-mysqlclient-mysqlcommandmembers.html. commandInterceptor: NoOpCommandInterceptor.Instance) }, {AdoNetInvariants.InvariantNameOracleDatabase, new DbConstants( startEscapeIndicator: '\"', endEscapeIndicator: '\"', unionAllSelectTemplate: " FROM DUAL UNION ALL SELECT ", isSynchronousAdoNetImplementation: true, supportsStreamNatively: false, supportsCommandCancellation: false, // Is supported but the remarks sound scary: https://docs.oracle.com/cd/E11882_01/win.112/e23174/OracleCommandClass.htm#DAFIEHHG. commandInterceptor: OracleCommandInterceptor.Instance) }, { AdoNetInvariants.InvariantNameMySqlConnector, new DbConstants(startEscapeIndicator: '[', endEscapeIndicator: ']', unionAllSelectTemplate: " UNION ALL SELECT ", isSynchronousAdoNetImplementation: false, supportsStreamNatively: true, supportsCommandCancellation: true, commandInterceptor: NoOpCommandInterceptor.Instance) }, { AdoNetInvariants.InvariantNameSqlLite, new DbConstants(startEscapeIndicator: '\"', endEscapeIndicator: '\"', unionAllSelectTemplate: " UNION ALL SELECT ", isSynchronousAdoNetImplementation: false, supportsStreamNatively: false, supportsCommandCancellation: true, commandInterceptor: NoOpCommandInterceptor.Instance) } }; public static DbConstants GetDbConstants(string invariantName) { return invariantNameToConsts[invariantName]; } /// /// If the underlying storage supports cancellation or not. /// /// The storage used. /// TRUE if cancellation is supported. FALSE otherwise. public static bool SupportsCommandCancellation(this IRelationalStorage storage) { return SupportsCommandCancellation(storage.InvariantName); } /// /// If the provider supports cancellation or not. /// /// The ADO.NET provider invariant string. /// TRUE if cancellation is supported. FALSE otherwise. public static bool SupportsCommandCancellation(string adoNetProvider) { return GetDbConstants(adoNetProvider).SupportsCommandCancellation; } /// /// If the underlying storage supports streaming natively. /// /// The storage used. /// TRUE if streaming is supported natively. FALSE otherwise. public static bool SupportsStreamNatively(this IRelationalStorage storage) { return SupportsStreamNatively(storage.InvariantName); } /// /// If the provider supports streaming natively. /// /// The ADO.NET provider invariant string. /// TRUE if streaming is supported natively. FALSE otherwise. public static bool SupportsStreamNatively(string adoNetProvider) { return GetDbConstants(adoNetProvider).SupportsStreamNatively; } /// /// If the underlying ADO.NET implementation is known to be synchronous. /// /// The storage used. /// public static bool IsSynchronousAdoNetImplementation(this IRelationalStorage storage) { //Currently the assumption is all but MySQL are asynchronous. return IsSynchronousAdoNetImplementation(storage.InvariantName); } /// /// If the provider supports cancellation or not. /// /// The ADO.NET provider invariant string. /// public static bool IsSynchronousAdoNetImplementation(string adoNetProvider) { return GetDbConstants(adoNetProvider).IsSynchronousAdoNetImplementation; } public static ICommandInterceptor GetDatabaseCommandInterceptor(string invariantName) { return GetDbConstants(invariantName).DatabaseCommandInterceptor; } } internal class DbConstants { /// /// A query template for union all select /// public readonly string UnionAllSelectTemplate; /// /// Indicates whether the ADO.net provider does only support synchronous operations. /// public readonly bool IsSynchronousAdoNetImplementation; /// /// Indicates whether the ADO.net provider does streaming operations natively. /// public readonly bool SupportsStreamNatively; /// /// Indicates whether the ADO.net provider supports cancellation of commands. /// public readonly bool SupportsCommandCancellation; /// /// The character that indicates a start escape key for columns and tables that are reserved words. /// public readonly char StartEscapeIndicator; /// /// The character that indicates an end escape key for columns and tables that are reserved words. /// public readonly char EndEscapeIndicator; public readonly ICommandInterceptor DatabaseCommandInterceptor; public DbConstants(char startEscapeIndicator, char endEscapeIndicator, string unionAllSelectTemplate, bool isSynchronousAdoNetImplementation, bool supportsStreamNatively, bool supportsCommandCancellation, ICommandInterceptor commandInterceptor) { StartEscapeIndicator = startEscapeIndicator; EndEscapeIndicator = endEscapeIndicator; UnionAllSelectTemplate = unionAllSelectTemplate; IsSynchronousAdoNetImplementation = isSynchronousAdoNetImplementation; SupportsStreamNatively = supportsStreamNatively; SupportsCommandCancellation = supportsCommandCancellation; DatabaseCommandInterceptor = commandInterceptor; } } } ================================================ FILE: src/AdoNet/Shared/Storage/DbExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Common; using System.Threading; using System.Threading.Tasks; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Contains some convenience methods to use in conjunction with IRelationalStorage and GenericRelationalStorage. /// internal static class DbExtensions { /// /// An explicit map of type CLR viz database type conversions. /// private static readonly ReadOnlyDictionary typeMap = new ReadOnlyDictionary(new Dictionary { { typeof(object), DbType.Object }, { typeof(int), DbType.Int32 }, { typeof(int?), DbType.Int32 }, { typeof(uint), DbType.UInt32 }, { typeof(uint?), DbType.UInt32 }, { typeof(long), DbType.Int64 }, { typeof(long?), DbType.Int64 }, { typeof(ulong), DbType.UInt64 }, { typeof(ulong?), DbType.UInt64 }, { typeof(float), DbType.Single }, { typeof(float?), DbType.Single }, { typeof(double), DbType.Double }, { typeof(double?), DbType.Double }, { typeof(decimal), DbType.Decimal }, { typeof(decimal?), DbType.Decimal }, { typeof(short), DbType.Int16 }, { typeof(short?), DbType.Int16 }, { typeof(ushort), DbType.UInt16 }, { typeof(ushort?), DbType.UInt16 }, { typeof(byte), DbType.Byte }, { typeof(byte?), DbType.Byte }, { typeof(sbyte), DbType.SByte }, { typeof(sbyte?), DbType.SByte }, { typeof(bool), DbType.Boolean }, { typeof(bool?), DbType.Boolean }, { typeof(string), DbType.String }, { typeof(char), DbType.StringFixedLength }, { typeof(char?), DbType.StringFixedLength }, { typeof(Guid), DbType.Guid }, { typeof(Guid?), DbType.Guid }, //Using DateTime for cross DB compatibility. The underlying DB table column type can be DateTime or DateTime2 { typeof(DateTime), DbType.DateTime }, { typeof(DateTime?), DbType.DateTime }, { typeof(TimeSpan), DbType.Time }, { typeof(byte[]), DbType.Binary }, { typeof(TimeSpan?), DbType.Time }, { typeof(DateTimeOffset), DbType.DateTimeOffset }, { typeof(DateTimeOffset?), DbType.DateTimeOffset }, }); /// /// Creates a new SQL parameter using the given arguments. /// /// The type of the parameter. /// The command to use to create the parameter. /// The direction of the parameter. /// The name of the parameter. /// The value of the parameter. /// The size of the parameter value. /// the of the parameter. /// A parameter created using the given arguments. public static IDbDataParameter CreateParameter(this IDbCommand command, ParameterDirection direction, string parameterName, T value, int? size = null, DbType? dbType = null) { //There should be no boxing for value types. See at: //http://stackoverflow.com/questions/8823239/comparing-a-generic-against-null-that-could-be-a-value-or-reference-type var parameter = command.CreateParameter(); parameter.ParameterName = parameterName; parameter.Value = (object)value ?? DBNull.Value; parameter.DbType = dbType ?? typeMap[typeof(T)]; parameter.Direction = direction; if (size != null) { parameter.Size = size.Value; } return parameter; } /// /// Creates and adds a new SQL parameter to the command. /// /// The type of the parameter. /// The command to use to create the parameter. /// The name of the parameter. /// The value of the parameter. /// The direction of the parameter. /// The size of the parameter value. /// the of the parameter. public static void AddParameter(this IDbCommand command, string parameterName, T value, ParameterDirection direction = ParameterDirection.Input, int? size = null, DbType? dbType = null) { command.Parameters.Add(command.CreateParameter(direction, parameterName, value, size)); } /// /// Returns a value if it is not , default(TValue) otherwise. /// /// The type of the value to request. /// The record from which to retrieve the value. /// The name of the field to retrieve. /// The default value if value in position is . /// Either the given value or the default for the requested type. /// This function throws if the given does not exist. public static TValue GetValueOrDefault(this IDataRecord record, string fieldName, TValue @default = default) { try { var ordinal = record.GetOrdinal(fieldName); return record.IsDBNull(ordinal) ? @default : (TValue)record.GetValue(ordinal); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value if it is not , default otherwise. /// /// The record from which to retrieve the value. /// The name of the field to retrieve. /// The default value if value in position is . /// Either the given value or the default for ?. /// /// An explicit function like this is needed in cases where to connector infers a type that is undesirable. /// An example here is Npgsql.NodaTime, which makes Npgsql return Noda type and consequently Orleans is not able to /// use it since it expects .NET . This function throws if the given does not exist. public static DateTime? GetDateTimeValueOrDefault(this IDataRecord record, string fieldName, DateTime? @default = default) { try { var ordinal = record.GetOrdinal(fieldName); return record.IsDBNull(ordinal) ? @default : DateTime.SpecifyKind(record.GetDateTime(ordinal), DateTimeKind.Utc); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value if it is not , default(TValue) otherwise. /// /// The type of the value to request. /// The record from which to retrieve the value. /// The name of the field to retrieve. /// The default value if value in position is . /// Either the given value or the default for the requested type. /// /// This function throws if the given does not exist. public static async Task GetValueOrDefaultAsync(this DbDataReader record, string fieldName, TValue @default = default) { try { var ordinal = record.GetOrdinal(fieldName); return (await record.IsDBNullAsync(ordinal).ConfigureAwait(false)) ? @default : (await record.GetFieldValueAsync(ordinal).ConfigureAwait(false)); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value if it is not , default(TValue) otherwise. /// /// The type of the value to request. /// The record from which to retrieve the value. /// The ordinal of the fieldname. /// The default value if value in position is . /// Either the given value or the default for the requested type. /// public static TValue GetValueOrDefault(this IDataRecord record, int ordinal, TValue @default = default) { return record.IsDBNull(ordinal) ? @default : (TValue)record.GetValue(ordinal); } /// /// Returns a value if it is not , default(TValue) otherwise. /// /// The type of the value to request. /// The record from which to retrieve the value. /// The ordinal of the fieldname. /// The default value if value in position is . /// Either the given value or the default for the requested type. /// public static async Task GetValueOrDefaultAsync(this DbDataReader record, int ordinal, TValue @default = default) { return (await record.IsDBNullAsync(ordinal).ConfigureAwait(false)) ? @default : (await record.GetFieldValueAsync(ordinal).ConfigureAwait(false)); } /// /// Returns a value with the given . /// /// The type of value to retrieve. /// The record from which to retrieve the value. /// The name of the field. /// Value in the given field indicated by . /// /// This function throws if the given does not exist. public static TValue GetValue(this IDataRecord record, string fieldName) { try { var ordinal = record.GetOrdinal(fieldName); return (TValue)record.GetValue(ordinal); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value with the given . /// /// The record from which to retrieve the value. /// The name of the field. /// DateTime Value in the given field. /// /// An explicit function like this is needed in cases where to connector infers a type that is undesirable. /// An example here is Npgsql.NodaTime, which makes Npgsql return Noda type and consequently Orleans is not able to /// use it since it expects .NET . This function throws if the given does not exist. public static DateTime GetDateTimeValue(this IDataRecord record, string fieldName) { try { var ordinal = record.GetOrdinal(fieldName); return DateTime.SpecifyKind(record.GetDateTime(ordinal), DateTimeKind.Utc); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value with the given as int. /// /// The record from which to retrieve the value. /// The name of the field. /// /// Integer value in the given field indicated by . public static int GetInt32(this IDataRecord record, string fieldName) { try { var ordinal = record.GetOrdinal(fieldName); return record.GetInt32(ordinal); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value with the given as long. /// /// The record from which to retrieve the value. /// The name of the field. /// /// Integer value in the given field indicated by . public static long GetInt64(this IDataRecord record, string fieldName) { try { var ordinal = record.GetOrdinal(fieldName); // Original casting when old schema is used. Here to maintain backwards compatibility return record.GetFieldType(ordinal) == typeof(int) ? record.GetInt32(ordinal) : record.GetInt64(ordinal); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value with the given as nullable int. /// /// The record from which to retrieve the value. /// The name of the field. /// /// Nullable int value in the given field indicated by . public static int? GetNullableInt32(this IDataRecord record, string fieldName) { try { var ordinal = record.GetOrdinal(fieldName); var value = record.GetValue(ordinal); if (value == DBNull.Value) return null; return Convert.ToInt32(value); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Returns a value with the given . /// /// The type of value to retrieve. /// The record from which to retrieve the value. /// The name of the field. /// The cancellation token. Defaults to . /// Value in the given field indicated by . /// /// This function throws if the given does not exist. public static async Task GetValueAsync(this DbDataReader record, string fieldName, CancellationToken cancellationToken = default) { try { var ordinal = record.GetOrdinal(fieldName); return await record.GetFieldValueAsync(ordinal, cancellationToken).ConfigureAwait(false); } catch (IndexOutOfRangeException e) { throw new DataException($"Field '{fieldName}' not found in data record.", e); } } /// /// Adds given parameters to a command using reflection. /// /// The type of the parameters. /// The command. /// The parameters. /// Maps a given property name to another one defined in the map. /// Does not support collection parameters currently. Does not cache reflection results. public static void ReflectionParameterProvider(this IDbCommand command, T parameters, IReadOnlyDictionary nameMap = null) { if (!EqualityComparer.Default.Equals(parameters, default)) { var properties = parameters.GetType().GetProperties(); for (int i = 0; i < properties.Length; ++i) { var property = properties[i]; var value = property.GetValue(parameters, null); var parameter = command.CreateParameter(); parameter.Value = value ?? DBNull.Value; parameter.Direction = ParameterDirection.Input; parameter.ParameterName = nameMap != null && nameMap.ContainsKey(properties[i].Name) ? nameMap[property.Name] : properties[i].Name; parameter.DbType = typeMap[property.PropertyType]; command.Parameters.Add(parameter); } } } /// /// Creates object of the given type from the results of a query. /// /// The type to construct. /// The record from which to read the results. /// And object of type . /// Does not support of type dynamic. public static TResult ReflectionSelector(this IDataRecord record) { //This is done like this in order to box value types. //Otherwise property.SetValue() would have a copy of the struct, which would //get garbage collected. Consequently the original struct value would not be set. object obj = Activator.CreateInstance(); var properties = obj.GetType().GetProperties(); for (int i = 0; i < properties.Length; ++i) { var rp = record[properties[i].Name]; if (!Equals(rp, DBNull.Value)) { properties[i].SetValue(obj, rp, null); } } return (TResult)obj; } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/DbStoredQueries.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Net; using System.Reflection; using Orleans.Runtime; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif ORLEANS_REMINDERS_PROVIDER using Orleans.Reminders.AdoNet.Converters; namespace Orleans.Reminders.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// This class implements the expected contract between Orleans and the underlying relational storage. /// It makes sure all the stored queries are present and /// internal class DbStoredQueries { private readonly Dictionary queries; internal DbStoredQueries(Dictionary queries) { var fields = typeof(DbStoredQueries).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic) .Select(p => p.Name); var missingQueryKeys = fields.Except(queries.Keys).ToArray(); if (missingQueryKeys.Length > 0) { throw new ArgumentException( $"Not all required queries found. Missing are: {string.Join(",", missingQueryKeys)}"); } this.queries = queries; } /// /// The query that's used to get all the stored queries. /// this will probably be the same for all relational dbs. /// internal const string GetQueriesKey = "SELECT QueryKey, QueryText FROM OrleansQuery"; #if CLUSTERING_ADONET || TESTER_SQLUTILS /// /// A query template to retrieve gateway URIs. /// internal string GatewaysQueryKey => queries[nameof(GatewaysQueryKey)]; /// /// A query template to retrieve a single row of membership data. /// internal string MembershipReadRowKey => queries[nameof(MembershipReadRowKey)]; /// /// A query template to retrieve all membership data. /// internal string MembershipReadAllKey => queries[nameof(MembershipReadAllKey)]; /// /// A query template to insert a membership version row. /// internal string InsertMembershipVersionKey => queries[nameof(InsertMembershipVersionKey)]; /// /// A query template to update "I Am Alive Time". /// internal string UpdateIAmAlivetimeKey => queries[nameof(UpdateIAmAlivetimeKey)]; /// /// A query template to insert a membership row. /// internal string InsertMembershipKey => queries[nameof(InsertMembershipKey)]; /// /// A query template to update a membership row. /// internal string UpdateMembershipKey => queries[nameof(UpdateMembershipKey)]; /// /// A query template to delete membership entries. /// internal string DeleteMembershipTableEntriesKey => queries[nameof(DeleteMembershipTableEntriesKey)]; /// /// A query template to cleanup defunct silo entries. /// internal string CleanupDefunctSiloEntriesKey => queries[nameof(CleanupDefunctSiloEntriesKey)]; #endif #if REMINDERS_ADONET || TESTER_SQLUTILS || ORLEANS_REMINDERS_PROVIDER /// /// A query template to read reminder entries. /// internal string ReadReminderRowsKey => queries[nameof(ReadReminderRowsKey)]; /// /// A query template to read reminder entries with ranges. /// internal string ReadRangeRows1Key => queries[nameof(ReadRangeRows1Key)]; /// /// A query template to read reminder entries with ranges. /// internal string ReadRangeRows2Key => queries[nameof(ReadRangeRows2Key)]; /// /// A query template to read a reminder entry with ranges. /// internal string ReadReminderRowKey => queries[nameof(ReadReminderRowKey)]; /// /// A query template to upsert a reminder row. /// internal string UpsertReminderRowKey => queries[nameof(UpsertReminderRowKey)]; /// /// A query template to delete a reminder row. /// internal string DeleteReminderRowKey => queries[nameof(DeleteReminderRowKey)]; /// /// A query template to delete all reminder rows. /// internal string DeleteReminderRowsKey => queries[nameof(DeleteReminderRowsKey)]; #endif #if STREAMING_ADONET || TESTER_SQLUTILS /// /// A query template to enqueue a message into the stream table. /// internal string QueueStreamMessageKey => queries[nameof(QueueStreamMessageKey)]; /// /// A query template to dequeue messages from the stream table. /// internal string GetStreamMessagesKey => queries[nameof(GetStreamMessagesKey)]; /// /// A query template to confirm message delivery from the stream table. /// internal string ConfirmStreamMessagesKey => queries[nameof(ConfirmStreamMessagesKey)]; /// /// A query template to evict a single message (by moving it to dead letters). /// internal string FailStreamMessageKey => queries[nameof(FailStreamMessageKey)]; /// /// A query template to batch evict messages (by moving them to dead letters). /// internal string EvictStreamMessagesKey => queries[nameof(EvictStreamMessagesKey)]; /// /// A query template to evict expired dead letters (by deleting them). /// internal string EvictStreamDeadLettersKey => queries[nameof(EvictStreamDeadLettersKey)]; #endif #if GRAINDIRECTORY_ADONET || TESTER_SQLUTILS /// /// A query template to register a grain activation. /// internal string RegisterGrainActivationKey => queries[nameof(RegisterGrainActivationKey)]; /// /// A query template to unregister a grain activation. /// internal string UnregisterGrainActivationKey => queries[nameof(UnregisterGrainActivationKey)]; /// /// A query template to lookup a grain activation. /// internal string LookupGrainActivationKey => queries[nameof(LookupGrainActivationKey)]; /// /// A query template to unregister all grain activations for a set of silos. /// internal string UnregisterGrainActivationsKey => queries[nameof(UnregisterGrainActivationsKey)]; #endif internal static class Converters { internal static KeyValuePair GetQueryKeyAndValue(IDataRecord record) { return new KeyValuePair(record.GetValue("QueryKey"), record.GetValue("QueryText")); } internal static Tuple GetMembershipEntry(IDataRecord record) { //TODO: This is a bit of hack way to check in the current version if there's membership data or not, but if there's a start time, there's member. DateTime? startTime = record.GetDateTimeValueOrDefault(nameof(Columns.StartTime)); MembershipEntry entry = null; if (startTime.HasValue) { entry = new MembershipEntry { SiloAddress = GetSiloAddress(record, nameof(Columns.Port)), SiloName = TryGetSiloName(record), HostName = record.GetValue(nameof(Columns.HostName)), Status = (SiloStatus)Enum.Parse(typeof(SiloStatus), record.GetInt32(nameof(Columns.Status)).ToString()), ProxyPort = record.GetInt32(nameof(Columns.ProxyPort)), StartTime = startTime.Value, IAmAliveTime = record.GetDateTimeValue(nameof(Columns.IAmAliveTime)) }; string suspectingSilos = record.GetValueOrDefault(nameof(Columns.SuspectTimes)); if (!string.IsNullOrWhiteSpace(suspectingSilos)) { entry.SuspectTimes = new List>(); entry.SuspectTimes.AddRange(suspectingSilos.Split('|').Select(s => { var split = s.Split(','); return new Tuple(SiloAddress.FromParsableString(split[0]), LogFormatter.ParseDate(split[1])); })); } } return Tuple.Create(entry, GetVersion(record)); } /// /// This method is for compatibility with membership tables that /// do not contain a SiloName field /// private static string TryGetSiloName(IDataRecord record) { int pos; try { pos = record.GetOrdinal(nameof(Columns.SiloName)); } catch (IndexOutOfRangeException) { return null; } return (string)record.GetValue(pos); } internal static int GetVersion(IDataRecord record) { return Convert.ToInt32(record.GetValue(nameof(Version))); } internal static Uri GetGatewayUri(IDataRecord record) { return GetSiloAddress(record, nameof(Columns.ProxyPort)).ToGatewayUri(); } private static SiloAddress GetSiloAddress(IDataRecord record, string portName) { //Use the GetInt32 method instead of the generic GetValue version to retrieve the value from the data record //GetValue causes an InvalidCastException with orcale data provider. See https://github.com/dotnet/orleans/issues/3561 int port = record.GetInt32(portName); int generation = record.GetInt32(nameof(Columns.Generation)); string address = record.GetValue(nameof(Columns.Address)); var siloAddress = SiloAddress.New(IPAddress.Parse(address), port, generation); return siloAddress; } internal static bool GetSingleBooleanValue(IDataRecord record) { if (record.FieldCount != 1) throw new InvalidOperationException("Expected a single column"); return Convert.ToBoolean(record.GetValue(0)); } } internal class Columns { private readonly IDbCommand command; internal Columns(IDbCommand cmd) { command = cmd; } private void Add(string paramName, T paramValue, DbType? dbType = null) { command.AddParameter(paramName, paramValue, dbType: dbType); } private void AddAddress(string name, IPAddress address) { Add(name, address.ToString(), dbType: DbType.AnsiString); } private void AddGrainHash(string name, uint grainHash) { Add(name, (int)grainHash); } internal string ClientId { set { Add(nameof(ClientId), value); } } internal int GatewayPort { set { Add(nameof(GatewayPort), value); } } internal IPAddress GatewayAddress { set { AddAddress(nameof(GatewayAddress), value); } } internal string SiloId { set { Add(nameof(SiloId), value); } } internal string Id { set { Add(nameof(Id), value); } } internal string Name { set { Add(nameof(Name), value); } } internal const string IsValueDelta = nameof(IsValueDelta); internal const string StatValue = nameof(StatValue); internal const string Statistic = nameof(Statistic); internal SiloAddress SiloAddress { set { Address = value.Endpoint.Address; Port = value.Endpoint.Port; Generation = value.Generation; } } /// /// Workaround for SiloAddress in the Grain Directory clashing with the existing property. /// internal string SiloAddressAsString { set => Add("SiloAddress", value); } internal string SiloAddresses { set => Add(nameof(SiloAddresses), value); } internal int Generation { set { Add(nameof(Generation), value); } } internal int Port { set { Add(nameof(Port), value); } } internal uint BeginHash { set { AddGrainHash(nameof(BeginHash), value); } } internal uint EndHash { set { AddGrainHash(nameof(EndHash), value); } } internal uint GrainHash { set { AddGrainHash(nameof(GrainHash), value); } } internal DateTime StartTime { set { Add(nameof(StartTime), value); } } internal IPAddress Address { set { AddAddress(nameof(Address), value); } } internal string ServiceId { set { Add(nameof(ServiceId), value); } } internal string ClusterId { set { Add(nameof(ClusterId), value); } } internal string DeploymentId { set { Add(nameof(DeploymentId), value); } } internal string SiloName { set { Add(nameof(SiloName), value); } } internal string HostName { set { Add(nameof(HostName), value); } } internal string Version { set { Add(nameof(Version), int.Parse(value)); } } internal DateTime IAmAliveTime { set { Add(nameof(IAmAliveTime), value); } } internal string GrainId { set { Add(nameof(GrainId), value, dbType: DbType.AnsiString); } } internal int GrainIdHash { set => Add(nameof(GrainIdHash), value); } internal string ReminderName { set { Add(nameof(ReminderName), value); } } internal TimeSpan Period { set { if (value.TotalMilliseconds <= int.MaxValue) { // Original casting when old schema is used. Here to maintain backwards compatibility Add(nameof(Period), (int)value.TotalMilliseconds); } else { Add(nameof(Period), (long)value.TotalMilliseconds); } } } internal SiloStatus Status { set { Add(nameof(Status), (int)value); } } internal int ProxyPort { set { Add(nameof(ProxyPort), value); } } internal List> SuspectTimes { set { Add(nameof(SuspectTimes), value == null ? null : string.Join("|", value.Select( s => $"{s.Item1.ToParsableString()},{LogFormatter.PrintDate(s.Item2)}"))); } } internal string QueueId { set => Add(nameof(QueueId), value); } internal long MessageId { set => Add(nameof(MessageId), value); } internal byte[] Payload { set => Add(nameof(Payload), value); } internal int ExpiryTimeout { set => Add(nameof(ExpiryTimeout), value); } internal int MaxCount { set => Add(nameof(MaxCount), value); } internal int MaxAttempts { set => Add(nameof(MaxAttempts), value); } internal int RemovalTimeout { set => Add(nameof(RemovalTimeout), value); } internal int VisibilityTimeout { set => Add(nameof(VisibilityTimeout), value); } internal int EvictionInterval { set => Add(nameof(EvictionInterval), value); } internal int EvictionBatchSize { set => Add(nameof(EvictionBatchSize), value); } internal string EventIds { set => Add(nameof(EventIds), value); } internal string ProviderId { set => Add(nameof(ProviderId), value); } internal string Items { set => Add(nameof(Items), value); } internal string ActivationId { set => Add(nameof(ActivationId), value); } } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/ICommandInterceptor.cs ================================================ using System.Data; #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { internal interface ICommandInterceptor { void Intercept(IDbCommand command); } } ================================================ FILE: src/AdoNet/Shared/Storage/IRelationalStorage.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Threading; using System.Threading.Tasks; #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// A common interface for all relational databases. /// #if TESTER_SQLUTILS public interface IRelationalStorage #else internal interface IRelationalStorage #endif { /// /// Executes a given statement. Especially intended to use with SELECT statement. /// /// The result type. /// The query to execute. /// Adds parameters to the query. The parameters must be in the same order with same names as defined in the query. /// This function transforms the raw results to type the parameter being the resultset number. /// The command behavior that should be used. Defaults to . /// The cancellation token. Defaults to . /// A list of objects as a result of the . /// This sample shows how to make a hand-tuned database call. /// /// //This struct holds the return value in this example. /// public struct Information /// { /// public string TABLE_CATALOG { get; set; } /// public string TABLE_NAME { get; set; } /// } /// /// //Here are defined two queries. There can be more than two queries, in which case /// //the result sets are differentiated by a count parameter. Here the queries are /// //SELECT clauses, but they can be whatever, even mixed ones. /// IEnumerable<Information> ret = /// await storage.ReadAsync<Information>("SELECT * FROM INFORMATION_SCHEMA.TABLES; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tp1", command => /// { /// //Parameters are added and created like this. /// //They are database vendor agnostic. /// var tp1 = command.CreateParameter(); /// tp1.ParameterName = "tp1"; /// tp1.Value = "some test value"; /// tp1.DbType = DbType.String; /// tp1.Direction = ParameterDirection.Input; /// command.Parameters.Add(tp1); /// /// //The selector is used to select the results within the result set. In this case there are two homogenous /// //result sets, so there is actually no need to check which result set the selector holds and it could /// //marked with by convention by underscore (_). /// }, (selector, resultSetCount) => /// { /// //This function is called once for each row returned, so the final result will be an /// //IEnumerable<Information>. /// return new Information /// { /// TABLE_CATALOG = selector.GetValueOrDefault<string>("TABLE_CATALOG"), /// TABLE_NAME = selector.GetValueOrDefault<string>("TABLE_NAME") /// } ///}).ConfigureAwait(continueOnCapturedContext: false); /// /// Task> ReadAsync(string query, Action parameterProvider, Func> selector, CommandBehavior commandBehavior = CommandBehavior.Default, CancellationToken cancellationToken = default); /// /// Executes a given statement. Especially intended to use with INSERT, UPDATE, DELETE or DDL queries. /// /// The query to execute. /// Adds parameters to the query. Parameter names must match those defined in the query. /// The command behavior that should be used. Defaults to . /// The cancellation token. Defaults to . /// Affected rows count. /// This sample shows how to make a hand-tuned database call. /// /// //In contract to reading, execute queries are simpler as they return only /// //the affected rows count if it is available. /// var query = ""IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'Test') CREATE TABLE Test(Id INT PRIMARY KEY IDENTITY(1, 1) NOT NULL);" /// int affectedRowsCount = await storage.ExecuteAsync(query, command => /// { /// //There aren't parameters here, but they'd be added like when reading. /// //As the affected rows count is the only thing returned, there isn't /// //facilities to read anything. /// }).ConfigureAwait(continueOnCapturedContext: false); /// /// Task ExecuteAsync(string query, Action parameterProvider, CommandBehavior commandBehavior = CommandBehavior.Default, CancellationToken cancellationToken = default); /// /// The well known invariant name of the underlying database. /// string InvariantName { get; } /// /// The connection string used to connect to the database. /// string ConnectionString { get; } } } ================================================ FILE: src/AdoNet/Shared/Storage/NoOpDatabaseCommandInterceptor.cs ================================================ using System.Data; #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { internal class NoOpCommandInterceptor : ICommandInterceptor { public static readonly ICommandInterceptor Instance = new NoOpCommandInterceptor(); private NoOpCommandInterceptor() { } public void Intercept(IDbCommand command) { //NOP } } } ================================================ FILE: src/AdoNet/Shared/Storage/OracleDatabaseCommandInterceptor.cs ================================================ using System; using System.Data; using System.Linq.Expressions; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// This interceptor bypasses some Oracle specifics. /// internal class OracleCommandInterceptor : ICommandInterceptor { public static readonly ICommandInterceptor Instance = new OracleCommandInterceptor(); private readonly Lazy> setClobOracleDbTypeAction; private readonly Lazy> setBlobOracleDbTypeAction; private readonly Lazy> setCommandBindByNameAction; private OracleCommandInterceptor() { setClobOracleDbTypeAction = new Lazy>(() => BuildSetOracleDbTypeAction("Clob")); setBlobOracleDbTypeAction = new Lazy>(() => BuildSetOracleDbTypeAction("Blob")); setCommandBindByNameAction = new Lazy>(BuildSetBindByNameAction); } /// /// Creates a compiled lambda which sets the BindByName property on OracleCommand to true. /// /// An action which takes a OracleCommand as IDbCommand private Action BuildSetBindByNameAction() { var type = Type.GetType("Oracle.ManagedDataAccess.Client.OracleCommand, Oracle.ManagedDataAccess"); var parameterExpression = Expression.Parameter(typeof(IDbCommand), "command"); var castExpression = Expression.Convert(parameterExpression, type); var booleanConstantExpression = Expression.Constant(true); var setMethod = type.GetProperty("BindByName").GetSetMethod(); var callExpression = Expression.Call(castExpression, setMethod, booleanConstantExpression); return Expression.Lambda>(callExpression, parameterExpression).Compile(); } /// /// Creates a compiled lambda which sets the OracleDbType property to the specified /// /// String value of a OracleDbType enum value. /// An action which takes a OracleParameter as IDbDataParameter. private static Action BuildSetOracleDbTypeAction(string enumName) { var type = Type.GetType("Oracle.ManagedDataAccess.Client.OracleParameter, Oracle.ManagedDataAccess"); var parameterExpression = Expression.Parameter(typeof(IDbDataParameter), "dbparameter"); var castExpression = Expression.Convert(parameterExpression, type); var enumType = Type.GetType("Oracle.ManagedDataAccess.Client.OracleDbType, Oracle.ManagedDataAccess"); var clob = Enum.Parse(enumType, enumName); var enumConstantExpression = Expression.Constant(clob, enumType); var setMethod = type.GetProperty("OracleDbType").GetSetMethod(); var callExpression = Expression.Call(castExpression, setMethod, enumConstantExpression); return Expression.Lambda>(callExpression, parameterExpression).Compile(); } public void Intercept(IDbCommand command) { foreach (IDbDataParameter commandParameter in command.Parameters) { //By default oracle binds parameters by index not name //The property BindByName must be set to true to change the default behaviour setCommandBindByNameAction.Value(command); //String parameters are mapped to NVarChar2 OracleDbType which is limited to 4000 bytes //This sets the OracleType explicitly to CLOB if (commandParameter.ParameterName == "PayloadJson") { setClobOracleDbTypeAction.Value(commandParameter); continue; } //Same like above if (commandParameter.ParameterName == "PayloadXml") { setClobOracleDbTypeAction.Value(commandParameter); continue; } //Byte arrays are mapped as RAW which causes problems //This sets the OracleDbType explicitly to BLOB if (commandParameter.ParameterName == "PayloadBinary") { setBlobOracleDbTypeAction.Value(commandParameter); continue; } //Oracle doesn´t support DbType.Boolean, instead //we map these to DbType.Int32 if (commandParameter.DbType == DbType.Boolean) { commandParameter.Value = commandParameter.ToString() == bool.TrueString ? 1 : 0; commandParameter.DbType = DbType.Int32; } } } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/OrleansRelationalDownloadStream.cs ================================================ using System; using System.Data.Common; using System.IO; using System.Threading; using System.Threading.Tasks; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// This is a chunked read implementation for ADO.NET providers which do /// not otherwise implement natively. /// internal class OrleansRelationalDownloadStream : Stream { /// /// A cached task as if there are multiple rounds of reads, it is likely /// the bytes read is the same. This saves one allocation. /// private Task _lastTask; /// /// The reader to use to read from the database. /// private DbDataReader _reader; /// /// The position in the overall stream. /// private long _position; /// /// The column ordinal to read from. /// private readonly int _ordinal; /// /// The total number of bytes in the column. /// private readonly long _totalBytes; /// /// The internal byte array buffer size used in .CopyToAsync. /// This size is just a guess and is likely dependent on the database /// tuning settings (e.g. read_buffer_size in case of MySQL). /// private const int InternalReadBufferLength = 4092; /// /// Initializes a new instance. /// /// The reader to use to read from the database. /// The column ordinal to read from. public OrleansRelationalDownloadStream(DbDataReader reader, int ordinal) { _reader = reader; _ordinal = ordinal; //This return the total length of the column pointed by the ordinal. _totalBytes = reader.GetBytes(ordinal, 0, null, 0, 0); } /// public override bool CanRead => _reader != null && (!_reader.IsClosed); /// public override bool CanSeek => false; /// public override bool CanTimeout => true; /// public override bool CanWrite => false; /// public override long Length => _totalBytes; /// public override long Position { get => _position; set => throw new NotSupportedException(); } /// public override void Flush() => throw new NotSupportedException(); /// public override int Read(byte[] buffer, int offset, int count) { //This will throw with the same parameter names if the parameters are not valid. ValidateReadParameters(buffer, offset, count); try { int length = Math.Min(count, (int)(_totalBytes - _position)); long bytesRead = 0; if (length > 0) { bytesRead = _reader.GetBytes(_ordinal, _position, buffer, offset, length); _position += bytesRead; } return (int)bytesRead; } catch (DbException dex) { //It's not OK to throw non-IOExceptions from a Stream. throw new IOException(dex.Message, dex); } } /// public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { //This will throw with the same parameter names if the parameters are not valid. ValidateReadParameters(buffer, offset, count); if (cancellationToken.IsCancellationRequested) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); tcs.SetCanceled(cancellationToken); return tcs.Task; } try { //The last used task is saved in order to avoid one allocation when the number of bytes read //will likely be the same multiple times. int bytesRead = Read(buffer, offset, count); var ret = _lastTask != null && bytesRead == _lastTask.Result ? _lastTask : (_lastTask = Task.FromResult(bytesRead)); return ret; } catch (Exception e) { //Due to call to Read, this is for sure a IOException and can be thrown out. var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); tcs.SetException(e); return tcs.Task; } } /// public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { if (!cancellationToken.IsCancellationRequested) { byte[] buffer = new byte[InternalReadBufferLength]; int bytesRead; while ((bytesRead = Read(buffer, 0, buffer.Length)) > 0) { if (cancellationToken.IsCancellationRequested) { break; } await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); } } } /// public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); /// public override void SetLength(long value) => throw new NotSupportedException(); /// public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); /// protected override void Dispose(bool disposing) { if (disposing) { _reader = null; } base.Dispose(disposing); } /// /// Checks the parameters passed into a ReadAsync() or Read() are valid. /// /// /// /// private static void ValidateReadParameters(byte[] buffer, long offset, long count) { ArgumentNullException.ThrowIfNull(buffer); ArgumentOutOfRangeException.ThrowIfNegative(offset); ArgumentOutOfRangeException.ThrowIfNegative(count); if (checked(offset + count) > buffer.Length) { throw new ArgumentException("Invalid offset length"); } } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/RelationalOrleansQueries.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using Orleans.Runtime; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS using Orleans.Streaming.AdoNet; using Orleans.GrainDirectory.AdoNet; namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// A class for all relational storages that support all systems stores : membership, reminders and statistics /// internal class RelationalOrleansQueries { /// /// the underlying storage /// private readonly IRelationalStorage storage; /// /// When inserting statistics and generating a batch insert clause, these are the columns in the statistics /// table that will be updated with multiple values. The other ones are updated with one value only. /// private static readonly string[] InsertStatisticsMultiupdateColumns = { DbStoredQueries.Columns.IsValueDelta, DbStoredQueries.Columns.StatValue, DbStoredQueries.Columns.Statistic }; /// /// the orleans functional queries /// private readonly DbStoredQueries dbStoredQueries; /// /// Constructor /// /// the underlying relational storage /// Orleans functional queries private RelationalOrleansQueries(IRelationalStorage storage, DbStoredQueries dbStoredQueries) { this.storage = storage; this.dbStoredQueries = dbStoredQueries; } /// /// Creates an instance of a database of type and Initializes Orleans queries from the database. /// Orleans uses only these queries and the variables therein, nothing more. /// /// The invariant name of the connector for this database. /// The connection string this database should use for database operations. internal static async Task CreateInstance(string invariantName, string connectionString) { var storage = RelationalStorage.CreateInstance(invariantName, connectionString); var queries = await storage.ReadAsync(DbStoredQueries.GetQueriesKey, DbStoredQueries.Converters.GetQueryKeyAndValue, null); return new RelationalOrleansQueries(storage, new DbStoredQueries(queries.ToDictionary(q => q.Key, q => q.Value))); } private Task ExecuteAsync(string query, Func parameterProvider) { return storage.ExecuteAsync(query, command => parameterProvider(command)); } private async Task ReadAsync(string query, Func selector, Func parameterProvider, Func, TAggregate> aggregator) { var ret = await storage.ReadAsync(query, selector, command => parameterProvider(command)); return aggregator(ret); } #if REMINDERS_ADONET || TESTER_SQLUTILS /// /// Reads Orleans reminder data from the tables. /// /// The service ID. /// The grain reference (ID). /// Reminder table data. internal Task ReadReminderRowsAsync(string serviceId, GrainId grainId) { return ReadAsync(dbStoredQueries.ReadReminderRowsKey, GetReminderEntry, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, GrainId = grainId.ToString() }, ret => new ReminderTableData(ret.ToList())); } /// /// Reads Orleans reminder data from the tables. /// /// The service ID. /// The begin hash. /// The end hash. /// Reminder table data. internal Task ReadReminderRowsAsync(string serviceId, uint beginHash, uint endHash) { var query = (int)beginHash < (int)endHash ? dbStoredQueries.ReadRangeRows1Key : dbStoredQueries.ReadRangeRows2Key; return ReadAsync(query, GetReminderEntry, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, BeginHash = beginHash, EndHash = endHash }, ret => new ReminderTableData(ret.ToList())); } internal static KeyValuePair GetQueryKeyAndValue(IDataRecord record) { return new KeyValuePair(record.GetValue("QueryKey"), record.GetValue("QueryText")); } internal static ReminderEntry GetReminderEntry(IDataRecord record) { //Having non-null field, GrainId, means with the query filter options, an entry was found. string grainId = record.GetValueOrDefault(nameof(DbStoredQueries.Columns.GrainId)); if (grainId != null) { return new ReminderEntry { GrainId = GrainId.Parse(grainId), ReminderName = record.GetValue(nameof(DbStoredQueries.Columns.ReminderName)), StartAt = record.GetDateTimeValue(nameof(DbStoredQueries.Columns.StartTime)), //Use the GetInt64 method instead of the generic GetValue version to retrieve the value from the data record //GetValue causes an InvalidCastException with oracle data provider. See https://github.com/dotnet/orleans/issues/3561 Period = TimeSpan.FromMilliseconds(record.GetInt64(nameof(DbStoredQueries.Columns.Period))), ETag = DbStoredQueries.Converters.GetVersion(record).ToString() }; } return null; } /// /// Reads one row of reminder data. /// /// Service ID. /// The grain reference (ID). /// The reminder name to retrieve. /// A remainder entry. internal Task ReadReminderRowAsync(string serviceId, GrainId grainId, string reminderName) { return ReadAsync(dbStoredQueries.ReadReminderRowKey, GetReminderEntry, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, GrainId = grainId.ToString(), ReminderName = reminderName }, ret => ret.FirstOrDefault()); } /// /// Either inserts or updates a reminder row. /// /// The service ID. /// The grain reference (ID). /// The reminder name to retrieve. /// Start time of the reminder. /// Period of the reminder. /// The new etag of the either or updated or inserted reminder row. internal Task UpsertReminderRowAsync(string serviceId, GrainId grainId, string reminderName, DateTime startTime, TimeSpan period) { return ReadAsync(dbStoredQueries.UpsertReminderRowKey, DbStoredQueries.Converters.GetVersion, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, GrainHash = grainId.GetUniformHashCode(), GrainId = grainId.ToString(), ReminderName = reminderName, StartTime = startTime, Period = period }, ret => ret.First().ToString()); } /// /// Deletes a reminder /// /// Service ID. /// /// /// /// internal Task DeleteReminderRowAsync(string serviceId, GrainId grainId, string reminderName, string etag) { return ReadAsync(dbStoredQueries.DeleteReminderRowKey, DbStoredQueries.Converters.GetSingleBooleanValue, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, GrainId = grainId.ToString(), ReminderName = reminderName, Version = etag }, ret => ret.First()); } /// /// Deletes all reminders rows of a service id. /// /// /// internal Task DeleteReminderRowsAsync(string serviceId) { return ExecuteAsync(dbStoredQueries.DeleteReminderRowsKey, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId }); } #endif #if CLUSTERING_ADONET || TESTER_SQLUTILS /// /// Lists active gateways. Used mainly by Orleans clients. /// /// The deployment for which to query the gateways. /// The gateways for the silo. internal Task> ActiveGatewaysAsync(string deploymentId) { return ReadAsync(dbStoredQueries.GatewaysQueryKey, DbStoredQueries.Converters.GetGatewayUri, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, Status = SiloStatus.Active }, ret => ret.ToList()); } /// /// Queries Orleans membership data. /// /// The deployment for which to query data. /// Silo data used as parameters in the query. /// Membership table data. internal Task MembershipReadRowAsync(string deploymentId, SiloAddress siloAddress) { return ReadAsync(dbStoredQueries.MembershipReadRowKey, DbStoredQueries.Converters.GetMembershipEntry, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, SiloAddress = siloAddress }, ConvertToMembershipTableData); } /// /// returns all membership data for a deployment id /// /// /// internal Task MembershipReadAllAsync(string deploymentId) { return ReadAsync(dbStoredQueries.MembershipReadAllKey, DbStoredQueries.Converters.GetMembershipEntry, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId }, ConvertToMembershipTableData); } /// /// deletes all membership entries for a deployment id /// /// /// internal Task DeleteMembershipTableEntriesAsync(string deploymentId) { return ExecuteAsync(dbStoredQueries.DeleteMembershipTableEntriesKey, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId }); } /// /// deletes all membership entries for inactive silos where the IAmAliveTime is before the beforeDate parameter /// and the silo status is . /// /// /// /// internal Task CleanupDefunctSiloEntriesAsync(DateTimeOffset beforeDate, string deploymentId) { return ExecuteAsync(dbStoredQueries.CleanupDefunctSiloEntriesKey, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, IAmAliveTime = beforeDate.UtcDateTime }); } /// /// Updates IAmAlive for a silo /// /// /// /// /// internal Task UpdateIAmAliveTimeAsync(string deploymentId, SiloAddress siloAddress, DateTime iAmAliveTime) { return ExecuteAsync(dbStoredQueries.UpdateIAmAlivetimeKey, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, SiloAddress = siloAddress, IAmAliveTime = iAmAliveTime }); } /// /// Inserts a version row if one does not already exist. /// /// The deployment for which to query data. /// TRUE if a row was inserted. FALSE otherwise. internal Task InsertMembershipVersionRowAsync(string deploymentId) { return ReadAsync(dbStoredQueries.InsertMembershipVersionKey, DbStoredQueries.Converters.GetSingleBooleanValue, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId }, ret => ret.First()); } /// /// Inserts a membership row if one does not already exist. /// /// The deployment with which to insert row. /// The membership entry data to insert. /// The table expected version etag. /// TRUE if insert succeeds. FALSE otherwise. internal Task InsertMembershipRowAsync(string deploymentId, MembershipEntry membershipEntry, string etag) { return ReadAsync(dbStoredQueries.InsertMembershipKey, DbStoredQueries.Converters.GetSingleBooleanValue, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, IAmAliveTime = membershipEntry.IAmAliveTime, SiloName = membershipEntry.SiloName, HostName = membershipEntry.HostName, SiloAddress = membershipEntry.SiloAddress, StartTime = membershipEntry.StartTime, Status = membershipEntry.Status, ProxyPort = membershipEntry.ProxyPort, Version = etag }, ret => ret.First()); } /// /// Updates membership row data. /// /// The deployment with which to insert row. /// The membership data to used to update database. /// The table expected version etag. /// TRUE if update SUCCEEDS. FALSE ot internal Task UpdateMembershipRowAsync(string deploymentId, MembershipEntry membershipEntry, string etag) { return ReadAsync(dbStoredQueries.UpdateMembershipKey, DbStoredQueries.Converters.GetSingleBooleanValue, command => new DbStoredQueries.Columns(command) { DeploymentId = deploymentId, SiloAddress = membershipEntry.SiloAddress, IAmAliveTime = membershipEntry.IAmAliveTime, Status = membershipEntry.Status, SuspectTimes = membershipEntry.SuspectTimes, Version = etag }, ret => ret.First()); } private static MembershipTableData ConvertToMembershipTableData(IEnumerable> ret) { var retList = ret.ToList(); var tableVersionEtag = retList[0].Item2; var membershipEntries = new List>(); if (retList[0].Item1 != null) { membershipEntries.AddRange(retList.Select(i => new Tuple(i.Item1, string.Empty))); } return new MembershipTableData(membershipEntries, new TableVersion(tableVersionEtag, tableVersionEtag.ToString())); } #endif #if STREAMING_ADONET || TESTER_SQLUTILS /// /// Queues a stream message to the stream message table. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The serialized event payload. /// The expiry timeout for this event batch. /// An acknowledgement that the message was queued. internal Task QueueStreamMessageAsync(string serviceId, string providerId, string queueId, byte[] payload, int expiryTimeout) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); return ReadAsync( dbStoredQueries.QueueStreamMessageKey, record => new AdoNetStreamMessageAck( (string)record[nameof(AdoNetStreamMessageAck.ServiceId)], (string)record[nameof(AdoNetStreamMessageAck.ProviderId)], (string)record[nameof(AdoNetStreamMessageAck.QueueId)], (long)record[nameof(AdoNetStreamMessageAck.MessageId)]), command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, Payload = payload, ExpiryTimeout = expiryTimeout, }, result => result.Single()); } /// /// Gets stream messages from the stream message table. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The maximum count of event batches to get. /// The maximum attempts to lock an unprocessed event batch. /// The visibility timeout for the retrieved event batches. /// The timeout before the message is to be deleted from dead letters. /// The interval between opportunistic data eviction. /// The number of messages to evict in each batch. /// A list of dequeued payloads. internal Task> GetStreamMessagesAsync(string serviceId, string providerId, string queueId, int maxCount, int maxAttempts, int visibilityTimeout, int removalTimeout, int evictionInterval, int evictionBatchSize) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); return ReadAsync>( dbStoredQueries.GetStreamMessagesKey, record => new AdoNetStreamMessage( (string)record[nameof(AdoNetStreamMessage.ServiceId)], (string)record[nameof(AdoNetStreamMessage.ProviderId)], (string)record[nameof(AdoNetStreamMessage.QueueId)], (long)record[nameof(AdoNetStreamMessage.MessageId)], (int)record[nameof(AdoNetStreamMessage.Dequeued)], (DateTime)record[nameof(AdoNetStreamMessage.VisibleOn)], (DateTime)record[nameof(AdoNetStreamMessage.ExpiresOn)], (DateTime)record[nameof(AdoNetStreamMessage.CreatedOn)], (DateTime)record[nameof(AdoNetStreamMessage.ModifiedOn)], (byte[])record[nameof(AdoNetStreamMessage.Payload)]), command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, MaxCount = maxCount, MaxAttempts = maxAttempts, VisibilityTimeout = visibilityTimeout, RemovalTimeout = removalTimeout, EvictionInterval = evictionInterval, EvictionBatchSize = evictionBatchSize }, result => result.ToList()); } /// /// Confirms delivery of messages from the stream message table. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The messages to confirm. /// A list of confirmations. /// /// If is empty then an empty confirmation list is returned. /// internal Task> ConfirmStreamMessagesAsync(string serviceId, string providerId, string queueId, IList messages) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); ArgumentNullException.ThrowIfNull(messages); if (messages.Count == 0) { return Task.FromResult>([]); } // this builds a string in the form "1:2|3:4|5:6" where the first number is the message id and the second is the dequeue counter which acts as a receipt // while we have more efficient ways of passing this data per RDMS, we use a string here to ensure call compatibility across ADONET providers // it is the responsibility of the RDMS implementation to parse this string and apply it correctly var items = messages.Aggregate(new StringBuilder(), (b, m) => b.Append(b.Length > 0 ? "|" : "").Append(m.MessageId).Append(':').Append(m.Dequeued), b => b.ToString()); return ReadAsync>( dbStoredQueries.ConfirmStreamMessagesKey, record => new AdoNetStreamConfirmationAck( (string)record[nameof(AdoNetStreamConfirmationAck.ServiceId)], (string)record[nameof(AdoNetStreamConfirmationAck.ProviderId)], (string)record[nameof(AdoNetStreamConfirmationAck.QueueId)], (long)record[nameof(AdoNetStreamConfirmationAck.MessageId)]), command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, Items = items }, result => result.ToList()); } /// /// Applies delivery failure logic to a stream message, such as making the message visible again or moving it to dead letters. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The message identifier. internal Task FailStreamMessageAsync(string serviceId, string providerId, string queueId, long messageId, int maxAttempts, int removalTimeout) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); return ExecuteAsync( dbStoredQueries.FailStreamMessageKey, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, MessageId = messageId, MaxAttempts = maxAttempts, RemovalTimeout = removalTimeout }); } /// /// Moves eligible messages from the stream message table to the dead letter table. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The max number of messages to move in this batch. /// The max number of times a message can be dequeued. /// The timeout before the message is to be deleted from dead letters. internal Task EvictStreamMessagesAsync(string serviceId, string providerId, string queueId, int maxCount, int maxAttempts, int removalTimeout) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); return ExecuteAsync( dbStoredQueries.EvictStreamMessagesKey, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, MaxCount = maxCount, MaxAttempts = maxAttempts, RemovalTimeout = removalTimeout }); } /// /// Removes messages from the dead letter after their removal timeout expires. /// /// The service identifier. /// The provider identifier. /// The queue identifier. /// The max number of messages to move in this batch. internal Task EvictStreamDeadLettersAsync(string serviceId, string providerId, string queueId, int maxCount) { ArgumentNullException.ThrowIfNull(serviceId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(queueId); return ExecuteAsync( dbStoredQueries.EvictStreamDeadLettersKey, command => new DbStoredQueries.Columns(command) { ServiceId = serviceId, ProviderId = providerId, QueueId = queueId, MaxCount = maxCount }); } #endif #if GRAINDIRECTORY_ADONET || TESTER_SQLUTILS /// /// Registers a new grain activation. /// /// The cluster identifier. /// The grain identifier. /// The silo address. /// The activation identifier. /// The count of rows affected. internal Task RegisterGrainActivationAsync(string clusterId, string providerId, string grainId, string siloAddress, string activationId) { ArgumentNullException.ThrowIfNull(clusterId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(grainId); ArgumentNullException.ThrowIfNull(siloAddress); ArgumentNullException.ThrowIfNull(activationId); return ReadAsync( dbStoredQueries.RegisterGrainActivationKey, record => new AdoNetGrainDirectoryEntry( (string)record[nameof(AdoNetGrainDirectoryEntry.ClusterId)], (string)record[nameof(AdoNetGrainDirectoryEntry.ProviderId)], (string)record[nameof(AdoNetGrainDirectoryEntry.GrainId)], (string)record[nameof(AdoNetGrainDirectoryEntry.SiloAddress)], (string)record[nameof(AdoNetGrainDirectoryEntry.ActivationId)]), command => new DbStoredQueries.Columns(command) { ClusterId = clusterId, ProviderId = providerId, GrainId = grainId, SiloAddressAsString = siloAddress, ActivationId = activationId }, result => result.Single()); } /// /// Unregisters a grain activation. /// /// The cluster identifier. /// The grain identifier. /// The activation identifier. /// The count of rows affected. internal Task UnregisterGrainActivationAsync(string clusterId, string providerId, string grainId, string activationId) { ArgumentNullException.ThrowIfNull(clusterId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(grainId); ArgumentNullException.ThrowIfNull(activationId); return ReadAsync( dbStoredQueries.UnregisterGrainActivationKey, record => record.GetInt32(0), command => new DbStoredQueries.Columns(command) { ClusterId = clusterId, ProviderId = providerId, GrainId = grainId, ActivationId = activationId }, result => result.Single()); } /// /// Looks up a grain activation. /// /// The cluster identifier. /// The grain identifier. /// The grain activation if found or null if not. internal Task LookupGrainActivationAsync(string clusterId, string providerId, string grainId) { ArgumentNullException.ThrowIfNull(clusterId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(grainId); return ReadAsync( dbStoredQueries.LookupGrainActivationKey, record => new AdoNetGrainDirectoryEntry( (string)record[nameof(AdoNetGrainDirectoryEntry.ClusterId)], (string)record[nameof(AdoNetGrainDirectoryEntry.ProviderId)], (string)record[nameof(AdoNetGrainDirectoryEntry.GrainId)], (string)record[nameof(AdoNetGrainDirectoryEntry.SiloAddress)], (string)record[nameof(AdoNetGrainDirectoryEntry.ActivationId)]), command => new DbStoredQueries.Columns(command) { ClusterId = clusterId, ProviderId = providerId, GrainId = grainId, }, result => result.SingleOrDefault()); } /// /// Unregisters all grain activations for a set of silos. /// /// The cluster identifier. /// The pipe separated set of silos. /// The count of rows affected. internal Task UnregisterGrainActivationsAsync(string clusterId, string providerId, string siloAddresses) { ArgumentNullException.ThrowIfNull(clusterId); ArgumentNullException.ThrowIfNull(providerId); ArgumentNullException.ThrowIfNull(siloAddresses); return ReadAsync( dbStoredQueries.UnregisterGrainActivationsKey, record => record.GetInt32(0), command => new DbStoredQueries.Columns(command) { ClusterId = clusterId, ProviderId = providerId, SiloAddresses = siloAddresses }, result => result.Single()); } #endif } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/RelationalStorage.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// A general purpose class to work with a given relational database and ADO.NET provider. /// [DebuggerDisplay("InvariantName = {InvariantName}, ConnectionString = {ConnectionString}")] internal class RelationalStorage : IRelationalStorage { /// /// The connection string to use. /// private readonly string _connectionString; /// /// The invariant name of the connector for this database. /// private readonly string _invariantName; /// /// If the ADO.NET provider of this storage supports cancellation or not. This /// capability is queried and the result is cached here. /// private readonly bool _supportsCommandCancellation; /// /// If the underlying ADO.NET implementation is natively asynchronous /// (the ADO.NET Db*.XXXAsync classes are overridden) or not. /// private readonly bool _isSynchronousAdoNetImplementation; /// /// Command interceptor for the given data provider. /// private readonly ICommandInterceptor _databaseCommandInterceptor; /// /// The invariant name of the connector for this database. /// public string InvariantName { get { return _invariantName; } } /// /// The connection string used to connect to the database. /// public string ConnectionString { get { return _connectionString; } } /// /// Creates an instance of a database of type . /// /// The invariant name of the connector for this database. /// The connection string this database should use for database operations. /// public static IRelationalStorage CreateInstance(string invariantName, string connectionString) { if (string.IsNullOrWhiteSpace(invariantName)) { throw new ArgumentException("The name of invariant must contain characters", nameof(invariantName)); } if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentException("Connection string must contain characters", nameof(connectionString)); } return new RelationalStorage(invariantName, connectionString); } /// /// Executes a given statement. Especially intended to use with SELECT statement. /// /// The result type. /// Executes a given statement. Especially intended to use with SELECT statement. /// Adds parameters to the query. Parameter names must match those defined in the query. /// This function transforms the raw results to type the parameter being the resultset number. /// The command behavior that should be used. Defaults to . /// The cancellation token. Defaults to . /// A list of objects as a result of the . /// This sample shows how to make a hand-tuned database call. /// /// //This struct holds the return value in this example. /// public struct Information /// { /// public string TABLE_CATALOG { get; set; } /// public string TABLE_NAME { get; set; } /// } /// /// //Here are defined two queries. There can be more than two queries, in which case /// //the result sets are differentiated by a count parameter. Here the queries are /// //SELECT clauses, but they can be whatever, even mixed ones. /// IEnumerable<Information> ret = /// await storage.ReadAsync<Information>("SELECT * FROM INFORMATION_SCHEMA.TABLES; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tp1", command => /// { /// //Parameters are added and created like this. /// //They are database vendor agnostic. /// var tp1 = command.CreateParameter(); /// tp1.ParameterName = "tp1"; /// tp1.Value = "some test value"; /// tp1.DbType = DbType.String; /// tp1.Direction = ParameterDirection.Input; /// command.Parameters.Add(tp1); /// /// //The selector is used to select the results within the result set. In this case there are two homogenous /// //result sets, so there is actually no need to check which result set the selector holds and it could /// //marked with by convention by underscore (_). /// }, (selector, resultSetCount) => /// { /// //This function is called once for each row returned, so the final result will be an /// //IEnumerable<Information>. /// return new Information /// { /// TABLE_CATALOG = selector.GetValueOrDefault<string>("TABLE_CATALOG"), /// TABLE_NAME = selector.GetValueOrDefault<string>("TABLE_NAME") /// } ///}).ConfigureAwait(continueOnCapturedContext: false); /// /// public async Task> ReadAsync(string query, Action parameterProvider, Func> selector, CommandBehavior commandBehavior = CommandBehavior.Default, CancellationToken cancellationToken = default) { //If the query is something else that is not acceptable (e.g. an empty string), there will an appropriate database exception. if (query == null) { throw new ArgumentNullException(nameof(query)); } if (selector == null) { throw new ArgumentNullException(nameof(selector)); } return (await ExecuteAsync(query, parameterProvider, ExecuteReaderAsync, selector, commandBehavior, cancellationToken).ConfigureAwait(false)).Item1; } /// /// Executes a given statement. Especially intended to use with INSERT, UPDATE, DELETE or DDL queries. /// /// The query to execute. /// Adds parameters to the query. Parameter names must match those defined in the query. /// The command behavior that should be used. Defaults to . /// The cancellation token. Defaults to . /// Affected rows count. /// This sample shows how to make a hand-tuned database call. /// /// //In contract to reading, execute queries are simpler as they return only /// //the affected rows count if it is available. /// var query = ""IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Test') CREATE TABLE Test(Id INT PRIMARY KEY IDENTITY(1, 1) NOT NULL);" /// int affectedRowsCount = await storage.ExecuteAsync(query, command => /// { /// //There aren't parameters here, but they'd be added like when reading. /// //As the affected rows count is the only thing returned, there isn't /// //facilities to read anything. /// }).ConfigureAwait(continueOnCapturedContext: false); /// /// public async Task ExecuteAsync(string query, Action parameterProvider, CommandBehavior commandBehavior = CommandBehavior.Default, CancellationToken cancellationToken = default) { //If the query is something else that is not acceptable (e.g. an empty string), there will an appropriate database exception. if (query == null) { throw new ArgumentNullException(nameof(query)); } return (await ExecuteAsync(query, parameterProvider, ExecuteReaderAsync, (unit, id, c) => Task.FromResult(unit), commandBehavior, cancellationToken).ConfigureAwait(false)).Item2; } /// /// Creates an instance of a database of type . /// /// The invariant name of the connector for this database. /// The connection string this database should use for database operations. private RelationalStorage(string invariantName, string connectionString) { this._connectionString = connectionString; this._invariantName = invariantName; _supportsCommandCancellation = DbConstantsStore.SupportsCommandCancellation(InvariantName); _isSynchronousAdoNetImplementation = DbConstantsStore.IsSynchronousAdoNetImplementation(InvariantName); this._databaseCommandInterceptor = DbConstantsStore.GetDatabaseCommandInterceptor(InvariantName); } private static async Task, int>> SelectAsync(DbDataReader reader, Func> selector, CancellationToken cancellationToken) { var results = new List(); var resultSetCount = 0; do { while (await reader.ReadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) { var obj = await selector(reader, resultSetCount, cancellationToken).ConfigureAwait(false); results.Add(obj); } ++resultSetCount; } while (await reader.NextResultAsync(cancellationToken).ConfigureAwait(false)); return Tuple.Create(results.AsEnumerable(), reader.RecordsAffected); } private async Task, int>> ExecuteReaderAsync(DbCommand command, Func> selector, CommandBehavior commandBehavior, CancellationToken cancellationToken) { using (var reader = await command.ExecuteReaderAsync(commandBehavior, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) { CancellationTokenRegistration cancellationRegistration = default; try { if (cancellationToken.CanBeCanceled && _supportsCommandCancellation) { cancellationRegistration = cancellationToken.Register(CommandCancellation, Tuple.Create(reader, command), useSynchronizationContext: false); } return await SelectAsync(reader, selector, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { cancellationRegistration.Dispose(); } } } private async Task, int>> ExecuteAsync( string query, Action parameterProvider, Func>, CommandBehavior, CancellationToken, Task, int>>> executor, Func> selector, CommandBehavior commandBehavior, CancellationToken cancellationToken) { using (var connection = DbConnectionFactory.CreateConnection(_invariantName, _connectionString)) { await connection.OpenAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); using (var command = connection.CreateCommand()) { parameterProvider?.Invoke(command); command.CommandText = query; _databaseCommandInterceptor.Intercept(command); Task, int>> ret; if (_isSynchronousAdoNetImplementation) { ret = Task.Run(() => executor(command, selector, commandBehavior, cancellationToken), cancellationToken); } else { ret = executor(command, selector, commandBehavior, cancellationToken); } return await ret.ConfigureAwait(continueOnCapturedContext: false); } } } private static void CommandCancellation(object state) { //The MSDN documentation tells that DbCommand.Cancel() should not be called for SqlCommand if the reader has been closed //in order to avoid a race condition that would cause the SQL Server to stream the result set //despite the connection already closed. Source: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.cancel(v=vs.110).aspx. //Enforcing this behavior across all providers does not seem to hurt. var stateTuple = (Tuple)state; if (!stateTuple.Item1.IsClosed) { stateTuple.Item2.Cancel(); } } } } #nullable restore ================================================ FILE: src/AdoNet/Shared/Storage/RelationalStorageExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; #nullable disable #if CLUSTERING_ADONET namespace Orleans.Clustering.AdoNet.Storage #elif PERSISTENCE_ADONET namespace Orleans.Persistence.AdoNet.Storage #elif REMINDERS_ADONET namespace Orleans.Reminders.AdoNet.Storage #elif STREAMING_ADONET namespace Orleans.Streaming.AdoNet.Storage #elif GRAINDIRECTORY_ADONET namespace Orleans.GrainDirectory.AdoNet.Storage #elif TESTER_SQLUTILS namespace Orleans.Tests.SqlUtils #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Convenience functions to work with objects of type . /// internal static class RelationalStorageExtensions { /// /// Used to format .NET objects suitable to relational database format. /// private static readonly AdoNetFormatProvider adoNetFormatProvider = new AdoNetFormatProvider(); /// /// This is a template to produce query parameters that are indexed. /// private const string indexedParameterTemplate = "@p{0}"; /// /// Executes a multi-record insert query clause with SELECT UNION ALL. /// /// /// The storage to use. /// The table name to against which to execute the query. /// The parameters to insert. /// If provided, maps property names from to ones provided in the map. /// If given, SQL parameter values for the given property types are generated only once. Effective only when is TRUE. /// TRUE if the query should be in parameterized form. FALSE otherwise. /// The cancellation token. Defaults to . /// The rows affected. public static Task ExecuteMultipleInsertIntoAsync(this IRelationalStorage storage, string tableName, IEnumerable parameters, IReadOnlyDictionary nameMap = null, IEnumerable onlyOnceColumns = null, bool useSqlParams = true, CancellationToken cancellationToken = default) { if(string.IsNullOrWhiteSpace(tableName)) { throw new ArgumentException("The name must be a legal SQL table name", nameof(tableName)); } if(parameters == null) { throw new ArgumentNullException(nameof(parameters)); } var storageConsts = DbConstantsStore.GetDbConstants(storage.InvariantName); var startEscapeIndicator = storageConsts.StartEscapeIndicator; var endEscapeIndicator = storageConsts.EndEscapeIndicator; //SqlParameters map is needed in case the query needs to be parameterized in order to avoid two //reflection passes as first a query needs to be constructed and after that when a database //command object has been created, parameters need to be provided to them. var sqlParameters = new Dictionary(); const string insertIntoValuesTemplate = "INSERT INTO {0} ({1}) SELECT {2};"; var columns = string.Empty; var values = new List(); if(parameters.Any()) { //Type and property information are the same for all of the objects. //The following assumes the property names will be retrieved in the same //order as is the index iteration done. var onlyOnceRow = new List(); var properties = parameters.First().GetType().GetProperties(); columns = string.Join(",", nameMap == null ? properties.Select(pn => string.Format("{0}{1}{2}", startEscapeIndicator, pn.Name, endEscapeIndicator)) : properties.Select(pn => string.Format("{0}{1}{2}", startEscapeIndicator, (nameMap.TryGetValue(pn.Name, out var pnName) ? pnName : pn.Name), endEscapeIndicator))); if (onlyOnceColumns != null && onlyOnceColumns.Any()) { var onlyOnceProperties = properties.Where(pn => onlyOnceColumns.Contains(pn.Name)).Select(pn => pn).ToArray(); var onlyOnceData = parameters.First(); for(int i = 0; i < onlyOnceProperties.Length; ++i) { var currentProperty = onlyOnceProperties[i]; var parameterValue = currentProperty.GetValue(onlyOnceData, null); if(useSqlParams) { var parameterName = string.Format("@{0}", (nameMap.TryGetValue(onlyOnceProperties[i].Name, out var parameter) ? parameter : onlyOnceProperties[i].Name)); onlyOnceRow.Add(parameterName); sqlParameters.Add(parameterName, parameterValue); } else { onlyOnceRow.Add(string.Format(adoNetFormatProvider, "{0}", parameterValue)); } } } var dataRows = new List(); var multiProperties = onlyOnceColumns == null ? properties : properties.Where(pn => !onlyOnceColumns.Contains(pn.Name)).Select(pn => pn).ToArray(); int parameterCount = 0; foreach(var row in parameters) { for(int i = 0; i < multiProperties.Length; ++i) { var currentProperty = multiProperties[i]; var parameterValue = currentProperty.GetValue(row, null); if(useSqlParams) { var parameterName = string.Format(indexedParameterTemplate, parameterCount); dataRows.Add(parameterName); sqlParameters.Add(parameterName, parameterValue); ++parameterCount; } else { dataRows.Add(string.Format(adoNetFormatProvider, "{0}", parameterValue)); } } values.Add(string.Format("{0}", string.Join(",", onlyOnceRow.Concat(dataRows)))); dataRows.Clear(); } } var query = string.Format(insertIntoValuesTemplate, tableName, columns, string.Join(storageConsts.UnionAllSelectTemplate, values)); return storage.ExecuteAsync(query, command => { if (useSqlParams) { foreach (var sp in sqlParameters) { var p = command.CreateParameter(); p.ParameterName = sp.Key; p.Value = sp.Value ?? DBNull.Value; p.Direction = ParameterDirection.Input; command.Parameters.Add(p); } } }, cancellationToken: cancellationToken); } /// /// A simplified version of /// /// /// /// /// /// /// public static Task> ReadAsync(this IRelationalStorage storage, string query, Func selector, Action parameterProvider) { return storage.ReadAsync(query, parameterProvider, (record, i, cancellationToken) => Task.FromResult(selector(record))); } /// /// Uses with . /// /// The type of the result. /// The storage to use. /// Executes a given statement. Especially intended to use with SELECT statement, but works with other queries too. /// Adds parameters to the query. Parameter names must match those defined in the query. /// The cancellation token. Defaults to . /// A list of objects as a result of the . /// This uses reflection to read results and match the parameters. /// /// //This struct holds the return value in this example. /// public struct Information /// { /// public string TABLE_CATALOG { get; set; } /// public string TABLE_NAME { get; set; } /// } /// /// //Here reflection () /// is used to match parameter names as well as to read back the results (). /// var query = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tname;"; /// IEnumerable<Information> informationData = await db.ReadAsync<Information>(query, new { tname = 200000 }); /// /// public static Task> ReadAsync(this IRelationalStorage storage, string query, object parameters, CancellationToken cancellationToken = default) { return storage.ReadAsync(query, command => { if (parameters != null) { command.ReflectionParameterProvider(parameters); } }, (selector, resultSetCount, token) => Task.FromResult(selector.ReflectionSelector()), cancellationToken: cancellationToken); } /// /// Uses with DbExtensions.ReflectionParameterProvider. /// /// The type of the result. /// The storage to use. /// Executes a given statement. Especially intended to use with SELECT statement, but works with other queries too. /// The cancellation token. Defaults to . /// A list of objects as a result of the . public static Task> ReadAsync(this IRelationalStorage storage, string query, CancellationToken cancellationToken = default) { return ReadAsync(storage, query, null, cancellationToken); } /// /// Uses with . /// /// The storage to use. /// Executes a given statement. Especially intended to use with INSERT, UPDATE, DELETE or DDL queries. /// Adds parameters to the query. Parameter names must match those defined in the query. /// The cancellation token. Defaults to . /// Affected rows count. /// This uses reflection to provide parameters to an execute /// query that reads only affected rows count if available. /// /// //Here reflection () /// is used to match parameter names as well as to read back the results (). /// var query = "IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tname) CREATE TABLE Test(Id INT PRIMARY KEY IDENTITY(1, 1) NOT NULL);" /// await db.ExecuteAsync(query, new { tname = "test_table" }); /// /// public static Task ExecuteAsync(this IRelationalStorage storage, string query, object parameters, CancellationToken cancellationToken = default) { return storage.ExecuteAsync(query, command => { if (parameters != null) { command.ReflectionParameterProvider(parameters); } }, cancellationToken: cancellationToken); } /// /// Uses with . /// /// The storage to use. /// Executes a given statement. Especially intended to use with INSERT, UPDATE, DELETE or DDL queries. /// The cancellation token. Defaults to . /// Affected rows count. public static Task ExecuteAsync(this IRelationalStorage storage, string query, CancellationToken cancellationToken = default) { return ExecuteAsync(storage, query, null, cancellationToken); } /// /// Returns a native implementation of for those providers /// which support it. Otherwise returns a chunked read using . /// /// The reader from which to return the stream. /// The ordinal column for which to return the stream. /// The storage that gives the invariant. /// public static Stream GetStream(this DbDataReader reader, int ordinal, IRelationalStorage storage) { if(storage.SupportsStreamNatively()) { return reader.GetStream(ordinal); } return new OrleansRelationalDownloadStream(reader, ordinal); } } } #nullable restore ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/AzureBasedMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Net; using System.Text; using System.Threading.Tasks; using Azure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.AzureUtils; using Orleans.Clustering.AzureStorage; using Orleans.Clustering.AzureStorage.Utilities; using Orleans.Configuration; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Orleans.Runtime.MembershipService { internal partial class AzureBasedMembershipTable : IMembershipTable { private readonly ILogger logger; private readonly ILoggerFactory loggerFactory; private OrleansSiloInstanceManager tableManager; private readonly AzureStorageClusteringOptions options; private readonly string clusterId; public AzureBasedMembershipTable( ILoggerFactory loggerFactory, IOptions clusteringOptions, IOptions clusterOptions) { this.loggerFactory = loggerFactory; this.logger = loggerFactory.CreateLogger(); this.options = clusteringOptions.Value; this.clusterId = clusterOptions.Value.ClusterId; } public async Task InitializeMembershipTable(bool tryInitTableVersion) { LogFormatter.SetExceptionDecoder(typeof(RequestFailedException), AzureTableUtils.PrintStorageException); this.tableManager = await OrleansSiloInstanceManager.GetManager( this.clusterId, this.loggerFactory, this.options); // even if I am not the one who created the table, // try to insert an initial table version if it is not already there, // so we always have a first table version row, before this silo starts working. if (tryInitTableVersion) { // ignore return value, since we don't care if I inserted it or not, as long as it is in there. bool created = await tableManager.TryCreateTableVersionEntryAsync(); if (created) LogInformationCreatedNewTableVersionRow(); } } public Task DeleteMembershipTableEntries(string clusterId) { return tableManager.DeleteTableEntries(clusterId); } public Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { return tableManager.CleanupDefunctSiloEntries(beforeDate); } public async Task ReadRow(SiloAddress key) { try { var entries = await tableManager.FindSiloEntryAndTableVersionRow(key); MembershipTableData data = Convert(entries); LogDebugReadMyEntry(key, data); return data; } catch (Exception exc) { LogWarningIntermediateErrorReadingSiloEntry(exc, key, tableManager.TableName); throw; } } public async Task ReadAll() { try { var entries = await tableManager.FindAllSiloEntries(); MembershipTableData data = Convert(entries); LogTraceReadAllTable(data); return data; } catch (Exception exc) { LogWarningIntermediateErrorReadingAllSiloEntries(exc, tableManager.TableName); throw; } } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { try { LogDebugInsertRow(entry, tableVersion); var tableEntry = Convert(entry, tableManager.DeploymentId); var versionEntry = tableManager.CreateTableVersionEntry(tableVersion.Version); bool result = await tableManager.InsertSiloEntryConditionally( tableEntry, versionEntry, tableVersion.VersionEtag); if (result == false) LogWarningTableContention(entry, tableVersion); return result; } catch (Exception exc) { LogWarningInsertingMembershipEntry(exc, entry, tableVersion is null ? "null" : tableVersion.ToString(), tableManager.TableName); throw; } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { try { LogDebugUpdateRow(entry, etag, tableVersion); var siloEntry = Convert(entry, tableManager.DeploymentId); var versionEntry = tableManager.CreateTableVersionEntry(tableVersion.Version); bool result = await tableManager.UpdateSiloEntryConditionally(siloEntry, etag, versionEntry, tableVersion.VersionEtag); if (result == false) LogWarningTableContentionEtag(entry, etag, tableVersion); return result; } catch (Exception exc) { LogWarningUpdatingMembershipEntry(exc, entry, tableVersion is null ? "null" : tableVersion.ToString(), tableManager.TableName); throw; } } public async Task UpdateIAmAlive(MembershipEntry entry) { try { LogDebugMergeEntry(entry); var siloEntry = ConvertPartial(entry, tableManager.DeploymentId); await tableManager.MergeTableEntryAsync(siloEntry); } catch (Exception exc) { LogWarningUpdatingMembershipEntry(exc, entry, tableManager.TableName); throw; } } private MembershipTableData Convert(List<(SiloInstanceTableEntry Entity, string ETag)> entries) { try { var memEntries = new List>(); TableVersion tableVersion = null; foreach (var tuple in entries) { var tableEntry = tuple.Entity; if (tableEntry.RowKey.Equals(SiloInstanceTableEntry.TABLE_VERSION_ROW)) { tableVersion = new TableVersion(int.Parse(tableEntry.MembershipVersion), tuple.ETag); } else { try { MembershipEntry membershipEntry = Parse(tableEntry); memEntries.Add(new Tuple(membershipEntry, tuple.ETag)); } catch (Exception exc) { LogErrorParsingMembershipTableDataIgnoring(exc, tableEntry); } } } var data = new MembershipTableData(memEntries, tableVersion); return data; } catch (Exception exc) { LogErrorParsingMembershipTableData(exc, new(entries)); throw; } } private static MembershipEntry Parse(SiloInstanceTableEntry tableEntry) { var parse = new MembershipEntry { HostName = tableEntry.HostName, Status = (SiloStatus)Enum.Parse(typeof(SiloStatus), tableEntry.Status) }; if (!string.IsNullOrEmpty(tableEntry.ProxyPort)) parse.ProxyPort = int.Parse(tableEntry.ProxyPort); int port = 0; if (!string.IsNullOrEmpty(tableEntry.Port)) int.TryParse(tableEntry.Port, out port); int gen = 0; if (!string.IsNullOrEmpty(tableEntry.Generation)) int.TryParse(tableEntry.Generation, out gen); parse.SiloAddress = SiloAddress.New(IPAddress.Parse(tableEntry.Address), port, gen); parse.RoleName = tableEntry.RoleName; if (!string.IsNullOrEmpty(tableEntry.SiloName)) { parse.SiloName = tableEntry.SiloName; } else if (!string.IsNullOrEmpty(tableEntry.InstanceName)) { // this is for backward compatability: in a mixed cluster of old and new version, // some entries will have the old InstanceName column. parse.SiloName = tableEntry.InstanceName; } if (!string.IsNullOrEmpty(tableEntry.UpdateZone)) parse.UpdateZone = int.Parse(tableEntry.UpdateZone); if (!string.IsNullOrEmpty(tableEntry.FaultZone)) parse.FaultZone = int.Parse(tableEntry.FaultZone); parse.StartTime = !string.IsNullOrEmpty(tableEntry.StartTime) ? LogFormatter.ParseDate(tableEntry.StartTime) : default; parse.IAmAliveTime = !string.IsNullOrEmpty(tableEntry.IAmAliveTime) ? LogFormatter.ParseDate(tableEntry.IAmAliveTime) : default; var suspectingSilos = new List(); var suspectingTimes = new List(); if (!string.IsNullOrEmpty(tableEntry.SuspectingSilos)) { string[] silos = tableEntry.SuspectingSilos.Split('|'); foreach (string silo in silos) { suspectingSilos.Add(SiloAddress.FromParsableString(silo)); } } if (!string.IsNullOrEmpty(tableEntry.SuspectingTimes)) { string[] times = tableEntry.SuspectingTimes.Split('|'); foreach (string time in times) suspectingTimes.Add(LogFormatter.ParseDate(time)); } if (suspectingSilos.Count != suspectingTimes.Count) throw new OrleansException(string.Format("SuspectingSilos.Length of {0} as read from Azure table is not equal to SuspectingTimes.Length of {1}", suspectingSilos.Count, suspectingTimes.Count)); for (int i = 0; i < suspectingSilos.Count; i++) parse.AddSuspector(suspectingSilos[i], suspectingTimes[i]); return parse; } private static SiloInstanceTableEntry Convert(MembershipEntry memEntry, string deploymentId) { var tableEntry = new SiloInstanceTableEntry { DeploymentId = deploymentId, Address = memEntry.SiloAddress.Endpoint.Address.ToString(), Port = memEntry.SiloAddress.Endpoint.Port.ToString(CultureInfo.InvariantCulture), Generation = memEntry.SiloAddress.Generation.ToString(CultureInfo.InvariantCulture), HostName = memEntry.HostName, Status = memEntry.Status.ToString(), ProxyPort = memEntry.ProxyPort.ToString(CultureInfo.InvariantCulture), RoleName = memEntry.RoleName, SiloName = memEntry.SiloName, // this is for backward compatability: in a mixed cluster of old and new version, // we need to populate both columns. InstanceName = memEntry.SiloName, UpdateZone = memEntry.UpdateZone.ToString(CultureInfo.InvariantCulture), FaultZone = memEntry.FaultZone.ToString(CultureInfo.InvariantCulture), StartTime = LogFormatter.PrintDate(memEntry.StartTime), IAmAliveTime = LogFormatter.PrintDate(memEntry.IAmAliveTime) }; if (memEntry.SuspectTimes != null) { var siloList = new StringBuilder(); var timeList = new StringBuilder(); bool first = true; foreach (var tuple in memEntry.SuspectTimes) { if (!first) { siloList.Append('|'); timeList.Append('|'); } siloList.Append(tuple.Item1.ToParsableString()); timeList.Append(LogFormatter.PrintDate(tuple.Item2)); first = false; } tableEntry.SuspectingSilos = siloList.ToString(); tableEntry.SuspectingTimes = timeList.ToString(); } else { tableEntry.SuspectingSilos = string.Empty; tableEntry.SuspectingTimes = string.Empty; } tableEntry.PartitionKey = deploymentId; tableEntry.RowKey = SiloInstanceTableEntry.ConstructRowKey(memEntry.SiloAddress); return tableEntry; } private static SiloInstanceTableEntry ConvertPartial(MembershipEntry memEntry, string deploymentId) { return new SiloInstanceTableEntry { DeploymentId = deploymentId, IAmAliveTime = LogFormatter.PrintDate(memEntry.IAmAliveTime), PartitionKey = deploymentId, RowKey = SiloInstanceTableEntry.ConstructRowKey(memEntry.SiloAddress) }; } private readonly struct UtilsEnumerableToStringLogValue(IEnumerable<(SiloInstanceTableEntry Entity, string ETag)> entries) { public override string ToString() => Utils.EnumerableToString(entries, tuple => tuple.Entity.ToString()); } [LoggerMessage( Level = LogLevel.Information, Message = "Created new table version row." )] private partial void LogInformationCreatedNewTableVersionRow(); [LoggerMessage( Level = LogLevel.Debug, Message = "Read my entry {SiloAddress} Table=\n{Data}" )] private partial void LogDebugReadMyEntry(SiloAddress siloAddress, MembershipTableData data); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error reading silo entry for key {SiloAddress} from the table {TableName}." )] private partial void LogWarningIntermediateErrorReadingSiloEntry(Exception exception, SiloAddress siloAddress, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "ReadAll Table={Data}" )] private partial void LogTraceReadAllTable(MembershipTableData data); [LoggerMessage( Level = LogLevel.Warning, Message = "Intermediate error reading all silo entries {TableName}." )] private partial void LogWarningIntermediateErrorReadingAllSiloEntries(Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "InsertRow entry = {Data}, table version = {TableVersion}" )] private partial void LogDebugInsertRow(MembershipEntry data, TableVersion tableVersion); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_22, Level = LogLevel.Warning, Message = "Insert failed due to contention on the table. Will retry. Entry {Data}, table version = {TableVersion}" )] private partial void LogWarningTableContention(MembershipEntry data, TableVersion tableVersion); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_23, Level = LogLevel.Warning, Message = "Intermediate error inserting entry {Data} tableVersion {TableVersion} to the table {TableName}" )] private partial void LogWarningInsertingMembershipEntry(Exception ex, MembershipEntry data, string tableVersion, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "UpdateRow entry = {Data}, etag = {ETag}, table version = {TableVersion}" )] private partial void LogDebugUpdateRow(MembershipEntry data, string eTag, TableVersion tableVersion); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_24, Level = LogLevel.Warning, Message = "Update failed due to contention on the table. Will retry. Entry {Data}, eTag {ETag}, table version = {TableVersion}" )] private partial void LogWarningTableContentionEtag(MembershipEntry data, string eTag, TableVersion tableVersion); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_25, Level = LogLevel.Warning, Message = "Intermediate error updating entry {Data} tableVersion {TableVersion} to the table {TableName}" )] private partial void LogWarningUpdatingMembershipEntry(Exception ex, MembershipEntry data, string tableVersion, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "Merge entry = {Data}" )] private partial void LogDebugMergeEntry(MembershipEntry data); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_26, Level = LogLevel.Warning, Message = "Intermediate error updating IAmAlive field for entry {Data} to the table {TableName}." )] private partial void LogWarningUpdatingMembershipEntry(Exception ex, MembershipEntry data, string tableName); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_61, Level = LogLevel.Error, Message = "Intermediate error parsing SiloInstanceTableEntry to MembershipTableData: {Data}. Ignoring this entry." )] private partial void LogErrorParsingMembershipTableDataIgnoring(Exception ex, SiloInstanceTableEntry data); [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_60, Level = LogLevel.Error, Message = "Intermediate error parsing SiloInstanceTableEntry to MembershipTableData: {Data}." )] private partial void LogErrorParsingMembershipTableData(Exception ex, UtilsEnumerableToStringLogValue data); } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/AzureGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Clustering.AzureStorage; using Orleans.Configuration; using Orleans.Messaging; namespace Orleans.AzureUtils { internal class AzureGatewayListProvider : IGatewayListProvider { private OrleansSiloInstanceManager siloInstanceManager; private readonly string clusterId; private readonly AzureStorageGatewayOptions options; private readonly ILoggerFactory loggerFactory; public AzureGatewayListProvider(ILoggerFactory loggerFactory, IOptions options, IOptions clusterOptions, IOptions gatewayOptions) { this.loggerFactory = loggerFactory; this.clusterId = clusterOptions.Value.ClusterId; this.MaxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; this.options = options.Value; } public async Task InitializeGatewayListProvider() { this.siloInstanceManager = await OrleansSiloInstanceManager.GetManager( this.clusterId, this.loggerFactory, this.options); } // no caching public Task> GetGateways() { // FindAllGatewayProxyEndpoints already returns a deep copied List. return this.siloInstanceManager.FindAllGatewayProxyEndpoints(); } public TimeSpan MaxStaleness { get; } public bool IsUpdatable => true; } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/AzureTableClusteringExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.AzureUtils; using Orleans.Clustering.AzureStorage; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Runtime.MembershipService; namespace Orleans.Hosting { public static class AzureTableClusteringExtensions { /// /// Configures the silo to use Azure Storage for clustering. /// /// /// The silo builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseAzureStorageClustering( this ISiloBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton() .ConfigureFormatter(); }); } /// /// Configures the silo to use Azure Storage for clustering. /// /// /// The silo builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseAzureStorageClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddTransient(sp => new AzureStorageClusteringOptionsValidator(sp.GetRequiredService>().Get(Options.DefaultName), Options.DefaultName)); services.AddSingleton() .ConfigureFormatter(); }); } /// /// Configures the client to use Azure Storage for clustering. /// /// /// The client builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseAzureStorageClustering( this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton() .ConfigureFormatter(); }); } /// /// Configures the client to use Azure Storage for clustering. /// /// /// The client builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseAzureStorageClustering( this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddTransient(sp => new AzureStorageGatewayOptionsValidator(sp.GetRequiredService>().Get(Options.DefaultName), Options.DefaultName)); services.AddSingleton() .ConfigureFormatter(); }); } } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/AzureTableStorageClusteringProviderBuilder.cs ================================================ using System; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Clustering.AzureStorage; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AzureTableStorage", "Clustering", "Silo", typeof(AzureTableStorageClusteringProviderBuilder))] [assembly: RegisterProvider("AzureTableStorage", "Clustering", "Client", typeof(AzureTableStorageClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureTableStorageClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAzureStorageClustering((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var tableName = configurationSection["TableName"]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.TableServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new TableServiceClient(uri); } else { options.TableServiceClient = new TableServiceClient(connectionString); } } } })); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAzureStorageClustering((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var tableName = configurationSection["TableName"]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.TableServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new TableServiceClient(uri); } else { options.TableServiceClient = new TableServiceClient(connectionString); } } } })); } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/Options/AzureStorageClusteringOptions.cs ================================================ namespace Orleans.Clustering.AzureStorage; /// /// Specify options used for AzureTableBasedMembership /// public class AzureStorageClusteringOptions : AzureStorageOperationOptions { public override string TableName { get; set; } = DEFAULT_TABLE_NAME; public const string DEFAULT_TABLE_NAME = "OrleansSiloInstances"; } /// /// Configuration validator for . /// public class AzureStorageClusteringOptionsValidator : AzureStorageOperationOptionsValidator { /// /// Initializes a new instance of the class. /// /// The option to be validated. /// The option name to be validated. public AzureStorageClusteringOptionsValidator(AzureStorageClusteringOptions options, string name) : base(options, name) { } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/Options/AzureStorageGatewayOptions.cs ================================================ namespace Orleans.Clustering.AzureStorage; public class AzureStorageGatewayOptions : AzureStorageOperationOptions { public override string TableName { get; set; } = AzureStorageClusteringOptions.DEFAULT_TABLE_NAME; } /// /// Configuration validator for . /// public class AzureStorageGatewayOptionsValidator : AzureStorageOperationOptionsValidator { /// /// Initializes a new instance of the class. /// /// The option to be validated. /// The option name to be validated. public AzureStorageGatewayOptionsValidator(AzureStorageGatewayOptions options, string name) : base(options, name) { } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/Orleans.Clustering.AzureStorage.csproj ================================================ Microsoft.Orleans.Clustering.AzureStorage Microsoft Orleans Azure Table Storage Clustering Provider Microsoft Orleans clustering provider backed by Azure Table Storage $(PackageTags) Azure Table Storage $(DefaultTargetFrameworks) Orleans.Clustering.AzureStorage Orleans.Clustering.AzureStorage true $(DefineConstants);ORLEANS_CLUSTERING README.md ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/OrleansSiloInstanceManager.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Extensions.Logging; using Orleans.Clustering.AzureStorage; using Orleans.Clustering.AzureStorage.Utilities; using Orleans.Runtime; namespace Orleans.AzureUtils { internal partial class OrleansSiloInstanceManager { public string TableName { get; } private const string INSTANCE_STATUS_CREATED = nameof(SiloStatus.Created); //"Created"; private const string INSTANCE_STATUS_ACTIVE = nameof(SiloStatus.Active); //"Active"; private const string INSTANCE_STATUS_DEAD = nameof(SiloStatus.Dead); //"Dead"; private readonly AzureTableDataManager storage; private readonly ILogger logger; private readonly AzureStoragePolicyOptions storagePolicyOptions; public string DeploymentId { get; private set; } private OrleansSiloInstanceManager( string clusterId, ILoggerFactory loggerFactory, AzureStorageOperationOptions options) { DeploymentId = clusterId; TableName = options.TableName; logger = loggerFactory.CreateLogger(); storage = new AzureTableDataManager( options, loggerFactory.CreateLogger>()); this.storagePolicyOptions = options.StoragePolicyOptions; } public static async Task GetManager( string clusterId, ILoggerFactory loggerFactory, AzureStorageOperationOptions options) { var instance = new OrleansSiloInstanceManager(clusterId, loggerFactory, options); try { await instance.storage.InitTableAsync(); } catch (Exception ex) { instance.LogErrorConnectingToAzureTable(ex, instance.storage.TableName); throw; } return instance; } public SiloInstanceTableEntry CreateTableVersionEntry(int tableVersion) { return new SiloInstanceTableEntry { DeploymentId = DeploymentId, PartitionKey = DeploymentId, RowKey = SiloInstanceTableEntry.TABLE_VERSION_ROW, MembershipVersion = tableVersion.ToString(CultureInfo.InvariantCulture) }; } public void RegisterSiloInstance(SiloInstanceTableEntry entry) { entry.Status = INSTANCE_STATUS_CREATED; LogRegisterSiloInstance(entry); Task.WaitAll(new Task[] { storage.UpsertTableEntryAsync(entry) }); } public Task UnregisterSiloInstance(SiloInstanceTableEntry entry) { entry.Status = INSTANCE_STATUS_DEAD; LogUnregisterSiloInstance(entry); return storage.UpsertTableEntryAsync(entry); } public Task ActivateSiloInstance(SiloInstanceTableEntry entry) { LogActivateSiloInstance(entry); entry.Status = INSTANCE_STATUS_ACTIVE; return storage.UpsertTableEntryAsync(entry); } /// /// Represent a silo instance entry in the gateway URI format. /// /// The input silo instance /// private static Uri ConvertToGatewayUri(SiloInstanceTableEntry gateway) { int proxyPort = 0; if (!string.IsNullOrEmpty(gateway.ProxyPort)) int.TryParse(gateway.ProxyPort, out proxyPort); int gen = 0; if (!string.IsNullOrEmpty(gateway.Generation)) int.TryParse(gateway.Generation, out gen); SiloAddress address = SiloAddress.New(IPAddress.Parse(gateway.Address), proxyPort, gen); return address.ToGatewayUri(); } public async Task> FindAllGatewayProxyEndpoints() { LogDebugSearchingGateway(this.DeploymentId); try { const string Zero = "0"; var queryResults = await storage.ReadTableEntriesAndEtagsAsync(TableClient.CreateQueryFilter($"PartitionKey eq {DeploymentId} and Status eq {INSTANCE_STATUS_ACTIVE} and ProxyPort ne {Zero}")); var gatewaySiloInstances = queryResults.Select(entity => ConvertToGatewayUri(entity.Item1)).ToList(); LogFoundGateway(gatewaySiloInstances.Count, this.DeploymentId); return gatewaySiloInstances; }catch(Exception exc) { LogErrorSearchingGateway(exc, this.DeploymentId); throw; } } public async Task DumpSiloInstanceTable() { var queryResults = await storage.ReadAllTableEntriesForPartitionAsync(this.DeploymentId); SiloInstanceTableEntry[] entries = queryResults.Select(entry => entry.Item1).ToArray(); var sb = new StringBuilder(); sb.Append(string.Format("Deployment {0}. Silos: ", DeploymentId)); // Loop through the results, displaying information about the entity Array.Sort(entries, (e1, e2) => { if (e1 == null) return (e2 == null) ? 0 : -1; if (e2 == null) return (e1 == null) ? 0 : 1; if (e1.SiloName == null) return (e2.SiloName == null) ? 0 : -1; if (e2.SiloName == null) return (e1.SiloName == null) ? 0 : 1; return string.CompareOrdinal(e1.SiloName, e2.SiloName); }); foreach (SiloInstanceTableEntry entry in entries) { sb.AppendLine(string.Format("[IP {0}:{1}:{2}, {3}, Instance={4}, Status={5}]", entry.Address, entry.Port, entry.Generation, entry.HostName, entry.SiloName, entry.Status)); } return sb.ToString(); } internal Task MergeTableEntryAsync(SiloInstanceTableEntry data) { return storage.MergeTableEntryAsync(data, AzureTableUtils.ANY_ETAG); // we merge this without checking eTags. } internal Task<(SiloInstanceTableEntry, string)> ReadSingleTableEntryAsync(string partitionKey, string rowKey) { return storage.ReadSingleTableEntryAsync(partitionKey, rowKey); } internal async Task DeleteTableEntries(string clusterId) { if (clusterId == null) throw new ArgumentNullException(nameof(clusterId)); var entries = await storage.ReadAllTableEntriesForPartitionAsync(clusterId); await DeleteEntriesBatch(entries); return entries.Count; } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { var entriesList = (await FindAllSiloEntries()) .Where(entry => !string.Equals(SiloInstanceTableEntry.TABLE_VERSION_ROW, entry.Item1.RowKey) && entry.Item1.Status != INSTANCE_STATUS_ACTIVE && entry.Item1.Timestamp < beforeDate) .ToList(); await DeleteEntriesBatch(entriesList); } private async Task DeleteEntriesBatch(List<(SiloInstanceTableEntry, string)> entriesList) { if (entriesList.Count <= this.storagePolicyOptions.MaxBulkUpdateRows) { await storage.DeleteTableEntriesAsync(entriesList); } else { var tasks = new List(); foreach (var batch in entriesList.BatchIEnumerable(this.storagePolicyOptions.MaxBulkUpdateRows)) { tasks.Add(storage.DeleteTableEntriesAsync(batch)); } await Task.WhenAll(tasks); } } internal async Task> FindSiloEntryAndTableVersionRow(SiloAddress siloAddress) { string rowKey = SiloInstanceTableEntry.ConstructRowKey(siloAddress); var filter = TableClient.CreateQueryFilter($"(PartitionKey eq {DeploymentId}) and ((RowKey eq {rowKey}) or (RowKey eq {SiloInstanceTableEntry.TABLE_VERSION_ROW}))"); var queryResults = await storage.ReadTableEntriesAndEtagsAsync(filter); if (queryResults.Count < 1 || queryResults.Count > 2) throw new KeyNotFoundException(string.Format("Could not find table version row or found too many entries. Was looking for key {0}, found = {1}", siloAddress, Utils.EnumerableToString(queryResults))); int numTableVersionRows = queryResults.Count(tuple => tuple.Item1.RowKey == SiloInstanceTableEntry.TABLE_VERSION_ROW); if (numTableVersionRows < 1) throw new KeyNotFoundException(string.Format("Did not read table version row. Read = {0}", Utils.EnumerableToString(queryResults))); if (numTableVersionRows > 1) throw new KeyNotFoundException(string.Format("Read {0} table version rows, while was expecting only 1. Read = {1}", numTableVersionRows, Utils.EnumerableToString(queryResults))); return queryResults; } internal async Task> FindAllSiloEntries() { var queryResults = await storage.ReadAllTableEntriesForPartitionAsync(this.DeploymentId); if (queryResults.Count < 1) throw new KeyNotFoundException(string.Format("Could not find enough rows in the FindAllSiloEntries call. Found = {0}", Utils.EnumerableToString(queryResults))); int numTableVersionRows = queryResults.Count(tuple => tuple.Item1.RowKey == SiloInstanceTableEntry.TABLE_VERSION_ROW); if (numTableVersionRows < 1) throw new KeyNotFoundException(string.Format("Did not find table version row. Read = {0}", Utils.EnumerableToString(queryResults))); if (numTableVersionRows > 1) throw new KeyNotFoundException(string.Format("Read {0} table version rows, while was expecting only 1. Read = {1}", numTableVersionRows, Utils.EnumerableToString(queryResults))); return queryResults; } /// /// Insert (create new) row entry /// internal async Task TryCreateTableVersionEntryAsync() { try { var versionRow = await storage.ReadSingleTableEntryAsync(DeploymentId, SiloInstanceTableEntry.TABLE_VERSION_ROW); if (versionRow.Entity != null) { return false; } SiloInstanceTableEntry entry = CreateTableVersionEntry(0); await storage.CreateTableEntryAsync(entry); return true; } catch (Exception exc) { if (!AzureTableUtils.EvaluateException(exc, out var httpStatusCode, out var restStatus)) throw; LogTraceInsertSiloEntryConditionallyFailed(httpStatusCode, restStatus); if (AzureTableUtils.IsContentionError(httpStatusCode)) return false; throw; } } /// /// Insert (create new) row entry /// /// Silo Entry to be written /// Version row to update /// Version row eTag internal async Task InsertSiloEntryConditionally(SiloInstanceTableEntry siloEntry, SiloInstanceTableEntry tableVersionEntry, string tableVersionEtag) { try { await storage.InsertTwoTableEntriesConditionallyAsync(siloEntry, tableVersionEntry, tableVersionEtag); return true; } catch (Exception exc) { if (!AzureTableUtils.EvaluateException(exc, out var httpStatusCode, out var restStatus)) throw; LogTraceInsertSiloEntryConditionallyFailed(httpStatusCode, restStatus); if (AzureTableUtils.IsContentionError(httpStatusCode)) return false; throw; } } /// /// Conditionally update the row for this entry, but only if the eTag matches with the current record in data store /// /// Silo Entry to be written /// ETag value for the entry being updated /// Version row to update /// ETag value for the version row /// internal async Task UpdateSiloEntryConditionally(SiloInstanceTableEntry siloEntry, string entryEtag, SiloInstanceTableEntry tableVersionEntry, string versionEtag) { try { await storage.UpdateTwoTableEntriesConditionallyAsync(siloEntry, entryEtag, tableVersionEntry, versionEtag); return true; } catch (Exception exc) { if (!AzureTableUtils.EvaluateException(exc, out var httpStatusCode, out var restStatus)) throw; LogTraceUpdateSiloEntryConditionallyFailed(httpStatusCode, restStatus); if (AzureTableUtils.IsContentionError(httpStatusCode)) return false; throw; } } [LoggerMessage( EventId = (int)TableStorageErrorCode.AzureTable_33, Level = LogLevel.Error, Message = "Exception trying to create or connect to the Azure table {TableName}" )] private partial void LogErrorConnectingToAzureTable(Exception exception, string tableName); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100270, Level = LogLevel.Information, Message = "Registering silo instance: {Data}" )] private partial void LogRegisterSiloInstance(SiloInstanceTableEntry data); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100271, Level = LogLevel.Information, Message = "Unregistering silo instance: {Data}" )] private partial void LogUnregisterSiloInstance(SiloInstanceTableEntry data); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100272, Level = LogLevel.Information, Message = "Activating silo instance: {Data}" )] private partial void LogActivateSiloInstance(SiloInstanceTableEntry data); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100277, Level = LogLevel.Debug, Message = "Searching for active gateway silos for deployment {DeploymentId}." )] private partial void LogDebugSearchingGateway(string deploymentId); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100278, Level = LogLevel.Information, Message = "Found {GatewaySiloCount} active Gateway Silos for deployment {DeploymentId}." )] private partial void LogFoundGateway(int gatewaySiloCount, string deploymentId); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100331, Level = LogLevel.Error, Message = "Error searching for active gateway silos for deployment {DeploymentId} " )] private partial void LogErrorSearchingGateway(Exception exception, string deploymentId); [LoggerMessage( Level = LogLevel.Trace, Message = "InsertSiloEntryConditionally failed with httpStatusCode={HttpStatusCode}, restStatus={RestStatus}" )] private partial void LogTraceInsertSiloEntryConditionallyFailed(HttpStatusCode httpStatusCode, string restStatus); [LoggerMessage( Level = LogLevel.Trace, Message = "UpdateSiloEntryConditionally failed with httpStatusCode={HttpStatusCode}, restStatus={RestStatus}" )] private partial void LogTraceUpdateSiloEntryConditionallyFailed(HttpStatusCode httpStatusCode, string restStatus); } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/README.md ================================================ # Microsoft Orleans Clustering Provider for Azure Storage ## Introduction Microsoft Orleans Clustering Provider for Azure Storage allows Orleans silos to organize themselves as a cluster using Azure Table Storage. This provider enables silos to discover each other, maintain cluster membership, and detect and handle failures. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Clustering.AzureStorage ``` ## Example - Configuring Azure Storage Clustering ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } // Implement the grain interface public class HelloGrain : Grain, IHelloGrain { public Task SayHello(string greeting) { return Task.FromResult($"Hello, {greeting}!"); } } var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder // Configure Azure Table Storage for clustering .UseAzureStorageClustering(options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; options.TableName = "OrleansClustering"; // Optional: defaults to "OrleansClustering" }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("World"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Configuring Client to Connect to Cluster ```csharp using Microsoft.Extensions.Hosting; using Orleans; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace ExampleGrains; // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } var clientBuilder = Host.CreateApplicationBuilder(args) .UseOrleansClient(clientBuilder => { clientBuilder // Configure the client to use Azure Storage for clustering .UseAzureStorageClustering(options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; options.TableName = "OrleansClustering"; // Optional: defaults to "OrleansClustering" }); }); var host = clientBuilder.Build(); await host.StartAsync(); var client = host.Services.GetRequiredService(); // Get a reference to a grain and call it var grain = client.GetGrain("user123"); var response = await grain.SayHello("World"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Clustering providers](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) - [Azure Storage provider](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/azure-storage-providers) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/SiloInstanceTableEntry.cs ================================================ using System; using System.Diagnostics; using System.Net; using System.Text; using Azure; using Azure.Data.Tables; using Orleans.Runtime; namespace Orleans.AzureUtils { internal class SiloInstanceTableEntry : ITableEntity { public string DeploymentId { get; set; } // PartitionKey public string Address { get; set; } // RowKey public string Port { get; set; } // RowKey public string Generation { get; set; } // RowKey public string HostName { get; set; } // Mandatory public string Status { get; set; } // Mandatory public string ProxyPort { get; set; } // Optional public string RoleName { get; set; } // Optional - only for Azure role public string SiloName { get; set; } public string InstanceName { get; set; } // For backward compatability we leave the old column, untill all clients update the code to new version. public string UpdateZone { get; set; } // Optional - only for Azure role public string FaultZone { get; set; } // Optional - only for Azure role public string SuspectingSilos { get; set; } // For liveness public string SuspectingTimes { get; set; } // For liveness public string StartTime { get; set; } // Time this silo was started. For diagnostics. public string IAmAliveTime { get; set; } // Time this silo updated it was alive. For diagnostics. public string MembershipVersion { get; set; } // Special version row (for serializing table updates). // We'll have a designated row with only MembershipVersion column. public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } internal const string TABLE_VERSION_ROW = "VersionRow"; // Row key for version row. internal const char Seperator = '-'; public static string ConstructRowKey(SiloAddress silo) { return string.Format("{0}-{1}-{2}", silo.Endpoint.Address, silo.Endpoint.Port, silo.Generation); } internal static SiloAddress UnpackRowKey(string rowKey) { var debugInfo = "UnpackRowKey"; try { #if DEBUG debugInfo = string.Format("UnpackRowKey: RowKey={0}", rowKey); Trace.TraceInformation(debugInfo); #endif int idx1 = rowKey.IndexOf(Seperator); int idx2 = rowKey.LastIndexOf(Seperator); #if DEBUG debugInfo = string.Format("UnpackRowKey: RowKey={0} Idx1={1} Idx2={2}", rowKey, idx1, idx2); #endif ReadOnlySpan rowKeySpan = rowKey.AsSpan(); ReadOnlySpan addressStr = rowKeySpan[..idx1]; ReadOnlySpan portStr = rowKeySpan.Slice(idx1 + 1, idx2 - idx1 - 1); ReadOnlySpan genStr = rowKeySpan[(idx2 + 1)..]; #if DEBUG debugInfo = string.Format("UnpackRowKey: RowKey={0} -> Address={1} Port={2} Generation={3}", rowKey, addressStr.ToString(), portStr.ToString(), genStr.ToString()); Trace.TraceInformation(debugInfo); #endif IPAddress address = IPAddress.Parse(addressStr); int port = int.Parse(portStr); int generation = int.Parse(genStr); return SiloAddress.New(address, port, generation); } catch (Exception exc) { throw new AggregateException("Error from " + debugInfo, exc); } } public override string ToString() { var sb = new StringBuilder(); if (RowKey.Equals(TABLE_VERSION_ROW)) { sb.Append("VersionRow [").Append(DeploymentId); sb.Append(" Deployment=").Append(DeploymentId); sb.Append(" MembershipVersion=").Append(MembershipVersion); sb.Append("]"); } else { sb.Append("OrleansSilo ["); sb.Append(" Deployment=").Append(DeploymentId); sb.Append(" LocalEndpoint=").Append(Address); sb.Append(" LocalPort=").Append(Port); sb.Append(" Generation=").Append(Generation); sb.Append(" Host=").Append(HostName); sb.Append(" Status=").Append(Status); sb.Append(" ProxyPort=").Append(ProxyPort); if (!string.IsNullOrEmpty(RoleName)) sb.Append(" RoleName=").Append(RoleName); sb.Append(" SiloName=").Append(SiloName); sb.Append(" UpgradeZone=").Append(UpdateZone); sb.Append(" FaultZone=").Append(FaultZone); if (!string.IsNullOrEmpty(SuspectingSilos)) sb.Append(" SuspectingSilos=").Append(SuspectingSilos); if (!string.IsNullOrEmpty(SuspectingTimes)) sb.Append(" SuspectingTimes=").Append(SuspectingTimes); sb.Append(" StartTime=").Append(StartTime); sb.Append(" IAmAliveTime=").Append(IAmAliveTime); sb.Append("]"); } return sb.ToString(); } } } ================================================ FILE: src/Azure/Orleans.Clustering.AzureStorage/Utilities/TableStorageErrorCode.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Orleans.Clustering.AzureStorage.Utilities { [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum TableStorageErrorCode { Runtime = 100000, AzureTableBase = Runtime + 800, AzureTable_20 = AzureTableBase + 20, AzureTable_21 = AzureTableBase + 21, AzureTable_22 = AzureTableBase + 22, AzureTable_23 = AzureTableBase + 23, AzureTable_24 = AzureTableBase + 24, AzureTable_25 = AzureTableBase + 25, AzureTable_26 = AzureTableBase + 26, AzureTable_32 = AzureTableBase + 32, AzureTable_33 = AzureTableBase + 33, AzureTable_60 = AzureTableBase + 60, AzureTable_61 = AzureTableBase + 61, AzureTable_65 = AzureTableBase + 65, AzureTable_66 = AzureTableBase + 66, } } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/CosmosClusteringProviderBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Clustering.Cosmos; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AzureCosmosDB", "Clustering", "Silo", typeof(CosmosClusteringProviderBuilder))] [assembly: RegisterProvider("AzureCosmosDB", "Clustering", "Client", typeof(CosmosClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class CosmosClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string? name, IConfigurationSection configurationSection) => builder.UseCosmosClustering(optionsBuilder => optionsBuilder.Configure((options, services) => { var databaseName = configurationSection[nameof(options.DatabaseName)]; if (!string.IsNullOrEmpty(databaseName)) { options.DatabaseName = databaseName; } var containerName = configurationSection[nameof(options.ContainerName)]; if (!string.IsNullOrEmpty(containerName)) { options.ContainerName = containerName; } if (bool.TryParse(configurationSection[nameof(options.IsResourceCreationEnabled)], out var irce)) { options.IsResourceCreationEnabled = irce; } if(int.TryParse(configurationSection[nameof(options.DatabaseThroughput)], out var dt)) { options.DatabaseThroughput = dt; } if(bool.TryParse(configurationSection[nameof(options.CleanResourcesOnInitialization)], out var croi)) { options.CleanResourcesOnInitialization = croi; } var serviceKey = configurationSection["ServiceKey"]; if(!string.IsNullOrEmpty(serviceKey)) { options.ConfigureCosmosClient(sp=> new ValueTask(sp.GetRequiredKeyedService(serviceKey))); } else { var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigureCosmosClient(connectionString); } } })); public void Configure(IClientBuilder builder, string? name, IConfigurationSection configurationSection) => builder.UseCosmosGatewayListProvider(optionsBuilder => optionsBuilder.Configure((options, services) => { var databaseName = configurationSection[nameof(options.DatabaseName)]; if (!string.IsNullOrEmpty(databaseName)) { options.DatabaseName = databaseName; } var containerName = configurationSection[nameof(options.ContainerName)]; if (!string.IsNullOrEmpty(containerName)) { options.ContainerName = containerName; } if (bool.TryParse(configurationSection[nameof(options.IsResourceCreationEnabled)], out var irce)) { options.IsResourceCreationEnabled = irce; } if (int.TryParse(configurationSection[nameof(options.DatabaseThroughput)], out var dt)) { options.DatabaseThroughput = dt; } if (bool.TryParse(configurationSection[nameof(options.CleanResourcesOnInitialization)], out var croi)) { options.CleanResourcesOnInitialization = croi; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { options.ConfigureCosmosClient(sp => new ValueTask(sp.GetRequiredKeyedService(serviceKey))); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigureCosmosClient(connectionString); } } })); } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/HostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Messaging; using Orleans.Clustering.Cosmos; namespace Orleans.Hosting; /// /// Extension methods for configuring Azure Cosmos DB clustering. /// public static class HostingExtensions { /// /// Adds clustering backed by Azure Cosmos DB. /// /// The silo builder. /// The delegate used to configure the provider. /// The provided . public static ISiloBuilder UseCosmosClustering( this ISiloBuilder builder, Action configureOptions) { builder.Services.UseCosmosClustering(configureOptions); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The silo builder. /// The delegate used to configure the provider. /// The provided . public static ISiloBuilder UseCosmosClustering( this ISiloBuilder builder, Action> configureOptions) { builder.Services.UseCosmosClustering(configureOptions); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The silo builder. /// The provided . public static ISiloBuilder UseCosmosClustering(this ISiloBuilder builder) { builder.Services.AddOptions(); builder.Services.AddSingleton(); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The client builder. /// The delegate used to configure the provider. /// The provided . public static IClientBuilder UseCosmosGatewayListProvider( this IClientBuilder builder, Action configureOptions) { builder.Services.UseCosmosGatewayListProvider(configureOptions); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The client builder. /// The provided . public static IClientBuilder UseCosmosGatewayListProvider(this IClientBuilder builder) { builder.Services.AddOptions(); builder.Services.AddSingleton(); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The client builder. /// The delegate used to configure the provider. /// The provided . public static IClientBuilder UseCosmosGatewayListProvider( this IClientBuilder builder, Action> configureOptions) { builder.Services.UseCosmosGatewayListProvider(configureOptions); return builder; } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The service collection. /// The delegate used to configure the provider. /// The provided . public static IServiceCollection UseCosmosClustering( this IServiceCollection services, Action configureOptions) { return services.UseCosmosClustering(ob => ob.Configure(configureOptions)); } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The service collection. /// The delegate used to configure the provider. /// The provided . public static IServiceCollection UseCosmosClustering( this IServiceCollection services, Action> configureOptions) { configureOptions?.Invoke(services.AddOptions()); return services.AddSingleton(); } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The service collection. /// The delegate used to configure the provider. /// The provided . public static IServiceCollection UseCosmosGatewayListProvider( this IServiceCollection services, Action configureOptions) { return services.UseCosmosGatewayListProvider(ob => ob.Configure(configureOptions)); } /// /// Adds clustering backed by Azure Cosmos DB. /// /// The service collection. /// The delegate used to configure the provider. /// The provided . public static IServiceCollection UseCosmosGatewayListProvider( this IServiceCollection services, Action> configureOptions) { configureOptions?.Invoke(services.AddOptions()); services.AddTransient( sp => new CosmosOptionsValidator( sp.GetRequiredService>().CurrentValue, nameof(CosmosClusteringOptions))); return services.AddSingleton(); } } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Membership/CosmosGatewayListProvider.cs ================================================ using System.Net; using Orleans.Messaging; using Orleans.Clustering.Cosmos.Models; namespace Orleans.Clustering.Cosmos; internal partial class CosmosGatewayListProvider : IGatewayListProvider { private readonly ILogger _logger; private readonly string _clusterId; private readonly IServiceProvider _serviceProvider; private readonly CosmosClusteringOptions _options; private readonly QueryRequestOptions _queryRequestOptions; private Container _container = default!; public TimeSpan MaxStaleness { get; } public bool IsUpdatable => true; public CosmosGatewayListProvider( ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptions options, IOptions clusterOptions, IOptions gatewayOptions ) { _logger = loggerFactory.CreateLogger(); _serviceProvider = serviceProvider; _clusterId = clusterOptions.Value.ClusterId; _options = options.Value; _queryRequestOptions = new QueryRequestOptions { PartitionKey = new(_clusterId) }; MaxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; } public async Task InitializeGatewayListProvider() { try { var client = await _options.CreateClient!(_serviceProvider).ConfigureAwait(false); _container = client.GetContainer(_options.DatabaseName, _options.ContainerName); } catch (Exception ex) { LogErrorInitializingGatewayListProvider(ex); throw; } } public async Task> GetGateways() { try { var query = _container .GetItemLinqQueryable(requestOptions: _queryRequestOptions) .Where(g => g.EntityType == nameof(SiloEntity) && g.Status == (int)SiloStatus.Active && g.ProxyPort.HasValue && g.ProxyPort.Value != 0) .ToFeedIterator(); var entities = new List(); do { var items = await query.ReadNextAsync(); entities.AddRange(items); } while (query.HasMoreResults); var uris = entities.Select(ConvertToGatewayUri).ToArray(); return uris; } catch (Exception ex) { LogErrorReadingGatewayListFromCosmosDb(ex); throw; } } private static Uri ConvertToGatewayUri(SiloEntity gateway) => SiloAddress.New(new IPEndPoint(IPAddress.Parse(gateway.Address), gateway.ProxyPort!.Value), gateway.Generation).ToGatewayUri(); [LoggerMessage( Level = LogLevel.Error, Message = "Error initializing Azure Cosmos DB gateway list provider" )] private partial void LogErrorInitializingGatewayListProvider(Exception ex); [LoggerMessage( Level = LogLevel.Error, Message = "Error reading gateway list from Azure Cosmos DB" )] private partial void LogErrorReadingGatewayListFromCosmosDb(Exception ex); } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Membership/CosmosMembershipTable.cs ================================================ using System.Net; using Orleans.Clustering.Cosmos.Models; namespace Orleans.Clustering.Cosmos; internal partial class CosmosMembershipTable : IMembershipTable { private const string PARTITION_KEY = "/ClusterId"; private const string CLUSTER_VERSION_ID = "ClusterVersion"; private readonly ILogger _logger; private readonly CosmosClusteringOptions _options; private readonly IServiceProvider _serviceProvider; private readonly string _clusterId; private readonly PartitionKey _partitionKey; private readonly QueryRequestOptions _queryRequestOptions; private CosmosClient _client = default!; private Container _container = default!; private SiloEntity? _self = null; public CosmosMembershipTable( ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptions options, IOptions clusterOptions) { _logger = loggerFactory.CreateLogger(); _serviceProvider = serviceProvider; _options = options.Value; _clusterId = clusterOptions.Value.ClusterId; _partitionKey = new(_clusterId); _queryRequestOptions = new() { PartitionKey = _partitionKey }; } public async Task InitializeMembershipTable(bool tryInitTableVersion) { await InitializeCosmosClient().ConfigureAwait(false); if (_options.IsResourceCreationEnabled) { if (_options.CleanResourcesOnInitialization) { await TryDeleteDatabase().ConfigureAwait(false); } await TryCreateCosmosResources().ConfigureAwait(false); } _container = _client.GetContainer(_options.DatabaseName, _options.ContainerName); ClusterVersionEntity? versionEntity = null; try { versionEntity = (await _container.ReadItemAsync(CLUSTER_VERSION_ID, _partitionKey).ConfigureAwait(false)).Resource; } catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.NotFound) { if (versionEntity is null) { versionEntity = new ClusterVersionEntity { ClusterId = _clusterId, ClusterVersion = 0, Id = CLUSTER_VERSION_ID }; var response = await _container.CreateItemAsync(versionEntity, _partitionKey).ConfigureAwait(false); if (response.StatusCode == HttpStatusCode.Created) { LogDebugCreatedNewClusterVersionEntity(); } } } } public async Task DeleteMembershipTableEntries(string clusterId) { try { var silos = await ReadSilos().ConfigureAwait(false); var batch = _container.CreateTransactionalBatch(_partitionKey); foreach (var silo in silos) { batch = batch.DeleteItem(silo.Id); } batch = batch.DeleteItem(CLUSTER_VERSION_ID); await batch.ExecuteAsync().ConfigureAwait(false); } catch (Exception ex) { LogErrorDeletingMembershipTableEntries(ex); WrappedException.CreateAndRethrow(ex); } } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { try { var silos = (await ReadSilos(SiloStatus.Dead).ConfigureAwait(false)).Where(s => s.IAmAliveTime < beforeDate).ToList(); if (silos.Count == 0) { return; } var batch = _container.CreateTransactionalBatch(_partitionKey); foreach (var silo in silos) { batch = batch.DeleteItem(silo.Id); } await batch.ExecuteAsync().ConfigureAwait(false); } catch (Exception ex) { LogErrorCleaningUpDefunctSiloEntries(ex); WrappedException.CreateAndRethrow(ex); } } public async Task ReadRow(SiloAddress key) { var id = ConstructSiloEntityId(key); try { var readClusterVersionTask = ReadClusterVersion(); var readSiloTask = _container.ReadItemAsync(id, _partitionKey); await Task.WhenAll(readClusterVersionTask, readSiloTask).ConfigureAwait(false); var clusterVersion = await readClusterVersionTask; var silo = await readSiloTask; TableVersion? version = null; if (clusterVersion is not null) { version = new TableVersion(clusterVersion.ClusterVersion, clusterVersion.ETag); } else { LogErrorClusterVersionEntityDoesNotExist(); } var memEntries = new List> { Tuple.Create(ParseEntity(silo.Resource), silo.Resource.ETag) }; return new MembershipTableData(memEntries, version); } catch (Exception exc) { LogWarningFailureReadingSiloEntry(exc, key, _clusterId); WrappedException.CreateAndRethrow(exc); throw; } } public async Task ReadAll() { try { var readClusterVersionTask = ReadClusterVersion(); var readSilosTask = ReadSilos(); await Task.WhenAll(readClusterVersionTask, readSilosTask).ConfigureAwait(false); var clusterVersion = await readClusterVersionTask; var silos = await readSilosTask; TableVersion? version = null; if (clusterVersion is not null) { version = new TableVersion(clusterVersion.ClusterVersion, clusterVersion.ETag); } else { LogErrorClusterVersionEntityDoesNotExist(); } var memEntries = new List>(); foreach (var entity in silos) { try { var membershipEntry = ParseEntity(entity); memEntries.Add(new Tuple(membershipEntry, entity.ETag)); } catch (Exception exc) { LogErrorReadingAllMembershipRecords(exc); WrappedException.CreateAndRethrow(exc); throw; } } return new MembershipTableData(memEntries, version); } catch (Exception exc) { LogWarningReadingEntries(exc, _clusterId); WrappedException.CreateAndRethrow(exc); throw; } } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { try { var siloEntity = ConvertToEntity(entry, _clusterId); var versionEntity = BuildVersionEntity(tableVersion); var response = await _container.CreateTransactionalBatch(_partitionKey) .ReplaceItem(versionEntity.Id, versionEntity, new TransactionalBatchItemRequestOptions { IfMatchEtag = tableVersion.VersionEtag }) .CreateItem(siloEntity) .ExecuteAsync().ConfigureAwait(false); return response.IsSuccessStatusCode; } catch (CosmosException exc) { if (exc.StatusCode == HttpStatusCode.PreconditionFailed) return false; WrappedException.CreateAndRethrow(exc); throw; } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { try { var siloEntity = ConvertToEntity(entry, _clusterId); siloEntity.ETag = etag; var versionEntity = BuildVersionEntity(tableVersion); var response = await _container.CreateTransactionalBatch(_partitionKey) .ReplaceItem(versionEntity.Id, versionEntity, new TransactionalBatchItemRequestOptions { IfMatchEtag = tableVersion.VersionEtag }) .ReplaceItem(siloEntity.Id, siloEntity, new TransactionalBatchItemRequestOptions { IfMatchEtag = siloEntity.ETag }) .ExecuteAsync().ConfigureAwait(false); return response.IsSuccessStatusCode; } catch (CosmosException exc) { if (exc.StatusCode == HttpStatusCode.PreconditionFailed) return false; WrappedException.CreateAndRethrow(exc); throw; } } public async Task UpdateIAmAlive(MembershipEntry entry) { var siloEntityId = ConstructSiloEntityId(entry.SiloAddress); if (_self is not { } selfRow) { var response = await _container.ReadItemAsync(siloEntityId, _partitionKey).ConfigureAwait(false); if (response.StatusCode != HttpStatusCode.OK) { LogWarningUnableToQueryEntry(new(entry)); throw new OrleansException($"Unable to query for SiloEntity {entry.ToFullString()}"); } _self = selfRow = response.Resource; } selfRow.IAmAliveTime = entry.IAmAliveTime; try { var replaceResponse = await _container.ReplaceItemAsync( selfRow, siloEntityId, _partitionKey, new ItemRequestOptions { IfMatchEtag = selfRow.ETag }).ConfigureAwait(false); _self = replaceResponse.Resource; } catch (Exception exc) { _self = null; WrappedException.CreateAndRethrow(exc); throw; } } private async Task InitializeCosmosClient() { try { _client = await _options.CreateClient!(_serviceProvider).ConfigureAwait(false); } catch (Exception ex) { LogErrorInitializingCosmosClient(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task TryDeleteDatabase() { try { await _client.GetDatabase(_options.DatabaseName).DeleteAsync().ConfigureAwait(false); } catch (CosmosException dce) when (dce.StatusCode == HttpStatusCode.NotFound) { return; } catch (Exception ex) { LogErrorDeletingCosmosDBDatabase(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task TryCreateCosmosResources() { var dbResponse = await _client.CreateDatabaseIfNotExistsAsync(_options.DatabaseName, _options.DatabaseThroughput).ConfigureAwait(false); var db = dbResponse.Database; var containerProperties = new ContainerProperties(_options.ContainerName, PARTITION_KEY); containerProperties.IndexingPolicy.IndexingMode = IndexingMode.Consistent; containerProperties.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/Address/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/Port/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/Generation/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/Hostname/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/SiloName/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/\"SuspectingSilos\"/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/\"SuspectingTimes\"/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/StartTime/*" }); containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/IAmAliveTime/*" }); const int maxRetries = 3; for (var retry = 0; retry <= maxRetries; ++retry) { var containerResponse = await db.CreateContainerIfNotExistsAsync( containerProperties, _options.ContainerThroughputProperties).ConfigureAwait(false); if (retry == maxRetries || dbResponse.StatusCode != HttpStatusCode.Created || containerResponse.StatusCode == HttpStatusCode.Created) { break; // Apparently some throttling logic returns HttpStatusCode.OK (not 429) when the collection wasn't created in a new DB. } await Task.Delay(1000); } } private async Task ReadClusterVersion() { try { var response = await _container.ReadItemAsync( CLUSTER_VERSION_ID, _partitionKey).ConfigureAwait(false); return response.StatusCode == HttpStatusCode.OK ? response.Resource : response.StatusCode == HttpStatusCode.NotFound ? null : throw new Exception($"Error reading Cluster Version entity. Status code: {response.StatusCode}"); } catch (Exception ex) { LogErrorReadingClusterVersionEntity(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task> ReadSilos(SiloStatus? status = null) { try { var query = _container .GetItemLinqQueryable(requestOptions: _queryRequestOptions) .Where(g => g.EntityType == nameof(SiloEntity)); if (status is not null) { query = query.Where(g => (SiloStatus)g.Status == status); } var iterator = query.ToFeedIterator(); var silos = new List(); do { var items = await iterator.ReadNextAsync().ConfigureAwait(false); silos.AddRange(items); } while (iterator.HasMoreResults); return silos; } catch (Exception exc) { LogErrorReadingSiloEntities(exc); WrappedException.CreateAndRethrow(exc); throw; } } private static string ConstructSiloEntityId(SiloAddress silo) => $"{silo.Endpoint.Address}-{silo.Endpoint.Port}-{silo.Generation}"; private static MembershipEntry ParseEntity(SiloEntity entity) { var entry = new MembershipEntry { HostName = entity.Hostname, Status = (SiloStatus)entity.Status }; if (entity.ProxyPort.HasValue) entry.ProxyPort = entity.ProxyPort.Value; entry.SiloAddress = SiloAddress.New(new IPEndPoint(IPAddress.Parse(entity.Address), entity.Port), entity.Generation); entry.SiloName = entity.SiloName; entry.StartTime = entity.StartTime.UtcDateTime; entry.IAmAliveTime = entity.IAmAliveTime.UtcDateTime; var suspectingSilos = new List(); var suspectingTimes = new List(); foreach (var silo in entity.SuspectingSilos) { suspectingSilos.Add(SiloAddress.FromParsableString(silo)); } foreach (var time in entity.SuspectingTimes) { suspectingTimes.Add(LogFormatter.ParseDate(time)); } if (suspectingSilos.Count != suspectingTimes.Count) { throw new OrleansException($"SuspectingSilos.Length of {suspectingSilos.Count} as read from Azure Cosmos DB is not equal to SuspectingTimes.Length of {suspectingTimes.Count}"); } for (var i = 0; i < suspectingSilos.Count; i++) { entry.AddSuspector(suspectingSilos[i], suspectingTimes[i]); } return entry; } private static SiloEntity ConvertToEntity(MembershipEntry memEntry, string clusterId) { var tableEntry = new SiloEntity { Id = ConstructSiloEntityId(memEntry.SiloAddress), ClusterId = clusterId, Address = memEntry.SiloAddress.Endpoint.Address.ToString(), Port = memEntry.SiloAddress.Endpoint.Port, Generation = memEntry.SiloAddress.Generation, Hostname = memEntry.HostName, Status = (int)memEntry.Status, ProxyPort = memEntry.ProxyPort, SiloName = memEntry.SiloName, StartTime = memEntry.StartTime, IAmAliveTime = memEntry.IAmAliveTime }; if (memEntry.SuspectTimes != null) { foreach (var tuple in memEntry.SuspectTimes) { tableEntry.SuspectingSilos.Add(tuple.Item1.ToParsableString()); tableEntry.SuspectingTimes.Add(LogFormatter.PrintDate(tuple.Item2)); } } return tableEntry; } private ClusterVersionEntity BuildVersionEntity(TableVersion tableVersion) { return new ClusterVersionEntity { ClusterId = _clusterId, ClusterVersion = tableVersion.Version, Id = CLUSTER_VERSION_ID, ETag = tableVersion.VersionEtag }; } private readonly struct MembershipEntryLogValue(MembershipEntry membershipEntry) { public override string ToString() => membershipEntry.ToFullString(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Created new Cluster Version entity." )] private partial void LogDebugCreatedNewClusterVersionEntity(); [LoggerMessage( Level = LogLevel.Error, Message = "Error deleting membership table entries." )] private partial void LogErrorDeletingMembershipTableEntries(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error cleaning up defunct silo entries." )] private partial void LogErrorCleaningUpDefunctSiloEntries(Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Failure reading silo entry {Key} for cluster {Cluster}" )] private partial void LogWarningFailureReadingSiloEntry(Exception exception, SiloAddress key, string cluster); [LoggerMessage( Level = LogLevel.Error, Message = "Initial ClusterVersionEntity entity does not exist." )] private partial void LogErrorClusterVersionEntityDoesNotExist(); [LoggerMessage( Level = LogLevel.Error, Message = "Failure reading all membership records." )] private partial void LogErrorReadingAllMembershipRecords(Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Failure reading entries for cluster {Cluster}" )] private partial void LogWarningReadingEntries(Exception exception, string cluster); [LoggerMessage( Level = LogLevel.Error, Message = "Error initializing Azure Cosmos DB Client for membership table provider." )] private partial void LogErrorInitializingCosmosClient(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error deleting Azure Cosmos DB database." )] private partial void LogErrorDeletingCosmosDBDatabase(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error reading Cluster Version entity." )] private partial void LogErrorReadingClusterVersionEntity(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error reading Silo entities." )] private partial void LogErrorReadingSiloEntities(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipBase, Level = LogLevel.Warning, Message = "Unable to query entry {Entry}" )] private partial void LogWarningUnableToQueryEntry(MembershipEntryLogValue entry); } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Models/BaseClusterEntity.cs ================================================ using Newtonsoft.Json; namespace Orleans.Clustering.Cosmos; internal abstract class BaseClusterEntity : BaseEntity { [JsonProperty(nameof(ClusterId))] [JsonPropertyName(nameof(ClusterId))] public string ClusterId { get; set; } = default!; [JsonProperty(nameof(EntityType))] [JsonPropertyName(nameof(EntityType))] public abstract string EntityType { get; } } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Models/ClusterVersionEntity.cs ================================================ using Newtonsoft.Json; namespace Orleans.Clustering.Cosmos; internal class ClusterVersionEntity : BaseClusterEntity { public override string EntityType => nameof(ClusterVersionEntity); [JsonProperty(nameof(ClusterVersion))] [JsonPropertyName(nameof(ClusterVersion))] public int ClusterVersion { get; set; } = 0; } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Models/SiloEntity.cs ================================================ using Newtonsoft.Json; namespace Orleans.Clustering.Cosmos.Models; internal class SiloEntity : BaseClusterEntity { public override string EntityType => nameof(SiloEntity); [JsonProperty(nameof(Address))] [JsonPropertyName(nameof(Address))] public string Address { get; set; } = default!; [JsonProperty(nameof(Port))] [JsonPropertyName(nameof(Port))] public int Port { get; set; } [JsonProperty(nameof(Generation))] [JsonPropertyName(nameof(Generation))] public int Generation { get; set; } [JsonProperty(nameof(Hostname))] [JsonPropertyName(nameof(Hostname))] public string Hostname { get; set; } = default!; [JsonProperty(nameof(Status))] [JsonPropertyName(nameof(Status))] public int Status { get; set; } [JsonProperty(nameof(ProxyPort))] [JsonPropertyName(nameof(ProxyPort))] public int? ProxyPort { get; set; } [JsonProperty(nameof(SiloName))] [JsonPropertyName(nameof(SiloName))] public string SiloName { get; set; } = default!; [JsonProperty(nameof(SuspectingSilos))] [JsonPropertyName(nameof(SuspectingSilos))] public List SuspectingSilos { get; set; } = new(); [JsonProperty(nameof(SuspectingTimes))] [JsonPropertyName(nameof(SuspectingTimes))] public List SuspectingTimes { get; set; } = new(); [JsonProperty(nameof(StartTime))] [JsonPropertyName(nameof(StartTime))] public DateTimeOffset StartTime { get; set; } [JsonProperty(nameof(IAmAliveTime))] [JsonPropertyName(nameof(IAmAliveTime))] public DateTimeOffset IAmAliveTime { get; set; } } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Options/CosmosClusteringOptions.cs ================================================ namespace Orleans.Clustering.Cosmos; /// /// Options for configuring Azure Cosmos DB clustering. /// public class CosmosClusteringOptions : CosmosOptions { private const string ORLEANS_CLUSTER_CONTAINER = "OrleansCluster"; /// /// Initializes a new instance. /// public CosmosClusteringOptions() { ContainerName = ORLEANS_CLUSTER_CONTAINER; } } /// /// Configuration validator for . /// public class CosmosClusteringOptionsValidator : CosmosOptionsValidator { /// /// Initializes a new instance of the class. /// /// The option to be validated. /// The option name to be validated. public CosmosClusteringOptionsValidator(CosmosClusteringOptions options, string name) : base(options, name) { } } ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/Orleans.Clustering.Cosmos.csproj ================================================ Microsoft.Orleans.Clustering.Cosmos Microsoft Orleans clustering provider for Azure Cosmos DB Microsoft Orleans clustering provider backed by Azure Cosmos DB $(PackageTags) Azure Cosmos DB $(DefaultTargetFrameworks) Orleans.Clustering.Cosmos Orleans.Clustering.Cosmos true $(DefineConstants);ORLEANS_CLUSTERING enable README.md <_Parameter1>Orleans.Cosmos.Tests ================================================ FILE: src/Azure/Orleans.Clustering.Cosmos/README.md ================================================ # Microsoft Orleans Clustering for Azure Cosmos DB ## Introduction Microsoft Orleans Clustering for Azure Cosmos DB provides cluster membership functionality for Microsoft Orleans using Azure Cosmos DB. This allows Orleans silos to coordinate and form a cluster using Azure Cosmos DB as the backing store. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Clustering.Cosmos ``` ## Example - Configuring Azure Cosmos DB Membership ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseCosmosClustering(options => { options.AccountEndpoint = "https://YOUR_COSMOS_ENDPOINT"; options.AccountKey = "YOUR_COSMOS_KEY"; options.DB = "YOUR_DATABASE_NAME"; options.CanCreateResources = true; }); }); // Run the host await builder.RunAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Configuration Guide](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/) - [Orleans Clustering](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/AzureStorageJobShard.Log.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.DurableJobs.AzureStorage; internal sealed partial class AzureStorageJobShard { [LoggerMessage( Level = LogLevel.Information, Message = "Initializing shard '{ShardId}' from Azure Storage blob" )] private static partial void LogInitializingShard(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Shard '{ShardId}' initialized successfully. Loaded {JobCount} job(s) in {ElapsedMilliseconds}ms" )] private static partial void LogShardInitialized(ILogger logger, string shardId, int jobCount, long elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Debug, Message = "Adding job '{JobId}' (Name: '{JobName}') to shard '{ShardId}' with due time {DueTime}" )] private static partial void LogAddingJob(ILogger logger, string jobId, string jobName, string shardId, DateTimeOffset dueTime); [LoggerMessage( Level = LogLevel.Debug, Message = "Removing job '{JobId}' from shard '{ShardId}'" )] private static partial void LogRemovingJob(ILogger logger, string jobId, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Retrying job '{JobId}' in shard '{ShardId}' with new due time {NewDueTime}" )] private static partial void LogRetryingJob(ILogger logger, string jobId, string shardId, DateTimeOffset newDueTime); [LoggerMessage( Level = LogLevel.Trace, Message = "Flushing batch of {OperationCount} job operation(s) to shard '{ShardId}'" )] private static partial void LogFlushingBatch(ILogger logger, int operationCount, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Batch of {OperationCount} job operation(s) written to shard '{ShardId}' in {ElapsedMilliseconds}ms. Total committed blocks: {CommittedBlockCount}" )] private static partial void LogBatchWritten(ILogger logger, int operationCount, string shardId, long elapsedMilliseconds, int committedBlockCount); [LoggerMessage( Level = LogLevel.Trace, Message = "Updating metadata for shard '{ShardId}'" )] private static partial void LogUpdatingMetadata(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Metadata updated for shard '{ShardId}'" )] private static partial void LogMetadataUpdated(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Warning, Message = "Shard '{ShardId}' has {CommittedBlockCount} committed blocks, approaching Azure Blob append limit of 50,000" )] private static partial void LogApproachingBlockLimit(ILogger logger, string shardId, int committedBlockCount); [LoggerMessage( Level = LogLevel.Warning, Message = "Large batch detected for shard '{ShardId}': {OperationCount} operations (max configured: {MaxBatchSize})" )] private static partial void LogLargeBatch(ILogger logger, string shardId, int operationCount, int maxBatchSize); [LoggerMessage( Level = LogLevel.Error, Message = "Error writing batch of {OperationCount} operation(s) to shard '{ShardId}'" )] private static partial void LogErrorWritingBatch(ILogger logger, Exception exception, int operationCount, string shardId); [LoggerMessage( Level = LogLevel.Error, Message = "Error updating metadata for shard '{ShardId}'" )] private static partial void LogErrorUpdatingMetadata(ILogger logger, Exception exception, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping storage processor for shard '{ShardId}'" )] private static partial void LogStoppingProcessor(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Storage processor stopped for shard '{ShardId}'" )] private static partial void LogProcessorStopped(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Trace, Message = "Processing storage operation queue for shard '{ShardId}'" )] private static partial void LogProcessingStorageQueue(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Waiting for additional operations to batch (current size: {CurrentSize}, min size: {MinSize}) for shard '{ShardId}'" )] private static partial void LogWaitingForBatch(ILogger logger, int currentSize, int minSize, string shardId); } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/AzureStorageJobShard.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Transactions; using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Microsoft.Extensions.Logging; using Orleans.Hosting; using Orleans.Runtime; using Orleans.Serialization.Buffers.Adaptors; namespace Orleans.DurableJobs.AzureStorage; internal sealed partial class AzureStorageJobShard : JobShard { private readonly Channel _storageOperationChannel; private readonly Task _storageProcessorTask; private readonly CancellationTokenSource _shutdownCts = new(); private readonly AzureStorageJobShardOptions _options; private readonly ILogger _logger; internal AppendBlobClient BlobClient { get; init; } internal ETag? ETag { get; private set; } internal int CommitedBlockCount { get; private set; } public AzureStorageJobShard(string id, DateTimeOffset startTime, DateTimeOffset endTime, AppendBlobClient blobClient, IDictionary? metadata, ETag? eTag, AzureStorageJobShardOptions options, ILogger logger) : base(id, startTime, endTime) { BlobClient = blobClient; ETag = eTag; Metadata = metadata; _options = options; _logger = logger; // Create unbounded channel for storage operations _storageOperationChannel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true, SingleWriter = false }); // Start the background task that processes storage operations _storageProcessorTask = ProcessStorageOperationsAsync(); } protected override async Task PersistAddJobAsync(string jobId, string jobName, DateTimeOffset dueTime, GrainId target, IReadOnlyDictionary? metadata, CancellationToken cancellationToken) { LogAddingJob(_logger, jobId, jobName, Id, dueTime); var operation = JobOperation.CreateAddOperation(jobId, jobName, dueTime, target, metadata); await EnqueueStorageOperationAsync(StorageOperation.CreateAppendOperation(operation), cancellationToken); } protected override async Task PersistRemoveJobAsync(string jobId, CancellationToken cancellationToken) { LogRemovingJob(_logger, jobId, Id); var operation = JobOperation.CreateRemoveOperation(jobId); await EnqueueStorageOperationAsync(StorageOperation.CreateAppendOperation(operation), cancellationToken); } protected override async Task PersistRetryJobAsync(string jobId, DateTimeOffset newDueTime, CancellationToken cancellationToken) { LogRetryingJob(_logger, jobId, Id, newDueTime); var operation = JobOperation.CreateRetryOperation(jobId, newDueTime); await EnqueueStorageOperationAsync(StorageOperation.CreateAppendOperation(operation), cancellationToken); } public async Task UpdateBlobMetadata(IDictionary metadata, CancellationToken cancellationToken) { LogUpdatingMetadata(_logger, Id); await EnqueueStorageOperationAsync(StorageOperation.CreateMetadataOperation(metadata), cancellationToken); } public async ValueTask InitializeAsync(CancellationToken cancellationToken) { LogInitializingShard(_logger, Id); var sw = Stopwatch.StartNew(); // Load existing blob var response = await BlobClient.DownloadAsync(cancellationToken: cancellationToken); using var stream = response.Value.Content; // Rebuild state by replaying operations var addedJobs = new Dictionary(); var deletedJobs = new HashSet(); var jobRetryCounters = new Dictionary(); await foreach (var operation in NetstringJsonSerializer.DecodeAsync(stream, JobOperationJsonContext.Default.JobOperation, cancellationToken)) { switch (operation.Type) { case JobOperation.OperationType.Add: if (!deletedJobs.Contains(operation.Id)) { addedJobs[operation.Id] = operation; } break; case JobOperation.OperationType.Remove: deletedJobs.Add(operation.Id); addedJobs.Remove(operation.Id); jobRetryCounters.Remove(operation.Id); break; case JobOperation.OperationType.Retry: if (!deletedJobs.Contains(operation.Id)) { if (!jobRetryCounters.ContainsKey(operation.Id)) { jobRetryCounters[operation.Id] = (1, operation.DueTime); } else { var entry = jobRetryCounters[operation.Id]; jobRetryCounters[operation.Id] = (entry.dequeueCount + 1, operation.DueTime); } } break; } } // Rebuild the priority queue foreach (var op in addedJobs.Values) { var retryCounter = 0; var dueTime = op.DueTime!.Value; if (jobRetryCounters.TryGetValue(op.Id, out var retryEntries)) { retryCounter = retryEntries.dequeueCount; dueTime = retryEntries.newDueTime ?? dueTime; } EnqueueJob(new DurableJob { Id = op.Id, Name = op.Name!, DueTime = dueTime, TargetGrainId = op.TargetGrainId!.Value, ShardId = Id, Metadata = op.Metadata, }, retryCounter); } ETag = response.Value.Details.ETag; sw.Stop(); LogShardInitialized(_logger, Id, addedJobs.Count, sw.ElapsedMilliseconds); } private async Task EnqueueStorageOperationAsync(StorageOperation operation, CancellationToken cancellationToken) { await _storageOperationChannel.Writer.WriteAsync(operation, cancellationToken); await operation.CompletionSource.Task; } private async Task ProcessStorageOperationsAsync() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding); var cancellationToken = _shutdownCts.Token; // TODO: AppendBlob has a limit of 50,000 blocks. Implement blob rotation when this limit is approached. var batchOperations = new List(_options.MaxBatchSize); try { while (await _storageOperationChannel.Reader.WaitToReadAsync(cancellationToken)) { // Read first operation if (!_storageOperationChannel.Reader.TryRead(out var firstOperation)) { continue; } // Handle metadata operations immediately (cannot be batched) if (firstOperation.Type is StorageOperationType.UpdateMetadata) { try { await UpdateMetadataAsync(firstOperation.Metadata!, cancellationToken); LogMetadataUpdated(_logger, Id); firstOperation.CompletionSource.TrySetResult(); } catch (Exception ex) { LogErrorUpdatingMetadata(_logger, ex, Id); firstOperation.CompletionSource?.TrySetException(ex); } continue; } // Collect job operations for batching batchOperations.Add(firstOperation); // Try to collect more operations up to the maximum batch size if (TryCollectJobOperationsForBatch(batchOperations)) { // Not enough operations to meet the minimum batch size, wait for more or timeout if (batchOperations.Count < _options.MinBatchSize) { LogWaitingForBatch(_logger, batchOperations.Count, _options.MinBatchSize, Id); } await Task.Delay(_options.BatchFlushInterval, cancellationToken); TryCollectJobOperationsForBatch(batchOperations); } // Process the batch of job operations if (batchOperations.Count > 0) { try { LogFlushingBatch(_logger, batchOperations.Count, Id); await AppendJobOperationBatchAsync(batchOperations, cancellationToken); // Mark all operations as completed foreach (var op in batchOperations) { op.CompletionSource.TrySetResult(); } } catch (Exception ex) { LogErrorWritingBatch(_logger, ex, batchOperations.Count, Id); // Mark all operations as failed foreach (var op in batchOperations) { op.CompletionSource?.TrySetException(ex); } } finally { batchOperations.Clear(); } } } } catch (OperationCanceledException) { // Ignore } finally { // Expected during shutdown - cancel all pending operations while (_storageOperationChannel.Reader.TryRead(out var operation)) { operation.CompletionSource?.TrySetCanceled(cancellationToken); } } // Local function to collect job operations for batching. Returns true if more operations can be collected. bool TryCollectJobOperationsForBatch(List batchOperations) { // Collect more jobs, up to a maximum batch size while (batchOperations.Count < _options.MaxBatchSize && _storageOperationChannel.Reader.TryPeek(out var nextOperation)) { if (nextOperation.Type is StorageOperationType.UpdateMetadata) { // Stop batching if we encounter a metadata operation return false; } _storageOperationChannel.Reader.TryRead(out var operation); Debug.Assert(operation != null); batchOperations.Add(operation!); } return batchOperations.Count != _options.MaxBatchSize; } } private async Task AppendJobOperationBatchAsync(List operations, CancellationToken cancellationToken) { var sw = Stopwatch.StartNew(); using var stream = PooledBufferStream.Rent(); try { stream.Position = 0; // TODO Remove that once PooledBufferStream fixed // Encode all job operations into a single stream foreach (var operation in operations) { NetstringJsonSerializer.Encode(operation.JobOperation!.Value, stream, JobOperationJsonContext.Default.JobOperation); } stream.Position = 0; var result = await BlobClient.AppendBlockAsync( stream, new AppendBlobAppendBlockOptions { Conditions = new AppendBlobRequestConditions { IfMatch = ETag } }, cancellationToken); ETag = result.Value.ETag; CommitedBlockCount = result.Value.BlobCommittedBlockCount; sw.Stop(); LogBatchWritten(_logger, operations.Count, Id, sw.ElapsedMilliseconds, CommitedBlockCount); // Warn if approaching the 50,000 block limit (warn at 80%) if (CommitedBlockCount > 40000) { LogApproachingBlockLimit(_logger, Id, CommitedBlockCount); } // Warn if batch is unusually large if (operations.Count > _options.MaxBatchSize * 0.8) { LogLargeBatch(_logger, Id, operations.Count, _options.MaxBatchSize); } } finally { PooledBufferStream.Return(stream); } } private async Task UpdateMetadataAsync(IDictionary metadata, CancellationToken cancellationToken) { var result = await BlobClient.SetMetadataAsync( metadata, new BlobRequestConditions { IfMatch = ETag }, cancellationToken); ETag = result.Value.ETag; Metadata = metadata; } /// /// Stops the background storage processor and waits for all pending operations to complete. /// After calling this method, no new storage operations can be enqueued. /// This method is idempotent and can be called multiple times safely. /// internal async Task StopProcessorAsync(CancellationToken cancellationToken) { LogStoppingProcessor(_logger, Id); // Complete the channel to stop accepting new operations (idempotent operation) if (_storageOperationChannel.Writer.TryComplete()) { _shutdownCts.Cancel(); } // Wait for the background processor to finish all pending operations try { await _storageProcessorTask.WaitAsync(cancellationToken); LogProcessorStopped(_logger, Id); } catch (OperationCanceledException) { // Expected during normal shutdown LogProcessorStopped(_logger, Id); } } public override async ValueTask DisposeAsync() { await StopProcessorAsync(CancellationToken.None); _shutdownCts.Dispose(); await base.DisposeAsync(); } } internal enum StorageOperationType { AppendJobOperation, UpdateMetadata } internal sealed class StorageOperation { public required StorageOperationType Type { get; init; } public JobOperation? JobOperation { get; init; } public IDictionary? Metadata { get; init; } public TaskCompletionSource CompletionSource { get; init; } = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public static StorageOperation CreateAppendOperation(JobOperation jobOperation) { return new StorageOperation { Type = StorageOperationType.AppendJobOperation, JobOperation = jobOperation }; } public static StorageOperation CreateMetadataOperation(IDictionary metadata) { return new StorageOperation { Type = StorageOperationType.UpdateMetadata, Metadata = metadata }; } } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/AzureStorageJobShardManager.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Hosting; using Orleans.Runtime; namespace Orleans.DurableJobs.AzureStorage; public sealed partial class AzureStorageJobShardManager : JobShardManager { private readonly BlobServiceClient _blobServiceClient; private readonly string _containerName; private readonly string _blobPrefix; private BlobContainerClient _client = null!; private readonly IClusterMembershipService _clusterMembership; private readonly ConcurrentDictionary _jobShardCache = new(); private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly AzureStorageJobShardOptions _options; private readonly DurableJobsOptions _durableJobsOptions; private long _shardCounter = 0; // For generating unique shard IDs private const string AdoptedCountKey = "AdoptedCount"; private const string LastAdoptedTimeKey = "LastAdoptedTime"; private const string LegacyStolenCountKey = "StolenCount"; private const string LegacyLastStolenTimeKey = "LastStolenTime"; public AzureStorageJobShardManager( SiloAddress siloAddress, BlobServiceClient client, string containerName, string blobPrefix, AzureStorageJobShardOptions options, IOptions durableJobsOptions, IClusterMembershipService clusterMembership, ILoggerFactory loggerFactory) : base(siloAddress) { _blobServiceClient = client; _containerName = containerName; _blobPrefix = blobPrefix; _clusterMembership = clusterMembership; _logger = loggerFactory.CreateLogger(); _loggerFactory = loggerFactory; _options = options; _durableJobsOptions = durableJobsOptions.Value; } public AzureStorageJobShardManager( ILocalSiloDetails localSiloDetails, IOptions options, IOptions durableJobsOptions, IClusterMembershipService clusterMembership, ILoggerFactory loggerFactory) : this(localSiloDetails.SiloAddress, options.Value.BlobServiceClient, options.Value.ContainerName, localSiloDetails.ClusterId, options.Value, durableJobsOptions, clusterMembership, loggerFactory) { } public override async Task> AssignJobShardsAsync(DateTimeOffset maxShardStartTime, CancellationToken cancellationToken) { await InitializeIfNeeded(cancellationToken); LogAssigningShards(_logger, SiloAddress, maxShardStartTime, _containerName); var result = new List(); await foreach (var blob in _client.GetBlobsAsync(traits: BlobTraits.Metadata, states: BlobStates.None, cancellationToken: cancellationToken, prefix: _blobPrefix)) { // Get the owner and creator of the shard var (owner, membershipVersion, shardStartTime, maxDueTime) = ParseMetadata(blob.Metadata); // Check if the membership version is more recent than our current version if (membershipVersion > _clusterMembership.CurrentSnapshot.Version) { // Refresh membership to at least that version await _clusterMembership.Refresh(membershipVersion, cancellationToken); } if (shardStartTime > maxShardStartTime) { // This shard is too new. Since blobs are returned in alphabetical order and our blob names // contain timestamps (yyyyMMddHHmm format), all subsequent blobs will also be too new. LogShardTooNew(_logger, blob.Name, shardStartTime, maxShardStartTime); break; } // If I am the owner, the shard must be in cache - always return it if (owner is not null && owner.Equals(SiloAddress)) { if (_jobShardCache.TryGetValue(blob.Name, out var shard)) { LogShardAssigned(_logger, blob.Name, SiloAddress); result.Add(shard); } else { // Shard is owned by us but not in cache - this is unexpected, release ownership Debug.Assert(false, $"Shard '{blob.Name}' is owned by this silo but not in cache - releasing ownership"); await ReleaseOwnership(blob.Name); } continue; } // In debug, verify that if we're not the owner, the shard should not be in our cache Debug.Assert(!_jobShardCache.ContainsKey(blob.Name), $"Shard '{blob.Name}' is in cache but we are not the owner (owner: {owner?.ToParsableString() ?? "none"})"); // Check if the owner is valid var ownerStatus = owner is not null ? _clusterMembership.CurrentSnapshot.GetSiloStatus(owner) : SiloStatus.None; if (ownerStatus is not SiloStatus.Dead and not SiloStatus.None) { // Owner is still active and it's not me, skip this shard LogShardStillOwned(_logger, blob.Name, owner!); continue; } // Determine if this is an adopted shard (taken from dead owner) vs orphaned (gracefully released) var isAdopted = owner is not null && ownerStatus == SiloStatus.Dead; // Try to claim orphaned or adopted shard LogClaimingShard(_logger, blob.Name, SiloAddress, owner); var blobClient = _client.GetAppendBlobClient(blob.Name); var metadata = blob.Metadata; var orphanedShard = new AzureStorageJobShard(blob.Name, shardStartTime, maxDueTime, blobClient, metadata, blob.Properties.ETag, _options, _loggerFactory.CreateLogger()); if (!await TryTakeOwnership(orphanedShard, metadata, SiloAddress, isAdopted, cancellationToken)) { // Either poisoned shard or someone else took ownership - dispose and continue await orphanedShard.DisposeAsync(); continue; } await orphanedShard.InitializeAsync(cancellationToken); // We don't want to add new jobs to shards that we just took ownership of await orphanedShard.MarkAsCompleteAsync(cancellationToken); _jobShardCache[blob.Name] = orphanedShard; LogShardAssigned(_logger, blob.Name, SiloAddress); result.Add(orphanedShard); } LogAssignmentCompleted(_logger, result.Count, SiloAddress); return result; async Task ReleaseOwnership(string blobName) { try { var blobClient = _client.GetAppendBlobClient(blobName); var properties = await blobClient.GetPropertiesAsync(cancellationToken: cancellationToken); var metadata = properties.Value.Metadata; metadata.Remove("Owner"); // Reset adopted count since we're gracefully releasing metadata.Remove(AdoptedCountKey); metadata.Remove(LastAdoptedTimeKey); metadata.Remove(LegacyStolenCountKey); metadata.Remove(LegacyLastStolenTimeKey); await blobClient.SetMetadataAsync(metadata, new BlobRequestConditions { IfMatch = properties.Value.ETag }, cancellationToken); } catch (Exception ex) { // Log but continue - we'll let another silo claim it _logger.LogWarning(ex, "Failed to release ownership of shard '{ShardId}' that was not in cache", blobName); } } async Task TryTakeOwnership(AzureStorageJobShard shard, IDictionary metadata, SiloAddress newOwner, bool isAdopted, CancellationToken ct) { if (isAdopted) { var existingAdoptedCount = GetAdoptedCount(metadata); if (existingAdoptedCount > _durableJobsOptions.MaxAdoptedCount) { // Already marked as poisoned. return false; } // Increment adopted count for shards taken from dead owners. var adoptedCount = existingAdoptedCount + 1; if (adoptedCount > _durableJobsOptions.MaxAdoptedCount) { // Persist poisoned marker so this shard is not repeatedly re-evaluated as newly poisoned. SetAdoptedMetadata(metadata, adoptedCount, DateTimeOffset.UtcNow); try { await shard.UpdateBlobMetadata(metadata, ct); } catch (RequestFailedException ex) { LogOwnershipFailed(_logger, ex, shard.Id, newOwner); } LogPoisonedShardDetected(_logger, shard.Id, adoptedCount, _durableJobsOptions.MaxAdoptedCount); return false; } SetAdoptedMetadata(metadata, adoptedCount, DateTimeOffset.UtcNow); LogShardAdopted(_logger, shard.Id, newOwner, adoptedCount); } metadata["Owner"] = newOwner.ToParsableString(); metadata["MembershipVersion"] = _clusterMembership.CurrentSnapshot.Version.Value.ToString(); try { await shard.UpdateBlobMetadata(metadata, ct); LogOwnershipTaken(_logger, shard.Id, newOwner); return true; } catch (RequestFailedException ex) { // Someone else took over the shard LogOwnershipFailed(_logger, ex, shard.Id, newOwner); return false; } } static int GetAdoptedCount(IDictionary metadata) { if (metadata.TryGetValue(AdoptedCountKey, out var countStr) && int.TryParse(countStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out var adoptedCount)) { return adoptedCount; } return metadata.TryGetValue(LegacyStolenCountKey, out countStr) && int.TryParse(countStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out var legacyCount) ? legacyCount : 0; } static void SetAdoptedMetadata(IDictionary metadata, int adoptedCount, DateTimeOffset adoptedTime) { metadata[AdoptedCountKey] = adoptedCount.ToString(CultureInfo.InvariantCulture); metadata[LastAdoptedTimeKey] = adoptedTime.ToString("o", CultureInfo.InvariantCulture); metadata.Remove(LegacyStolenCountKey); metadata.Remove(LegacyLastStolenTimeKey); } } public override async Task CreateShardAsync(DateTimeOffset minDueTime, DateTimeOffset maxDueTime, IDictionary metadata, CancellationToken cancellationToken) { await InitializeIfNeeded(cancellationToken); LogRegisteringShard(_logger, SiloAddress, minDueTime, maxDueTime, _containerName); var i = 0; while (true) { var counter = Interlocked.Increment(ref _shardCounter); var shardId = $"{_blobPrefix}-{minDueTime:yyyyMMddHHmm}-{SiloAddress.ToParsableString()}-{counter}"; var blobClient = _client.GetAppendBlobClient(shardId); var metadataInfo = CreateMetadata(metadata, SiloAddress, _clusterMembership.CurrentSnapshot.Version, minDueTime, maxDueTime); metadataInfo["Owner"] = SiloAddress.ToParsableString(); try { var response = await blobClient.CreateIfNotExistsAsync(metadata: metadataInfo, cancellationToken: cancellationToken); if (response == null) { // Blob already exists, try again with a different name LogShardIdCollision(_logger, shardId, i); continue; } } catch (RequestFailedException ex) { i++; if (i > _options.MaxBlobCreationRetries) { throw new InvalidOperationException($"Failed to create shard blob '{shardId}' after {i} attempts", ex); } // Blob already exists, try again with a different name LogShardRegistrationRetry(_logger, ex, shardId, i); continue; } var shard = new AzureStorageJobShard(shardId, minDueTime, maxDueTime, blobClient, metadataInfo, null, _options, _loggerFactory.CreateLogger()); await shard.InitializeAsync(cancellationToken); _jobShardCache[shardId] = shard; LogShardRegistered(_logger, shardId, SiloAddress); return shard; } } public override async Task UnregisterShardAsync(Orleans.DurableJobs.IJobShard shard, CancellationToken cancellationToken) { var azureShard = shard as AzureStorageJobShard ?? throw new ArgumentException("Shard is not an AzureStorageJobShard", nameof(shard)); LogUnregisteringShard(_logger, shard.Id, SiloAddress); // Stop the background storage processor to ensure no more changes can happen await azureShard.StopProcessorAsync(cancellationToken); // Now we can safely get a consistent view of the state var count = await shard.GetJobCountAsync(); // We want to make sure to get the latest properties var properties = await azureShard.BlobClient.GetPropertiesAsync(cancellationToken: cancellationToken); // But we don't want to update the metadata if the ETag has changed var currentETag = properties.Value.ETag; var conditions = new BlobRequestConditions { IfMatch = currentETag }; var metadata = properties.Value.Metadata; var (owner, _, _, _) = ParseMetadata(metadata); if (owner != SiloAddress) { LogUnregisterWrongOwner(_logger, shard.Id, SiloAddress, owner); throw new InvalidOperationException("Cannot unregister a shard owned by another silo"); } if (count > 0) { // There are still jobs in the shard, release ownership gracefully. metadata.Remove("Owner"); // Reset adopted count since we're gracefully releasing (not crashing) metadata.Remove(AdoptedCountKey); metadata.Remove(LastAdoptedTimeKey); metadata.Remove(LegacyStolenCountKey); metadata.Remove(LegacyLastStolenTimeKey); await azureShard.BlobClient.SetMetadataAsync(metadata, conditions, cancellationToken); _jobShardCache.TryRemove(shard.Id, out _); LogShardOwnershipReleased(_logger, shard.Id, SiloAddress, count); } else { // No jobs left, we can delete the shard await azureShard.BlobClient.DeleteIfExistsAsync(conditions: conditions, cancellationToken: cancellationToken); _jobShardCache.TryRemove(shard.Id, out _); LogShardDeleted(_logger, shard.Id, SiloAddress); } // Dispose the shard's resources await azureShard.DisposeAsync(); } private async ValueTask InitializeIfNeeded(CancellationToken cancellationToken = default) { if (_client != null) return; LogInitializing(_logger, _containerName); _client = _blobServiceClient.GetBlobContainerClient(_containerName); await _client.CreateIfNotExistsAsync(cancellationToken: cancellationToken); LogInitialized(_logger, _containerName); } private static Dictionary CreateMetadata(IDictionary existingMetadata, SiloAddress siloAddress, MembershipVersion membershipVersion, DateTimeOffset minDueTime, DateTimeOffset maxDueTime) { var metadata = new Dictionary(existingMetadata) { { "MinDueTime", minDueTime.ToString("o") }, { "MaxDueTime", maxDueTime.ToString("o") }, { "MembershipVersion", membershipVersion.Value.ToString(CultureInfo.InvariantCulture) } }; return metadata; } private static (SiloAddress? owner, MembershipVersion membershipVersion, DateTimeOffset minDueTime, DateTimeOffset maxDueTime) ParseMetadata(IDictionary metadata) { var owner = metadata.TryGetValue("Owner", out var ownerStr) ? SiloAddress.FromParsableString(ownerStr) : null; var membershipVersion = metadata.TryGetValue("MembershipVersion", out var membershipVersionStr) && long.TryParse(membershipVersionStr, out var versionValue) ? new MembershipVersion(versionValue) : MembershipVersion.MinValue; var minDueTime = metadata.TryGetValue("MinDueTime", out var minDueTimeStr) && DateTimeOffset.TryParse(minDueTimeStr, out var minDt) ? minDt : DateTimeOffset.MinValue; var maxDueTime = metadata.TryGetValue("MaxDueTime", out var maxDueTimeStr) && DateTimeOffset.TryParse(maxDueTimeStr, out var maxDt) ? maxDt : DateTimeOffset.MaxValue; return (owner, membershipVersion, minDueTime, maxDueTime); } [LoggerMessage( Level = LogLevel.Information, Message = "Initializing Azure Storage Job Shard Manager with container '{ContainerName}'" )] private static partial void LogInitializing(ILogger logger, string containerName); [LoggerMessage( Level = LogLevel.Information, Message = "Azure Storage Job Shard Manager initialized successfully for container '{ContainerName}'" )] private static partial void LogInitialized(ILogger logger, string containerName); [LoggerMessage( Level = LogLevel.Debug, Message = "Assigning job shards for silo {SiloAddress} with max time {MaxDateTime} from container '{ContainerName}'" )] private static partial void LogAssigningShards(ILogger logger, SiloAddress siloAddress, DateTimeOffset maxDateTime, string containerName); [LoggerMessage( Level = LogLevel.Trace, Message = "Ignoring shard '{ShardId}' since its start time is greater than specified maximum (MinDueTime={MinDueTime}, MaxDateTime={MaxDateTime})" )] private static partial void LogShardTooNew(ILogger logger, string shardId, DateTimeOffset minDueTime, DateTimeOffset maxDateTime); [LoggerMessage( Level = LogLevel.Trace, Message = "Shard '{ShardId}' is still owned by active silo {Owner}" )] private static partial void LogShardStillOwned(ILogger logger, string shardId, SiloAddress owner); [LoggerMessage( Level = LogLevel.Debug, Message = "Reclaiming shard '{ShardId}' from cache for silo {SiloAddress}" )] private static partial void LogReclaimingShardFromCache(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Claiming shard '{ShardId}' for silo {SiloAddress} (Previous Owner={PreviousOwner})" )] private static partial void LogClaimingShard(ILogger logger, string shardId, SiloAddress siloAddress, SiloAddress? previousOwner); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to take ownership of shard '{ShardId}' for silo {SiloAddress} due to conflict" )] private static partial void LogShardOwnershipConflict(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Shard '{ShardId}' assigned to silo {SiloAddress}" )] private static partial void LogShardAssigned(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Information, Message = "Assigned {ShardCount} shard(s) to silo {SiloAddress}" )] private static partial void LogAssignmentCompleted(ILogger logger, int shardCount, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Took ownership of shard '{ShardId}' for silo {SiloAddress}" )] private static partial void LogOwnershipTaken(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to take ownership of shard '{ShardId}' for silo {SiloAddress}" )] private static partial void LogOwnershipFailed(ILogger logger, Exception exception, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Information, Message = "Creating new shard for silo {SiloAddress} (MinDueTime={MinDueTime}, MaxDueTime={MaxDueTime}) in container '{ContainerName}'" )] private static partial void LogRegisteringShard(ILogger logger, SiloAddress siloAddress, DateTimeOffset minDueTime, DateTimeOffset maxDueTime, string containerName); [LoggerMessage( Level = LogLevel.Trace, Message = "Shard ID collision for '{ShardId}' (attempt {Attempt}), retrying with new ID" )] private static partial void LogShardIdCollision(ILogger logger, string shardId, int attempt); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to register shard '{ShardId}' (attempt {Attempt}), retrying" )] private static partial void LogShardRegistrationRetry(ILogger logger, Exception exception, string shardId, int attempt); [LoggerMessage( Level = LogLevel.Information, Message = "Shard '{ShardId}' created successfully for silo {SiloAddress}" )] private static partial void LogShardRegistered(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Information, Message = "Unregistering shard '{ShardId}' for silo {SiloAddress}" )] private static partial void LogUnregisteringShard(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Cannot unregister shard '{ShardId}' - silo {SiloAddress} is not the owner (Owner={Owner})" )] private static partial void LogUnregisterWrongOwner(ILogger logger, string shardId, SiloAddress siloAddress, SiloAddress? owner); [LoggerMessage( Level = LogLevel.Information, Message = "Released ownership of shard '{ShardId}' by silo {SiloAddress} ({JobCount} jobs remaining)" )] private static partial void LogShardOwnershipReleased(ILogger logger, string shardId, SiloAddress siloAddress, int jobCount); [LoggerMessage( Level = LogLevel.Information, Message = "Deleted shard '{ShardId}' by silo {SiloAddress} (no jobs remaining)" )] private static partial void LogShardDeleted(ILogger logger, string shardId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Poisoned shard detected: '{ShardId}' has been adopted {AdoptedCount} times (max allowed: {MaxAdoptedCount}). Shard will not be assigned." )] private static partial void LogPoisonedShardDetected(ILogger logger, string shardId, int adoptedCount, int maxAdoptedCount); [LoggerMessage( Level = LogLevel.Information, Message = "Shard '{ShardId}' adopted by silo {SiloAddress} (adopted count: {AdoptedCount})" )] private static partial void LogShardAdopted(ILogger logger, string shardId, SiloAddress siloAddress, int adoptedCount); } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/Hosting/AzureStorageDurableJobsExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.DurableJobs; using Orleans.DurableJobs.AzureStorage; namespace Orleans.Hosting; /// /// Extensions for configuring Azure Blob Storage durable jobs. /// public static class AzureStorageDurableJobsExtensions { /// /// Adds durable jobs storage backed by Azure Blob Storage. /// /// /// The builder. /// /// /// The delegate used to configure the durable jobs storage. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseAzureBlobDurableJobs(this ISiloBuilder builder, Action configure) { builder.ConfigureServices(services => services.UseAzureBlobDurableJobs(configure)); return builder; } /// /// Adds durable jobs storage backed by Azure Blob Storage. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseAzureBlobDurableJobs(this ISiloBuilder builder, Action> configureOptions) { builder.ConfigureServices(services => services.UseAzureBlobDurableJobs(configureOptions)); return builder; } /// /// Adds durable jobs storage backed by Azure Blob Storage. /// /// /// The service collection. /// /// /// The delegate used to configure the durable jobs storage. /// /// /// The provided , for chaining. /// public static IServiceCollection UseAzureBlobDurableJobs(this IServiceCollection services, Action configure) { services.AddDurableJobs(); services.AddSingleton(); services.AddFromExisting(); services.Configure(configure); services.ConfigureFormatter(); return services; } /// /// Adds durable jobs storage backed by Azure Blob Storage. /// /// /// The service collection. /// /// /// The configuration delegate. /// /// /// The provided , for chaining. /// public static IServiceCollection UseAzureBlobDurableJobs(this IServiceCollection services, Action> configureOptions) { services.AddDurableJobs(); services.AddSingleton(); services.AddFromExisting(); configureOptions?.Invoke(services.AddOptions()); services.ConfigureFormatter(); services.AddTransient(sp => new AzureStorageJobShardOptionsValidator(sp.GetRequiredService>().Get(Options.DefaultName), Options.DefaultName)); return services; } } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/Hosting/AzureStorageJobShardOptions.cs ================================================ using System; using Azure.Storage.Blobs; namespace Orleans.Hosting; public class AzureStorageJobShardOptions { /// /// Gets or sets the instance used to store job shards. /// public BlobServiceClient BlobServiceClient { get; set; } = null!; /// /// Gets or sets the name of the container used to store durable jobs. /// public string ContainerName { get; set; } = "jobs"; /// /// Gets or sets the maximum number of job operations to batch together in a single blob write. /// Default is 50 operations. /// public int MaxBatchSize { get; set; } = 50; /// /// Gets or sets the minimum number of job operations to batch together before flushing. /// If more than 1 then the we will wait for additional operations. /// Default is 1 operation (immediate flush, optimized for latency). /// public int MinBatchSize { get; set; } = 1; /// /// Gets or sets the maximum time to wait for additional operations if the minimum batch size isn't reached /// before flushing a batch. /// Default is 50 milliseconds. /// public TimeSpan BatchFlushInterval { get; set; } = TimeSpan.FromMilliseconds(50); /// /// Gets or sets the maximum number of retries for creating a blob for a job shard in case of name collisions. /// public int MaxBlobCreationRetries { get; internal set; } = 3; } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/Hosting/AzureStorageJobShardOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Configuration.Internal; using Orleans.Runtime; namespace Orleans.Hosting; /// /// Validates . /// public class AzureStorageJobShardOptionsValidator : IConfigurationValidator { private readonly AzureStorageJobShardOptions _options; private readonly string _name; /// /// Initializes a new instance of the class. /// /// The options. /// The name. public AzureStorageJobShardOptionsValidator(AzureStorageJobShardOptions options, string name) { _options = options; _name = name; } /// public void ValidateConfiguration() { if (_options.BlobServiceClient is null) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(AzureStorageJobShardOptions)} with name '{_name}'. {nameof(_options.BlobServiceClient)} is required."); } if (string.IsNullOrWhiteSpace(_options.ContainerName)) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(AzureStorageJobShardOptions)} with name '{_name}'. {nameof(_options.ContainerName)} is required."); } } } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/JobOperation.cs ================================================ using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Orleans.Runtime; namespace Orleans.DurableJobs.AzureStorage; /// /// Represents an operation to be performed on a durable job. /// internal struct JobOperation { /// /// The type of operation to perform. /// public enum OperationType { Add, Remove, Retry, } /// /// Gets or sets the type of operation. /// public OperationType Type { get; init; } /// /// Gets or sets the job identifier. /// public string Id { get; init; } /// /// Gets or sets the job name (only used for Add operations). /// public string? Name { get; init; } /// /// Gets or sets the due time (used for Add and Retry operations). /// public DateTimeOffset? DueTime { get; init; } /// /// Gets or sets the target grain ID (only used for Add operations). /// public GrainId? TargetGrainId { get; init; } /// /// Gets or sets the job metadata (only used for Add operations). /// public IReadOnlyDictionary? Metadata { get; init; } /// /// Creates an Add operation for scheduling a new job. /// /// The job identifier. /// The job name. /// The job due time. /// The target grain ID. /// The job metadata. /// A new JobOperation for adding a job. /// Thrown when or is null or empty. public static JobOperation CreateAddOperation(string id, string name, DateTimeOffset dueTime, GrainId targetGrainId, IReadOnlyDictionary? metadata) { ArgumentException.ThrowIfNullOrEmpty(id); ArgumentException.ThrowIfNullOrEmpty(name); return new() { Type = OperationType.Add, Id = id, Name = name, DueTime = dueTime, TargetGrainId = targetGrainId, Metadata = metadata }; } /// /// Creates a Remove operation for canceling a job. /// /// The job identifier. /// A new JobOperation for removing a job. /// Thrown when is null or empty. public static JobOperation CreateRemoveOperation(string id) { ArgumentException.ThrowIfNullOrEmpty(id); return new() { Type = OperationType.Remove, Id = id }; } /// /// Creates a Retry operation for rescheduling a job. /// /// The job identifier. /// The new due time. /// A new JobOperation for retrying a job. /// Thrown when is null or empty. public static JobOperation CreateRetryOperation(string id, DateTimeOffset dueTime) { ArgumentException.ThrowIfNullOrEmpty(id); return new() { Type = OperationType.Retry, Id = id, DueTime = dueTime }; } } /// /// JSON serialization context for JobOperation with compile-time source generation. /// [JsonSerializable(typeof(JobOperation))] [JsonSourceGenerationOptions( DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = false)] internal partial class JobOperationJsonContext : JsonSerializerContext { } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/NetstringJsonSerializer.cs ================================================ using System; using System.Buffers; using System.Buffers.Text; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Orleans.Serialization.Buffers.Adaptors; namespace Orleans.DurableJobs.AzureStorage; /// /// Provides methods for serializing and deserializing JSON data using the netstring format. /// Netstrings are a simple, self-delimiting way to encode data with length prefixes. /// Format: [6 hex digits]:[data]\n /// Maximum data size is 10MB (0xA00000 bytes). /// public static class NetstringJsonSerializer { private const int MaxLength = 0xA00000; // 10MB /// /// Encodes an object as a netstring by serializing it to JSON and writing directly to a stream. /// /// The object to encode. /// The stream to write the netstring-encoded data to. /// The JSON type info for serialization. /// Thrown when the serialized data exceeds the maximum length. public static void Encode(T value, Stream stream, JsonTypeInfo jsonTypeInfo) { // Remember starting position var startPosition = stream.Position; // Skip past where the length prefix will go (6 hex digits + colon) Span lengthBytes = stackalloc byte[7]; stream.Write(lengthBytes); // Remember position where data starts var dataStartPosition = stream.Position; // Serialize JSON directly to stream using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = false })) { JsonSerializer.Serialize(writer, value, jsonTypeInfo); } stream.Flush(); // Calculate JSON length var jsonLength = (int)(stream.Position - dataStartPosition); if (jsonLength > MaxLength) { throw new InvalidOperationException($"Serialized data exceeds maximum length of {MaxLength} bytes"); } // Write trailing newline stream.WriteByte((byte)'\n'); // Remember end position var endPosition = stream.Position; // Seek back to write the length prefix stream.Position = startPosition; // Format length as 6-digit hex and write directly if (!Utf8Formatter.TryFormat(jsonLength, lengthBytes, out _, new StandardFormat('X', 6))) { throw new InvalidOperationException("Failed to format length prefix"); } lengthBytes[6] = (byte)':'; stream.Write(lengthBytes); // Restore position to end stream.Position = endPosition; } /// /// Reads netstring-encoded JSON objects from a stream and deserializes them. /// /// The stream to read from. /// The JSON type info for deserialization. /// The cancellation token to cancel the operation. /// An async enumerable of deserialized objects. /// Thrown when the stream contains invalid netstring data. public static async IAsyncEnumerable DecodeAsync(Stream stream, JsonTypeInfo jsonTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { const int TypicalBufferSize = 4096; // 4KB var buffer = ArrayPool.Shared.Rent(TypicalBufferSize); try { while (true) { // Try to read length prefix (6 hex digits + colon) try { await stream.ReadExactlyAsync(buffer, 0, 7, cancellationToken); } catch (EndOfStreamException) { // We are done yield break; } // Verify colon if (buffer[6] != ':') { throw new InvalidDataException($"Expected colon at position 6, got byte value {buffer[6]}"); } // Parse length as hex if (!Utf8Parser.TryParse(buffer.AsSpan(0, 6), out int length, out _, 'X')) { throw new InvalidDataException($"Invalid netstring length: {System.Text.Encoding.UTF8.GetString(buffer, 0, 6)}"); } if (length < 0 || length > MaxLength) { throw new InvalidDataException($"Netstring length out of valid range: {length}"); } // Ensure buffer is large enough for the data + newline var totalLength = length + 1; if (buffer.Length < totalLength) { ArrayPool.Shared.Return(buffer); buffer = ArrayPool.Shared.Rent(totalLength); } // Read data + trailing newline try { await stream.ReadExactlyAsync(buffer.AsMemory(0, totalLength), cancellationToken); } catch (EndOfStreamException ex) { throw new InvalidDataException("Unexpected end of stream while reading netstring data", ex); } // Verify trailing newline if (buffer[length] != '\n') { throw new InvalidDataException($"Expected newline at end of netstring, got byte value {buffer[length]}"); } // Deserialize JSON directly from UTF-8 bytes var result = JsonSerializer.Deserialize(buffer.AsSpan(0, length), jsonTypeInfo); if (result is null) { throw new JsonException("Deserialized JSON resulted in null value"); } yield return result; } } finally { ArrayPool.Shared.Return(buffer); } } } ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/Orleans.DurableJobs.AzureStorage.csproj ================================================ README.md Microsoft.Orleans.DurableJobs.AzureStorage Microsoft Orleans Azure Storage Durable Jobs Provider Microsoft Orleans durable jobs provider backed by Azure Blob Storage $(PackageTags) Azure Storage $(DefaultTargetFrameworks) Orleans.DurableJobs.AzureStorage Orleans.DurableJobs.AzureStorage true $(DefineConstants) enable $(VersionSuffix).alpha.1 alpha.1 ================================================ FILE: src/Azure/Orleans.DurableJobs.AzureStorage/README.md ================================================ # Microsoft Orleans Durable Jobs for Azure Storage ## Introduction Microsoft Orleans Durable Jobs for Azure Storage provides persistent storage for Orleans Durable Jobs using Azure Blob Storage. This allows your Orleans applications to schedule jobs that survive silo restarts, grain deactivation, and cluster reconfigurations. Jobs are stored in append blobs, providing efficient storage and retrieval for time-based job scheduling. ## Getting Started ### Installation To use this package, install it via NuGet along with the core package: ```shell dotnet add package Microsoft.Orleans.DurableJobs dotnet add package Microsoft.Orleans.DurableJobs.AzureStorage ``` ### Configuration #### Using Connection String ```csharp using Azure.Storage.Blobs; using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(siloBuilder => { siloBuilder .UseAzureStorageClustering(options => options.ConfigureTableServiceClient("YOUR_STORAGE_ACCOUNT_URI")) .UseAzureStorageDurableJobs(options => { options.Configure(o => { o.BlobServiceClient = new BlobServiceClient("YOUR_AZURE_STORAGE_CONNECTION_STRING"); o.ContainerName = "durable-jobs"; }); }); }); await builder.Build().RunAsync(); ``` #### Using Managed Identity (Recommended for Production) ```csharp using Azure.Identity; using Azure.Storage.Blobs; using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(siloBuilder => { siloBuilder .UseAzureStorageClustering(options => options.ConfigureTableServiceClient("YOUR_STORAGE_ACCOUNT_URI")) .UseAzureStorageDurableJobs(options => { options.Configure(o => { var credential = new DefaultAzureCredential(); o.BlobServiceClient = new BlobServiceClient( new Uri("https://youraccount.blob.core.windows.net"), credential); o.ContainerName = "durable-jobs"; }); }); }); await builder.Build().RunAsync(); ``` #### With Advanced Options ```csharp using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; builder.UseOrleans(siloBuilder => { siloBuilder .UseAzureStorageClustering(options => options.ConfigureTableServiceClient(connectionString)) .UseAzureStorageDurableJobs(options => { options.Configure(o => { o.BlobServiceClient = new BlobServiceClient(connectionString); // Use different containers for different environments o.ContainerName = $"durable-jobs-{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.ToLowerInvariant()}"; }); }) .ConfigureServices(services => { services.Configure(options => { // Shard duration: balance between latency and storage overhead options.ShardDuration = TimeSpan.FromMinutes(5); // Control concurrency to prevent overwhelming the system options.MaxConcurrentJobsPerSilo = 50; // Custom retry policy with exponential backoff options.ShouldRetry = (context, exception) => { // Don't retry on permanent failures if (exception is ArgumentException or InvalidOperationException) return null; // Exponential backoff with max 3 retries if (context.DequeueCount < 3) { var delay = TimeSpan.FromSeconds(Math.Pow(2, context.DequeueCount)); return DateTimeOffset.UtcNow.Add(delay); } return null; }; }); }); }); ``` ## Usage Example ### Email Scheduling with Cancellation ```csharp using Orleans; using Orleans.DurableJobs; public interface IEmailGrain : IGrainWithStringKey { Task ScheduleEmail(string subject, string body, DateTimeOffset sendTime); Task CancelScheduledEmail(); } public class EmailGrain : Grain, IEmailGrain, IDurableJobHandler { private readonly ILocalDurableJobManager _jobManager; private readonly IEmailService _emailService; private readonly ILogger _logger; private IDurableJob? _durableEmailJob; public EmailGrain( ILocalDurableJobManager jobManager, IEmailService emailService, ILogger logger) { _jobManager = jobManager; _emailService = emailService; _logger = logger; } public async Task ScheduleEmail(string subject, string body, DateTimeOffset sendTime) { var emailAddress = this.GetPrimaryKeyString(); var metadata = new Dictionary { ["Subject"] = subject, ["Body"] = body }; _durableEmailJob = await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "SendEmail", DueTime = sendTime, Metadata = metadata }, CancellationToken.None); _logger.LogInformation( "Scheduled email to {EmailAddress} for {SendTime} (JobId: {JobId})", emailAddress, sendTime, _durableEmailJob.Id); } public async Task CancelScheduledEmail() { if (_durableEmailJob is null) { _logger.LogWarning("No scheduled email to cancel"); return; } var canceled = await _jobManager.TryCancelDurableJobAsync(_durableEmailJob); if (canceled) { _logger.LogInformation("Email job {JobId} canceled successfully", _durableEmailJob.Id); _durableEmailJob = null; } else { _logger.LogWarning("Failed to cancel email job {JobId} (may have already executed)", _durableEmailJob.Id); } } public async Task ExecuteJobAsync(IDurableJobContext context, CancellationToken cancellationToken) { var emailAddress = this.GetPrimaryKeyString(); var subject = context.Job.Metadata?["Subject"]; var body = context.Job.Metadata?["Body"]; _logger.LogInformation( "Sending email to {EmailAddress} (Job: {JobId}, Attempt: {Attempt})", emailAddress, context.Job.Id, context.DequeueCount); try { await _emailService.SendEmailAsync(emailAddress, subject, body, cancellationToken); _logger.LogInformation("Email sent successfully to {EmailAddress}", emailAddress); _durableEmailJob = null; } catch (Exception ex) { _logger.LogError(ex, "Failed to send email to {EmailAddress}", emailAddress); throw; // Let the retry policy handle it } } } ``` ### Order Workflow with Multiple Scheduled Steps ```csharp public interface IOrderGrain : IGrainWithGuidKey { Task PlaceOrder(OrderDetails order); Task CancelOrder(); } public class OrderGrain : Grain, IOrderGrain, IDurableJobHandler { private readonly ILocalDurableJobManager _jobManager; private readonly IOrderService _orderService; private readonly IGrainFactory _grainFactory; private readonly ILogger _logger; private OrderDetails? _orderDetails; public OrderGrain( ILocalDurableJobManager jobManager, IOrderService orderService, IGrainFactory grainFactory, ILogger logger) { _jobManager = jobManager; _orderService = orderService; _grainFactory = grainFactory; _logger = logger; } public async Task PlaceOrder(OrderDetails order) { _orderDetails = order; var orderId = this.GetPrimaryKey(); // Create the order await _orderService.CreateOrderAsync(orderId, order); _logger.LogInformation("Order {OrderId} created for customer {CustomerId}", orderId, order.CustomerId); // Schedule payment reminder after 1 hour var paymentReminderTime = DateTimeOffset.UtcNow.AddHours(1); await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "PaymentReminder", DueTime = paymentReminderTime, Metadata = new Dictionary { ["Step"] = "PaymentReminder", ["CustomerEmail"] = order.CustomerEmail } }, CancellationToken.None); // Schedule order expiration after 24 hours var expirationTime = DateTimeOffset.UtcNow.AddHours(24); await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "OrderExpiration", DueTime = expirationTime, Metadata = new Dictionary { ["Step"] = "OrderExpiration" } }, CancellationToken.None); _logger.LogInformation( "Scheduled payment reminder for {ReminderTime} and expiration for {ExpirationTime}", paymentReminderTime, expirationTime); } public async Task CancelOrder() { var orderId = this.GetPrimaryKey(); await _orderService.CancelOrderAsync(orderId); _orderDetails = null; _logger.LogInformation("Order {OrderId} canceled", orderId); } public async Task ExecuteJobAsync(IDurableJobContext context, CancellationToken cancellationToken) { var step = context.Job.Metadata!["Step"]; var orderId = this.GetPrimaryKey(); _logger.LogInformation( "Executing workflow step {Step} for order {OrderId} (Attempt: {Attempt})", step, orderId, context.DequeueCount); switch (step) { case "PaymentReminder": await HandlePaymentReminder(context, cancellationToken); break; case "OrderExpiration": await HandleOrderExpiration(cancellationToken); break; default: _logger.LogWarning("Unknown workflow step: {Step}", step); break; } } private async Task HandlePaymentReminder(IDurableJobContext context, CancellationToken ct) { var orderId = this.GetPrimaryKey(); var order = await _orderService.GetOrderAsync(orderId, ct); if (order?.Status == OrderStatus.Pending) { var customerEmail = context.Job.Metadata!["CustomerEmail"]; var emailGrain = _grainFactory.GetGrain(customerEmail); await emailGrain.ScheduleEmail( "Payment Reminder", $"Your order {orderId} is awaiting payment. Please complete your purchase within 23 hours.", DateTimeOffset.UtcNow); _logger.LogInformation("Payment reminder sent for order {OrderId}", orderId); } else { _logger.LogInformation( "Skipping payment reminder for order {OrderId} - status is {Status}", orderId, order?.Status); } } private async Task HandleOrderExpiration(CancellationToken ct) { var orderId = this.GetPrimaryKey(); var order = await _orderService.GetOrderAsync(orderId, ct); if (order?.Status == OrderStatus.Pending) { await _orderService.CancelOrderAsync(orderId, ct); _logger.LogInformation("Order {OrderId} expired and canceled", orderId); // Notify customer var emailGrain = _grainFactory.GetGrain(order.CustomerEmail); await emailGrain.ScheduleEmail( "Order Expired", $"Your order {orderId} has expired due to pending payment.", DateTimeOffset.UtcNow); } else { _logger.LogInformation( "Order {OrderId} did not expire - status is {Status}", orderId, order?.Status); } } } // Supporting types public class OrderDetails { public string CustomerId { get; set; } = ""; public string CustomerEmail { get; set; } = ""; public decimal Amount { get; set; } public List Items { get; set; } = new(); } public enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled } ``` ## How It Works ### Storage Architecture 1. **Blob Container**: All jobs are stored in a single Azure Blob Storage container 2. **Append Blobs**: Each job shard is stored as an append blob, providing efficient sequential writes 3. **Blob Naming**: Blobs are named with the pattern: `{ShardStartTime:yyyyMMddHHmm}-{SiloAddress}-{Index}` 4. **Metadata**: Blob metadata stores ownership and time range information: - `Owner`: The silo currently processing this shard - `Creator`: The silo that created this shard - `MinDueTime`: Start of the time range for jobs in this shard - `MaxDueTime`: End of the time range for jobs in this shard ### Shard Ownership and High Availability 1. **Optimistic Concurrency**: ETags prevent conflicting updates when multiple silos try to claim a shard 2. **Ownership Transfer**: When a silo fails, other silos detect the failure and claim orphaned shards 3. **Creator Priority**: The silo that created a shard gets priority to reclaim it if it loses ownership 4. **Automatic Cleanup**: Empty shards are deleted automatically after processing ### Job Lifecycle with Azure Storage ``` ┌─────────────────────┐ │ Job Scheduled │ ──▶ Written to append blob └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Waiting in Shard │ ──▶ Persisted in Azure Blob Storage └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Shard Owned │ ──▶ Silo acquires ownership via metadata update └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Job Executed │ ──▶ Handler invoked on target grain └─────────────────────┘ │ ├──▶ Success ──▶ Job entry removed from blob │ └──▶ Failure ──▶ Retry: Updated due time in blob No Retry: Job entry removed ``` ## Performance Considerations ### Concurrency Settings ```csharp services.Configure(options => { // Adjust based on your workload and Azure Storage limits options.MaxConcurrentJobsPerSilo = 50; }); ``` ### Storage Costs - **Container**: One container per cluster - **Blobs**: One blob per active time shard - **Operations**: - Schedule job: 1-2 append operations - Execute job: 1 read + 1 delete operation - Shard ownership transfer: 1 metadata update ## Monitoring and Troubleshooting ### Enable Logging ```csharp builder.Logging.AddFilter("Orleans.DurableJobs", LogLevel.Information); builder.Logging.AddFilter("Orleans.DurableJobs.AzureStorage", LogLevel.Information); ``` ### Key Metrics to Monitor - **Shard Assignment Time**: Time to claim ownership of unassigned shards - **Job Execution Latency**: Time between due time and actual execution - **Retry Rate**: Percentage of jobs requiring retry - **Blob Operations**: Number of read/write/delete operations per minute ## Security Best Practices ### Use Managed Identity ```csharp var credential = new DefaultAzureCredential(); var blobServiceClient = new BlobServiceClient(storageAccountUri, credential); ``` ### Network Security - Enable firewall rules to restrict access - Use private endpoints for enhanced security - Consider Azure Virtual Network integration ### Access Control ```csharp // Minimum required permissions: // - Storage Blob Data Contributor (for read/write/delete operations) // - Or custom role with: // - Microsoft.Storage/storageAccounts/blobServices/containers/read // - Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read // - Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write // - Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Azure Blob Storage Documentation](https://learn.microsoft.com/azure/storage/blobs/) - [Orleans Durable Jobs Core Package](../../../Orleans.DurableJobs/README.md) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/AzureTableGrainDirectory.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using Azure; using Azure.Data.Tables; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.GrainDirectory.AzureStorage { public class AzureTableGrainDirectory : IGrainDirectory, ILifecycleParticipant { private readonly AzureTableDataManager tableDataManager; private readonly string clusterId; internal class GrainDirectoryEntity : ITableEntity { public required string SiloAddress { get; set; } public required string ActivationId { get; set; } public required long MembershipVersion { get; set; } public required string PartitionKey { get; set; } public required string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } public GrainAddress ToGrainAddress() { return new GrainAddress { GrainId = RowKeyToGrainId(this.RowKey), SiloAddress = Runtime.SiloAddress.FromParsableString(this.SiloAddress), ActivationId = Runtime.ActivationId.FromParsableString(this.ActivationId), MembershipVersion = new MembershipVersion(this.MembershipVersion) }; } public static GrainDirectoryEntity FromGrainAddress(string clusterId, GrainAddress address) { ArgumentNullException.ThrowIfNull(address.SiloAddress); return new GrainDirectoryEntity { PartitionKey = clusterId, RowKey = GrainIdToRowKey(address.GrainId), SiloAddress = address.SiloAddress.ToParsableString(), ActivationId = address.ActivationId.ToParsableString(), MembershipVersion = address.MembershipVersion.Value, }; } internal static string GrainIdToRowKey(GrainId grainId) => HttpUtility.UrlEncode(grainId.ToString(), Encoding.UTF8); internal static GrainId RowKeyToGrainId(string rowKey) => GrainId.Parse(HttpUtility.UrlDecode(rowKey, Encoding.UTF8)); } public AzureTableGrainDirectory( AzureTableGrainDirectoryOptions directoryOptions, IOptions clusterOptions, ILoggerFactory loggerFactory) { this.tableDataManager = new AzureTableDataManager( directoryOptions, loggerFactory.CreateLogger>()); this.clusterId = clusterOptions.Value.ClusterId; } public async Task Lookup(GrainId grainId) { var result = await this.tableDataManager.ReadSingleTableEntryAsync(this.clusterId, GrainDirectoryEntity.GrainIdToRowKey(grainId)); if (result.Entity is null) { return null; } return result.Item1.ToGrainAddress(); } public Task Register(GrainAddress address) => Register(address, null); public async Task Register(GrainAddress address, GrainAddress? previousAddress) { (bool isSuccess, string eTag) result; if (previousAddress is not null) { var entry = GrainDirectoryEntity.FromGrainAddress(this.clusterId, address); var previousEntry = GrainDirectoryEntity.FromGrainAddress(this.clusterId, previousAddress); var (storedEntry, eTag) = await tableDataManager.ReadSingleTableEntryAsync(entry.PartitionKey, entry.RowKey); if (storedEntry is null) { result = await this.tableDataManager.InsertTableEntryAsync(entry); } else if (storedEntry.ActivationId != previousEntry.ActivationId || storedEntry.SiloAddress != previousEntry.SiloAddress) { return await Lookup(address.GrainId); } else { _ = await tableDataManager.UpdateTableEntryAsync(entry, eTag); return address; } } else { var entry = GrainDirectoryEntity.FromGrainAddress(this.clusterId, address); result = await this.tableDataManager.InsertTableEntryAsync(entry); } // Possible race condition? return result.isSuccess ? address : await Lookup(address.GrainId); } public async Task Unregister(GrainAddress address) { var result = await this.tableDataManager.ReadSingleTableEntryAsync(this.clusterId, GrainDirectoryEntity.GrainIdToRowKey(address.GrainId)); // No entry found if (result.Entity is null) { return; } // Check if the entry in storage match the one we were asked to delete var entity = result.Item1; if (entity.ActivationId == address.ActivationId.ToParsableString()) await this.tableDataManager.DeleteTableEntryAsync(GrainDirectoryEntity.FromGrainAddress(this.clusterId, address), entity.ETag.ToString()); } public async Task UnregisterMany(List addresses) { if (addresses.Count <= this.tableDataManager.StoragePolicyOptions.MaxBulkUpdateRows) { await UnregisterManyBlock(addresses); } else { var tasks = new List(); foreach (var subList in addresses.BatchIEnumerable(this.tableDataManager.StoragePolicyOptions.MaxBulkUpdateRows)) { tasks.Add(UnregisterManyBlock(subList)); } await Task.WhenAll(tasks); } } public Task UnregisterSilos(List siloAddresses) { // Too costly to implement using Azure Table return Task.CompletedTask; } private async Task UnregisterManyBlock(List addresses) { var queryBuilder = new StringBuilder(); queryBuilder.Append(TableClient.CreateQueryFilter($"(PartitionKey eq {clusterId}) and (")); var first = true; foreach (var addr in addresses) { if (!first) { queryBuilder.Append(" or "); } else { first = false; } var rowKey = GrainDirectoryEntity.GrainIdToRowKey(addr.GrainId); var queryClause = TableClient.CreateQueryFilter($"((RowKey eq {rowKey}) and (ActivationId eq {addr.ActivationId.ToString()}))"); queryBuilder.Append(queryClause); } queryBuilder.Append(')'); var entities = await this.tableDataManager.ReadTableEntriesAndEtagsAsync(queryBuilder.ToString()); await this.tableDataManager.DeleteTableEntriesAsync(entities); } // Called by lifecycle, should not be called explicitely, except for tests public async Task InitializeIfNeeded(CancellationToken ct = default) { await this.tableDataManager.InitTableAsync(); } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(nameof(AzureTableGrainDirectory), ServiceLifecycleStage.RuntimeInitialize, InitializeIfNeeded); } } } ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/Hosting/AzureTableGrainDirectoryExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; namespace Orleans.Hosting { public static class AzureTableGrainDirectorySiloBuilderExtensions { public static ISiloBuilder UseAzureTableGrainDirectoryAsDefault( this ISiloBuilder builder, Action configureOptions) { return builder.UseAzureTableGrainDirectoryAsDefault(ob => ob.Configure(configureOptions)); } public static ISiloBuilder UseAzureTableGrainDirectoryAsDefault( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices(services => services.AddAzureTableGrainDirectory(GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY, configureOptions)); } public static ISiloBuilder AddAzureTableGrainDirectory( this ISiloBuilder builder, string name, Action configureOptions) { return builder.AddAzureTableGrainDirectory(name, ob => ob.Configure(configureOptions)); } public static ISiloBuilder AddAzureTableGrainDirectory( this ISiloBuilder builder, string name, Action> configureOptions) { return builder.ConfigureServices(services => services.AddAzureTableGrainDirectory(name, configureOptions)); } } } ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/Hosting/AzureTableGrainDirectoryServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.GrainDirectory.AzureStorage; using Orleans.Runtime; using Orleans.Runtime.Hosting; namespace Orleans.Hosting { /// /// extensions. /// public static class AzureTableGrainDirectoryServiceCollectionExtensions { internal static IServiceCollection AddAzureTableGrainDirectory( this IServiceCollection services, string name, Action> configureOptions) { configureOptions.Invoke(services.AddOptions(name)); services .AddTransient(sp => new AzureTableGrainDirectoryOptionsValidator(sp.GetRequiredService>().Get(name), name)) .ConfigureNamedOptionForLogging(name) .AddGrainDirectory(name, (sp, name) => ActivatorUtilities.CreateInstance(sp, sp.GetOptionsByName(name))); return services; } } } ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/Hosting/AzureTableStorageGrainDirectoryProviderBuilder.cs ================================================ using System; using Azure.Data.Tables; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AzureTableStorage", "GrainDirectory", "Silo", typeof(AzureTableStorageGrainDirectoryProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureTableStorageGrainDirectoryProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAzureTableGrainDirectory(name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var tableName = configurationSection["TableName"]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.TableServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new TableServiceClient(uri); } else { options.TableServiceClient = new TableServiceClient(connectionString); } } } })); } } ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/Options/AzureTableGrainDirectoryOptions.cs ================================================ using Orleans.GrainDirectory.AzureStorage; namespace Orleans.Configuration { public class AzureTableGrainDirectoryOptions : AzureStorageOperationOptions { /// /// Table name for Azure Storage /// public override string TableName { get; set; } = DEFAULT_TABLE_NAME; public const string DEFAULT_TABLE_NAME = "GrainDirectory"; } public class AzureTableGrainDirectoryOptionsValidator : AzureStorageOperationOptionsValidator { public AzureTableGrainDirectoryOptionsValidator(AzureTableGrainDirectoryOptions options, string name) : base(options, name) { } } } ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/Orleans.GrainDirectory.AzureStorage.csproj ================================================ README.md Microsoft.Orleans.GrainDirectory.AzureStorage Microsoft Orleans Azure Storage Grain Directory Provider Microsoft Orleans Grain Directory provider backed by Azure Storage $(PackageTags) Azure Grain Directory $(DefaultTargetFrameworks) Orleans.GrainDirectory.AzureStorage Orleans.GrainDirectory.AzureStorage true $(DefineConstants);ORLEANS_DIRECTORY ================================================ FILE: src/Azure/Orleans.GrainDirectory.AzureStorage/README.md ================================================ # Microsoft Orleans Grain Directory for Azure Storage ## Introduction Microsoft Orleans Grain Directory for Azure Storage provides a grain directory implementation using Azure Storage. The grain directory is used to locate active grain instances across the cluster, and this package allows Orleans to store that information in Azure Storage. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.GrainDirectory.AzureStorage ``` ## Example - Configuring Azure Storage Grain Directory ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Storage as grain directory .UseAzureStorageGrainDirectoryAsDefault(options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; options.TableName = "GrainDirectory"; }); }); // Run the host await builder.RunAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Configuration Guide](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/) - [Implementation Details](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/index) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/AzureAppendBlobLogStorage.cs ================================================ using Azure; using Azure.Storage.Blobs.Specialized; using Azure.Storage.Blobs.Models; using System.Runtime.CompilerServices; using Azure.Storage.Sas; using Orleans.Serialization.Buffers; using Microsoft.Extensions.Logging; namespace Orleans.Journaling; internal sealed partial class AzureAppendBlobLogStorage : IStateMachineStorage { private static readonly AppendBlobCreateOptions CreateOptions = new() { Conditions = new() { IfNoneMatch = ETag.All } }; private readonly AppendBlobClient _client; private readonly ILogger _logger; private readonly LogExtentBuilder.ReadOnlyStream _stream; private readonly AppendBlobAppendBlockOptions _appendOptions; private bool _exists; private int _numBlocks; public bool IsCompactionRequested => _numBlocks > 10; public AzureAppendBlobLogStorage(AppendBlobClient client, ILogger logger) { _client = client; _logger = logger; _stream = new(); // For the first request, if we have not performed a read yet, we want to guard against clobbering an existing blob. _appendOptions = new AppendBlobAppendBlockOptions() { Conditions = new AppendBlobRequestConditions { IfNoneMatch = ETag.All } }; } public async ValueTask AppendAsync(LogExtentBuilder value, CancellationToken cancellationToken) { if (!_exists) { var response = await _client.CreateAsync(CreateOptions, cancellationToken); _appendOptions.Conditions.IfNoneMatch = default; _appendOptions.Conditions.IfMatch = response.Value.ETag; _exists = true; } _stream.SetBuilder(value); var result = await _client.AppendBlockAsync(_stream, _appendOptions, cancellationToken).ConfigureAwait(false); LogAppend(_logger, value.Length, _client.BlobContainerName, _client.Name); _stream.Reset(); _appendOptions.Conditions.IfNoneMatch = default; _appendOptions.Conditions.IfMatch = result.Value.ETag; _numBlocks = result.Value.BlobCommittedBlockCount; } public async ValueTask DeleteAsync(CancellationToken cancellationToken) { var conditions = new BlobRequestConditions { IfMatch = _appendOptions.Conditions.IfMatch }; await _client.DeleteAsync(conditions: conditions, cancellationToken: cancellationToken).ConfigureAwait(false); // Expect no blob to have been created when we append to it. _appendOptions.Conditions.IfNoneMatch = ETag.All; _appendOptions.Conditions.IfMatch = default; _numBlocks = 0; } public async IAsyncEnumerable ReadAsync([EnumeratorCancellation] CancellationToken cancellationToken) { Response result; try { // If the blob was not newly created, then download the blob. result = await _client.DownloadStreamingAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } catch (RequestFailedException exception) when (exception.Status is 404) { _exists = false; yield break; } // If the blob has a size of zero, check for a snapshot and restore the blob from the snapshot if one exists. if (result.Value.Details.ContentLength == 0) { if (result.Value.Details.Metadata.TryGetValue("snapshot", out var snapshot) && snapshot is { Length: > 0 }) { result = await CopyFromSnapshotAsync(result.Value.Details.ETag, snapshot, cancellationToken).ConfigureAwait(false); } } _numBlocks = result.Value.Details.BlobCommittedBlockCount; _appendOptions.Conditions.IfNoneMatch = default; _appendOptions.Conditions.IfMatch = result.Value.Details.ETag; _exists = true; // Read everything into a single log segment. We could change this to read in chunks, // yielding when the stream does not return synchronously, if we wanted to support larger state machines. var rawStream = result.Value.Content; using var buffer = new ArcBufferWriter(); while (true) { var mem = buffer.GetMemory(); var bytesRead = await rawStream.ReadAsync(mem, cancellationToken); if (bytesRead == 0) { if (buffer.Length > 0) { LogRead(_logger, buffer.Length, _client.BlobContainerName, _client.Name); yield return new LogExtent(buffer.ConsumeSlice(buffer.Length)); } yield break; } buffer.AdvanceWriter(bytesRead); } } private async Task> CopyFromSnapshotAsync(ETag eTag, string snapshotDetail, CancellationToken cancellationToken) { // Read snapshot and append it to the blob. var snapshot = _client.WithSnapshot(snapshotDetail); var uri = snapshot.GenerateSasUri(permissions: BlobSasPermissions.Read, expiresOn: DateTimeOffset.UtcNow.AddHours(1)); var copyResult = await _client.SyncCopyFromUriAsync( uri, new BlobCopyFromUriOptions { DestinationConditions = new BlobRequestConditions { IfNoneMatch = eTag } }, cancellationToken).ConfigureAwait(false); if (copyResult.Value.CopyStatus is not CopyStatus.Success) { throw new InvalidOperationException($"Copy did not complete successfully. Status: {copyResult.Value.CopyStatus}"); } var result = await _client.DownloadStreamingAsync(cancellationToken: cancellationToken).ConfigureAwait(false); _exists = true; return result; } public async ValueTask ReplaceAsync(LogExtentBuilder value, CancellationToken cancellationToken) { // Create a snapshot of the blob for recovery purposes. var blobSnapshot = await _client.CreateSnapshotAsync(conditions: _appendOptions.Conditions, cancellationToken: cancellationToken).ConfigureAwait(false); // Open the blob for writing, overwriting existing contents. var createOptions = new AppendBlobCreateOptions() { Conditions = _appendOptions.Conditions, Metadata = new Dictionary { ["snapshot"] = blobSnapshot.Value.Snapshot }, }; var createResult = await _client.CreateAsync(createOptions, cancellationToken).ConfigureAwait(false); _appendOptions.Conditions.IfMatch = createResult.Value.ETag; _appendOptions.Conditions.IfNoneMatch = default; // Write the state machine snapshot. _stream.SetBuilder(value); var result = await _client.AppendBlockAsync(_stream, _appendOptions, cancellationToken).ConfigureAwait(false); LogReplace(_logger, _client.BlobContainerName, _client.Name, value.Length); _stream.Reset(); _appendOptions.Conditions.IfNoneMatch = default; _appendOptions.Conditions.IfMatch = result.Value.ETag; _numBlocks = result.Value.BlobCommittedBlockCount; // Delete the blob snapshot. await _client.WithSnapshot(blobSnapshot.Value.Snapshot).DeleteAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } [LoggerMessage( Level = LogLevel.Debug, Message = "Appended {Length} bytes to blob \"{ContainerName}/{BlobName}\"")] private static partial void LogAppend(ILogger logger, long length, string containerName, string blobName); [LoggerMessage( Level = LogLevel.Debug, Message = "Read {Length} bytes from blob \"{ContainerName}/{BlobName}\"")] private static partial void LogRead(ILogger logger, long length, string containerName, string blobName); [LoggerMessage( Level = LogLevel.Debug, Message = "Replaced blob \"{ContainerName}/{BlobName}\", writing {Length} bytes")] private static partial void LogReplace(ILogger logger, string containerName, string blobName, long length); } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/AzureAppendBlobStateMachineStorageOptions.cs ================================================ using Azure; using Azure.Storage.Blobs; using Azure.Storage; using Azure.Core; using Orleans.Runtime; namespace Orleans.Journaling; /// /// Options for configuring the Azure Append Blob state machine storage provider. /// public sealed class AzureAppendBlobStateMachineStorageOptions { private BlobServiceClient? _blobServiceClient; /// /// Container name where state machine state is stored. /// public string ContainerName { get; set; } = DEFAULT_CONTAINER_NAME; public const string DEFAULT_CONTAINER_NAME = "state"; /// /// Gets or sets the delegate used to generate the blob name for a given grain. /// public Func GetBlobName { get; set; } = DefaultGetBlobName; private static readonly Func DefaultGetBlobName = static (GrainId grainId) => $"{grainId}.bin"; /// /// Options to be used when configuring the blob storage client, or to use the default options. /// public BlobClientOptions? ClientOptions { get; set; } /// /// Gets or sets the client used to access the Azure Blob Service. /// public BlobServiceClient? BlobServiceClient { get => _blobServiceClient; set { ArgumentNullException.ThrowIfNull(value); _blobServiceClient = value; CreateClient = ct => Task.FromResult(value); } } /// /// The optional delegate used to create a instance. /// internal Func>? CreateClient { get; private set; } /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// /// A function for building container factory instances. /// public Func BuildContainerFactory { get; set; } = static (provider, options) => new DefaultBlobContainerFactory(options); /// /// Configures the using a connection string. /// public void ConfigureBlobServiceClient(string connectionString) { ArgumentException.ThrowIfNullOrWhiteSpace(connectionString); CreateClient = ct => Task.FromResult(new BlobServiceClient(connectionString, ClientOptions)); } /// /// Configures the using an authenticated service URI. /// public void ConfigureBlobServiceClient(Uri serviceUri) { ArgumentNullException.ThrowIfNull(serviceUri); CreateClient = ct => Task.FromResult(new BlobServiceClient(serviceUri, ClientOptions)); } /// /// Configures the using the provided callback. /// public void ConfigureBlobServiceClient(Func> createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using an authenticated service URI and a . /// public void ConfigureBlobServiceClient(Uri serviceUri, TokenCredential tokenCredential) { ArgumentNullException.ThrowIfNull(serviceUri); ArgumentNullException.ThrowIfNull(tokenCredential); CreateClient = ct => Task.FromResult(new BlobServiceClient(serviceUri, tokenCredential, ClientOptions)); } /// /// Configures the using an authenticated service URI and a . /// public void ConfigureBlobServiceClient(Uri serviceUri, AzureSasCredential azureSasCredential) { ArgumentNullException.ThrowIfNull(serviceUri); ArgumentNullException.ThrowIfNull(azureSasCredential); CreateClient = ct => Task.FromResult(new BlobServiceClient(serviceUri, azureSasCredential, ClientOptions)); } /// /// Configures the using an authenticated service URI and a . /// public void ConfigureBlobServiceClient(Uri serviceUri, StorageSharedKeyCredential sharedKeyCredential) { ArgumentNullException.ThrowIfNull(serviceUri); ArgumentNullException.ThrowIfNull(sharedKeyCredential); CreateClient = ct => Task.FromResult(new BlobServiceClient(serviceUri, sharedKeyCredential, ClientOptions)); } } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/AzureAppendBlobStateMachineStorageProvider.cs ================================================ using Azure.Storage.Blobs.Specialized; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.Journaling; internal sealed class AzureAppendBlobStateMachineStorageProvider( IOptions options, IServiceProvider serviceProvider, ILogger logger) : IStateMachineStorageProvider, ILifecycleParticipant { private readonly IBlobContainerFactory _containerFactory = options.Value.BuildContainerFactory(serviceProvider, options.Value); private readonly AzureAppendBlobStateMachineStorageOptions _options = options.Value; private async Task Initialize(CancellationToken cancellationToken) { var client = await _options.CreateClient!(cancellationToken); await _containerFactory.InitializeAsync(client, cancellationToken).ConfigureAwait(false); } public IStateMachineStorage Create(IGrainContext grainContext) { var container = _containerFactory.GetBlobContainerClient(grainContext.GrainId); var blobName = _options.GetBlobName(grainContext.GrainId); var blobClient = container.GetAppendBlobClient(blobName); return new AzureAppendBlobLogStorage(blobClient, logger); } public void Participate(ISiloLifecycle observer) { observer.Subscribe( nameof(AzureAppendBlobStateMachineStorageProvider), ServiceLifecycleStage.RuntimeInitialize, onStart: Initialize); } } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/AzureBlobStorageGrainJournalingProviderBuilder.cs ================================================ using Azure.Storage.Blobs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Hosting; using Orleans.Journaling; using Orleans.Providers; [assembly: RegisterProvider("AzureBlobStorage", "GrainJournaling", "Silo", typeof(AzureBlobStorageGrainJournalingProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureBlobStorageGrainJournalingProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string? name, IConfigurationSection configurationSection) { builder.AddAzureAppendBlobStateMachineStorage(); var optionsBuilder = builder.Services.AddOptions(); optionsBuilder.Configure((options, services) => { var containerName = configurationSection["ContainerName"]; if (!string.IsNullOrEmpty(containerName)) { options.ContainerName = containerName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.BlobServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.BlobServiceClient = new(uri); } else { options.BlobServiceClient = new(connectionString); } } } }); } } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/AzureBlobStorageHostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration.Internal; using Orleans.Runtime; using Orleans.Hosting; namespace Orleans.Journaling; public static class AzureBlobStorageHostingExtensions { public static ISiloBuilder AddAzureAppendBlobStateMachineStorage(this ISiloBuilder builder) => builder.AddAzureAppendBlobStateMachineStorage(configure: null); public static ISiloBuilder AddAzureAppendBlobStateMachineStorage(this ISiloBuilder builder, Action? configure) { builder.AddStateMachineStorage(); var services = builder.Services; var options = builder.Services.AddOptions(); if (configure is not null) { options.Configure(configure); } if (services.Any(service => service.ServiceType.Equals(typeof(AzureAppendBlobStateMachineStorageProvider)))) { return builder; } builder.Services.AddSingleton(); builder.Services.AddFromExisting(); builder.Services.AddFromExisting, AzureAppendBlobStateMachineStorageProvider>(); return builder; } } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/DefaultBlobContainerFactory.cs ================================================ using Azure.Storage.Blobs; using Orleans.Runtime; namespace Orleans.Journaling; /// /// A default blob container factory that uses the default container name. /// /// /// Initializes a new instance of the class. /// /// The blob storage options internal sealed class DefaultBlobContainerFactory(AzureAppendBlobStateMachineStorageOptions options) : IBlobContainerFactory { private BlobContainerClient _defaultContainer = null!; /// public BlobContainerClient GetBlobContainerClient(GrainId grainId) => _defaultContainer; /// public async Task InitializeAsync(BlobServiceClient client, CancellationToken cancellationToken) { _defaultContainer = client.GetBlobContainerClient(options.ContainerName); await _defaultContainer.CreateIfNotExistsAsync(cancellationToken: cancellationToken); } } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/IBlobContainerFactory.cs ================================================ using Azure.Storage.Blobs; using Orleans.Runtime; namespace Orleans.Journaling; /// /// A factory for building container clients for blob storage using GrainId /// public interface IBlobContainerFactory { /// /// Gets the container which should be used for the specified grain. /// /// The grain id /// A configured blob client public BlobContainerClient GetBlobContainerClient(GrainId grainId); /// /// Initialize any required dependencies using the provided client and options. /// /// The connected blob client /// A token used to cancel the request. /// A representing the asynchronous operation. public Task InitializeAsync(BlobServiceClient client, CancellationToken cancellationToken); } ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/Orleans.Journaling.AzureStorage.csproj ================================================  Microsoft.Orleans.Journaling.AzureStorage README.md $(DefaultTargetFrameworks) enable enable $(VersionSuffix).alpha.1 alpha.1 true ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/Properties/AssemblyInfo.cs ================================================ using System.Diagnostics.CodeAnalysis; [assembly: Experimental("ORLEANSEXP005")] ================================================ FILE: src/Azure/Orleans.Journaling.AzureStorage/README.md ================================================ # Microsoft Orleans Journaling for Azure Storage ## Introduction Microsoft Orleans Journaling for Azure Storage provides an Azure Storage implementation of the Orleans Journaling provider. This allows logging and tracking of grain operations using Azure Storage as a backing store. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Journaling.AzureStorage ``` ## Example - Configuring Azure Storage Journaling ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Configuration; using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using MyGrainNamespace; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Storage as a journaling provider .AddAzureAppendBlobStateMachineStorage(optionsBuilder => { optionsBuilder.Configure((options, serviceProvider) => options.BlobServiceClient = serviceProvider.GetRequiredService()); }); }); var host = await builder.StartAsync(); // Get a reference to the grain var shoppingCart = host.Services.GetRequiredService() .GetGrain("user1-cart"); // Use the grain await shoppingCart.UpdateItem("apple", 5, 0); await shoppingCart.UpdateItem("banana", 3, 1); // Get and print the cart contents var (contents, version) = await shoppingCart.GetCart(); Console.WriteLine($"Shopping cart (version {version}):"); foreach (var item in contents) { Console.WriteLine($"- {item.Key}: {item.Value}"); } // Wait for the application to terminate await host.WaitForShutdownAsync(); ``` ## Example - Using Journaling in a Grain ```csharp using Orleans.Runtime; namespace MyGrainNamespace; public interface IShoppingCartGrain : IGrain { ValueTask<(bool success, long version)> UpdateItem(string itemId, int quantity, long version); ValueTask<(Dictionary Contents, long Version)> GetCart(); ValueTask GetVersion(); ValueTask<(bool success, long version)> Clear(long version); } public class ShoppingCartGrain( [FromKeyedServices("shopping-cart")] IDurableDictionary cart, [FromKeyedServices("version")] IDurableValue version) : DurableGrain, IShoppingCartGrain { private readonly IDurableValue _version = version; public async ValueTask<(bool success, long version)> UpdateItem(string itemId, int quantity, long version) { if (_version.Value != version) { // Conflict return (false, _version.Value); } if (quantity == 0) { cart.Remove(itemId); } else { cart[itemId] = quantity; } _version.Value++; await WriteStateAsync(); return (true, _version.Value); } public ValueTask<(Dictionary Contents, long Version)> GetCart() => new((cart.ToDictionary(), _version.Value)); public ValueTask GetVersion() => new(_version.Value); public async ValueTask<(bool success, long version)> Clear(long version) { if (_version.Value != version) { // Conflict return (false, _version.Value); } cart.Clear(); _version.Value++; await WriteStateAsync(); return (true, _version.Value); } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Journaling](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/event-sourcing) - [Event Sourcing Grains](https://learn.microsoft.com/en-us/dotnet/orleans/grains/event-sourcing) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Hosting/AzureBlobGrainStorageServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Hosting; using Orleans.Storage; namespace Orleans.Hosting { /// /// extensions. /// public static class AzureBlobGrainStorageServiceCollectionExtensions { /// /// Configure silo to use azure blob storage as the default grain storage. /// public static IServiceCollection AddAzureBlobGrainStorageAsDefault(this IServiceCollection services, Action configureOptions) { return services.AddAzureBlobGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use azure blob storage for grain storage. /// public static IServiceCollection AddAzureBlobGrainStorage(this IServiceCollection services, string name, Action configureOptions) { return services.AddAzureBlobGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use azure blob storage as the default grain storage. /// public static IServiceCollection AddAzureBlobGrainStorageAsDefault(this IServiceCollection services, Action> configureOptions = null) { return services.AddAzureBlobGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure blob storage for grain storage. /// public static IServiceCollection AddAzureBlobGrainStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient(sp => new AzureBlobStorageOptionsValidator(sp.GetRequiredService>().Get(name), name)); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); services.ConfigureNamedOptionForLogging(name); return services.AddGrainStorage(name, AzureBlobGrainStorageFactory.Create); } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Hosting/AzureBlobSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; namespace Orleans.Hosting { public static class AzureBlobSiloBuilderExtensions { /// /// Configure silo to use azure blob storage as the default grain storage. /// public static ISiloBuilder AddAzureBlobGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddAzureBlobGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure blob storage for grain storage. /// public static ISiloBuilder AddAzureBlobGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddAzureBlobGrainStorage(name, configureOptions)); } /// /// Configure silo to use azure blob storage as the default grain storage. /// public static ISiloBuilder AddAzureBlobGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddAzureBlobGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure blob storage for grain storage. /// public static ISiloBuilder AddAzureBlobGrainStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder.ConfigureServices(services => services.AddAzureBlobGrainStorage(name, configureOptions)); } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Hosting/AzureBlobStorageGrainStorageProviderBuilder.cs ================================================ using System; using Azure.Storage.Blobs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Storage; [assembly: RegisterProvider("AzureBlobStorage", "GrainStorage", "Silo", typeof(AzureBlobStorageGrainStorageProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureBlobStorageGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAzureBlobGrainStorage(name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var containerName = configurationSection["ContainerName"]; if (!string.IsNullOrEmpty(containerName)) { options.ContainerName = containerName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.BlobServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.BlobServiceClient = new(uri); } else { options.BlobServiceClient = new(connectionString); } } } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } })); } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Hosting/AzureTableSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Hosting; using Orleans.Storage; namespace Orleans.Hosting { public static class AzureTableSiloBuilderExtensions { /// /// Configure silo to use azure table storage as the default grain storage. /// public static ISiloBuilder AddAzureTableGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddAzureTableGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure table storage for grain storage. /// public static ISiloBuilder AddAzureTableGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddAzureTableGrainStorage(name, ob => ob.Configure(configureOptions))); } /// /// Configure silo to use azure table storage as the default grain storage. /// public static ISiloBuilder AddAzureTableGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddAzureTableGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure table storage for grain storage. /// public static ISiloBuilder AddAzureTableGrainStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder.ConfigureServices(services => services.AddAzureTableGrainStorage(name, configureOptions)); } internal static IServiceCollection AddAzureTableGrainStorage( this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient(sp => new AzureTableGrainStorageOptionsValidator(sp.GetRequiredService>().Get(name), name)); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); services.ConfigureNamedOptionForLogging(name); return services.AddGrainStorage(name, AzureTableGrainStorageFactory.Create); } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Hosting/AzureTableStorageGrainStorageProviderBuilder.cs ================================================ using System; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Storage; [assembly: RegisterProvider("AzureTableStorage", "GrainStorage", "Silo", typeof(AzureTableStorageGrainStorageProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureTableStorageGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAzureTableGrainStorage(name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var tableName = configurationSection["TableName"]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.TableServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new TableServiceClient(uri); } else { options.TableServiceClient = new TableServiceClient(connectionString); } } } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } })); } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Orleans.Persistence.AzureStorage.csproj ================================================ Microsoft.Orleans.Persistence.AzureStorage Microsoft Orleans Azure Storage Persistence Provider Microsoft Orleans persistence providers backed by Azure Storage $(PackageTags) Azure Table Blob Storage $(DefaultTargetFrameworks) Orleans.Persistence.AzureStorage Orleans.Persistence.AzureStorage true $(DefineConstants);ORLEANS_PERSISTENCE README.md ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/AzureProviderErrorCode.cs ================================================ namespace Orleans.Providers.Azure { internal enum AzureProviderErrorCode { ProvidersBase = 200000, // Azure storage provider related AzureTableProviderBase = ProvidersBase + 100, AzureTableProvider_DataNotFound = AzureTableProviderBase + 1, AzureTableProvider_ReadingData = AzureTableProviderBase + 2, AzureTableProvider_WritingData = AzureTableProviderBase + 3, AzureTableProvider_Storage_Reading = AzureTableProviderBase + 4, AzureTableProvider_Storage_Writing = AzureTableProviderBase + 5, AzureTableProvider_Storage_DataRead = AzureTableProviderBase + 6, AzureTableProvider_WriteError = AzureTableProviderBase + 7, AzureTableProvider_DeleteError = AzureTableProviderBase + 8, AzureTableProvider_InitProvider = AzureTableProviderBase + 9, AzureTableProvider_ParamConnectionString = AzureTableProviderBase + 10, AzureBlobProviderBase = ProvidersBase + 300, AzureBlobProvider_BlobNotFound = AzureBlobProviderBase + 1, AzureBlobProvider_ContainerNotFound = AzureBlobProviderBase + 2, AzureBlobProvider_BlobEmpty = AzureBlobProviderBase + 3, AzureBlobProvider_ReadingData = AzureBlobProviderBase + 4, AzureBlobProvider_WritingData = AzureBlobProviderBase + 5, AzureBlobProvider_Storage_Reading = AzureBlobProviderBase + 6, AzureBlobProvider_Storage_Writing = AzureBlobProviderBase + 7, AzureBlobProvider_Storage_DataRead = AzureBlobProviderBase + 8, AzureBlobProvider_WriteError = AzureBlobProviderBase + 9, AzureBlobProvider_DeleteError = AzureBlobProviderBase + 10, AzureBlobProvider_InitProvider = AzureBlobProviderBase + 11, AzureBlobProvider_ParamConnectionString = AzureBlobProviderBase + 12, AzureBlobProvider_ReadError = AzureBlobProviderBase + 13, AzureBlobProvider_ClearError = AzureBlobProviderBase + 14, AzureBlobProvider_ClearingData = AzureBlobProviderBase + 15, AzureBlobProvider_Cleared = AzureBlobProviderBase + 16, } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureBlobStorage.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers.Azure; using Orleans.Runtime; using Orleans.Serialization.Serializers; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Orleans.Storage { /// /// Simple storage provider for writing grain state data to Azure blob storage in JSON format. /// public partial class AzureBlobGrainStorage : IGrainStorage, ILifecycleParticipant { private readonly ILogger logger; private readonly string name; private readonly IBlobContainerFactory blobContainerFactory; private readonly IActivatorProvider _activatorProvider; private readonly AzureBlobStorageOptions options; private readonly IGrainStorageSerializer grainStorageSerializer; /// Default constructor public AzureBlobGrainStorage( string name, AzureBlobStorageOptions options, IBlobContainerFactory blobContainerFactory, IActivatorProvider activatorProvider, ILogger logger) { this.name = name; this.options = options; this.blobContainerFactory = blobContainerFactory; _activatorProvider = activatorProvider; this.grainStorageSerializer = options.GrainStorageSerializer; this.logger = logger; } /// Read state data function for this storage provider. /// public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var blobName = GetBlobName(grainType, grainId); var container = this.blobContainerFactory.GetBlobContainerClient(grainId); LogTraceReading(grainType, grainId, grainState.ETag, blobName, container.Name); try { var blob = container.GetBlobClient(blobName); var response = await blob.DownloadContentAsync(); grainState.ETag = response.Value.Details.ETag.ToString(); var contents = response.Value.Content; T? loadedState; if (contents is null || contents.IsEmpty) { loadedState = default; LogTraceBlobEmptyReading(grainType, grainId, grainState.ETag, blobName, container.Name); } else { loadedState = this.ConvertFromStorageFormat(contents); LogTraceDataRead(grainType, grainId, grainState.ETag, blobName, container.Name); } grainState.State = loadedState ?? CreateInstance(); grainState.RecordExists = loadedState is not null; } catch (RequestFailedException ex) when (ex.IsNotFound()) { ResetGrainState(grainState); if (ex.IsBlobNotFound()) { LogTraceBlobNotFoundReading(grainType, grainId, grainState.ETag, blobName, container.Name); } else if (ex.IsContainerNotFound()) { LogTraceContainerNotFoundReading(grainType, grainId, grainState.ETag, blobName, container.Name); } } catch (Exception ex) { LogErrorReading(ex, grainType, grainId, grainState.ETag, blobName, container.Name); throw; } } private void ResetGrainState(IGrainState grainState) { grainState.ETag = null; grainState.RecordExists = false; grainState.State = CreateInstance(); } private static string GetBlobName(string grainType, GrainId grainId) => $"{grainType}-{grainId}.json"; /// Write state data function for this storage provider. /// public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var blobName = GetBlobName(grainType, grainId); var container = this.blobContainerFactory.GetBlobContainerClient(grainId); try { LogTraceWriting(grainType, grainId, grainState.ETag, blobName, container.Name); var contents = ConvertToStorageFormat(grainState.State); var blob = container.GetBlobClient(blobName); await WriteStateAndCreateContainerIfNotExists(grainType, grainId, grainState, contents, "application/octet-stream", blob); LogTraceDataWritten(grainType, grainId, grainState.ETag, blobName, container.Name); } catch (Exception ex) { LogErrorWriting(ex, grainType, grainId, grainState.ETag, blobName, container.Name); throw; } } /// Clear / Delete state data function for this storage provider. /// public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var blobName = GetBlobName(grainType, grainId); var container = this.blobContainerFactory.GetBlobContainerClient(grainId); try { LogTraceClearing(grainType, grainId, grainState.ETag, blobName, container.Name); var blob = container.GetBlobClient(blobName); var conditions = string.IsNullOrEmpty(grainState.ETag) ? new BlobRequestConditions { IfNoneMatch = ETag.All } : new BlobRequestConditions { IfMatch = new ETag(grainState.ETag) }; if (options.DeleteStateOnClear) { await DoOptimisticUpdate( static state => state.blob.DeleteIfExistsAsync(DeleteSnapshotsOption.None, conditions: state.conditions), (blob, conditions), blob, grainState.ETag).ConfigureAwait(false); grainState.ETag = null; } else { var options = new BlobUploadOptions { Conditions = conditions }; var response = await DoOptimisticUpdate( static state => state.blob.UploadAsync(BinaryData.Empty, state.options), (blob, options, conditions), blob, grainState.ETag).ConfigureAwait(false); grainState.ETag = response.Value.ETag.ToString(); } grainState.RecordExists = false; grainState.State = CreateInstance(); LogTraceCleared(grainType, grainId, grainState.ETag, blobName, container.Name); } catch (Exception ex) { LogErrorClearing(ex, grainType, grainId, grainState.ETag, blobName, container.Name); throw; } } private async Task WriteStateAndCreateContainerIfNotExists(string grainType, GrainId grainId, IGrainState grainState, BinaryData contents, string mimeType, BlobClient blob) { var container = this.blobContainerFactory.GetBlobContainerClient(grainId); try { var conditions = string.IsNullOrEmpty(grainState.ETag) ? new BlobRequestConditions { IfNoneMatch = ETag.All } : new BlobRequestConditions { IfMatch = new ETag(grainState.ETag) }; var options = new BlobUploadOptions { HttpHeaders = new BlobHttpHeaders { ContentType = mimeType }, Conditions = conditions, }; var result = await DoOptimisticUpdate( static state => state.blob.UploadAsync(state.contents, state.options), (blob, contents, options), blob, grainState.ETag) .ConfigureAwait(false); grainState.ETag = result.Value.ETag.ToString(); grainState.RecordExists = true; } catch (RequestFailedException exception) when (exception.IsContainerNotFound()) { // if the container does not exist, create it, and make another attempt LogTraceContainerNotFound(grainType, grainId, grainState.ETag, blob.Name, container.Name); await container.CreateIfNotExistsAsync().ConfigureAwait(false); await WriteStateAndCreateContainerIfNotExists(grainType, grainId, grainState, contents, mimeType, blob).ConfigureAwait(false); } } private static async Task DoOptimisticUpdate(Func> updateOperation, TState state, BlobClient blob, string currentETag) { try { return await updateOperation(state).ConfigureAwait(false); } catch (RequestFailedException ex) when (ex.IsPreconditionFailed() || ex.IsConflict() || ex.IsNotFound() && !ex.IsContainerNotFound()) { throw new InconsistentStateException($"Blob storage condition not Satisfied. BlobName: {blob.Name}, Container: {blob.BlobContainerName}, CurrentETag: {currentETag}", "Unknown", currentETag, ex); } } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.name), this.options.InitStage, Init); } /// Initialization function for this storage provider. private async Task Init(CancellationToken ct) { var stopWatch = Stopwatch.StartNew(); try { LogDebugInitializing(this.name, this.options.ContainerName); if (options.CreateClient is not { } createClient) { throw new OrleansConfigurationException($"No credentials specified. Use the {options.GetType().Name}.{nameof(AzureBlobStorageOptions.ConfigureBlobServiceClient)} method to configure the Azure Blob Service client."); } var client = await createClient(); await this.blobContainerFactory.InitializeAsync(client); stopWatch.Stop(); LogInformationInitProvider(this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds); } catch (Exception ex) { stopWatch.Stop(); LogErrorFromInit(ex, this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds); throw; } } /// /// Serialize to the configured storage format /// /// The grain state data to be serialized private BinaryData ConvertToStorageFormat(T grainState) => this.grainStorageSerializer.Serialize(grainState); /// /// Deserialize from the configured storage format /// /// The serialized contents. private T? ConvertFromStorageFormat(BinaryData contents) => this.grainStorageSerializer.Deserialize(contents); private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_Storage_Reading, Message = "Reading: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceReading(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_BlobEmpty, Message = "BlobEmpty reading: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceBlobEmptyReading(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_Storage_DataRead, Message = "Read: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceDataRead(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_ReadError, Message = "Error reading: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogErrorReading(Exception exception, string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_BlobNotFound, Message = "BlobNotFound reading: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceBlobNotFoundReading(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_ContainerNotFound, Message = "ContainerNotFound reading: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceContainerNotFoundReading(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_Storage_Writing, Message = "Writing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceWriting(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_Storage_DataRead, Message = "Written: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceDataWritten(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_WriteError, Message = "Error writing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to BlobName={BlobName} in Container={ContainerName}" )] private partial void LogErrorWriting(Exception exception, string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_ClearingData, Message = "Clearing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceClearing(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_Cleared, Message = "Cleared: GrainType={GrainType} GrainId={GrainId} ETag={ETag} BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceCleared(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_ClearError, Message = "Error clearing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} BlobName={BlobName} in Container={ContainerName}" )] private partial void LogErrorClearing(Exception exception, string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_ContainerNotFound, Message = "Creating container: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to BlobName={BlobName} in Container={ContainerName}" )] private partial void LogTraceContainerNotFound(string grainType, GrainId grainId, string? eTag, string blobName, string containerName); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)AzureProviderErrorCode.AzureTableProvider_InitProvider, Message = "AzureBlobGrainStorage {Name} is initializing: ContainerName={ContainerName}" )] private partial void LogDebugInitializing(string name, string containerName); [LoggerMessage( Level = LogLevel.Information, EventId = (int)AzureProviderErrorCode.AzureBlobProvider_InitProvider, Message = "Initializing provider {ProviderName} of type {ProviderType} in stage {Stage} took {ElapsedMilliseconds} Milliseconds." )] private partial void LogInformationInitProvider(string providerName, string providerType, int stage, long elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Provider_ErrorFromInit, Message = "Initialization failed for provider {ProviderName} of type {ProviderType} in stage {Stage} in {ElapsedMilliseconds} Milliseconds." )] private partial void LogErrorFromInit(Exception exception, string providerName, string providerType, int stage, long elapsedMilliseconds); } public static class AzureBlobGrainStorageFactory { public static AzureBlobGrainStorage Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); var options = optionsMonitor.Get(name); var containerFactory = options.BuildContainerFactory(services, options); return ActivatorUtilities.CreateInstance(services, name, options, containerFactory); } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureBlobStorageOptions.cs ================================================ using System; using System.Threading.Tasks; using Azure; using Azure.Core; using Azure.Storage; using Azure.Storage.Blobs; using Microsoft.Extensions.DependencyInjection; using Orleans.Persistence.AzureStorage; using Orleans.Runtime; using Orleans.Storage; namespace Orleans.Configuration { public class AzureBlobStorageOptions : IStorageProviderSerializerOptions { private BlobServiceClient _blobServiceClient; /// /// Container name where grain stage is stored /// public string ContainerName { get; set; } = DEFAULT_CONTAINER_NAME; public const string DEFAULT_CONTAINER_NAME = "grainstate"; /// /// Options to be used when configuring the blob storage client, or to use the default options. /// public BlobClientOptions ClientOptions { get; set; } /// /// The optional delegate used to create a instance. /// internal Func> CreateClient { get; private set; } /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// public IGrainStorageSerializer GrainStorageSerializer { get; set; } /// /// Gets or sets the client used to access the Azure Blob Service. /// public BlobServiceClient BlobServiceClient { get => _blobServiceClient; set { _blobServiceClient = value; CreateClient = () => Task.FromResult(value); } } /// /// Gets or sets a value indicating whether to delete the state when is called. Defaults to true. /// public bool DeleteStateOnClear { get; set; } = true; /// /// A function for building container factory instances /// public Func BuildContainerFactory { get; set; } = static (provider, options) => ActivatorUtilities.CreateInstance(provider, options); /// /// Configures the using a connection string. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(string connectionString) { BlobServiceClient = new BlobServiceClient(connectionString, ClientOptions); } /// /// Configures the using an authenticated service URI. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri) { BlobServiceClient = new BlobServiceClient(serviceUri, ClientOptions); } /// /// Configures the using the provided callback. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Func> createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, TokenCredential tokenCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, tokenCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, AzureSasCredential azureSasCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, azureSasCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, StorageSharedKeyCredential sharedKeyCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, sharedKeyCredential, ClientOptions); } } /// /// Configuration validator for AzureBlobStorageOptions /// public class AzureBlobStorageOptionsValidator : IConfigurationValidator { private readonly AzureBlobStorageOptions options; private readonly string name; /// /// Constructor /// /// The option to be validated. /// The option name to be validated. public AzureBlobStorageOptionsValidator(AzureBlobStorageOptions options, string name) { this.options = options; this.name = name; } public void ValidateConfiguration() { if (this.options.CreateClient is null) { throw new OrleansConfigurationException($"No credentials specified. Use the {options.GetType().Name}.{nameof(AzureBlobStorageOptions.ConfigureBlobServiceClient)} method to configure the Azure Blob Service client."); } try { AzureBlobUtils.ValidateContainerName(options.ContainerName); AzureBlobUtils.ValidateBlobName(this.name); } catch(ArgumentException e) { throw new OrleansConfigurationException( $"Configuration for AzureBlobStorageOptions {name} is invalid. {nameof(this.options.ContainerName)} is not valid", e); } } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using Azure; using Azure.Data.Tables; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Overrides; using Orleans.Persistence.AzureStorage; using Orleans.Providers.Azure; using Orleans.Runtime; using Orleans.Serialization.Serializers; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Orleans.Storage { /// /// Simple storage for writing grain state data to Azure table storage. /// public partial class AzureTableGrainStorage : IGrainStorage, IRestExceptionDecoder, ILifecycleParticipant { private readonly AzureTableStorageOptions options; private readonly ClusterOptions clusterOptions; private readonly IGrainStorageSerializer storageSerializer; private readonly ILogger logger; private readonly IActivatorProvider _activatorProvider; private GrainStateTableDataManager? tableDataManager; // each property can hold 64KB of data and each entity can take 1MB in total, so 15 full properties take // 15 * 64 = 960 KB leaving room for the primary key, timestamp etc private const int MAX_DATA_CHUNK_SIZE = 64 * 1024; private const int MAX_STRING_PROPERTY_LENGTH = 32 * 1024; private const int MAX_DATA_CHUNKS_COUNT = 15; private const string BINARY_DATA_PROPERTY_NAME = "Data"; private const string STRING_DATA_PROPERTY_NAME = "StringData"; private readonly string name; /// Default constructor public AzureTableGrainStorage( string name, AzureTableStorageOptions options, IOptions clusterOptions, ILogger logger, IActivatorProvider activatorProvider) { this.options = options; this.clusterOptions = clusterOptions.Value; this.name = name; this.storageSerializer = options.GrainStorageSerializer; this.logger = logger; _activatorProvider = activatorProvider; } /// Read state data function for this storage provider. /// public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (tableDataManager == null) throw new ArgumentException("GrainState-Table property not initialized"); string pk = GetKeyString(grainId); LogTraceReadingGrainState(grainType, pk, grainId, this.options.TableName); string partitionKey = pk; string rowKey = AzureTableUtils.SanitizeTableProperty(grainType); var entity = await tableDataManager.Read(partitionKey, rowKey).ConfigureAwait(false); if (entity is not null) { var loadedState = ConvertFromStorageFormat(entity); grainState.RecordExists = loadedState != null; grainState.State = loadedState ?? CreateInstance(); grainState.ETag = entity.ETag.ToString(); } else { grainState.RecordExists = false; grainState.ETag = null; grainState.State = CreateInstance(); } } /// Write state data function for this storage provider. /// public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (tableDataManager == null) throw new ArgumentException("GrainState-Table property not initialized"); string pk = GetKeyString(grainId); LogTraceWritingGrainState(grainType, pk, grainId, grainState.ETag, this.options.TableName); var rowKey = AzureTableUtils.SanitizeTableProperty(grainType); var entity = new TableEntity(pk, rowKey) { ETag = new ETag(grainState.ETag) }; ConvertToStorageFormat(grainState.State, entity); try { await DoOptimisticUpdate(() => tableDataManager.Write(entity), grainType, grainId, this.options.TableName, grainState.ETag).ConfigureAwait(false); grainState.ETag = entity.ETag.ToString(); grainState.RecordExists = true; } catch (Exception exc) { LogErrorWriteGrainState(grainType, grainId, grainState.ETag, this.options.TableName, exc); throw; } } /// Clear / Delete state data function for this storage provider. /// /// If the DeleteStateOnClear is set to true then the table row /// for this grain will be deleted / removed, otherwise the table row will be /// cleared by overwriting with default / null values. /// /// public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { if (tableDataManager == null) throw new ArgumentException("GrainState-Table property not initialized"); string pk = GetKeyString(grainId); LogTraceClearingGrainState(grainType, pk, grainId, grainState.ETag, this.options.DeleteStateOnClear, this.options.TableName); var rowKey = AzureTableUtils.SanitizeTableProperty(grainType); var entity = new TableEntity(pk, rowKey) { ETag = new ETag(grainState.ETag) }; string operation = "Clearing"; try { if (this.options.DeleteStateOnClear) { operation = "Deleting"; await DoOptimisticUpdate(() => tableDataManager.Delete(entity), grainType, grainId, this.options.TableName, grainState.ETag).ConfigureAwait(false); grainState.ETag = null; } else { await DoOptimisticUpdate(() => tableDataManager.Write(entity), grainType, grainId, this.options.TableName, grainState.ETag).ConfigureAwait(false); grainState.ETag = entity.ETag.ToString(); // Update in-memory data to the new ETag } grainState.RecordExists = false; grainState.State = CreateInstance(); } catch (Exception exc) { LogErrorClearingGrainState(operation, grainType, grainId, grainState.ETag!, this.options.TableName, exc); throw; } } private static async Task DoOptimisticUpdate(Func updateOperation, string grainType, GrainId grainId, string tableName, string currentETag) { try { await updateOperation.Invoke().ConfigureAwait(false); } catch (RequestFailedException ex) when (ex.IsPreconditionFailed() || ex.IsConflict() || ex.IsNotFound()) { throw new TableStorageUpdateConditionNotSatisfiedException(grainType, grainId.ToString(), tableName, "Unknown", currentETag, ex); } } /// /// Serialize to Azure storage format in either binary or JSON format. /// /// The grain state data to be serialized /// The Azure table entity the data should be stored in /// /// See: /// http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx /// for more on the JSON serializer. /// internal void ConvertToStorageFormat(T grainState, TableEntity entity) { var binaryData = storageSerializer.Serialize(grainState); CheckMaxDataSize(binaryData.ToMemory().Length, MAX_DATA_CHUNK_SIZE * MAX_DATA_CHUNKS_COUNT); if (options.UseStringFormat) { var properties = SplitStringData(binaryData.ToString().AsMemory()); foreach (var keyValuePair in properties.Zip(GetPropertyNames(STRING_DATA_PROPERTY_NAME), (property, name) => new KeyValuePair(name, property.ToString()))) { entity[keyValuePair.Key] = keyValuePair.Value; } } else { var properties = SplitBinaryData(binaryData); foreach (var keyValuePair in properties.Zip(GetPropertyNames(BINARY_DATA_PROPERTY_NAME), (property, name) => new KeyValuePair(name, property.ToArray()))) { entity[keyValuePair.Key] = keyValuePair.Value; } } } private void CheckMaxDataSize(int dataSize, int maxDataSize) { if (dataSize > maxDataSize) { var msg = string.Format("Data too large to write to Azure table. Size={0} MaxSize={1}", dataSize, maxDataSize); LogErrorDataTooLarge(dataSize, maxDataSize); throw new ArgumentOutOfRangeException("GrainState.Size", msg); } } private static IEnumerable> SplitStringData(ReadOnlyMemory stringData) { var startIndex = 0; while (startIndex < stringData.Length) { var chunkSize = Math.Min(MAX_STRING_PROPERTY_LENGTH, stringData.Length - startIndex); yield return stringData.Slice(startIndex, chunkSize); startIndex += chunkSize; } } private static IEnumerable> SplitBinaryData(ReadOnlyMemory binaryData) { var startIndex = 0; while (startIndex < binaryData.Length) { var chunkSize = Math.Min(MAX_DATA_CHUNK_SIZE, binaryData.Length - startIndex); yield return binaryData.Slice(startIndex, chunkSize); startIndex += chunkSize; } } private static IEnumerable GetPropertyNames(string basePropertyName) { yield return basePropertyName; for (var i = 1; i < MAX_DATA_CHUNKS_COUNT; ++i) { yield return basePropertyName + i; } } private static IEnumerable ReadBinaryDataChunks(TableEntity entity) { foreach (var binaryDataPropertyName in GetPropertyNames(BINARY_DATA_PROPERTY_NAME)) { if (entity.TryGetValue(binaryDataPropertyName, out var dataProperty)) { switch (dataProperty) { // if TablePayloadFormat.JsonNoMetadata is used case string stringValue: if (!string.IsNullOrEmpty(stringValue)) { yield return Convert.FromBase64String(stringValue); } break; // if any payload type providing metadata is used case byte[] binaryValue: if (binaryValue != null && binaryValue.Length > 0) { yield return binaryValue; } break; } } } } private static byte[] ReadBinaryData(TableEntity entity) { var dataChunks = ReadBinaryDataChunks(entity).ToArray(); var dataSize = dataChunks.Select(d => d.Length).Sum(); var result = new byte[dataSize]; var startIndex = 0; foreach (var dataChunk in dataChunks) { Array.Copy(dataChunk, 0, result, startIndex, dataChunk.Length); startIndex += dataChunk.Length; } return result; } private static IEnumerable ReadStringDataChunks(TableEntity entity) { foreach (var stringDataPropertyName in GetPropertyNames(STRING_DATA_PROPERTY_NAME)) { if (entity.TryGetValue(stringDataPropertyName, out var dataProperty)) { if (dataProperty is string { Length: > 0 } data) { yield return data; } } } } private static string ReadStringData(TableEntity entity) { return string.Join(string.Empty, ReadStringDataChunks(entity)); } /// /// Deserialize from Azure storage format /// /// The Azure table entity the stored data internal T? ConvertFromStorageFormat(TableEntity entity) { // Read from both column type for backward compatibility var binaryData = ReadBinaryData(entity); var stringData = ReadStringData(entity); T? dataValue = default; try { var input = binaryData.Length > 0 ? new BinaryData(binaryData) : new BinaryData(stringData); if (input.Length > 0) dataValue = this.storageSerializer.Deserialize(input); } catch (Exception exc) { var sb = new StringBuilder(); if (binaryData.Length > 0) { sb.AppendFormat("Unable to convert from storage format GrainStateEntity.Data={0}", binaryData); } else if (!string.IsNullOrEmpty(stringData)) { sb.AppendFormat("Unable to convert from storage format GrainStateEntity.StringData={0}", stringData); } if (dataValue != null) { sb.AppendFormat("Data Value={0} Type={1}", dataValue, dataValue.GetType()); } LogErrorSimpleMessage(sb, exc); throw new AggregateException(sb.ToString(), exc); } return dataValue; } private string GetKeyString(GrainId grainId) { var key = $"{clusterOptions.ServiceId}_{grainId}"; return AzureTableUtils.SanitizeTableProperty(key); } private partial class GrainStateTableDataManager { public string TableName { get; private set; } private readonly AzureTableDataManager tableManager; private readonly ILogger logger; public GrainStateTableDataManager(AzureStorageOperationOptions options, ILogger logger) { this.logger = logger; TableName = options.TableName; tableManager = new AzureTableDataManager(options, logger); } public Task InitTableAsync() { return tableManager.InitTableAsync(); } public async Task Read(string partitionKey, string rowKey) { LogTraceReadingPartitionKeyRowKey(partitionKey, rowKey, TableName); try { var data = await tableManager.ReadSingleTableEntryAsync(partitionKey, rowKey).ConfigureAwait(false); if (data.Entity == null) { LogTraceDataNotFoundReading(partitionKey, rowKey, TableName); return default; } var record = data.Entity; record.ETag = new ETag(data.ETag); LogTraceDataRead(record.PartitionKey, record.RowKey, TableName, record.ETag.ToString()); return record; } catch (Exception exc) { if (AzureTableUtils.TableStorageDataNotFound(exc)) { LogTraceDataNotFoundReadingException(partitionKey, rowKey, TableName, exc); return default; // No data } throw; } } public async Task Write(TableEntity entity) { LogTraceWritingPartitionKeyRowKey(entity.PartitionKey, entity.RowKey, TableName, entity.ETag.ToString()); string eTag = string.IsNullOrEmpty(entity.ETag.ToString()) ? await tableManager.CreateTableEntryAsync(entity).ConfigureAwait(false) : await tableManager.UpdateTableEntryAsync(entity, entity.ETag).ConfigureAwait(false); entity.ETag = new ETag(eTag); } public async Task Delete(TableEntity entity) { if (string.IsNullOrWhiteSpace(entity.ETag.ToString())) { LogTraceNotAttemptingDelete(entity.PartitionKey, entity.RowKey, TableName, entity.ETag.ToString()); return; } LogTraceWritingPartitionKeyRowKey(entity.PartitionKey, entity.RowKey, TableName, entity.ETag.ToString()); await tableManager.DeleteTableEntryAsync(entity, entity.ETag).ConfigureAwait(false); entity.ETag = default; } // Partial log methods for GrainStateTableDataManager [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_Storage_Reading, Level = LogLevel.Trace, Message = "Reading: PartitionKey={PartitionKey} RowKey={RowKey} from Table={TableName}" )] private partial void LogTraceReadingPartitionKeyRowKey(string partitionKey, string rowKey, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_DataNotFound, Level = LogLevel.Trace, Message = "DataNotFound reading: PartitionKey={PartitionKey} RowKey={RowKey} from Table={TableName}" )] private partial void LogTraceDataNotFoundReading(string partitionKey, string rowKey, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_Storage_DataRead, Level = LogLevel.Trace, Message = "Read: PartitionKey={PartitionKey} RowKey={RowKey} from Table={TableName} with ETag={ETag}" )] private partial void LogTraceDataRead(string partitionKey, string rowKey, string tableName, string eTag); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_DataNotFound, Level = LogLevel.Trace, Message = "DataNotFound reading (exception): PartitionKey={PartitionKey} RowKey={RowKey} from Table={TableName}" )] private partial void LogTraceDataNotFoundReadingException(string partitionKey, string rowKey, string tableName, Exception exception); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_Storage_Writing, Level = LogLevel.Trace, Message = "Writing: PartitionKey={PartitionKey} RowKey={RowKey} to Table={TableName} with ETag={ETag}" )] private partial void LogTraceWritingPartitionKeyRowKey(string partitionKey, string rowKey, string tableName, string eTag); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_DataNotFound, Level = LogLevel.Trace, Message = "Not attempting to delete non-existent persistent state: PartitionKey={PartitionKey} RowKey={RowKey} from Table={TableName} with ETag={ETag}" )] private partial void LogTraceNotAttemptingDelete(string partitionKey, string rowKey, string tableName, string eTag); } /// Decodes Storage exceptions. public bool DecodeException(Exception e, out HttpStatusCode httpStatusCode, out string restStatus, bool getRESTErrors = false) { return AzureTableUtils.EvaluateException(e, out httpStatusCode, out restStatus, getRESTErrors); } private async Task Init(CancellationToken ct) { var stopWatch = Stopwatch.StartNew(); try { LogDebugStorageInitializing(name, this.options.TableName); this.tableDataManager = new GrainStateTableDataManager(this.options, this.logger); await this.tableDataManager.InitTableAsync(); stopWatch.Stop(); LogInfoInitializingProvider(this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds); } catch (Exception ex) { stopWatch.Stop(); LogErrorInitializationFailed(this.name, this.GetType().Name, this.options.InitStage, stopWatch.ElapsedMilliseconds, ex); throw; } } private Task Close(CancellationToken ct) { this.tableDataManager = null; return Task.CompletedTask; } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.name), this.options.InitStage, Init, Close); } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_ReadingData, Level = LogLevel.Trace, Message = "Reading: GrainType={GrainType} Pk={PartitionKey} GrainId={GrainId} from Table={TableName}" )] private partial void LogTraceReadingGrainState(string grainType, string partitionKey, GrainId grainId, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_WritingData, Level = LogLevel.Trace, Message = "Writing: GrainType={GrainType} Pk={PartitionKey} GrainId={GrainId} ETag={ETag} to Table={TableName}" )] private partial void LogTraceWritingGrainState(string grainType, string partitionKey, GrainId grainId, string eTag, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_WriteError, Level = LogLevel.Error, Message = "Error Writing: GrainType={GrainType} GrainId={GrainId} ETag={ETag} to Table={TableName}" )] private partial void LogErrorWriteGrainState(string grainType, GrainId grainId, string eTag, string tableName, Exception exception); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_WritingData, Level = LogLevel.Trace, Message = "Clearing: GrainType={GrainType} Pk={PartitionKey} GrainId={GrainId} ETag={ETag} DeleteStateOnClear={DeleteStateOnClear} from Table={TableName}" )] private partial void LogTraceClearingGrainState(string grainType, string partitionKey, GrainId grainId, string eTag, bool deleteStateOnClear, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_DeleteError, Level = LogLevel.Error, Message = "Error {Operation}: GrainType={GrainType} GrainId={GrainId} ETag={ETag} from Table={TableName}" )] private partial void LogErrorClearingGrainState(string operation, string grainType, GrainId grainId, string eTag, string tableName, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Data too large to write to Azure table. Size={Size} MaxSize={MaxSize}" )] private partial void LogErrorDataTooLarge(int size, int maxSize); [LoggerMessage( Level = LogLevel.Error, Message = "{Message}" )] private partial void LogErrorSimpleMessage(StringBuilder message, Exception exception); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_InitProvider, Level = LogLevel.Debug, Message = "AzureTableGrainStorage {ProviderName} is initializing: TableName={TableName}" )] private partial void LogDebugStorageInitializing(string providerName, string tableName); [LoggerMessage( EventId = (int)AzureProviderErrorCode.AzureTableProvider_InitProvider, Level = LogLevel.Information, Message = "Initializing provider {ProviderName} of type {ProviderType} in stage {Stage} took {ElapsedMilliseconds} Milliseconds." )] private partial void LogInfoInitializingProvider(string providerName, string providerType, int stage, long elapsedMilliseconds); [LoggerMessage( EventId = (int)ErrorCode.Provider_ErrorFromInit, Level = LogLevel.Error, Message = "Initialization failed for provider {ProviderName} of type {ProviderType} in stage {Stage} in {ElapsedMilliseconds} Milliseconds." )] private partial void LogErrorInitializationFailed(string providerName, string providerType, int stage, long elapsedMilliseconds, Exception exception); } public static class AzureTableGrainStorageFactory { public static AzureTableGrainStorage Create(IServiceProvider services, string name) { var optionsSnapshot = services.GetRequiredService>(); var clusterOptions = services.GetProviderClusterOptions(name); return ActivatorUtilities.CreateInstance(services, name, optionsSnapshot.Get(name), clusterOptions); } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorageOptions.cs ================================================ using Orleans.Persistence.AzureStorage; using Orleans.Storage; namespace Orleans.Configuration { /// /// Configuration for AzureTableGrainStorage /// public class AzureTableStorageOptions : AzureStorageOperationOptions, IStorageProviderSerializerOptions { /// /// Table name where grain stage is stored /// public override string TableName { get; set; } = DEFAULT_TABLE_NAME; public const string DEFAULT_TABLE_NAME = "OrleansGrainState"; /// /// Indicates if grain data should be deleted or reset to defaults when a grain clears it's state. /// public bool DeleteStateOnClear { get; set; } = false; /// /// Indicates if grain data should be stored in string or in binary format. /// public bool UseStringFormat { get; set; } /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// public IGrainStorageSerializer GrainStorageSerializer { get; set; } } /// /// Configuration validator for AzureTableStorageOptions /// public class AzureTableGrainStorageOptionsValidator : AzureStorageOperationOptionsValidator { /// /// Constructor /// /// The option to be validated. /// The option name to be validated. public AzureTableGrainStorageOptionsValidator(AzureTableStorageOptions options, string name) : base(options, name) { } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/IBlobContainerFactory.cs ================================================ using System.Threading.Tasks; using Azure.Storage.Blobs; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.Storage; /// /// A factory for building container clients for blob storage using grainType and grainId /// public interface IBlobContainerFactory { /// /// Gets the container which should be used for the specified grain. /// /// The grain id /// A configured blob client public BlobContainerClient GetBlobContainerClient(GrainId grainId); /// /// Initialize any required dependencies using the provided client and options. /// /// The connected blob client /// A representing the asynchronous operation. public Task InitializeAsync(BlobServiceClient client); } /// /// A default blob container factory that uses the default container name. /// internal class DefaultBlobContainerFactory : IBlobContainerFactory { private readonly AzureBlobStorageOptions _options; private BlobContainerClient _defaultContainer = null!; /// /// Initializes a new instance of the class. /// /// The blob storage options public DefaultBlobContainerFactory(AzureBlobStorageOptions options) { _options = options; } /// public BlobContainerClient GetBlobContainerClient(GrainId grainId) => _defaultContainer; /// public async Task InitializeAsync(BlobServiceClient client) { _defaultContainer = client.GetBlobContainerClient(_options.ContainerName); await _defaultContainer.CreateIfNotExistsAsync(); } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/README.md ================================================ # Microsoft Orleans Persistence for Azure Storage ## Introduction Microsoft Orleans Persistence for Azure Storage provides grain persistence for Microsoft Orleans using Azure Storage (Blob and Table). This allows your grains to persist their state in Azure Storage and reload it when they are reactivated. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Persistence.AzureStorage ``` ## Example - Configuring Azure Storage Persistence ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; // Define grain interface public interface IMyGrain : IGrainWithStringKey { Task SetData(string data); Task GetData(); } var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Table Storage as grain storage .AddAzureTableGrainStorage( name: "tableStore", configureOptions: options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; }) // Configure Azure Blob Storage as grain storage .AddAzureBlobGrainStorage( name: "blobStore", configureOptions: options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); await grain.SetData("Hello from Azure Storage!"); var response = await grain.GetData(); // Print the result Console.WriteLine($"Grain data: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Using Grain Storage in a Grain ```csharp using System; using System.Threading.Tasks; using Orleans; using Orleans.Runtime; namespace ExampleGrains; // Define grain state class public class MyGrainState { public string Data { get; set; } public int Version { get; set; } } // Grain implementation that uses the Azure storage public class MyGrain : Grain, IMyGrain, IGrainWithStringKey { private readonly IPersistentState _state; public MyGrain([PersistentState("state", "tableStore")] IPersistentState state) { _state = state; } public async Task SetData(string data) { _state.State.Data = data; _state.State.Version++; await _state.WriteStateAsync(); } public Task GetData() { return Task.FromResult(_state.State.Data); } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) - [Azure Storage Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence/azure-storage) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Storage/StorageExceptionExtensions.cs ================================================ using System.Net; using Azure; using Azure.Storage.Blobs.Models; namespace Orleans.Storage { internal static class StorageExceptionExtensions { public static bool IsNotFound(this RequestFailedException requestFailedException) { return requestFailedException?.Status == (int)HttpStatusCode.NotFound; } public static bool IsPreconditionFailed(this RequestFailedException requestFailedException) { return requestFailedException?.Status == (int)HttpStatusCode.PreconditionFailed; } public static bool IsConflict(this RequestFailedException requestFailedException) { return requestFailedException?.Status == (int)HttpStatusCode.Conflict; } public static bool IsContainerNotFound(this RequestFailedException requestFailedException) { return requestFailedException?.Status == (int)HttpStatusCode.NotFound && requestFailedException.ErrorCode == BlobErrorCode.ContainerNotFound; } public static bool IsBlobNotFound(this RequestFailedException requestFailedException) { return requestFailedException?.Status == (int)HttpStatusCode.NotFound && requestFailedException.ErrorCode == BlobErrorCode.BlobNotFound; } } } ================================================ FILE: src/Azure/Orleans.Persistence.AzureStorage/Storage/TableStorageUpdateConditionNotSatisfiedException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Storage { /// /// Exception thrown when a storage provider detects an Etag inconsistency when attempting to perform a WriteStateAsync operation. /// [Serializable] [GenerateSerializer] public class TableStorageUpdateConditionNotSatisfiedException : InconsistentStateException { private const string DefaultMessageFormat = "Table storage condition not Satisfied. GrainType: {0}, GrainId: {1}, TableName: {2}, StoredETag: {3}, CurrentETag: {4}"; /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// public TableStorageUpdateConditionNotSatisfiedException( string errorMsg, string grainType, string grainId, string tableName, string storedEtag, string currentEtag, Exception storageException) : base(errorMsg, storedEtag, currentEtag, storageException) { this.GrainType = grainType; this.GrainId = grainId; this.TableName = tableName; } /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// public TableStorageUpdateConditionNotSatisfiedException( string grainType, string grainId, string tableName, string storedEtag, string currentEtag, Exception storageException) : this(CreateDefaultMessage(grainType, grainId, tableName, storedEtag, currentEtag), grainType, grainId, tableName, storedEtag, currentEtag, storageException) { } /// /// Id of grain /// [Id(0)] public string GrainId { get; } /// /// Type of grain that throw this exception /// [Id(1)] public string GrainType { get; } /// /// Azure table name /// [Id(2)] public string TableName { get; } /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// public TableStorageUpdateConditionNotSatisfiedException() { } /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// public TableStorageUpdateConditionNotSatisfiedException(string msg) : base(msg) { } /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// public TableStorageUpdateConditionNotSatisfiedException(string msg, Exception exc) : base(msg, exc) { } private static string CreateDefaultMessage( string grainType, string grainId, string tableName, string storedEtag, string currentEtag) { return string.Format(DefaultMessageFormat, grainType, grainId, tableName, storedEtag, currentEtag); } /// /// Exception thrown when an azure table storage exception is thrown due to update conditions not being satisfied. /// [Obsolete] protected TableStorageUpdateConditionNotSatisfiedException(SerializationInfo info, StreamingContext context) : base(info, context) { this.GrainType = info.GetString("GrainType"); this.GrainId = info.GetString("GrainId"); this.TableName = info.GetString("TableName"); } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException(nameof(info)); info.AddValue("GrainType", this.GrainType); info.AddValue("GrainId", this.GrainId); info.AddValue("TableName", this.TableName); base.GetObjectData(info, context); } } } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/CosmosConditionNotSatisfiedException.cs ================================================ using System.Runtime.Serialization; using Orleans.Storage; namespace Orleans.Persistence.Cosmos; /// /// Exception thrown when a storage provider detects an Etag inconsistency when attempting to perform a WriteStateAsync operation. /// [Serializable] [GenerateSerializer] public class CosmosConditionNotSatisfiedException : InconsistentStateException { private const string DefaultMessageFormat = "Cosmos DB condition not satisfied. GrainType: {0}, GrainId: {1}, TableName: {2}, StoredETag: {3}, CurrentETag: {4}"; /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// public CosmosConditionNotSatisfiedException( string errorMsg, string grainType, GrainId grainId, string collection, string storedEtag, string currentEtag) : base(errorMsg, storedEtag, currentEtag) { GrainType = grainType; GrainId = grainId.ToString(); Collection = collection; } /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// public CosmosConditionNotSatisfiedException( string grainType, GrainId grainId, string collection, string storedEtag, string currentEtag) : this(CreateDefaultMessage(grainType, grainId, collection, storedEtag, currentEtag), grainType, grainId, collection, storedEtag, currentEtag) { } /// /// Gets the id of the affected grain. /// [Id(0)] public string GrainId { get; } = default!; /// /// Gets the grain type of the affected grain. /// [Id(1)] public string GrainType { get; } = default!; /// /// Gets the collection name /// [Id(2)] public string Collection { get; } = default!; /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// public CosmosConditionNotSatisfiedException() { } /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// public CosmosConditionNotSatisfiedException(string msg) : base(msg) { } /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// public CosmosConditionNotSatisfiedException(string msg, Exception exc) : base(msg, exc) { } private static string CreateDefaultMessage( string grainType, GrainId grainId, string collection, string storedEtag, string currentEtag) => string.Format(DefaultMessageFormat, grainType, grainId, collection, storedEtag, currentEtag); /// /// Exception thrown when a Cosmos DB exception is thrown due to update conditions not being satisfied. /// [Obsolete] protected CosmosConditionNotSatisfiedException(SerializationInfo info, StreamingContext context) : base(info, context) { GrainType = info.GetString("GrainType")!; GrainId = info.GetString("GrainId")!; Collection = info.GetString("Collection")!; } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException(nameof(info)); info.AddValue("GrainType", GrainType); info.AddValue("GrainId", GrainId); info.AddValue("Collection", Collection); base.GetObjectData(info, context); } } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs ================================================ using System.Net; using System.Threading; using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Orleans.Storage; using static Orleans.Persistence.Cosmos.CosmosIdSanitizer; using Orleans.Serialization.Serializers; namespace Orleans.Persistence.Cosmos; public sealed partial class CosmosGrainStorage : IGrainStorage, ILifecycleParticipant { private const string ANY_ETAG = "*"; private const string KEY_STRING_SEPARATOR = "__"; private const string GRAINTYPE_PARTITION_KEY_PATH = "/GrainType"; private readonly ILogger _logger; private readonly CosmosGrainStorageOptions _options; private readonly string _name; private readonly IServiceProvider _serviceProvider; private readonly string _serviceId; private string _partitionKeyPath; private readonly IPartitionKeyProvider _partitionKeyProvider; private readonly IActivatorProvider _activatorProvider; private readonly ICosmosOperationExecutor _executor; private CosmosClient _client = default!; private Container _container = default!; public CosmosGrainStorage( string name, CosmosGrainStorageOptions options, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptions clusterOptions, IPartitionKeyProvider partitionKeyProvider, IActivatorProvider activatorProvider) { _logger = loggerFactory.CreateLogger(); _options = options; _name = name; _serviceProvider = serviceProvider; _serviceId = clusterOptions.Value.ServiceId; _partitionKeyProvider = partitionKeyProvider; _activatorProvider = activatorProvider; _executor = options.OperationExecutor; _partitionKeyPath = _options.PartitionKeyPath; } public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var id = GetKeyString(grainId); var partitionKey = await BuildPartitionKey(grainType, grainId); LogTraceReadingState(grainType, id, grainId, _options.ContainerName, partitionKey); try { var pk = new PartitionKey(partitionKey); var entity = await _executor.ExecuteOperation(static args => { var (self, id, pk) = args; return self._container.ReadItemAsync>(id, pk); }, (this, id, pk)).ConfigureAwait(false); if (entity.Resource.State != null) { grainState.State = entity.Resource.State; grainState.RecordExists = true; } else { grainState.State = CreateInstance(); grainState.RecordExists = false; } grainState.ETag = entity.Resource.ETag; } catch (CosmosException dce) { if (dce.StatusCode == HttpStatusCode.NotFound) { // State is new, just activate a default and return. ResetGrainState(grainState); return; } LogErrorReadingState(dce, grainType, id); WrappedException.CreateAndRethrow(dce); throw; } catch (Exception exc) { LogErrorReadingState(exc, grainType, id); WrappedException.CreateAndRethrow(exc); throw; } } public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var id = GetKeyString(grainId); var partitionKey = await BuildPartitionKey(grainType, grainId); LogTraceWritingState(grainType, id, grainId, grainState.ETag, _options.ContainerName, partitionKey); ItemResponse>? response = null; try { var entity = new GrainStateEntity { ETag = grainState.ETag, Id = id, GrainType = grainType, State = grainState.State, PartitionKey = partitionKey }; var pk = new PartitionKey(partitionKey); if (string.IsNullOrWhiteSpace(grainState.ETag)) { response = await _executor.ExecuteOperation( static args => { var (self, entity, pk) = args; return self._container.CreateItemAsync(entity, pk); }, (this, entity, pk)).ConfigureAwait(false); } else if (grainState.ETag == ANY_ETAG) { var requestOptions = new ItemRequestOptions { IfMatchEtag = grainState.ETag }; response = await _executor.ExecuteOperation( static args => { var (self, entity, pk, requestOptions) = args; return self._container.UpsertItemAsync(entity, pk, requestOptions); }, (this, entity, pk, requestOptions)).ConfigureAwait(false); } else { var requestOptions = new ItemRequestOptions { IfMatchEtag = grainState.ETag }; response = await _executor.ExecuteOperation( static args => { var (self, entity, pk, requestOptions) = args; return self._container.ReplaceItemAsync(entity, entity.Id, pk, requestOptions); }, (this, entity, pk, requestOptions)).ConfigureAwait(false); } grainState.ETag = response.Resource.ETag; grainState.RecordExists = true; } catch (CosmosException ex) when (ex.StatusCode is HttpStatusCode.PreconditionFailed or HttpStatusCode.Conflict or HttpStatusCode.NotFound) { throw new CosmosConditionNotSatisfiedException(grainType, grainId, _options.ContainerName, "Unknown", grainState.ETag); } catch (Exception exc) { LogErrorWritingState(exc, grainType, id); WrappedException.CreateAndRethrow(exc); throw; } } public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var id = GetKeyString(grainId); var partitionKey = await BuildPartitionKey(grainType, grainId); LogTraceClearingState(grainType, id, grainId, grainState.ETag, _options.DeleteStateOnClear, _options.ContainerName, partitionKey); var pk = new PartitionKey(partitionKey); var requestOptions = new ItemRequestOptions { IfMatchEtag = grainState.ETag }; try { if (_options.DeleteStateOnClear) { if (string.IsNullOrWhiteSpace(grainState.ETag)) { try { var entity = await _executor.ExecuteOperation(static args => { var (self, id, pk) = args; return self._container.ReadItemAsync>(id, pk); }, (this, id, pk)).ConfigureAwait(false); // State exists but the current activation has not observed state creation. Therefore, we have inconsistent // state and should throw to give the grain a chance to deactivate and recover. throw new CosmosConditionNotSatisfiedException(grainType, grainId, _options.ContainerName, "None", entity.ETag); } catch (CosmosException dce) when (dce.StatusCode == HttpStatusCode.NotFound) { // Ignore, since this is the expected outcome. // All other exceptions will be handled by the outer catch blocks. } } else { await _executor.ExecuteOperation(static args => { var (self, id, pk, requestOptions) = args; return self._container.DeleteItemAsync>(id, pk, requestOptions); }, (this, id, pk, requestOptions)); } ResetGrainState(grainState); } else { var entity = new GrainStateEntity { ETag = grainState.ETag, Id = id, GrainType = grainType, State = default!, PartitionKey = partitionKey }; var response = await _executor.ExecuteOperation(static args => { var (self, grainState, entity, pk, requestOptions) = args; return grainState.ETag switch { null or { Length: 0 } => self._container.CreateItemAsync(entity, pk), ANY_ETAG => self._container.ReplaceItemAsync(entity, entity.Id, pk, requestOptions), _ => self._container.ReplaceItemAsync(entity, entity.Id, pk, requestOptions), }; }, (this, grainState, entity, pk, requestOptions)).ConfigureAwait(false); grainState.ETag = response.Resource.ETag; grainState.RecordExists = false; grainState.State = CreateInstance(); } } catch (CosmosException ex) when (ex.StatusCode is HttpStatusCode.PreconditionFailed or HttpStatusCode.Conflict or HttpStatusCode.NotFound) { throw new CosmosConditionNotSatisfiedException(grainType, grainId, _options.ContainerName, "Unknown", grainState?.ETag ?? "Unknown"); } catch (Exception exc) { LogErrorClearingState(exc, grainType, id); WrappedException.CreateAndRethrow(exc); throw; } } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(_name), _options.InitStage, Init); } private string GetKeyString(GrainId grainId) => $"{Sanitize(_serviceId)}{KEY_STRING_SEPARATOR}{Sanitize(grainId.Type.ToString()!)}{SeparatorChar}{Sanitize(grainId.Key.ToString()!)}"; private ValueTask BuildPartitionKey(string grainType, GrainId grainId) => _partitionKeyProvider.GetPartitionKey(grainType, grainId); private async Task Init(CancellationToken ct) { var stopWatch = Stopwatch.StartNew(); try { LogDebugInit(_name, _serviceId, _options.ContainerName, _options.DeleteStateOnClear); await InitializeCosmosClient().ConfigureAwait(false); if (_options.IsResourceCreationEnabled) { if (_options.CleanResourcesOnInitialization) { await TryDeleteDatabase().ConfigureAwait(false); } await TryCreateResources().ConfigureAwait(false); } _container = _client.GetContainer(_options.DatabaseName, _options.ContainerName); stopWatch.Stop(); LogDebugInitializingProvider(_name, GetType().Name, _options.InitStage, stopWatch.ElapsedMilliseconds); } catch (Exception ex) { stopWatch.Stop(); LogErrorInitializationFailed(ex, _name, GetType().Name, _options.InitStage, stopWatch.ElapsedMilliseconds); WrappedException.CreateAndRethrow(ex); throw; } } private async Task InitializeCosmosClient() { try { _client = await _options.CreateClient(_serviceProvider).ConfigureAwait(false); } catch (Exception ex) { LogErrorInitializingClient(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task TryCreateResources() { var dbResponse = await _client.CreateDatabaseIfNotExistsAsync(_options.DatabaseName, _options.DatabaseThroughput); var db = dbResponse.Database; var stateContainer = new ContainerProperties(_options.ContainerName, _options.PartitionKeyPath); stateContainer.IndexingPolicy.IndexingMode = IndexingMode.Consistent; stateContainer.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); stateContainer.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/\"State\"/*" }); if (_options.StateFieldsToIndex != null) { foreach (var idx in _options.StateFieldsToIndex) { var path = idx.StartsWith("/") ? idx[1..] : idx; stateContainer.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = $"/\"State\"/\"{path}\"/?" }); } } const int maxRetries = 3; for (var retry = 0; retry <= maxRetries; ++retry) { var containerResponse = await db.CreateContainerIfNotExistsAsync(stateContainer, _options.ContainerThroughputProperties); if (containerResponse.StatusCode == HttpStatusCode.OK || containerResponse.StatusCode == HttpStatusCode.Created) { var container = containerResponse.Resource; _partitionKeyPath = container.PartitionKeyPath; if (_partitionKeyPath == GRAINTYPE_PARTITION_KEY_PATH && _partitionKeyProvider is not DefaultPartitionKeyProvider) throw new OrleansConfigurationException("Custom partition key provider is not compatible with partition key path set to /GrainType"); } if (retry == maxRetries || dbResponse.StatusCode != HttpStatusCode.Created || containerResponse.StatusCode == HttpStatusCode.Created) { break; // Apparently some throttling logic returns HttpStatusCode.OK (not 429) when the collection wasn't created in a new DB. } await Task.Delay(1000); } } private async Task TryDeleteDatabase() { try { await _client.GetDatabase(_options.DatabaseName).DeleteAsync().ConfigureAwait(false); } catch (CosmosException dce) when (dce.StatusCode == HttpStatusCode.NotFound) { return; } catch (Exception ex) { LogErrorDeletingDatabase(ex); WrappedException.CreateAndRethrow(ex); throw; } } private void ResetGrainState(IGrainState grainState) { grainState.State = CreateInstance(); grainState.ETag = null; grainState.RecordExists = false; } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( Level = LogLevel.Trace, Message = "Reading: GrainType={GrainType} Key={Id} GrainId={GrainId} from Container={Container} with PartitionKey={PartitionKey}" )] private partial void LogTraceReadingState(string grainType, string id, GrainId grainId, string container, string partitionKey); [LoggerMessage( Level = LogLevel.Error, Message = "Failure reading state for Grain Type {GrainType} with Id {Id}" )] private partial void LogErrorReadingState(Exception exception, string grainType, string id); [LoggerMessage( Level = LogLevel.Trace, Message = "Writing: GrainType={GrainType} Key={Id} GrainId={GrainId} ETag={ETag} from Container={Container} with PartitionKey={PartitionKey}" )] private partial void LogTraceWritingState(string grainType, string id, GrainId grainId, string eTag, string container, string partitionKey); [LoggerMessage( Level = LogLevel.Error, Message = "Failure writing state for Grain Type {GrainType} with Id {Id}" )] private partial void LogErrorWritingState(Exception exception, string grainType, string id); [LoggerMessage( Level = LogLevel.Trace, Message = "Clearing: GrainType={GrainType} Key={Id} GrainId={GrainId} ETag={ETag} DeleteStateOnClear={DeleteStateOnClear} from Container={Container} with PartitionKey {PartitionKey}" )] private partial void LogTraceClearingState(string grainType, string id, GrainId grainId, string eTag, bool deleteStateOnClear, string container, string partitionKey); [LoggerMessage( Level = LogLevel.Error, Message = "Failure clearing state for Grain Type {GrainType} with Id {Id}" )] private partial void LogErrorClearingState(Exception exception, string grainType, string id); [LoggerMessage( Level = LogLevel.Debug, Message = "Initializing: Name={Name} ServiceId={ServiceId} Collection={Collection} DeleteStateOnClear={DeleteStateOnClear}" )] private partial void LogDebugInit(string name, string serviceId, string collection, bool deleteStateOnClear); [LoggerMessage( Level = LogLevel.Debug, Message = "Initializing provider {ProviderName} of type {ProviderType} in stage {Stage} took {ElapsedMilliseconds} milliseconds" )] private partial void LogDebugInitializingProvider(string providerName, string providerType, int stage, long elapsedMilliseconds); [LoggerMessage( EventId = (int)ErrorCode.Provider_ErrorFromInit, Level = LogLevel.Error, Message = "Initialization failed for provider {ProviderName} of type {ProviderType} in stage {Stage} in {ElapsedMilliseconds} milliseconds" )] private partial void LogErrorInitializationFailed(Exception exception, string providerName, string providerType, int stage, long elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, Message = "Error initializing Azure Cosmos DB client for grain storage provider" )] private partial void LogErrorInitializingClient(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error deleting Azure Cosmos DB database" )] private partial void LogErrorDeletingDatabase(Exception exception); } public static class CosmosStorageFactory { public static CosmosGrainStorage Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); var partitionKeyProvider = services.GetKeyedService(name) ?? services.GetRequiredService(); var loggerFactory = services.GetRequiredService(); var clusterOptions = services.GetRequiredService>(); var activatorProvider = services.GetRequiredService(); return new CosmosGrainStorage( name, optionsMonitor.Get(name), loggerFactory, services, clusterOptions, partitionKeyProvider, activatorProvider); } } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/CosmosStorageOptions.cs ================================================ using Orleans.Core; namespace Orleans.Persistence.Cosmos; /// /// Options for Azure Cosmos DB grain persistence. /// public class CosmosGrainStorageOptions : CosmosOptions { private const string ORLEANS_STORAGE_CONTAINER = "OrleansStorage"; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; private const string DEFAULT_PARTITION_KEY_PATH = "/PartitionKey"; /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; /// /// Gets or sets a value indicating whether state should be deleted when is called. /// public bool DeleteStateOnClear { get; set; } /// /// List of JSON path strings. /// Each entry on this list represents a property in the State Object that will be included in the document index. /// The default is to not add any property in the State object. /// public List StateFieldsToIndex { get; set; } = new(); public string PartitionKeyPath { get; set; } = DEFAULT_PARTITION_KEY_PATH; /// /// Initializes a new instance. /// public CosmosGrainStorageOptions() { ContainerName = ORLEANS_STORAGE_CONTAINER; } } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/HostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.Storage; using Orleans.Providers; using Orleans.Persistence.Cosmos; using Orleans.Runtime.Hosting; namespace Orleans.Hosting; /// /// Extension methods for configuring Azure Cosmos DB persistence. /// public static class HostingExtensions { /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage using a custom Partition Key Provider. /// /// The custom partition key provider type. /// The silo builder. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Action configureOptions) where TPartitionKeyProvider : class, IPartitionKeyProvider { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage using a custom Partition Key Provider. /// /// The custom partition key provider type. /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Action configureOptions) where TPartitionKeyProvider : class, IPartitionKeyProvider { builder.Services.AddKeyedSingleton(name); builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage using a custom Partition Key Provider. /// /// The silo builder. /// The delegate used to configure the provider. /// The custom partition key provider type. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Action configureOptions, Type customPartitionKeyProviderType) { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions, customPartitionKeyProviderType); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage using a custom Partition Key Provider. /// /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. /// The custom partition key provider type. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Action configureOptions, Type customPartitionKeyProviderType) { if (customPartitionKeyProviderType != null) { builder.Services.TryAddSingleton(typeof(IPartitionKeyProvider), customPartitionKeyProviderType); } builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage. /// /// The silo builder. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Action configureOptions) { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage. /// /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Action configureOptions) { builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage using a custom Partition Key Provider. /// /// The custom partition key provider type. /// The silo builder. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Action>? configureOptions = null) where TPartitionKeyProvider : class, IPartitionKeyProvider { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage using a custom Partition Key Provider. /// /// The custom partition key provider type. /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Action>? configureOptions = null) where TPartitionKeyProvider : class, IPartitionKeyProvider { builder.Services.AddKeyedSingleton(name); builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage using a custom Partition Key Provider. /// /// The silo builder. /// The custom partition key provider type. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Type customPartitionKeyProviderType, Action>? configureOptions = null) { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, customPartitionKeyProviderType, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage using a custom Partition Key Provider. /// /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Type customPartitionKeyProviderType, Action>? configureOptions = null) { if (customPartitionKeyProviderType != null) { builder.Services.AddKeyedSingleton(typeof(IPartitionKeyProvider), name, customPartitionKeyProviderType); } builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage. /// /// The silo builder. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorageAsDefault( this ISiloBuilder builder, Action>? configureOptions = null) { return builder.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage. /// /// The silo builder. /// The storage provider name. /// The delegate used to configure the provider. public static ISiloBuilder AddCosmosGrainStorage( this ISiloBuilder builder, string name, Action>? configureOptions = null) { builder.Services.AddCosmosGrainStorage(name, configureOptions); return builder; } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage. /// /// The service collection. /// The delegate used to configure the provider. public static IServiceCollection AddCosmosGrainStorageAsDefault( this IServiceCollection services, Action configureOptions) { return services.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage. /// /// The service collection. /// The storage provider name. /// The delegate used to configure the provider. public static IServiceCollection AddCosmosGrainStorage( this IServiceCollection services, string name, Action configureOptions) { return services.AddCosmosGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use Azure Cosmos DB storage as the default grain storage. /// /// The service collection. /// The delegate used to configure the provider. public static IServiceCollection AddCosmosGrainStorageAsDefault( this IServiceCollection services, Action>? configureOptions = null) { return services.AddCosmosGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use Azure Cosmos DB storage for grain storage. /// /// The service collection. /// The storage provider name. /// The delegate used to configure the provider. public static IServiceCollection AddCosmosGrainStorage( this IServiceCollection services, string name, Action>? configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient( sp => new CosmosOptionsValidator( sp.GetService>()!.Get(name), name)); services.ConfigureNamedOptionForLogging(name); services.TryAddSingleton(); return services.AddGrainStorage(name, CosmosStorageFactory.Create); } } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/IPartitionKeyProvider.cs ================================================ namespace Orleans.Persistence.Cosmos; /// /// Creates a partition key for the provided grain. /// public interface IPartitionKeyProvider { /// /// Creates a partition key for the provided grain. /// /// The grain type. /// The grain identifier. /// The partition key. ValueTask GetPartitionKey(string grainType, GrainId grainId); } internal class DefaultPartitionKeyProvider : IPartitionKeyProvider { public ValueTask GetPartitionKey(string grainType, GrainId grainId) => new(CosmosIdSanitizer.Sanitize(grainType)); } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/Models/GrainStateEntity.cs ================================================ using Newtonsoft.Json; namespace Orleans.Persistence.Cosmos; internal class GrainStateEntity : BaseEntity { [JsonProperty(nameof(GrainType))] [JsonPropertyName(nameof(GrainType))] public string GrainType { get; set; } = default!; [JsonProperty(nameof(State))] [JsonPropertyName(nameof(State))] public TState State { get; set; } = default!; [JsonProperty(nameof(PartitionKey))] [JsonPropertyName(nameof(PartitionKey))] public string PartitionKey { get; set; } = default!; } ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/Orleans.Persistence.Cosmos.csproj ================================================ Microsoft.Orleans.Persistence.Cosmos Microsoft Orleans Grain Storage for Azure Cosmos DB Microsoft Orleans persistence providers for Azure Cosmos DB $(PackageTags) Azure Cosmos DB Storage $(DefaultTargetFrameworks) Orleans.Persistence.Cosmos Orleans.Persistence.Cosmos true $(DefineConstants);ORLEANS_PERSISTENCE enable README.md <_Parameter1>Orleans.Cosmos.Tests ================================================ FILE: src/Azure/Orleans.Persistence.Cosmos/README.md ================================================ # Microsoft Orleans Persistence for Azure Cosmos DB ## Introduction Microsoft Orleans Persistence for Azure Cosmos DB provides grain persistence for Microsoft Orleans using Azure Cosmos DB. This allows your grains to persist their state in Azure Cosmos DB and reload it when they are reactivated, offering a globally distributed, multi-model database service for your Orleans applications. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Persistence.Cosmos ``` ## Example - Configuring Azure Cosmos DB Persistence ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Cosmos DB as grain storage .AddCosmosGrainStorage( name: "cosmosStore", configureOptions: options => { options.AccountEndpoint = "https://YOUR_COSMOS_ENDPOINT"; options.AccountKey = "YOUR_COSMOS_KEY"; options.DB = "YOUR_DATABASE_NAME"; options.CanCreateResources = true; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Grain Storage in a Grain ```csharp // Define grain state class public class MyGrainState { public string Data { get; set; } public int Version { get; set; } } // Grain implementation that uses the Cosmos DB storage public class MyGrain : Grain, IMyGrain, IGrainWithStringKey { private readonly IPersistentState _state; public MyGrain([PersistentState("state", "cosmosStore")] IPersistentState state) { _state = state; } public async Task SetData(string data) { _state.State.Data = data; _state.State.Version++; await _state.WriteStateAsync(); } public Task GetData() { return Task.FromResult(_state.State.Data); } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) - [Azure Storage Providers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence/azure-storage) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/AzureStorageReminderServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Reminders.AzureStorage; using Orleans.Runtime.ReminderService; namespace Orleans.Hosting { /// /// extensions. /// public static class AzureStorageReminderServiceCollectionExtensions { /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The service collection. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static IServiceCollection UseAzureTableReminderService(this IServiceCollection services, Action configure) { services.AddReminders(); services.AddSingleton(); services.Configure(configure); services.ConfigureFormatter(); return services; } /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The service collection. /// /// /// The configuration delegate. /// /// /// The provided , for chaining. /// public static IServiceCollection UseAzureTableReminderService(this IServiceCollection services, Action> configureOptions) { services.AddReminders(); services.AddSingleton(); configureOptions?.Invoke(services.AddOptions()); services.ConfigureFormatter(); services.AddTransient(sp => new AzureTableReminderStorageOptionsValidator(sp.GetRequiredService>().Get(Options.DefaultName), Options.DefaultName)); return services; } /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The service collection. /// /// /// The storage connection string. /// /// /// The provided , for chaining. /// public static IServiceCollection UseAzureTableReminderService(this IServiceCollection services, string connectionString) { services.UseAzureTableReminderService(options => { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new(uri); } else { options.TableServiceClient = new(connectionString); } }); return services; } } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/AzureStorageReminderSiloBuilderReminderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Reminders.AzureStorage; namespace Orleans.Hosting { /// /// Silo host builder extensions. /// public static class AzureStorageReminderSiloBuilderExtensions { /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The builder. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, Action configure) { builder.ConfigureServices(services => services.UseAzureTableReminderService(configure)); return builder; } /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, Action> configureOptions) { builder.ConfigureServices(services => services.UseAzureTableReminderService(configureOptions)); return builder; } /// /// Adds reminder storage backed by Azure Table Storage. /// /// /// The builder. /// /// /// The storage connection string. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, string connectionString) { builder.UseAzureTableReminderService(options => { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new(uri); } else { options.TableServiceClient = new(connectionString); } }); return builder; } } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/AzureTableStorageRemindersProviderBuilder.cs ================================================ using System; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Reminders.AzureStorage; [assembly: RegisterProvider("AzureTableStorage", "Reminders", "Silo", typeof(AzureTableStorageRemindersProviderBuilder))] namespace Orleans.Hosting; internal sealed class AzureTableStorageRemindersProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseAzureTableReminderService((OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { var tableName = configurationSection["TableName"]; if (!string.IsNullOrEmpty(tableName)) { options.TableName = tableName; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.TableServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { options.TableServiceClient = new(uri); } else { options.TableServiceClient = new(connectionString); } } } })); } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/Orleans.Reminders.AzureStorage.csproj ================================================ README.md Microsoft.Orleans.Reminders.AzureStorage Microsoft Orleans Azure Table Storage Reminders Provider Microsoft Orleans reminders provider backed by Azure Table Storage $(PackageTags) Azure Table Storage $(DefaultTargetFrameworks) Orleans.Reminders.AzureStorage Orleans.Reminders.AzureStorage true $(DefineConstants);ORLEANS_REMINDERS ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/README.md ================================================ # Microsoft Orleans Reminders for Azure Storage ## Introduction Microsoft Orleans Reminders for Azure Storage provides persistence for Orleans reminders using Azure Table Storage. This allows your Orleans applications to schedule persistent reminders that will be triggered even after silo restarts or grain deactivation. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Reminders.AzureStorage ``` ## Example - Configuring Azure Storage Reminders ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Table Storage as reminder storage .UseAzureTableReminderService(options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; options.TableName = "OrleansReminders"; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Reminders in a Grain ```csharp public interface IReminderGrain { Task StartReminder(string reminderName); Task StopReminder(); } public class ReminderGrain : Grain, IReminderGrain, IRemindable { private string _reminderName = "MyReminder"; public async Task StartReminder(string reminderName) { _reminderName = reminderName; // Register a persistent reminder await RegisterOrUpdateReminder( reminderName, TimeSpan.FromMinutes(2), // Time to delay before the first tick (must be > 1 minute) TimeSpan.FromMinutes(5)); // Period of the reminder (must be > 1 minute) } public async Task StopReminder() { // Find and unregister the reminder var reminder = await GetReminder(_reminderName); if (reminder != null) { await UnregisterReminder(reminder); } } public Task ReceiveReminder(string reminderName, TickStatus status) { // This method is called when the reminder ticks Console.WriteLine($"Reminder {reminderName} triggered at {DateTime.UtcNow}. Status: {status}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Reminders and Timers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) - [Reminder Services](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/reminder-services) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/Storage/AzureBasedReminderTable.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Azure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.AzureUtils.Utilities; using Orleans.Configuration; using Orleans.Reminders.AzureStorage; namespace Orleans.Runtime.ReminderService { public sealed partial class AzureBasedReminderTable : IReminderTable { private readonly ILogger logger; private readonly ILoggerFactory loggerFactory; private readonly ClusterOptions clusterOptions; private readonly AzureTableReminderStorageOptions storageOptions; private readonly RemindersTableManager remTableManager; private readonly TaskCompletionSource _initializationTask = new(TaskCreationOptions.RunContinuationsAsynchronously); public AzureBasedReminderTable( ILoggerFactory loggerFactory, IOptions clusterOptions, IOptions storageOptions) { this.logger = loggerFactory.CreateLogger(); this.loggerFactory = loggerFactory; this.clusterOptions = clusterOptions.Value; this.storageOptions = storageOptions.Value; this.remTableManager = new RemindersTableManager( this.clusterOptions.ServiceId, this.clusterOptions.ClusterId, this.storageOptions, this.loggerFactory); } public async Task StartAsync(CancellationToken cancellationToken) { try { while (true) { try { await remTableManager.InitTableAsync(); _initializationTask.TrySetResult(); return; } catch (Exception ex) when (!cancellationToken.IsCancellationRequested) { LogErrorCreatingAzureTable(ex); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } } } catch (OperationCanceledException ex) { LogErrorReminderTableInitializationCanceled(ex); _initializationTask.TrySetCanceled(ex.CancellationToken); throw; } catch (Exception ex) { LogErrorInitializingReminderTable(ex); _initializationTask.TrySetException(ex); throw; } } public Task StopAsync(CancellationToken cancellationToken) { _initializationTask.TrySetCanceled(CancellationToken.None); return Task.CompletedTask; } private ReminderTableData ConvertFromTableEntryList(List<(ReminderTableEntry Entity, string ETag)> entries) { var remEntries = new List(); foreach (var entry in entries) { #pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception. try { ReminderEntry converted = ConvertFromTableEntry(entry.Entity, entry.ETag); remEntries.Add(converted); } catch (Exception) { // Ignoring... } #pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception. } return new ReminderTableData(remEntries); } private ReminderEntry ConvertFromTableEntry(ReminderTableEntry tableEntry, string eTag) { try { return new ReminderEntry { GrainId = GrainId.Parse(tableEntry.GrainReference), ReminderName = tableEntry.ReminderName, StartAt = LogFormatter.ParseDate(tableEntry.StartAt), Period = TimeSpan.Parse(tableEntry.Period), ETag = eTag, }; } catch (Exception exc) { LogErrorParsingReminderEntry(exc, tableEntry); throw; } finally { string serviceIdStr = this.clusterOptions.ServiceId; if (!tableEntry.ServiceId.Equals(serviceIdStr)) { LogWarningAzureTable_ReadWrongReminder(tableEntry, serviceIdStr); throw new OrleansException($"Read a reminder entry for wrong Service id. Read {tableEntry}, but my service id is {serviceIdStr}. Going to discard it."); } } } private static ReminderTableEntry ConvertToTableEntry(ReminderEntry remEntry, string serviceId, string deploymentId) { string partitionKey = ReminderTableEntry.ConstructPartitionKey(serviceId, remEntry.GrainId); string rowKey = ReminderTableEntry.ConstructRowKey(remEntry.GrainId, remEntry.ReminderName); var consistentHash = remEntry.GrainId.GetUniformHashCode(); return new ReminderTableEntry { PartitionKey = partitionKey, RowKey = rowKey, ServiceId = serviceId, DeploymentId = deploymentId, GrainReference = remEntry.GrainId.ToString(), ReminderName = remEntry.ReminderName, StartAt = LogFormatter.PrintDate(remEntry.StartAt), Period = remEntry.Period.ToString(), GrainRefConsistentHash = consistentHash.ToString("X8"), ETag = new ETag(remEntry.ETag), }; } public async Task TestOnlyClearTable() { await _initializationTask.Task; await this.remTableManager.DeleteTableEntries(); } public async Task ReadRows(GrainId grainId) { try { await _initializationTask.Task; var entries = await this.remTableManager.FindReminderEntries(grainId); ReminderTableData data = ConvertFromTableEntryList(entries); LogTraceReadForGrain(grainId, data); return data; } catch (Exception exc) { LogWarningReadingReminders(exc, grainId, this.remTableManager.TableName); throw; } } public async Task ReadRows(uint begin, uint end) { try { await _initializationTask.Task; var entries = await this.remTableManager.FindReminderEntries(begin, end); ReminderTableData data = ConvertFromTableEntryList(entries); LogTraceReadInRange(new(begin, end), data); return data; } catch (Exception exc) { LogWarningReadingReminderRange(exc, new(begin, end), this.remTableManager.TableName); throw; } } public async Task ReadRow(GrainId grainId, string reminderName) { try { await _initializationTask.Task; LogDebugReadRow(grainId, reminderName); var result = await this.remTableManager.FindReminderEntry(grainId, reminderName); return result.Entity is null ? null : ConvertFromTableEntry(result.Entity, result.ETag); } catch (Exception exc) { LogWarningReadingReminderRow(exc, grainId, reminderName, this.remTableManager.TableName); throw; } } public async Task UpsertRow(ReminderEntry entry) { try { await _initializationTask.Task; LogDebugUpsertRow(entry); ReminderTableEntry remTableEntry = ConvertToTableEntry(entry, this.clusterOptions.ServiceId, this.clusterOptions.ClusterId); string result = await this.remTableManager.UpsertRow(remTableEntry); if (result == null) { LogWarningReminderUpsertFailed(entry); } return result; } catch (Exception exc) { LogWarningUpsertReminderEntry(exc, entry, this.remTableManager.TableName); throw; } } public async Task RemoveRow(GrainId grainId, string reminderName, string eTag) { var entry = new ReminderTableEntry { PartitionKey = ReminderTableEntry.ConstructPartitionKey(this.clusterOptions.ServiceId, grainId), RowKey = ReminderTableEntry.ConstructRowKey(grainId, reminderName), ETag = new ETag(eTag), }; try { await _initializationTask.Task; LogTraceRemoveRow(entry); bool result = await this.remTableManager.DeleteReminderEntryConditionally(entry, eTag); if (result == false) { LogWarningOnReminderDeleteRetry(entry); } return result; } catch (Exception exc) { LogWarningWhenDeletingReminder(exc, entry, this.remTableManager.TableName); throw; } } private readonly struct RingRangeLogValue(uint Begin, uint End) { public override string ToString() => RangeFactory.CreateRange(Begin, End).ToString(); } [LoggerMessage( Level = LogLevel.Error, EventId = (int)AzureReminderErrorCode.AzureTable_39, Message = "Exception trying to create or connect to the Azure table" )] private partial void LogErrorCreatingAzureTable(Exception ex); [LoggerMessage( Level = LogLevel.Error, Message = "Reminder table initialization canceled." )] private partial void LogErrorReminderTableInitializationCanceled(Exception ex); [LoggerMessage( Level = LogLevel.Error, Message = "Error initializing reminder table." )] private partial void LogErrorInitializingReminderTable(Exception ex); [LoggerMessage( Level = LogLevel.Error, EventId = (int)AzureReminderErrorCode.AzureTable_49, Message = "Failed to parse ReminderTableEntry: {TableEntry}. This entry is corrupt, going to ignore it." )] private partial void LogErrorParsingReminderEntry(Exception ex, object tableEntry); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_ReadWrongReminder, Message = "Read a reminder entry for wrong Service id. Read {TableEntry}, but my service id is {ServiceId}. Going to discard it." )] private partial void LogWarningAzureTable_ReadWrongReminder(ReminderTableEntry tableEntry, string serviceId); [LoggerMessage( Level = LogLevel.Trace, Message = "Read for grain {GrainId} Table={Data}" )] private partial void LogTraceReadForGrain(GrainId grainId, ReminderTableData data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_47, Message = "Intermediate error reading reminders for grain {GrainId} in table {TableName}." )] private partial void LogWarningReadingReminders(Exception ex, GrainId grainId, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Read in {RingRange} Table={Data}" )] private partial void LogTraceReadInRange(RingRangeLogValue ringRange, ReminderTableData data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_40, Message = "Intermediate error reading reminders in range {RingRange} for table {TableName}." )] private partial void LogWarningReadingReminderRange(Exception ex, RingRangeLogValue ringRange, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "ReadRow grainRef = {GrainId} reminderName = {ReminderName}" )] private partial void LogDebugReadRow(GrainId grainId, string reminderName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_46, Message = "Intermediate error reading row with grainId = {GrainId} reminderName = {ReminderName} from table {TableName}." )] private partial void LogWarningReadingReminderRow(Exception ex, GrainId grainId, string reminderName, string tableName); [LoggerMessage( Level = LogLevel.Debug, Message = "UpsertRow entry = {Data}" )] private partial void LogDebugUpsertRow(ReminderEntry data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_45, Message = "Upsert failed on the reminder table. Will retry. Entry = {Data}" )] private partial void LogWarningReminderUpsertFailed(ReminderEntry data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_42, Message = "Intermediate error upserting reminder entry {Data} to the table {TableName}." )] private partial void LogWarningUpsertReminderEntry(Exception ex, ReminderEntry data, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "RemoveRow entry = {Data}" )] private partial void LogTraceRemoveRow(ReminderTableEntry data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_43, Message = "Delete failed on the reminder table. Will retry. Entry = {Data}" )] private partial void LogWarningOnReminderDeleteRetry(ReminderTableEntry data); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureReminderErrorCode.AzureTable_44, Message = "Intermediate error when deleting reminder entry {Data} to the table {TableName}." )] private partial void LogWarningWhenDeletingReminder(Exception ex, ReminderTableEntry data, string tableName); } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/Storage/AzureTableReminderStorageOptions.cs ================================================ namespace Orleans.Reminders.AzureStorage { /// Options for Azure Table based reminder table. public class AzureTableReminderStorageOptions : AzureStorageOperationOptions { /// /// Table name for Azure Storage /// public override string TableName { get; set; } = DEFAULT_TABLE_NAME; public const string DEFAULT_TABLE_NAME = "OrleansReminders"; } /// /// Configuration validator for . /// public class AzureTableReminderStorageOptionsValidator : AzureStorageOperationOptionsValidator { /// /// Initializes a new instance of the class. /// /// The option to be validated. /// The option name to be validated. public AzureTableReminderStorageOptionsValidator(AzureTableReminderStorageOptions options, string name) : base(options, name) { } } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/Storage/RemindersTableManager.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Threading.Tasks; using Azure; using Azure.Data.Tables; using Microsoft.Extensions.Logging; using Orleans.Reminders.AzureStorage; namespace Orleans.Runtime.ReminderService { internal sealed class ReminderTableEntry : ITableEntity { public string GrainReference { get; set; } // Part of RowKey public string ReminderName { get; set; } // Part of RowKey public string ServiceId { get; set; } // Part of PartitionKey public string DeploymentId { get; set; } public string StartAt { get; set; } public string Period { get; set; } public string GrainRefConsistentHash { get; set; } // Part of PartitionKey public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } public static string ConstructRowKey(GrainId grainId, string reminderName) => AzureTableUtils.SanitizeTableProperty($"{grainId}-{reminderName}"); public static (string LowerBound, string UpperBound) ConstructRowKeyBounds(GrainId grainId) { var baseKey = AzureTableUtils.SanitizeTableProperty(grainId.ToString()); return (baseKey + '-', baseKey + (char)('-' + 1)); } public static string ConstructPartitionKey(string serviceId, GrainId grainId) => ConstructPartitionKey(serviceId, grainId.GetUniformHashCode()); public static string ConstructPartitionKey(string serviceId, uint number) { // IMPORTANT NOTE: Other code using this return data is very sensitive to format changes, // so take great care when making any changes here!!! // this format of partition key makes sure that the comparisons in FindReminderEntries(begin, end) work correctly // the idea is that when converting to string, negative numbers start with 0, and positive start with 1. Now, // when comparisons will be done on strings, this will ensure that positive numbers are always greater than negative // string grainHash = number < 0 ? string.Format("0{0}", number.ToString("X")) : string.Format("1{0:d16}", number); return AzureTableUtils.SanitizeTableProperty($"{serviceId}_{number:X8}"); } public static (string LowerBound, string UpperBound) ConstructPartitionKeyBounds(string serviceId) { var baseKey = AzureTableUtils.SanitizeTableProperty(serviceId); return (baseKey + '_', baseKey + (char)('_' + 1)); } public override string ToString() => $"Reminder [PartitionKey={PartitionKey} RowKey={RowKey} GrainId={GrainReference} ReminderName={ReminderName} Deployment={DeploymentId} ServiceId={ServiceId} StartAt={StartAt} Period={Period} GrainRefConsistentHash={GrainRefConsistentHash}]"; } internal sealed partial class RemindersTableManager : AzureTableDataManager { private readonly string _serviceId; private readonly string _clusterId; public RemindersTableManager( string serviceId, string clusterId, AzureStorageOperationOptions options, ILoggerFactory loggerFactory) : base(options, loggerFactory.CreateLogger()) { _clusterId = clusterId; _serviceId = serviceId; } internal async Task> FindReminderEntries(uint begin, uint end) { string sBegin = ReminderTableEntry.ConstructPartitionKey(_serviceId, begin); string sEnd = ReminderTableEntry.ConstructPartitionKey(_serviceId, end); string query; if (begin < end) { // Query between the specified lower and upper bounds. // Note that the lower bound is exclusive and the upper bound is inclusive in the below query. query = TableClient.CreateQueryFilter($"(PartitionKey gt {sBegin}) and (PartitionKey le {sEnd})"); } else { var (partitionKeyLowerBound, partitionKeyUpperBound) = ReminderTableEntry.ConstructPartitionKeyBounds(_serviceId); if (begin == end) { // Query the entire range query = TableClient.CreateQueryFilter($"(PartitionKey gt {partitionKeyLowerBound}) and (PartitionKey lt {partitionKeyUpperBound})"); } else { // (begin > end) // Query wraps around the ends of the range, so the query is the union of two disjunct queries // Include everything outside of the (begin, end] range, which wraps around to become: // [partitionKeyLowerBound, end] OR (begin, partitionKeyUpperBound] Debug.Assert(begin > end); query = TableClient.CreateQueryFilter($"((PartitionKey gt {partitionKeyLowerBound}) and (PartitionKey le {sEnd})) or ((PartitionKey gt {sBegin}) and (PartitionKey lt {partitionKeyUpperBound}))"); } } return await ReadTableEntriesAndEtagsAsync(query); } internal async Task> FindReminderEntries(GrainId grainId) { var partitionKey = ReminderTableEntry.ConstructPartitionKey(_serviceId, grainId); var (rowKeyLowerBound, rowKeyUpperBound) = ReminderTableEntry.ConstructRowKeyBounds(grainId); var query = TableClient.CreateQueryFilter($"(PartitionKey eq {partitionKey}) and ((RowKey gt {rowKeyLowerBound}) and (RowKey le {rowKeyUpperBound}))"); return await ReadTableEntriesAndEtagsAsync(query); } internal async Task<(ReminderTableEntry Entity, string ETag)> FindReminderEntry(GrainId grainId, string reminderName) { string partitionKey = ReminderTableEntry.ConstructPartitionKey(_serviceId, grainId); string rowKey = ReminderTableEntry.ConstructRowKey(grainId, reminderName); return await ReadSingleTableEntryAsync(partitionKey, rowKey); } private Task> FindAllReminderEntries() { return FindReminderEntries(0, 0); } internal async Task UpsertRow(ReminderTableEntry reminderEntry) { try { return await UpsertTableEntryAsync(reminderEntry); } catch(Exception exc) { if (AzureTableUtils.EvaluateException(exc, out var httpStatusCode, out var restStatus)) { LogTraceUpsertRowFailed(Logger, httpStatusCode, restStatus); if (AzureTableUtils.IsContentionError(httpStatusCode)) return null; // false; } throw; } } internal async Task DeleteReminderEntryConditionally(ReminderTableEntry reminderEntry, string eTag) { try { await DeleteTableEntryAsync(reminderEntry, eTag); return true; } catch(Exception exc) { if (AzureTableUtils.EvaluateException(exc, out var httpStatusCode, out var restStatus)) { LogTraceDeleteReminderEntryConditionallyFailed(Logger, httpStatusCode, restStatus); if (AzureTableUtils.IsContentionError(httpStatusCode)) return false; } throw; } } internal async Task DeleteTableEntries() { List<(ReminderTableEntry Entity, string ETag)> entries = await FindAllReminderEntries(); // return manager.DeleteTableEntries(entries); // this doesnt work as entries can be across partitions, which is not allowed // group by grain hashcode so each query goes to different partition var tasks = new List(); var groupedByHash = entries .Where(tuple => tuple.Entity.ServiceId.Equals(_serviceId)) .Where(tuple => tuple.Entity.DeploymentId.Equals(_clusterId)) // delete only entries that belong to our DeploymentId. .GroupBy(x => x.Entity.GrainRefConsistentHash).ToDictionary(g => g.Key, g => g.ToList()); foreach (var entriesPerPartition in groupedByHash.Values) { foreach (var batch in entriesPerPartition.BatchIEnumerable(this.StoragePolicyOptions.MaxBulkUpdateRows)) { tasks.Add(DeleteTableEntriesAsync(batch)); } } await Task.WhenAll(tasks); } [LoggerMessage( Level = LogLevel.Trace, Message = "UpsertRow failed with HTTP status code: {HttpStatusCode}, REST status: {RestStatus}" )] private static partial void LogTraceUpsertRowFailed(ILogger logger, HttpStatusCode httpStatusCode, string restStatus); [LoggerMessage( Level = LogLevel.Trace, Message = "DeleteReminderEntryConditionally failed with HTTP status code: {HttpStatusCode}, REST status: {RestStatus}" )] private static partial void LogTraceDeleteReminderEntryConditionallyFailed(ILogger logger, HttpStatusCode httpStatusCode, string restStatus); } } ================================================ FILE: src/Azure/Orleans.Reminders.AzureStorage/Utilities/AzureReminderErrorCode.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Orleans.AzureUtils.Utilities { [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum AzureReminderErrorCode { Runtime = 100000, AzureTableBase = Runtime + 800, // reminders related AzureTable_38 = AzureTableBase + 38, AzureTable_39 = AzureTableBase + 39, AzureTable_40 = AzureTableBase + 40, AzureTable_42 = AzureTableBase + 42, AzureTable_43 = AzureTableBase + 43, AzureTable_44 = AzureTableBase + 44, AzureTable_45 = AzureTableBase + 45, AzureTable_46 = AzureTableBase + 46, AzureTable_47 = AzureTableBase + 47, AzureTable_49 = AzureTableBase + 49, AzureTable_ReadWrongReminder = AzureTableBase + 64 } } ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/CosmosReminderTable.cs ================================================ using System.Net; using System.Diagnostics; using Orleans.Reminders.Cosmos.Models; namespace Orleans.Reminders.Cosmos; internal partial class CosmosReminderTable : IReminderTable { private const HttpStatusCode TooManyRequests = (HttpStatusCode)429; private const string PARTITION_KEY_PATH = "/PartitionKey"; private readonly CosmosReminderTableOptions _options; private readonly ClusterOptions _clusterOptions; private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly Func _convertEntityToEntry; private readonly ICosmosOperationExecutor _executor; private CosmosClient _client = default!; private Container _container = default!; public CosmosReminderTable( ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptions options, IOptions clusterOptions) { _logger = loggerFactory.CreateLogger(); _serviceProvider = serviceProvider; _options = options.Value; _clusterOptions = clusterOptions.Value; _convertEntityToEntry = FromEntity; _executor = options.Value.OperationExecutor; } public async Task Init() { var stopWatch = Stopwatch.StartNew(); try { LogDebugInitializingCosmosReminderTable(_clusterOptions.ServiceId, _options.ContainerName); await InitializeCosmosClient(); if (_options.IsResourceCreationEnabled) { if (_options.CleanResourcesOnInitialization) { await TryDeleteDatabase(); } await TryCreateCosmosResources(); } _container = _client.GetContainer(_options.DatabaseName, _options.ContainerName); stopWatch.Stop(); LogTraceInitializingCosmosReminderTableTook(stopWatch.ElapsedMilliseconds); } catch (Exception exc) { stopWatch.Stop(); LogErrorInitializationFailedForProviderCosmosReminderTable(exc, stopWatch.ElapsedMilliseconds); WrappedException.CreateAndRethrow(exc); throw; } } public async Task ReadRows(GrainId grainId) { try { var pk = new PartitionKey(ReminderEntity.ConstructPartitionKey(_clusterOptions.ServiceId, grainId)); var requestOptions = new QueryRequestOptions { PartitionKey = pk }; var response = await _executor.ExecuteOperation(static async args => { var (self, grainId, requestOptions) = args; var query = self._container.GetItemLinqQueryable(requestOptions: requestOptions).ToFeedIterator(); var reminders = new List(); do { var queryResponse = await query.ReadNextAsync().ConfigureAwait(false); if (queryResponse != null && queryResponse.Count > 0) { reminders.AddRange(queryResponse); } else { break; } } while (query.HasMoreResults); return reminders; }, (this, grainId, requestOptions)).ConfigureAwait(false); return new ReminderTableData(response.Select(_convertEntityToEntry)); } catch (Exception exc) { LogErrorFailureReadingRemindersForGrain(exc, grainId, _container.Id); WrappedException.CreateAndRethrow(exc); throw; } } public async Task ReadRows(uint begin, uint end) { try { var response = await _executor.ExecuteOperation(static async args => { var (self, begin, end) = args; var query = self._container.GetItemLinqQueryable() .Where(entity => entity.ServiceId == self._clusterOptions.ServiceId); query = begin < end ? query.Where(r => r.GrainHash > begin && r.GrainHash <= end) : query.Where(r => r.GrainHash > begin || r.GrainHash <= end); var iterator = query.ToFeedIterator(); var reminders = new List(); do { var queryResponse = await iterator.ReadNextAsync().ConfigureAwait(false); if (queryResponse != null && queryResponse.Count > 0) { reminders.AddRange(queryResponse); } else { break; } } while (iterator.HasMoreResults); return reminders; }, (this, begin, end)).ConfigureAwait(false); return new ReminderTableData(response.Select(_convertEntityToEntry)); } catch (Exception exc) { LogErrorFailureReadingRemindersForService(exc, _clusterOptions.ServiceId, new(begin), new(end)); WrappedException.CreateAndRethrow(exc); throw; } } public async Task ReadRow(GrainId grainId, string reminderName) { try { var pk = new PartitionKey(ReminderEntity.ConstructPartitionKey(_clusterOptions.ServiceId, grainId)); var id = ReminderEntity.ConstructId(grainId, reminderName); var response = await _executor.ExecuteOperation(async args => { try { var (self, id, pk) = args; var result = await self._container.ReadItemAsync(id, pk).ConfigureAwait(false); return result.Resource; } catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.NotFound) { return null; } }, (this, id, pk)).ConfigureAwait(false); return response != null ? FromEntity(response)! : default!; } catch (Exception exc) { LogErrorFailureReadingReminder(exc, reminderName, _clusterOptions.ServiceId, grainId); WrappedException.CreateAndRethrow(exc); throw; } } public async Task UpsertRow(ReminderEntry entry) { try { var entity = ToEntity(entry); var pk = new PartitionKey(ReminderEntity.ConstructPartitionKey(_clusterOptions.ServiceId, entry.GrainId)); var options = new ItemRequestOptions { IfMatchEtag = entity.ETag }; var response = await _executor.ExecuteOperation(static async args => { var (self, pk, entity, options) = args; var result = await self._container.UpsertItemAsync(entity, pk, options).ConfigureAwait(false); return result.Resource; }, (this, pk, entity, options)).ConfigureAwait(false); return response.ETag; } catch (Exception exc) { LogErrorFailureToUpsertReminder(exc, _clusterOptions.ServiceId); WrappedException.CreateAndRethrow(exc); throw; } } public async Task RemoveRow(GrainId grainId, string reminderName, string eTag) { try { var id = ReminderEntity.ConstructId(grainId, reminderName); var options = new ItemRequestOptions { IfMatchEtag = eTag, }; var pk = new PartitionKey(ReminderEntity.ConstructPartitionKey(_clusterOptions.ServiceId, grainId)); await _executor.ExecuteOperation(static args => { var (self, id, pk, options) = args; return self._container.DeleteItemAsync(id, pk, options); }, (this, id, pk, options)).ConfigureAwait(false); return true; } catch (CosmosException dce) when (dce.StatusCode is HttpStatusCode.PreconditionFailed) { return false; } catch (Exception exc) { LogErrorFailureRemovingReminders(exc, _clusterOptions.ServiceId, grainId, reminderName); WrappedException.CreateAndRethrow(exc); throw; } } public async Task TestOnlyClearTable() { try { var entities = await _executor.ExecuteOperation(static async self => { var query = self._container.GetItemLinqQueryable() .Where(entity => entity.ServiceId == self._clusterOptions.ServiceId) .ToFeedIterator(); var reminders = new List(); do { var queryResponse = await query.ReadNextAsync().ConfigureAwait(false); if (queryResponse != null && queryResponse.Count > 0) { reminders.AddRange(queryResponse); } else { break; } } while (query.HasMoreResults); return reminders; }, this).ConfigureAwait(false); var deleteTasks = new List(); foreach (var entity in entities) { deleteTasks.Add(_executor.ExecuteOperation( static args => { var (self, id, pk) = args; return self._container.DeleteItemAsync(id, pk); }, (this, entity.Id, new PartitionKey(entity.PartitionKey)))); } await Task.WhenAll(deleteTasks).ConfigureAwait(false); } catch (Exception exc) { LogErrorFailureToClearReminders(exc, _clusterOptions.ServiceId); WrappedException.CreateAndRethrow(exc); throw; } } private async Task InitializeCosmosClient() { try { _client = await _options.CreateClient!(_serviceProvider).ConfigureAwait(false); } catch (Exception ex) { LogErrorInitializingAzureCosmosDbClient(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task TryDeleteDatabase() { try { await _client.GetDatabase(_options.DatabaseName).DeleteAsync().ConfigureAwait(false); } catch (CosmosException dce) when (dce.StatusCode == HttpStatusCode.NotFound) { return; } catch (Exception ex) { LogErrorDeletingAzureCosmosDBDatabase(ex); WrappedException.CreateAndRethrow(ex); throw; } } private async Task TryCreateCosmosResources() { var dbResponse = await _client.CreateDatabaseIfNotExistsAsync(_options.DatabaseName, _options.DatabaseThroughput).ConfigureAwait(false); var db = dbResponse.Database; var remindersCollection = new ContainerProperties(_options.ContainerName, PARTITION_KEY_PATH); remindersCollection.IndexingPolicy.IndexingMode = IndexingMode.Consistent; remindersCollection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" }); remindersCollection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/StartAt/*" }); remindersCollection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/Period/*" }); remindersCollection.IndexingPolicy.IndexingMode = IndexingMode.Consistent; const int maxRetries = 3; for (var retry = 0; retry <= maxRetries; ++retry) { var collResponse = await db.CreateContainerIfNotExistsAsync(remindersCollection, _options.ContainerThroughputProperties).ConfigureAwait(false); if (retry == maxRetries || dbResponse.StatusCode != HttpStatusCode.Created || collResponse.StatusCode == HttpStatusCode.Created) { break; // Apparently some throttling logic returns HttpStatusCode.OK (not 429) when the collection wasn't created in a new DB. } await Task.Delay(1000); } } private ReminderEntry FromEntity(ReminderEntity entity) { return new ReminderEntry { GrainId = GrainId.Parse(entity.GrainId), ReminderName = entity.Name, Period = entity.Period, StartAt = entity.StartAt.UtcDateTime, ETag = entity.ETag }; } private ReminderEntity ToEntity(ReminderEntry entry) { return new ReminderEntity { Id = ReminderEntity.ConstructId(entry.GrainId, entry.ReminderName), PartitionKey = ReminderEntity.ConstructPartitionKey(_clusterOptions.ServiceId, entry.GrainId), ServiceId = _clusterOptions.ServiceId, GrainHash = entry.GrainId.GetUniformHashCode(), GrainId = entry.GrainId.ToString(), Name = entry.ReminderName, StartAt = entry.StartAt, Period = entry.Period }; } private readonly struct UIntLogValue(uint value) { public override string ToString() => value.ToString("X"); } [LoggerMessage( Level = LogLevel.Debug, Message = "Azure Cosmos DB Reminder Storage CosmosReminderTable is initializing: Name=CosmosReminderTable ServiceId={ServiceId} Collection={Container}" )] private partial void LogDebugInitializingCosmosReminderTable(string serviceId, string container); [LoggerMessage( Level = LogLevel.Trace, Message = "Initializing CosmosReminderTable took {Elapsed} milliseconds" )] private partial void LogTraceInitializingCosmosReminderTableTook(long elapsed); [LoggerMessage( Level = LogLevel.Error, Message = "Initialization failed for provider CosmosReminderTable in {Elapsed} milliseconds" )] private partial void LogErrorInitializationFailedForProviderCosmosReminderTable(Exception ex, long elapsed); [LoggerMessage( Level = LogLevel.Error, Message = "Failure reading reminders for grain {GrainId} in container {Container}" )] private partial void LogErrorFailureReadingRemindersForGrain(Exception ex, GrainId grainId, string container); [LoggerMessage( Level = LogLevel.Error, Message = "Failure reading reminders for service {Service} for range {Begin} to {End}" )] private partial void LogErrorFailureReadingRemindersForService(Exception ex, string service, UIntLogValue begin, UIntLogValue end); [LoggerMessage( Level = LogLevel.Error, Message = "Failure reading reminder {Name} for service {ServiceId} and grain {GrainId}" )] private partial void LogErrorFailureReadingReminder(Exception ex, string name, string serviceId, GrainId grainId); [LoggerMessage( Level = LogLevel.Error, Message = "Failure to upsert reminder for service {ServiceId}" )] private partial void LogErrorFailureToUpsertReminder(Exception ex, string serviceId); [LoggerMessage( Level = LogLevel.Error, Message = "Failure removing reminders for service {ServiceId} with GrainId {GrainId} and name {ReminderName}" )] private partial void LogErrorFailureRemovingReminders(Exception ex, string serviceId, GrainId grainId, string reminderName); [LoggerMessage( Level = LogLevel.Error, Message = "Failure to clear reminders for service {ServiceId}" )] private partial void LogErrorFailureToClearReminders(Exception ex, string serviceId); [LoggerMessage( Level = LogLevel.Error, Message = "Error initializing Azure Cosmos DB client for membership table provider" )] private partial void LogErrorInitializingAzureCosmosDbClient(Exception ex); [LoggerMessage( Level = LogLevel.Error, Message = "Error deleting Azure Cosmos DB database" )] private partial void LogErrorDeletingAzureCosmosDBDatabase(Exception ex); } ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/CosmosReminderTableOptions.cs ================================================ namespace Orleans.Reminders.Cosmos; /// /// Options for Azure Cosmos DB Reminder Storage. /// public class CosmosReminderTableOptions : CosmosOptions { private const string ORLEANS_REMINDERS_CONTAINER = "OrleansReminders"; /// /// Initializes a new instance. /// public CosmosReminderTableOptions() { ContainerName = ORLEANS_REMINDERS_CONTAINER; } } ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/HostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Reminders.Cosmos; namespace Orleans.Hosting; /// /// Extension methods for configuring the Azure Cosmos DB reminder table provider. /// public static class HostingExtensions { /// /// Adds reminder storage backed by Azure Cosmos DB. /// /// /// The builder. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseCosmosReminderService(this ISiloBuilder builder, Action configure) { builder.Services.UseCosmosReminderService(configure); return builder; } /// /// Adds reminder storage backed by Azure Cosmos DB. /// /// /// The builder. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseCosmosReminderService(this ISiloBuilder builder, Action> configure) { builder.Services.UseCosmosReminderService(configure); return builder; } /// /// Adds reminder storage backed by Azure Cosmos DB. /// /// /// The service collection. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static IServiceCollection UseCosmosReminderService(this IServiceCollection services, Action configure) => services.UseCosmosReminderService(optionsBuilder => optionsBuilder.Configure(configure)); /// /// Adds reminder storage backed by Azure Cosmos DB. /// /// /// The service collection. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static IServiceCollection UseCosmosReminderService(this IServiceCollection services, Action> configure) { services.AddReminders(); services.AddSingleton(); configure(services.AddOptions()); services.ConfigureFormatter(); services.AddTransient(sp => new CosmosOptionsValidator( sp.GetRequiredService>().CurrentValue, nameof(CosmosReminderTableOptions))); return services; } } ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/Models/ReminderEntity.cs ================================================ using Newtonsoft.Json; using static Orleans.Reminders.Cosmos.CosmosIdSanitizer; namespace Orleans.Reminders.Cosmos.Models; internal class ReminderEntity : BaseEntity { [JsonProperty(nameof(PartitionKey))] [JsonPropertyName(nameof(PartitionKey))] public string PartitionKey { get; set; } = default!; [JsonProperty(nameof(ServiceId))] [JsonPropertyName(nameof(ServiceId))] public string ServiceId { get; set; } = default!; [JsonProperty(nameof(GrainId))] [JsonPropertyName(nameof(GrainId))] public string GrainId { get; set; } = default!; [JsonProperty(nameof(Name))] [JsonPropertyName(nameof(Name))] public string Name { get; set; } = default!; [JsonProperty(nameof(StartAt))] [JsonPropertyName(nameof(StartAt))] public DateTimeOffset StartAt { get; set; } [JsonProperty(nameof(Period))] [JsonPropertyName(nameof(Period))] public TimeSpan Period { get; set; } [JsonProperty(nameof(GrainHash))] [JsonPropertyName(nameof(GrainHash))] public uint GrainHash { get; set; } public static string ConstructId(GrainId grainId, string reminderName) { var grainType = grainId.Type.ToString(); var grainKey = grainId.Key.ToString(); if (grainType is null || grainKey is null) { throw new ArgumentNullException(nameof(grainId)); } return $"{Sanitize(grainType)}{SeparatorChar}{Sanitize(grainKey)}{SeparatorChar}{Sanitize(reminderName)}"; } public static string ConstructPartitionKey(string serviceId, GrainId grainId) => $"{serviceId}_{grainId.GetUniformHashCode():X}"; } ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/Orleans.Reminders.Cosmos.csproj ================================================ Microsoft.Orleans.Reminders.Cosmos Microsoft Orleans Reminders Azure Cosmos DB Microsoft Orleans reminders provider for Azure Cosmos DB $(PackageTags) Azure Cosmos DB $(DefaultTargetFrameworks) Orleans.Reminders.Cosmos Orleans.Reminders.Cosmos true $(DefineConstants);ORLEANS_REMINDERS enable README.md <_Parameter1>Orleans.Cosmos.Tests ================================================ FILE: src/Azure/Orleans.Reminders.Cosmos/README.md ================================================ # Microsoft Orleans Reminders for Azure Cosmos DB ## Introduction Microsoft Orleans Reminders for Azure Cosmos DB provides persistence for Orleans reminders using Azure Cosmos DB. This allows your Orleans applications to schedule persistent reminders that will be triggered even after silo restarts or grain deactivation. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Reminders.Cosmos ``` ## Example - Configuring Azure Cosmos DB Reminders ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Cosmos DB as reminder storage .UseCosmosDBReminders(options => { options.AccountEndpoint = "https://YOUR_COSMOS_ENDPOINT"; options.AccountKey = "YOUR_COSMOS_KEY"; options.DB = "YOUR_DATABASE_NAME"; options.CanCreateResources = true; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Reminders in a Grain ```csharp public interface IReminderGrain { Task StartReminder(string reminderName); Task StopReminder(); } public class ReminderGrain : Grain, IReminderGrain, IRemindable { private string _reminderName = "MyReminder"; public async Task StartReminder(string reminderName) { _reminderName = reminderName; // Register a persistent reminder await RegisterOrUpdateReminder( reminderName, TimeSpan.FromMinutes(2), // Time to delay before the first tick (must be > 1 minute) TimeSpan.FromMinutes(5)); // Period of the reminder (must be > 1 minute) } public async Task StopReminder() { // Find and unregister the reminder var reminder = await GetReminder(_reminderName); if (reminder != null) { await UnregisterReminder(reminder); } } public Task ReceiveReminder(string reminderName, TickStatus status) { // This method is called when the reminder ticks Console.WriteLine($"Reminder {reminderName} triggered at {DateTime.UtcNow}. Status: {status}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Reminders and Timers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) - [Reminder Services](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/reminder-services) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Hosting/AzureQueueStreamProviderBuilder.cs ================================================ using System; using System.Collections.Generic; using Azure.Storage.Queues; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("AzureQueueStorage", "Streaming", "Silo", typeof(AzureQueueStreamProviderBuilder))] [assembly: RegisterProvider("AzureQueueStorage", "Streaming", "Client", typeof(AzureQueueStreamProviderBuilder))] namespace Orleans.Hosting; public sealed class AzureQueueStreamProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAzureQueueStreams(name, GetQueueOptionBuilder(configurationSection)); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddAzureQueueStreams(name, GetQueueOptionBuilder(configurationSection)); } private static Action> GetQueueOptionBuilder(IConfigurationSection configurationSection) { return (OptionsBuilder optionsBuilder) => { optionsBuilder.Configure((options, services) => { var queueNames = configurationSection.GetSection("QueueNames")?.Get>(); if (queueNames != null) { options.QueueNames = queueNames; } var visibilityTimeout = configurationSection["MessageVisibilityTimeout"]; if (TimeSpan.TryParse(visibilityTimeout, out var visibilityTimeoutTimeSpan)) { options.MessageVisibilityTimeout = visibilityTimeoutTimeSpan; } var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a client by name. options.QueueServiceClient = services.GetRequiredKeyedService(serviceKey); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) { if (!string.IsNullOrEmpty(uri.Query)) { // SAS URI options.QueueServiceClient = new QueueServiceClient(uri); } else { options.QueueServiceClient = new QueueServiceClient(uri, credential: new Azure.Identity.DefaultAzureCredential()); } } else { options.QueueServiceClient = new QueueServiceClient(connectionString); } } } }); }; } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Hosting/ClientBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Hosting { public static class ClientBuilderExtensions { /// /// Configure cluster client to use azure queue persistent streams. /// public static IClientBuilder AddAzureQueueStreams(this IClientBuilder builder, string name, Action configure) { //the constructor wires up DI with AzureQueueStream, so has to be called regardless configure is null or not var configurator = new ClusterClientAzureQueueStreamConfigurator(name, builder); configure?.Invoke(configurator); return builder; } /// /// Configure cluster client to use azure queue persistent streams. /// public static IClientBuilder AddAzureQueueStreams(this IClientBuilder builder, string name, Action> configureOptions) { builder.AddAzureQueueStreams(name, b=> b.ConfigureAzureQueue(configureOptions)); return builder; } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Hosting/SiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.LeaseProviders; namespace Orleans.Hosting { public static class SiloBuilderExtensions { /// /// Configure silo to use azure queue persistent streams. /// public static ISiloBuilder AddAzureQueueStreams(this ISiloBuilder builder, string name, Action configure) { var configurator = new SiloAzureQueueStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure?.Invoke(configurator); return builder; } /// /// Configure silo to use azure queue persistent streams with default settings /// public static ISiloBuilder AddAzureQueueStreams(this ISiloBuilder builder, string name, Action> configureOptions) { builder.AddAzureQueueStreams(name, b => b.ConfigureAzureQueue(configureOptions)); return builder; } /// /// Configure silo to use azure blob lease provider /// public static ISiloBuilder UseAzureBlobLeaseProvider(this ISiloBuilder builder, Action> configureOptions) { builder.ConfigureServices(services => ConfigureAzureBlobLeaseProviderServices(services, configureOptions)); return builder; } private static void ConfigureAzureBlobLeaseProviderServices(IServiceCollection services, Action> configureOptions) { configureOptions?.Invoke(services.AddOptions()); services.AddTransient(); services.ConfigureFormatter(); services.AddTransient(); services.AddFromExisting(); } /// /// Configure silo to use azure blob lease provider /// public static void UseAzureBlobLeaseProvider(this ISiloPersistentStreamConfigurator configurator, Action> configureOptions) { configurator.ConfigureDelegate(services => { services.AddTransient(sp => AzureBlobLeaseProviderOptionsValidator.Create(sp, configurator.Name)); }); configurator.ConfigureComponent(AzureBlobLeaseProvider.Create, configureOptions); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Options/AzureBlobLeaseProviderOptions.cs ================================================ using System; using System.Threading.Tasks; using Azure; using Azure.Core; using Azure.Storage; using Azure.Storage.Blobs; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Streaming.AzureStorage; namespace Orleans.Configuration { /// /// Options for configuring a lease provider backed by Azure Blob Storage leases. /// public class AzureBlobLeaseProviderOptions { private BlobServiceClient _blobServiceClient; public string BlobContainerName { get; set; } = DefaultBlobContainerName; public const string DefaultBlobContainerName = "Leases"; /// /// Options to be used when configuring the blob storage client, or to use the default options. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public BlobClientOptions ClientOptions { get; set; } /// /// The optional delegate used to create a instance. /// internal Func> CreateClient { get; private set; } /// /// Gets or sets the client used to access the Azure Blob Service. /// public BlobServiceClient BlobServiceClient { get => _blobServiceClient; set { _blobServiceClient = value; CreateClient = () => Task.FromResult(value); } } /// /// Configures the using a connection string. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(string connectionString) { BlobServiceClient = new BlobServiceClient(connectionString, ClientOptions); } /// /// Configures the using an authenticated service URI. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri) { BlobServiceClient = new BlobServiceClient(serviceUri, ClientOptions); } /// /// Configures the using the provided callback. /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Func> createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, TokenCredential tokenCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, tokenCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, AzureSasCredential azureSasCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, azureSasCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(BlobServiceClient)} property directly.")] public void ConfigureBlobServiceClient(Uri serviceUri, StorageSharedKeyCredential sharedKeyCredential) { BlobServiceClient = new BlobServiceClient(serviceUri, sharedKeyCredential, ClientOptions); } } /// /// Configuration validator for AzureBlobLeaseProviderOptions /// public class AzureBlobLeaseProviderOptionsValidator : IConfigurationValidator { private readonly AzureBlobLeaseProviderOptions options; private readonly string name; /// /// Constructor /// /// The option to be validated. public AzureBlobLeaseProviderOptionsValidator(IOptions options) { this.options = options.Value; } /// /// Creates creates validator for named instance of AzureBlobLeaseProviderOptions options. /// /// /// /// public static IConfigurationValidator Create(IServiceProvider services, string name) { var options = services.GetOptionsByName(name); return new AzureBlobLeaseProviderOptionsValidator(options, name); } /// /// Constructor /// /// The option to be validated. /// The option name to be validated. private AzureBlobLeaseProviderOptionsValidator(AzureBlobLeaseProviderOptions options, string name) { this.options = options; this.name = name ?? string.Empty; } public void ValidateConfiguration() { // name can be null, but not empty or white space. if(this.name != null && string.IsNullOrWhiteSpace(this.name)) { throw new OrleansConfigurationException($"Named option {nameof(AzureBlobLeaseProviderOptions)} of name {this.name} is invalid. Name cannot be empty or whitespace."); } if (this.options.CreateClient is null) { throw new OrleansConfigurationException($"No credentials specified for Azure Blob Service lease provider \"{name}\". Use the {options.GetType().Name}.{nameof(AzureBlobLeaseProviderOptions.ConfigureBlobServiceClient)} method to configure the Azure Blob Service client."); } try { AzureBlobUtils.ValidateContainerName(this.options.BlobContainerName); } catch (ArgumentException e) { var errorStr = string.IsNullOrEmpty(this.name) ? $"Configuration for {nameof(AzureBlobLeaseProviderOptions)} {this.name} is invalid. {nameof(this.options.BlobContainerName)} is not valid" : $"Configuration for {nameof(AzureBlobLeaseProviderOptions)} is invalid. {nameof(this.options.BlobContainerName)} is not valid"; throw new OrleansConfigurationException(errorStr , e); } } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Orleans.Streaming.AzureStorage.csproj ================================================ README.md Microsoft.Orleans.Streaming.AzureStorage Microsoft Orleans Azure Queue Storage Streaming Provider Microsoft Orleans streaming provider backed by Azure Queue Storage $(PackageTags) Azure Table Blob Storage $(DefaultTargetFrameworks) Orleans.Streaming.AzureStorage Orleans.Streaming.AzureStorage true $(DefineConstants);ORLEANS_STREAMING ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Lease/AzureBlobLeaseProvider.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.LeaseProviders { public class AzureBlobLeaseProvider : ILeaseProvider { private BlobContainerClient container; private readonly AzureBlobLeaseProviderOptions options; private BlobServiceClient blobClient; public AzureBlobLeaseProvider(IOptions options) : this(options.Value) { } private AzureBlobLeaseProvider(AzureBlobLeaseProviderOptions options) { this.options = options; } private async Task InitContainerIfNotExistsAsync() { if (this.container == null) { this.blobClient = await options.CreateClient(); var tmpContainer = blobClient.GetBlobContainerClient(this.options.BlobContainerName); await tmpContainer.CreateIfNotExistsAsync().ConfigureAwait(false); this.container = tmpContainer; } } private BlobClient GetBlobClient(string category, string resourceKey) => this.container.GetBlobClient($"{category.ToLowerInvariant()}-{resourceKey.ToLowerInvariant()}.json"); public async Task Acquire(string category, LeaseRequest[] leaseRequests) { await InitContainerIfNotExistsAsync(); var tasks = new List>(leaseRequests.Length); foreach (var leaseRequest in leaseRequests) { tasks.Add(Acquire(category, leaseRequest)); } //Task.WhenAll will return results for each task in an array, in the same order of supplied tasks return await Task.WhenAll(tasks); } private async Task Acquire(string category, LeaseRequest leaseRequest) { try { var blobClient = GetBlobClient(category, leaseRequest.ResourceKey); //create this blob await blobClient.UploadAsync(new MemoryStream(Encoding.UTF8.GetBytes("blob")), new BlobHttpHeaders { ContentType = "application/json" }); var leaseClient = blobClient.GetBlobLeaseClient(); var lease = await leaseClient.AcquireAsync(leaseRequest.Duration); return new AcquireLeaseResult(new AcquiredLease(leaseRequest.ResourceKey, leaseRequest.Duration, lease.Value.LeaseId, DateTime.UtcNow), ResponseCode.OK, null); } catch (RequestFailedException e) { ResponseCode statusCode; //This mapping is based on references : https://learn.microsoft.com/rest/api/storageservices/blob-service-error-codes // https://learn.microsoft.com/rest/api/storageservices/Lease-Blob?redirectedfrom=MSDN switch (e.Status) { case 404: case 409: case 412: statusCode = ResponseCode.LeaseNotAvailable; break; default: statusCode = ResponseCode.TransientFailure; break; } return new AcquireLeaseResult(new AcquiredLease(leaseRequest.ResourceKey), statusCode, e); } } public async Task Release(string category, AcquiredLease[] acquiredLeases) { await InitContainerIfNotExistsAsync(); var tasks = new List(acquiredLeases.Length); foreach (var acquiredLease in acquiredLeases) { tasks.Add(Release(category, acquiredLease)); } await Task.WhenAll(tasks); } private Task Release(string category, AcquiredLease acquiredLease) { var leaseClient = GetBlobClient(category, acquiredLease.ResourceKey).GetBlobLeaseClient(acquiredLease.Token); return leaseClient.ReleaseAsync(); } public async Task Renew(string category, AcquiredLease[] acquiredLeases) { await InitContainerIfNotExistsAsync(); var tasks = new List>(acquiredLeases.Length); foreach (var acquiredLease in acquiredLeases) { tasks.Add(Renew(category, acquiredLease)); } //Task.WhenAll will return results for each task in an array, in the same order of supplied tasks return await Task.WhenAll(tasks); } private async Task Renew(string category, AcquiredLease acquiredLease) { var leaseClient = GetBlobClient(category, acquiredLease.ResourceKey).GetBlobLeaseClient(acquiredLease.Token); try { await leaseClient.RenewAsync(); return new AcquireLeaseResult(new AcquiredLease(acquiredLease.ResourceKey, acquiredLease.Duration, acquiredLease.Token, DateTime.UtcNow), ResponseCode.OK, null); } catch (RequestFailedException e) { ResponseCode statusCode; //This mapping is based on references : https://learn.microsoft.com/rest/api/storageservices/blob-service-error-codes // https://learn.microsoft.com/rest/api/storageservices/Lease-Blob?redirectedfrom=MSDN switch (e.Status) { case 404: case 409: case 412: statusCode = ResponseCode.InvalidToken; break; default: statusCode = ResponseCode.TransientFailure; break; } return new AcquireLeaseResult(new AcquiredLease(acquiredLease.ResourceKey), statusCode, e); } } public static ILeaseProvider Create(IServiceProvider services, string name) { AzureBlobLeaseProviderOptions options = services.GetOptionsByName(name); return new AzureBlobLeaseProvider(options); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueAdapter.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.AzureUtils; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { internal sealed class AzureQueueAdapter : IQueueAdapter { private readonly AzureQueueOptions queueOptions; private readonly HashRingBasedPartitionedStreamQueueMapper streamQueueMapper; private readonly ILoggerFactory loggerFactory; private readonly ConcurrentDictionary Queues = new(); private readonly IQueueDataAdapter dataAdapter; public string Name { get; } public bool IsRewindable => false; public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; public AzureQueueAdapter( IQueueDataAdapter dataAdapter, HashRingBasedPartitionedStreamQueueMapper streamQueueMapper, ILoggerFactory loggerFactory, AzureQueueOptions queueOptions, string providerName) { this.queueOptions = queueOptions; Name = providerName; this.streamQueueMapper = streamQueueMapper; this.dataAdapter = dataAdapter; this.loggerFactory = loggerFactory; } public IQueueAdapterReceiver CreateReceiver(QueueId queueId) => AzureQueueAdapterReceiver.Create(loggerFactory, streamQueueMapper.QueueToPartition(queueId), queueOptions, dataAdapter); public async Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { if(token != null) throw new ArgumentException("AzureQueue stream provider currently does not support non-null StreamSequenceToken.", nameof(token)); var queueId = streamQueueMapper.GetQueueForStream(streamId); if (!Queues.TryGetValue(queueId, out var queue)) { var tmpQueue = new AzureQueueDataManager(loggerFactory, streamQueueMapper.QueueToPartition(queueId), queueOptions); await tmpQueue.InitQueueAsync(); queue = Queues.GetOrAdd(queueId, tmpQueue); } var cloudMsg = this.dataAdapter.ToQueueMessage(streamId, events, null, requestContext); await queue.AddQueueMessage(cloudMsg); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueAdapterFactory.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { /// Factory class for Azure Queue based stream provider. public class AzureQueueAdapterFactory : IQueueAdapterFactory { private readonly string providerName; private readonly AzureQueueOptions options; private readonly IQueueDataAdapter dataAdapter; private readonly ILoggerFactory loggerFactory; private readonly HashRingBasedPartitionedStreamQueueMapper streamQueueMapper; private readonly SimpleQueueAdapterCache adapterCache; /// /// Application level failure handler override. /// protected Func> StreamFailureHandlerFactory { private get; set; } public AzureQueueAdapterFactory( string name, AzureQueueOptions options, SimpleQueueCacheOptions cacheOptions, IQueueDataAdapter dataAdapter, ILoggerFactory loggerFactory) { this.providerName = name; this.options = options ?? throw new ArgumentNullException(nameof(options)); this.dataAdapter = dataAdapter ?? throw new ArgumentNullException(nameof(dataAdapter)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.streamQueueMapper = new(options.QueueNames, providerName); this.adapterCache = new SimpleQueueAdapterCache(cacheOptions, this.providerName, this.loggerFactory); } /// Init the factory. public virtual void Init() { this.StreamFailureHandlerFactory = this.StreamFailureHandlerFactory ?? ((qid) => Task.FromResult(new NoOpStreamDeliveryFailureHandler())); } /// Creates the Azure Queue based adapter. public virtual Task CreateAdapter() { var adapter = new AzureQueueAdapter( this.dataAdapter, this.streamQueueMapper, this.loggerFactory, this.options, this.providerName); return Task.FromResult(adapter); } /// Creates the adapter cache. public virtual IQueueAdapterCache GetQueueAdapterCache() { return adapterCache; } /// Creates the factory stream queue mapper. public IStreamQueueMapper GetStreamQueueMapper() { return streamQueueMapper; } /// /// Creates a delivery failure handler for the specified queue. /// /// /// public Task GetDeliveryFailureHandler(QueueId queueId) { return StreamFailureHandlerFactory(queueId); } public static AzureQueueAdapterFactory Create(IServiceProvider services, string name) { var azureQueueOptions = services.GetOptionsByName(name); var cacheOptions = services.GetOptionsByName(name); var dataAdapter = services.GetKeyedService>(name) ?? services.GetService>(); var factory = ActivatorUtilities.CreateInstance(services, name, azureQueueOptions, cacheOptions, dataAdapter); factory.Init(); return factory; } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueAdapterReceiver.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Azure.Storage.Queues.Models; using Microsoft.Extensions.Logging; using Orleans.AzureUtils; using Orleans.AzureUtils.Utilities; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { /// /// Receives batches of messages from a single partition of a message queue. /// internal partial class AzureQueueAdapterReceiver : IQueueAdapterReceiver { private AzureQueueDataManager queue; private long lastReadMessage; private Task outstandingTask; private readonly ILogger logger; private readonly IQueueDataAdapter dataAdapter; private readonly List pending; private readonly string azureQueueName; public static IQueueAdapterReceiver Create(ILoggerFactory loggerFactory, string azureQueueName, AzureQueueOptions queueOptions, IQueueDataAdapter dataAdapter) { if (azureQueueName == null) throw new ArgumentNullException(nameof(azureQueueName)); if (queueOptions == null) throw new ArgumentNullException(nameof(queueOptions)); if (dataAdapter == null) throw new ArgumentNullException(nameof(dataAdapter)); var queue = new AzureQueueDataManager(loggerFactory, azureQueueName, queueOptions); return new AzureQueueAdapterReceiver(azureQueueName, loggerFactory, queue, dataAdapter); } private AzureQueueAdapterReceiver(string azureQueueName, ILoggerFactory loggerFactory, AzureQueueDataManager queue, IQueueDataAdapter dataAdapter) { this.azureQueueName = azureQueueName ?? throw new ArgumentNullException(nameof(azureQueueName)); this.queue = queue?? throw new ArgumentNullException(nameof(queue)); this.dataAdapter = dataAdapter?? throw new ArgumentNullException(nameof(dataAdapter)); this.logger = loggerFactory.CreateLogger(); this.pending = new List(); } public Task Initialize(TimeSpan timeout) { if (queue != null) // check in case we already shut it down. { return queue.InitQueueAsync(); } return Task.CompletedTask; } public async Task Shutdown(TimeSpan timeout) { try { // await the last storage operation, so after we shutdown and stop this receiver we don't get async operation completions from pending storage operations. if (outstandingTask != null) await outstandingTask; } finally { // remember that we shut down so we never try to read from the queue again. queue = null; } } public async Task> GetQueueMessagesAsync(int maxCount) { const int MaxNumberOfMessagesToPeek = 32; try { var queueRef = queue; // store direct ref, in case we are somehow asked to shutdown while we are receiving. if (queueRef == null) return new List(); int count = maxCount < 0 || maxCount == QueueAdapterConstants.UNLIMITED_GET_QUEUE_MSG ? MaxNumberOfMessagesToPeek : Math.Min(maxCount, MaxNumberOfMessagesToPeek) ; var task = queueRef.GetQueueMessages(count); outstandingTask = task; IEnumerable messages = await task; List azureQueueMessages = new List(); foreach (var message in messages) { IBatchContainer container = this.dataAdapter.FromQueueMessage(message.MessageText, lastReadMessage++); azureQueueMessages.Add(container); this.pending.Add(new PendingDelivery(container.SequenceToken, message)); } return azureQueueMessages; } finally { outstandingTask = null; } } public async Task MessagesDeliveredAsync(IList messages) { try { var queueRef = queue; // store direct ref, in case we are somehow asked to shutdown while we are receiving. if (messages.Count == 0 || queueRef==null) return; // get sequence tokens of delivered messages List deliveredTokens = messages.Select(message => message.SequenceToken).ToList(); // find oldest delivered message StreamSequenceToken oldest = deliveredTokens.Max(); // finalize all pending messages at or befor the oldest List finalizedDeliveries = pending .Where(pendingDelivery => !pendingDelivery.Token.Newer(oldest)) .ToList(); if (finalizedDeliveries.Count == 0) return; // remove all finalized deliveries from pending, regardless of if it was delivered or not. pending.RemoveRange(0, finalizedDeliveries.Count); // get the queue messages for all finalized deliveries that were delivered. List deliveredCloudQueueMessages = finalizedDeliveries .Where(finalized => deliveredTokens.Contains(finalized.Token)) .Select(finalized => finalized.Message) .ToList(); if (deliveredCloudQueueMessages.Count == 0) return; // delete all delivered queue messages from the queue. Anything finalized but not delivered will show back up later outstandingTask = Task.WhenAll(deliveredCloudQueueMessages.Select(queueRef.DeleteQueueMessage)); try { await outstandingTask; } catch (Exception exc) { LogWarningOnDeleteQueueMessage(exc, this.azureQueueName); } } finally { outstandingTask = null; } } [LoggerMessage( Level = LogLevel.Warning, EventId = (int)AzureQueueErrorCode.AzureQueue_15, Message = "Exception upon DeleteQueueMessage on queue {QueueName}. Ignoring." )] private partial void LogWarningOnDeleteQueueMessage(Exception exception, string queueName); private class PendingDelivery { public PendingDelivery(StreamSequenceToken token, QueueMessage message) { this.Token = token; this.Message = message; } public QueueMessage Message { get; } public StreamSequenceToken Token { get; } } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueBatchContainer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { [Serializable] [GenerateSerializer] internal class AzureQueueBatchContainer : IBatchContainer { [JsonProperty] [Id(0)] private EventSequenceToken sequenceToken; [JsonProperty] [Id(1)] private readonly List events; [JsonProperty] [Id(2)] private readonly Dictionary requestContext; [Id(3)] public StreamId StreamId { get; private set; } public StreamSequenceToken SequenceToken { get { return sequenceToken; } } internal EventSequenceToken RealSequenceToken { set { sequenceToken = value; } } [JsonConstructor] public AzureQueueBatchContainer( StreamId streamId, List events, Dictionary requestContext, EventSequenceToken sequenceToken) : this(streamId, events, requestContext) { this.sequenceToken = sequenceToken; } public AzureQueueBatchContainer(StreamId streamId, List events, Dictionary requestContext) { if (events == null) throw new ArgumentNullException(nameof(events), "Message contains no events"); StreamId = streamId; this.events = events; this.requestContext = requestContext; } public IEnumerable> GetEvents() { return events.OfType().Select((e, i) => Tuple.Create(e, sequenceToken.CreateSequenceTokenForEvent(i))); } public bool ImportRequestContext() { if (requestContext != null) { RequestContextExtensions.Import(requestContext); return true; } return false; } public override string ToString() { return string.Format("[AzureQueueBatchContainer:Stream={0},#Items={1}]", StreamId, events.Count); //return string.Format("[AzureBatch:#Items={0},Items{1}]", events.Count, Utils.EnumerableToString(events.Select((e, i) => String.Format("{0}-{1}", e, sequenceToken.CreateSequenceTokenForEvent(i))))); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueBatchContainerV2.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { /// /// Second version of AzureQueueBatchContainer. This version supports external serializers (like json) /// [Serializable] [GenerateSerializer] internal class AzureQueueBatchContainerV2 : IBatchContainer { [JsonProperty] [Id(0)] private EventSequenceTokenV2 sequenceToken; [JsonProperty] [Id(1)] private readonly List events; [JsonProperty] [Id(2)] private readonly Dictionary requestContext; [Id(3)] public StreamId StreamId { get; } public StreamSequenceToken SequenceToken => sequenceToken; internal EventSequenceTokenV2 RealSequenceToken { set { sequenceToken = value; } } [JsonConstructor] public AzureQueueBatchContainerV2( StreamId streamId, List events, Dictionary requestContext, EventSequenceTokenV2 sequenceToken) : this(streamId, events, requestContext) { this.sequenceToken = sequenceToken; } public AzureQueueBatchContainerV2(StreamId streamId, List events, Dictionary requestContext) { if (events == null) throw new ArgumentNullException(nameof(events), "Message contains no events"); StreamId = streamId; this.events = events; this.requestContext = requestContext; } public IEnumerable> GetEvents() { return events.OfType().Select((e, i) => Tuple.Create(e, sequenceToken.CreateSequenceTokenForEvent(i))); } public bool ImportRequestContext() { if (requestContext != null) { RequestContextExtensions.Import(requestContext); return true; } return false; } public override string ToString() { return $"[AzureQueueBatchContainerV2:Stream={StreamId},#Items={events.Count}]"; } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueStreamBuilder.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Providers.Streams.AzureQueue; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Hosting { public interface IAzureQueueStreamConfigurator : INamedServiceConfigurator { } public static class AzureQueueStreamConfiguratorExtensions { public static void ConfigureAzureQueue(this IAzureQueueStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } public static void ConfigureQueueDataAdapter(this IAzureQueueStreamConfigurator configurator, Func> factory) { configurator.ConfigureComponent(factory); } public static void ConfigureQueueDataAdapter(this IAzureQueueStreamConfigurator configurator) where TQueueDataAdapter : IQueueDataAdapter { configurator.ConfigureComponent>((sp, n) => ActivatorUtilities.CreateInstance(sp)); } } public interface ISiloAzureQueueStreamConfigurator : IAzureQueueStreamConfigurator, ISiloPersistentStreamConfigurator { } public static class SiloAzureQueueStreamConfiguratorExtensions { public static void ConfigureCacheSize(this ISiloAzureQueueStreamConfigurator configurator, int cacheSize = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE) { configurator.Configure(ob => ob.Configure(options => options.CacheSize = cacheSize)); } } public class SiloAzureQueueStreamConfigurator : SiloPersistentStreamConfigurator, ISiloAzureQueueStreamConfigurator { public SiloAzureQueueStreamConfigurator(string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, AzureQueueAdapterFactory.Create) { this.ConfigureComponent(AzureQueueOptionsValidator.Create); this.ConfigureComponent(SimpleQueueCacheOptionsValidator.Create); //configure default queue names this.ConfigureAzureQueue(ob => ob.PostConfigure>((op, clusterOp) => { if (op.QueueNames == null || op.QueueNames?.Count == 0) { op.QueueNames = AzureQueueStreamProviderUtils.GenerateDefaultAzureQueueNames(clusterOp.Value.ServiceId, this.Name); } })); this.ConfigureDelegate(services => services.TryAddSingleton, AzureQueueDataAdapterV2>()); } } public interface IClusterClientAzureQueueStreamConfigurator : IAzureQueueStreamConfigurator, IClusterClientPersistentStreamConfigurator { } public class ClusterClientAzureQueueStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientAzureQueueStreamConfigurator { public ClusterClientAzureQueueStreamConfigurator(string name, IClientBuilder builder) : base(name, builder, AzureQueueAdapterFactory.Create) { this.ConfigureComponent(AzureQueueOptionsValidator.Create); //configure default queue names this.ConfigureAzureQueue(ob => ob.PostConfigure>((op, clusterOp) => { if (op.QueueNames == null || op.QueueNames?.Count == 0) { op.QueueNames = AzureQueueStreamProviderUtils.GenerateDefaultAzureQueueNames(clusterOp.Value.ServiceId, this.Name); } })); this.ConfigureDelegate(services => services.TryAddSingleton, AzureQueueDataAdapterV2>()); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueStreamOptions.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Azure; using Azure.Core; using Azure.Storage; using Azure.Storage.Queues; using Orleans.AzureUtils; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Azure queue stream provider options. /// public class AzureQueueOptions { private QueueServiceClient _queueServiceClient; /// /// Options to be used when configuring the queue storage client, or to use the default options. /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public QueueClientOptions ClientOptions { get; set; } = new QueueClientOptions { Retry = { Mode = RetryMode.Fixed, Delay = AzureQueueDefaultPolicies.PauseBetweenQueueOperationRetries, MaxRetries = AzureQueueDefaultPolicies.MaxQueueOperationRetries, NetworkTimeout = AzureQueueDefaultPolicies.QueueOperationTimeout, } }; /// /// The optional delegate used to create a instance. /// internal Func> CreateClient { get; private set; } public TimeSpan? MessageVisibilityTimeout { get; set; } public List QueueNames { get; set; } /// /// Gets or sets the used to access the Azure Queue Service. /// public QueueServiceClient QueueServiceClient { get => _queueServiceClient; set { _queueServiceClient = value; CreateClient = () => Task.FromResult(value); } } /// /// Configures the using a connection string. /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString)); CreateClient = () => Task.FromResult(new QueueServiceClient(connectionString, ClientOptions)); } /// /// Configures the using an authenticated service URI. /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(Uri serviceUri) { if (serviceUri is null) throw new ArgumentNullException(nameof(serviceUri)); CreateClient = () => Task.FromResult(new QueueServiceClient(serviceUri, ClientOptions)); } /// /// Configures the using the provided callback. /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(Func> createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(Uri serviceUri, TokenCredential tokenCredential) { if (serviceUri is null) throw new ArgumentNullException(nameof(serviceUri)); if (tokenCredential is null) throw new ArgumentNullException(nameof(tokenCredential)); CreateClient = () => Task.FromResult(new QueueServiceClient(serviceUri, tokenCredential, ClientOptions)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(Uri serviceUri, AzureSasCredential azureSasCredential) { if (serviceUri is null) throw new ArgumentNullException(nameof(serviceUri)); if (azureSasCredential is null) throw new ArgumentNullException(nameof(azureSasCredential)); CreateClient = () => Task.FromResult(new QueueServiceClient(serviceUri, azureSasCredential, ClientOptions)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(QueueServiceClient)} property directly.")] public void ConfigureQueueServiceClient(Uri serviceUri, StorageSharedKeyCredential sharedKeyCredential) { if (serviceUri is null) throw new ArgumentNullException(nameof(serviceUri)); if (sharedKeyCredential is null) throw new ArgumentNullException(nameof(sharedKeyCredential)); CreateClient = () => Task.FromResult(new QueueServiceClient(serviceUri, sharedKeyCredential, ClientOptions)); } } public class AzureQueueOptionsValidator : IConfigurationValidator { private readonly AzureQueueOptions options; private readonly string name; private AzureQueueOptionsValidator(AzureQueueOptions options, string name) { this.options = options; this.name = name; } public void ValidateConfiguration() { if (this.options.CreateClient is null) { throw new OrleansConfigurationException($"No credentials specified. Use the {options.GetType().Name}.{nameof(AzureQueueOptions.ConfigureQueueServiceClient)} method to configure the Azure Queue Service client."); } if (options.QueueNames == null || options.QueueNames.Count == 0) throw new OrleansConfigurationException( $"{nameof(AzureQueueOptions)} on stream provider {this.name} is invalid. {nameof(AzureQueueOptions.QueueNames)} is invalid"); } public static IConfigurationValidator Create(IServiceProvider services, string name) { AzureQueueOptions aqOptions = services.GetOptionsByName(name); return new AzureQueueOptionsValidator(aqOptions, name); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/AzureQueueStreamProviderUtils.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.AzureUtils; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { /// /// Utility functions for azure queue Persistent stream provider. /// public class AzureQueueStreamProviderUtils { /// /// Generate default azure queue names /// /// /// /// public static List GenerateDefaultAzureQueueNames(string serviceId, string providerName) { var defaultQueueMapper = new HashRingBasedStreamQueueMapper(new HashRingStreamQueueMapperOptions(), providerName); return defaultQueueMapper.GetAllQueues() .Select(queueName => $"{serviceId}-{queueName}").ToList(); } /// /// Helper method for testing. Deletes all the queues used by the specified stream provider. /// /// logger factory to use /// azure queue names to be deleted. /// The azure storage connection string. public static async Task DeleteAllUsedAzureQueues(ILoggerFactory loggerFactory, List azureQueueNames, string storageConnectionString) { var options = new AzureQueueOptions(); options.QueueServiceClient = new(storageConnectionString); await DeleteAllUsedAzureQueues(loggerFactory, azureQueueNames, options); } /// /// Helper method for testing. Deletes all the queues used by the specified stream provider. /// /// logger factory to use /// azure queue names to be deleted. /// The azure storage options. public static async Task DeleteAllUsedAzureQueues(ILoggerFactory loggerFactory, List azureQueueNames, AzureQueueOptions queueOptions) { var deleteTasks = new List(); foreach (var queueName in azureQueueNames) { var manager = new AzureQueueDataManager(loggerFactory, queueName, queueOptions); deleteTasks.Add(manager.DeleteQueue()); } await Task.WhenAll(deleteTasks); } /// /// Helper method for testing. Clears all messages in all the queues used by the specified stream provider. /// /// logger factory to use /// The deployment ID hosting the stream provider. /// The azure storage connection string. public static async Task ClearAllUsedAzureQueues(ILoggerFactory loggerFactory, List azureQueueNames, string storageConnectionString) { var options = new AzureQueueOptions(); options.QueueServiceClient = new(storageConnectionString); await ClearAllUsedAzureQueues(loggerFactory, azureQueueNames, options); } /// /// Helper method for testing. Clears all messages in all the queues used by the specified stream provider. /// /// logger factory to use /// The deployment ID hosting the stream provider. /// The azure storage options. public static async Task ClearAllUsedAzureQueues(ILoggerFactory loggerFactory, List azureQueueNames, AzureQueueOptions queueOptions) { var deleteTasks = new List(); foreach (var queueName in azureQueueNames) { var manager = new AzureQueueDataManager(loggerFactory, queueName, queueOptions); deleteTasks.Add(manager.ClearQueue()); } await Task.WhenAll(deleteTasks); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/AzureQueue/IAzureQueueDataAdapter.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; namespace Orleans.Providers.Streams.AzureQueue { /// /// Original data adapter. Here to maintain backwards compatibility, but does not support json and other custom serializers /// [SerializationCallbacks(typeof(OnDeserializedCallbacks))] public class AzureQueueDataAdapterV1 : IQueueDataAdapter, IOnDeserialized { private Serializer serializer; /// /// Initializes a new instance of the class. /// /// public AzureQueueDataAdapterV1(Serializer serializer) { this.serializer = serializer.GetSerializer(); } /// /// Creates a cloud queue message from stream event data. /// public string ToQueueMessage(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { var azureQueueBatchMessage = new AzureQueueBatchContainer(streamId, events.Cast().ToList(), requestContext); var rawBytes = this.serializer.SerializeToArray(azureQueueBatchMessage); return Convert.ToBase64String(rawBytes); } /// /// Creates a batch container from a cloud queue message /// public IBatchContainer FromQueueMessage(string cloudMsg, long sequenceId) { var azureQueueBatch = this.serializer.Deserialize(Convert.FromBase64String(cloudMsg)); azureQueueBatch.RealSequenceToken = new EventSequenceToken(sequenceId); return azureQueueBatch; } void IOnDeserialized.OnDeserialized(DeserializationContext context) { this.serializer = context.ServiceProvider.GetRequiredService>(); } } /// /// Data adapter that uses types that support custom serializers (like json). /// [SerializationCallbacks(typeof(OnDeserializedCallbacks))] public class AzureQueueDataAdapterV2 : IQueueDataAdapter, IOnDeserialized { private Serializer serializer; /// /// Initializes a new instance of the class. /// /// public AzureQueueDataAdapterV2(Serializer serializer) { this.serializer = serializer.GetSerializer(); } /// /// Creates a cloud queue message from stream event data. /// public string ToQueueMessage(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { var azureQueueBatchMessage = new AzureQueueBatchContainerV2(streamId, events.Cast().ToList(), requestContext); var rawBytes = this.serializer.SerializeToArray(azureQueueBatchMessage); return Convert.ToBase64String(rawBytes); } /// /// Creates a batch container from a cloud queue message /// public IBatchContainer FromQueueMessage(string cloudMsg, long sequenceId) { var azureQueueBatch = this.serializer.Deserialize(Convert.FromBase64String(cloudMsg)); azureQueueBatch.RealSequenceToken = new EventSequenceTokenV2(sequenceId); return azureQueueBatch; } void IOnDeserialized.OnDeserialized(DeserializationContext context) { this.serializer = context.ServiceProvider.GetRequiredService>(); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/PersistentStreams/AzureTableStorageStreamFailureHandler.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streaming.AzureStorage; using Orleans.Streams; namespace Orleans.Providers.Streams.PersistentStreams { /// /// Delivery failure handler that writes failures to azure table storage. /// /// public class AzureTableStorageStreamFailureHandler : IStreamFailureHandler where TEntity : StreamDeliveryFailureEntity, new() { private static readonly Func DefaultCreateEntity = () => new TEntity(); private readonly Serializer serializer; private readonly string clusterId; private readonly AzureTableDataManager dataManager; private readonly Func createEntity; /// /// Delivery failure handler that writes failures to azure table storage. /// /// /// logger factory to use /// /// /// /// public AzureTableStorageStreamFailureHandler(Serializer serializer, ILoggerFactory loggerFactory, bool faultOnFailure, string clusterId, AzureStorageOperationOptions azureStorageOptions, Func createEntity = null) { if (string.IsNullOrEmpty(clusterId)) { throw new ArgumentNullException(nameof(clusterId)); } if (string.IsNullOrEmpty(azureStorageOptions.TableName)) { throw new ArgumentNullException(nameof(azureStorageOptions.TableName)); } this.serializer = serializer; this.clusterId = clusterId; ShouldFaultSubsriptionOnError = faultOnFailure; this.createEntity = createEntity ?? DefaultCreateEntity; dataManager = new AzureTableDataManager( azureStorageOptions, loggerFactory.CreateLogger>()); } /// /// Indicates if the subscription should be put in a faulted state upon stream failures /// public bool ShouldFaultSubsriptionOnError { get; private set; } /// /// Initialization /// /// public Task InitAsync() { return dataManager.InitTableAsync(); } /// /// Should be called when an event could not be delivered to a consumer, after exhausting retry attempts. /// /// /// /// /// /// public Task OnDeliveryFailure(GuidId subscriptionId, string streamProviderName, StreamId streamId, StreamSequenceToken sequenceToken) { return OnFailure(subscriptionId, streamProviderName, streamId, sequenceToken); } /// /// Should be called when a subscription requested by a consumer could not be setup, after exhausting retry attempts. /// /// /// /// /// /// public Task OnSubscriptionFailure(GuidId subscriptionId, string streamProviderName, StreamId streamId, StreamSequenceToken sequenceToken) { return OnFailure(subscriptionId, streamProviderName, streamId, sequenceToken); } private async Task OnFailure(GuidId subscriptionId, string streamProviderName, StreamId streamId, StreamSequenceToken sequenceToken) { if (subscriptionId == null) { throw new ArgumentNullException(nameof(subscriptionId)); } if (string.IsNullOrWhiteSpace(streamProviderName)) { throw new ArgumentNullException(nameof(streamProviderName)); } var failureEntity = createEntity(); failureEntity.SubscriptionId = subscriptionId.Guid; failureEntity.StreamProviderName = streamProviderName; failureEntity.StreamGuid = streamId.GetKeyAsString(); failureEntity.StreamNamespace = streamId.GetNamespace(); failureEntity.SetSequenceToken(this.serializer, sequenceToken); failureEntity.SetPartitionKey(this.clusterId); failureEntity.SetRowkey(); await dataManager.CreateTableEntryAsync(failureEntity); } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Providers/Streams/PersistentStreams/StreamDeliveryFailureEntity.cs ================================================ using System; using Azure; using Azure.Data.Tables; using Orleans.Serialization; using Orleans.Streams; namespace Orleans.Providers.Streams.PersistentStreams { /// /// Delivery failure table storage entity. /// public class StreamDeliveryFailureEntity : ITableEntity { public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } /// /// Id of the subscription on which this delivery failure occurred. /// public Guid SubscriptionId { get; set; } /// /// Name of the stream provider generating this failure. /// public string StreamProviderName { get; set; } /// /// Guid Id of the stream on which the failure occurred. /// public string StreamGuid { get; set; } /// /// Namespace of the stream on which the failure occurred. /// public string StreamNamespace { get; set; } /// /// Serialized sequence token of the event that failed delivery. /// public byte[] SequenceToken { get; set; } /// /// Sets the partition key before persist call. /// public virtual void SetPartitionKey(string deploymentId) { PartitionKey = MakeDefaultPartitionKey(StreamProviderName, deploymentId); } /// /// Default partition key /// public static string MakeDefaultPartitionKey(string streamProviderName, string deploymentId) { return $"DeliveryFailure_{streamProviderName}_{deploymentId}"; } /// /// Sets the row key before persist call /// public virtual void SetRowkey() { RowKey = $"{ReverseOrderTimestampTicks():x16}_{Guid.NewGuid()}"; } /// /// Sets sequence token by serializing it to property. /// /// /// public virtual void SetSequenceToken(Serializer serializer, StreamSequenceToken token) { SequenceToken = token != null ? serializer.SerializeToArray(token) : null; } /// /// Gets sequence token by deserializing it from property. /// /// public virtual StreamSequenceToken GetSequenceToken(Serializer serializer) { return SequenceToken != null ? serializer.Deserialize(SequenceToken) : null; } /// /// Returns the number of ticks from now (UTC) to the year 9683. /// /// /// This is useful for ordering the most recent failures at the start of the partition. While useful /// for efficient table storage queries, under heavy failure load this may cause a hot spot in the /// table. This is not an expected occurrence, but if it happens, we recommend subdividing your row /// key with some other field (stream namespace?). /// /// protected static long ReverseOrderTimestampTicks() { var now = DateTime.UtcNow; return DateTime.MaxValue.Ticks - now.Ticks; } } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/README.md ================================================ # Microsoft Orleans Streaming for Azure Storage Queues ## Introduction Microsoft Orleans Streaming for Azure Storage provides a stream provider implementation for Orleans using Azure Storage Queues. This allows for publishing and subscribing to streams of events with Azure Storage Queues as the underlying messaging infrastructure. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Streaming.AzureStorage ``` ## Example - Configuring Azure Storage Queues Streaming ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Streams; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Storage Queues as a stream provider .AddAzureQueueStreams( name: "AzureQueueStreamProvider", b => b.ConfigureAzureQueue(ob => ob.Configure((options, dep) => { options.ConfigureTestDefaults(); options.QueueNames = Enumerable.Range(0, 8).Select(num => $"{dep.Value.ClusterId}-{num}").ToList(); }))); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Azure Storage Queue Streams in a Grain ```csharp // Producer grain public class ProducerGrain : Grain, IProducerGrain { private IAsyncStream _stream; public override Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("AzureQueueStreamProvider"); _stream = streamProvider.GetStream(Guid.NewGuid(), "MyStreamNamespace"); return base.OnActivateAsync(cancellationToken); } public async Task SendMessage(string message) { // Send a message to the stream await _stream.OnNextAsync(message); } } // Consumer grain public class ConsumerGrain : Grain, IConsumerGrain, IAsyncObserver { private StreamSubscriptionHandle _subscription; public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Get a reference to a stream var streamProvider = GetStreamProvider("AzureQueueStreamProvider"); var stream = streamProvider.GetStream(this.GetPrimaryKey(), "MyStreamNamespace"); // Subscribe to the stream _subscription = await stream.SubscribeAsync(this); await base.OnActivateAsync(cancellationToken); } public Task OnNextAsync(string item, StreamSequenceToken token = null) { Console.WriteLine($"Received message: {item}"); return Task.CompletedTask; } public Task OnCompletedAsync() { Console.WriteLine("Stream completed"); return Task.CompletedTask; } public Task OnErrorAsync(Exception ex) { Console.WriteLine($"Stream error: {ex.Message}"); return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Streams](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/index) - [Stream Providers](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/stream-providers) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Storage/AzureQueueDataManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Azure; using Azure.Storage.Queues; using Azure.Storage.Queues.Models; using Microsoft.Extensions.Logging; using Orleans.AzureUtils.Utilities; using Orleans.Configuration; namespace Orleans.AzureUtils { /// /// How to use the Queue Storage Service: http://www.windowsazure.com/en-us/develop/net/how-to-guides/queue-service/ /// Windows Azure Storage Abstractions and their Scalability Targets: http://blogs.msdn.com/b/windowsazurestorage/archive/2010/05/10/windows-azure-storage-abstractions-and-their-scalability-targets.aspx /// Naming Queues and Metadata: http://msdn.microsoft.com/en-us/library/windowsazure/dd179349.aspx /// Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted: http://msdn.microsoft.com/en-us/library/hh767287(VS.103).aspx /// Status and Error Codes: http://msdn.microsoft.com/en-us/library/dd179382.aspx /// /// http://blogs.msdn.com/b/windowsazurestorage/archive/tags/scalability/ /// http://blogs.msdn.com/b/windowsazurestorage/archive/2010/12/30/windows-azure-storage-architecture-overview.aspx /// http://blogs.msdn.com/b/windowsazurestorage/archive/2010/11/06/how-to-get-most-out-of-windows-azure-tables.aspx /// /// internal static class AzureQueueDefaultPolicies { public static int MaxQueueOperationRetries; public static TimeSpan PauseBetweenQueueOperationRetries; public static TimeSpan QueueOperationTimeout; static AzureQueueDefaultPolicies() { MaxQueueOperationRetries = 5; PauseBetweenQueueOperationRetries = TimeSpan.FromMilliseconds(100); QueueOperationTimeout = PauseBetweenQueueOperationRetries.Multiply(MaxQueueOperationRetries).Multiply(6); // 3 sec } } /// /// Utility class to encapsulate access to Azure queue storage. /// /// /// Used by Azure queue streaming provider. /// public partial class AzureQueueDataManager { /// Name of the table queue instance is managing. public string QueueName { get; private set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] private readonly ILogger logger; private readonly TimeSpan? messageVisibilityTimeout; private readonly AzureQueueOptions options; private QueueClient _queueClient; /// /// Constructor. /// /// logger factory to use /// Name of the queue to be connected to. /// Connection string for the Azure storage account used to host this table. /// A TimeSpan specifying the visibility timeout interval public AzureQueueDataManager(ILoggerFactory loggerFactory, string queueName, string storageConnectionString, TimeSpan? visibilityTimeout = null) : this (loggerFactory, queueName, ConfigureOptions(storageConnectionString, visibilityTimeout)) { } private static AzureQueueOptions ConfigureOptions(string storageConnectionString, TimeSpan? visibilityTimeout) { var options = new AzureQueueOptions { MessageVisibilityTimeout = visibilityTimeout }; options.QueueServiceClient = new(storageConnectionString); return options; } /// /// Constructor. /// /// logger factory to use /// Name of the queue to be connected to. /// Queue connection options. public AzureQueueDataManager(ILoggerFactory loggerFactory, string queueName, AzureQueueOptions options) { queueName = SanitizeQueueName(queueName); ValidateQueueName(queueName); logger = loggerFactory.CreateLogger(); QueueName = queueName; messageVisibilityTimeout = options.MessageVisibilityTimeout; this.options = options; } private ValueTask GetQueueClient() { if (_queueClient is { } client) { return new(client); } return GetQueueClientAsync(); async ValueTask GetQueueClientAsync() => _queueClient ??= await GetCloudQueueClient(options); } /// /// Initializes the connection to the queue. /// public async Task InitQueueAsync() { var startTime = DateTime.UtcNow; try { // Create the queue if it doesn't already exist. var client = await GetQueueClient(); var response = await client.CreateIfNotExistsAsync(); LogInfoAzureQueueConnection(QueueName); } catch (Exception exc) { ReportErrorAndRethrow(exc, "CreateIfNotExist", AzureQueueErrorCode.AzureQueue_02); } finally { CheckAlertSlowAccess(startTime, "InitQueue_Async"); } } /// /// Deletes the queue. /// public async Task DeleteQueue() { var startTime = DateTime.UtcNow; LogTraceDeletingQueue(QueueName); try { // that way we don't have first to create the queue to be able later to delete it. var client = await GetQueueClient(); if (await client.DeleteIfExistsAsync()) { LogInfoAzureQueueDeleted(QueueName); } } catch (Exception exc) { ReportErrorAndRethrow(exc, "DeleteQueue", AzureQueueErrorCode.AzureQueue_04); } finally { CheckAlertSlowAccess(startTime, "DeleteQueue"); } } /// /// Clears the queue. /// public async Task ClearQueue() { var startTime = DateTime.UtcNow; LogTraceClearingAQueue(QueueName); try { // that way we don't have first to create the queue to be able later to delete it. var client = await GetQueueClient(); await client.ClearMessagesAsync(); LogInfoAzureQueueClear(QueueName); } catch (RequestFailedException exc) { if (exc.Status != (int)HttpStatusCode.NotFound) { ReportErrorAndRethrow(exc, "ClearQueue", AzureQueueErrorCode.AzureQueue_06); } } catch (Exception exc) { ReportErrorAndRethrow(exc, "ClearQueue", AzureQueueErrorCode.AzureQueue_06); } finally { CheckAlertSlowAccess(startTime, "ClearQueue"); } } /// /// Adds a new message to the queue. /// /// Message to be added to the queue. public async Task AddQueueMessage(string message) { var startTime = DateTime.UtcNow; LogTraceAddingMessage(message, QueueName); try { var client = await GetQueueClient(); await client.SendMessageAsync(message); } catch (Exception exc) { ReportErrorAndRethrow(exc, "AddQueueMessage", AzureQueueErrorCode.AzureQueue_07); } finally { CheckAlertSlowAccess(startTime, "AddQueueMessage"); } } /// /// Peeks in the queue for latest message, without dequeuing it. /// public async Task PeekQueueMessage() { var startTime = DateTime.UtcNow; LogTracePeekingMessage(QueueName); try { var client = await GetQueueClient(); var messages = await client.PeekMessagesAsync(maxMessages: 1); return messages.Value.FirstOrDefault(); } catch (Exception exc) { ReportErrorAndRethrow(exc, "PeekQueueMessage", AzureQueueErrorCode.AzureQueue_08); return null; // Dummy statement to keep compiler happy } finally { CheckAlertSlowAccess(startTime, "PeekQueueMessage"); } } /// /// Gets a new message from the queue. /// public async Task GetQueueMessage() { var startTime = DateTime.UtcNow; LogTraceGettingMessage(QueueName); try { //BeginGetMessage and EndGetMessage is not supported in netstandard, may be use GetMessageAsync // http://msdn.microsoft.com/en-us/library/ee758456.aspx // If no messages are visible in the queue, GetMessage returns null. var client = await GetQueueClient(); var messages = await client.ReceiveMessagesAsync(maxMessages: 1, messageVisibilityTimeout); return messages.Value.FirstOrDefault(); } catch (Exception exc) { ReportErrorAndRethrow(exc, "GetQueueMessage", AzureQueueErrorCode.AzureQueue_09); return null; // Dummy statement to keep compiler happy } finally { CheckAlertSlowAccess(startTime, "GetQueueMessage"); } } /// /// Gets a number of new messages from the queue. /// /// Number of messages to get from the queue. public async Task> GetQueueMessages(int? count = null) { var startTime = DateTime.UtcNow; if (count == -1) { count = null; } LogTraceGettingUpToMessages(count, QueueName); try { var client = await GetQueueClient(); var messages = await client.ReceiveMessagesAsync(count, messageVisibilityTimeout); return messages.Value; } catch (Exception exc) { ReportErrorAndRethrow(exc, "GetQueueMessages", AzureQueueErrorCode.AzureQueue_10); return null; // Dummy statement to keep compiler happy } finally { CheckAlertSlowAccess(startTime, "GetQueueMessages"); } } /// /// Deletes a messages from the queue. /// /// A message to be deleted from the queue. public async Task DeleteQueueMessage(QueueMessage message) { var startTime = DateTime.UtcNow; LogTraceDeletingAMessage(QueueName); try { var client = await GetQueueClient(); await client.DeleteMessageAsync(message.MessageId, message.PopReceipt); } catch (RequestFailedException exc) { if (exc.Status != (int)HttpStatusCode.NotFound) { ReportErrorAndRethrow(exc, "DeleteMessage", AzureQueueErrorCode.AzureQueue_11); } } catch (Exception exc) { ReportErrorAndRethrow(exc, "DeleteMessage", AzureQueueErrorCode.AzureQueue_11); } finally { CheckAlertSlowAccess(startTime, "DeleteQueueMessage"); } } internal async Task GetAndDeleteQueueMessage() { var message = await GetQueueMessage(); await DeleteQueueMessage(message); } /// /// Returns an approximate number of messages in the queue. /// public async Task GetApproximateMessageCount() { var startTime = DateTime.UtcNow; LogTraceGetApproximateMessageCount(QueueName); try { var client = await GetQueueClient(); var properties = await client.GetPropertiesAsync(); return properties.Value.ApproximateMessagesCount; } catch (Exception exc) { ReportErrorAndRethrow(exc, "FetchAttributes", AzureQueueErrorCode.AzureQueue_12); return 0; // Dummy statement to keep compiler happy } finally { CheckAlertSlowAccess(startTime, "GetApproximateMessageCount"); } } private void CheckAlertSlowAccess(DateTime startOperation, string operation) { var timeSpan = DateTime.UtcNow - startOperation; if (timeSpan > AzureQueueDefaultPolicies.QueueOperationTimeout) { LogWarningSlowAzureQueueAccess(QueueName, operation, timeSpan); } } private void ReportErrorAndRethrow(Exception exc, string operation, AzureQueueErrorCode errorCode) { var errMsg = string.Format( "Error doing {0} for Azure storage queue {1} " + Environment.NewLine + "Exception = {2}", operation, QueueName, exc); logger.LogError((int)errorCode, exc, "{Message}", errMsg); // TODO: pending on https://github.com/dotnet/runtime/issues/110570 throw new AggregateException(errMsg, exc); } private async Task GetCloudQueueClient(AzureQueueOptions options) { try { var client = await options.CreateClient(); return client.GetQueueClient(QueueName); } catch (Exception exc) { LogErrorCreatingAzureQueueClient(exc); throw; } } private static string SanitizeQueueName(string queueName) { var tmp = queueName; //Azure queue naming rules : https://learn.microsoft.com/rest/api/storageservices/Naming-Queues-and-Metadata?redirectedfrom=MSDN tmp = tmp.ToLowerInvariant(); tmp = tmp .Replace('/', '-') // Forward slash .Replace('\\', '-') // Backslash .Replace('#', '-') // Pound sign .Replace('?', '-') // Question mark .Replace('&', '-') .Replace('+', '-') .Replace(':', '-') .Replace('.', '-') .Replace('%', '-'); return tmp; } private static void ValidateQueueName(string queueName) { // Naming Queues and Metadata: http://msdn.microsoft.com/en-us/library/windowsazure/dd179349.aspx if (!(queueName.Length >= 3 && queueName.Length <= 63)) { // A queue name must be from 3 through 63 characters long. throw new ArgumentException(string.Format("A queue name must be from 3 through 63 characters long, while your queueName length is {0}, queueName is {1}.", queueName.Length, queueName), queueName); } if (!char.IsLetterOrDigit(queueName.First())) { // A queue name must start with a letter or number throw new ArgumentException(string.Format("A queue name must start with a letter or number, while your queueName is {0}.", queueName), queueName); } if (!char.IsLetterOrDigit(queueName.Last())) { // The first and last letters in the queue name must be alphanumeric. The dash (-) character cannot be the first or last character. throw new ArgumentException(string.Format("The last letter in the queue name must be alphanumeric, while your queueName is {0}.", queueName), queueName); } if (!queueName.All(c => char.IsLetterOrDigit(c) || c.Equals('-'))) { // A queue name can only contain letters, numbers, and the dash (-) character. throw new ArgumentException(string.Format("A queue name can only contain letters, numbers, and the dash (-) character, while your queueName is {0}.", queueName), queueName); } if (queueName.Contains("--")) { // Consecutive dash characters are not permitted in the queue name. throw new ArgumentException(string.Format("Consecutive dash characters are not permitted in the queue name, while your queueName is {0}.", queueName), queueName); } if (queueName.Where(char.IsLetter).Any(c => !char.IsLower(c))) { // All letters in a queue name must be lowercase. throw new ArgumentException(string.Format("All letters in a queue name must be lowercase, while your queueName is {0}.", queueName), queueName); } } [LoggerMessage( EventId = (int)AzureQueueErrorCode.AzureQueue_01, Level = LogLevel.Information, Message = "Connected to Azure storage queue {QueueName}" )] private partial void LogInfoAzureQueueConnection(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Deleting queue: {QueueName}" )] private partial void LogTraceDeletingQueue(string queueName); [LoggerMessage( EventId = (int)AzureQueueErrorCode.AzureQueue_03, Level = LogLevel.Information, Message = "Deleted Azure Queue {QueueName}" )] private partial void LogInfoAzureQueueDeleted(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Clearing a queue: {QueueName}" )] private partial void LogTraceClearingAQueue(string queueName); [LoggerMessage( EventId = (int)AzureQueueErrorCode.AzureQueue_05, Level = LogLevel.Information, Message = "Cleared Azure Queue {QueueName}" )] private partial void LogInfoAzureQueueClear(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Adding message {Data} to queue: {QueueName}" )] private partial void LogTraceAddingMessage(string data, string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Peeking a message from queue: {QueueName}" )] private partial void LogTracePeekingMessage(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Getting a message from queue: {QueueName}" )] private partial void LogTraceGettingMessage(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Getting up to {MessageCount} messages from queue: {QueueName}" )] private partial void LogTraceGettingUpToMessages(int? messageCount, string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "Deleting a message from queue: {QueueName}" )] private partial void LogTraceDeletingAMessage(string queueName); [LoggerMessage( Level = LogLevel.Trace, Message = "GetApproximateMessageCount a message from queue: {QueueName}" )] private partial void LogTraceGetApproximateMessageCount(string queueName); [LoggerMessage( EventId = (int)AzureQueueErrorCode.AzureQueue_13, Level = LogLevel.Warning, Message = "Slow access to Azure queue {QueueName} for {Operation}, which took {TimeSpan}." )] private partial void LogWarningSlowAzureQueueAccess(string queueName, string operation, TimeSpan timeSpan); [LoggerMessage( EventId = (int)AzureQueueErrorCode.AzureQueue_14, Level = LogLevel.Error, Message = "Error creating Azure Storage Queues client" )] private partial void LogErrorCreatingAzureQueueClient(Exception exception); } } ================================================ FILE: src/Azure/Orleans.Streaming.AzureStorage/Utilities/AzureQueueErrorCode.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Orleans.AzureUtils.Utilities { [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum AzureQueueErrorCode { Runtime = 100000, AzureQueueBase = Runtime + 3200, AzureQueue_01 = AzureQueueBase + 1, AzureQueue_02 = AzureQueueBase + 2, AzureQueue_03 = AzureQueueBase + 3, AzureQueue_04 = AzureQueueBase + 4, AzureQueue_05 = AzureQueueBase + 5, AzureQueue_06 = AzureQueueBase + 6, AzureQueue_07 = AzureQueueBase + 7, AzureQueue_08 = AzureQueueBase + 8, AzureQueue_09 = AzureQueueBase + 9, AzureQueue_10 = AzureQueueBase + 10, AzureQueue_11 = AzureQueueBase + 11, AzureQueue_12 = AzureQueueBase + 12, AzureQueue_13 = AzureQueueBase + 13, AzureQueue_14 = AzureQueueBase + 14, AzureQueue_15 = AzureQueueBase + 15, } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Hosting/ClientBuilderExtensions.cs ================================================ using System; using Orleans.Configuration; namespace Orleans.Hosting { public static class ClientBuilderExtensions { /// /// Configure cluster client to use event hub persistent streams. /// public static IClientBuilder AddEventHubStreams( this IClientBuilder builder, string name, Action configure) { var configurator = new ClusterClientEventHubStreamConfigurator(name,builder); configure?.Invoke(configurator); return builder; } /// /// Configure cluster client to use event hub persistent streams with default settings. /// public static IClientBuilder AddEventHubStreams( this IClientBuilder builder, string name, Action configureEventHub) { builder.AddEventHubStreams(name, b=>b.ConfigureEventHub(ob => ob.Configure(configureEventHub))); return builder; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Hosting/DeveloperExtensions.cs ================================================ using System; namespace Orleans.Hosting.Developer { public static class SiloBuilderExtensions { /// /// Configure silo to use event data generator streams. /// public static ISiloBuilder AddEventDataGeneratorStreams( this ISiloBuilder builder, string name, Action configure) { var configurator = new EventDataGeneratorStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure?.Invoke(configurator); return builder; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Hosting/SiloBuilderExtensions.cs ================================================ using System; using Orleans.Configuration; namespace Orleans.Hosting { public static class SiloBuilderExtensions { /// /// Configure silo to use event hub persistent streams. /// public static ISiloBuilder AddEventHubStreams( this ISiloBuilder builder, string name, Action configure) { var configurator = new SiloEventHubStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure?.Invoke(configurator); return builder; } /// /// Configure silo to use event hub persistent streams with default check pointer and other settings /// public static ISiloBuilder AddEventHubStreams( this ISiloBuilder builder, string name, Action configureEventHub, Action configureDefaultCheckpointer) { return builder.AddEventHubStreams(name, b => { b.ConfigureEventHub(ob => ob.Configure(configureEventHub)); b.UseAzureTableCheckpointer(ob => ob.Configure(configureDefaultCheckpointer)); }); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Orleans.Streaming.EventHubs.csproj ================================================ Microsoft.Orleans.Streaming.EventHubs Microsoft Orleans Azure Event Hubs Streaming Provider Microsoft Orleans streaming provider backed by Azure Event Hubs $(PackageTags) Azure EventHubs $(DefineConstants);ORLEANS_EVENTHUBS $(DefaultTargetFrameworks) Orleans.Streaming.EventHubs Orleans.Streaming.EventHubs true README.md ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/OrleansServiceBusErrorCode.cs ================================================ namespace Orleans.Streaming.EventHubs { /// /// Orleans ServiceBus error codes /// internal enum OrleansEventHubErrorCode { /// /// Start of orlean servicebus error codes /// ServiceBus = 1<<16, FailedPartitionRead = ServiceBus + 1, RetryReceiverInit = ServiceBus + 2, } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/EventDataGeneratorAdapterFactory.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Azure.Messaging.EventHubs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Statistics; using Orleans.Streams; namespace Orleans.Streaming.EventHubs.Testing { /// /// This is a persistent stream provider adapter that generates it's own events rather than reading them from Eventhub. /// This is primarily for test purposes. /// public partial class EventDataGeneratorAdapterFactory : EventHubAdapterFactory, IControllable { private readonly EventDataGeneratorStreamOptions ehGeneratorOptions; public EventDataGeneratorAdapterFactory( string name, EventDataGeneratorStreamOptions options, EventHubOptions ehOptions, EventHubReceiverOptions receiverOptions, EventHubStreamCachePressureOptions cacheOptions, StreamCacheEvictionOptions evictionOptions, StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdapter, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IEnvironmentStatisticsProvider environmentStatisticsProvider) : base(name, ehOptions, receiverOptions, cacheOptions, evictionOptions, statisticOptions, dataAdapter, serviceProvider, loggerFactory, environmentStatisticsProvider) { this.ehGeneratorOptions = options; } public override void Init() { this.EventHubReceiverFactory = this.EHGeneratorReceiverFactory; base.Init(); } /// protected override void InitEventHubClient() { //do nothing, EventDataGeneratorStreamProvider doesn't need connection with EventHubClient } /// /// Generate mocked eventhub partition Ids from EventHubGeneratorStreamProviderSettings /// /// protected override Task GetPartitionIdsAsync() { return Task.FromResult(GenerateEventHubPartitions(this.ehGeneratorOptions.EventHubPartitionCount)); } private IEventHubReceiver EHGeneratorReceiverFactory(EventHubPartitionSettings settings, string offset, ILogger logger) { var streamGeneratorFactory = this.serviceProvider.GetKeyedService>>(this.Name) ?? SimpleStreamEventDataGenerator.CreateFactory(this.serviceProvider); var generator = new EventHubPartitionDataGenerator(this.ehGeneratorOptions, streamGeneratorFactory, logger); return new EventHubPartitionGeneratorReceiver(generator); } private void RandomlyPlaceStreamToQueue(StreamRandomPlacementArg args) { if (args == null) return; var allQueueInTheCluster = EventHubQueueMapper?.GetAllQueues(); if (allQueueInTheCluster != null) { var allQueues = allQueueInTheCluster as QueueId[] ?? allQueueInTheCluster.ToArray(); //every agent receive the same random number, do a mod on queue count, get the same random queue to assign stream to. var queueToAssign = allQueues[args.RandomNumber % allQueues.Length]; if (EventHubReceivers.TryGetValue(queueToAssign, out var receiverToAssign)) { receiverToAssign.ConfigureDataGeneratorForStream(args.StreamId); LogInfoStreamAssignedToQueue(logger, args.StreamId, queueToAssign); } } else { LogInfoCannotGetQueues(logger); } } private void StopProducingOnStream(StreamId streamId) { foreach (var ehReceiver in this.EventHubReceivers) { //if the stream is assigned to this receiver/queue, then it will ask the data generator to stop producing ehReceiver.Value.StopProducingOnStream(streamId); } } public static string[] GenerateEventHubPartitions(int partitionCount) { var partitions = new string[partitionCount]; for (int i = 0; i < partitions.Length; i++) partitions[i] = $"partition-{i}"; return partitions; } /// /// Commands for IControllable /// public enum Commands { /// /// Command for Randomly_Place_Stream_To_Queue /// Randomly_Place_Stream_To_Queue = (int)PersistentStreamProviderCommand.AdapterFactoryCommandStartRange + 4, /// /// Command for Stop_Producing_On_Stream /// Stop_Producing_On_Stream = (int)PersistentStreamProviderCommand.AdapterFactoryCommandStartRange + 5 } /// /// Args for RandomlyPlaceStreamToQueue method /// [Serializable] [GenerateSerializer] public class StreamRandomPlacementArg { /// /// StreamId /// [Id(0)] public StreamId StreamId { get; set; } /// /// A random number /// [Id(1)] public int RandomNumber { get; set; } /// /// Constructor /// /// /// public StreamRandomPlacementArg(StreamId streamId, int randomNumber) { this.StreamId = streamId; this.RandomNumber = randomNumber; } } /// /// Execute Command /// /// /// /// public virtual Task ExecuteCommand(int command, object arg) { switch (command) { case (int)Commands.Randomly_Place_Stream_To_Queue: this.RandomlyPlaceStreamToQueue(arg as StreamRandomPlacementArg); break; case (int)Commands.Stop_Producing_On_Stream: this.StopProducingOnStream((StreamId) arg); break; default: break; } return Task.FromResult((object)true); } public new static EventDataGeneratorAdapterFactory Create(IServiceProvider services, string name) { var generatorOptions= services.GetOptionsByName(name); var ehOptions = services.GetOptionsByName(name); var receiverOptions = services.GetOptionsByName(name); var cacheOptions = services.GetOptionsByName(name); var statisticOptions = services.GetOptionsByName(name); var evictionOptions = services.GetOptionsByName(name); IEventHubDataAdapter dataAdapter = services.GetKeyedService(name) ?? services.GetService() ?? ActivatorUtilities.CreateInstance(services); var factory = ActivatorUtilities.CreateInstance(services, name, generatorOptions, ehOptions, receiverOptions, cacheOptions, evictionOptions, statisticOptions, dataAdapter); factory.Init(); return factory; } [LoggerMessage( Level = LogLevel.Information, Message = "Stream {StreamId} is assigned to queue {QueueId}" )] private static partial void LogInfoStreamAssignedToQueue(ILogger logger, StreamId streamId, QueueId queueId); [LoggerMessage( Level = LogLevel.Information, Message = "Cannot get queues in the cluster, current streamQueueMapper is not EventHubQueueMapper" )] private static partial void LogInfoCannotGetQueues(ILogger logger); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/EventDataGeneratorStreamOptions.cs ================================================  namespace Orleans.Configuration { /// /// Setting class for EHGeneratorStreamProvider /// public class EventDataGeneratorStreamOptions { /// /// Configure eventhub partition count wanted. EventDataGeneratorStreamProvider would generate the same set of partitions based on the count, when initializing. /// For example, if partition count set at 5, the generated partitions will be partition-0, partition-1, partition-2, partition-3, partition-4 /// public int EventHubPartitionCount = DefaultEventHubPartitionCount; /// /// Default EventHubPartitionRangeStart /// public const int DefaultEventHubPartitionCount = 4; } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/EventHubPartitionDataGenerator.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Azure.Messaging.EventHubs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization; namespace Orleans.Streaming.EventHubs.Testing { /// /// Generate data for one stream /// public partial class SimpleStreamEventDataGenerator : IStreamDataGenerator { /// public StreamId StreamId { get; set; } /// public IIntCounter SequenceNumberCounter { set; private get; } /// public bool ShouldProduce { private get; set; } private readonly ILogger logger; private readonly DeepCopier deepCopier; private readonly Serializer serializer; public SimpleStreamEventDataGenerator(StreamId streamId, ILogger logger, DeepCopier deepCopier, Serializer serializer) { this.StreamId = streamId; this.logger = logger; this.ShouldProduce = true; this.deepCopier = deepCopier; this.serializer = serializer; } /// public bool TryReadEvents(int maxCount, out IEnumerable events) { if (!this.ShouldProduce) { events = null; return false; } int count = maxCount; List eventDataList = new List(maxCount); while (count-- > 0) { this.SequenceNumberCounter.Increment(); // Create an EventData instance with an empty body. The body will be set later // from the batch container's context. Because there is a need to explicitly set // broker-owned properties such as the offset, sequence number, and partition key, // an instance is created using the model factory, which avoids the need to set // directly via the underlying AMQP message. var eventData = EventHubsModelFactory.EventData( eventBody: BinaryData.Empty, partitionKey: StreamId.GetKeyAsString(), offsetString: DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture), sequenceNumber: this.SequenceNumberCounter.Value); EventHubBatchContainer.UpdateEventData( eventData, this.serializer, this.StreamId, GenerateEvent(this.SequenceNumberCounter.Value), RequestContextExtensions.Export(this.deepCopier)); eventDataList.Add(eventData); LogInfoGenerateData(this.SequenceNumberCounter.Value, this.StreamId); } events = eventDataList; return eventDataList.Count > 0; } private static IEnumerable GenerateEvent(int sequenceNumber) { return [sequenceNumber]; } public static Func> CreateFactory(IServiceProvider services) { return (streamId) => ActivatorUtilities.CreateInstance(services, streamId); } [LoggerMessage( Level = LogLevel.Information, Message = "Generate data of SequenceNumber {SequenceNumber} for stream {StreamId}" )] private partial void LogInfoGenerateData(int sequenceNumber, StreamId streamId); } /// /// EHPartitionDataGenerator generate data for a EH partition, which can include data from different streams /// public partial class EventHubPartitionDataGenerator : IDataGenerator, IStreamDataGeneratingController { //differnt stream in the same partition should use the same sequenceNumberCounter private readonly EventDataGeneratorStreamOptions options; private readonly IntCounter sequenceNumberCounter = new IntCounter(); private readonly ILogger logger; private readonly Func> generatorFactory; private readonly List> generators; /// /// Constructor /// /// /// /// public EventHubPartitionDataGenerator(EventDataGeneratorStreamOptions options, Func> generatorFactory, ILogger logger) { this.options = options; this.generatorFactory = generatorFactory; this.generators = new List>(); this.logger = logger; } /// public void AddDataGeneratorForStream(StreamId streamId) { var generator = this.generatorFactory(streamId); generator.SequenceNumberCounter = sequenceNumberCounter; LogInfoOnStreamSetup(streamId); this.generators.Add(generator); } /// public void StopProducingOnStream(StreamId streamId) { this.generators.ForEach(generator => { if (generator.StreamId.Equals(streamId)) { generator.ShouldProduce = false; LogInfoOnStreamStop(streamId); } }); } /// public bool TryReadEvents(int maxCount, out IEnumerable events) { if (this.generators.Count == 0) { events = new List(); return false; } var eventDataList = new List(); var iterator = this.generators.AsEnumerable().GetEnumerator(); var batchCount = maxCount / this.generators.Count; batchCount = batchCount == 0 ? batchCount + 1 : batchCount; while (eventDataList.Count < maxCount) { //if reach to the end of the list, reset iterator to the head if (!iterator.MoveNext()) { iterator.Reset(); iterator.MoveNext(); } IEnumerable eventData; var remainingCount = maxCount - eventDataList.Count; var count = remainingCount > batchCount ? batchCount : remainingCount; if (iterator.Current.TryReadEvents(count, out eventData)) { foreach (var data in eventData) { eventDataList.Add(data); } } } iterator.Dispose(); events = eventDataList.AsEnumerable(); return eventDataList.Count > 0; } [LoggerMessage( Level = LogLevel.Information, Message = "Data generator set up on stream {StreamId}." )] private partial void LogInfoOnStreamSetup(StreamId streamId); [LoggerMessage( Level = LogLevel.Information, Message = "Stop producing data on stream {StreamId}." )] private partial void LogInfoOnStreamStop(StreamId streamId); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/EventHubPartitionGeneratorReceiver.cs ================================================ using Orleans.Runtime; using Azure.Messaging.EventHubs; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Streaming.EventHubs.Testing { /// /// Eventhub receiver which configured with data generator /// public class EventHubPartitionGeneratorReceiver : IEventHubReceiver { private readonly IDataGenerator generator; /// /// Constructor /// /// public EventHubPartitionGeneratorReceiver(IDataGenerator generator) { this.generator = generator; } /// public async Task> ReceiveAsync(int maxCount, TimeSpan waitTime) { IEnumerable events; //mimic real life response time await Task.Delay(TimeSpan.FromMilliseconds(30)); if (generator.TryReadEvents(maxCount, out events)) { return events; } //if no events generated, wait for waitTime to pass await Task.Delay(waitTime); return new List().AsEnumerable(); } /// public void StopProducingOnStream(StreamId streamId) { (this.generator as IStreamDataGeneratingController)?.StopProducingOnStream(streamId); } /// public void ConfigureDataGeneratorForStream(StreamId streamId) { (this.generator as IStreamDataGeneratingController)?.AddDataGeneratorForStream(streamId); } /// public Task CloseAsync() { return Task.CompletedTask; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/IEventDataGenerator.cs ================================================ using Orleans.Runtime; using System.Collections.Generic; namespace Orleans.Streaming.EventHubs.Testing { /// /// Data generator for test purpose /// /// public interface IDataGenerator { /// /// Data generator mimic event reading /// /// /// /// bool TryReadEvents(int maxCount, out IEnumerable events); } /// /// StreamDataGeneratingController control stream data generating start and stop /// public interface IStreamDataGeneratingController { /// /// configure data generator for a stream /// /// void AddDataGeneratorForStream(StreamId streamId); /// /// Ask one stream to stop producing /// /// void StopProducingOnStream(StreamId streamId); } /// /// data generator for a specific stream /// /// public interface IStreamDataGenerator: IDataGenerator { /// /// counter for sequence number /// IIntCounter SequenceNumberCounter { set; } /// /// Stream identity for this data generator /// StreamId StreamId { get; } /// /// /// bool ShouldProduce { set; } } /// /// counter for integer /// public interface IIntCounter { /// /// counter value /// int Value { get; } /// /// increment the counter /// void Increment(); } internal class IntCounter : IIntCounter { private int counter = 0; public int Value { get { return this.counter; } } public void Increment() { counter++; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/EventDataGeneratorStreamProvider/NoOpCheckpointer.cs ================================================ using Orleans.Streams; using System; using System.Threading.Tasks; namespace Orleans.Streaming.EventHubs.Testing { public class NoOpCheckpointerFactory : IStreamQueueCheckpointerFactory { public static NoOpCheckpointerFactory Instance = new NoOpCheckpointerFactory(); public Task> Create(string partition) { return Task.FromResult>(NoOpCheckpointer.Instance); } } /// /// NoOpCheckpointer is used in EventDataGeneratorStreamProvider ecosystem to replace the default Checkpointer which requires a back end storage. In EventHubDataGeneratorStreamProvider, /// it is generating EventData on the fly when receiver pull messages from the queue, which means it doesn't support recoverable stream, hence check pointing won't bring much value there. /// So a checkpointer with no ops should be enough. /// public class NoOpCheckpointer : IStreamQueueCheckpointer { public static NoOpCheckpointer Instance = new NoOpCheckpointer(); public bool CheckpointExists => true; public Task Load() { return Task.FromResult(EventHubConstants.StartOfStream); } public void Update(string offset, DateTime utcNow) { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/CachePressureMonitors/AggregatedCachePressureMonitor.cs ================================================ using Orleans.Providers.Streams.Common; using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; namespace Orleans.Streaming.EventHubs { /// /// Aggregated cache pressure monitor /// public partial class AggregatedCachePressureMonitor : List, ICachePressureMonitor { private bool isUnderPressure; private readonly ILogger logger; /// /// Cache monitor which is used to report cache related metrics /// public ICacheMonitor CacheMonitor { set; private get; } /// /// Constructor /// /// /// public AggregatedCachePressureMonitor(ILogger logger, ICacheMonitor monitor = null) { this.isUnderPressure = false; this.logger = logger; this.CacheMonitor = monitor; } /// /// Record cache pressure to every monitor in this aggregated cache monitor group /// /// public void RecordCachePressureContribution(double cachePressureContribution) { this.ForEach(monitor => { monitor.RecordCachePressureContribution(cachePressureContribution); }); } /// /// Add one monitor to this aggregated cache monitor group /// /// public void AddCachePressureMonitor(ICachePressureMonitor monitor) { this.Add(monitor); } /// /// If any monitor in this aggregated cache monitor group is under pressure, then return true /// /// /// public bool IsUnderPressure(DateTime utcNow) { bool underPressure = this.Exists(monitor => monitor.IsUnderPressure(utcNow)); if (this.isUnderPressure != underPressure) { this.isUnderPressure = underPressure; this.CacheMonitor?.TrackCachePressureMonitorStatusChange(this.GetType().Name, this.isUnderPressure, null, null, null); if (this.isUnderPressure) { LogInfoIngestingMessagesTooFast(); } else { LogInfoMessageIngestionIsHealthy(); } } return underPressure; } [LoggerMessage( Level = LogLevel.Information, Message = "Ingesting messages too fast. Throttling message reading." )] private partial void LogInfoIngestingMessagesTooFast(); [LoggerMessage( Level = LogLevel.Information, Message = "Message ingestion is healthy." )] private partial void LogInfoMessageIngestionIsHealthy(); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/CachePressureMonitors/AveragingCachePressureMonitor.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Providers.Streams.Common; using Orleans.Configuration; namespace Orleans.Streaming.EventHubs { /// /// Cache pressure monitor whose back pressure algorithm is based on averaging pressure value /// over all pressure contribution /// public partial class AveragingCachePressureMonitor : ICachePressureMonitor { /// /// Cache monitor which is used to report cache related metrics /// public ICacheMonitor CacheMonitor { set; private get; } private static readonly TimeSpan checkPeriod = TimeSpan.FromSeconds(2); private readonly ILogger logger; private double accumulatedCachePressure; private double cachePressureContributionCount; private DateTime nextCheckedTime; private bool isUnderPressure; private readonly double flowControlThreshold; /// /// Constructor /// /// /// public AveragingCachePressureMonitor(ILogger logger, ICacheMonitor monitor=null) :this(EventHubStreamCachePressureOptions.DEFAULT_AVERAGING_CACHE_PRESSURE_MONITORING_THRESHOLD, logger, monitor) { } /// /// Constructor /// /// /// /// public AveragingCachePressureMonitor(double flowControlThreshold, ILogger logger, ICacheMonitor monitor=null) { this.flowControlThreshold = flowControlThreshold; this.logger = logger; nextCheckedTime = DateTime.MinValue; isUnderPressure = false; this.CacheMonitor = monitor; } /// public void RecordCachePressureContribution(double cachePressureContribution) { // Weight unhealthy contributions thrice as much as healthy ones. // This is a crude compensation for the fact that healthy consumers wil consume more often than unhealthy ones. double weight = cachePressureContribution < flowControlThreshold ? 1.0 : 3.0; accumulatedCachePressure += cachePressureContribution * weight; cachePressureContributionCount += weight; } /// public bool IsUnderPressure(DateTime utcNow) { if (nextCheckedTime < utcNow) { CalculatePressure(); nextCheckedTime = utcNow + checkPeriod; } return isUnderPressure; } private void CalculatePressure() { // if we don't have any contributions, don't change status if (cachePressureContributionCount < 0.5) { // after 5 checks with no contributions, check anyway cachePressureContributionCount += 0.1; return; } double pressure = accumulatedCachePressure / cachePressureContributionCount; bool wasUnderPressure = isUnderPressure; isUnderPressure = pressure > flowControlThreshold; // If we changed state, log if (isUnderPressure != wasUnderPressure) { this.CacheMonitor?.TrackCachePressureMonitorStatusChange(this.GetType().Name, isUnderPressure, cachePressureContributionCount, pressure, this.flowControlThreshold); if (isUnderPressure) { LogDebugIngestingMessagesTooFast(accumulatedCachePressure, cachePressureContributionCount, pressure, flowControlThreshold); } else { LogDebugMessageIngestionIsHealthy(accumulatedCachePressure, cachePressureContributionCount, pressure, flowControlThreshold); } } cachePressureContributionCount = 0.0; accumulatedCachePressure = 0.0; } [LoggerMessage( Level = LogLevel.Debug, Message = "Ingesting messages too fast. Throttling message reading. AccumulatedCachePressure: {AccumulatedCachePressure}, Contributions: {Contributions}, AverageCachePressure: {AverageCachePressure}, Threshold: {FlowControlThreshold}" )] private partial void LogDebugIngestingMessagesTooFast(double accumulatedCachePressure, double contributions, double averageCachePressure, double flowControlThreshold); [LoggerMessage( Level = LogLevel.Debug, Message = "Message ingestion is healthy. AccumulatedCachePressure: {AccumulatedCachePressure}, Contributions: {Contributions}, AverageCachePressure: {AverageCachePressure}, Threshold: {FlowControlThreshold}" )] private partial void LogDebugMessageIngestionIsHealthy(double accumulatedCachePressure, double contributions, double averageCachePressure, double flowControlThreshold); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/CachePressureMonitors/ICachePressureMonitor.cs ================================================ using Orleans.Providers.Streams.Common; using System; namespace Orleans.Streaming.EventHubs { /// /// Cache pressure monitor records pressure contribution to the cache, and determine if the cache is under pressure based on its /// back pressure algorithm /// public interface ICachePressureMonitor { /// /// Record cache pressure contribution to the monitor /// /// void RecordCachePressureContribution(double cachePressureContribution); /// /// Determine if the monitor is under pressure /// /// /// bool IsUnderPressure(DateTime utcNow); /// /// Cache monitor which is used to report cache related metrics /// ICacheMonitor CacheMonitor { set; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/CachePressureMonitors/SlowConsumingPressureMonitor.cs ================================================ using Orleans.Providers.Streams.Common; using System; using Microsoft.Extensions.Logging; namespace Orleans.Streaming.EventHubs { /// /// Pressure monitor which is in favor of the slow consumer in the cache /// public partial class SlowConsumingPressureMonitor : ICachePressureMonitor { /// /// DefaultPressureWindowSize /// public static TimeSpan DefaultPressureWindowSize = TimeSpan.FromMinutes(1); /// /// Cache monitor which is used to report cache related metrics /// public ICacheMonitor CacheMonitor { set; private get; } /// /// Default flow control threshold /// public const double DefaultFlowControlThreshold = 0.5; /// /// PressureWindowSize /// public TimeSpan PressureWindowSize { get; set; } /// /// FlowControlThreshold /// public double FlowControlThreshold { get; set; } private readonly ILogger logger; private double biggestPressureInCurrentWindow; private DateTime nextCheckedTime; private bool wasUnderPressure; /// /// Constructor /// /// /// public SlowConsumingPressureMonitor(ILogger logger, ICacheMonitor monitor = null) : this(DefaultFlowControlThreshold, DefaultPressureWindowSize, logger, monitor) { } /// /// Constructor /// /// /// /// public SlowConsumingPressureMonitor(TimeSpan pressureWindowSize, ILogger logger, ICacheMonitor monitor = null) : this(DefaultFlowControlThreshold, pressureWindowSize, logger, monitor) { } /// /// Constructor /// /// /// /// public SlowConsumingPressureMonitor(double flowControlThreshold, ILogger logger, ICacheMonitor monitor = null) : this(flowControlThreshold, DefaultPressureWindowSize, logger, monitor) { } /// /// Constructor /// /// /// /// /// public SlowConsumingPressureMonitor(double flowControlThreshold, TimeSpan pressureWindowSzie, ILogger logger, ICacheMonitor monitor = null) { this.FlowControlThreshold = flowControlThreshold; this.logger = logger; this.nextCheckedTime = DateTime.MinValue; this.biggestPressureInCurrentWindow = 0; this.wasUnderPressure = false; this.CacheMonitor = monitor; this.PressureWindowSize = pressureWindowSzie; } /// public void RecordCachePressureContribution(double cachePressureContribution) { if (cachePressureContribution > this.biggestPressureInCurrentWindow) biggestPressureInCurrentWindow = cachePressureContribution; } /// public bool IsUnderPressure(DateTime utcNow) { //if any pressure contribution in current period is bigger than flowControlThreshold //we see the cache is under pressure bool underPressure = this.biggestPressureInCurrentWindow > this.FlowControlThreshold; if (underPressure && !this.wasUnderPressure) { //if under pressure, extend the nextCheckedTime, make sure wasUnderPressure is true for a whole window this.wasUnderPressure = underPressure; this.nextCheckedTime = utcNow + this.PressureWindowSize; this.CacheMonitor?.TrackCachePressureMonitorStatusChange(this.GetType().Name, underPressure, null, biggestPressureInCurrentWindow, this.FlowControlThreshold); LogDebugIngestingMessagesTooFast(biggestPressureInCurrentWindow, FlowControlThreshold); this.biggestPressureInCurrentWindow = 0; } if (this.nextCheckedTime < utcNow) { //at the end of each check period, reset biggestPressureInCurrentPeriod this.nextCheckedTime = utcNow + this.PressureWindowSize; this.biggestPressureInCurrentWindow = 0; //if at the end of the window, pressure clears out, log if (this.wasUnderPressure && !underPressure) { this.CacheMonitor?.TrackCachePressureMonitorStatusChange(this.GetType().Name, underPressure, null, biggestPressureInCurrentWindow, this.FlowControlThreshold); LogDebugMessageIngestionIsHealthy(biggestPressureInCurrentWindow, FlowControlThreshold); } this.wasUnderPressure = underPressure; } return this.wasUnderPressure; } [LoggerMessage( Level = LogLevel.Debug, Message = "Ingesting messages too fast. Throttling message reading. BiggestPressureInCurrentPeriod: {BiggestPressureInCurrentWindow}, Threshold: {FlowControlThreshold}" )] private partial void LogDebugIngestingMessagesTooFast(double biggestPressureInCurrentWindow, double flowControlThreshold); [LoggerMessage( Level = LogLevel.Debug, Message = "Message ingestion is healthy. BiggestPressureInCurrentPeriod: {BiggestPressureInCurrentWindow}, Threshold: {FlowControlThreshold}" )] private partial void LogDebugMessageIngestionIsHealthy(double biggestPressureInCurrentWindow, double flowControlThreshold); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventDataExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Azure.Messaging.EventHubs; namespace Orleans.Streaming.EventHubs { /// /// Extends EventData to support streaming /// public static class EventDataExtensions { private const string EventDataPropertyStreamNamespaceKey = "StreamNamespace"; /// /// Adds stream namespace to the EventData /// /// /// public static void SetStreamNamespaceProperty(this EventData eventData, string streamNamespace) { eventData.Properties[EventDataPropertyStreamNamespaceKey] = streamNamespace; } /// /// Gets stream namespace from the EventData /// /// /// public static string GetStreamNamespaceProperty(this EventData eventData) { object namespaceObj; if (eventData.Properties.TryGetValue(EventDataPropertyStreamNamespaceKey, out namespaceObj)) { return (string)namespaceObj; } return null; } /// /// Serializes event data properties /// public static byte[] SerializeProperties(this EventData eventData, Serialization.Serializer serializer) { var result = serializer.SerializeToArray(eventData.Properties.Where(kvp => !string.Equals(kvp.Key, EventDataPropertyStreamNamespaceKey, StringComparison.Ordinal)).ToList()); return result; } /// /// Deserializes event data properties /// public static IDictionary DeserializeProperties(this ArraySegment bytes, Serialization.Serializer serializer) { return serializer.Deserialize>>(bytes.AsSpan()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventDataGeneratorStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Streaming.EventHubs; using Orleans.Streaming.EventHubs.Testing; namespace Orleans.Hosting.Developer { public interface IEventDataGeneratorStreamConfigurator : ISiloRecoverableStreamConfigurator { } public static class EventDataGeneratorConfiguratorExtensions { public static void UseDataAdapter(this IEventDataGeneratorStreamConfigurator configurator, Func factory) { configurator.ConfigureComponent(factory); } public static void ConfigureCachePressuring(this IEventDataGeneratorStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } } public class EventDataGeneratorStreamConfigurator : SiloRecoverableStreamConfigurator, IEventDataGeneratorStreamConfigurator { public EventDataGeneratorStreamConfigurator(string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, EventDataGeneratorAdapterFactory.Create) { this.ConfigureDelegate(services => services.ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new EventHubOptionsValidator(sp.GetOptionsByName(name), name)) .AddTransient(sp => new StreamCheckpointerConfigurationValidator(sp, name))); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubAdapterFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Azure.Messaging.EventHubs; using Azure.Messaging.EventHubs.Producer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Statistics; using Orleans.Streams; namespace Orleans.Streaming.EventHubs { /// /// Queue adapter factory which allows the PersistentStreamProvider to use EventHub as its backend persistent event queue. /// public class EventHubAdapterFactory : IQueueAdapterFactory, IQueueAdapter, IQueueAdapterCache { private readonly ILoggerFactory loggerFactory; private readonly IEnvironmentStatisticsProvider environmentStatisticsProvider; /// /// Data adapter /// protected readonly IEventHubDataAdapter dataAdapter; /// /// Orleans logging /// protected ILogger logger; /// /// Framework service provider /// protected readonly IServiceProvider serviceProvider; /// /// Stream provider settings /// private readonly EventHubOptions ehOptions; private readonly EventHubStreamCachePressureOptions cacheOptions; private readonly EventHubReceiverOptions receiverOptions; private readonly StreamStatisticOptions statisticOptions; private readonly StreamCacheEvictionOptions cacheEvictionOptions; private HashRingBasedPartitionedStreamQueueMapper streamQueueMapper; private string[] partitionIds; private ConcurrentDictionary receivers; private EventHubProducerClient client; /// /// Name of the adapter. Primarily for logging purposes /// public string Name { get; } /// /// Determines whether this is a rewindable stream adapter - supports subscribing from previous point in time. /// /// True if this is a rewindable stream adapter, false otherwise. public bool IsRewindable => true; /// /// Direction of this queue adapter: Read, Write or ReadWrite. /// /// The direction in which this adapter provides data. public StreamProviderDirection Direction { get; protected set; } = StreamProviderDirection.ReadWrite; /// /// Creates a message cache for an eventhub partition. /// protected Func, ILoggerFactory, IEventHubQueueCache> CacheFactory { get; set; } /// /// Creates a partition checkpointer. /// private IStreamQueueCheckpointerFactory checkpointerFactory; /// /// Creates a failure handler for a partition. /// protected Func> StreamFailureHandlerFactory { get; set; } /// /// Create a queue mapper to map EventHub partitions to queues /// protected Func QueueMapperFactory { get; set; } /// /// Create a receiver monitor to report performance metrics. /// Factory function should return an IEventHubReceiverMonitor. /// protected Func ReceiverMonitorFactory { get; set; } //for testing purpose, used in EventHubGeneratorStreamProvider /// /// Factory to create a IEventHubReceiver /// protected Func EventHubReceiverFactory; internal ConcurrentDictionary EventHubReceivers => receivers; internal HashRingBasedPartitionedStreamQueueMapper EventHubQueueMapper => streamQueueMapper; public EventHubAdapterFactory( string name, EventHubOptions ehOptions, EventHubReceiverOptions receiverOptions, EventHubStreamCachePressureOptions cacheOptions, StreamCacheEvictionOptions cacheEvictionOptions, StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdapter, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IEnvironmentStatisticsProvider environmentStatisticsProvider) { this.Name = name; this.cacheEvictionOptions = cacheEvictionOptions ?? throw new ArgumentNullException(nameof(cacheEvictionOptions)); this.statisticOptions = statisticOptions ?? throw new ArgumentNullException(nameof(statisticOptions)); this.ehOptions = ehOptions ?? throw new ArgumentNullException(nameof(ehOptions)); this.cacheOptions = cacheOptions?? throw new ArgumentNullException(nameof(cacheOptions)); this.dataAdapter = dataAdapter ?? throw new ArgumentNullException(nameof(dataAdapter)); this.receiverOptions = receiverOptions?? throw new ArgumentNullException(nameof(receiverOptions)); this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.environmentStatisticsProvider = environmentStatisticsProvider; } public virtual void Init() { this.receivers = new ConcurrentDictionary(); InitEventHubClient(); if (this.CacheFactory == null) { this.CacheFactory = CreateCacheFactory(this.cacheOptions).CreateCache; } if (this.StreamFailureHandlerFactory == null) { //TODO: Add a queue specific default failure handler with reasonable error reporting. this.StreamFailureHandlerFactory = partition => Task.FromResult(new NoOpStreamDeliveryFailureHandler()); } if (this.QueueMapperFactory == null) { this.QueueMapperFactory = partitions => new(partitions, this.Name); } if (this.ReceiverMonitorFactory == null) { this.ReceiverMonitorFactory = (dimensions, logger) => new DefaultEventHubReceiverMonitor(dimensions); } this.logger = this.loggerFactory.CreateLogger($"{this.GetType().FullName}.{this.ehOptions.EventHubName}"); } //should only need checkpointer on silo side, so move its init logic when it is used private void InitCheckpointerFactory() { this.checkpointerFactory = this.serviceProvider.GetRequiredKeyedService(this.Name); } /// /// Create queue adapter. /// /// public async Task CreateAdapter() { if (this.streamQueueMapper == null) { this.partitionIds = await GetPartitionIdsAsync(); this.streamQueueMapper = this.QueueMapperFactory(this.partitionIds); } return this; } /// /// Create queue message cache adapter /// /// public IQueueAdapterCache GetQueueAdapterCache() { return this; } /// /// Create queue mapper /// /// public IStreamQueueMapper GetStreamQueueMapper() { //TODO: CreateAdapter must be called first. Figure out how to safely enforce this return this.streamQueueMapper; } /// /// Acquire delivery failure handler for a queue /// /// /// public Task GetDeliveryFailureHandler(QueueId queueId) { return this.StreamFailureHandlerFactory(this.streamQueueMapper.QueueToPartition(queueId)); } /// /// Writes a set of events to the queue as a single batch associated with the provided streamId. /// /// /// /// /// /// /// public virtual Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { EventData eventData = this.dataAdapter.ToQueueMessage(streamId, events, token, requestContext); string partitionKey = this.dataAdapter.GetPartitionKey(streamId); return this.client.SendAsync(new[] { eventData }, new SendEventOptions { PartitionKey = partitionKey }); } /// /// Creates a queue receiver for the specified queueId /// /// /// public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { return GetOrCreateReceiver(queueId); } /// /// Create a cache for a given queue id /// /// public IQueueCache CreateQueueCache(QueueId queueId) { return GetOrCreateReceiver(queueId); } private EventHubAdapterReceiver GetOrCreateReceiver(QueueId queueId) { return this.receivers.GetOrAdd(queueId, (q, instance) => instance.MakeReceiver(q), this); } protected virtual void InitEventHubClient() { var connectionOptions = ehOptions.ConnectionOptions; var connection = ehOptions.CreateConnection(connectionOptions); this.client = new EventHubProducerClient(connection, new EventHubProducerClientOptions { ConnectionOptions = connectionOptions }); } /// /// Create a IEventHubQueueCacheFactory. It will create a EventHubQueueCacheFactory by default. /// User can override this function to return their own implementation of IEventHubQueueCacheFactory, /// and other customization of IEventHubQueueCacheFactory if they may. /// /// protected virtual IEventHubQueueCacheFactory CreateCacheFactory(EventHubStreamCachePressureOptions eventHubCacheOptions) { var eventHubPath = this.ehOptions.EventHubName; var sharedDimensions = new EventHubMonitorAggregationDimensions(eventHubPath); return new EventHubQueueCacheFactory(eventHubCacheOptions, cacheEvictionOptions, statisticOptions, this.dataAdapter, sharedDimensions); } private EventHubAdapterReceiver MakeReceiver(QueueId queueId) { var config = new EventHubPartitionSettings { Hub = ehOptions, Partition = this.streamQueueMapper.QueueToPartition(queueId), ReceiverOptions = this.receiverOptions }; var receiverMonitorDimensions = new EventHubReceiverMonitorDimensions { EventHubPartition = config.Partition, EventHubPath = config.Hub.EventHubName, }; if (this.checkpointerFactory == null) InitCheckpointerFactory(); return new EventHubAdapterReceiver( config, this.CacheFactory, this.checkpointerFactory.Create, this.loggerFactory, this.ReceiverMonitorFactory(receiverMonitorDimensions, this.loggerFactory), this.serviceProvider.GetRequiredService>().Value, this.environmentStatisticsProvider, this.EventHubReceiverFactory); } /// /// Get partition Ids from eventhub /// /// protected virtual async Task GetPartitionIdsAsync() { return await client.GetPartitionIdsAsync(); } public static EventHubAdapterFactory Create(IServiceProvider services, string name) { var ehOptions = services.GetOptionsByName(name); var receiverOptions = services.GetOptionsByName(name); var cacheOptions = services.GetOptionsByName(name); var statisticOptions = services.GetOptionsByName(name); var evictionOptions = services.GetOptionsByName(name); IEventHubDataAdapter dataAdapter = services.GetKeyedService(name) ?? services.GetService() ?? ActivatorUtilities.CreateInstance(services); var factory = ActivatorUtilities.CreateInstance(services, name, ehOptions, receiverOptions, cacheOptions, evictionOptions, statisticOptions, dataAdapter); factory.Init(); return factory; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubAdapterReceiver.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; using Orleans.Streaming.EventHubs.Testing; using Azure.Messaging.EventHubs; using Orleans.Statistics; namespace Orleans.Streaming.EventHubs { /// /// Event Hub Partition settings /// public class EventHubPartitionSettings { /// /// Eventhub settings /// public EventHubOptions Hub { get; set; } public EventHubReceiverOptions ReceiverOptions { get; set; } /// /// Partition name /// public string Partition { get; set; } } internal partial class EventHubAdapterReceiver : IQueueAdapterReceiver, IQueueCache { public const int MaxMessagesPerRead = 1000; private static readonly TimeSpan ReceiveTimeout = TimeSpan.FromSeconds(5); private readonly EventHubPartitionSettings settings; private readonly Func, ILoggerFactory, IEventHubQueueCache> cacheFactory; private readonly Func>> checkpointerFactory; private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; private readonly IQueueAdapterReceiverMonitor monitor; private readonly LoadSheddingOptions loadSheddingOptions; private readonly IEnvironmentStatisticsProvider environmentStatisticsProvider; private IEventHubQueueCache cache; private IEventHubReceiver receiver; private readonly Func eventHubReceiverFactory; private IStreamQueueCheckpointer checkpointer; private AggregatedQueueFlowController flowController; // Receiver life cycle private int receiverState = ReceiverShutdown; private const int ReceiverShutdown = 0; private const int ReceiverRunning = 1; public int GetMaxAddCount() { return this.flowController.GetMaxAddCount(); } public EventHubAdapterReceiver(EventHubPartitionSettings settings, Func, ILoggerFactory, IEventHubQueueCache> cacheFactory, Func>> checkpointerFactory, ILoggerFactory loggerFactory, IQueueAdapterReceiverMonitor monitor, LoadSheddingOptions loadSheddingOptions, IEnvironmentStatisticsProvider environmentStatisticsProvider, Func eventHubReceiverFactory = null) { this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); this.cacheFactory = cacheFactory ?? throw new ArgumentNullException(nameof(cacheFactory)); this.checkpointerFactory = checkpointerFactory ?? throw new ArgumentNullException(nameof(checkpointerFactory)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.logger = this.loggerFactory.CreateLogger(); this.monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.loadSheddingOptions = loadSheddingOptions ?? throw new ArgumentNullException(nameof(loadSheddingOptions)); this.environmentStatisticsProvider = environmentStatisticsProvider; this.eventHubReceiverFactory = eventHubReceiverFactory == null ? EventHubAdapterReceiver.CreateReceiver : eventHubReceiverFactory; } public Task Initialize(TimeSpan timeout) { LogInfoInitializingEventHubPartition(this.settings.Hub.EventHubName, this.settings.Partition); // if receiver was already running, do nothing return ReceiverRunning == Interlocked.Exchange(ref this.receiverState, ReceiverRunning) ? Task.CompletedTask : Initialize(); } /// /// Initialization of EventHub receiver is performed at adapter receiver initialization, but if it fails, /// it will be retried when messages are requested /// /// private async Task Initialize() { var watch = Stopwatch.StartNew(); try { this.checkpointer = await this.checkpointerFactory(this.settings.Partition); if(this.cache != null) { this.cache.Dispose(); this.cache = null; } this.cache = this.cacheFactory(this.settings.Partition, this.checkpointer, this.loggerFactory); this.flowController = new AggregatedQueueFlowController(MaxMessagesPerRead) { this.cache, LoadShedQueueFlowController.CreateAsPercentOfLoadSheddingLimit(this.loadSheddingOptions, environmentStatisticsProvider) }; string offset = await this.checkpointer.Load(); this.receiver = this.eventHubReceiverFactory(this.settings, offset, this.logger); watch.Stop(); this.monitor?.TrackInitialization(true, watch.Elapsed, null); } catch (Exception ex) { watch.Stop(); this.monitor?.TrackInitialization(false, watch.Elapsed, ex); throw; } } public async Task> GetQueueMessagesAsync(int maxCount) { if (this.receiverState == ReceiverShutdown || maxCount <= 0) { return new List(); } // if receiver initialization failed, retry if (this.receiver == null) { LogWarningRetryingInitializationOfEventHubPartition(this.settings.Hub.EventHubName, this.settings.Partition); await Initialize(); if (this.receiver == null) { // should not get here, should throw instead, but just incase. return new List(); } } var watch = Stopwatch.StartNew(); List messages; try { messages = (await this.receiver.ReceiveAsync(maxCount, ReceiveTimeout))?.ToList(); watch.Stop(); this.monitor?.TrackRead(true, watch.Elapsed, null); } catch (Exception ex) { watch.Stop(); this.monitor?.TrackRead(false, watch.Elapsed, ex); LogWarningFailedToReadFromEventHubPartition(this.settings.Hub.EventHubName, this.settings.Partition, ex); throw; } var batches = new List(); if (messages == null || messages.Count == 0) { this.monitor?.TrackMessagesReceived(0, null, null); return batches; } // monitor message age var dequeueTimeUtc = DateTime.UtcNow; DateTime oldestMessageEnqueueTime = messages[0].EnqueuedTime.UtcDateTime; DateTime newestMessageEnqueueTime = messages[messages.Count - 1].EnqueuedTime.UtcDateTime; this.monitor?.TrackMessagesReceived(messages.Count, oldestMessageEnqueueTime, newestMessageEnqueueTime); List messageStreamPositions = this.cache.Add(messages, dequeueTimeUtc); foreach (var streamPosition in messageStreamPositions) { batches.Add(new StreamActivityNotificationBatch(streamPosition)); } if (!this.checkpointer.CheckpointExists) { this.checkpointer.Update( messages[0].OffsetString, DateTime.UtcNow); } return batches; } public void AddToCache(IList messages) { // do nothing, we add data directly into cache. No need for agent involvement } public bool TryPurgeFromCache(out IList purgedItems) { purgedItems = null; //if not under pressure, signal the cache to do a time based purge //if under pressure, which means consuming speed is less than producing speed, then shouldn't purge, and don't read more message into the cache if (!this.IsUnderPressure()) this.cache.SignalPurge(); return false; } public IQueueCacheCursor GetCacheCursor(StreamId streamId, StreamSequenceToken token) { return new Cursor(this.cache, streamId, token); } public bool IsUnderPressure() { return this.GetMaxAddCount() <= 0; } public Task MessagesDeliveredAsync(IList messages) { return Task.CompletedTask; } public async Task Shutdown(TimeSpan timeout) { var watch = Stopwatch.StartNew(); try { // if receiver was already shutdown, do nothing if (ReceiverShutdown == Interlocked.Exchange(ref this.receiverState, ReceiverShutdown)) { return; } LogInfoStoppingReadingFromEventHubPartition(this.settings.Hub.EventHubName, this.settings.Partition); // clear cache and receiver IEventHubQueueCache localCache = Interlocked.Exchange(ref this.cache, null); var localReceiver = Interlocked.Exchange(ref this.receiver, null); // start closing receiver Task closeTask = Task.CompletedTask; if (localReceiver != null) { closeTask = localReceiver.CloseAsync(); } // dispose of cache localCache?.Dispose(); // finish return receiver closing task await closeTask; watch.Stop(); this.monitor?.TrackShutdown(true, watch.Elapsed, null); } catch (Exception ex) { watch.Stop(); this.monitor?.TrackShutdown(false, watch.Elapsed, ex); throw; } } private static IEventHubReceiver CreateReceiver(EventHubPartitionSettings partitionSettings, string offset, ILogger logger) { return new EventHubReceiverProxy(partitionSettings, offset, logger); } /// /// For test purpose. ConfigureDataGeneratorForStream will configure a data generator for the stream /// /// internal void ConfigureDataGeneratorForStream(StreamId streamId) { (this.receiver as EventHubPartitionGeneratorReceiver)?.ConfigureDataGeneratorForStream(streamId); } internal void StopProducingOnStream(StreamId streamId) { (this.receiver as EventHubPartitionGeneratorReceiver)?.StopProducingOnStream(streamId); } [GenerateSerializer] internal class StreamActivityNotificationBatch : IBatchContainer { [Id(0)] public StreamPosition Position { get; } public StreamId StreamId => this.Position.StreamId; public StreamSequenceToken SequenceToken => this.Position.SequenceToken; public StreamActivityNotificationBatch(StreamPosition position) { this.Position = position; } public IEnumerable> GetEvents() { throw new NotSupportedException(); } public bool ImportRequestContext() { throw new NotSupportedException(); } } private class Cursor : IQueueCacheCursor { private readonly IEventHubQueueCache cache; private readonly object cursor; private IBatchContainer current; public Cursor(IEventHubQueueCache cache, StreamId streamId, StreamSequenceToken token) { this.cache = cache; this.cursor = cache.GetCursor(streamId, token); } public void Dispose() { } public IBatchContainer GetCurrent(out Exception exception) { exception = null; return this.current; } public bool MoveNext() { IBatchContainer next; if (!this.cache.TryGetNextMessage(this.cursor, out next)) { return false; } this.current = next; return true; } public void Refresh(StreamSequenceToken token) { } public void RecordDeliveryFailure() { } } [LoggerMessage( Level = LogLevel.Information, Message = "Initializing EventHub partition {EventHubName}-{Partition}." )] private partial void LogInfoInitializingEventHubPartition(string eventHubName, string partition); [LoggerMessage( Level = LogLevel.Information, Message = "Stopping reading from EventHub partition {EventHubName}-{Partition}" )] private partial void LogInfoStoppingReadingFromEventHubPartition(string eventHubName, string partition); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)OrleansEventHubErrorCode.FailedPartitionRead, Message = "Retrying initialization of EventHub partition {EventHubName}-{Partition}." )] private partial void LogWarningRetryingInitializationOfEventHubPartition(string eventHubName, string partition); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)OrleansEventHubErrorCode.FailedPartitionRead, Message = "Failed to read from EventHub partition {EventHubName}-{Partition}" )] private partial void LogWarningFailedToReadFromEventHubPartition(string eventHubName, string partition, Exception exception); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubBatchContainer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Azure.Messaging.EventHubs; using Newtonsoft.Json; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; namespace Orleans.Streaming.EventHubs { /// /// Batch container that is delivers payload and stream position information for a set of events in an EventHub EventData. /// [Serializable] [GenerateSerializer] public class EventHubBatchContainer : IBatchContainer { [JsonProperty] [Id(0)] private readonly EventHubMessage eventHubMessage; [JsonIgnore] [field: NonSerialized] internal Serializer Serializer { get; set; } [JsonProperty] [Id(1)] private readonly EventHubSequenceToken token; /// /// Stream identifier for the stream this batch is part of. /// public StreamId StreamId => eventHubMessage.StreamId; /// /// Stream Sequence Token for the start of this batch. /// public StreamSequenceToken SequenceToken => token; // Payload is local cache of deserialized payloadBytes. Should never be serialized as part of batch container. During batch container serialization raw payloadBytes will always be used. [NonSerialized] private Body payload; private Body GetPayload() => payload ?? (payload = this.Serializer.Deserialize(eventHubMessage.Payload)); [Serializable] [GenerateSerializer] internal class Body { [Id(0)] public List Events { get; set; } [Id(1)] public Dictionary RequestContext { get; set; } } /// /// Batch container that delivers events from cached EventHub data associated with an orleans stream /// public EventHubBatchContainer(EventHubMessage eventHubMessage, Serializer serializer) { this.eventHubMessage = eventHubMessage; this.Serializer = serializer; token = new EventHubSequenceTokenV2(eventHubMessage.Offset, eventHubMessage.SequenceNumber, 0); } [GeneratedActivatorConstructor] internal EventHubBatchContainer(Serializer serializer) { this.Serializer = serializer; } /// /// Gets events of a specific type from the batch. /// /// /// public IEnumerable> GetEvents() { return GetPayload().Events.Cast().Select((e, i) => Tuple.Create(e, new EventHubSequenceTokenV2(token.EventHubOffset, token.SequenceNumber, i))); } /// /// Gives an opportunity to IBatchContainer to set any data in the RequestContext before this IBatchContainer is sent to consumers. /// It can be the data that was set at the time event was generated and enqueued into the persistent provider or any other data. /// /// True if the RequestContext was indeed modified, false otherwise. public bool ImportRequestContext() { if (GetPayload().RequestContext != null) { RequestContextExtensions.Import(GetPayload().RequestContext); return true; } return false; } /// /// Put events list and its context into a EventData object /// public static EventData ToEventData(Serializer bodySerializer, StreamId streamId, IEnumerable events, Dictionary requestContext) { var eventData = new EventData(); UpdateEventData(eventData, bodySerializer, streamId, events.Cast().ToList(), requestContext); return eventData; } /// /// Updates the event data with the events list and its context. /// /// The instance to update with a new body and context. /// The serializer to use for creating the event body payload. /// The stream identifier to associate with the event context. /// The events list to use for the payload. /// The request context to associate with the event. public static void UpdateEventData(EventData eventData, Serializer bodySerializer, StreamId streamId, IEnumerable events, Dictionary requestContext) { eventData.EventBody = CreateEventDataBody(bodySerializer, events, requestContext); eventData.SetStreamNamespaceProperty(streamId.GetNamespace()); } private static BinaryData CreateEventDataBody(Serializer bodySerializer, IEnumerable events, Dictionary requestContext) { var payload = new Body { Events = events.Cast().ToList(), RequestContext = requestContext }; var bytes = bodySerializer.SerializeToArray(payload); return new BinaryData(bytes); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubCheckpointer.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Streams; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration.Overrides; namespace Orleans.Streaming.EventHubs { public class EventHubCheckpointerFactory : IStreamQueueCheckpointerFactory { private readonly ILoggerFactory loggerFactory; private readonly string providerName; private readonly AzureTableStreamCheckpointerOptions options; private readonly ClusterOptions clusterOptions; public EventHubCheckpointerFactory(string providerName, AzureTableStreamCheckpointerOptions options, IOptions clusterOptions, ILoggerFactory loggerFactory) { this.options = options; this.clusterOptions = clusterOptions.Value; this.loggerFactory = loggerFactory; this.providerName = providerName; } public Task> Create(string partition) { return EventHubCheckpointer.Create(options, providerName, partition, this.clusterOptions.ServiceId.ToString(), loggerFactory); } public static IStreamQueueCheckpointerFactory CreateFactory(IServiceProvider services, string providerName) { var options = services.GetOptionsByName(providerName); IOptions clusterOptions = services.GetProviderClusterOptions(providerName); return ActivatorUtilities.CreateInstance(services, providerName, options, clusterOptions); } } /// /// This class stores EventHub partition checkpointer information (a partition offset) in azure table storage. /// public partial class EventHubCheckpointer : IStreamQueueCheckpointer { private readonly AzureTableDataManager dataManager; private readonly TimeSpan persistInterval; private readonly ILogger logger; private EventHubPartitionCheckpointEntity entity; private Task inProgressSave; private DateTime? throttleSavesUntilUtc; /// /// Indicates if a checkpoint exists /// public bool CheckpointExists => entity != null && entity.Offset != EventHubConstants.StartOfStream; /// /// Factory function that creates and initializes the checkpointer /// /// /// /// /// /// /// public static async Task> Create(AzureTableStreamCheckpointerOptions options, string streamProviderName, string partition, string serviceId, ILoggerFactory loggerFactory) { var checkpointer = new EventHubCheckpointer(options, streamProviderName, partition, serviceId, loggerFactory); await checkpointer.Initialize(); return checkpointer; } private EventHubCheckpointer(AzureTableStreamCheckpointerOptions options, string streamProviderName, string partition, string serviceId, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (string.IsNullOrWhiteSpace(streamProviderName)) { throw new ArgumentNullException(nameof(streamProviderName)); } if (string.IsNullOrWhiteSpace(partition)) { throw new ArgumentNullException(nameof(partition)); } this.logger = loggerFactory.CreateLogger(); LogCreatingEventHubCheckpointer(partition, streamProviderName, serviceId); persistInterval = options.PersistInterval; dataManager = new AzureTableDataManager( options, loggerFactory.CreateLogger()); entity = EventHubPartitionCheckpointEntity.Create(streamProviderName, serviceId, partition); } private Task Initialize() { return dataManager.InitTableAsync(); } /// /// Loads a checkpoint /// /// public async Task Load() { var results = await dataManager.ReadSingleTableEntryAsync(entity.PartitionKey, entity.RowKey); if (results.Entity != null) { entity = results.Entity; } return entity.Offset; } /// /// Updates the checkpoint. This is a best effort. It does not always update the checkpoint. /// /// /// public void Update(string offset, DateTime utcNow) { // if offset has not changed, do nothing if (string.Compare(entity.Offset, offset, StringComparison.Ordinal) == 0) { return; } // if we've saved before but it's not time for another save or the last save operation has not completed, do nothing if (throttleSavesUntilUtc.HasValue && (throttleSavesUntilUtc.Value > utcNow || !inProgressSave.IsCompleted)) { return; } entity.Offset = offset; throttleSavesUntilUtc = utcNow + persistInterval; inProgressSave = dataManager.UpsertTableEntryAsync(entity); inProgressSave.Ignore(); } [LoggerMessage( Level = LogLevel.Information, Message = "Creating EventHub checkpointer for partition {Partition} of stream provider {StreamProviderName} with serviceId {ServiceId}." )] private partial void LogCreatingEventHubCheckpointer(string partition, string streamProviderName, string serviceId); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubCheckpointerOptions.cs ================================================ using Orleans.Streaming.EventHubs; using System; namespace Orleans.Configuration { public class AzureTableStreamCheckpointerOptions : AzureStorageOperationOptions { /// /// Azure table name. /// public override string TableName { get; set; } = DEFAULT_TABLE_NAME; public const string DEFAULT_TABLE_NAME = "Checkpoint"; /// /// Interval to write checkpoints. Prevents spamming storage. /// public TimeSpan PersistInterval { get; set; } = DEFAULT_CHECKPOINT_PERSIST_INTERVAL; public static readonly TimeSpan DEFAULT_CHECKPOINT_PERSIST_INTERVAL = TimeSpan.FromMinutes(1); } //TOOD: how to wire this validator into DI? public class AzureTableStreamCheckpointerOptionsValidator : AzureStorageOperationOptionsValidator { public AzureTableStreamCheckpointerOptionsValidator(AzureTableStreamCheckpointerOptions options, string name) : base(options, name) { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubConstants.cs ================================================ namespace Orleans.Streaming.EventHubs { internal class EventHubConstants { public const string StartOfStream = "-1"; } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubDataAdapter.cs ================================================ using System; using System.Collections.Generic; using Azure.Messaging.EventHubs; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Streaming.EventHubs { /// /// Default event hub data adapter. Users may subclass to override event data to stream mapping. /// public class EventHubDataAdapter : IEventHubDataAdapter { private readonly Serialization.Serializer serializer; /// /// Cache data adapter that adapts EventHub's EventData to CachedEventHubMessage used in cache /// public EventHubDataAdapter(Serialization.Serializer serializer) { this.serializer = serializer; } /// /// Converts a cached message to a batch container for delivery /// /// /// public virtual IBatchContainer GetBatchContainer(ref CachedMessage cachedMessage) { var evenHubMessage = new EventHubMessage(cachedMessage, this.serializer); return GetBatchContainer(evenHubMessage); } /// /// Convert an EventHubMessage to a batch container /// /// /// protected virtual IBatchContainer GetBatchContainer(EventHubMessage eventHubMessage) { return new EventHubBatchContainer(eventHubMessage, this.serializer); } /// /// Gets the stream sequence token from a cached message. /// /// /// public virtual StreamSequenceToken GetSequenceToken(ref CachedMessage cachedMessage) { return new EventHubSequenceTokenV2("", cachedMessage.SequenceNumber, 0); } public virtual EventData ToQueueMessage(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { if (token != null) throw new ArgumentException("EventHub streams currently does not support non-null StreamSequenceToken.", nameof(token)); return EventHubBatchContainer.ToEventData(this.serializer, streamId, events, requestContext); } public virtual CachedMessage FromQueueMessage(StreamPosition streamPosition, EventData queueMessage, DateTime dequeueTime, Func> getSegment) { return new CachedMessage() { StreamId = streamPosition.StreamId, SequenceNumber = queueMessage.SequenceNumber, EventIndex = streamPosition.SequenceToken.EventIndex, EnqueueTimeUtc = queueMessage.EnqueuedTime.UtcDateTime, DequeueTimeUtc = dequeueTime, Segment = EncodeMessageIntoSegment(queueMessage, getSegment) }; } public virtual StreamPosition GetStreamPosition(string partition, EventData queueMessage) { StreamId streamId = this.GetStreamIdentity(queueMessage); StreamSequenceToken token = new EventHubSequenceTokenV2(queueMessage.OffsetString, queueMessage.SequenceNumber, 0); return new StreamPosition(streamId, token); } /// /// Get offset from cached message. Left to derived class, as only it knows how to get this from the cached message. /// public virtual string GetOffset(CachedMessage lastItemPurged) { // TODO figure out how to get this from the adapter int readOffset = 0; return SegmentBuilder.ReadNextString(lastItemPurged.Segment, ref readOffset); // read offset } /// /// Get the Event Hub partition key to use for a stream. /// /// The stream Guid. /// The partition key to use for the stream. public virtual string GetPartitionKey(StreamId streamId) => streamId.GetKeyAsString(); /// /// Get the for an event message. /// /// The event message. /// The stream identity. public virtual StreamId GetStreamIdentity(EventData queueMessage) { string streamKey = queueMessage.PartitionKey; string streamNamespace = queueMessage.GetStreamNamespaceProperty(); return StreamId.Create(streamNamespace, streamKey); } // Placed object message payload into a segment. protected virtual ArraySegment EncodeMessageIntoSegment(EventData queueMessage, Func> getSegment) { byte[] propertiesBytes = queueMessage.SerializeProperties(this.serializer); var payload = queueMessage.Body.Span; var offset = queueMessage.OffsetString; // get size of namespace, offset, partitionkey, properties, and payload int size = SegmentBuilder.CalculateAppendSize(offset) + SegmentBuilder.CalculateAppendSize(queueMessage.PartitionKey) + SegmentBuilder.CalculateAppendSize(propertiesBytes) + SegmentBuilder.CalculateAppendSize(payload); // get segment ArraySegment segment = getSegment(size); // encode namespace, offset, partitionkey, properties and payload into segment int writeOffset = 0; SegmentBuilder.Append(segment, ref writeOffset, offset); SegmentBuilder.Append(segment, ref writeOffset, queueMessage.PartitionKey); SegmentBuilder.Append(segment, ref writeOffset, propertiesBytes); SegmentBuilder.Append(segment, ref writeOffset, payload); return segment; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubMessage.cs ================================================ using System; using System.Collections.Generic; using Orleans.Providers.Streams.Common; using Orleans.Runtime; namespace Orleans.Streaming.EventHubs { /// /// Replication of EventHub EventData class, reconstructed from cached data CachedEventHubMessage /// [Serializable] [GenerateSerializer] public class EventHubMessage { /// /// Constructor /// /// Stream Identity /// EventHub partition key for message /// Offset into the EventHub partition where this message was from /// Offset into the EventHub partition where this message was from /// Time in UTC when this message was injected by EventHub /// Time in UTC when this message was read from EventHub into the current service /// User properties from EventData object /// Binary data from EventData object public EventHubMessage(StreamId streamId, string partitionKey, string offset, long sequenceNumber, DateTime enqueueTimeUtc, DateTime dequeueTimeUtc, IDictionary properties, byte[] payload) { StreamId = streamId; PartitionKey = partitionKey; Offset = offset; SequenceNumber = sequenceNumber; EnqueueTimeUtc = enqueueTimeUtc; DequeueTimeUtc = dequeueTimeUtc; Properties = properties; Payload = payload; } /// /// Duplicate of EventHub's EventData class. /// public EventHubMessage(CachedMessage cachedMessage, Serialization.Serializer serializer) { int readOffset = 0; StreamId = cachedMessage.StreamId; Offset = SegmentBuilder.ReadNextString(cachedMessage.Segment, ref readOffset); PartitionKey = SegmentBuilder.ReadNextString(cachedMessage.Segment, ref readOffset); SequenceNumber = cachedMessage.SequenceNumber; EnqueueTimeUtc = cachedMessage.EnqueueTimeUtc; DequeueTimeUtc = cachedMessage.DequeueTimeUtc; Properties = SegmentBuilder.ReadNextBytes(cachedMessage.Segment, ref readOffset).DeserializeProperties(serializer); Payload = SegmentBuilder.ReadNextBytes(cachedMessage.Segment, ref readOffset).ToArray(); } /// /// Stream identifier /// [Id(0)] public StreamId StreamId { get; } /// /// EventHub partition key /// [Id(1)] public string PartitionKey { get; } /// /// Offset into EventHub partition /// [Id(2)] public string Offset { get; } /// /// Sequence number in EventHub partition /// [Id(3)] public long SequenceNumber { get; } /// /// Time event was written to EventHub /// [Id(4)] public DateTime EnqueueTimeUtc { get; } /// /// Time event was read from EventHub and added to cache /// [Id(5)] public DateTime DequeueTimeUtc { get; } /// /// User EventData properties /// [Id(6)] public IDictionary Properties { get; } /// /// Binary event data /// [Id(7)] public byte[] Payload { get; } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubPartitionCheckpointEntity.cs ================================================ using System; using Azure; using Azure.Data.Tables; namespace Orleans.Streaming.EventHubs { internal class EventHubPartitionCheckpointEntity : ITableEntity { public string Offset { get; set; } public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } public EventHubPartitionCheckpointEntity() { Offset = EventHubConstants.StartOfStream; } public static EventHubPartitionCheckpointEntity Create(string streamProviderName, string serviceId, string partition) { return new EventHubPartitionCheckpointEntity { PartitionKey = MakePartitionKey(streamProviderName, serviceId), RowKey = MakeRowKey(partition) }; } public static string MakePartitionKey(string streamProviderName, string checkpointNamespace) { string key = $"EventHubCheckpoints_{streamProviderName}_{checkpointNamespace}"; return AzureTableUtils.SanitizeTableProperty(key); } public static string MakeRowKey(string partition) { string key = $"partition_{partition}"; return AzureTableUtils.SanitizeTableProperty(key); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubQueueCache.cs ================================================ using System; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Azure.Messaging.EventHubs; namespace Orleans.Streaming.EventHubs { /// /// EventHub queue cache /// public partial class EventHubQueueCache : IEventHubQueueCache { public string Partition { get; private set; } /// /// Default max number of items that can be added to the cache between purge calls /// private readonly int defaultMaxAddCount; /// /// Underlying message cache implementation /// Protected for test purposes /// protected readonly PooledQueueCache cache; private readonly IObjectPool bufferPool; private readonly IEventHubDataAdapter dataAdapter; private readonly IEvictionStrategy evictionStrategy; private readonly IStreamQueueCheckpointer checkpointer; private readonly ILogger logger; private readonly AggregatedCachePressureMonitor cachePressureMonitor; private readonly ICacheMonitor cacheMonitor; private FixedSizeBuffer currentBuffer; /// /// EventHub queue cache. /// /// Partition this instance is caching. /// Default max number of items that can be added to the cache between purge calls. /// raw data block pool. /// Adapts EventData to cached. /// Eviction strategy manage purge related events /// Logic used to store queue position. /// /// /// /// public EventHubQueueCache( string partition, int defaultMaxAddCount, IObjectPool bufferPool, IEventHubDataAdapter dataAdapter, IEvictionStrategy evictionStrategy, IStreamQueueCheckpointer checkpointer, ILogger logger, ICacheMonitor cacheMonitor, TimeSpan? cacheMonitorWriteInterval, TimeSpan? metadataMinTimeInCache) { this.Partition = partition; this.defaultMaxAddCount = defaultMaxAddCount; this.bufferPool = bufferPool; this.dataAdapter = dataAdapter; this.checkpointer = checkpointer; this.cache = new PooledQueueCache(dataAdapter, logger, cacheMonitor, cacheMonitorWriteInterval, metadataMinTimeInCache); this.cacheMonitor = cacheMonitor; this.evictionStrategy = evictionStrategy; this.evictionStrategy.OnPurged = this.OnPurge; this.evictionStrategy.PurgeObservable = this.cache; this.cachePressureMonitor = new AggregatedCachePressureMonitor(logger, cacheMonitor); this.logger = logger; } /// public void SignalPurge() { this.evictionStrategy.PerformPurge(DateTime.UtcNow); } /// /// Add cache pressure monitor to the cache's back pressure algorithm /// /// public void AddCachePressureMonitor(ICachePressureMonitor monitor) { monitor.CacheMonitor = this.cacheMonitor; this.cachePressureMonitor.AddCachePressureMonitor(monitor); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { this.evictionStrategy.OnPurged = null; } /// /// The limit of the maximum number of items that can be added /// public int GetMaxAddCount() { return cachePressureMonitor.IsUnderPressure(DateTime.UtcNow) ? 0 : defaultMaxAddCount; } /// /// Add a list of EventHub EventData to the cache. /// /// /// /// public List Add(List messages, DateTime dequeueTimeUtc) { List positions = new List(); List cachedMessages = new List(); foreach (EventData message in messages) { StreamPosition position = this.dataAdapter.GetStreamPosition(this.Partition, message); cachedMessages.Add(this.dataAdapter.FromQueueMessage(position, message, dequeueTimeUtc, this.GetSegment)); positions.Add(position); } cache.Add(cachedMessages, dequeueTimeUtc); return positions; } /// /// Get a cursor into the cache to read events from a stream. /// /// /// /// public object GetCursor(StreamId streamId, StreamSequenceToken sequenceToken) { return cache.GetCursor(streamId, sequenceToken); } /// /// Try to get the next message in the cache for the provided cursor. /// /// /// /// public bool TryGetNextMessage(object cursorObj, out IBatchContainer message) { if (!cache.TryGetNextMessage(cursorObj, out message)) return false; double cachePressureContribution; cachePressureMonitor.RecordCachePressureContribution( TryCalculateCachePressureContribution(message.SequenceToken, out cachePressureContribution) ? cachePressureContribution : 0.0); return true; } /// /// Handles cache purge signals /// /// /// private void OnPurge(CachedMessage? lastItemPurged, CachedMessage? newestItem) { if (lastItemPurged.HasValue && newestItem.HasValue) { LogDebugCachePeriod( new(lastItemPurged.Value.EnqueueTimeUtc), new(newestItem.Value.EnqueueTimeUtc), new(lastItemPurged.Value.DequeueTimeUtc), new(newestItem.Value.DequeueTimeUtc)); } if (lastItemPurged.HasValue) { checkpointer.Update(this.dataAdapter.GetOffset(lastItemPurged.Value), DateTime.UtcNow); } } /// /// cachePressureContribution should be a double between 0-1, indicating how much danger the item is of being removed from the cache. /// 0 indicating no danger, /// 1 indicating removal is imminent. /// private bool TryCalculateCachePressureContribution(StreamSequenceToken token, out double cachePressureContribution) { cachePressureContribution = 0; // if cache is empty or has few items, don't calculate pressure if (cache.IsEmpty || !cache.Newest.HasValue || !cache.Oldest.HasValue || cache.Newest.Value.SequenceNumber - cache.Oldest.Value.SequenceNumber < 10 * defaultMaxAddCount) // not enough items in cache. { return false; } IEventHubPartitionLocation location = (IEventHubPartitionLocation)token; double cacheSize = cache.Newest.Value.SequenceNumber - cache.Oldest.Value.SequenceNumber; long distanceFromNewestMessage = cache.Newest.Value.SequenceNumber - location.SequenceNumber; // pressure is the ratio of the distance from the front of the cache to the cachePressureContribution = distanceFromNewestMessage / cacheSize; return true; } private ArraySegment GetSegment(int size) { // get segment from current block ArraySegment segment; if (currentBuffer == null || !currentBuffer.TryGetSegment(size, out segment)) { // no block or block full, get new block and try again currentBuffer = bufferPool.Allocate(); //call EvictionStrategy's OnBlockAllocated method this.evictionStrategy.OnBlockAllocated(currentBuffer); // if this fails with clean block, then requested size is too big if (!currentBuffer.TryGetSegment(size, out segment)) { throw new ArgumentOutOfRangeException(nameof(size), $"Message size is to big. MessageSize: {size}"); } } return segment; } private readonly struct DateTimeLogRecord(DateTime ts) { public override string ToString() => LogFormatter.PrintDate(ts); } [LoggerMessage( Level = LogLevel.Debug, Message = "CachePeriod: EnqueueTimeUtc: {OldestEnqueueTimeUtc} to {NewestEnqueueTimeUtc}, DequeueTimeUtc: {OldestDequeueTimeUtc} to {NewestDequeueTimeUtc}" )] private partial void LogDebugCachePeriod( DateTimeLogRecord oldestEnqueueTimeUtc, DateTimeLogRecord newestEnqueueTimeUtc, DateTimeLogRecord oldestDequeueTimeUtc, DateTimeLogRecord newestDequeueTimeUtc); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubQueueCacheFactory.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Streams; using Orleans.Streaming.EventHubs.StatisticMonitors; namespace Orleans.Streaming.EventHubs { /// /// Factory class to configure and create IEventHubQueueCache /// public class EventHubQueueCacheFactory : IEventHubQueueCacheFactory { private readonly EventHubStreamCachePressureOptions cacheOptions; private readonly StreamCacheEvictionOptions evictionOptions; private readonly StreamStatisticOptions statisticOptions; private readonly IEventHubDataAdapter dataAdater; private readonly TimePurgePredicate timePurge; private readonly EventHubMonitorAggregationDimensions sharedDimensions; private IObjectPool bufferPool; private string bufferPoolId; /// /// Create a cache monitor to report performance metrics. /// Factory function should return an ICacheMonitor. /// public Func CacheMonitorFactory { set; get; } /// /// Create a block pool monitor to report performance metrics. /// Factory function should return an IObjectPoolMonitor. /// public Func BlockPoolMonitorFactory { set; get; } /// /// Constructor for EventHubQueueCacheFactory /// public EventHubQueueCacheFactory( EventHubStreamCachePressureOptions cacheOptions, StreamCacheEvictionOptions evictionOptions, StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdater, EventHubMonitorAggregationDimensions sharedDimensions, Func cacheMonitorFactory = null, Func blockPoolMonitorFactory = null) { this.cacheOptions = cacheOptions; this.evictionOptions = evictionOptions; this.statisticOptions = statisticOptions; this.dataAdater = dataAdater; this.timePurge = new TimePurgePredicate(evictionOptions.DataMinTimeInCache, evictionOptions.DataMaxAgeInCache); this.sharedDimensions = sharedDimensions; this.CacheMonitorFactory = cacheMonitorFactory ?? ((dimensions, logger) => new DefaultEventHubCacheMonitor(dimensions)); this.BlockPoolMonitorFactory = blockPoolMonitorFactory ?? ((dimensions, logger) => new DefaultEventHubBlockPoolMonitor(dimensions)); } /// /// Function which create an EventHubQueueCache, which by default will configure the EventHubQueueCache using configuration in CreateBufferPool function /// and AddCachePressureMonitors function. /// /// public IEventHubQueueCache CreateCache(string partition, IStreamQueueCheckpointer checkpointer, ILoggerFactory loggerFactory) { string blockPoolId; var blockPool = CreateBufferPool(this.statisticOptions, loggerFactory, this.sharedDimensions, out blockPoolId); var cache = CreateCache(partition, dataAdater, this.statisticOptions, this.evictionOptions, checkpointer, loggerFactory, blockPool, blockPoolId, this.timePurge, this.sharedDimensions); AddCachePressureMonitors(cache, this.cacheOptions, loggerFactory.CreateLogger($"{typeof(EventHubQueueCache).FullName}.{this.sharedDimensions.EventHubPath}.{partition}")); return cache; } /// /// Function used to configure BufferPool for EventHubQueueCache. User can override this function to provide more customization on BufferPool creation /// protected virtual IObjectPool CreateBufferPool(StreamStatisticOptions statisticOptions, ILoggerFactory loggerFactory, EventHubMonitorAggregationDimensions sharedDimensions, out string blockPoolId) { if (this.bufferPool == null) { var bufferSize = 1 << 20; this.bufferPoolId = $"BlockPool-{new Guid().ToString()}-BlockSize-{bufferSize}"; var monitorDimensions = new EventHubBlockPoolMonitorDimensions(sharedDimensions, this.bufferPoolId); var objectPoolMonitor = new ObjectPoolMonitorBridge(this.BlockPoolMonitorFactory(monitorDimensions, loggerFactory), bufferSize); this.bufferPool = new ObjectPool(() => new FixedSizeBuffer(bufferSize), objectPoolMonitor, statisticOptions.StatisticMonitorWriteInterval); } blockPoolId = this.bufferPoolId; return this.bufferPool; } /// /// Function used to configure cache pressure monitors for EventHubQueueCache. /// User can override this function to provide more customization on cache pressure monitors /// /// /// /// protected virtual void AddCachePressureMonitors( IEventHubQueueCache cache, EventHubStreamCachePressureOptions providerOptions, ILogger cacheLogger) { if (providerOptions.AveragingCachePressureMonitorFlowControlThreshold.HasValue) { var avgMonitor = new AveragingCachePressureMonitor( providerOptions.AveragingCachePressureMonitorFlowControlThreshold.Value, cacheLogger); cache.AddCachePressureMonitor(avgMonitor); } if (providerOptions.SlowConsumingMonitorPressureWindowSize.HasValue || providerOptions.SlowConsumingMonitorFlowControlThreshold.HasValue) { var slowConsumeMonitor = new SlowConsumingPressureMonitor(cacheLogger); if (providerOptions.SlowConsumingMonitorFlowControlThreshold.HasValue) { slowConsumeMonitor.FlowControlThreshold = providerOptions.SlowConsumingMonitorFlowControlThreshold.Value; } if (providerOptions.SlowConsumingMonitorPressureWindowSize.HasValue) { slowConsumeMonitor.PressureWindowSize = providerOptions.SlowConsumingMonitorPressureWindowSize.Value; } cache.AddCachePressureMonitor(slowConsumeMonitor); } } /// /// Default function to be called to create an EventhubQueueCache in IEventHubQueueCacheFactory.CreateCache method. User can /// override this method to add more customization. /// protected virtual IEventHubQueueCache CreateCache( string partition, IEventHubDataAdapter dataAdatper, StreamStatisticOptions statisticOptions, StreamCacheEvictionOptions streamCacheEvictionOptions, IStreamQueueCheckpointer checkpointer, ILoggerFactory loggerFactory, IObjectPool bufferPool, string blockPoolId, TimePurgePredicate timePurge, EventHubMonitorAggregationDimensions sharedDimensions) { var cacheMonitorDimensions = new EventHubCacheMonitorDimensions(sharedDimensions, partition, blockPoolId); var cacheMonitor = this.CacheMonitorFactory(cacheMonitorDimensions, loggerFactory); var logger = loggerFactory.CreateLogger($"{typeof(EventHubQueueCache).FullName}.{sharedDimensions.EventHubPath}.{partition}"); var evictionStrategy = new ChronologicalEvictionStrategy(logger, timePurge, cacheMonitor, statisticOptions.StatisticMonitorWriteInterval); return new EventHubQueueCache(partition, EventHubAdapterReceiver.MaxMessagesPerRead, bufferPool, dataAdatper, evictionStrategy, checkpointer, logger, cacheMonitor, statisticOptions.StatisticMonitorWriteInterval, streamCacheEvictionOptions.MetadataMinTimeInCache); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubSequenceToken.cs ================================================ using System; using System.Globalization; using Newtonsoft.Json; using Orleans.Providers.Streams.Common; namespace Orleans.Streaming.EventHubs { /// /// Location of a message within an EventHub partition /// public interface IEventHubPartitionLocation { /// /// Offset of the message within an EventHub partition /// string EventHubOffset { get; } /// /// EventHub sequence id of the message /// long SequenceNumber { get; } } /// /// Event Hub messages consist of a batch of application layer events, so EventHub tokens contain three pieces of information. /// EventHubOffset - this is a unique value per partition that is used to start reading from this message in the partition. /// SequenceNumber - EventHub sequence numbers are unique ordered message IDs for messages within a partition. /// The SequenceNumber is required for uniqueness and ordering of EventHub messages within a partition. /// event Index - Since each EventHub message may contain more than one application layer event, this value /// indicates which application layer event this token is for, within an EventHub message. It is required for uniqueness /// and ordering of application layer events within an EventHub message. /// [Serializable] [GenerateSerializer] public class EventHubSequenceToken : EventSequenceToken, IEventHubPartitionLocation { /// /// Offset of the message within an EventHub partition /// [Id(0)] [JsonProperty] public string EventHubOffset { get; } /// /// Initializes a new instance of the class. /// /// EventHub offset within the partition from which this message came. /// EventHub sequenceNumber for this message. /// Index into a batch of events, if multiple events were delivered within a single EventHub message. public EventHubSequenceToken(string eventHubOffset, long sequenceNumber, int eventIndex) : base(sequenceNumber, eventIndex) { EventHubOffset = eventHubOffset; } /// /// Initializes a new instance of the class. /// /// /// This constructor is exposed for serializer use only. /// public EventHubSequenceToken() : base() { } /// Returns a string that represents the current object. /// A string that represents the current object. /// 2 public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "EventHubSequenceToken(EventHubOffset: {0}, SequenceNumber: {1}, EventIndex: {2})", EventHubOffset, SequenceNumber, EventIndex); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubSequenceTokenV2.cs ================================================ using System; namespace Orleans.Streaming.EventHubs { /// /// Event Hub messages consist of a batch of application layer events, so EventHub tokens contain three pieces of information. /// EventHubOffset - this is a unique value per partition that is used to start reading from this message in the partition. /// SequenceNumber - EventHub sequence numbers are unique ordered message IDs for messages within a partition. /// The SequenceNumber is required for uniqueness and ordering of EventHub messages within a partition. /// event Index - Since each EventHub message may contain more than one application layer event, this value /// indicates which application layer event this token is for, within an EventHub message. It is required for uniqueness /// and ordering of application layer events within an EventHub message. /// [Serializable] [GenerateSerializer] public class EventHubSequenceTokenV2 : EventHubSequenceToken { /// /// Initializes a new instance of the class. /// /// EventHub offset within the partition from which this message came. /// EventHub sequenceNumber for this message. /// Index into a batch of events, if multiple events were delivered within a single EventHub message. public EventHubSequenceTokenV2(string eventHubOffset, long sequenceNumber, int eventIndex) : base(eventHubOffset, sequenceNumber, eventIndex) { } /// /// Initializes a new instance of the class. /// /// /// This constructor is exposed for serializer use only. /// public EventHubSequenceTokenV2() : base() { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubStreamBuilder.cs ================================================ using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Streaming.EventHubs; using Orleans.Streams; namespace Orleans.Hosting { public interface IEventHubStreamConfigurator : INamedServiceConfigurator {} public static class EventHubStreamConfiguratorExtensions { public static void ConfigureEventHub(this IEventHubStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } public static void UseDataAdapter(this IEventHubStreamConfigurator configurator, Func factory) { configurator.ConfigureComponent(factory); } } public interface ISiloEventHubStreamConfigurator : IEventHubStreamConfigurator, ISiloRecoverableStreamConfigurator { } public static class SiloEventHubStreamConfiguratorExtensions { public static void ConfigureCheckpointer(this ISiloEventHubStreamConfigurator configurator, Func checkpointerFactoryBuilder, Action> configureOptions) where TOptions : class, new() { configurator.ConfigureComponent(checkpointerFactoryBuilder, configureOptions); } public static void ConfigurePartitionReceiver(this ISiloEventHubStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } public static void ConfigureCachePressuring(this ISiloEventHubStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } public static void UseAzureTableCheckpointer(this ISiloEventHubStreamConfigurator configurator, Action> configureOptions) { configurator.ConfigureCheckpointer(EventHubCheckpointerFactory.CreateFactory, configureOptions); } } public class SiloEventHubStreamConfigurator : SiloRecoverableStreamConfigurator, ISiloEventHubStreamConfigurator { public SiloEventHubStreamConfigurator(string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, EventHubAdapterFactory.Create) { this.ConfigureDelegate(services => services.ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new EventHubOptionsValidator(sp.GetOptionsByName(name), name)) .AddTransient(sp => new StreamCheckpointerConfigurationValidator(sp, name))); } } public interface IClusterClientEventHubStreamConfigurator : IEventHubStreamConfigurator, IClusterClientPersistentStreamConfigurator { } public class ClusterClientEventHubStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientEventHubStreamConfigurator { public ClusterClientEventHubStreamConfigurator(string name, IClientBuilder builder) : base(name, builder, EventHubAdapterFactory.Create) { builder .ConfigureServices(services => services.ConfigureNamedOptionForLogging(name) .AddTransient(sp => new EventHubOptionsValidator(sp.GetOptionsByName(name), name))); } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/EventHubStreamOptions.cs ================================================ using Azure; using Azure.Core; using Azure.Messaging.EventHubs; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Streams; using System; namespace Orleans.Configuration { /// /// EventHub settings for a specific hub /// public class EventHubOptions { /// /// Gets the delegate used to create connections to Azure Event Hub. /// internal CreateConnectionDelegate CreateConnection { get; private set; } /// /// Event Hub consumer group. /// internal string ConsumerGroup { get; private set; } /// /// Event Hub name. /// internal string EventHubName { get; private set; } /// /// Connection options used when creating a connection to an Azure Event Hub. /// public EventHubConnectionOptions ConnectionOptions { get; set; } = new EventHubConnectionOptions { TransportType = EventHubsTransportType.AmqpTcp }; /// /// Creates an Azure Event Hub connection. /// /// The connection options. /// An Azure Event Hub connection. public delegate EventHubConnection CreateConnectionDelegate(EventHubConnectionOptions connectionOptions); /// /// Configures the Azure Event Hub connection using the provided connection string. /// public void ConfigureEventHubConnection(string connectionString, string eventHubName, string consumerGroup) { EventHubName = eventHubName; ConsumerGroup = consumerGroup; if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(connectionString)); } ValidateValues(eventHubName, consumerGroup); CreateConnection = connectionOptions => new EventHubConnection(connectionString, EventHubName, connectionOptions); } /// /// Configures the Azure Event Hub connection using the provided fully-qualified namespace string and credential. /// public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, AzureNamedKeyCredential credential) { EventHubName = eventHubName; ConsumerGroup = consumerGroup; if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(fullyQualifiedNamespace)); } ValidateValues(eventHubName, consumerGroup); if (credential is null) { throw new ArgumentNullException(nameof(credential)); } CreateConnection = connectionOptions => new EventHubConnection(fullyQualifiedNamespace, EventHubName, credential, connectionOptions); } /// /// Configures the Azure Event Hub connection using the provided fully-qualified namespace string and credential. /// public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, AzureSasCredential credential) { EventHubName = eventHubName; ConsumerGroup = consumerGroup; if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(fullyQualifiedNamespace)); } ValidateValues(eventHubName, consumerGroup); if (credential is null) { throw new ArgumentNullException(nameof(credential)); } CreateConnection = connectionOptions => new EventHubConnection(fullyQualifiedNamespace, EventHubName, credential, connectionOptions); } /// /// Configures the Azure Event Hub connection using the provided fully-qualified namespace string and credential. /// public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, TokenCredential credential) { EventHubName = eventHubName; ConsumerGroup = consumerGroup; if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(fullyQualifiedNamespace)); } ValidateValues(eventHubName, consumerGroup); if (credential is null) { throw new ArgumentNullException(nameof(credential)); } CreateConnection = connectionOptions => new EventHubConnection(fullyQualifiedNamespace, EventHubName, credential, connectionOptions); } /// /// Configures the Azure Event Hub connection using the provided connection instance. /// public void ConfigureEventHubConnection(EventHubConnection connection, string consumerGroup) { EventHubName = connection.EventHubName; ConsumerGroup = consumerGroup; ValidateValues(connection.EventHubName, consumerGroup); if (connection is null) throw new ArgumentNullException(nameof(connection)); CreateConnection = _ => connection; } /// /// Configures the Azure Event Hub connection using the provided delegate. /// public void ConfigureEventHubConnection(CreateConnectionDelegate createConnection, string eventHubName, string consumerGroup) { EventHubName = eventHubName; ConsumerGroup = consumerGroup; ValidateValues(eventHubName, consumerGroup); CreateConnection = createConnection ?? throw new ArgumentNullException(nameof(createConnection)); } private static void ValidateValues(string eventHubName, string consumerGroup) { if (string.IsNullOrWhiteSpace(eventHubName)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(eventHubName)); } if (string.IsNullOrWhiteSpace(consumerGroup)) { throw new ArgumentException("A non-null, non-empty value must be provided.", nameof(consumerGroup)); } } } public class EventHubOptionsValidator : IConfigurationValidator { private readonly EventHubOptions options; private readonly string name; public EventHubOptionsValidator(EventHubOptions options, string name) { this.options = options; this.name = name; } public void ValidateConfiguration() { if (options.CreateConnection is null) { throw new OrleansConfigurationException($"Azure Event Hub connection not configured for stream provider options {nameof(EventHubOptions)} with name \"{name}\". Use the {options.GetType().Name}.{nameof(EventHubOptions.ConfigureEventHubConnection)} method to configure the connection."); } if (string.IsNullOrEmpty(options.ConsumerGroup)) { throw new OrleansConfigurationException($"{nameof(EventHubOptions)} on stream provider {this.name} is invalid. {nameof(EventHubOptions.ConsumerGroup)} is invalid"); } if (string.IsNullOrEmpty(options.EventHubName)) { throw new OrleansConfigurationException($"{nameof(EventHubOptions)} on stream provider {this.name} is invalid. {nameof(EventHubOptions.EventHubName)} is invalid"); } } } public class StreamCheckpointerConfigurationValidator : IConfigurationValidator { private readonly IServiceProvider services; private readonly string name; public StreamCheckpointerConfigurationValidator(IServiceProvider services, string name) { this.services = services; this.name = name; } public void ValidateConfiguration() { var checkpointerFactory = services.GetKeyedService(this.name); if (checkpointerFactory == null) throw new OrleansConfigurationException($"No IStreamQueueCheckpointer is configured with PersistentStreamProvider {this.name}. Please configure one."); } } public class EventHubReceiverOptions { /// /// Optional parameter that configures the receiver prefetch count. /// public int? PrefetchCount { get; set; } /// /// In cases where no checkpoint is found, this indicates if service should read from the most recent data, or from the beginning of a partition. /// public bool StartFromNow { get; set; } = DEFAULT_START_FROM_NOW; private const bool DEFAULT_START_FROM_NOW = true; } public class EventHubStreamCachePressureOptions { /// /// SlowConsumingPressureMonitorConfig /// public double? SlowConsumingMonitorFlowControlThreshold { get; set; } /// /// SlowConsumingMonitorPressureWindowSize /// public TimeSpan? SlowConsumingMonitorPressureWindowSize { get; set; } /// /// AveragingCachePressureMonitorFlowControlThreshold, AveragingCachePressureMonitor is turn on by default. /// User can turn it off by setting this value to null /// public double? AveragingCachePressureMonitorFlowControlThreshold { get; set; } = DEFAULT_AVERAGING_CACHE_PRESSURE_MONITORING_THRESHOLD; internal const double DEFAULT_AVERAGING_CACHE_PRESSURE_MONITORING_THRESHOLD = 1.0 / 3.0; } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/IEventHubDataAdapter.cs ================================================ using System; using Azure.Messaging.EventHubs; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Streaming.EventHubs { public interface IEventHubDataAdapter : IQueueDataAdapter, ICacheDataAdapter { CachedMessage FromQueueMessage(StreamPosition position, EventData queueMessage, DateTime dequeueTime, Func> getSegment); StreamPosition GetStreamPosition(string partition, EventData queueMessage); string GetOffset(CachedMessage cachedMessage); string GetPartitionKey(StreamId streamId); StreamId GetStreamIdentity(EventData queueMessage); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/IEventHubQueueCache.cs ================================================ using System; using Orleans.Streams; using System.Collections.Generic; using Azure.Messaging.EventHubs; using Orleans.Runtime; namespace Orleans.Streaming.EventHubs { /// /// Interface for a stream message cache that stores EventHub EventData /// public interface IEventHubQueueCache : IQueueFlowController, IDisposable { /// /// Add a list of EventHub EventData to the cache. /// /// /// /// List Add(List message, DateTime dequeueTimeUtc); /// /// Get a cursor into the cache to read events from a stream. /// /// /// /// object GetCursor(StreamId streamId, StreamSequenceToken sequenceToken); /// /// Try to get the next message in the cache for the provided cursor. /// /// /// /// bool TryGetNextMessage(object cursorObj, out IBatchContainer message); /// /// Add cache pressure monitor to the cache's back pressure algorithm /// /// void AddCachePressureMonitor(ICachePressureMonitor monitor); /// /// Send purge signal to the cache, the cache will perform a time based purge on its cached messages /// void SignalPurge(); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/IEventHubQueueCacheFactory.cs ================================================ using Microsoft.Extensions.Logging; using Orleans.Streams; namespace Orleans.Streaming.EventHubs { /// /// Factory responsible for creating a message cache for an EventHub partition. /// public interface IEventHubQueueCacheFactory { /// /// Function used to create a IEventHubQueueCache /// IEventHubQueueCache CreateCache(string partition, IStreamQueueCheckpointer checkpointer, ILoggerFactory loggerFactory); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/IEventHubReceiver.cs ================================================ using Azure.Messaging.EventHubs; using Azure.Messaging.EventHubs.Consumer; using Azure.Messaging.EventHubs.Primitives; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streaming.EventHubs { /// /// Abstraction on EventhubReceiver class, used to configure EventHubReceiver class in EventhubAdapterReceiver, /// also used to configure EHGeneratorReceiver in EventHubAdapterReceiver for testing purpose /// public interface IEventHubReceiver { /// /// Send an async message to the partition asking for more messages /// /// Max amount of message which should be delivered in this request /// Wait time of this request /// Task> ReceiveAsync(int maxCount, TimeSpan waitTime); /// /// Send a clean up message /// /// Task CloseAsync(); } /// /// pass through decorator class for EventHubReceiver /// internal partial class EventHubReceiverProxy : IEventHubReceiver { private readonly PartitionReceiver client; public EventHubReceiverProxy(EventHubPartitionSettings partitionSettings, string offset, ILogger logger) { var receiverOptions = new PartitionReceiverOptions(); if (partitionSettings.ReceiverOptions.PrefetchCount != null) { receiverOptions.PrefetchCount = partitionSettings.ReceiverOptions.PrefetchCount.Value; } var options = partitionSettings.Hub; receiverOptions.ConnectionOptions = options.ConnectionOptions; var connection = options.CreateConnection(options.ConnectionOptions); this.client = new PartitionReceiver(options.ConsumerGroup, partitionSettings.Partition, GetEventPosition(), connection, receiverOptions); EventPosition GetEventPosition() { EventPosition eventPosition; // If we have a starting offset, read from offset if (offset != EventHubConstants.StartOfStream) { LogInfoStartingRead(logger, options.EventHubName, partitionSettings.Partition, offset); eventPosition = EventPosition.FromOffset(offset, true); } // else, if configured to start from now, start reading from most recent data else if (partitionSettings.ReceiverOptions.StartFromNow) { eventPosition = EventPosition.Latest; LogInfoStartingReadLatest(logger, options.EventHubName, partitionSettings.Partition); } else // else, start reading from begining of the partition { eventPosition = EventPosition.Earliest; LogInfoStartingReadBegin(logger, options.EventHubName, partitionSettings.Partition); } return eventPosition; } } public async Task> ReceiveAsync(int maxCount, TimeSpan waitTime) { return await client.ReceiveBatchAsync(maxCount, waitTime); } public async Task CloseAsync() { await client.CloseAsync(); } [LoggerMessage( Level = LogLevel.Information, Message = "Starting to read from EventHub partition {EventHubName}-{Partition} at offset {Offset}" )] private static partial void LogInfoStartingRead(ILogger logger, string eventHubName, string partition, string offset); [LoggerMessage( Level = LogLevel.Information, Message = "Starting to read latest messages from EventHub partition {EventHubName}-{Partition}." )] private static partial void LogInfoStartingReadLatest(ILogger logger, string eventHubName, string partition); [LoggerMessage( Level = LogLevel.Information, Message = "Starting to read messages from begining of EventHub partition {EventHubName}-{Partition}." )] private static partial void LogInfoStartingReadBegin(ILogger logger, string eventHubName, string partition); } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/StatisticMonitors/DefaultEventHubBlockPoolMonitor.cs ================================================ using System.Collections.Generic; using Orleans.Providers.Streams.Common; namespace Orleans.Streaming.EventHubs.StatisticMonitors { /// /// Default monitor for Object pool used by EventHubStreamProvider /// public class DefaultEventHubBlockPoolMonitor : DefaultBlockPoolMonitor { /// /// Constructor /// /// public DefaultEventHubBlockPoolMonitor(EventHubBlockPoolMonitorDimensions dimensions) : base(new KeyValuePair[] { new("Path", dimensions.EventHubPath), new("ObjectPoolId", dimensions.BlockPoolId) }) { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/StatisticMonitors/DefaultEventHubCacheMonitor.cs ================================================ using System.Collections.Generic; using Orleans.Providers.Streams.Common; namespace Orleans.Streaming.EventHubs.StatisticMonitors { /// /// Default cache monitor for eventhub streaming provider ecosystem /// public class DefaultEventHubCacheMonitor : DefaultCacheMonitor { /// /// Constructor /// /// public DefaultEventHubCacheMonitor(EventHubCacheMonitorDimensions dimensions) : base(new KeyValuePair[] { new("Path", dimensions.EventHubPath), new("Partition", dimensions.EventHubPartition) }) { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/StatisticMonitors/DefaultEventHubReceiverMonitor.cs ================================================ using System.Collections.Generic; using Orleans.Providers.Streams.Common; namespace Orleans.Streaming.EventHubs { /// /// Default EventHub receiver monitor that tracks metrics using loggers PKI support. /// public class DefaultEventHubReceiverMonitor : DefaultQueueAdapterReceiverMonitor { /// /// Constructor /// /// Aggregation Dimension bag for EventhubReceiverMonitor public DefaultEventHubReceiverMonitor(EventHubReceiverMonitorDimensions dimensions) : base(new KeyValuePair[] { new("Path", dimensions.EventHubPath), new("Partition", dimensions.EventHubPartition) }) { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/Providers/Streams/EventHub/StatisticMonitors/MonitorAggregationDimentions.cs ================================================ namespace Orleans.Streaming.EventHubs { /// /// Base class for monitor aggregation dimensions, which is an information bag for the monitoring target. /// Monitors can use this information bag to build its aggregation dimensions. /// public class EventHubMonitorAggregationDimensions { /// /// Eventhub path /// public string EventHubPath { get; set; } /// /// Constructor /// /// public EventHubMonitorAggregationDimensions(string ehHubPath) { this.EventHubPath = ehHubPath; } /// /// Constructor /// /// public EventHubMonitorAggregationDimensions(EventHubMonitorAggregationDimensions dimensions) { this.EventHubPath = dimensions.EventHubPath; } /// /// Zero parameter constructor /// public EventHubMonitorAggregationDimensions() { } } /// /// Aggregation dimensions for EventHubReceiverMonitor /// public class EventHubReceiverMonitorDimensions : EventHubMonitorAggregationDimensions { /// /// Eventhub partition /// public string EventHubPartition { get; set; } /// /// Constructor /// /// /// public EventHubReceiverMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string ehPartition) :base(dimensions) { this.EventHubPartition = ehPartition; } /// /// Zero parameter constructor /// public EventHubReceiverMonitorDimensions() { } } /// /// Aggregation dimensions for cache monitor used in Eventhub stream provider ecosystem /// public class EventHubCacheMonitorDimensions : EventHubReceiverMonitorDimensions { /// /// Block pool this cache belongs to /// public string BlockPoolId { get; set; } /// /// Constructor /// /// /// /// public EventHubCacheMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string ehPartition, string blockPoolId) :base(dimensions, ehPartition) { this.BlockPoolId = blockPoolId; } /// /// Zero parameters constructor /// public EventHubCacheMonitorDimensions() { } } /// /// Aggregation dimensions for block pool monitor used in Eventhub stream provider ecosystem /// public class EventHubBlockPoolMonitorDimensions : EventHubMonitorAggregationDimensions { /// /// Block pool Id /// public string BlockPoolId { get; set; } /// /// Constructor /// /// /// public EventHubBlockPoolMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string blockPoolId) :base(dimensions) { this.BlockPoolId = blockPoolId; } /// /// Zero parameter constructor /// public EventHubBlockPoolMonitorDimensions() { } } } ================================================ FILE: src/Azure/Orleans.Streaming.EventHubs/README.md ================================================ # Microsoft Orleans Stream Provider for Azure Event Hubs ## Introduction Microsoft Orleans Stream Provider for Azure Event Hubs enables Orleans applications to leverage Azure Event Hubs for reliable, scalable event processing. This provider allows you to use Event Hubs as a streaming backbone for your Orleans application to both produce and consume streams of events. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Streaming.EventHubs ``` ## Example - Configuring Event Hubs Stream Provider ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; namespace ExampleGrains; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Event Hubs as a stream provider .AddEventHubStreams( "EventHubStreamProvider", configurator => { configurator.ConfigureEventHub(builder => builder.Configure(options => { options.ConnectionString = "YOUR_EVENT_HUB_CONNECTION_STRING"; options.ConsumerGroup = "YOUR_CONSUMER_GROUP"; // Default is "$Default" options.Path = "YOUR_EVENT_HUB_NAME"; })); configurator.UseAzureTableCheckpointer(builder => builder.Configure(options => { options.ConnectionString = "YOUR_STORAGE_CONNECTION_STRING"; options.TableName = "EventHubCheckpoints"; // Optional })); }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Event Hub Streams in a Grain ```csharp // Grain interface public interface IStreamProcessingGrain : IGrainWithGuidKey { Task StartProcessing(); } // Grain implementation public class StreamProcessingGrain : Grain, IStreamProcessingGrain { private IStreamProvider _streamProvider; private IAsyncStream _stream; private StreamSubscriptionHandle _subscription; public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Get the stream provider _streamProvider = GetStreamProvider("EventHubStreamProvider"); // Get a reference to a specific stream _stream = _streamProvider.GetStream(this.GetPrimaryKey(), "MyStreamNamespace"); await base.OnActivateAsync(cancellationToken); } public async Task StartProcessing() { // Subscribe to the stream to process events _subscription = await _stream.SubscribeAsync(OnNextAsync); } private Task OnNextAsync(MyEvent evt, StreamSequenceToken token) { Console.WriteLine($"Received event: {evt.Data}"); return Task.CompletedTask; } // Produce an event to the stream public Task SendEvent(MyEvent evt) { return _stream.OnNextAsync(evt); } } // Event class public class MyEvent { public string Data { get; set; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Streams](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/) - [Event Hubs integration](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/streams-implementation) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/Hosting/AzureTableTransactionServicecollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.AzureStorage; namespace Orleans.Hosting { /// /// extensions. /// public static class AzureTableTransactionServicecollectionExtensions { internal static IServiceCollection AddAzureTableTransactionalStateStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient(sp => new AzureTableTransactionalStateOptionsValidator(sp.GetRequiredService>().Get(name), name)); services.TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); services.AddKeyedSingleton(name, (sp, key) => AzureTableTransactionalStateStorageFactory.Create(sp, key as string)); services.AddSingleton>(s => (ILifecycleParticipant)s.GetRequiredKeyedService(name)); return services; } } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/Hosting/AzureTableTransactionsSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; namespace Orleans.Hosting { public static class AzureTableTransactionSiloBuilderExtensions { /// /// Configure silo to use azure table storage as the default transactional grain storage. /// public static ISiloBuilder AddAzureTableTransactionalStateStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddAzureTableTransactionalStateStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure table storage for transactional grain storage. /// public static ISiloBuilder AddAzureTableTransactionalStateStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddAzureTableTransactionalStateStorage(name, ob => ob.Configure(configureOptions))); } /// /// Configure silo to use azure table storage as the default transactional grain storage. /// public static ISiloBuilder AddAzureTableTransactionalStateStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddAzureTableTransactionalStateStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use azure table storage for transactional grain storage. /// public static ISiloBuilder AddAzureTableTransactionalStateStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder.ConfigureServices(services => services.AddAzureTableTransactionalStateStorage(name, configureOptions)); } } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/Orleans.Transactions.AzureStorage.csproj ================================================ README.md Microsoft.Orleans.Transactions.AzureStorage Microsoft Orleans Azure Storage Transactions Provider Microsoft Orleans transactions storage provider backed by Azure Storage $(PackageTags) Azure Transactions $(DefaultTargetFrameworks) Orleans.Transactions.AzureStorage Orleans.Transactions.AzureStorage $(DefineConstants);ORLEANS_TRANSACTIONS true ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/README.md ================================================ # Microsoft Orleans Transactions for Azure Storage ## Introduction Microsoft Orleans Transactions for Azure Storage provides the infrastructure to store Orleans transaction logs in Azure Storage. This package allows Orleans applications to use ACID transactions across multiple grain calls with Azure Storage as the backing transaction log store. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Transactions.AzureStorage ``` ## Example - Configuring Azure Storage for Transactions ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Transactions; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Enable transactions .AddAzureTableTransactionalStateStorage( name: "TransactionStore", configureOptions: options => { options.ConnectionString = "YOUR_AZURE_STORAGE_CONNECTION_STRING"; }) .UseTransactions(); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Transactions in Grains ```csharp // A grain with transactional state public class MyTransactionalGrain : Grain, IMyTransactionalGrain { private readonly ITransactionalState _state; // Inject the transactional state public MyTransactionalGrain( [TransactionalState("state", "TransactionStore")] ITransactionalState state) { _state = state; } // Method that performs a transaction [Transaction(TransactionOption.Create)] public async Task Transfer(string otherGrainKey, int amount) { // Read our state within the transaction var myState = await _state.PerformRead(state => state); // Ensure we have enough balance if (myState.Balance < amount) throw new InvalidOperationException("Insufficient funds"); // Update our state within the transaction await _state.PerformUpdate(s => s.Balance -= amount); // Call another grain within the same transaction var otherGrain = GrainFactory.GetGrain(otherGrainKey); await otherGrain.Deposit(amount); } // Method that participates in a transaction [Transaction(TransactionOption.Join)] public Task Deposit(int amount) { // Update state within the joined transaction return _state.PerformUpdate(s => s.Balance += amount); } // Read operation within a transaction [Transaction(TransactionOption.CreateOrJoin)] public Task GetBalance() { return _state.PerformRead(s => s.Balance); } } // State class public class MyState { public int Balance { get; set; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Transactions](https://learn.microsoft.com/en-us/dotnet/orleans/grains/transactions) - [Distributed ACID Transactions](https://learn.microsoft.com/en-us/dotnet/orleans/grains/transactions/acid-transactions) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/AzureTableTransactionalStateOptions.cs ================================================ using Orleans.Transactions.AzureStorage; namespace Orleans.Configuration { public class AzureTableTransactionalStateOptions : AzureStorageOperationOptions { /// /// Azure table where transactional grain state will be stored /// public override string TableName { get; set; } = "TransactionalState"; /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; } /// /// Configuration validator for . /// public class AzureTableTransactionalStateOptionsValidator : AzureStorageOperationOptionsValidator { /// /// Initializes a new instance of the class. /// /// The option to be validated. /// The option name to be validated. public AzureTableTransactionalStateOptionsValidator(AzureTableTransactionalStateOptions options, string name) : base(options, name) { } } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/AzureTableTransactionalStateStorage.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Azure; using Azure.Data.Tables; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.AzureStorage { public partial class AzureTableTransactionalStateStorage : ITransactionalStateStorage where TState : class, new() { private readonly TableClient table; private readonly string partition; private readonly JsonSerializerSettings jsonSettings; private readonly ILogger logger; private KeyEntity key; private List> states; public AzureTableTransactionalStateStorage(TableClient table, string partition, JsonSerializerSettings JsonSettings, ILogger> logger) { this.table = table; this.partition = partition; this.jsonSettings = JsonSettings; this.logger = logger; // default values must be included // otherwise, we get errors for explicitly specified default values // (e.g. Orleans.Transactions.Azure.Tests.TestState.state) this.jsonSettings.DefaultValueHandling = DefaultValueHandling.Include; } public async Task> Load() { try { var keyTask = ReadKey(); var statesTask = ReadStates(); key = await keyTask.ConfigureAwait(false); states = await statesTask.ConfigureAwait(false); if (string.IsNullOrEmpty(key.ETag.ToString())) { LogDebugLoadedV0Fresh(partition); // first time load return new TransactionalStorageLoadResponse(); } else { TState committedState; if (this.key.CommittedSequenceId == 0) { committedState = new TState(); } else { if (!FindState(this.key.CommittedSequenceId, out var pos)) { var error = $"Storage state corrupted: no record for committed state v{this.key.CommittedSequenceId}"; LogCriticalPartitionError(partition, error); throw new InvalidOperationException(error); } committedState = states[pos].Value.GetState(this.jsonSettings); } var PrepareRecordsToRecover = new List>(); for (int i = 0; i < states.Count; i++) { var kvp = states[i]; // pending states for already committed transactions can be ignored if (kvp.Key <= key.CommittedSequenceId) continue; // upon recovery, local non-committed transactions are considered aborted if (kvp.Value.TransactionManager == null) break; ParticipantId tm = JsonConvert.DeserializeObject(kvp.Value.TransactionManager, this.jsonSettings); PrepareRecordsToRecover.Add(new PendingTransactionState() { SequenceId = kvp.Key, State = kvp.Value.GetState(this.jsonSettings), TimeStamp = kvp.Value.TransactionTimestamp, TransactionId = kvp.Value.TransactionId, TransactionManager = tm }); } // clear the state strings... no longer needed, ok to GC now for (int i = 0; i < states.Count; i++) { var entity = states[i].Value; entity.StateJson = null; } LogDebugLoadedPartitionKeyRows(partition, this.key.CommittedSequenceId, new(states)); TransactionalStateMetaData metadata = JsonConvert.DeserializeObject(this.key.Metadata, this.jsonSettings); return new TransactionalStorageLoadResponse(this.key.ETag.ToString(), committedState, this.key.CommittedSequenceId, metadata, PrepareRecordsToRecover); } } catch (Exception ex) { LogErrorTransactionalStateLoadFailed(ex); throw; } } public async Task Store(string expectedETag, TransactionalStateMetaData metadata, List> statesToPrepare, long? commitUpTo, long? abortAfter) { var keyETag = key.ETag.ToString(); if ((!string.IsNullOrWhiteSpace(keyETag) || !string.IsNullOrWhiteSpace(expectedETag)) && keyETag != expectedETag) { throw new ArgumentException(nameof(expectedETag), "Etag does not match"); } // assemble all storage operations into a single batch // these operations must commit in sequence, but not necessarily atomically // so we can split this up if needed var batchOperation = new BatchOperation(logger, key, table); // first, clean up aborted records if (abortAfter.HasValue && states.Count != 0) { while (states.Count > 0 && states[states.Count - 1].Key > abortAfter) { var entity = states[states.Count - 1].Value; await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Delete, entity.Entity, entity.ETag)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; states.RemoveAt(states.Count - 1); LogTraceDeleteTransaction(partition, entity.RowKey, entity.TransactionId); } } // second, persist non-obsolete prepare records var obsoleteBefore = commitUpTo.HasValue ? commitUpTo.Value : key.CommittedSequenceId; if (statesToPrepare != null) foreach (var s in statesToPrepare) if (s.SequenceId >= obsoleteBefore) { if (FindState(s.SequenceId, out var pos)) { // overwrite with new pending state StateEntity existing = states[pos].Value; existing.TransactionId = s.TransactionId; existing.TransactionTimestamp = s.TimeStamp; existing.TransactionManager = JsonConvert.SerializeObject(s.TransactionManager, this.jsonSettings); existing.SetState(s.State, this.jsonSettings); await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, existing.Entity, existing.ETag)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; LogTraceUpdateTransaction(partition, existing.RowKey, existing.TransactionId); } else { var entity = StateEntity.Create(this.jsonSettings, this.partition, s); await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, entity.Entity)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; states.Insert(pos, new KeyValuePair(s.SequenceId, entity)); LogTraceInsertTransaction(partition, entity.RowKey, entity.TransactionId); } } // third, persist metadata and commit position key.Metadata = JsonConvert.SerializeObject(metadata, this.jsonSettings); if (commitUpTo.HasValue && commitUpTo.Value > key.CommittedSequenceId) { key.CommittedSequenceId = commitUpTo.Value; } if (string.IsNullOrEmpty(this.key.ETag.ToString())) { await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, key)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; LogTraceInsertWithCount(partition, KeyEntity.RK, this.key.CommittedSequenceId, metadata.CommitRecords.Count); } else { await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, key, key.ETag)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; LogTraceUpdateWithCount(partition, KeyEntity.RK, this.key.CommittedSequenceId, metadata.CommitRecords.Count); } // fourth, remove obsolete records if (states.Count > 0 && states[0].Key < obsoleteBefore) { FindState(obsoleteBefore, out var pos); for (int i = 0; i < pos; i++) { await batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Delete, states[i].Value.Entity, states[i].Value.ETag)).ConfigureAwait(false); key.ETag = batchOperation.KeyETag; LogTraceDeleteTransaction(partition, states[i].Value.RowKey, states[i].Value.TransactionId); } states.RemoveRange(0, pos); } await batchOperation.Flush().ConfigureAwait(false); LogDebugStoredETag(partition, this.key.CommittedSequenceId, key.ETag); return key.ETag.ToString(); } private bool FindState(long sequenceId, out int pos) { pos = 0; while (pos < states.Count) { switch (states[pos].Key.CompareTo(sequenceId)) { case 0: return true; case -1: pos++; continue; case 1: return false; } } return false; } private async Task ReadKey() { var queryResult = table.QueryAsync(AzureTableUtils.PointQuery(this.partition, KeyEntity.RK)).ConfigureAwait(false); await foreach (var result in queryResult) { return result; } return new KeyEntity() { PartitionKey = partition, RowKey = KeyEntity.RK }; } private async Task>> ReadStates() { var query = AzureTableUtils.RangeQuery(this.partition, StateEntity.RK_MIN, StateEntity.RK_MAX); var results = new List>(); var queryResult = table.QueryAsync(query).ConfigureAwait(false); await foreach (var entity in queryResult) { var state = new StateEntity(entity); results.Add(new KeyValuePair(state.SequenceId, state)); }; return results; } private class BatchOperation { private readonly List batchOperation; private readonly ILogger logger; private readonly TableClient table; private KeyEntity key; private int keyIndex = -1; public BatchOperation(ILogger logger, KeyEntity key, TableClient table) { this.batchOperation = new(); this.logger = logger; this.key = key; this.table = table; } public ETag KeyETag => key.ETag; private bool BatchHasKey => keyIndex >= 0; public async ValueTask Add(TableTransactionAction operation) { if (!BatchHasKey && operation.Entity.RowKey == key.RowKey && operation.Entity.PartitionKey == key.PartitionKey) { key = (KeyEntity)operation.Entity; keyIndex = batchOperation.Count; } batchOperation.Add(operation); if (batchOperation.Count == AzureTableConstants.MaxBatchSize - (BatchHasKey ? 0 : 1)) { // the key serves as a synchronizer, to prevent modification by multiple grains under edge conditions, // like duplicate activations or deployments.Every batch write needs to include the key, // even if the key values don't change. if (!BatchHasKey) { keyIndex = batchOperation.Count; if (string.IsNullOrEmpty(key.ETag.ToString())) { batchOperation.Add(new TableTransactionAction(TableTransactionActionType.Add, key)); } else { batchOperation.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, key, key.ETag)); } } await Flush().ConfigureAwait(false); } } public async Task Flush() { if (batchOperation.Count > 0) { try { var batchResponse = await table.SubmitTransactionAsync(batchOperation).ConfigureAwait(false); if (batchResponse?.Value is { Count: > 0 } responses) { if (BatchHasKey && responses.Count >= keyIndex && responses[keyIndex].Headers.ETag is { } etag) { key.ETag = etag; } } if (logger.IsEnabled(LogLevel.Trace)) { for (int i = 0; i < batchOperation.Count; i++) { LogTraceBatchOpOk(logger, batchOperation[i].Entity.PartitionKey, batchOperation[i].Entity.RowKey, i); } } batchOperation.Clear(); keyIndex = -1; } catch (Exception ex) { if (logger.IsEnabled(LogLevel.Trace)) { for (int i = 0; i < batchOperation.Count; i++) { LogTraceBatchOpFailed(logger, batchOperation[i].Entity.PartitionKey, batchOperation[i].Entity.RowKey, i); } } LogErrorTransactionalStateStoreFailed(logger, ex); throw; } } } } [LoggerMessage( Level = LogLevel.Debug, Message = "{Partition} Loaded v0, fresh" )] private partial void LogDebugLoadedV0Fresh(string partition); [LoggerMessage( Level = LogLevel.Critical, Message = "{Partition} {Error}" )] private partial void LogCriticalPartitionError(string partition, string error); private readonly struct StatesLogRecord(List> states) { public override string ToString() => string.Join(",", states.Select(s => s.Key.ToString("x16"))); } [LoggerMessage( Level = LogLevel.Debug, Message = "{PartitionKey} Loaded v{CommittedSequenceId} rows={Data}" )] private partial void LogDebugLoadedPartitionKeyRows(string partitionKey, long committedSequenceId, StatesLogRecord data); [LoggerMessage( Level = LogLevel.Error, Message = "Transactional state load failed" )] private partial void LogErrorTransactionalStateLoadFailed(Exception ex); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} Delete {TransactionId}" )] private partial void LogTraceDeleteTransaction(string partitionKey, string rowKey, string transactionId); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} Update {TransactionId}" )] private partial void LogTraceUpdateTransaction(string partitionKey, string rowKey, string transactionId); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} Insert {TransactionId}" )] private partial void LogTraceInsertTransaction(string partitionKey, string rowKey, string transactionId); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} Insert. v{CommittedSequenceId}, {CommitRecordsCount}c" )] private partial void LogTraceInsertWithCount(string partitionKey, string rowKey, long committedSequenceId, int commitRecordsCount); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} Update. v{CommittedSequenceId}, {CommitRecordsCount}c" )] private partial void LogTraceUpdateWithCount(string partitionKey, string rowKey, long committedSequenceId, int commitRecordsCount); [LoggerMessage( Level = LogLevel.Debug, Message = "{PartitionKey} Stored v{CommittedSequenceId} eTag={ETag}" )] private partial void LogDebugStoredETag(string partitionKey, long committedSequenceId, ETag eTag); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} batch-op ok {BatchCount}" )] private static partial void LogTraceBatchOpOk(ILogger logger, string partitionKey, string rowKey, int batchCount); [LoggerMessage( Level = LogLevel.Trace, Message = "{PartitionKey}.{RowKey} batch-op failed {BatchCount}" )] private static partial void LogTraceBatchOpFailed(ILogger logger, string partitionKey, string rowKey, int batchCount); [LoggerMessage( Level = LogLevel.Error, Message = "Transactional state store failed." )] private static partial void LogErrorTransactionalStateStoreFailed(ILogger logger, Exception ex); } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/AzureTableTransactionalStateStorageFactory.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.AzureStorage { public class AzureTableTransactionalStateStorageFactory : ITransactionalStateStorageFactory, ILifecycleParticipant { private readonly string name; private readonly AzureTableTransactionalStateOptions options; private readonly ClusterOptions clusterOptions; private readonly JsonSerializerSettings jsonSettings; private readonly ILoggerFactory loggerFactory; private TableClient table; public static ITransactionalStateStorageFactory Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); return ActivatorUtilities.CreateInstance(services, name, optionsMonitor.Get(name)); } public AzureTableTransactionalStateStorageFactory(string name, AzureTableTransactionalStateOptions options, IOptions clusterOptions, IServiceProvider services, ILoggerFactory loggerFactory) { this.name = name; this.options = options; this.clusterOptions = clusterOptions.Value; this.jsonSettings = TransactionalStateFactory.GetJsonSerializerSettings(services); this.loggerFactory = loggerFactory; } public ITransactionalStateStorage Create(string stateName, IGrainContext context) where TState : class, new() { string partitionKey = MakePartitionKey(context, stateName); return ActivatorUtilities.CreateInstance>(context.ActivationServices, this.table, partitionKey, this.jsonSettings); } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.name), this.options.InitStage, Init); } private string MakePartitionKey(IGrainContext context, string stateName) { string grainKey = context.GrainReference.GrainId.ToString(); var key = $"{grainKey}_{this.clusterOptions.ServiceId}_{stateName}"; return AzureTableUtils.SanitizeTableProperty(key); } private async Task CreateTable() { var tableManager = new AzureTableDataManager( this.options, this.loggerFactory.CreateLogger>()); await tableManager.InitTableAsync(); this.table = tableManager.Table; } private Task Init(CancellationToken cancellationToken) { return CreateTable(); } } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/KeyEntity.cs ================================================ using System; using Azure; using Azure.Data.Tables; namespace Orleans.Transactions.AzureStorage { internal class KeyEntity : ITableEntity { public const string RK = "k"; public KeyEntity() { this.RowKey = RK; } public long CommittedSequenceId { get; set; } public string Metadata { get; set; } public string PartitionKey { get; set; } public string RowKey { get; set; } public DateTimeOffset? Timestamp { get; set; } public ETag ETag { get; set; } } } ================================================ FILE: src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/StateEntity.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Azure; using Azure.Data.Tables; using Newtonsoft.Json; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.AzureStorage { internal readonly struct StateEntity { // Each property can hold 64KB of data and each entity can take 1MB in total, so 15 full properties take // 15 * 64 = 960 KB leaving room for the primary key, timestamp etc private const int MAX_DATA_CHUNK_SIZE = 64 * 1024; private const int MAX_STRING_PROPERTY_LENGTH = 32 * 1024; private const int MAX_DATA_CHUNKS_COUNT = 15; private const string STRING_DATA_PROPERTY_NAME_PREFIX = nameof(StateJson); private static readonly string[] StringDataPropertyNames = GetPropertyNames().ToArray(); public TableEntity Entity { get; } public StateEntity(TableEntity entity) => Entity = entity; public string PartitionKey => Entity.PartitionKey; public string RowKey => Entity.RowKey; public DateTimeOffset? Timestamp => Entity.Timestamp; public ETag ETag => Entity.ETag; public static string MakeRowKey(long sequenceId) { return $"{RK_PREFIX}{sequenceId.ToString("x16")}"; } public long SequenceId => long.Parse(this.RowKey[RK_PREFIX.Length..], NumberStyles.AllowHexSpecifier); // Row keys range from s0000000000000001 to s7fffffffffffffff public const string RK_PREFIX = "s_"; public const string RK_MIN = RK_PREFIX; public const string RK_MAX = RK_PREFIX + "~"; public string TransactionId { get => this.GetPropertyOrDefault(nameof(this.TransactionId)) as string; set => this.Entity[nameof(this.TransactionId)] = value; } public DateTime TransactionTimestamp { get => this.Entity.GetDateTimeOffset(nameof(this.TransactionTimestamp)).GetValueOrDefault().UtcDateTime; set => this.Entity[nameof(this.TransactionTimestamp)] = new DateTimeOffset(value.ToUniversalTime()); } public string TransactionManager { get => this.GetPropertyOrDefault(nameof(this.TransactionManager)) as string; set => this.Entity[nameof(this.TransactionManager)] = value; } public string StateJson { get => this.GetStateInternal(); set => this.SetStateInternal(value); } public static StateEntity Create(JsonSerializerSettings JsonSettings, string partitionKey, PendingTransactionState pendingState) where T : class, new() { var entity = new TableEntity(partitionKey, MakeRowKey(pendingState.SequenceId)); var result = new StateEntity(entity) { TransactionId = pendingState.TransactionId, TransactionTimestamp = pendingState.TimeStamp, TransactionManager = JsonConvert.SerializeObject(pendingState.TransactionManager, JsonSettings), }; result.SetState(pendingState.State, JsonSettings); return result; } public T GetState(JsonSerializerSettings jsonSettings) { return JsonConvert.DeserializeObject(this.GetStateInternal(), jsonSettings); } public void SetState(T state, JsonSerializerSettings jsonSettings) { this.SetStateInternal(JsonConvert.SerializeObject(state, jsonSettings)); } private void SetStateInternal(string stringData) { CheckMaxDataSize((stringData ?? string.Empty).Length * 2, MAX_DATA_CHUNK_SIZE * MAX_DATA_CHUNKS_COUNT); foreach (var key in StringDataPropertyNames) { this.Entity.Remove(key); } foreach (var entry in SplitStringData(stringData)) { this.Entity[entry.Key] = entry.Value; } static IEnumerable> SplitStringData(string stringData) { if (string.IsNullOrEmpty(stringData)) yield break; var columnIndex = 0; var stringStartIndex = 0; while (stringStartIndex < stringData.Length) { var chunkSize = Math.Min(MAX_STRING_PROPERTY_LENGTH, stringData.Length - stringStartIndex); var key = StringDataPropertyNames[columnIndex]; var value = stringData.Substring(stringStartIndex, chunkSize); yield return new KeyValuePair(key, value); columnIndex++; stringStartIndex += chunkSize; } } } private string GetStateInternal() { return string.Concat(ReadStringDataChunks(this.Entity)); static IEnumerable ReadStringDataChunks(IDictionary properties) { foreach (var stringDataPropertyName in StringDataPropertyNames) { if (properties.TryGetValue(stringDataPropertyName, out var dataProperty)) { if (dataProperty is string { Length: >0 } data) { yield return data; } } } } } private object GetPropertyOrDefault(string key) { this.Entity.TryGetValue(key, out var result); return result; } private static void CheckMaxDataSize(int dataSize, int maxDataSize) { if (dataSize > maxDataSize) { var msg = string.Format("Data too large to write to table. Size={0} MaxSize={1}", dataSize, maxDataSize); throw new ArgumentOutOfRangeException("state", msg); } } private static IEnumerable GetPropertyNames() { yield return STRING_DATA_PROPERTY_NAME_PREFIX; for (var i = 1; i < MAX_DATA_CHUNKS_COUNT; ++i) { yield return STRING_DATA_PROPERTY_NAME_PREFIX + i.ToString(CultureInfo.InvariantCulture); } } } } ================================================ FILE: src/Azure/Shared/Cosmos/BaseEntity.cs ================================================ using Newtonsoft.Json; #if ORLEANS_CLUSTERING namespace Orleans.Clustering.Cosmos; #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.Cosmos; #elif ORLEANS_REMINDERS namespace Orleans.Reminders.Cosmos; #elif ORLEANS_STREAMING namespace Orleans.Streaming.Cosmos; #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.Cosmos; #else // No default namespace intentionally to cause compile errors if something is not defined #endif internal abstract class BaseEntity { internal const string ID_FIELD = "id"; internal const string ETAG_FIELD = "_etag"; [JsonProperty(ID_FIELD)] [JsonPropertyName(ID_FIELD)] public string Id { get; set; } = default!; [JsonProperty(ETAG_FIELD)] [JsonPropertyName(ETAG_FIELD)] public string ETag { get; set; } = default!; } ================================================ FILE: src/Azure/Shared/Cosmos/CosmosIdSanitizer.cs ================================================ #if ORLEANS_CLUSTERING namespace Orleans.Clustering.Cosmos; #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.Cosmos; #elif ORLEANS_REMINDERS namespace Orleans.Reminders.Cosmos; #elif ORLEANS_STREAMING namespace Orleans.Streaming.Cosmos; #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.Cosmos; #else // No default namespace intentionally to cause compile errors if something is not defined #endif internal static class CosmosIdSanitizer { private const char EscapeChar = '~'; private static ReadOnlySpan SanitizedCharacters => new[] { '/', '\\', '?', '#', SeparatorChar, EscapeChar }; private static ReadOnlySpan ReplacementCharacters => new[] { '0', '1', '2', '3', '4', '5' }; public const char SeparatorChar = '_'; public static string Sanitize(string input) { var count = 0; foreach (var c in input) { var charId = SanitizedCharacters.IndexOf(c); if (charId >= 0) { ++count; } } if (count == 0) { return input; } return string.Create(input.Length + count, input, static (output, input) => { var i = 0; foreach (var c in input) { var charId = SanitizedCharacters.IndexOf(c); if (charId < 0) { output[i++] = c; continue; } output[i++] = EscapeChar; output[i++] = ReplacementCharacters[charId]; } }); } public static string Unsanitize(string input) { var count = 0; foreach (var c in input) { if (c == EscapeChar) { ++count; } } if (count == 0) { return input; } return string.Create(input.Length - count, input, static (output, input) => { var i = 0; var isEscaped = false; foreach (var c in input) { if (isEscaped) { var charId = ReplacementCharacters.IndexOf(c); if (charId < 0) { throw new ArgumentException($"Input is not in a valid format: Encountered unsupported escape sequence"); } output[i++] = SanitizedCharacters[charId]; isEscaped = false; } else if (c == EscapeChar) { isEscaped = true; } else { output[i++] = c; } } }); } } ================================================ FILE: src/Azure/Shared/Cosmos/CosmosOptions.cs ================================================ using System.Net; using Azure; using Azure.Core; #if ORLEANS_CLUSTERING namespace Orleans.Clustering.Cosmos; #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.Cosmos; #elif ORLEANS_REMINDERS namespace Orleans.Reminders.Cosmos; #elif ORLEANS_STREAMING namespace Orleans.Streaming.Cosmos; #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.Cosmos; #else // No default namespace intentionally to cause compile errors if something is not defined #endif /// /// Options for Azure Cosmos DB storage. /// public abstract class CosmosOptions { /// /// Tries to create the database and container used for clustering if it does not exist. Defaults to . /// public bool IsResourceCreationEnabled { get; set; } /// /// Database configured throughput. If set to , which is the default value, it will not be configured. /// /// public int? DatabaseThroughput { get; set; } /// /// The name of the database to use for clustering information. Defaults to Orleans. /// public string DatabaseName { get; set; } = "Orleans"; /// /// The name of the container to use to store clustering information. /// public string ContainerName { get; set; } = default!; /// /// Throughput properties for containers. The default value is , which indicates that the serverless throughput mode will be used. /// /// public ThroughputProperties? ContainerThroughputProperties { get; set; } /// /// Delete the database on initialization. Intended only for testing scenarios. /// /// This is only intended for use in testing scenarios. public bool CleanResourcesOnInitialization { get; set; } /// /// The options passed to the Cosmos DB client. /// public CosmosClientOptions ClientOptions { get; set; } = new(); /// /// The operation executor used to execute operations using the Cosmos DB client. /// public ICosmosOperationExecutor OperationExecutor { get; set; } = DefaultCosmosOperationExecutor.Instance; /// /// Configures the Cosmos DB client. /// /// The connection string. /// public void ConfigureCosmosClient(string connectionString) { CreateClient = _ => new(new CosmosClient(connectionString, ClientOptions)); } /// /// Configures the Cosmos DB client. /// /// The account endpoint. In the form of https://{databaseaccount}.documents.azure.com:443/, /// with master-key or resource token. /// public void ConfigureCosmosClient(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential) { CreateClient = _ => new(new CosmosClient(accountEndpoint, authKeyOrResourceTokenCredential, ClientOptions)); } /// /// Configures the Cosmos DB client. /// /// The account endpoint. In the form of https://{databaseaccount}.documents.azure.com:443/, /// The token to provide AAD for authorization. /// public void ConfigureCosmosClient(string accountEndpoint, TokenCredential tokenCredential) { CreateClient = _ => new(new CosmosClient(accountEndpoint, tokenCredential, ClientOptions)); } /// /// Configures the Cosmos DB client. /// /// The account endpoint. In the form of https://{databaseaccount}.documents.azure.com:443/, /// The Cosmos account key or resource token to use to create the client. /// public void ConfigureCosmosClient(string accountEndpoint, string authKeyOrResourceToken) { CreateClient = _ => new(new CosmosClient(accountEndpoint, authKeyOrResourceToken, ClientOptions)); } /// /// Configures the Cosmos DB client. /// /// The delegate used to create the Cosmos DB client. public void ConfigureCosmosClient(Func> createClient) { CreateClient = createClient ?? throw new ArgumentNullException(nameof(createClient)); } /// /// Factory method for creating a . /// internal Func> CreateClient { get; private set; } = null!; } /// /// Functionality for executing operations using the Cosmos DB client. /// public interface ICosmosOperationExecutor { /// /// Executes the provided Cosmos DB operation. /// /// The function argument. /// The result value. /// The delegate to execute. /// The argument to pass to delegate invocations. /// The result of invoking the delegate. Task ExecuteOperation(Func> func, TArg arg); } internal sealed class DefaultCosmosOperationExecutor : ICosmosOperationExecutor { public static readonly DefaultCosmosOperationExecutor Instance = new(); private const HttpStatusCode TOO_MANY_REQUESTS = (HttpStatusCode)429; public async Task ExecuteOperation(Func> func, TArg arg) { // From: https://blogs.msdn.microsoft.com/bigdatasupport/2015/09/02/dealing-with-requestratetoolarge-errors-in-azure-documentdb-and-testing-performance/ while (true) { TimeSpan sleepTime; try { return await func(arg).ConfigureAwait(false); } catch (CosmosException dce) when (dce.StatusCode == TOO_MANY_REQUESTS) { sleepTime = dce.RetryAfter ?? TimeSpan.Zero; } catch (AggregateException ae) when (ae.InnerException is CosmosException dce && dce.StatusCode == TOO_MANY_REQUESTS) { sleepTime = dce.RetryAfter ?? TimeSpan.Zero; } await Task.Delay(sleepTime); } } } ================================================ FILE: src/Azure/Shared/Cosmos/CosmosOptionsValidator.cs ================================================ #if ORLEANS_CLUSTERING namespace Orleans.Clustering.Cosmos; #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.Cosmos; #elif ORLEANS_REMINDERS namespace Orleans.Reminders.Cosmos; #elif ORLEANS_STREAMING namespace Orleans.Streaming.Cosmos; #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.Cosmos; #else // No default namespace intentionally to cause compile errors if something is not defined #endif /// /// Validates instances of . /// /// The options type. public class CosmosOptionsValidator : IConfigurationValidator where TOptions : CosmosOptions { private readonly TOptions _options; private readonly string _name; /// /// Initializes a new instance of the type. /// /// The instance to be validated. /// The option name to be validated. public CosmosOptionsValidator(TOptions options, string name) { _options = options; _name = name; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(_options.DatabaseName)) throw new OrleansConfigurationException( $"Configuration for Azure Cosmos DB provider {_name} is invalid. {nameof(_options.DatabaseName)} is not valid."); if (string.IsNullOrWhiteSpace(_options.ContainerName)) throw new OrleansConfigurationException( $"Configuration for Azure Cosmos DB provider {_name} is invalid. {nameof(_options.ContainerName)} is not valid."); if (_options.CreateClient is null) { throw new OrleansConfigurationException( $"Configuration for Azure Cosmos DB provider {_name} is invalid. You must call {nameof(_options.ConfigureCosmosClient)} to configure access to Azure Cosmos DB."); } } } ================================================ FILE: src/Azure/Shared/Cosmos/Usings.cs ================================================ global using System; global using System.Linq; global using System.Threading.Tasks; global using System.Collections.Generic; global using System.Text.Json.Serialization; global using Orleans.Runtime; global using Orleans.Serialization; global using Orleans.Configuration; global using Microsoft.Azure.Cosmos; global using Microsoft.Azure.Cosmos.Linq; global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Logging; ================================================ FILE: src/Azure/Shared/Storage/AzureBlobUtils.cs ================================================ using System; using System.Linq; using System.Text.RegularExpressions; #if ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// General utility functions related to Azure Blob storage. /// internal static partial class AzureBlobUtils { [GeneratedRegex("^[a-z0-9]+(-[a-z0-9]+)*$", RegexOptions.ExplicitCapture | RegexOptions.Singleline | RegexOptions.CultureInvariant)] private static partial Regex ContainerNameRegex(); internal static void ValidateContainerName(string containerName) { if (string.IsNullOrWhiteSpace(containerName) || containerName.Length < 3 || containerName.Length > 63 || !ContainerNameRegex().IsMatch(containerName)) { throw new ArgumentException("Invalid container name", nameof(containerName)); } } internal static void ValidateBlobName(string blobName) { if (string.IsNullOrWhiteSpace(blobName) || blobName.Length > 1024 || blobName.Count(c => c == '/') >= 254) { throw new ArgumentException("Invalid blob name", nameof(blobName)); } } } } ================================================ FILE: src/Azure/Shared/Storage/AzureStorageOperationOptions.cs ================================================ using System; using System.Threading.Tasks; using Azure; using Azure.Core; using Azure.Data.Tables; using Orleans.Runtime; #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { public class AzureStorageOperationOptions { private TableServiceClient _tableServiceClient; /// /// Table name for Azure Storage /// public virtual string TableName { get; set; } /// /// Azure Storage Policy Options /// public AzureStoragePolicyOptions StoragePolicyOptions { get; } = new AzureStoragePolicyOptions(); /// /// Options to be used when configuring the table storage client, or to use the default options. /// public TableClientOptions ClientOptions { get; set; } /// /// The delegate used to create a instance. /// internal Func> CreateClient { get; private set; } /// /// Gets or sets the client used to access the Azure Table Service. /// public TableServiceClient TableServiceClient { get => _tableServiceClient; set { _tableServiceClient = value; CreateClient = () => Task.FromResult(value); } } /// /// Configures the using a connection string. /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString)); TableServiceClient = new TableServiceClient(connectionString, ClientOptions); } /// /// Configures the using an authenticated service URI. /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(Uri serviceUri) { if (serviceUri is null) throw new ArgumentNullException(nameof(serviceUri)); TableServiceClient = new TableServiceClient(serviceUri, ClientOptions); } /// /// Configures the using the provided callback. /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(Func> createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(Uri serviceUri, TokenCredential tokenCredential) { TableServiceClient = new TableServiceClient(serviceUri, tokenCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(Uri serviceUri, AzureSasCredential azureSasCredential) { TableServiceClient = new TableServiceClient(serviceUri, azureSasCredential, ClientOptions); } /// /// Configures the using an authenticated service URI and a . /// [Obsolete($"Set the {nameof(TableServiceClient)} property directly.")] public void ConfigureTableServiceClient(Uri serviceUri, TableSharedKeyCredential sharedKeyCredential) { TableServiceClient = new TableServiceClient(serviceUri, sharedKeyCredential, ClientOptions); } internal void Validate(string name) { if (CreateClient is null) { throw new OrleansConfigurationException($"No credentials specified. Use the {GetType().Name}.{nameof(ConfigureTableServiceClient)} method to configure the Azure Table Service client."); } try { AzureTableUtils.ValidateTableName(TableName); } catch (Exception ex) { throw GetException($"{nameof(TableName)} is not valid.", ex); } Exception GetException(string message, Exception inner = null) => new OrleansConfigurationException($"Configuration for {GetType().Name} {name} is invalid. {message}", inner); } } public class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { Options = options; Name = name; } public TOptions Options { get; } public string Name { get; } public virtual void ValidateConfiguration() { Options.Validate(Name); } } } ================================================ FILE: src/Azure/Shared/Storage/AzureStoragePolicyOptions.cs ================================================ using System; using System.Threading; #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { public class AzureStoragePolicyOptions { private TimeSpan? creationTimeout; private TimeSpan? operationTimeout; public int MaxBulkUpdateRows { get; set; } = 100; public int MaxCreationRetries { get; set; } = 60; public int MaxOperationRetries { get; set; } = 5; public TimeSpan PauseBetweenCreationRetries { get; set; } = TimeSpan.FromSeconds(1); public TimeSpan PauseBetweenOperationRetries { get; set; } = TimeSpan.FromMilliseconds(100); public TimeSpan CreationTimeout { get => this.creationTimeout ?? TimeSpan.FromMilliseconds(this.PauseBetweenCreationRetries.TotalMilliseconds * this.MaxCreationRetries * 3); set => SetIfValidTimeout(ref this.creationTimeout, value, nameof(CreationTimeout)); } public TimeSpan OperationTimeout { get => this.operationTimeout ?? TimeSpan.FromMilliseconds(this.PauseBetweenOperationRetries.TotalMilliseconds * this.MaxOperationRetries * 6); set => SetIfValidTimeout(ref this.operationTimeout, value, nameof(OperationTimeout)); } private static void SetIfValidTimeout(ref TimeSpan? field, TimeSpan value, string propertyName) { if (value > TimeSpan.Zero || value.Equals(Timeout.InfiniteTimeSpan)) { field = value; } else { throw new ArgumentNullException(propertyName); } } } } ================================================ FILE: src/Azure/Shared/Storage/AzureTableDataManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Azure; using Azure.Data.Tables; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Runtime; using LogLevel = Microsoft.Extensions.Logging.LogLevel; // // Number of #ifs can be reduced (or removed), once we separate test projects by feature/area, otherwise we are ending up with ambigous types and build errors. // #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Utility class to encapsulate row-based access to Azure table storage. /// /// Table data entry used by this table / manager. internal partial class AzureTableDataManager where T : class, ITableEntity { private readonly AzureStorageOperationOptions options; /// Name of the table this instance is managing. public string TableName { get; } /// Logger for this table manager instance. protected internal ILogger Logger { get; } public AzureStoragePolicyOptions StoragePolicyOptions { get; } public TableClient Table { get; private set; } /// /// Creates a new instance. /// /// Storage configuration. /// Logger to use. public AzureTableDataManager(AzureStorageOperationOptions options, ILogger logger) { this.options = options ?? throw new ArgumentNullException(nameof(options)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); TableName = options.TableName ?? throw new ArgumentNullException(nameof(options.TableName)); StoragePolicyOptions = options.StoragePolicyOptions ?? throw new ArgumentNullException(nameof(options.StoragePolicyOptions)); AzureTableUtils.ValidateTableName(TableName); } /// /// Connects to, or creates and initializes a new Azure table if it does not already exist. /// /// Completion promise for this operation. public async Task InitTableAsync() { const string operation = "InitTable"; var startTime = DateTime.UtcNow; try { TableServiceClient tableCreationClient = await GetCloudTableCreationClientAsync(); var table = tableCreationClient.GetTableClient(TableName); var response = await table.CreateIfNotExistsAsync(); var alreadyExisted = response.GetRawResponse().Status == (int)HttpStatusCode.Conflict; LogInfoTableCreation(Logger, alreadyExisted ? "Attached to" : "Created", TableName); Table = table; } catch (TimeoutException te) { LogErrorTableCreationInTimeout(Logger, te, StoragePolicyOptions.CreationTimeout); throw new OrleansException($"Unable to create or connect to the Azure table in {StoragePolicyOptions.CreationTimeout}", te); } catch (Exception exc) { LogErrorTableCreation(Logger, exc, TableName); throw; } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Deletes the Azure table. /// /// Completion promise for this operation. public async Task DeleteTableAsync() { const string operation = "DeleteTable"; var startTime = DateTime.UtcNow; try { var tableCreationClient = await GetCloudTableCreationClientAsync(); var response = await tableCreationClient.DeleteTableAsync(TableName); if (response.Status == 204) { LogInfoTableDeletion(Logger, TableName); } } catch (Exception exc) { LogErrorTableDeletion(Logger, exc, TableName); throw; } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Deletes all entities the Azure table. /// /// Completion promise for this operation. public async Task ClearTableAsync() { var items = await ReadAllTableEntriesAsync(); IEnumerable work = items.GroupBy(item => item.Item1.PartitionKey) .SelectMany(partition => partition.ToBatch(this.StoragePolicyOptions.MaxBulkUpdateRows)) .Select(batch => DeleteTableEntriesAsync(batch.ToList())); await Task.WhenAll(work); } /// /// Create a new data entry in the Azure table (insert new, not update existing). /// Fails if the data already exists. /// /// Data to be inserted into the table. /// Value promise with new Etag for this data entry after completing this storage operation. public async Task CreateTableEntryAsync(T data) { const string operation = "CreateTableEntry"; var startTime = DateTime.UtcNow; LogTraceTableEntryCreation(Logger, TableName, data); try { try { // Presumably FromAsync(BeginExecute, EndExecute) has a slightly better performance then CreateIfNotExistsAsync. var opResult = await Table.AddEntityAsync(data); return opResult.Headers.ETag.GetValueOrDefault().ToString(); } catch (Exception exc) { CheckAlertWriteError(operation, data, null, exc); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Inserts a data entry in the Azure table: creates a new one if does not exists or overwrites (without eTag) an already existing version (the "update in place" semantics). /// /// Data to be inserted or replaced in the table. /// Value promise with new Etag for this data entry after completing this storage operation. public async Task UpsertTableEntryAsync(T data) { const string operation = "UpsertTableEntry"; var startTime = DateTime.UtcNow; LogTraceTableEntry(Logger, operation, data, TableName); try { try { var opResult = await Table.UpsertEntityAsync(data); return opResult.Headers.ETag.GetValueOrDefault().ToString(); } catch (Exception exc) { LogWarningUpsertTableEntry(Logger, exc, data, TableName); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Inserts a data entry in the Azure table: creates a new one if does not exists /// /// Data to be inserted or replaced in the table. /// Value promise with new Etag for this data entry after completing this storage operation. public async Task<(bool isSuccess, string eTag)> InsertTableEntryAsync(T data) { const string operation = "InsertTableEntry"; var startTime = DateTime.UtcNow; LogTraceTableEntry(Logger, operation, data, TableName); try { try { var opResult = await Table.AddEntityAsync(data); return (true, opResult.Headers.ETag.GetValueOrDefault().ToString()); } catch (RequestFailedException storageException) when (storageException.Status == (int)HttpStatusCode.Conflict) { return (false, null); } catch (Exception exc) { LogWarningInsertTableEntry(Logger, exc, data, TableName); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Merges a data entry in the Azure table. /// /// Data to be merged in the table. /// ETag to apply. /// Value promise with new Etag for this data entry after completing this storage operation. internal Task MergeTableEntryAsync(T data, string eTag) => MergeTableEntryAsync(data, new ETag(eTag)); /// /// Merges a data entry in the Azure table. /// /// Data to be merged in the table. /// ETag to apply. /// Value promise with new Etag for this data entry after completing this storage operation. internal async Task MergeTableEntryAsync(T data, ETag eTag) { const string operation = "MergeTableEntry"; var startTime = DateTime.UtcNow; LogTraceTableEntry(Logger, operation, data, TableName); try { try { // Merge requires an ETag (which may be the '*' wildcard). data.ETag = eTag; var opResult = await Table.UpdateEntityAsync(data, data.ETag, TableUpdateMode.Merge); return opResult.Headers.ETag.GetValueOrDefault().ToString(); } catch (Exception exc) { LogWarningMergeTableEntry(Logger, exc, data, TableName); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Updates a data entry in the Azure table: updates an already existing data in the table, by using eTag. /// Fails if the data does not already exist or of eTag does not match. /// /// Data to be updated into the table. /// /// ETag to use. /// Value promise with new Etag for this data entry after completing this storage operation. public Task UpdateTableEntryAsync(T data, string dataEtag) => UpdateTableEntryAsync(data, new ETag(dataEtag)); /// /// Updates a data entry in the Azure table: updates an already existing data in the table, by using eTag. /// Fails if the data does not already exist or of eTag does not match. /// /// Data to be updated into the table. /// /// ETag to use. /// Value promise with new Etag for this data entry after completing this storage operation. public async Task UpdateTableEntryAsync(T data, ETag dataEtag) { const string operation = "UpdateTableEntryAsync"; var startTime = DateTime.UtcNow; LogTraceTableEntry(Logger, operation, data, TableName); try { try { data.ETag = dataEtag; var opResult = await Table.UpdateEntityAsync(data, data.ETag, TableUpdateMode.Replace); //The ETag of data is needed in further operations. return opResult.Headers.ETag.GetValueOrDefault().ToString(); } catch (Exception exc) { CheckAlertWriteError(operation, data, null, exc); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Deletes an already existing data in the table, by using eTag. /// Fails if the data does not already exist or if eTag does not match. /// /// Data entry to be deleted from the table. /// ETag to use. /// Completion promise for this storage operation. public Task DeleteTableEntryAsync(T data, string eTag) => DeleteTableEntryAsync(data, new ETag(eTag)); /// /// Deletes an already existing data in the table, by using eTag. /// Fails if the data does not already exist or if eTag does not match. /// /// Data entry to be deleted from the table. /// ETag to use. /// Completion promise for this storage operation. public async Task DeleteTableEntryAsync(T data, ETag eTag) { const string operation = "DeleteTableEntryAsync"; var startTime = DateTime.UtcNow; LogTraceTableEntry(Logger, operation, data, TableName); try { data.ETag = eTag; try { var response = await Table.DeleteEntityAsync(data.PartitionKey, data.RowKey, data.ETag); if (response is { Status: 404 }) { throw new RequestFailedException(response.Status, "Resource not found", response.ReasonPhrase, null); } } catch (Exception exc) { LogWarningDeleteTableEntry(Logger, exc, data, TableName); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Read a single table entry from the storage table. /// /// The partition key for the entry. /// The row key for the entry. /// Value promise for tuple containing the data entry and its corresponding etag. public async Task<(T Entity, string ETag)> ReadSingleTableEntryAsync(string partitionKey, string rowKey) { const string operation = "ReadSingleTableEntryAsync"; var startTime = DateTime.UtcNow; LogTraceTableOperation(Logger, operation, TableName, partitionKey, rowKey); try { try { var result = await Table.GetEntityIfExistsAsync(partitionKey, rowKey); if (result.HasValue) { //The ETag of data is needed in further operations. return (result.Value, result.Value.ETag.ToString()); } } catch (RequestFailedException exception) { if (!AzureTableUtils.TableStorageDataNotFound(exception)) { throw; } } LogDebugTableEntryNotFound(Logger, partitionKey, rowKey); return (default, default); // No data } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Read all entries in one partition of the storage table. /// NOTE: This could be an expensive and slow operation for large table partitions! /// /// The key for the partition to be searched. /// Enumeration of all entries in the specified table partition. public Task> ReadAllTableEntriesForPartitionAsync(string partitionKey) { string query = TableClient.CreateQueryFilter($"PartitionKey eq {partitionKey}"); return ReadTableEntriesAndEtagsAsync(query); } /// /// Read all entries in the table. /// NOTE: This could be a very expensive and slow operation for large tables! /// /// Enumeration of all entries in the table. public Task> ReadAllTableEntriesAsync() { return ReadTableEntriesAndEtagsAsync(null); } /// /// Deletes a set of already existing data entries in the table, by using eTag. /// Fails if the data does not already exist or if eTag does not match. /// /// Data entries and their corresponding etags to be deleted from the table. /// Completion promise for this storage operation. public async Task DeleteTableEntriesAsync(List<(T Entity, string ETag)> collection) { const string operation = "DeleteTableEntries"; var startTime = DateTime.UtcNow; LogTraceTableEntries(Logger, operation, new(collection), TableName); if (collection == null) throw new ArgumentNullException(nameof(collection)); if (collection.Count > this.StoragePolicyOptions.MaxBulkUpdateRows) { throw new ArgumentOutOfRangeException(nameof(collection), collection.Count, "Too many rows for bulk delete - max " + this.StoragePolicyOptions.MaxBulkUpdateRows); } if (collection.Count == 0) { return; } try { var entityBatch = new List(); foreach (var tuple in collection) { T item = tuple.Entity; item.ETag = new ETag(tuple.ETag); entityBatch.Add(new TableTransactionAction(TableTransactionActionType.Delete, item, item.ETag)); } try { _ = await Table.SubmitTransactionAsync(entityBatch); } catch (Exception exc) { LogWarningDeleteTableEntries(Logger, exc, new(collection), TableName); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Read data entries and their corresponding eTags from the Azure table. /// /// Filter string to use for querying the table and filtering the results. /// Enumeration of entries in the table which match the query condition. public async Task> ReadTableEntriesAndEtagsAsync(string filter) { const string operation = "ReadTableEntriesAndEtags"; var startTime = DateTime.UtcNow; try { try { async Task> executeQueryHandleContinuations() { var list = new List<(T, string)>(); var results = Table.QueryAsync(filter); await foreach (var value in results) { list.Add((value, value.ETag.ToString())); } return list; } #if !ORLEANS_TRANSACTIONS IBackoffProvider backoff = new FixedBackoff(this.StoragePolicyOptions.PauseBetweenOperationRetries); List<(T, string)> results = await AsyncExecutorWithRetries.ExecuteWithRetries( counter => executeQueryHandleContinuations(), this.StoragePolicyOptions.MaxOperationRetries, (exc, counter) => AzureTableUtils.AnalyzeReadException(exc.GetBaseException(), counter, TableName, Logger), this.StoragePolicyOptions.OperationTimeout, backoff); #else List<(T, string)> results = await executeQueryHandleContinuations(); #endif // Data was read successfully if we got to here return results; } catch (Exception exc) { // Out of retries... if (!AzureTableUtils.TableStorageDataNotFound(exc)) { LogWarningReadTable(Logger, exc, TableName); } throw new OrleansException($"Failed to read Azure Storage table {TableName}", exc); } } finally { CheckAlertSlowAccess(startTime, operation); } } /// /// Inserts a set of new data entries into the table. /// Fails if the data does already exists. /// /// Data entries to be inserted into the table. /// Completion promise for this storage operation. public async Task BulkInsertTableEntries(IReadOnlyCollection collection) { const string operation = "BulkInsertTableEntries"; if (collection == null) throw new ArgumentNullException(nameof(collection)); if (collection.Count > this.StoragePolicyOptions.MaxBulkUpdateRows) { throw new ArgumentOutOfRangeException(nameof(collection), collection.Count, "Too many rows for bulk update - max " + this.StoragePolicyOptions.MaxBulkUpdateRows); } if (collection.Count == 0) { return; } var startTime = DateTime.UtcNow; LogTraceTableEntriesCount(Logger, operation, collection.Count, TableName); try { var entityBatch = new List(collection.Count); foreach (T entry in collection) { entityBatch.Add(new TableTransactionAction(TableTransactionActionType.Add, entry)); } try { await Table.SubmitTransactionAsync(entityBatch); } catch (Exception exc) { LogWarningBulkInsertTableEntries(Logger, exc, collection.Count, TableName); } } finally { CheckAlertSlowAccess(startTime, operation); } } internal async Task<(string, string)> InsertTwoTableEntriesConditionallyAsync(T data1, T data2, string data2Etag) { const string operation = "InsertTableEntryConditionally"; string data2Str = data2 == null ? "null" : data2.ToString(); var startTime = DateTime.UtcNow; LogTraceTableEntries(Logger, operation, data1, data2Str, TableName); try { try { data2.ETag = new ETag(data2Etag); var opResults = await Table.SubmitTransactionAsync(new TableTransactionAction[] { new TableTransactionAction(TableTransactionActionType.Add, data1), new TableTransactionAction(TableTransactionActionType.UpdateReplace, data2, data2.ETag) }); //The batch results are returned in order of execution, //see reference at https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storage.table.cloudtable.executebatch.aspx. //The ETag of data is needed in further operations. var resultETag1 = opResults.Value[0].Headers.ETag.GetValueOrDefault().ToString(); var resultETag2 = opResults.Value[1].Headers.ETag.GetValueOrDefault().ToString(); return (resultETag1, resultETag2); } catch (Exception exc) { CheckAlertWriteError(operation, data1, data2Str, exc); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } internal async Task<(string, string)> UpdateTwoTableEntriesConditionallyAsync(T data1, string data1Etag, T data2, string data2Etag) { const string operation = "UpdateTableEntryConditionally"; string data2Str = data2 == null ? "null" : data2.ToString(); var startTime = DateTime.UtcNow; LogTraceTableEntries(Logger, operation, data1, data2Str, TableName); try { try { data1.ETag = new ETag(data1Etag); var entityBatch = new List(2); entityBatch.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, data1, data1.ETag)); if (data2 != null && data2Etag != null) { data2.ETag = new ETag(data2Etag); entityBatch.Add(new TableTransactionAction(TableTransactionActionType.UpdateReplace, data2, data2.ETag)); } var opResults = await Table.SubmitTransactionAsync(entityBatch); //The batch results are returned in order of execution, //see reference at https://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storage.table.cloudtable.executebatch.aspx. //The ETag of data is needed in further operations. var resultETag1 = opResults.Value[0].Headers.ETag.GetValueOrDefault().ToString(); var resultETag2 = opResults.Value[1].Headers.ETag.GetValueOrDefault().ToString(); return (resultETag1, resultETag2); } catch (Exception exc) { CheckAlertWriteError(operation, data1, data2Str, exc); throw; } } finally { CheckAlertSlowAccess(startTime, operation); } } private async ValueTask GetCloudTableCreationClientAsync() { try { return await options.CreateClient(); } catch (Exception exc) { LogErrorTableServiceClientCreation(Logger, exc); throw; } } private void CheckAlertWriteError(string operation, object data1, string data2, Exception exc) { HttpStatusCode httpStatusCode; if (AzureTableUtils.EvaluateException(exc, out httpStatusCode, out _) && AzureTableUtils.IsContentionError(httpStatusCode)) { // log at Verbose, since failure on conditional is not not an error. Will analyze and warn later, if required. LogWarningTableWrite(Logger, operation, TableName, data1 ?? "null", data2 ?? "null"); } else { LogErrorTableWrite(Logger, exc, operation, TableName, data1); } } private void CheckAlertSlowAccess(DateTime startOperation, string operation) { var timeSpan = DateTime.UtcNow - startOperation; if (timeSpan > this.StoragePolicyOptions.OperationTimeout) { LogWarningSlowAccess(Logger, operation, TableName, timeSpan); } } [LoggerMessage( Level = LogLevel.Information, EventId = (int)Utilities.ErrorCode.AzureTable_01, Message = "{Action} Azure storage table {TableName}" )] private static partial void LogInfoTableCreation(ILogger logger, string action, string tableName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)Utilities.ErrorCode.AzureTable_TableNotCreated, Message = "Unable to create or connect to the Azure table in {CreationTimeout}" )] private static partial void LogErrorTableCreationInTimeout(ILogger logger, TimeoutException exception, TimeSpan creationTimeout); [LoggerMessage( Level = LogLevel.Error, EventId = (int)Utilities.ErrorCode.AzureTable_02, Message = "Could not initialize connection to storage table {TableName}" )] private static partial void LogErrorTableCreation(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Information, EventId = (int)Utilities.ErrorCode.AzureTable_03, Message = "Deleted Azure storage table {TableName}" )] private static partial void LogInfoTableDeletion(ILogger logger, string tableName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)Utilities.ErrorCode.AzureTable_04, Message = "Could not delete storage table {TableName}" )] private static partial void LogErrorTableDeletion(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "Creating {TableName} table entry: {Data}" )] private static partial void LogTraceTableEntryCreation(ILogger logger, string tableName, T data); [LoggerMessage( Level = LogLevel.Trace, Message = "{Operation} entry {Data} into table {TableName}" )] private static partial void LogTraceTableEntry(ILogger logger, string operation, T data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_06, Message = "Intermediate error upserting entry {Data} to the table {TableName}" )] private static partial void LogWarningUpsertTableEntry(ILogger logger, Exception exception, T data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_06, Message = "Intermediate error inserting entry {Data} to the table {TableName}" )] private static partial void LogWarningInsertTableEntry(ILogger logger, Exception exception, T data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_07, Message = "Intermediate error merging entry {Data} to the table {TableName}" )] private static partial void LogWarningMergeTableEntry(ILogger logger, Exception exception, T data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_08, Message = "Intermediate error deleting entry {Data} from the table {TableName}." )] private static partial void LogWarningDeleteTableEntry(ILogger logger, Exception exception, T data, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "{Operation} table {TableName} partitionKey {PartitionKey} rowKey {RowKey}" )] private static partial void LogTraceTableOperation(ILogger logger, string operation, string tableName, string partitionKey, string rowKey); [LoggerMessage( Level = LogLevel.Debug, Message = "Could not find table entry for PartitionKey={PartitionKey} RowKey={RowKey}" )] private static partial void LogDebugTableEntryNotFound(ILogger logger, string partitionKey, string rowKey); private readonly struct CollectionLogEntry(List<(T Entity, string ETag)> collection) { public override string ToString() => Utils.EnumerableToString(collection); } [LoggerMessage( Level = LogLevel.Trace, Message = "{Operation} entries: {Data} table {TableName}" )] private static partial void LogTraceTableEntries(ILogger logger, string operation, CollectionLogEntry data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_08, Message = "Intermediate error deleting entries {Data} from the table {TableName}." )] private static partial void LogWarningDeleteTableEntries(ILogger logger, Exception exception, CollectionLogEntry data, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_09, Message = "Failed to read Azure Storage table {TableName}" )] private static partial void LogWarningReadTable(ILogger logger, Exception exception, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "{Operation} {Count} entries table {TableName}" )] private static partial void LogTraceTableEntriesCount(ILogger logger, string operation, int count, string tableName); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_37, Message = "Intermediate error bulk inserting {Count} entries in the table {TableName}" )] private static partial void LogWarningBulkInsertTableEntries(ILogger logger, Exception exception, int count, string tableName); [LoggerMessage( Level = LogLevel.Trace, Message = "{Operation} data1 {Data1} data2 {Data2} table {TableName}" )] private static partial void LogTraceTableEntries(ILogger logger, string operation, T data1, string data2, string tableName); [LoggerMessage( Level = LogLevel.Error, Message = "Error creating TableServiceClient." )] private static partial void LogErrorTableServiceClientCreation(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)Utilities.ErrorCode.AzureTable_13, Message = "Intermediate Azure table write error {Operation} to table {TableName} data1 {Data1} data2 {Data2}" )] private static partial void LogWarningTableWrite(ILogger logger, string operation, string tableName, object data1, object data2); [LoggerMessage( Level = LogLevel.Error, EventId = (int)Utilities.ErrorCode.AzureTable_14, Message = "Azure table access write error {Operation} to table {TableName} entry {Data1}" )] private static partial void LogErrorTableWrite(ILogger logger, Exception exception, string operation, string tableName, object data1); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_15, Message = "Slow access to Azure Table {TableName} for {Operation}, which took {Duration}" )] private static partial void LogWarningSlowAccess(ILogger logger, string tableName, string operation, TimeSpan duration); } internal static class TableDataManagerInternalExtensions { internal static IEnumerable> ToBatch(this IEnumerable source, int size) { using (IEnumerator enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) yield return Take(enumerator, size); } private static IEnumerable Take(IEnumerator source, int size) { int i = 0; do yield return source.Current; while (++i < size && source.MoveNext()); } } } ================================================ FILE: src/Azure/Shared/Storage/AzureTableDefaultPolicies.cs ================================================ using System; using System.Diagnostics; using Microsoft.Azure.Cosmos.Table; // // Number of #ifs can be reduced (or removed), once we separate test projects by feature/area, otherwise we are ending up with ambigous types and build errors. // #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Utility class for default retry / timeout settings for Azure storage. /// /// /// These functions are mostly intended for internal usage by Orleans runtime, but due to certain assembly packaging constraints this class needs to have public visibility. /// public static class AzureTableDefaultPolicies { public static int MaxTableCreationRetries { get; private set; } public static int MaxTableOperationRetries { get; private set; } public static int MaxBusyRetries { get; internal set; } public static TimeSpan PauseBetweenTableCreationRetries { get; private set; } public static TimeSpan PauseBetweenTableOperationRetries { get; private set; } public static TimeSpan PauseBetweenBusyRetries { get; private set; } public static TimeSpan TableCreationTimeout { get; private set; } public static TimeSpan TableOperationTimeout { get; private set; } public static TimeSpan BusyRetriesTimeout { get; private set; } public static IRetryPolicy TableCreationRetryPolicy { get; private set; } public static IRetryPolicy TableOperationRetryPolicy { get; private set; } public const int MAX_BULK_UPDATE_ROWS = 100; static AzureTableDefaultPolicies() { MaxTableCreationRetries = 60; PauseBetweenTableCreationRetries = (!Debugger.IsAttached) ? TimeSpan.FromSeconds(1) : TimeSpan.FromSeconds(100); MaxTableOperationRetries = 5; PauseBetweenTableOperationRetries = (!Debugger.IsAttached) ? TimeSpan.FromMilliseconds(100) : TimeSpan.FromSeconds(10); MaxBusyRetries = 120; PauseBetweenBusyRetries = (!Debugger.IsAttached) ? TimeSpan.FromMilliseconds(500) : TimeSpan.FromSeconds(5); TableCreationRetryPolicy = new LinearRetry(PauseBetweenTableCreationRetries, MaxTableCreationRetries); // 60 x 1s TableCreationTimeout = TimeSpan.FromMilliseconds(PauseBetweenTableCreationRetries.TotalMilliseconds * MaxTableCreationRetries * 3); // 3 min TableOperationRetryPolicy = new LinearRetry(PauseBetweenTableOperationRetries, MaxTableOperationRetries); // 5 x 100ms TableOperationTimeout = TimeSpan.FromMilliseconds(PauseBetweenTableOperationRetries.TotalMilliseconds * MaxTableOperationRetries *6); // 3 sec BusyRetriesTimeout = TimeSpan.FromMilliseconds(PauseBetweenBusyRetries.TotalMilliseconds * MaxBusyRetries); // 1 minute } } } ================================================ FILE: src/Azure/Shared/Storage/AzureTableUtils.cs ================================================ using System; using System.Net; using System.Text.RegularExpressions; using Azure; using Azure.Data.Tables; using Azure.Data.Tables.Models; using Microsoft.Extensions.Logging; using LogLevel = Microsoft.Extensions.Logging.LogLevel; // // Number of #ifs can be reduced (or removed), once we separate test projects by feature/area, otherwise we are ending up with ambiguous types and build errors. // #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage #else // No default namespace intentionally to cause compile errors if something is not defined #endif { /// /// Constants related to Azure Table storage (also applies to Table endpoints in Cosmos DB). /// internal static class AzureTableConstants { public const string ANY_ETAG = "*"; public const string PKProperty = nameof(ITableEntity.PartitionKey); public const string RKProperty = nameof(ITableEntity.RowKey); public const int MaxBatchSize = 100; } /// /// General utility functions related to Azure Table storage (also applies to Table endpoints in Cosmos DB). /// internal static partial class AzureTableUtils { /// /// ETag of value "*" to match any etag for conditional table operations (update, merge, delete). /// public const string ANY_ETAG = AzureTableConstants.ANY_ETAG; /// /// Inspect an exception returned from Azure storage libraries to check whether it means that attempt was made to read some data that does not exist in storage table. /// /// Exception that was returned by Azure storage library operation /// True if this exception means the data being read was not present in Azure table storage public static bool TableStorageDataNotFound(Exception exc) { if (EvaluateException(exc, out var httpStatusCode, out var restStatus, true)) { if (IsNotFoundError(httpStatusCode)) { return true; } return TableErrorCode.EntityNotFound.ToString().Equals(restStatus, StringComparison.OrdinalIgnoreCase) || TableErrorCode.ResourceNotFound.ToString().Equals(restStatus, StringComparison.OrdinalIgnoreCase); } return false; } /// /// Extract REST error code from DataServiceClientException or DataServiceQueryException /// /// Exception to be inspected. /// Returns REST error code if found, otherwise null private static string ExtractRestErrorCode(Exception exc) { while (exc != null && exc is not RequestFailedException) { exc = exc?.InnerException?.GetBaseException(); } return (exc as RequestFailedException)?.ErrorCode; } /// /// Examine a storage exception, and if applicable extracts the HTTP status code, and REST error code if getRESTErrors=true. /// /// Exception to be examined. /// Output HTTP status code if applicable, otherwise HttpStatusCode.Unused (306) /// When getRESTErrors=true, will output REST error code if applicable, otherwise null /// Whether REST error code should also be examined / extracted. /// Returns true if HTTP status code and REST error were extracted. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public static bool EvaluateException( Exception e, out HttpStatusCode httpStatusCode, out string restStatus, bool getRESTErrors = false) { httpStatusCode = HttpStatusCode.Unused; restStatus = null; try { while (e != null) { if (e is RequestFailedException ste) { httpStatusCode = (HttpStatusCode)ste.Status; if (getRESTErrors) { restStatus = ExtractRestErrorCode(ste); } return true; } e = e.InnerException; } } catch { // if we failed to parse the exception, treat it as if we could not EvaluateException. return false; } return false; } /// /// Returns true if the specified HTTP status / error code is returned in a transient / retriable error condition /// /// HTTP error code value /// REST error code value /// true if this is a transient / retriable error condition public static bool IsRetriableHttpError(HttpStatusCode httpStatusCode, string restStatusCode) { // Note: We ignore the 20X values as they are successful outcomes, not errors return ( httpStatusCode == HttpStatusCode.RequestTimeout /* 408 */ || httpStatusCode == HttpStatusCode.BadGateway /* 502 */ || httpStatusCode == HttpStatusCode.ServiceUnavailable /* 503 */ || httpStatusCode == HttpStatusCode.GatewayTimeout /* 504 */ || (httpStatusCode == HttpStatusCode.InternalServerError /* 500 */ && !string.IsNullOrEmpty(restStatusCode) && TableErrorCode.OperationTimedOut.ToString().Equals(restStatusCode, StringComparison.OrdinalIgnoreCase)) ); } /// /// Check whether a HTTP status code returned from a REST call might be due to a (temporary) storage contention error. /// /// HTTP status code to be examined. /// Returns true if the HTTP status code is due to storage contention. public static bool IsContentionError(HttpStatusCode httpStatusCode) { // Status and Error Codes // http://msdn.microsoft.com/en-us/library/dd179382.aspx if (httpStatusCode == HttpStatusCode.PreconditionFailed) return true; if (httpStatusCode == HttpStatusCode.Conflict) return true; //Primary key violation. The app is trying to insert an entity, but there’s an entity on the table with the same values for PartitionKey and RowKey properties on the entity being inserted. if (httpStatusCode == HttpStatusCode.NotFound) return true; if (httpStatusCode == HttpStatusCode.NotImplemented) return true; // New table: Azure table schema not yet initialized, so need to do first create return false; } /// /// Check whether a HTTP status code returned from a REST call might be due to a (temporary) storage contention error. /// /// HTTP status code to be examined. /// Returns true if the HTTP status code is due to storage contention. public static bool IsNotFoundError(HttpStatusCode httpStatusCode) { // Status and Error Codes // http://msdn.microsoft.com/en-us/library/dd179382.aspx if (httpStatusCode == HttpStatusCode.NotFound) return true; if (httpStatusCode == HttpStatusCode.NotImplemented) return true; // New table: Azure table schema not yet initialized, so need to do first create return false; } [GeneratedRegex("^[A-Za-z][A-Za-z0-9]{2,62}$")] private static partial Regex TableNameRegex(); internal static void ValidateTableName(string tableName) { // Regular expression from documentation: https://learn.microsoft.com/rest/api/storageservices/understanding-the-table-service-data-model#table-names if (!TableNameRegex().IsMatch(tableName)) { throw new ArgumentException($"Table name \"{tableName}\" is invalid according to the following rules:" + " 1. Table names may contain only alphanumeric characters." + " 2. Table names cannot begin with a numeric character." + " 3. Table names must be from 3 to 63 characters long."); } } /// /// Remove any characters that can't be used in Azure PartitionKey or RowKey values. /// /// /// public static string SanitizeTableProperty(string key) { // Remove any characters that can't be used in Azure PartitionKey or RowKey values // http://www.jamestharpe.com/web-development/azure-table-service-character-combinations-disallowed-in-partitionkey-rowkey/ key = key .Replace('/', '_') // Forward slash .Replace('\\', '_') // Backslash .Replace('#', '_') // Pound sign .Replace('?', '_'); // Question mark if (key.Length >= 1024) throw new ArgumentException(string.Format("Key length {0} is too long to be an Azure table key. Key={1}", key.Length, key)); return key; } internal static bool AnalyzeReadException(Exception exc, int iteration, string tableName, ILogger logger) { bool isLastErrorRetriable; var we = exc as WebException; if (we != null) { isLastErrorRetriable = true; LogWarningIntermediateIssue(logger, exc, tableName, we.Status); } else { if (EvaluateException(exc, out var httpStatusCode, out var restStatus, true)) { if (nameof(TableErrorCode.ResourceNotFound).Equals(restStatus)) { LogDebugDataNotFound(logger, exc, tableName, iteration == 0 ? string.Empty : (" Repeat=" + iteration), httpStatusCode, restStatus); isLastErrorRetriable = false; } else { isLastErrorRetriable = IsRetriableHttpError(httpStatusCode, restStatus); LogWarningIntermediateIssueWithRestStatusCode(logger, exc, tableName, iteration == 0 ? "" : (" Repeat=" + iteration), isLastErrorRetriable, httpStatusCode, restStatus); } } else { LogErrorUnexpectedIssue(logger, exc, tableName); isLastErrorRetriable = false; } } return isLastErrorRetriable; } internal static string PrintStorageException(Exception exception) { if (exception is not RequestFailedException storeExc) { throw new ArgumentException($"Unexpected exception type {exception.GetType().FullName}"); } return $"Message = {storeExc.Message}, HTTP Status = {storeExc.Status}, HTTP Error Code = {storeExc.ErrorCode}."; } internal static string PointQuery(string partitionKey, string rowKey) { return TableClient.CreateQueryFilter($"(PartitionKey eq {partitionKey}) and (RowKey eq {rowKey})"); } internal static string RangeQuery(string partitionKey, string minRowKey, string maxRowKey) { return TableClient.CreateQueryFilter($"((PartitionKey eq {partitionKey}) and (RowKey ge {minRowKey})) and (RowKey le {maxRowKey})"); } [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_10, Message = "Intermediate issue reading Azure storage table {TableName}: HTTP status code={StatusCode}" )] private static partial void LogWarningIntermediateIssue(ILogger logger, Exception exception, string tableName, WebExceptionStatus statusCode); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)Utilities.ErrorCode.AzureTable_DataNotFound, Message = "DataNotFound reading Azure storage table {TableName}:{Retry} HTTP status code={StatusCode} REST status code={RESTStatusCode}" )] private static partial void LogDebugDataNotFound(ILogger logger, Exception exception, string tableName, string retry, HttpStatusCode statusCode, string restStatusCode); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)Utilities.ErrorCode.AzureTable_11, Message = "Intermediate issue reading Azure storage table {TableName}:{Retry} IsRetriable={IsLastErrorRetriable} HTTP status code={StatusCode} REST status code={RestStatusCode}" )] private static partial void LogWarningIntermediateIssueWithRestStatusCode(ILogger logger, Exception exception, string tableName, string retry, bool isLastErrorRetriable, HttpStatusCode statusCode, string restStatusCode); [LoggerMessage( Level = LogLevel.Error, EventId = (int)Utilities.ErrorCode.AzureTable_12, Message = "Unexpected issue reading Azure storage table {TableName}" )] private static partial void LogErrorUnexpectedIssue(ILogger logger, Exception exception, string tableName); } } ================================================ FILE: src/Azure/Shared/Utilities/ErrorCode.cs ================================================ using System.Diagnostics.CodeAnalysis; // // Number of #ifs can be reduced (or removed), once we separate test projects by feature/area, otherwise we are ending up with ambigous types and build errors. // #if ORLEANS_CLUSTERING namespace Orleans.Clustering.AzureStorage.Utilities #elif ORLEANS_PERSISTENCE namespace Orleans.Persistence.AzureStorage.Utilities #elif ORLEANS_REMINDERS_PROVIDER namespace Orleans.Reminders #elif ORLEANS_REMINDERS namespace Orleans.Reminders.AzureStorage.Utilities #elif ORLEANS_STREAMING namespace Orleans.Streaming.AzureStorage.Utilities #elif ORLEANS_EVENTHUBS namespace Orleans.Streaming.EventHubs.Utilities #elif TESTER_AZUREUTILS namespace Orleans.Tests.AzureUtils.Utilities #elif ORLEANS_TRANSACTIONS namespace Orleans.Transactions.AzureStorage.Utilities #elif ORLEANS_DIRECTORY namespace Orleans.GrainDirectory.AzureStorage.Utilities #else // No default namespace intentionally to cause compile errors if something is not defined #endif { [SuppressMessage("ReSharper", "InconsistentNaming")] internal enum ErrorCode { Runtime = 100000, AzureTableBase = Runtime + 800, AzureTable_01 = AzureTableBase + 1, AzureTable_02 = AzureTableBase + 2, AzureTable_03 = AzureTableBase + 3, AzureTable_04 = AzureTableBase + 4, AzureTable_06 = AzureTableBase + 6, AzureTable_07 = AzureTableBase + 7, AzureTable_08 = AzureTableBase + 8, AzureTable_09 = AzureTableBase + 9, AzureTable_10 = AzureTableBase + 10, AzureTable_11 = AzureTableBase + 11, AzureTable_12 = AzureTableBase + 12, AzureTable_13 = AzureTableBase + 13, AzureTable_14 = AzureTableBase + 14, AzureTable_15 = AzureTableBase + 15, AzureTable_17 = AzureTableBase + 17, AzureTable_18 = AzureTableBase + 18, AzureTable_19 = AzureTableBase + 19, AzureTable_37 = AzureTableBase + 37, // Azure storage provider related AzureTable_DataNotFound = AzureTableBase + 50, AzureTable_TableNotCreated = AzureTableBase + 51, } } ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/CassandraClusteringTable.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Cassandra; using Microsoft.Extensions.Options; using Orleans.Clustering.Cassandra.Hosting; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.Clustering.Cassandra; internal sealed class CassandraClusteringTable : IMembershipTable { private const string NotInitializedMessage = $"This instance has not been initialized. Ensure that {nameof(IMembershipTable.InitializeMembershipTable)} is called to initialize this instance before use."; private readonly ClusterOptions _clusterOptions; private readonly CassandraClusteringOptions _options; private readonly int? _ttlSeconds; private readonly IServiceProvider _serviceProvider; private ISession? _session; private OrleansQueries? _queries; private readonly string _identifier; public CassandraClusteringTable( IOptions clusterOptions, IOptions options, IOptions clusterMembershipOptions, IServiceProvider serviceProvider) { _clusterOptions = clusterOptions.Value; _options = options.Value; _identifier = $"{_clusterOptions.ServiceId}-{_clusterOptions.ClusterId}"; _serviceProvider = serviceProvider; _ttlSeconds = _options.GetCassandraTtlSeconds(clusterMembershipOptions.Value); } private ISession Session => _session ?? throw new InvalidOperationException(NotInitializedMessage); private OrleansQueries Queries => _queries ?? throw new InvalidOperationException(NotInitializedMessage); async Task IMembershipTable.InitializeMembershipTable(bool tryInitTableVersion) { _session = await _options.CreateSessionAsync(_serviceProvider); if (_session is null) { throw new InvalidOperationException($"Session created from configuration '{nameof(CassandraClusteringOptions)}' is null."); } _queries = await OrleansQueries.CreateInstance(_session); await _queries.EnsureTableExistsAsync(_options.InitializeRetryMaxDelay, _ttlSeconds); if (tryInitTableVersion) await _queries.EnsureClusterVersionExistsAsync(_options.InitializeRetryMaxDelay, _identifier); } async Task IMembershipTable.DeleteMembershipTableEntries(string clusterId) { if (string.Compare(clusterId, _clusterOptions.ClusterId, StringComparison.InvariantCultureIgnoreCase) != 0) { throw new ArgumentException( $"Cluster id {clusterId} does not match CassandraClusteringTable value of '{_clusterOptions.ClusterId}'.", nameof(clusterId)); } await Session.ExecuteAsync(await Queries.DeleteMembershipTableEntries(_identifier)); } async Task IMembershipTable.InsertRow(MembershipEntry entry, TableVersion tableVersion) { // Prevent duplicate rows var existingRow = await ((IMembershipTable)this).ReadRow(entry.SiloAddress); if (existingRow is not null) { if (existingRow.Version.Version >= tableVersion.Version) { return false; } if (existingRow.Members.Any(m => m.Item1.SiloAddress.Equals(entry.SiloAddress))) { return false; } } var query = await Session.ExecuteAsync(await Queries.InsertMembership(_identifier, entry, tableVersion.Version - 1)); return (bool)query.First()["[applied]"]; } async Task IMembershipTable.UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { var query = await Session.ExecuteAsync(await Queries.UpdateMembership(_identifier, entry, tableVersion.Version - 1)); return (bool)query.First()["[applied]"]; } private static MembershipEntry? GetMembershipEntry(Row row) { if (row["start_time"] == null) { return null; } var result = new MembershipEntry { SiloAddress = SiloAddress.New(new IPEndPoint(IPAddress.Parse((string)row["address"]), (int)row["port"]), (int)row["generation"]), SiloName = (string)row["silo_name"], HostName = (string)row["host_name"], Status = (SiloStatus)(int)row["status"], ProxyPort = (int)row["proxy_port"], StartTime = ((DateTimeOffset)row["start_time"]).UtcDateTime, IAmAliveTime = ((DateTimeOffset)row["i_am_alive_time"]).UtcDateTime }; var suspectingSilos = (string)row["suspect_times"]; if (!string.IsNullOrWhiteSpace(suspectingSilos)) { result.SuspectTimes = [ .. suspectingSilos.Split('|').Select(s => { var split = s.Split(','); return new Tuple(SiloAddress.FromParsableString(split[0]), LogFormatter.ParseDate(split[1])); }), ]; } return result; } private async Task GetMembershipTableData(RowSet rows) { var firstRow = rows.FirstOrDefault(); if (firstRow != null) { var version = (int)firstRow["version"]; var entries = new List>(); foreach (var row in new[] { firstRow }.Concat(rows)) { var entry = GetMembershipEntry(row); if (entry != null) { entries.Add(new Tuple(entry, string.Empty)); } } return new MembershipTableData(entries, new TableVersion(version, version.ToString())); } else { var result = (await Session.ExecuteAsync(await Queries.MembershipReadVersion(_identifier))).FirstOrDefault(); if (result is null) { return new MembershipTableData([], new TableVersion(0, "0")); } var version = (int)result["version"]; return new MembershipTableData([], new TableVersion(version, version.ToString())); } } async Task IMembershipTable.ReadAll() { return await GetMembershipTableData(await Session.ExecuteAsync(await Queries.MembershipReadAll(_identifier))); } async Task IMembershipTable.ReadRow(SiloAddress key) { return await GetMembershipTableData(await Session.ExecuteAsync(await Queries.MembershipReadRow(_identifier, key))); } async Task IMembershipTable.UpdateIAmAlive(MembershipEntry entry) { if (_ttlSeconds.HasValue) { // User has opted in to Cassandra TTL behavior for membership table rows, which means the entire row's data // has to be written back so each cell's TTL can be updated MembershipTableData existingRow = await ((IMembershipTable)this).ReadRow(entry.SiloAddress); await Session.ExecuteAsync(await Queries.UpdateIAmAliveTimeWithTtL( clusterIdentifier: _identifier, // The MembershipEntry given to this method by Orleans only contains the SiloAddress and new IAmAliveTime iAmAliveEntry: entry, existingEntry: existingRow.Members[0].Item1, existingVersion: existingRow.Version)); } else { await Session.ExecuteAsync(await Queries.UpdateIAmAliveTime(_identifier, entry)); } } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { var allEntries = (await Session.ExecuteAsync(await Queries.MembershipReadAll(_identifier))) .Select(GetMembershipEntry) .Where((MembershipEntry? e) => e is not null) .Cast(); foreach (var e in allEntries) { if (e is not { Status: SiloStatus.Active } && new DateTime(Math.Max(e.IAmAliveTime.Ticks, e.StartTime.Ticks), DateTimeKind.Utc) < beforeDate) { await Session.ExecuteAsync(await Queries.DeleteMembershipEntry(_identifier, e)); } } } } ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/CassandraGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Cassandra; using Microsoft.Extensions.Options; using Orleans.Clustering.Cassandra.Hosting; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Runtime; namespace Orleans.Clustering.Cassandra; internal sealed class CassandraGatewayListProvider : IGatewayListProvider { private const string NotInitializedMessage = $"This instance has not been initialized. Ensure that {nameof(IGatewayListProvider.InitializeGatewayListProvider)} is called to initialize this instance before use."; private readonly TimeSpan _maxStaleness; private readonly string _identifier; private readonly CassandraClusteringOptions _options; private readonly IServiceProvider _serviceProvider; private readonly int? _ttlSeconds; private ISession? _session; private OrleansQueries? _queries; private DateTime _cacheUntil; private List? _cachedResult; TimeSpan IGatewayListProvider.MaxStaleness => _maxStaleness; bool IGatewayListProvider.IsUpdatable => true; public CassandraGatewayListProvider( IOptions clusterOptions, IOptions gatewayOptions, IOptions options, IOptions clusterMembershipOptions, IServiceProvider serviceProvider) { _identifier = $"{clusterOptions.Value.ServiceId}-{clusterOptions.Value.ClusterId}"; _options = options.Value; _serviceProvider = serviceProvider; _maxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; _ttlSeconds = _options.GetCassandraTtlSeconds(clusterMembershipOptions.Value); } private ISession Session => _session ?? throw new InvalidOperationException(NotInitializedMessage); private OrleansQueries Queries => _queries ?? throw new InvalidOperationException(NotInitializedMessage); async Task IGatewayListProvider.InitializeGatewayListProvider() { _session = await _options.CreateSessionAsync(_serviceProvider); if (_session is null) { throw new InvalidOperationException($"Session created from configuration '{nameof(CassandraClusteringOptions)}' is null."); } _queries = await OrleansQueries.CreateInstance(_session); await _queries.EnsureTableExistsAsync(_options.InitializeRetryMaxDelay, _ttlSeconds); } async Task> IGatewayListProvider.GetGateways() { if (_cachedResult is not null && _cacheUntil > DateTime.UtcNow) { return [.. _cachedResult]; } var rows = await Session.ExecuteAsync(await Queries.GatewaysQuery(_identifier, (int)SiloStatus.Active)); var result = new List(); foreach (var row in rows) { result.Add(SiloAddress.New(new IPEndPoint(IPAddress.Parse((string)row["address"]), (int)row["proxy_port"]), (int)row["generation"]).ToGatewayUri()); } _cacheUntil = DateTime.UtcNow + _maxStaleness; _cachedResult = result; return [.. result]; } } ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/Hosting/CassandraClusteringOptions.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Cassandra; using Orleans.Configuration; namespace Orleans.Clustering.Cassandra.Hosting; /// /// Options for configuring Cassandra clustering. /// public class CassandraClusteringOptions { /// /// Optionally configure time-to-live behavior for the membership table row data in Cassandra itself, allowing /// defunct silo cleanup even if a cluster is no longer running. /// /// When this is true, CAN be null to enable /// Cassandra-only defunct silo cleanup. Either way, the Cassandra TTL will still be configured from the /// configured value. /// /// /// Initial implementation of https://github.com/dotnet/orleans/issues/9164 in that it only affects silo entries /// that are updated with IAmAlive and will not attempt to update, for instance, the entire membership table. It /// also will not affect membership tables that have already been created, since it uses the Cassandra table-level /// default_time_to_live. /// public bool UseCassandraTtl { get; set; } /// /// Specifies the maximum amount of time to wait after encountering /// contention during initialization before retrying. /// /// This is generally only encountered with large numbers of silos connecting /// in a short time period and using multi-datacenter Cassandara clusters public TimeSpan InitializeRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(20); internal int? GetCassandraTtlSeconds(ClusterMembershipOptions clusterMembershipOptions) => UseCassandraTtl ? Convert.ToInt32( Math.Round( clusterMembershipOptions.DefunctSiloExpiration.TotalSeconds, MidpointRounding.AwayFromZero)) : null; /// /// Configures the Cassandra client. /// /// The connection string. /// The keyspace. public void ConfigureClient(string connectionString, string keyspace = "orleans") { ArgumentException.ThrowIfNullOrEmpty(connectionString); ArgumentNullException.ThrowIfNull(keyspace); CreateSessionAsync = async sp => { var c = Cluster.Builder().WithConnectionString(connectionString) .Build(); var session = await c.ConnectAsync(keyspace).ConfigureAwait(false); return session; }; } /// /// Configures the Cassandra client. /// /// The connection string. public void ConfigureClient(Func> configurationDelegate) { ArgumentNullException.ThrowIfNull(configurationDelegate); CreateSessionAsync = configurationDelegate; } [NotNull] internal Func> CreateSessionAsync { get; private set; } = default!; } ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/Hosting/CassandraMembershipHostingExtensions.cs ================================================ using System; using System.Threading.Tasks; using Cassandra; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Messaging; namespace Orleans.Clustering.Cassandra.Hosting; /// /// Extension methods for configuring Cassandra as a clustering provider. /// public static class CassandraMembershipHostingExtensions { /// /// Configures Orleans clustering using Cassandra. /// /// The client builder. /// The client builder. public static IClientBuilder UseCassandraClustering(this IClientBuilder builder) => builder.ConfigureServices(services => { services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The client builder. /// A delegate used to create an . /// The client builder. public static IClientBuilder UseCassandraClustering(this IClientBuilder builder, Func> sessionProvider) => builder.ConfigureServices(services => { services.Configure(o => o.ConfigureClient(sessionProvider)); services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The client builder. /// A delegate used to configure the Cassandra client. /// The client builder. public static IClientBuilder UseCassandraClustering(this IClientBuilder builder, Action configureOptions) => builder.ConfigureServices(services => { services.Configure(configureOptions); services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The client builder. /// The Cassandra connection string. /// The Cassandra keyspace, which defaults to orleans. /// The client builder. public static IClientBuilder UseCassandraClustering(this IClientBuilder builder, string connectionString, string keyspace = "orleans") => builder.ConfigureServices(services => { services.Configure(o => o.ConfigureClient(connectionString, keyspace)); services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The silo builder. /// The silo builder. /// Pulls of type and from the DI container public static ISiloBuilder UseCassandraClustering(this ISiloBuilder builder) => builder.ConfigureServices(services => { services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The silo builder. /// A delegate used to create an . /// The silo builder. public static ISiloBuilder UseCassandraClustering(this ISiloBuilder builder, Func> sessionProvider) => builder.ConfigureServices(services => { services.Configure(o => o.ConfigureClient(sessionProvider)); services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The silo builder. /// A delegate used to configure the Cassandra client. /// The silo builder. public static ISiloBuilder UseCassandraClustering(this ISiloBuilder builder, Action configureOptions) => builder.ConfigureServices(services => { services.Configure(configureOptions); services.AddSingleton(); }); /// /// Configures Orleans clustering using Cassandra. /// /// The silo builder. /// The Cassandra connection string. /// The Cassandra keyspace, which defaults to orleans. /// The silo builder. public static ISiloBuilder UseCassandraClustering(this ISiloBuilder builder, string connectionString, string keyspace = "orleans") => builder.ConfigureServices(services => { services.Configure(o => o.ConfigureClient(connectionString, keyspace)); services.AddSingleton(); }); } ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/Orleans.Clustering.Cassandra.csproj ================================================ Microsoft.Orleans.Clustering.Cassandra Microsoft Orleans Cassandra Clustering Provider Microsoft Orleans clustering provider backed by Cassandra $(PackageTags) Cassandra $(DefaultTargetFrameworks) Orleans.Clustering.Cassandra Orleans.Clustering.Cassandra true $(DefineConstants);ORLEANS_CLUSTERING enable ================================================ FILE: src/Cassandra/Orleans.Clustering.Cassandra/OrleansQueries.cs ================================================ using System; using Cassandra; using Orleans.Runtime; using System.Linq; using System.Threading.Tasks; namespace Orleans.Clustering.Cassandra; /// /// This class is responsible for keeping a list of prepared queries and /// knowing their parameters (including type and conversion to the target /// type). /// internal sealed class OrleansQueries { public ISession Session { get; } private PreparedStatement? _insertMembershipVersionPreparedStatement; private PreparedStatement? _deleteMembershipTablePreparedStatement; private PreparedStatement? _insertMembershipPreparedStatement; private PreparedStatement? _membershipReadAllPreparedStatement; private PreparedStatement? _membershipReadVersionPreparedStatement; private PreparedStatement? _updateIAmAlivePreparedStatement; private PreparedStatement? _updateIAmAliveWithTtlPreparedStatement; private PreparedStatement? _deleteMembershipEntryPreparedStatement; private PreparedStatement? _updateMembershipPreparedStatement; private PreparedStatement? _membershipReadRowPreparedStatement; private PreparedStatement? _membershipGatewaysQueryPreparedStatement; public static Task CreateInstance(ISession session) { return Task.FromResult(new OrleansQueries(session)); } private OrleansQueries(ISession session) { MembershipReadConsistencyLevel = ConsistencyLevel.Quorum; MembershipWriteConsistencyLevel = ConsistencyLevel.Quorum; Session = session; } internal async Task EnsureTableExistsAsync(TimeSpan maxRetryDelay, int? ttl) { if (!await DoesTableAlreadyExistAsync()) { try { await MakeTableAsync(ttl); } catch (WriteTimeoutException) // If there's contention on table creation, backoff a bit and try once more { // Randomize the delay to avoid contention, preferring that more instances will wait longer var nextSingle = Random.Shared.NextSingle(); await Task.Delay(maxRetryDelay * Math.Sqrt(nextSingle)); if (!await DoesTableAlreadyExistAsync()) { await MakeTableAsync(ttl); } } } } internal async Task EnsureClusterVersionExistsAsync(TimeSpan maxRetryDelay, string clusterIdentifier) { if (!await DoesClusterVersionAlreadyExistAsync(clusterIdentifier)) { try { await Session.ExecuteAsync(await InsertMembershipVersion(clusterIdentifier)); } catch (WriteTimeoutException) // If there's contention on table creation, backoff a bit and try once more { // Randomize the delay to avoid contention, preferring that more instances will wait longer var nextSingle = Random.Shared.NextSingle(); await Task.Delay(maxRetryDelay * Math.Sqrt(nextSingle)); if (!await DoesClusterVersionAlreadyExistAsync(clusterIdentifier)) { await Session.ExecuteAsync(await InsertMembershipVersion(clusterIdentifier)); } } } } private async Task DoesClusterVersionAlreadyExistAsync(string clusterIdentifier) { try { var resultSet = await Session.ExecuteAsync(CheckIfClusterVersionExists(clusterIdentifier, ConsistencyLevel.LocalOne)); return resultSet.Any(); } catch (UnavailableException) { var resultSet = await Session.ExecuteAsync(CheckIfClusterVersionExists(clusterIdentifier, ConsistencyLevel.One)); return resultSet.Any(); } } private async Task DoesTableAlreadyExistAsync() { try { var resultSet = await Session.ExecuteAsync(CheckIfTableExists(Session.Keyspace, ConsistencyLevel.LocalOne)); return resultSet.Any(); } catch (UnavailableException) { var resultSet = await Session.ExecuteAsync(CheckIfTableExists(Session.Keyspace, ConsistencyLevel.One)); return resultSet.Any(); } catch (UnauthorizedException) { return false; } } private async Task MakeTableAsync(int? ttlSeconds) { await Session.ExecuteAsync(EnsureTableExists(ttlSeconds)); await Session.ExecuteAsync(EnsureIndexExists); } public ConsistencyLevel MembershipWriteConsistencyLevel { get; set; } public ConsistencyLevel MembershipReadConsistencyLevel { get; set; } public IStatement CheckIfClusterVersionExists(string clusterIdentifier, ConsistencyLevel consistencyLevel) => new SimpleStatement( $"SELECT version FROM membership WHERE partition_key = '{clusterIdentifier}';") .SetConsistencyLevel(consistencyLevel); public IStatement CheckIfTableExists(string keyspace, ConsistencyLevel consistencyLevel) => new SimpleStatement( $"SELECT * FROM system_schema.tables WHERE keyspace_name = '{keyspace}' AND table_name = 'membership';") .SetConsistencyLevel(consistencyLevel); /// /// In Cassandra, a table-level default_time_to_live of 0 is treated as disabled. /// /// See https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/cqlCreateTable.html#tabProp__cqlTableDefaultTTL /// public IStatement EnsureTableExists(int? defaultTimeToLiveSeconds) => new SimpleStatement( $$""" CREATE TABLE IF NOT EXISTS membership ( partition_key ascii, version int static, address ascii, port int, generation int, silo_name text, host_name text, status int, proxy_port int, suspect_times ascii, start_time timestamp, i_am_alive_time timestamp, PRIMARY KEY(partition_key, address, port, generation) ) WITH compression = { 'class' : 'LZ4Compressor', 'enabled' : true } AND default_time_to_live = {{defaultTimeToLiveSeconds.GetValueOrDefault(0)}}; """); public IStatement EnsureIndexExists => new SimpleStatement(""" CREATE INDEX IF NOT EXISTS ix_membership_status ON membership(status); """); public async ValueTask InsertMembership(string clusterIdentifier, MembershipEntry membershipEntry, int version) { _insertMembershipPreparedStatement ??= await PrepareStatementAsync(""" UPDATE membership SET version = :new_version, status = :status, start_time = :start_time, silo_name = :silo_name, host_name = :host_name, proxy_port = :proxy_port, i_am_alive_time = :i_am_alive_time WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation IF version = :expected_version; """, MembershipWriteConsistencyLevel); return _insertMembershipPreparedStatement.Bind(new { partition_key = clusterIdentifier, address = membershipEntry.SiloAddress.Endpoint.Address.ToString(), port = membershipEntry.SiloAddress.Endpoint.Port, generation = membershipEntry.SiloAddress.Generation, silo_name = membershipEntry.SiloName, host_name = membershipEntry.HostName, status = (int)membershipEntry.Status, proxy_port = membershipEntry.ProxyPort, start_time = membershipEntry.StartTime, i_am_alive_time = membershipEntry.IAmAliveTime, new_version = version + 1, expected_version = version }); } public async ValueTask InsertMembershipVersion(string clusterIdentifier) { _insertMembershipVersionPreparedStatement ??= await PrepareStatementAsync(""" INSERT INTO membership( partition_key, version ) VALUES ( :partition_key, 0 ) IF NOT EXISTS; """, MembershipWriteConsistencyLevel); return _insertMembershipVersionPreparedStatement.Bind(clusterIdentifier); } public async ValueTask DeleteMembershipTableEntries(string clusterIdentifier) { _deleteMembershipTablePreparedStatement ??= await PrepareStatementAsync(""" DELETE FROM membership WHERE partition_key = :partition_key; """, MembershipWriteConsistencyLevel); return _deleteMembershipTablePreparedStatement.Bind(clusterIdentifier); } public async ValueTask UpdateIAmAliveTime(string clusterIdentifier, MembershipEntry membershipEntry) { _updateIAmAlivePreparedStatement ??= await PrepareStatementAsync(""" UPDATE membership SET i_am_alive_time = :i_am_alive_time WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation; """, ConsistencyLevel.Any); return _updateIAmAlivePreparedStatement.Bind(new { partition_key = clusterIdentifier, i_am_alive_time = membershipEntry.IAmAliveTime, address = membershipEntry.SiloAddress.Endpoint.Address.ToString(), port = membershipEntry.SiloAddress.Endpoint.Port, generation = membershipEntry.SiloAddress.Generation }); } /// /// When the user has opted in to Cassandra TTL behavior, the entire membership row needs to be read and written /// back so that each cell is updated with the table's default TTL. /// /// Cassandra TTLs are cell-based, not row-based, which is why all the data needs to be re-inserted in order to /// update the TTLs for all cells in the row. /// /// https://docs.datastax.com/en/cql-oss/3.x/cql/cql_reference/cqlInsert.html /// public async ValueTask UpdateIAmAliveTimeWithTtL( string clusterIdentifier, MembershipEntry iAmAliveEntry, MembershipEntry existingEntry, TableVersion existingVersion) { _updateIAmAliveWithTtlPreparedStatement ??= await PrepareStatementAsync( """ UPDATE membership SET version = :same_version, silo_name = :silo_name, host_name = :host_name, status = :status, proxy_port = :proxy_port, suspect_times = :suspect_times, start_time = :start_time, i_am_alive_time = :i_am_alive_time WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation IF version = :expected_version; """, // This is ignored because we're creating a LWT MembershipWriteConsistencyLevel); BoundStatement updateIAmAliveTimeWithTtL = _updateIAmAliveWithTtlPreparedStatement.Bind(new { partition_key = clusterIdentifier, // The same version still needs to be written, to update its cell-level TTL same_version = existingVersion.Version, address = existingEntry.SiloAddress.Endpoint.Address.ToString(), port = existingEntry.SiloAddress.Endpoint.Port, generation = existingEntry.SiloAddress.Generation, silo_name = existingEntry.SiloName, host_name = existingEntry.HostName, status = (int)existingEntry.Status, proxy_port = existingEntry.ProxyPort, suspect_times = GetSuspectTimesString(existingEntry), start_time = existingEntry.StartTime, i_am_alive_time = iAmAliveEntry.IAmAliveTime, // But we still check that the version was the same during the update so we don't stomp on another update expected_version = existingVersion.Version, }); // To improve performance, we allow IAmAlive updates to be LocalSerial updateIAmAliveTimeWithTtL.SetSerialConsistencyLevel(ConsistencyLevel.LocalSerial); return updateIAmAliveTimeWithTtL; } public async ValueTask DeleteMembershipEntry(string clusterIdentifier, MembershipEntry membershipEntry) { _deleteMembershipEntryPreparedStatement ??= await PrepareStatementAsync(""" DELETE FROM membership WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation; """, MembershipWriteConsistencyLevel); return _deleteMembershipEntryPreparedStatement.Bind(new { partition_key = clusterIdentifier, address = membershipEntry.SiloAddress.Endpoint.Address.ToString(), port = membershipEntry.SiloAddress.Endpoint.Port, generation = membershipEntry.SiloAddress.Generation }); } public async ValueTask UpdateMembership(string clusterIdentifier, MembershipEntry membershipEntry, int version) { _updateMembershipPreparedStatement ??= await PrepareStatementAsync(""" UPDATE membership SET version = :new_version, status = :status, suspect_times = :suspect_times, i_am_alive_time = :i_am_alive_time WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation IF version = :expected_version; """, MembershipWriteConsistencyLevel); return _updateMembershipPreparedStatement.Bind(new { partition_key = clusterIdentifier, new_version = version + 1, expected_version = version, status = (int)membershipEntry.Status, suspect_times = GetSuspectTimesString(membershipEntry), i_am_alive_time = membershipEntry.IAmAliveTime, address = membershipEntry.SiloAddress.Endpoint.Address.ToString(), port = membershipEntry.SiloAddress.Endpoint.Port, generation = membershipEntry.SiloAddress.Generation }); } public async ValueTask MembershipReadVersion(string clusterIdentifier) { _membershipReadVersionPreparedStatement ??= await PrepareStatementAsync(""" SELECT version FROM membership WHERE partition_key = :partition_key; """, MembershipReadConsistencyLevel); return _membershipReadVersionPreparedStatement.Bind(clusterIdentifier); } public async ValueTask MembershipReadAll(string clusterIdentifier) { _membershipReadAllPreparedStatement ??= await PrepareStatementAsync(""" SELECT version, address, port, generation, silo_name, host_name, status, proxy_port, suspect_times, start_time, i_am_alive_time FROM membership WHERE partition_key = :partition_key; """, MembershipReadConsistencyLevel); return _membershipReadAllPreparedStatement.Bind(clusterIdentifier); } public async ValueTask MembershipReadRow(string clusterIdentifier, SiloAddress siloAddress) { _membershipReadRowPreparedStatement ??= await PrepareStatementAsync(""" SELECT version, address, port, generation, silo_name, host_name, status, proxy_port, suspect_times, start_time, i_am_alive_time FROM membership WHERE partition_key = :partition_key AND address = :address AND port = :port AND generation = :generation; """, MembershipReadConsistencyLevel); return _membershipReadRowPreparedStatement.Bind(new { partition_key = clusterIdentifier, address = siloAddress.Endpoint.Address.ToString(), port = siloAddress.Endpoint.Port, generation = siloAddress.Generation }); } public async ValueTask GatewaysQuery(string clusterIdentifier, int status) { // Filtering is only for the `proxy_port` filtering. We're already hitting the partition // and secondary index on status which both don't need "ALLOW FILTERING" _membershipGatewaysQueryPreparedStatement ??= await PrepareStatementAsync(""" SELECT address, proxy_port, generation FROM membership WHERE partition_key = :partition_key AND status = :status AND proxy_port > 0 ALLOW FILTERING; """, MembershipReadConsistencyLevel); return _membershipGatewaysQueryPreparedStatement.Bind(new { partition_key = clusterIdentifier, status = status }); } private async ValueTask PrepareStatementAsync(string cql, ConsistencyLevel consistencyLevel) { var statement = await Session.PrepareAsync(cql); statement.SetConsistencyLevel(consistencyLevel); return statement; } private static string? GetSuspectTimesString(MembershipEntry entry) => entry.SuspectTimes == null ? null : string.Join( "|", entry.SuspectTimes.Select((Tuple s) => $"{s.Item1.ToParsableString()},{LogFormatter.PrintDate(s.Item2)}")); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/.azdo/.npmrc ================================================ registry=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ always-auth=true ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/DashboardClient.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; namespace Orleans.Dashboard.Core; internal sealed class DashboardClient(IGrainFactory grainFactory) : IDashboardClient { private readonly IDashboardGrain _dashboardGrain = grainFactory.GetGrain(0); private readonly IDashboardRemindersGrain _remindersGrain = grainFactory.GetGrain(0); private readonly IGrainFactory _grainFactory = grainFactory; public async Task> DashboardCounters(string[] exclusions) => await _dashboardGrain.GetCounters(exclusions); public async Task>> ClusterStats() => await _dashboardGrain.GetClusterTracing(); public async Task> GetReminders(int pageNumber, int pageSize) => await _remindersGrain.GetReminders(pageNumber, pageSize); public async Task> HistoricalStats(string siloAddress) => await Silo(siloAddress).GetRuntimeStatistics(); public async Task>> SiloProperties(string siloAddress) => await Silo(siloAddress).GetExtendedProperties(); public async Task>> SiloMetadata(string siloAddress) => await Silo(siloAddress).GetMetadata(); public async Task>> SiloStats(string siloAddress) => await _dashboardGrain.GetSiloTracing(siloAddress); public async Task> GetCounters(string siloAddress) => await Silo(siloAddress).GetCounters(); public async Task>>> GrainStats( string grainName) => await _dashboardGrain.GetGrainTracing(grainName); public async Task>> TopGrainMethods(int take, string[] exclusions) => await _dashboardGrain.TopGrainMethods(take, exclusions); private ISiloGrainProxy Silo(string siloAddress) => _grainFactory.GetGrain(siloAddress); public async Task> GetGrainState(string id, string grainType) => await _dashboardGrain.GetGrainState(id, grainType); public async Task> GetGrainTypes(string[] exclusions = null) => await _dashboardGrain.GetGrainTypes(exclusions); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/Extensions.cs ================================================ using System; using System.Globalization; using Orleans.Runtime; namespace Orleans.Dashboard.Core; internal static class Extensions { private static readonly DateTime UnixStart = new(1970, 1, 1); public static string PrimaryKeyAsString(this GrainReference grainRef) { if (grainRef.IsPrimaryKeyBasedOnLong()) // Long { var longKey = grainRef.GetPrimaryKeyLong(out var longExt); return longExt != null ? $"{longKey} + {longExt}" : longKey.ToString(); } var stringKey = grainRef.GetPrimaryKeyString(); if (stringKey == null) // Guid { var guidKey = grainRef.GetPrimaryKey(out var guidExt).ToString(); return guidExt != null ? $"{guidKey} + {guidExt}" : guidKey; } return stringKey; } public static string ToPeriodString(this DateTime value) => value.ToString("yyyy-MM-ddTHH:mm:ss"); public static long ToPeriodNumber(this DateTime value) => (long)value.Subtract(UnixStart).TotalSeconds; public static string ToISOString(this DateTime value) => value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/IDashboardClient.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; namespace Orleans.Dashboard.Core; internal interface IDashboardClient { Task> DashboardCounters(string[] exclusions = null); Task>> ClusterStats(); Task> GetReminders(int pageNumber, int pageSize); Task> HistoricalStats(string siloAddress); Task>> SiloProperties(string siloAddress); Task>> SiloMetadata(string siloAddress); Task>> SiloStats(string siloAddress); Task> GetCounters(string siloAddress); Task>>> GrainStats(string grainName); Task>> TopGrainMethods(int take, string[] exclusions = null); Task> GetGrainState(string id, string grainType); Task> GetGrainTypes(string[] exclusions = null); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/IDashboardGrain.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; namespace Orleans.Dashboard.Core; [Alias("Orleans.Dashboard.Core.IDashboardGrain")] internal interface IDashboardGrain : IGrainWithIntegerKey { [OneWay] [Alias("InitializeAsync")] Task InitializeAsync(); [OneWay] [Alias("SubmitTracing")] Task SubmitTracing(string siloAddress, Immutable grainCallTime); [Alias("GetCounters")] Task> GetCounters(string[] exclusions = null); [Alias("GetGrainTracing")] Task>>> GetGrainTracing(string grain); [Alias("GetClusterTracing")] Task>> GetClusterTracing(); [Alias("GetSiloTracing")] Task>> GetSiloTracing(string address); [Alias("TopGrainMethods")] Task>> TopGrainMethods(int take, string[] exclusions = null); [Alias("GetGrainState")] Task> GetGrainState(string id, string grainType); [Alias("GetGrainTypes")] Task> GetGrainTypes(string[] exclusions = null); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/IDashboardRemindersGrain.cs ================================================ using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Dashboard.Model; namespace Orleans.Dashboard.Core; [Alias("Orleans.Dashboard.Core.IDashboardRemindersGrain")] internal interface IDashboardRemindersGrain : IGrainWithIntegerKey { [Alias("GetReminders")] Task> GetReminders(int pageNumber, int pageSize); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/ISiloGrainClient.cs ================================================ using Orleans.Runtime; using Orleans.Services; namespace Orleans.Dashboard.Core; internal interface ISiloGrainClient : IGrainServiceClient { ISiloGrainService GrainService(SiloAddress destination); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/ISiloGrainProxy.cs ================================================ using Orleans.Concurrency; namespace Orleans.Dashboard.Core; [Alias("Orleans.Dashboard.Core.ISiloGrainProxy")] internal interface ISiloGrainProxy : IGrainWithStringKey, ISiloGrainService { [Alias("GetMetadata")] Task>> GetMetadata(); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Core/ISiloGrainService.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Services; using Orleans.Dashboard.Model; namespace Orleans.Dashboard.Core; [Alias("Orleans.Dashboard.Core.ISiloGrainService")] internal interface ISiloGrainService : IGrainService { [Alias("SetVersion")] Task SetVersion(string orleans, string host); [OneWay] [Alias("ReportCounters")] Task ReportCounters(Immutable stats); [Alias("Enable")] Task Enable(bool enabled); [Alias("GetExtendedProperties")] Task>> GetExtendedProperties(); [Alias("GetRuntimeStatistics")] Task> GetRuntimeStatistics(); [Alias("GetCounters")] Task> GetCounters(); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/DashboardHost.cs ================================================ using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Metrics; using Orleans.Runtime; using Orleans.Dashboard.Implementation; using Microsoft.Extensions.Hosting; using Orleans.Dashboard.Core; namespace Orleans.Dashboard; internal sealed class DashboardHost( ILogger logger, ILocalSiloDetails localSiloDetails, IGrainFactory grainFactory, DashboardTelemetryExporter dashboardTelemetryExporter, ISiloGrainClient siloGrainClient) : IHostedService, IAsyncDisposable, IDisposable { private MeterProvider _meterProvider; public async Task StartAsync(CancellationToken cancellationToken) { await Task.WhenAll( ActivateDashboardGrainAsync(), ActivateSiloGrainAsync(), StartOpenTelemetryConsumerAsync()).ConfigureAwait(false); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; private async Task ActivateSiloGrainAsync() { try { var siloGrain = siloGrainClient.GrainService(localSiloDetails.SiloAddress); await siloGrain.SetVersion(GetOrleansVersion(), GetHostVersion()).ConfigureAwait(false); } catch (Exception ex) { logger.LogWarning(ex, "Unable to activate silo grain service during startup. The service will be activated on first use."); } } private async Task ActivateDashboardGrainAsync() { try { var dashboardGrain = grainFactory.GetGrain(0); await dashboardGrain.InitializeAsync().ConfigureAwait(false); } catch (Exception ex) { logger.LogWarning(ex, "Unable to activate dashboard grain during startup. The grain will be activated on first use."); } } private Task StartOpenTelemetryConsumerAsync() { _meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter("Orleans") .AddReader(new PeriodicExportingMetricReader(dashboardTelemetryExporter, 1000, 1000)) .Build(); return Task.CompletedTask; } public async ValueTask DisposeAsync() { await DisposeAsync(_meterProvider).ConfigureAwait(false); static async ValueTask DisposeAsync(object obj) { try { if (obj is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } else if (obj is IDisposable disposable) { disposable.Dispose(); } } catch { // Ignore. } } } public void Dispose() { try { _meterProvider?.Dispose(); } catch { /* NOOP */ } } private static string GetOrleansVersion() { var assembly = typeof(SiloAddress).GetTypeInfo().Assembly; return $"{assembly.GetCustomAttribute()?.InformationalVersion} ({assembly.GetName().Version})"; } private static string GetHostVersion() { try { var assembly = Assembly.GetEntryAssembly(); if (assembly != null) { return assembly.GetName().Version.ToString(); } } catch { /* NOOP */ } return "1.0.0.0"; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/DashboardOptions.cs ================================================ namespace Orleans.Dashboard; /// /// Configuration options for the Orleans Dashboard. /// public sealed class DashboardOptions { /// /// Gets or sets a value indicating whether to disable the trace feature. /// When true, the live log streaming endpoint will be disabled. /// The default is false. /// public bool HideTrace { get; set; } /// /// Gets or sets the number of milliseconds between counter samples. /// Must be greater than or equal to 1000. /// The default is 1000 (1 second). /// public int CounterUpdateIntervalMs { get; set; } = 1000; /// /// Gets or sets the length of the history to maintain for metrics. /// Higher values provide more historical data but consume more memory. /// The default is 100. /// public int HistoryLength { get; set; } = 100; } ================================================ FILE: src/Dashboard/Orleans.Dashboard/EmbeddedAssetProvider.cs ================================================ using System; using System.Collections.Frozen; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Security.Cryptography; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Orleans.Dashboard; internal sealed class EmbeddedAssetProvider { private const string GZipEncodingValue = "gzip"; private static readonly StringValues GzipEncodingHeader = new(GZipEncodingValue); private static readonly Assembly Assembly = typeof(EmbeddedAssetProvider).Assembly; private static readonly FileExtensionContentTypeProvider ContentTypeProvider = new(); private static readonly StringValues CacheControl = new(new CacheControlHeaderValue() { NoCache = true, NoStore = true, }.ToString()); private readonly FrozenDictionary _resourceCache; public EmbeddedAssetProvider() { // Build resource cache for all embedded resources var resourceNamePrefix = $"{Assembly.GetName().Name}.wwwroot."; _resourceCache = Assembly .GetManifestResourceNames() .Where(p => p.StartsWith(resourceNamePrefix, StringComparison.Ordinal)) .ToFrozenDictionary( p => p[resourceNamePrefix.Length..], CreateResourceEntry, StringComparer.OrdinalIgnoreCase); } public IResult ServeAsset(string name, HttpContext context) { // Embedded resources use dots instead of slashes for directory separators var resourceKey = name.Replace('/', '.'); if (!_resourceCache.TryGetValue(resourceKey, out var entry)) { return Results.NotFound(); } // Check if client has cached version if (context.Request.Headers.IfNoneMatch == entry.ETag) { return Results.StatusCode(StatusCodes.Status304NotModified); } byte[] contents; var response = context.Response; response.Headers.CacheControl = CacheControl; if (entry.CompressedContent is not null && IsGZipAccepted(context.Request)) { response.Headers.ContentEncoding = GzipEncodingHeader; contents = entry.CompressedContent; } else { contents = entry.DecompressedContent; } return Results.Bytes( contents, contentType: entry.ContentType, entityTag: new EntityTagHeaderValue(entry.ETag)); } private static bool IsGZipAccepted(HttpRequest httpRequest) { if (httpRequest.GetTypedHeaders().AcceptEncoding is not { Count: > 0 } acceptEncoding) { return false; } for (int i = 0; i < acceptEncoding.Count; i++) { var encoding = acceptEncoding[i]; if (encoding.Quality is not 0 && string.Equals(encoding.Value.Value, GZipEncodingValue, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static ResourceEntry CreateResourceEntry(string fullResourceName) { using var resourceStream = Assembly.GetManifestResourceStream(fullResourceName) ?? throw new FileNotFoundException($"Embedded resource not found: {fullResourceName}"); using var decompressedContent = new MemoryStream(); resourceStream.CopyTo(decompressedContent); var decompressedArray = decompressedContent.ToArray(); // Compress the content using var compressedContent = new MemoryStream(); using (var gzip = new GZipStream(compressedContent, CompressionMode.Compress, leaveOpen: true)) { gzip.Write(decompressedArray); } // Only use compression if it actually reduces size byte[] compressedArray = compressedContent.Length < decompressedArray.Length ? compressedContent.ToArray() : null; var hash = SHA256.HashData(compressedArray ?? decompressedArray); var eTag = $"\"{Convert.ToBase64String(hash)}\""; // Extract just the file name with extension for content type detection var fileName = fullResourceName.Split('.').TakeLast(2).FirstOrDefault() + "." + fullResourceName.Split('.').Last(); var contentType = ContentTypeProvider.TryGetContentType(fileName, out var ct) ? ct : "application/octet-stream"; return new ResourceEntry(decompressedArray, compressedArray, eTag, contentType); } private sealed class ResourceEntry(byte[] decompressedContent, byte[] compressedContent, string eTag, string contentType) { public byte[] CompressedContent { get; } = compressedContent; public string ContentType { get; } = contentType; public byte[] DecompressedContent { get; } = decompressedContent; public string ETag { get; } = eTag; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/GrainProfilerOptions.cs ================================================ namespace Orleans.Dashboard; /// /// Configuration options for the grain profiler. /// public sealed class GrainProfilerOptions { /// /// Gets or sets a value indicating whether tracing should always be enabled, regardless of dashboard activity. /// When set to , profiling data is continuously collected even when the dashboard is not being queried. /// When set to (default), profiling is automatically disabled after of inactivity. /// Default is . /// public bool TraceAlways { get; set; } /// /// Gets or sets the duration of inactivity (no dashboard queries) after which profiling is automatically disabled. /// This setting only applies when is . /// After this period without queries, profiling stops to reduce overhead until the next dashboard query. /// Default is 1 minute. /// public TimeSpan DeactivationTime { get; set; } = TimeSpan.FromMinutes(1); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/DashboardLogger.cs ================================================ using System; using System.Collections.Immutable; using Microsoft.Extensions.Logging; #pragma warning disable IDE0069 // Disposable fields should be disposed namespace Orleans.Dashboard.Implementation; internal sealed class DashboardLogger : ILoggerProvider, ILogger { private readonly NoopDisposable _scope = new(); private ImmutableArray> _actions = []; public void Add(Action action) => _actions = _actions.Add(action); public void Remove(Action action) => _actions = _actions.Remove(action); public void Dispose() { } public ILogger CreateLogger(string categoryName) => this; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { var currentActions = _actions; if (currentActions.Length <= 0) { return; } var logMessage = formatter(state, exception); foreach (var action in currentActions) { action(eventId, logLevel, logMessage); } } public bool IsEnabled(LogLevel logLevel) => true; public IDisposable BeginScope(TState state) => _scope; private sealed class NoopDisposable : IDisposable { public void Dispose() { } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/DashboardTelemetryExporter.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Metrics; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Dashboard.Model; using Orleans.Dashboard.Core; namespace Orleans.Dashboard.Implementation; internal sealed class DashboardTelemetryExporter( ILocalSiloDetails localSiloDetails, ISiloGrainClient siloGrainClient, ILogger logger) : BaseExporter { private readonly Dictionary> _metrics = []; private readonly ISiloGrainClient _siloGrainClient = siloGrainClient; private readonly ILogger _logger = logger; private readonly SiloAddress _siloAddress = localSiloDetails.SiloAddress; public readonly struct Value { public readonly T Current; public readonly T Last; public Value(T value) : this(value, value) { } public Value(T last, T current) { Last = last; Current = current; } public Value Update(T newValue) => new(Current, newValue); } public override ExportResult Export(in Batch batch) { var grain = _siloGrainClient.GrainService(_siloAddress); CollectMetricsFromBatch(batch); if (_metrics.Count == 0) { return ExportResult.Success; } var counters = new StatCounter[_metrics.Count]; var countersIndex = 0; foreach (var (key, value) in _metrics) { // In case new values have been added to metrics in another thread. It will be pushed the next time then. if (countersIndex == counters.Length) { break; } counters[countersIndex] = new StatCounter( key, value.Current.ToString(CultureInfo.InvariantCulture), ComputeDelta(value)); countersIndex++; } grain.ReportCounters(counters.AsImmutable()); return ExportResult.Success; } private void CollectMetricsFromBatch(in Batch batch) { foreach (var metric in batch) { switch (metric.MetricType) { case MetricType.LongSum: CollectMetric(metric, p => p.GetSumLong()); break; case MetricType.DoubleSum: CollectMetric(metric, p => p.GetSumDouble()); break; case MetricType.LongGauge: CollectMetric(metric, p => p.GetGaugeLastValueLong()); break; case MetricType.DoubleGauge: CollectMetric(metric, p => p.GetGaugeLastValueDouble()); break; case MetricType.Histogram: CollectMetric(metric, p => p.GetHistogramSum()); break; default: _logger.LogWarning("Ignoring unknown metric type {MetricType}", metric.MetricType); break; } } } private void CollectMetric(Metric metric, Func getValue) { foreach (var point in metric.GetMetricPoints()) { var value = getValue(point); if (!_metrics.ContainsKey(metric.Name)) _metrics[metric.Name] = new Value(0); _metrics[metric.Name] = _metrics[metric.Name].Update(value); } } private static string ComputeDelta(Value metric) { var delta = metric.Current - metric.Last; return delta.ToString(CultureInfo.InvariantCulture); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Details/MembershipTableSiloDetailsProvider.cs ================================================ using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Dashboard.Metrics.Details; using Orleans.Dashboard.Model; using Orleans.Dashboard.Core; namespace Orleans.Dashboard.Implementation.Details; internal sealed class MembershipTableSiloDetailsProvider : ISiloDetailsProvider { private readonly IGrainFactory grainFactory; public MembershipTableSiloDetailsProvider(IGrainFactory grainFactory) { this.grainFactory = grainFactory; } public async Task GetSiloDetails() { //default implementation uses managementgrain details var grain = grainFactory.GetGrain(0); var hosts = await grain.GetDetailedHosts(true); return hosts.Select(x => new SiloDetails { FaultZone = x.FaultZone, HostName = x.HostName, IAmAliveTime = x.IAmAliveTime.ToISOString(), ProxyPort = x.ProxyPort, RoleName = x.RoleName, SiloAddress = x.SiloAddress.ToParsableString(), SiloName = x.SiloName, StartTime = x.StartTime.ToISOString(), Status = x.Status.ToString(), SiloStatus = x.Status, UpdateZone = x.UpdateZone }).ToArray(); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Details/SiloStatusOracleSiloDetailsProvider.cs ================================================ using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Dashboard.Metrics.Details; using Orleans.Dashboard.Model; namespace Orleans.Dashboard.Implementation.Details; internal sealed class SiloStatusOracleSiloDetailsProvider(ISiloStatusOracle siloStatusOracle) : ISiloDetailsProvider { public Task GetSiloDetails() { return Task.FromResult(siloStatusOracle.GetApproximateSiloStatuses(true) .Select(x => new SiloDetails { Status = x.Value.ToString(), SiloStatus = x.Value, SiloAddress = x.Key.ToParsableString(), SiloName = x.Key.ToParsableString() // Use the address for naming }) .ToArray()); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/GrainProfilerFilter.cs ================================================ using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Dashboard.Metrics; namespace Orleans.Dashboard.Implementation; internal sealed class GrainProfilerFilter( IGrainProfiler profiler, ILogger logger, GrainProfilerFilter.GrainMethodFormatterDelegate formatMethodName) : IIncomingGrainCallFilter { private readonly GrainMethodFormatterDelegate _formatMethodName = formatMethodName ?? DefaultGrainMethodFormatter; private readonly IGrainProfiler _profiler = profiler; private readonly ILogger _logger = logger; private readonly ConcurrentDictionary _shouldSkipCache = new(); public delegate string GrainMethodFormatterDelegate(IIncomingGrainCallContext callContext); public static readonly GrainMethodFormatterDelegate DefaultGrainMethodFormatter = FormatMethodName; public async Task Invoke(IIncomingGrainCallContext context) { if (!_profiler.IsEnabled) { await context.Invoke(); return; } if (ShouldSkipProfiling(context)) { await context.Invoke(); return; } var stopwatch = Stopwatch.StartNew(); try { await context.Invoke(); Track(context, stopwatch, false); } catch (Exception) { Track(context, stopwatch, true); throw; } } private void Track(IIncomingGrainCallContext context, Stopwatch stopwatch, bool isException) { try { stopwatch.Stop(); var elapsedMs = stopwatch.Elapsed.TotalMilliseconds; var grainMethodName = _formatMethodName(context); _profiler.Track(elapsedMs, context.Grain.GetType(), grainMethodName, isException); } catch (Exception ex) { _logger.LogError(100002, ex, "error recording results for grain"); } } private static string FormatMethodName(IIncomingGrainCallContext context) { var methodName = context.ImplementationMethod?.Name ?? "Unknown"; if (methodName == nameof(IRemindable.ReceiveReminder) && context.Request.GetArgumentCount() == 2) { try { methodName = $"{methodName}({context.Request.GetArgument(0)})"; } catch { // Could fail if the argument types do not match. } } return methodName; } private bool ShouldSkipProfiling(IIncomingGrainCallContext context) { var grainMethod = context.ImplementationMethod; if (grainMethod == null) { return false; } if (!_shouldSkipCache.TryGetValue(grainMethod, out var shouldSkip)) { try { var grainType = context.Grain.GetType(); shouldSkip = grainType.GetCustomAttribute() != null || grainMethod.GetCustomAttribute() != null; } catch (Exception ex) { _logger.LogError(100003, ex, "error reading NoProfilingAttribute attribute for grain"); shouldSkip = false; } _shouldSkipCache.TryAdd(grainMethod, shouldSkip); } return shouldSkip; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Grains/DashboardGrain.cs ================================================ using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Serialization.Configuration; using Orleans.Dashboard.Implementation.Helpers; using Orleans.Dashboard.Metrics.Details; using Orleans.Dashboard.Metrics.History; using Orleans.Dashboard.Metrics.TypeFormatting; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; using Orleans.Dashboard.Core; namespace Orleans.Dashboard.Implementation.Grains; [Reentrant] internal sealed class DashboardGrain : Grain, IDashboardGrain { private readonly TraceHistory _history; private readonly ISiloDetailsProvider _siloDetailsProvider; private readonly ISiloGrainClient _siloGrainClient; private readonly DashboardCounters _counters; private readonly GrainProfilerOptions _grainProfilerOptions; private readonly TypeManifestOptions _typeManifestOptions; private readonly TimeSpan _updateInterval; private bool _isUpdating; private DateTime _startTime = DateTime.UtcNow; private DateTime _lastRefreshTime = DateTime.UtcNow; private DateTime _lastQuery = DateTime.UtcNow; private bool _isEnabled; public DashboardGrain( IOptions options, IOptions grainProfilerOptions, IOptions typeManifestOptions, ISiloDetailsProvider siloDetailsProvider, ISiloGrainClient siloGrainClient) { _siloDetailsProvider = siloDetailsProvider; _siloGrainClient = siloGrainClient; // Store the options to bypass the broadcase of the isEnabled flag. _grainProfilerOptions = grainProfilerOptions.Value; _typeManifestOptions = typeManifestOptions.Value; // Do not allow smaller timers than 1000ms = 1sec. _updateInterval = TimeSpan.FromMilliseconds(Math.Max(options.Value.CounterUpdateIntervalMs, 1000)); // Make the history configurable. _counters = new DashboardCounters(options.Value.HistoryLength); _history = new TraceHistory(options.Value.HistoryLength); } public override Task OnActivateAsync(CancellationToken cancellationToken) { _startTime = DateTime.UtcNow; if (!_grainProfilerOptions.TraceAlways) { var interval = TimeSpan.FromMinutes(1); this.RegisterGrainTimer(async x => { var timeSinceLastQuery = DateTimeOffset.UtcNow - _lastQuery; if (timeSinceLastQuery > _grainProfilerOptions.DeactivationTime && _isEnabled) { _isEnabled = false; await BroadcaseEnabled(); } }, new() { DueTime = interval, Period = interval, Interleave = true, KeepAlive = true }); } return base.OnActivateAsync(cancellationToken); } private Task EnsureIsActive() { _lastQuery = DateTime.UtcNow; if (!_isEnabled) { _isEnabled = true; _ = BroadcaseEnabled(); } return Task.CompletedTask; } private async Task BroadcaseEnabled() { if (_grainProfilerOptions.TraceAlways) { return; } var silos = await _siloDetailsProvider.GetSiloDetails(); foreach (var siloAddress in silos.Select(x => x.SiloAddress)) { await _siloGrainClient.GrainService(SiloAddress.FromParsableString(siloAddress)).Enable(_isEnabled); } } private async Task EnsureCountersAreUpToDate() { if (_isUpdating) { return; } var now = DateTime.UtcNow; if ((now - _lastRefreshTime) < _updateInterval) { return; } _isUpdating = true; try { var metricsGrain = GrainFactory.GetGrain(0); var activationCountTask = metricsGrain.GetTotalActivationCount(); var simpleGrainStatsTask = metricsGrain.GetSimpleGrainStatistics(); var siloDetailsTask = _siloDetailsProvider.GetSiloDetails(); await Task.WhenAll(activationCountTask, simpleGrainStatsTask, siloDetailsTask); RecalculateCounters(activationCountTask.Result, siloDetailsTask.Result, simpleGrainStatsTask.Result); _lastRefreshTime = now; } finally { _isUpdating = false; } } internal void RecalculateCounters(int activationCount, SiloDetails[] hosts, IList simpleGrainStatistics) { _counters.TotalActivationCount = activationCount; _counters.TotalActiveHostCount = hosts.Count(x => x.SiloStatus == SiloStatus.Active); _counters.TotalActivationCountHistory = _counters.TotalActivationCountHistory.Enqueue(activationCount).Dequeue(); _counters.TotalActiveHostCountHistory = _counters.TotalActiveHostCountHistory.Enqueue(_counters.TotalActiveHostCount).Dequeue(); var elapsedTime = Math.Min((DateTime.UtcNow - _startTime).TotalSeconds, 100); _counters.Hosts = hosts; var aggregatedTotals = _history.GroupByGrainAndSilo().ToLookup(x => (x.Grain, x.SiloAddress)); _counters.SimpleGrainStats = simpleGrainStatistics.Select(x => { var grainName = TypeFormatter.Parse(x.GrainType); var siloAddress = x.SiloAddress.ToParsableString(); var result = new SimpleGrainStatisticCounter { ActivationCount = x.ActivationCount, GrainType = grainName, SiloAddress = siloAddress, TotalSeconds = elapsedTime, }; foreach (var item in aggregatedTotals[(grainName, siloAddress)]) { result.TotalAwaitTime += item.ElapsedTime; result.TotalCalls += item.Count; result.TotalExceptions += item.ExceptionCount; } return result; }) .ToArray(); } public async Task> GetCounters(string[] exclusions) { await EnsureIsActive(); await EnsureCountersAreUpToDate(); var simpleGrainStats = exclusions != null && exclusions.Length > 0 ? [.. _counters.SimpleGrainStats.Where(x => !exclusions.Any(f => x.GrainType.StartsWith(f, StringComparison.OrdinalIgnoreCase)))] : _counters.SimpleGrainStats; return new DashboardCounters { Hosts = _counters.Hosts, SimpleGrainStats = simpleGrainStats, TotalActiveHostCount = _counters.TotalActiveHostCount, TotalActiveHostCountHistory = _counters.TotalActiveHostCountHistory, TotalActivationCount = _counters.TotalActivationCount, TotalActivationCountHistory = _counters.TotalActivationCountHistory, } .AsImmutable(); } public async Task>>> GetGrainTracing( string grain) { await EnsureIsActive(); await EnsureCountersAreUpToDate(); return _history.QueryGrain(grain).AsImmutable(); } public async Task>> GetClusterTracing() { await EnsureIsActive(); await EnsureCountersAreUpToDate(); return _history.QueryAll().AsImmutable(); } public async Task>> GetSiloTracing(string address) { await EnsureIsActive(); await EnsureCountersAreUpToDate(); return _history.QuerySilo(address).AsImmutable(); } public async Task>> TopGrainMethods(int take, string[] exclusions) { await EnsureIsActive(); await EnsureCountersAreUpToDate(); var values = _history.AggregateByGrainMethod(exclusions).ToList(); GrainMethodAggregate[] GetTotalCalls() { return values.OrderByDescending(x => x.Count).Take(take).ToArray(); } GrainMethodAggregate[] GetLatency() { return values.OrderByDescending(x => x.Count).Take(take).ToArray(); } GrainMethodAggregate[] GetErrors() { return values.Where(x => x.ExceptionCount > 0 && x.Count > 0) .OrderByDescending(x => x.ExceptionCount / x.Count).Take(take).ToArray(); } var result = new Dictionary { { "calls", GetTotalCalls() }, { "latency", GetLatency() }, { "errors", GetErrors() }, }; return result.AsImmutable(); } public Task InitializeAsync() => // just used to activate the grain Task.CompletedTask; public Task SubmitTracing(string siloAddress, Immutable grainTrace) { _history.Add(DateTime.UtcNow, siloAddress, grainTrace.Value); return Task.CompletedTask; } public async Task> GetGrainState(string id, string grainType) { var result = new ExpandoObject(); try { var implementationType = _typeManifestOptions.InterfaceImplementations .FirstOrDefault(w => w.FullName.Equals(grainType)); var mappedGrainId = GrainStateHelper.GetGrainId(id, implementationType); object grainId = mappedGrainId.Item1; string keyExtension = mappedGrainId.Item2; var propertiesAndFields = GrainStateHelper.GetPropertiesAndFieldsForGrainState(implementationType); var getGrainMethod = GrainStateHelper.GenerateGetGrainMethod(GrainFactory, grainId, keyExtension); var interfaceTypes = implementationType.GetInterfaces(); foreach (var interfaceType in interfaceTypes) { try { object[] grainMethodParameters; if (string.IsNullOrWhiteSpace(keyExtension)) grainMethodParameters = new object[] { interfaceType, grainId }; else grainMethodParameters = new object[] { interfaceType, grainId, keyExtension }; var grain = getGrainMethod.Invoke(GrainFactory, grainMethodParameters); var methods = interfaceType.GetMethods().Where(w => w.GetParameters().Length == 0); foreach (var method in methods) { try { if (method.ReturnType.IsAssignableTo(typeof(Task)) && ( method.ReturnType.GetGenericArguments() .Any(a => propertiesAndFields.Any(f => f == a) || method.Name == "GetState") ) ) { var task = (method.Invoke(grain, null) as Task); var resultProperty = task.GetType().GetProperty("Result"); if (resultProperty == null) continue; await task; result.TryAdd(method.Name, resultProperty.GetValue(task)); } } catch { // Because we got all the interfaces some errors with boxing and unboxing may happen with invocations } } } catch { // Because we got all the interfaces some errors with boxing and unboxing may happen when try to get the grain } } } catch (Exception ex) { result.TryAdd("error", string.Concat(ex.Message, " - ", ex?.InnerException.Message)); } return JsonSerializer.Serialize(result, options: new JsonSerializerOptions() { WriteIndented = true, }).AsImmutable(); } public Task> GetGrainTypes(string[] exclusions) { return Task.FromResult(_typeManifestOptions.InterfaceImplementations .Where(s => s.GetInterfaces().Any(i => i == typeof(IGrain) || i == typeof(ISystemTarget))) .Where(s => exclusions == null || !exclusions.Any(e => s.FullName.StartsWith(e, StringComparison.OrdinalIgnoreCase))) .Select(s => s.FullName) .ToArray() .AsImmutable()); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Grains/DashboardRemindersGrain.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Dashboard.Core; using Orleans.Dashboard.Model; namespace Orleans.Dashboard.Implementation.Grains; internal sealed class DashboardRemindersGrain : Grain, IDashboardRemindersGrain { private static readonly Immutable EmptyReminders = new ReminderResponse { Reminders = [] }.AsImmutable(); private readonly IReminderTable _reminderTable; public DashboardRemindersGrain(IServiceProvider serviceProvider) { _reminderTable = serviceProvider.GetService(typeof(IReminderTable)) as IReminderTable; } public async Task> GetReminders(int pageNumber, int pageSize) { if (_reminderTable == null) { return EmptyReminders; } var reminderData = await _reminderTable.ReadRows(0, 0xffffffff); if(!reminderData.Reminders.Any()) { return EmptyReminders; } return new ReminderResponse { Reminders = reminderData .Reminders .OrderBy(x => x.StartAt) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .Select(ToReminderInfo) .ToArray(), Count = reminderData.Reminders.Count }.AsImmutable(); } private static ReminderInfo ToReminderInfo(ReminderEntry entry) { return new ReminderInfo { PrimaryKey = entry.GrainId.Key.ToString(), GrainReference = entry.GrainId.ToString(), Name = entry.ReminderName, StartAt = entry.StartAt, Period = entry.Period, }; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Grains/SiloGrainProxy.cs ================================================ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Dashboard.Core; using Orleans.Dashboard.Model; using Orleans.Placement; using Orleans.Runtime; using Orleans.Runtime.MembershipService.SiloMetadata; namespace Orleans.Dashboard.Implementation.Grains; [PreferLocalPlacement] internal sealed class SiloGrainProxy : Grain, ISiloGrainProxy { private readonly ISiloGrainService _siloGrainService; private readonly Dictionary _siloMetadata; public SiloGrainProxy(ISiloGrainClient siloGrainClient, ISiloMetadataCache siloMetadataCache = null) { var siloAddress = SiloAddress.FromParsableString(this.GetPrimaryKeyString()); _siloGrainService = siloGrainClient.GrainService(siloAddress); _siloMetadata = new Dictionary(siloMetadataCache?.GetSiloMetadata(siloAddress).Metadata ?? ImmutableDictionary.Empty); } public Task SetVersion(string orleans, string host) => _siloGrainService.SetVersion(orleans, host); public Task ReportCounters(Immutable stats) => _siloGrainService.ReportCounters(stats); public Task Enable(bool enabled) => _siloGrainService.Enable(enabled); public Task>> GetExtendedProperties() => _siloGrainService.GetExtendedProperties(); public Task>> GetMetadata() => Task.FromResult(_siloMetadata.AsImmutable()); public Task> GetRuntimeStatistics() => _siloGrainService.GetRuntimeStatistics(); public Task> GetCounters() => _siloGrainService.GetCounters(); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/Helpers/GrainStateHelper.cs ================================================ using Orleans.Core; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Orleans.Dashboard.Implementation.Helpers; internal static class GrainStateHelper { public static (object, string) GetGrainId(string id, Type implementationType) { object grainId = null; string keyExtension = ""; var splitedGrainId = id.Split(","); try { if (implementationType.IsAssignableTo(typeof(IGrainWithGuidCompoundKey))) { if (splitedGrainId.Length != 2) throw new InvalidOperationException("Inform grain id in format `{ id},{additionalKey}`"); grainId = Guid.Parse(splitedGrainId.First()); keyExtension = splitedGrainId.Last(); } else if (implementationType.IsAssignableTo(typeof(IGrainWithIntegerCompoundKey))) { if (splitedGrainId.Length != 2) throw new InvalidOperationException("Inform grain id in format {id},{additionalKey}"); grainId = Convert.ToInt64(splitedGrainId.First()); keyExtension = splitedGrainId.Last(); } else if (implementationType.IsAssignableTo(typeof(IGrainWithIntegerKey))) { grainId = Convert.ToInt64(id); } else if (implementationType.IsAssignableTo(typeof(IGrainWithGuidKey))) { grainId = Guid.Parse(id); } else if (implementationType.IsAssignableTo(typeof(IGrainWithStringKey))) { grainId = id; } } catch (Exception ex) { throw new Exception("Error when trying to convert grain Id", ex); } return (grainId, keyExtension); } public static IEnumerable GetPropertiesAndFieldsForGrainState(Type implementationType) { var impProperties = implementationType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); var impFields = implementationType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); var filterProps = impProperties .Where(w => w.PropertyType.IsAssignableTo(typeof(IStorage))) .Select(s => s.PropertyType.GetGenericArguments().First()); var filterFields = impFields .Where(w => w.FieldType.IsAssignableTo(typeof(IStorage))) .Select(s => s.FieldType.GetGenericArguments().First()); return filterProps.Union(filterFields); } public static MethodInfo GenerateGetGrainMethod(IGrainFactory grainFactory, object grainId, string keyExtension) { if (string.IsNullOrWhiteSpace(keyExtension)) { return grainFactory.GetType().GetMethods() .First(w => w.Name == "GetGrain" && w.GetParameters().Count() == 2 && w.GetParameters()[0].ParameterType == typeof(Type) && w.GetParameters()[1].ParameterType == grainId.GetType()); } else { return grainFactory.GetType().GetMethods() .First(w => w.Name == "GetGrain" && w.GetParameters().Count() == 3 && w.GetParameters()[0].ParameterType == typeof(Type) && w.GetParameters()[1].ParameterType == grainId.GetType() && w.GetParameters()[2].ParameterType == typeof(string)); } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/SiloGrainClient.cs ================================================ using System; using Orleans.Dashboard.Core; using Orleans.Runtime; using Orleans.Runtime.Services; namespace Orleans.Dashboard.Implementation; internal sealed class SiloGrainClient(IServiceProvider serviceProvider) : GrainServiceClient(serviceProvider), ISiloGrainClient { public ISiloGrainService GrainService(SiloAddress destination) => GetGrainService(destination); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/SiloGrainService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Dashboard.Metrics; using Orleans.Dashboard.Model; using Orleans.Dashboard.Core; namespace Orleans.Dashboard.Implementation; internal sealed class SiloGrainService : GrainService, ISiloGrainService { private const int DefaultTimerIntervalMs = 1000; // 1 second private readonly Queue _statistics; private readonly Dictionary _counters = []; private readonly DashboardOptions _options; private readonly IGrainProfiler _profiler; private readonly IGrainFactory _grainFactory; private readonly ILogger _logger; private IDisposable _timer; private string _versionOrleans; private string _versionHost; public SiloGrainService( GrainId grainId, Silo silo, ILoggerFactory loggerFactory, IGrainProfiler profiler, IOptions options, IGrainFactory grainFactory) : base(grainId, silo, loggerFactory) { _profiler = profiler; _options = options.Value; _grainFactory = grainFactory; _statistics = new Queue(_options.HistoryLength + 1); _logger = loggerFactory.CreateLogger(); } public override async Task Start() { foreach (var _ in Enumerable.Range(1, _options.HistoryLength)) { _statistics.Enqueue(null); } var updateInterval = TimeSpan.FromMilliseconds( Math.Max(_options.CounterUpdateIntervalMs, DefaultTimerIntervalMs) ); try { _timer = RegisterTimer(x => CollectStatistics((bool) x), true, updateInterval, updateInterval); await CollectStatistics(false); } catch (InvalidOperationException) { _logger.LogWarning("Not running in Orleans runtime"); } await base.Start(); } private async Task CollectStatistics(bool canDeactivate) { var managementGrain = _grainFactory.GetGrain(0); try { var siloAddress = SiloAddress.FromParsableString(this.GetPrimaryKeyString()); var results = (await managementGrain.GetRuntimeStatistics([siloAddress])).FirstOrDefault(); _statistics.Enqueue(results); while (_statistics.Count > _options.HistoryLength) { _statistics.Dequeue(); } } catch (Exception) { // we can't get the silo stats, it's probably dead, so kill the grain if (canDeactivate) { _timer?.Dispose(); _timer = null; } } } public Task SetVersion(string orleans, string host) { _versionOrleans = orleans; _versionHost = host; return Task.CompletedTask; } public Task>> GetExtendedProperties() { var results = new Dictionary { ["hostVersion"] = _versionHost, ["orleansVersion"] = _versionOrleans }; return Task.FromResult(results.AsImmutable()); } public Task ReportCounters(Immutable reportCounters) { foreach (var counter in reportCounters.Value) { if (!string.IsNullOrWhiteSpace(counter.Name)) { _counters[counter.Name] = counter; } } return Task.CompletedTask; } public Task> GetRuntimeStatistics() { return Task.FromResult(_statistics.ToArray().AsImmutable()); } public Task> GetCounters() { return Task.FromResult(_counters.Values.OrderBy(x => x.Name).ToArray().AsImmutable()); } public Task Enable(bool enabled) { _profiler.Enable(enabled); return Task.CompletedTask; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Implementation/TraceWriter.cs ================================================ using System; using System.Globalization; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Orleans.Dashboard.Implementation; internal class TraceWriter : IAsyncDisposable { private readonly DashboardLogger _traceListener; private readonly HttpContext _context; private readonly StreamWriter _writer; public TraceWriter(DashboardLogger traceListener, HttpContext context) { _context = context; _writer = new StreamWriter(context.Response.Body); _traceListener = traceListener; _traceListener.Add(Write); } private void Write(EventId eventId, LogLevel level, string message) { var task = WriteAsync(eventId, level, message); task.Ignore(); } public async Task WriteAsync(string message) { try { await _writer.WriteAsync(message); await _writer.WriteAsync("\r\n"); await _writer.FlushAsync(); await _context.Response.Body.FlushAsync(); } catch (ObjectDisposedException) { } } public async Task WriteAsync(EventId eventId, LogLevel level, string message) { try { await _writer.WriteAsync($"{DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)} {GetLogLevelString(level)}: [{eventId,8}] {message}\r\n"); await _writer.FlushAsync(); await _context.Response.Body.FlushAsync(); } catch (ObjectDisposedException) { } } public ValueTask DisposeAsync() { _traceListener.Remove(Write); return _writer.DisposeAsync(); } private static string GetLogLevelString(LogLevel logLevel) { switch (logLevel) { case LogLevel.Trace: return "trce"; case LogLevel.Debug: return "dbug"; case LogLevel.Information: return "info"; case LogLevel.Warning: return "warn"; case LogLevel.Error: return "fail"; case LogLevel.Critical: return "crit"; default: throw new ArgumentOutOfRangeException(nameof(logLevel)); } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/Details/ISiloDetailsProvider.cs ================================================ using System.Threading.Tasks; using Orleans.Dashboard.Model; namespace Orleans.Dashboard.Metrics.Details; internal interface ISiloDetailsProvider { Task GetSiloDetails(); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/GrainProfiler.cs ================================================ using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Runtime; using Orleans.Dashboard.Model; using Orleans.Dashboard.Metrics.TypeFormatting; using System; using System.Collections.Concurrent; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Serialization.TypeSystem; using Orleans.Dashboard.Core; namespace Orleans.Dashboard.Metrics; internal sealed class GrainProfiler( IGrainFactory grainFactory, ILogger logger, ILocalSiloDetails localSiloDetails, IOptions options) : IGrainProfiler, ILifecycleParticipant { private ConcurrentDictionary _grainTrace = new(); private Timer _timer; private string _siloAddress; private bool _isEnabled; private IDashboardGrain _dashboardGrain; public bool IsEnabled => options.Value.TraceAlways || _isEnabled; public void Participate(ISiloLifecycle lifecycle) => lifecycle.Subscribe(ServiceLifecycleStage.Last, ct => OnStart(), ct => OnStop()); private Task OnStart() { _timer = new Timer(ProcessStats, null, 1 * 1000, 1 * 1000); return Task.CompletedTask; } private Task OnStop() { _timer.Dispose(); return Task.CompletedTask; } public void Track(double elapsedMs, Type grainType, [CallerMemberName] string methodName = null, bool failed = false) { ArgumentNullException.ThrowIfNull(grainType); if (string.IsNullOrWhiteSpace(methodName)) { throw new ArgumentException("Method name cannot be null or empty.", nameof(methodName)); } if (!IsEnabled) { return; } // This is the method that Orleans uses to convert a grain type into the grain type name when calling the GetSimpleGrainStatistics method var grainName = RuntimeTypeNameFormatter.Format(grainType); var grainMethodKey = $"{grainName}.{methodName}"; var exceptionCount = (failed ? 1 : 0); _grainTrace.AddOrUpdate(grainMethodKey, _ => new SiloGrainTraceEntry { Count = 1, ExceptionCount = exceptionCount, ElapsedTime = elapsedMs, Grain = grainName, Method = methodName }, (_, last) => { last.Count += 1; last.ElapsedTime += elapsedMs; if (failed) { last.ExceptionCount += exceptionCount; } return last; }); } private void ProcessStats(object state) { if (!IsEnabled) { return; } var currentTrace = Interlocked.Exchange(ref _grainTrace, new ConcurrentDictionary()); if (!currentTrace.IsEmpty) { _siloAddress ??= localSiloDetails.SiloAddress.ToParsableString(); var items = currentTrace.Values.ToArray(); foreach (var item in items) { item.Grain = TypeFormatter.Parse(item.Grain); } try { _dashboardGrain ??= grainFactory.GetGrain(0); _dashboardGrain.SubmitTracing(_siloAddress, items.AsImmutable()).Ignore(); } catch (Exception ex) { logger.LogWarning(100001, ex, "Exception thrown sending tracing to dashboard grain"); } } } public void Enable(bool enabled) => _isEnabled = enabled; } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/GrainProfilerExtensions.cs ================================================ using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Orleans.Dashboard.Metrics; internal static class GrainProfilerExtensions { public static void Track(this IGrainProfiler profiler, double elapsedMs, [CallerMemberName] string methodName = null, bool failed = false) => profiler.Track(elapsedMs, typeof(T), methodName, failed); public static async Task TrackAsync(this IGrainProfiler profiler, Func handler, [CallerMemberName] string methodName = null) { if (!profiler.IsEnabled) { await handler(); return; } var stopwatch = Stopwatch.StartNew(); try { await handler(); stopwatch.Stop(); profiler.Track(stopwatch.Elapsed.TotalMilliseconds, typeof(T), methodName); } catch (Exception) { stopwatch.Stop(); profiler.Track(stopwatch.Elapsed.TotalMilliseconds, typeof(T), methodName, true); throw; } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/GrainTraceEntryEqualityComparer.cs ================================================ using Orleans.Dashboard.Model; using System; using System.Collections.Generic; namespace Orleans.Dashboard.Metrics.History; internal sealed class GrainTraceEqualityComparer : IEqualityComparer { private readonly bool _withSiloAddress; public static readonly GrainTraceEqualityComparer ByGrainAndMethod = new(false); public static readonly GrainTraceEqualityComparer ByGrainAndMethodAndSilo = new(true); private GrainTraceEqualityComparer(bool withSiloAddress) { _withSiloAddress = withSiloAddress; } public bool Equals(GrainTraceEntry x, GrainTraceEntry y) { if (ReferenceEquals(x, y)) { return true; } if (x == null || y == null) { return false; } var isEquals = string.Equals(x.Grain, y.Grain, StringComparison.OrdinalIgnoreCase) && string.Equals(x.Method, y.Method, StringComparison.OrdinalIgnoreCase); if (_withSiloAddress) { isEquals &= string.Equals(x.SiloAddress, y.SiloAddress, StringComparison.OrdinalIgnoreCase); } return isEquals; } public int GetHashCode(GrainTraceEntry obj) { if (obj == null) { return 0; } var hashCode = 17; if (obj.Grain != null) { hashCode = hashCode * 23 + (obj.Grain?.GetHashCode() ?? 0); } if (obj.Grain != null) { hashCode = hashCode * 23 + (obj.Method?.GetHashCode() ?? 0); } if (obj.Grain != null && _withSiloAddress) { hashCode = hashCode * 23 + (obj.SiloAddress?.GetHashCode() ?? 0); } return hashCode; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/HistoryEntry.cs ================================================ using System; namespace Orleans.Dashboard.Metrics.History; internal record struct HistoryEntry { public DateTime Period { get; set; } public long PeriodNumber { get; set; } public long Count { get; set; } public long ExceptionCount { get; set; } public double ElapsedTime { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/HistoryKey.cs ================================================ namespace Orleans.Dashboard.Metrics.History; internal record struct HistoryKey(string SiloAddress, string Grain, string Method); ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/ITraceHistory.cs ================================================ using System; using System.Collections.Generic; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; namespace Orleans.Dashboard.Metrics.History; internal interface ITraceHistory { void Add(DateTime time, string siloAddress, SiloGrainTraceEntry[] grainTrace); Dictionary QueryAll(); Dictionary QuerySilo(string siloAddress); Dictionary> QueryGrain(string grain); IEnumerable GroupByGrainAndSilo(); IEnumerable AggregateByGrainMethod(string[] exclusions = null); } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/RingBuffer.cs ================================================ namespace Orleans.Dashboard.Metrics.History; internal sealed class RingBuffer(int capacity) { private readonly T[] _items = new T[capacity]; private int _startIndex; public int Count { get; private set; } public T this[int index] { get { var finalIndex = (_startIndex + index) % _items.Length; return _items[finalIndex]; } } public void Add(T item) { var newIndex = (_startIndex + Count) % _items.Length; _items[newIndex] = item; if (Count < _items.Length) { Count++; } else { _startIndex++; } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/History/TraceHistory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Dashboard.Core; using Orleans.Dashboard.Model; using Orleans.Dashboard.Model.History; namespace Orleans.Dashboard.Metrics.History; internal sealed class TraceHistory(int capacity = 100) : ITraceHistory { private readonly Dictionary> _history = new(100); public Dictionary> QueryGrain(string grain) { var results = new Dictionary>(); foreach (var group in _history.Where(x => x.Key.Grain == grain).GroupBy(x => (x.Key.Grain, x.Key.Method))) { var grainMethodKey = $"{group.Key.Grain}.{group.Key.Method}"; results[grainMethodKey] = GetTracings(group); } return results; } public Dictionary QueryAll() => GetTracings([.. _history]); public Dictionary QuerySilo(string siloAddress) => GetTracings([.. _history.Where(x => string.Equals(x.Key.SiloAddress, siloAddress, StringComparison.OrdinalIgnoreCase))]); private Dictionary GetTracings(IEnumerable>> traces) { var time = GetRetirementWindow(DateTime.UtcNow); var periodStart = time.ToPeriodNumber(); var aggregations = new GrainTraceEntry[capacity]; foreach (var traceList in traces) { var bufferList = traceList.Value; var bufferCount = bufferList.Count; for (var j = 0; j < bufferCount; j++) { var trace = bufferList[j]; var resultIndex = trace.PeriodNumber - periodStart; if (resultIndex >= 0 && resultIndex < capacity) { var entry = aggregations[resultIndex] ?? new GrainTraceEntry(); entry.Count += trace.Count; entry.ElapsedTime += trace.ElapsedTime; entry.ExceptionCount += trace.ExceptionCount; aggregations[resultIndex] = entry; } } } var result = new Dictionary(capacity); for (var i = 0; i < capacity; i++) { time = time.AddSeconds(1); var periodKey = time.ToPeriodString(); var entry = aggregations[i] ??= new GrainTraceEntry(); entry.Period = time; entry.PeriodKey = periodKey; result[periodKey] = entry; } return result; } public void Add(DateTime now, string siloAddress, SiloGrainTraceEntry[] grainTrace) { var periodNumber = now.ToPeriodNumber(); foreach (var trace in grainTrace) { var key = new HistoryKey(siloAddress, trace.Grain, trace.Method); if (!_history.TryGetValue(key, out var historyBuffer)) { historyBuffer = new RingBuffer(capacity); _history[key] = historyBuffer; } historyBuffer.Add(new HistoryEntry { Period = now, PeriodNumber = periodNumber, ExceptionCount = trace.ExceptionCount, ElapsedTime = trace.ElapsedTime, Count = trace.Count }); } } private DateTime GetRetirementWindow(DateTime now) => now.AddSeconds(-capacity); public IEnumerable GroupByGrainAndSilo() { var time = GetRetirementWindow(DateTime.UtcNow); var periodStart = time.ToPeriodNumber(); return _history.GroupBy(x => (x.Key.Grain, x.Key.SiloAddress)).Select(group => { var result = new TraceAggregate { SiloAddress = group.Key.SiloAddress, Grain = group.Key.Grain, }; foreach (var traceList in group) { var bufferList = traceList.Value; var bufferCount = bufferList.Count; for (var i = 0; i < bufferCount; i++) { var record = bufferList[i]; if (record.PeriodNumber < periodStart) continue; result.Count += record.Count; result.ExceptionCount += record.ExceptionCount; result.ElapsedTime += record.ElapsedTime; } } return result; }); } public IEnumerable AggregateByGrainMethod(string[] exclusions) { var time = GetRetirementWindow(DateTime.UtcNow); var periodStart = time.ToPeriodNumber(); var history = exclusions != null && exclusions.Length > 0 ? _history.Where(x => !exclusions.Any(f => x.Key.Grain.StartsWith(f, StringComparison.OrdinalIgnoreCase))) : _history; return history.GroupBy(x => (x.Key.Grain, x.Key.Method)).Select(group => { var result = new GrainMethodAggregate { Grain = group.Key.Grain, Method = group.Key.Method, NumberOfSamples = capacity }; foreach (var traceList in group) { var bufferList = traceList.Value; var bufferCount = bufferList.Count; for (var i = 0; i < bufferCount; i++) { var record = bufferList[i]; if (record.PeriodNumber < periodStart) continue; result.Count += record.Count; result.ExceptionCount += record.ExceptionCount; result.ElapsedTime += record.ElapsedTime; } } return result; }); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/IGrainProfiler.cs ================================================ using System; using System.Runtime.CompilerServices; namespace Orleans.Dashboard.Metrics; /// /// Provides profiling capabilities for grain method invocations, tracking execution time and failures. /// internal interface IGrainProfiler { /// /// Records a grain method invocation for profiling purposes. /// /// The elapsed time in milliseconds for the method invocation. /// The type of the grain. /// The name of the method that was invoked. Automatically captured from the caller if not specified. /// True if the method invocation resulted in an exception; otherwise, false. void Track(double elapsedMs, Type grainType, [CallerMemberName] string methodName = null, bool failed = false); /// /// Enables or disables the grain profiler. /// /// True to enable profiling; false to disable. void Enable(bool enabled); /// /// Gets a value indicating whether the grain profiler is currently enabled. /// bool IsEnabled { get; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/TypeFormatting/ParseState.cs ================================================ namespace Orleans.Dashboard.Metrics.TypeFormatting; internal enum ParseState { TypeNameSection, GenericCount, GenericArray, TypeArray } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/TypeFormatting/Token.cs ================================================ namespace Orleans.Dashboard.Metrics.TypeFormatting; internal readonly struct Token(TokenType type, string value) { public TokenType Type { get; } = type; public string Value { get; } = value; public override string ToString() => $"{Type} = {Value}"; } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/TypeFormatting/TokenType.cs ================================================ namespace Orleans.Dashboard.Metrics.TypeFormatting; internal enum TokenType { TypeNameSection, GenericCount, GenericArrayStart, GenericArrayEnd, TypeArrayStart, TypeArrayEnd, GenericSeparator, TypeSectionSeparator } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Metrics/TypeFormatting/TypeFormatter.cs ================================================ using Orleans.Serialization.TypeSystem; using System.Collections.Concurrent; using System.Linq; namespace Orleans.Dashboard.Metrics.TypeFormatting; internal sealed class TypeFormatter { private static readonly ConcurrentDictionary Cache = new(); public static string Parse(string typeName) => Cache.GetOrAdd(typeName, Format); private static string Format(string typeName) { var typeInfo = RuntimeTypeNameParser.Parse(typeName); return Format(typeInfo); } private static string Format(TypeSpec typeSpec) { switch (typeSpec) { case AssemblyQualifiedTypeSpec qualified: return Format(qualified.Type); case ConstructedGenericTypeSpec constructed: return $"{Format(constructed.UnconstructedType)}<{string.Join(", ", constructed.Arguments.Select(Format))}>"; default: var name = typeSpec.Format(); const string SystemPrefix = "System."; if (name.StartsWith(SystemPrefix)) { name = name[SystemPrefix.Length..]; } var genericCardinalityIndex = name.IndexOf('`'); if (genericCardinalityIndex > 0) { name = name[..genericCardinalityIndex]; } return name; } } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/DashboardCounters.cs ================================================ using System; using System.Collections.Immutable; using System.Linq; namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.DashboardCounters")] internal class DashboardCounters { [Id(0)] public SiloDetails[] Hosts { get; set; } = Array.Empty(); [Id(1)] public SimpleGrainStatisticCounter[] SimpleGrainStats { get; set; } = Array.Empty(); [Id(2)] public int TotalActiveHostCount { get; set; } [Id(3)] public ImmutableQueue TotalActiveHostCountHistory { get; set; } [Id(4)] public int TotalActivationCount { get; set; } [Id(5)] public ImmutableQueue TotalActivationCountHistory { get; set; } = ImmutableQueue.Empty; public DashboardCounters() { } public DashboardCounters(int initialLength) { var values = Enumerable.Repeat(1, initialLength).Select(x => 0); TotalActivationCountHistory = ImmutableQueue.CreateRange(values); TotalActiveHostCountHistory = ImmutableQueue.CreateRange(values); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/GrainTraceEntry.cs ================================================ using System; namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.GrainTraceEntry")] internal class GrainTraceEntry { [Id(0)] public string PeriodKey { get; set; } [Id(1)] public DateTime Period { get; set; } [Id(2)] public string SiloAddress { get; set; } [Id(3)] public string Grain { get; set; } [Id(4)] public string Method { get; set; } [Id(5)] public long Count { get; set; } [Id(6)] public long ExceptionCount { get; set; } [Id(7)] public double ElapsedTime { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/History/GrainMethodAggregate.cs ================================================ namespace Orleans.Dashboard.Model.History; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.History.GrainMethodAggregate")] internal struct GrainMethodAggregate { [Id(0)] public string Grain { get; set; } [Id(1)] public string Method { get; set; } [Id(2)] public long Count { get; set; } [Id(3)] public long ExceptionCount { get; set; } [Id(4)] public double ElapsedTime { get; set; } [Id(5)] public long NumberOfSamples { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/History/TraceAggregate.cs ================================================ namespace Orleans.Dashboard.Model.History; internal struct TraceAggregate { public string SiloAddress { get; set; } public string Grain { get; set; } public long Count { get; set; } public long ExceptionCount { get; set; } public double ElapsedTime { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/ReminderInfo.cs ================================================ using System; namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.ReminderInfo")] internal sealed class ReminderInfo { [Id(0)] public string GrainReference { get; set; } [Id(1)] public string Name { get; set; } [Id(2)] public DateTime StartAt { get; set; } [Id(3)] public TimeSpan Period { get; set; } [Id(4)] public string PrimaryKey { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/ReminderResponse.cs ================================================ namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.ReminderResponse")] internal sealed class ReminderResponse { [Id(0)] public int Count { get; set; } [Id(1)] public ReminderInfo[] Reminders { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/SiloDetails.cs ================================================ using Orleans.Runtime; namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.SiloDetails")] internal class SiloDetails { [Id(0)] public int FaultZone { get; set; } [Id(1)] public string HostName { get; set; } [Id(2)] public string IAmAliveTime { get; set; } [Id(3)] public int ProxyPort { get; set; } [Id(4)] public string RoleName { get; set; } [Id(5)] public string SiloAddress { get; set; } [Id(6)] public string SiloName { get; set; } [Id(7)] public string StartTime { get; set; } [Id(8)] public string Status { get; set; } [Id(9)] public int UpdateZone { get; set; } [Id(10)] public SiloStatus SiloStatus { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/SiloGrainTraceEntry.cs ================================================ namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.SiloGrainTraceEntry")] internal class SiloGrainTraceEntry { [Id(0)] public string Grain { get; set; } [Id(1)] public string Method { get; set; } [Id(2)] public long Count { get; set; } [Id(3)] public long ExceptionCount { get; set; } [Id(4)] public double ElapsedTime { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/SimpleGrainStatisticCounter.cs ================================================ namespace Orleans.Dashboard.Model; [GenerateSerializer] [Alias("Orleans.Dashboard.Model.SimpleGrainStatisticCounter")] internal sealed class SimpleGrainStatisticCounter { [Id(0)] public int ActivationCount { get; set; } [Id(1)] public string GrainType { get; set; } [Id(2)] public string SiloAddress { get; set; } [Id(3)] public double TotalAwaitTime { get; set; } [Id(4)] public long TotalCalls { get; set; } [Id(5)] public double CallsPerSecond { get; set; } [Id(6)] public object TotalSeconds { get; set; } [Id(7)] public long TotalExceptions { get; set; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Model/StatCounter.cs ================================================ namespace Orleans.Dashboard.Model; [GenerateSerializer] [Immutable] [Alias("Orleans.Dashboard.Model.StatCounter")] internal readonly struct StatCounter { [Id(0)] public readonly string Name; [Id(1)] public readonly string Value; [Id(2)] public readonly string Delta; public StatCounter(string name, string value, string delta) : this() { Name = name; Value = value; Delta = delta; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/Orleans.Dashboard.Frontend.targets ================================================ $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..\Orleans.Dashboard.App')) $([System.IO.Path]::GetFullPath('$(NpmAppRootDirectory)\dist\')) $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\.azdo\.npmrc')) $(BaseIntermediateOutputPath)frontend.build.marker --userconfig "$(AzureDevOpsNpmrcFile)" <_DistFiles Include="$(NpmBuildOutput)**\*" /> <_FrontendEmbeddedResources Include="@(EmbeddedResource)" Condition="'%(Link)' != '' and $([System.String]::Copy('%(Link)').StartsWith('wwwroot'))" /> ================================================ FILE: src/Dashboard/Orleans.Dashboard/Orleans.Dashboard.csproj ================================================  Microsoft.Orleans.Dashboard Microsoft Orleans Dashboard Real-time monitoring and visualization dashboard for Microsoft Orleans showing grain statistics, performance metrics, and cluster health. $(DefaultTargetFrameworks) true README.md Orleans.Dashboard ================================================ FILE: src/Dashboard/Orleans.Dashboard/README.md ================================================ # Microsoft Orleans Dashboard ## Introduction Microsoft Orleans Dashboard is a web-based monitoring and diagnostics tool for Orleans applications. It provides real-time visualization of grain activations, runtime statistics, silo health, and performance metrics through an intuitive web interface. ## Features - **Real-time Grain Statistics**: Monitor active grain counts, call rates, and exception rates - **Silo Health Monitoring**: View silo status, CPU usage, memory consumption, and network activity - **Performance Metrics**: Track method call latencies, throughput, and error rates - **Grain Method Profiling**: Analyze individual grain method performance - **Reminders Tracking**: View scheduled reminders across the cluster - **Dashboard Customization**: Configure authentication, port, and other options ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Dashboard ``` ## Example - Adding Dashboard to a Silo ```csharp using Orleans.Dashboard; var builder = WebApplication.CreateBuilder(args); // Configure Orleans builder.UseOrleans(siloBuilder => { siloBuilder.UseLocalhostClustering(); siloBuilder.UseInMemoryReminderService(); siloBuilder.AddMemoryGrainStorageAsDefault(); // Add the dashboard siloBuilder.AddDashboard(); }); var app = builder.Build(); // Map dashboard endpoints app.MapOrleansDashboard(); app.Run(); ``` The dashboard will be accessible at the application's base URL (e.g., `http://localhost:5000/`). ## Example - Adding Dashboard to a Separate Web Application For scenarios where you want to host the dashboard separately from your silos: ```csharp using Orleans.Dashboard; using System.Net; var builder = WebApplication.CreateBuilder(args); // Configure Orleans client builder.UseOrleansClient(clientBuilder => { clientBuilder.UseStaticClustering(options => options.Gateways.Add(new IPEndPoint(IPAddress.Loopback, 30000).ToGatewayUri())); // Add dashboard services clientBuilder.AddDashboard(); }); var app = builder.Build(); // Map dashboard endpoints app.MapOrleansDashboard(); await app.RunAsync(); ``` ## Configuration Options The dashboard can be configured with various options: ```csharp siloBuilder.AddDashboard(options => { options.CounterUpdateIntervalMs = 1000; // Metrics update interval (default: 1000ms) }); ``` You can customize the route prefix when mapping dashboard endpoints: ```csharp // Map dashboard at a custom path app.MapOrleansDashboard(routePrefix: "/dashboard"); // Add authentication app.MapOrleansDashboard().RequireAuthorization(); ``` ## Dashboard URL Once configured, the dashboard is accessible at: - Default: `http://localhost:{AppPort}/` (where AppPort is your web application's port) - With routePrefix: `http://localhost:{AppPort}/{routePrefix}/` ## Examples For complete working examples, see the playground projects: - `playground/DashboardCohosted` - Dashboard cohosted with Orleans in a web application - `playground/DashboardSeparateHost` - Dashboard in a separate web application connecting to an Orleans cluster ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans observability](https://learn.microsoft.com/en-us/dotnet/orleans/host/monitoring/) - [Server configuration](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Dashboard/Orleans.Dashboard/ServiceCollectionExtensions.cs ================================================ #nullable enable using System; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Hosting; using Orleans.Runtime; using Orleans.Dashboard.Implementation; using Orleans.Dashboard.Implementation.Details; using Orleans.Dashboard.Metrics; using Orleans.Dashboard.Metrics.Details; using Orleans.Dashboard.Model; using System.Diagnostics.CodeAnalysis; using Orleans.Dashboard.Core; using Microsoft.AspNetCore.Mvc; using Orleans.Configuration.Internal; // ReSharper disable CheckNamespace namespace Orleans.Dashboard; /// /// Provides extension methods for configuring and integrating the Orleans Dashboard. /// public static class ServiceCollectionExtensions { /// /// Adds Orleans Dashboard services to the silo builder. /// /// The silo builder. /// Optional configuration action for . /// The silo builder for method chaining. public static ISiloBuilder AddDashboard(this ISiloBuilder siloBuilder, Action? configureOptions = null) { siloBuilder.Services.AddOrleansDashboardForSiloCore(configureOptions); return siloBuilder; } internal static IServiceCollection AddOrleansDashboardForSiloCore( this IServiceCollection services, Action? configureOptions = null) { services.AddGrainService(); services.AddHostedService(); services.Configure(configureOptions ?? (x => { })); services.AddSingleton(); services.AddOptions(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddSingleton(); services.AddSingleton(c => (ILifecycleParticipant)c.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(c => c.GetService() switch { not null => c.GetRequiredService(), null => c.GetRequiredService(), }); services.TryAddSingleton(GrainProfilerFilter.DefaultGrainMethodFormatter); return services; } /// /// Maps Orleans Dashboard endpoints using ASP.NET Core minimal APIs. /// Returns an that can be used to apply authentication, /// authorization, or other endpoint configuration. /// /// /// /// // Basic usage /// app.MapOrleansDashboard(); /// /// // With authentication /// app.MapOrleansDashboard().RequireAuthorization(); /// /// // With custom base path /// app.MapOrleansDashboard(routePrefix: "/dashboard"); /// /// public static RouteGroupBuilder MapOrleansDashboard( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string? routePrefix = null) { // Create static assets provider var assets = endpoints.ServiceProvider.GetService() ?? throw new InvalidOperationException("Orleans Dashboard services have not been registered. " + "Please call AddDashboard on ISiloBuilder or IClientBuilder."); // Create a route group for all dashboard endpoints var group = endpoints.MapGroup(routePrefix ?? ""); // Static assets - these match the paths referenced in the built CSS/HTML // When a routePrefix is specified, redirect requests without trailing slash to include it. // This ensures relative asset paths (like index.min.js) resolve correctly. group.MapGet("/", (HttpContext ctx) => { if (!string.IsNullOrEmpty(routePrefix) && ctx.Request.Path.Value?.EndsWith('/') == false) { // Redirect to the same path with a trailing slash, preserving the query string var redirectUrl = $"{ctx.Request.PathBase}{ctx.Request.Path}/{ctx.Request.QueryString}"; return Results.Redirect(redirectUrl, permanent: true); } return assets.ServeAsset("index.html", ctx); }); group.MapGet("/index.html", (HttpContext ctx) => assets.ServeAsset("index.html", ctx)); group.MapGet("/favicon.ico", (HttpContext ctx) => assets.ServeAsset("favicon.ico", ctx)); group.MapGet("/index.min.js", (HttpContext ctx) => assets.ServeAsset("index.min.js", ctx)); group.MapGet("/index.css", (HttpContext ctx) => assets.ServeAsset("index.css", ctx)); // Font files - catch-all route for /fonts/ directory group.MapGet("/fonts/{**path}", (string path, HttpContext ctx) => assets.ServeAsset($"fonts.{path}", ctx)); // Image files - catch-all route for /img/ directory group.MapGet("/img/{**path}", (string path, HttpContext ctx) => assets.ServeAsset($"img.{path}", ctx)); // API endpoints var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, IncludeFields = true, Converters = { new TimeSpanConverter() } }; group.MapGet("/version", () => Results.Json( new { version = typeof(EmbeddedAssetProvider).Assembly.GetName().Version?.ToString() }, jsonOptions)); group.MapGet("/DashboardCounters", async (string[] exclude, [FromServices] IDashboardClient client) => { try { var result = await client.DashboardCounters(SanitizeExclusionFilters(exclude)); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/ClusterStats", async ([FromServices] IDashboardClient client) => { try { var result = await client.ClusterStats(); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/Reminders", async ([FromServices] IDashboardClient client) => await GetRemindersPage(1, client, jsonOptions)); group.MapGet("/Reminders/{page:int}", async (int page, [FromServices] IDashboardClient client) => await GetRemindersPage(page, client, jsonOptions)); group.MapGet("/HistoricalStats/{*path}", async (string path, [FromServices] IDashboardClient client) => { try { var result = await client.HistoricalStats(path); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/SiloProperties/{*address}", async (string address, [FromServices] IDashboardClient client) => { try { var result = await client.SiloProperties(address); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/SiloMetadata/{*address}", async (string address, [FromServices] IDashboardClient client) => { try { var result = await client.SiloMetadata(address); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/SiloStats/{*address}", async (string address, [FromServices] IDashboardClient client) => { try { var result = await client.SiloStats(address); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/SiloCounters/{*address}", async (string address, [FromServices] IDashboardClient client) => { try { var result = await client.GetCounters(address); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/GrainStats/{*grainName}", async (string grainName, [FromServices] IDashboardClient client) => { try { var result = await client.GrainStats(grainName); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/TopGrainMethods", async (string[] exclude, [FromServices] IDashboardClient client) => { try { var result = await client.TopGrainMethods(take: 5, SanitizeExclusionFilters(exclude)); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/GrainState", async (HttpContext context, [FromServices] IDashboardClient client) => { try { context.Request.Query.TryGetValue("grainId", out var grainId); context.Request.Query.TryGetValue("grainType", out var grainType); var result = await client.GetGrainState(grainId, grainType); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/GrainTypes", async (string[] exclude, [FromServices] IDashboardClient client) => { try { var result = await client.GetGrainTypes(SanitizeExclusionFilters(exclude)); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } }); group.MapGet("/Trace", async (HttpContext context, [FromServices] IOptions opts, [FromServices] DashboardLogger logger) => { if (opts.Value.HideTrace) { return Results.Problem("The trace endpoint is disabled in the dashboard options.", title: "Trace Endpoint Disabled", statusCode: StatusCodes.Status403Forbidden); } await StreamTraceAsync(context, logger); return Results.Empty; }); return group; } private static async Task GetRemindersPage(int page, IDashboardClient client, JsonSerializerOptions jsonOptions) { try { var result = await client.GetReminders(page, 50); return Results.Json(result.Value, jsonOptions); } catch (SiloUnavailableException) { return CreateUnavailableResult(true); } catch { // If reminders are not configured, return empty response return Results.Json(new ReminderResponse { Reminders = [], Count = 0 }, jsonOptions); } } private static async Task StreamTraceAsync(HttpContext context, DashboardLogger logger) { var token = context.RequestAborted; try { await using var writer = new TraceWriter(logger, context); await writer.WriteAsync(""" ____ _ _____ _ _ _ / __ \ | | | __ \ | | | | | | | | | |_ __| | ___ __ _ _ __ ___ | | | | __ _ ___| |__ | |__ ___ __ _ _ __ __| | | | | | '__| |/ _ \/ _` | '_ \/ __| | | | |/ _` / __| '_ \| '_ \ / _ \ / _` | '__/ _` | | |__| | | | | __/ (_| | | | \__ \ | |__| | (_| \__ \ | | | |_) | (_) | (_| | | | (_| | \____/|_| |_|\___|\__,_|_| |_|___/ |_____/ \__,_|___/_| |_|_.__/ \___/ \__,_|_| \__,_| You are connected to the Orleans Dashboard log streaming service """); await Task.Delay(TimeSpan.FromMinutes(60), token); await writer.WriteAsync("Disconnecting after 60 minutes\r\n"); } catch (OperationCanceledException) { } } private static IResult CreateUnavailableResult(bool lostConnectivity) { var message = lostConnectivity ? "The dashboard has lost connectivity with the Orleans cluster" : "The dashboard is still trying to connect to the Orleans cluster"; return Results.Text(message, "text/plain", statusCode: 503); } private static string[] SanitizeExclusionFilters(string[] filters) { return (filters == null || filters.Length == 0) ? [] : [.. filters .Select(f => f.Trim()) .Where(f => !string.IsNullOrWhiteSpace(f)) .Select(f => string.Concat(f, '.'))]; } /// /// Adds Orleans Dashboard services to an Orleans client builder. /// This allows you to host the Orleans Dashboard application on an Orleans client, so long as the silos also have the dashboard added. /// /// The client builder. /// Optional configuration action for . /// The service collection for method chaining. public static IClientBuilder AddDashboard(this IClientBuilder clientBuilder, Action? configureOptions = null) { clientBuilder.Services.Configure(configureOptions ?? (x => { })); clientBuilder.Services.AddSingleton(); clientBuilder.Services.AddFromExisting(); clientBuilder.Services.AddSingleton(); clientBuilder.Services.AddSingleton(); return clientBuilder; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard/TimeSpanConverter.cs ================================================ using System; using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; namespace Orleans.Dashboard; internal sealed class TimeSpanConverter : JsonConverter { public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var text = reader.GetString(); return TimeSpan.Parse(text, CultureInfo.InvariantCulture); } public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString("c", CultureInfo.InvariantCulture)); } ================================================ FILE: src/Dashboard/Orleans.Dashboard.Abstractions/NoProfilingAttribute.cs ================================================ using System; namespace Orleans.Dashboard; /// /// Suppresses profiling for grain classes or methods, preventing them from being tracked by the Orleans Dashboard profiler. /// Apply this attribute to exclude specific grains or methods from performance metrics collection. /// /// /// /// [NoProfiling] /// public class MyInternalGrain : Grain /// { /// // This entire grain will not be profiled /// } /// /// public class MyGrain : Grain /// { /// [NoProfiling] /// public Task InternalMethod() /// { /// // This specific method will not be profiled /// } /// } /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)] public sealed class NoProfilingAttribute : Attribute { } ================================================ FILE: src/Dashboard/Orleans.Dashboard.Abstractions/Orleans.Dashboard.Abstractions.csproj ================================================  Microsoft.Orleans.Dashboard.Abstractions Microsoft Orleans Dashboard Abstractions Shared types used by the Orleans Dashboard package. $(DefaultTargetFrameworks) Orleans.Dashboard README.md ================================================ FILE: src/Dashboard/Orleans.Dashboard.Abstractions/README.md ================================================ # Microsoft Orleans Dashboard Core ## Introduction Microsoft Orleans Dashboard Core provides the foundational infrastructure and data collection services for the Orleans Dashboard. This package contains the core grain services, metrics collection, and data models used by the dashboard UI. ## Getting Started This package is typically referenced automatically when you install `Microsoft.Orleans.Dashboard`. You generally don't need to reference this package directly unless you're building custom monitoring solutions or extending the dashboard functionality. To use this package directly, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Dashboard.Abstractions ``` ## What's Included This package provides: - **Metrics Collection Services**: Grain-based services that collect runtime statistics - **Data Models**: Shared types for representing silo and grain statistics - **History Tracking**: Time-series data storage for performance metrics - **Grain Profiling**: Method-level performance tracking infrastructure ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans observability](https://learn.microsoft.com/en-us/dotnet/orleans/host/monitoring/) - [Orleans Dashboard package](https://www.nuget.org/packages/Microsoft.Orleans.Dashboard/) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variable files .env .env.* !.env.example # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist .output # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp .cache # Sveltekit cache directory .svelte-kit/ # vitepress build output **/.vitepress/dist # vitepress cache directory **/.vitepress/cache # Docusaurus cache and generated files .docusaurus # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # Firebase cache directory .firebase/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v3 .pnp.* .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions # Vite files vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/.npmrc ================================================ # This file is intentionally empty for local development # Azure DevOps builds use .npmrc.azdo instead ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/README.md ================================================ # Orleans Dashboard App This directory contains the frontend web application for the Orleans Dashboard, built with React and Vite. ## Structure ``` Orleans.Dashboard.App/ ├── src/ # React application source code │ ├── components/ # Reusable React components │ ├── grains/ # Grain-specific views │ ├── logstream/ # Log streaming components │ ├── overview/ # Dashboard overview components │ ├── reminders/ # Reminders view components │ ├── silos/ # Silo management components │ ├── lib/ # Utility libraries │ └── index.jsx # Application entry point ├── public/ # Static assets (copied to build output) │ ├── favicon.ico │ ├── OrleansLogo.png │ └── *.css, *.js # Third-party CSS/JS libraries ├── screenshots/ # Dashboard screenshots for documentation ├── index.html # HTML template ├── package.json # npm dependencies and scripts ├── vite.config.ts # Vite build configuration └── tsconfig.json # TypeScript configuration ``` ## Development ### Prerequisites - Node.js (v18 or later) - npm ### Setup ```bash cd src/Dashboard/Orleans.Dashboard.App npm install ``` ### Development Server Run the Vite development server for hot module replacement: ```bash npm run dev ``` This will start the development server at `http://localhost:5173` (or another port if 5173 is in use). ### Building To build the production bundle: ```bash npm run build ``` This builds the application to `../Orleans.Dashboard/wwwroot/` which is then embedded as resources in the Orleans.Dashboard C# project. ### Preview Production Build To preview the production build locally: ```bash npm run preview ``` ## Integration with Orleans.Dashboard The frontend application is automatically built when you build the Orleans.Dashboard C# project. The build process is managed by `Orleans.Dashboard.Frontend.targets` which: 1. Installs npm packages if `node_modules` doesn't exist 2. Runs `npm run build` to compile the frontend 3. Embeds the built assets as resources in the Orleans.Dashboard assembly The build uses incremental compilation, so the frontend is only rebuilt when source files change. ## Migration from Browserify This project was migrated from Browserify to Vite for improved: - Build performance - Development experience with HMR - Modern JavaScript/TypeScript support - Better tree-shaking and optimization The migration updated: - React from v15 to v18 - Chart.js from v2 to v4 - Build tooling from Browserify/Babel to Vite - Module system from CommonJS to ESM ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/index.html ================================================ Orleans Dashboard
Loading...
================================================ FILE: src/Dashboard/Orleans.Dashboard.App/package.json ================================================ { "name": "orleans-dashboard-app", "private": true, "version": "1.0.0", "description": "An admin dashboard for Microsoft Orleans", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "lint": "eslint ." }, "dependencies": { "@fortawesome/fontawesome-free": "^7.1.0", "admin-lte": "^4.0.0-rc.3", "bootstrap": "^5.3.3", "chart.js": "^4.4.8", "eventthing": "^1.0.7", "humanize-duration": "^3.32.1", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", "react-dom": "^18.3.1" }, "devDependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@types/humanize-duration": "^3.27.4", "@types/react": "^18.3.26", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.7.0", "eslint": "^9.18.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "typescript": "~5.7.3", "vite": "^5.4.21", "vite-plugin-commonjs": "^0.10.4" }, "author": "Microsoft", "license": "MIT" } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/alert.tsx ================================================ import React from 'react'; interface AlertProps { onClose?: () => void; title?: string; children?: React.ReactNode; } export default class Alert extends React.Component { constructor(props: AlertProps) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { if (this.props.onClose) { this.props.onClose(); } } render() { return (

{this.props.title || 'Error'}

{this.props.children}.
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/brand-header.tsx ================================================ import React from 'react'; import logo from '../assets/img/OrleansLogo.png'; export default function BrandHeader() { return ( ); } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/checkbox-filter.tsx ================================================ import React from 'react'; interface Settings { systemGrainsHidden: boolean; dashboardGrainsHidden: boolean; } interface CheckboxFilterProps { preference: 'system' | 'dashboard'; settings: Settings; onChange: (newSettings: Partial) => void; } interface CheckboxFilterState { hidden: boolean; } export default class CheckboxFilter extends React.Component { constructor(props: CheckboxFilterProps) { super(props); this.state = { hidden: this.props.preference === 'system' ? this.props.settings.systemGrainsHidden : this.props.settings.dashboardGrainsHidden }; this.handleChangeFilter = this.handleChangeFilter.bind(this); } handleChangeFilter(e: React.MouseEvent) { // Prevent link navigation. e.preventDefault(); const hidden = (e.target as HTMLAnchorElement).getAttribute('name') === 'hidden'; const newSettings: Partial = { [this.props.preference === 'system' ? 'systemGrainsHidden' : 'dashboardGrainsHidden']: hidden }; this.props.onChange(newSettings); this.setState({ hidden }); } render() { return ( ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/counter-widget.tsx ================================================ import React from 'react'; interface CounterWidgetProps { icon: string; title: string; counter: number | string; link?: string; style?: React.CSSProperties; } export default class CounterWidget extends React.Component { constructor(props: CounterWidgetProps) { super(props); this.renderMore = this.renderMore.bind(this); } renderMore() { if (!this.props.link) return null; return ( More info ); } render() { return (
{this.props.title} {this.props.counter} {this.renderMore()}
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/display-grain-state.tsx ================================================ import React from 'react'; interface DisplayGrainStateProps { code: string; } export default class DisplayGrainState extends React.Component { constructor(props: DisplayGrainStateProps) { super(props); } render() { return (
        {this.props.code}
      
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/gauge-widget.tsx ================================================ import React from 'react'; import { Doughnut as Chart } from 'react-chartjs-2'; interface GaugeWidgetProps { title: string; value: number; max: number; description: string; } interface GaugeWidgetState { width: number; } export default class GaugeWidget extends React.Component { private containerRef: React.RefObject; constructor(props: GaugeWidgetProps) { super(props); this.state = { width: 0 }; this.containerRef = React.createRef(); this.getWidth = this.getWidth.bind(this); this.getColour = this.getColour.bind(this); this.renderChart = this.renderChart.bind(this); } getWidth() { if (this.containerRef.current) { this.setState({ width: this.containerRef.current.offsetWidth }); } } getColour(alpha: number): string { return `rgba(120, 57, 136, ${alpha})`; /* var percent = 100 * this.props.value / this.props.max; if (percent > 90) return 'rgba(201,48,44,' + alpha.toString() + ')'; if (percent > 66) return 'rgba(236,151,31,' + alpha.toString() + ')'; return 'rgba(51,122,183,' + alpha.toString() + ')'; */ } renderChart() { if (this.state.width === 0) return setTimeout(this.getWidth, 0); const data = { labels: ['', ''], datasets: [ { data: [this.props.value, this.props.max - this.props.value], backgroundColor: [this.getColour(1), this.getColour(0.2)], hoverBackgroundColor: [this.getColour(1), this.getColour(0.2)], borderWidth: [0, 0], hoverBorderWidth: [0, 0] } ] }; const options = { plugins: { legend: { display: false }, tooltip: { enabled: false } }, animation: false, cutout: '92%' }; return ( ); } render() { const percent = Math.floor((100 * this.props.value) / this.props.max); return (

{this.props.title}

{percent}%
{this.renderChart()} {this.props.description}
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/grain-method-table.tsx ================================================ import React from 'react'; import { getName } from '../lib/typeName'; interface GrainMethodValue { grain: string; method: string; [key: string]: any; } interface GrainMethodTableProps { values?: GrainMethodValue[]; valueFormatter: (value: GrainMethodValue) => string | number; } export default class GrainMethodTable extends React.Component { constructor(props: GrainMethodTableProps) { super(props); this.renderRow = this.renderRow.bind(this); } renderRow(value: GrainMethodValue) { return ( {this.props.valueFormatter(value)} {value.method}
{getName(value.grain)} ); } render() { const values = this.props.values || []; return ( {values.map(this.renderRow)} {values.length === 0 && }
No data
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/grain-table.tsx ================================================ import React from 'react'; import { getName } from '../lib/typeName'; interface GrainStat { grainType: string; siloAddress: string; activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; } interface AggregatedGrainStat { grainType: string; activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; } interface GrainTableProps { data: GrainStat[]; silo?: string; } interface GrainTableState { sortBy: string; sortByAsc: boolean; } export default class GrainTable extends React.Component { constructor(props: GrainTableProps) { super(props); this.state = { sortBy: 'activationCount', sortByAsc: false }; this.handleChangeSort = this.handleChangeSort.bind(this); } getSorter(): ((a: AggregatedGrainStat, b: AggregatedGrainStat) => number) | null { let sorter: (a: AggregatedGrainStat, b: AggregatedGrainStat) => number; switch (this.state.sortBy) { case 'activationCount': sorter = this.state.sortByAsc ? sortByActivationCountAsc : sortByActivationCountDesc; break; case 'grain': sorter = this.state.sortByAsc ? sortByGrainAsc : sortBygrainDesc; break; case 'exceptionRate': sorter = this.state.sortByAsc ? sortByExceptionRateAsc : sortByExceptionRateDesc; break; case 'totalCalls': sorter = this.state.sortByAsc ? sortBytotalCallsAsc : sortBytotalCallsDec; break; case 'totalAwaitTime': sorter = this.state.sortByAsc ? sortByTotalAwaitTimeAsc : sortByTotalAwaitTimeDesc; break; default: sorter = () => 0; break; } return sorter; } handleChangeSort(e: React.MouseEvent) { const column = e.currentTarget.dataset['column']; if (column) { this.setState({ sortBy: column, sortByAsc: this.state.sortBy === column ? !this.state.sortByAsc : false }); } } renderStat = (stat: AggregatedGrainStat) => { // shorten fully-qualified type names, including generics const grainClassName = getName(stat.grainType); const systemGrain = stat.grainType.startsWith('Orleans.'); const dashboardGrain = stat.grainType.startsWith('OrleansDashboard.'); return ( {grainClassName} {systemGrain ? ( System Grain ) : null} {dashboardGrain ? ( Dashboard Grain ) : null} {stat.activationCount} {stat.totalCalls === 0 ? '0.00' : ((100 * stat.totalExceptions) / stat.totalCalls).toFixed(2)} {' '} % {(stat.totalCalls / 100).toFixed(2)}{' '} req/sec {stat.totalCalls === 0 ? '0' : (stat.totalAwaitTime / stat.totalCalls).toFixed(2)} {' '} ms/req ); }; render() { const grainTypes: { [key: string]: AggregatedGrainStat } = {}; if (!this.props.data) return null; this.props.data.forEach(stat => { if (this.props.silo && stat.siloAddress !== this.props.silo) return; if (!grainTypes[stat.grainType]) { grainTypes[stat.grainType] = { grainType: stat.grainType, activationCount: 0, totalSeconds: 0, totalAwaitTime: 0, totalCalls: 0, totalExceptions: 0 }; } const x = grainTypes[stat.grainType]; x.activationCount += stat.activationCount; x.totalSeconds += stat.totalSeconds; x.totalAwaitTime += stat.totalAwaitTime; x.totalCalls += stat.totalCalls; x.totalExceptions += stat.totalExceptions; }); const values = Object.keys(grainTypes).map(key => { return grainTypes[key]; }); const sorter = this.getSorter(); if (sorter) { values.sort(sorter); } return ( {values.map(this.renderStat)}
Grain{' '} {this.state.sortBy === 'grain' ? ( this.state.sortByAsc ? ( ) : ( ) ) : null} Activations{' '} {this.state.sortBy === 'activationCount' ? ( this.state.sortByAsc ? ( ) : ( ) ) : null} Exception rate{' '} {this.state.sortBy === 'exceptionRate' ? ( this.state.sortByAsc ? ( ) : ( ) ) : null} Throughput{' '} {this.state.sortBy === 'totalCalls' ? ( this.state.sortByAsc ? ( ) : ( ) ) : null} Latency{' '} {this.state.sortBy === 'totalAwaitTime' ? ( this.state.sortByAsc ? ( ) : ( ) ) : null}
); } } function sortByActivationCountAsc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return a.activationCount - b.activationCount; } function sortByActivationCountDesc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return sortByActivationCountAsc(b, a); } function sortByGrainAsc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { const parts = (x: AggregatedGrainStat) => x.grainType.split('.'); const grainClassName = (x: AggregatedGrainStat) => parts(x)[parts(x).length - 1]; return grainClassName(a) < grainClassName(b) ? -1 : grainClassName(a) > grainClassName(b) ? 1 : 0; } function sortBygrainDesc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return sortByGrainAsc(b, a); } function sortByExceptionRateAsc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return a.totalExceptions - b.totalExceptions; } function sortByExceptionRateDesc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return sortByExceptionRateAsc(b, a); } function sortBytotalCallsAsc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return a.totalCalls - b.totalCalls; } function sortBytotalCallsDec(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return sortBytotalCallsAsc(b, a); } function sortByTotalAwaitTimeAsc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { if (a.totalCalls === 0 && b.totalCalls === 0) { return 0; } else if (a.totalCalls === 0 || b.totalCalls === 0) { return a.totalAwaitTime - b.totalAwaitTime; } else { return a.totalAwaitTime / a.totalCalls - b.totalAwaitTime / b.totalCalls; } } function sortByTotalAwaitTimeDesc(a: AggregatedGrainStat, b: AggregatedGrainStat): number { return sortByTotalAwaitTimeAsc(b, a); } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/loading.tsx ================================================ import React from 'react'; export default class Loading extends React.Component { render() { return (
Loading...
); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/menu.tsx ================================================ import React from 'react'; interface MenuSectionProps { active?: boolean; icon: string; name: string; path: string; isSeparated?: boolean; } interface MenuItem { name: string; path: string; icon: string; active?: boolean; isSeparated?: boolean; } interface MenuProps { menu: MenuItem[]; } class MenuSection extends React.Component { render() { const className = 'nav-item' + (this.props.active ? ' menu-open' : '') + (this.props.isSeparated ? ' mt-auto' : ''); return (
  • {this.props.name}

  • ); } } export default class Menu extends React.Component { render() { return (
      {this.props.menu.map(x => ( ))}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/multi-series-chart-widget.tsx ================================================ import React from 'react'; import { Line as Chart } from 'react-chartjs-2'; const colours = [[120, 57, 136], [236, 151, 31]]; interface MultiSeriesChartWidgetProps { series: number[][]; } interface MultiSeriesChartWidgetState { width: number; height: number; } // this control is a bit of a temporary hack, until I have a multi-series chart widget export default class MultiSeriesChartWidget extends React.Component { private containerRef: React.RefObject; constructor(props: MultiSeriesChartWidgetProps) { super(props); this.state = { width: 0, height: 0 }; this.containerRef = React.createRef(); this.getDimensions = this.getDimensions.bind(this); this.renderChart = this.renderChart.bind(this); } componentDidMount() { this.getDimensions(); } componentDidUpdate(prevProps: MultiSeriesChartWidgetProps, prevState: MultiSeriesChartWidgetState) { // Re-measure if dimensions are still 0 (e.g., after tab switch) if ((prevState.width === 0 && this.state.width === 0) || (prevState.height === 0 && this.state.height === 0)) { this.getDimensions(); } } getDimensions() { if (!this.containerRef.current) return; this.setState({ width: this.containerRef.current.offsetWidth - 20, height: this.containerRef.current.offsetHeight - 10 }); } renderChart() { if (this.state.width === 0 || this.state.height === 0) { setTimeout(this.getDimensions, 0); return null; } const data = { labels: this.props.series[0].map(function(x) { return ''; }), datasets: this.props.series.map((data, index) => { const colourString = colours[index % colours.length].join(); return { label: '', backgroundColor: `rgba(${colourString},0.1)`, borderColor: `rgba(${colourString},1)`, data: data, pointRadius: 0 }; }) }; return ( ); } render() { return
    {this.renderChart()}
    ; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/page.tsx ================================================ import React from 'react'; interface PageProps { title: string; subTitle?: React.ReactNode; children: React.ReactNode; } export default class Page extends React.Component { render() { return (

    {this.props.title} {this.props.subTitle}

    {this.props.children}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/panel.tsx ================================================ import React from 'react'; interface PanelProps { title: string; subTitle?: string; children: React.ReactNode; bodyPadding?: string; } export default class Panel extends React.Component { render() { let body: React.ReactNode; let footer: React.ReactNode; if (Array.isArray(this.props.children) && this.props.children.length) { body = this.props.children[0]; footer = (
    {this.props.children[1]}
    ); } else { body = this.props.children; footer = null; } const bodyStyle: React.CSSProperties = {}; if (this.props.bodyPadding) { bodyStyle.padding = this.props.bodyPadding; } return (

    {this.props.title} {this.props.subTitle}

    {body}
    {footer}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/preferences.tsx ================================================ import React from 'react'; import ThemeButtons from './theme-buttons'; import CheckboxFilter from './checkbox-filter'; import Panel from './panel'; interface Settings { dashboardGrainsHidden: boolean; systemGrainsHidden: boolean; } interface PreferencesProps { changeSettings: (newSettings: Partial) => void; settings: Settings; defaultTheme: string; light: () => void; dark: () => void; } const Preferences: React.FC = (props) => (

    The following preferences can be used to customize the Orleans Dashboard. The selected preferences are saved locally in this browser. The selected preferences do not affect other browsers or users.

    Selecting the "Hidden" option for System Grains or Dashboard Grains will exclude the corresponding types from counters, graphs, and tables with the exception of the Cluster Profiling graph on the Overview page and the Silo Profiling graph on a silo overview page.

    Dashboard Grains

    System Grains

    Theme

    ); export default Preferences; ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/properties-widget.tsx ================================================ import React from 'react'; interface PropertiesWidgetProps { data: { [key: string]: any }; } export default class PropertiesWidget extends React.Component { constructor(props: PropertiesWidgetProps) { super(props); this.renderRow = this.renderRow.bind(this); } renderRow(key: string) { return ( {key} {this.props.data[key]} ); } render() { return ( {Object.keys(this.props.data).map(this.renderRow)}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/reminder-table.tsx ================================================ import React from 'react'; interface ReminderData { grainReference: string; primaryKey: string; activationCount: number; name: string; startAt: string; period: string; } interface ReminderTableProps { data?: ReminderData[]; } interface ReminderTableState { grain_reference: string; primary_key: string; name: string; startAt: string; period: string; } export default class ReminderTable extends React.Component { constructor(props: ReminderTableProps) { super(props); this.state = { grain_reference: '', primary_key: '', name: '', startAt: '', period: '' }; this.handleChange = this.handleChange.bind(this); this.renderReminder = this.renderReminder.bind(this); this.filterData = this.filterData.bind(this); } handleChange(e: React.ChangeEvent) { this.setState({ [e.target.name]: e.target.value } as Pick); } renderReminder(reminderData: ReminderData, index: number) { return ( {reminderData.grainReference} {reminderData.primaryKey} {reminderData.activationCount} {reminderData.name} {new Date(reminderData.startAt).toLocaleString()} {reminderData.period} ); } filterData(data: ReminderData[]): ReminderData[] { return data .filter(x => this.state['grain_reference'] ? x.grainReference.indexOf(this.state['grain_reference']) > -1 : x ) .filter(x => this.state['primary_key'] ? x.primaryKey.indexOf(this.state['primary_key']) > -1 : x ) .filter(x => this.state['name'] ? x.name.indexOf(this.state['name']) > -1 : x ) .filter(x => this.state['startAt'] ? x.startAt.indexOf(this.state['startAt']) > -1 : x ) .filter(x => this.state['period'] ? x.period.indexOf(this.state['period']) > -1 : x ); } render() { if (!this.props.data) return null; const filteredData = this.filterData(this.props.data); return ( {filteredData.map(this.renderReminder)}
    Grain Reference Primary Key Name Start At Period
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/theme-buttons.tsx ================================================ import React from 'react'; interface ThemeButtonsProps { defaultTheme: string; light: () => void; dark: () => void; } interface ThemeButtonsState { light: boolean; } export default class ThemeButtons extends React.Component { constructor(props: ThemeButtonsProps) { super(props); this.state = { light: this.props.defaultTheme !== 'dark' }; this.pickLight = this.pickLight.bind(this); this.pickDark = this.pickDark.bind(this); } pickLight(event: React.MouseEvent) { // Prevent link navigation. event.preventDefault(); this.props.light(); this.setState({ light: true }); } pickDark(event: React.MouseEvent) { // Prevent link navigation. event.preventDefault(); this.props.dark(); this.setState({ light: false }); } render() { return ( ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/components/time-series-chart.tsx ================================================ import React from 'react'; import { Line as Chart } from 'react-chartjs-2'; interface TimeSeriesChartProps { timepoints: string[]; series: number[][]; } interface TimeSeriesChartState { width: number; } // this control is a bit of a temporary hack, until I have a multi-series chart widget export default class TimeSeriesChart extends React.Component { private containerRef: HTMLDivElement | null; private options: any; constructor(props: TimeSeriesChartProps) { super(props); this.state = { width: 0 }; this.containerRef = null; this.getWidth = this.getWidth.bind(this); this.setContainerRef = this.setContainerRef.bind(this); this.options = { plugins: { legend: { display: false }, tooltip: { enabled: false } }, maintainAspectRatio: false, animation: false, responsive: true, interaction: { mode: 'index', intersect: false }, scales: { x: { display: true, grid: { offset: false, drawOnChartArea: false }, ticks: { autoSkip: false, maxRotation: 0, minRotation: 0, font: { size: 9 } } }, y1: { type: 'linear', display: true, position: 'left', grid: { drawOnChartArea: false }, ticks: { beginAtZero: true } }, y2: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false }, ticks: { beginAtZero: true } } } }; } setContainerRef(element: HTMLDivElement | null) { this.containerRef = element; } componentDidMount() { this.getWidth(); } componentDidUpdate(prevProps: TimeSeriesChartProps, prevState: TimeSeriesChartState) { if (prevState.width === 0 && this.state.width === 0) { this.getWidth(); } } getWidth() { if (!this.containerRef) { return; } this.setState({ width: this.containerRef.offsetWidth }); } renderChart() { if (this.state.width === 0) { return null; } const data = { labels: this.props.timepoints.map(timepoint => { if (timepoint) { try { if (new Date(timepoint).getSeconds() % 30 == 0) { return new Date(timepoint).toLocaleTimeString(); } } catch (e) { // not a valid date string } } return ''; }), datasets: [ { label: 'Average Latency', backgroundColor: `rgba(236,151,31,0.2)`, borderColor: `rgba(236,151,31,1)`, data: this.props.series[2], pointRadius: 0, yAxisID: 'y2', fill: true, tension: 0.4, borderWidth: 2 }, { label: 'Failed Requests', backgroundColor: `rgba(236,31,31,0.4)`, borderColor: `rgba(236,31,31,1)`, data: this.props.series[0], pointRadius: 0, yAxisID: 'y1', fill: true, tension: 0.4, borderWidth: 2 }, { label: 'Requests per Second', backgroundColor: `rgba(120,57,136,0.4)`, borderColor: `rgba(120,57,136,1)`, data: this.props.series[1], pointRadius: 0, yAxisID: 'y1', fill: true, tension: 0.4, borderWidth: 2 } ] }; return ( ); } render() { return
    {this.renderChart()}
    ; } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/custom.css ================================================ /* Custom styles for Orleans Dashboard layout */ /* Brand logo styling */ .brand-logo { height: 45px; width: auto; object-fit: contain; filter: brightness(0.95); } .brand-title { margin: 0; font-size: 1.25rem; font-weight: 300; line-height: 1.2; } .brand-link { display: block; padding: 1rem 1rem 1.5rem 1rem; border-bottom: 1px solid rgba(0, 0, 0, 0.1); margin-bottom: 1rem; } .dark-mode .brand-link { border-bottom-color: rgba(255, 255, 255, 0.1); } .dark-mode .brand-logo { filter: brightness(1.1); } .brand-version { margin-top: 0.5rem; font-size: 0.875rem; color: #6c757d; } .log { height: 100%; } .log-filter { padding-left: 20px; border: 0; z-index: 1; outline: none; } .error-container { position: fixed; z-index: 10000; width: 400px; top: 20px; left: 100%; margin-left: -420px; } /* Close button styling for alerts */ .alert .close { position: relative; top: -2px; right: -21px; float: right; font-size: 1.5rem; font-weight: 700; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; opacity: 0.5; background: transparent; border: 0; padding: 0; cursor: pointer; transition: opacity 0.15s ease-in-out; } .alert .close:hover, .alert .close:focus { opacity: 0.75; text-decoration: none; outline: 0; } .alert-danger .close { color: #721c24; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } .dark-mode .alert .close { color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5); } .log { position: absolute; top: 50px; left: 10px; right: 10px; border: 0; bottom: 0; margin: 0; } .box { border-top: none; margin-bottom: 20px; } .main-sidebar { padding-top: 0 !important; } .content-wrapper { padding-top: 0 !important; padding: 1rem; } .content-wrapper .content { padding-top: 0; } h1 { font-weight: 100; } h2 { font-weight: 100; } h3 { font-weight: 100; } /* Fix info-box layout */ .info-box { display: flex; min-height: 80px; border-radius: .25rem; box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2); margin-bottom: 1rem; } .info-box-icon { border-radius: .25rem 0 0 .25rem; width: 90px; text-align: center; font-size: 2.5rem; display: flex; align-items: center; justify-content: center; } .info-box-content { display: flex; flex-direction: column; justify-content: center; padding: .5rem 1rem; flex: 1; } .info-box-text { text-transform: uppercase; font-size: .875rem; } .info-box-number { font-weight: 700; font-size: 1.5rem; } .small-box-footer { display: block; padding: 3px 0; margin-top: 5px; color: rgba(0,0,0,.5); text-decoration: none; } .small-box-footer:hover { color: rgba(0,0,0,.7); text-decoration: none; } /* Card/Panel consistency */ .card, .box { border-radius: .25rem; box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2); margin-bottom: 1rem; height: 100%; } .card-header, .box-header { padding: .75rem 1.25rem; } .card-body, .box-body { padding: 1.25rem; } /* Ensure proper row spacing */ .row { --bs-gutter-x: 1rem; } /* Make cards in rows have equal heights */ .row > [class*='col-'] { display: flex; flex-direction: column; margin-bottom: 0; } .row > [class*='col-'] > .card, .row > [class*='col-'] > .box { flex: 1; } /* Sidebar menu spacing */ .sidebar .nav-sidebar { display: flex; flex-direction: column; height: 100%; } .sidebar .nav-item.mt-auto { margin-top: auto !important; } /* Ensure sidebar scrolls properly */ .main-sidebar .sidebar { display: flex; flex-direction: column; height: calc(100vh - 100px); overflow-y: auto; } /* Fix menu items to display icon and text on same line */ .nav-sidebar .nav-link { display: flex; align-items: center; gap: 0.5rem; } .nav-sidebar .nav-link p { margin: 0; } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/grains/grain-details.tsx ================================================ import React from 'react'; import Page from '../components/page'; import http from '../lib/http'; import DisplayGrainState from '../components/display-grain-state'; import Panel from '../components/panel'; import { getName } from '../lib/typeName'; interface GrainDetailsProps { grainTypes: string[]; } interface GrainDetailsState { grainId: string; grainType: string | null; grainState: string; } export default class GrainDetails extends React.Component { constructor(props: GrainDetailsProps) { super(props); this.state = { grainId: '', grainType: null, grainState: '' }; this.handleGrainIdChange = this.handleGrainIdChange.bind(this); this.handleGrainTypeChange = this.handleGrainTypeChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleGrainIdChange(event: React.ChangeEvent) { this.setState({ grainId: event.target.value }); } handleGrainTypeChange(event: React.ChangeEvent) { this.setState({ grainType: event.target.value }); } handleSubmit(event: React.MouseEvent) { const component = this; http.get('GrainState?grainId=' + this.state.grainId + '&grainType=' + this.state.grainType, function (err, data) { component.setState({ grainState: data }); }).then(() => { }); event.preventDefault(); } renderEmpty() { return No state retrieved; } renderState() { let displayComponent: React.ReactNode; if (this.state.grainState !== '') { displayComponent = ; } else { displayComponent =
    ; } return (
    {displayComponent}
    ); } render() { return this.renderState(); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/grains/grain.tsx ================================================ import React from 'react'; import Chart from '../components/time-series-chart'; import CounterWidget from '../components/counter-widget'; import SiloBreakdown from './silo-table'; import Panel from '../components/panel'; import Page from '../components/page'; import { getName } from '../lib/typeName'; // Format a fully-qualified member name so the type is shortened but the // member/method part is preserved. Examples: // - "A.B.C.M" -> "C.M" // - "A.B.C.N.M" -> "C.N.M" function formatMemberName(value: string): string { if (!value) return value; // find last top-level separator (dot, slash, or '#') not inside generics let depth = 0; for (let i = value.length - 1; i >= 0; i--) { const ch = value[i]; if (ch === '>' || ch === ']') depth++; else if (ch === '<' || ch === '[') depth--; else if (depth === 0 && (ch === '.' || ch === '/' || ch === '#')) { const typePart = value.substring(0, i); const memberPart = value.substring(i + 1); return `${getName(typePart)}.${memberPart}`; } } return getName(value); } interface GrainMethodValue { count: number; elapsedTime: number; period: number | string; exceptionCount: number; } interface GrainStats { [grainMethod: string]: { [key: string]: GrainMethodValue; }; } interface SimpleGrainStat { grainType: string; activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; [key: string]: any; } interface DashboardCounters { simpleGrainStats: SimpleGrainStat[]; } interface GrainProps { grainType: string; dashboardCounters: DashboardCounters; grainStats: GrainStats; } interface GrainGraphProps { stats: { [key: string]: GrainMethodValue }; grainMethod: string; } const GrainGraph: React.FC = (props) => { const values: GrainMethodValue[] = []; const timepoints: (number | string)[] = []; Object.keys(props.stats).forEach(key => { values.push(props.stats[key]); timepoints.push(props.stats[key].period); }); if (!values.length) { return null; } while (values.length < 100) { values.unshift({ count: 0, elapsedTime: 0, period: 0, exceptionCount: 0 }); timepoints.unshift(''); } return (

    {props.grainMethod}

    z.exceptionCount), values.map(z => z.count), values.map(z => (z.count === 0 ? 0 : z.elapsedTime / z.count)) ]} />
    ); }; // add multiple axis to the chart // https://jsfiddle.net/devonuto/pa7k6xn9/ export default class Grain extends React.Component { renderEmpty() { return No messages recorded; } renderGraphs() { const stats = { activationCount: 0, totalSeconds: 0, totalAwaitTime: 0, totalCalls: 0, totalExceptions: 0 }; this.props.dashboardCounters.simpleGrainStats.forEach(stat => { if (stat.grainType !== this.props.grainType) return; stats.activationCount += stat.activationCount; stats.totalSeconds += stat.totalSeconds; stats.totalAwaitTime += stat.totalAwaitTime; stats.totalCalls += stat.totalCalls; stats.totalExceptions += stat.totalExceptions; }); return (
    / {' '} number of requests per second
    / {' '} failed requests
    / {' '} average latency in milliseconds {Object.keys(this.props.grainStats) .sort() .map(key => ( ))}
    ); } render() { if (Object.keys(this.props.grainStats).length === 0) return this.renderEmpty(); return this.renderGraphs(); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/grains/grains.tsx ================================================ import React from 'react'; import CounterWidget from '../components/counter-widget'; import ChartWidget from '../components/multi-series-chart-widget'; import GrainBreakdown from '../components/grain-table'; import Panel from '../components/panel'; interface SimpleGrainStat { activationCount: number; [key: string]: any; } interface DashboardCounters { simpleGrainStats: SimpleGrainStat[]; totalActivationCountHistory: number[]; } interface GrainsProps { dashboardCounters: DashboardCounters; } export default class Grains extends React.Component { render() { const stats = { totalActivationCount: 0 }; this.props.dashboardCounters.simpleGrainStats.forEach(stat => { stats.totalActivationCount += stat.activationCount; }); return (
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/grains/silo-table.tsx ================================================ import React from 'react'; interface SiloStat { siloAddress: string; activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; } interface GrainStat { siloAddress: string; grainType: string; activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; } interface SiloTableProps { data: GrainStat[]; grainType?: string; } export default class SiloTable extends React.Component { renderStat(stat: SiloStat) { return ( {stat.siloAddress} {stat.activationCount} {stat.totalCalls === 0 ? '0.00' : ((100 * stat.totalExceptions) / stat.totalCalls).toFixed(2)} {' '} % {stat.totalSeconds === 0 ? '0' : (stat.totalCalls / 100).toFixed(2)} {' '} req/sec {stat.totalCalls === 0 ? '0' : (stat.totalAwaitTime / stat.totalCalls).toFixed(2)} {' '} ms/req ); } render() { const silos: { [key: string]: SiloStat } = {}; if (!this.props.data) return null; this.props.data.forEach(stat => { if (!silos[stat.siloAddress]) { silos[stat.siloAddress] = { siloAddress: stat.siloAddress, activationCount: 0, totalSeconds: 0, totalAwaitTime: 0, totalCalls: 0, totalExceptions: 0 }; } if (this.props.grainType && stat.grainType !== this.props.grainType) return; const x = silos[stat.siloAddress]; x.activationCount += stat.activationCount; x.totalSeconds += stat.totalSeconds; x.totalAwaitTime += stat.totalAwaitTime; x.totalCalls += stat.totalCalls; x.totalExceptions += stat.totalExceptions; }); const values = Object.keys(silos) .map(function(key) { const x = silos[key]; x.siloAddress = key; return x; }) .sort(function(a, b) { return b.activationCount - a.activationCount; }); return ( {values.map(stat => this.renderStat(stat))}
    Silo Activations Exception rate Throughput Latency
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/index.tsx ================================================ // Import styles import './styles.css'; // Register Chart.js components import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, ArcElement, Title, Tooltip, Legend, Filler } from 'chart.js'; ChartJS.register( CategoryScale, LinearScale, PointElement, LineElement, ArcElement, Title, Tooltip, Legend, Filler ); import http from './lib/http'; import React from 'react'; import ReactDom from 'react-dom'; import routie from './lib/routie'; import Silo from './silos/silo'; import events from 'eventthing'; import Grain from './grains/grain'; import GrainDetails from './grains/grain-details'; import Page from './components/page'; import Loading from './components/loading'; import Menu from './components/menu'; import BrandHeader from './components/brand-header'; import Grains from './grains/grains'; import Silos from './silos/silos'; import Overview from './overview/overview'; import SiloState from './silos/silo-state-label'; import Alert from './components/alert'; import LogStream from './logstream/log-stream'; import SiloCounters from './silos/silo-counters'; import Reminders from './reminders/reminders'; import Preferences from './components/preferences'; import storage from './lib/storage'; interface Settings { dashboardGrainsHidden: boolean; systemGrainsHidden: boolean; } interface MenuItem { name: string; path: string; icon: string; active?: boolean; isSeparated?: boolean; } interface GrainStatItem { grain?: string; grainType?: string; } const target = document.getElementById('content'); // Restore theme preference. let defaultTheme = storage.get('theme') || 'dark'; if (defaultTheme === 'dark') { document.getElementById('body')!.classList.add('dark-mode'); dark(); } else { light(); } // Restore grain visibility preferences. let settings: Settings = { dashboardGrainsHidden: storage.get('dashboardGrains') === 'hidden', systemGrainsHidden: storage.get('systemGrains') === 'hidden' }; // Global state. let dashboardCounters: any = {}; let routeIndex = 0; function scroll() { try { document.getElementsByClassName('wrapper')[0].scrollTo(0, 0); } catch (e) { } } let errorTimer: NodeJS.Timeout | null; function showError(message: string) { ReactDom.render( {message}, document.getElementById('error-message-content') ); if (errorTimer) clearTimeout(errorTimer); errorTimer = setTimeout(closeError, 3000); } function closeError() { if (errorTimer) clearTimeout(errorTimer); errorTimer = null; ReactDom.render(, document.getElementById('error-message-content')); } http.onError(showError); function setIntervalDebounced(action: () => Promise, interval: number) { Promise.resolve(action()).finally(() => { setTimeout(setIntervalDebounced.bind(this, action, interval), interval); }); } // continually poll the dashboard counters function loadDashboardCounters() { return http.get(`DashboardCounters${getFilter(settings)}`, function (err, data) { dashboardCounters = data; events.emit('dashboard-counters', dashboardCounters); }); } function getVersion() { let version = '2'; const renderVersion = function () { ReactDom.render( v.{version} Source , document.getElementById('version-content') ); }; const loadData = function (cb?: any) { http.get('version', function (err, data) { version = data.version; renderVersion(); }); }; loadData(); } // we always want to refresh the dashboard counters setIntervalDebounced(loadDashboardCounters, 1000); loadDashboardCounters(); let render: () => void = () => { }; function renderLoading() { ReactDom.render(, target); } const menuElement = document.getElementById('menu'); const brandHeaderElement = document.getElementById('brand-header'); // Render brand header once ReactDom.render(, brandHeaderElement); function renderPage(jsx: JSX.Element, path: string) { ReactDom.render(jsx, target); const menu = getMenu(); menu.forEach(x => { x.active = x.path === path; }); ReactDom.render(, menuElement); } (routie as any)('', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); let clusterStats: any = {}; let grainMethodStats: any = []; let loadDataIsPending = false; const loadData = function (cb?: any) { if (!loadDataIsPending) { loadDataIsPending = true; http.get('ClusterStats', function (err, data) { clusterStats = data; http.get(`TopGrainMethods${getFilter(settings)}`, function (err, grainMethodsData) { grainMethodStats = grainMethodsData; render(); }).finally(() => loadDataIsPending = false); }).catch(() => loadDataIsPending = false); } }; render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/' ); }; events.on('dashboard-counters', render); events.on('refresh', loadData); loadDashboardCounters(); }); (routie as any)('/grains', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/grains' ); }; events.on('dashboard-counters', render); events.on('refresh', render); loadDashboardCounters(); }); (routie as any)('/silos', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/silos' ); }; events.on('dashboard-counters', render); events.on('refresh', render); loadDashboardCounters(); }); (routie as any)('/host/:host', function (host: string) { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); let siloProperties: any = {}; let siloMetadata: any = {}; let siloData: any[] = []; let siloStats: any[] = []; const loadData = function (cb?: any) { http.get(`HistoricalStats/${host}`, (err, data) => { siloData = data; render(); }); http.get(`SiloStats/${host}`, (err, data) => { siloStats = data; render(); }); }; const renderOverloaded = function () { if (!siloData.length) return null; if (!siloData[siloData.length - 1]) return null; if (!siloData[siloData.length - 1].isOverloaded) return null; return ( OVERLOADED ); }; render = function () { if (routeIndex != thisRouteIndex) return; const silo = (dashboardCounters.hosts || []).filter( (x: any) => x.siloAddress === host )[0] || {}; const subTitle = ( {renderOverloaded()} ); renderPage( , '#/silos' ); }; events.on('dashboard-counters', render); events.on('refresh', loadData); http.get('SiloProperties/' + host, function (err, data) { siloProperties = data; loadData(); }); http.get('SiloMetadata/' + host, function (err, data) { siloMetadata = data; loadData(); }); }); (routie as any)('/host/:host/counters', function (host: string) { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); http.get(`SiloCounters/${host}`, (err, data) => { if (routeIndex != thisRouteIndex) return; const subTitle = Silo Details; renderPage( , '#/silos' ); }); }); (routie as any)('/grain/:grainType', function (grainType: string) { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); let grainStats: any = {}; let loadDataIsPending = false; const loadData = function (cb?: any) { if (!loadDataIsPending) { http.get('GrainStats/' + grainType, function (err, data) { grainStats = data; render(); }).finally(() => loadDataIsPending = false); } }; render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/grains' ); }; events.on('dashboard-counters', render); events.on('refresh', loadData); loadData(); }); (routie as any)('/grainDetails', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); let grainTypes: any = {}; let loadDataIsPending = false; const loadData = function (cb?: any) { if (!loadDataIsPending) { http.get(`GrainTypes${getFilter(settings)}`, function (err, data) { grainTypes = data; render(); }).finally(() => loadDataIsPending = false); } }; render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/grainState' ); }; loadData(); }); (routie as any)('/reminders/:page?', function (page?: string) { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); let remindersData: any[] = []; let pageNum: number; if (page) { pageNum = parseInt(page); } else { pageNum = 1; } const renderReminders = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/reminders' ); }; const rerouteToLastPage = function (lastPage: number) { return (document.location.hash = `/reminders/${lastPage}`); }; let loadDataIsPending = false; const loadData = function (cb?: any) { if (!loadDataIsPending) { loadDataIsPending = true; http.get(`Reminders/${pageNum}`, function (err, data) { remindersData = data; renderReminders(); }).finally(() => loadDataIsPending = false); } }; events.on('long-refresh', loadData); loadData(); }); (routie as any)('/trace', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); const xhr = http.stream('Trace'); renderPage(, '#/trace'); }); (routie as any)('/preferences', function () { const thisRouteIndex = ++routeIndex; events.clearAll(); scroll(); renderLoading(); const changeSettings = (newSettings: Partial) => { settings = { ...settings }; if (newSettings.hasOwnProperty('dashboardGrainsHidden')) { storage.put( 'dashboardGrains', newSettings.dashboardGrainsHidden ? 'hidden' : 'visible' ); settings.dashboardGrainsHidden = newSettings.dashboardGrainsHidden!; } if (newSettings.hasOwnProperty('systemGrainsHidden')) { storage.put( 'systemGrains', newSettings.systemGrainsHidden ? 'hidden' : 'visible' ); settings.systemGrainsHidden = newSettings.systemGrainsHidden!; } loadDashboardCounters(); }; render = function () { if (routeIndex != thisRouteIndex) return; renderPage( , '#/preferences' ); }; loadDashboardCounters(); render(); }); setInterval(() => events.emit('refresh'), 1000); setInterval(() => events.emit('long-refresh'), 10000); (routie as any).reload(); getVersion(); function getMenu(): MenuItem[] { const result: MenuItem[] = [ { name: 'Overview', path: '#/', icon: 'fa fa-tachometer-alt' }, { name: 'Grains', path: '#/grains', icon: 'fa fa-cubes' }, { name: 'Grain Details', path: '#/grainDetails', icon: 'fa fa-cube' }, { name: 'Silos', path: '#/silos', icon: 'fa fa-database' }, { name: 'Reminders', path: '#/reminders', icon: 'fa fa-calendar' } ]; if (!(window as any).hideTrace) { result.push({ name: 'Log Stream', path: '#/trace', icon: 'fa fa-bars' }); } result.push({ name: 'Preferences', path: '#/preferences', icon: 'fa fa-cog', isSeparated: true }); return result; } function getFilter(settings: Settings): string { if (settings.dashboardGrainsHidden && settings.systemGrainsHidden) { return '?exclude=Orleans.Runtime&exclude=Orleans.Streams&exclude=Orleans.Storage&exclude=Orleans.Providers&exclude=Orleans.Dashboard'; } else if (settings.dashboardGrainsHidden) { return '?exclude=Orleans.Dashboard'; } else if (settings.systemGrainsHidden) { return '?exclude=Orleans.Runtime&exclude=Orleans.Streams&exclude=Orleans.Storage&exclude=Orleans.Providers'; } return ''; } function light() { // Save preference to localStorage. storage.put('theme', 'light'); defaultTheme = 'light'; // Remove dark mode class from body and set Bootstrap theme to light. const body = document.getElementById('body')!; body.classList.remove('dark-mode'); body.setAttribute('data-bs-theme', 'light'); } function dark() { // Save preference to localStorage. storage.put('theme', 'dark'); defaultTheme = 'dark'; // Add dark mode class to body and set Bootstrap theme to dark. const body = document.getElementById('body')!; body.classList.add('dark-mode'); body.setAttribute('data-bs-theme', 'dark'); } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/lib/http.ts ================================================ import events from 'eventthing'; type HttpCallback = (error: string | null, result?: any) => void; function makeRequest(method: string, uri: string, body: string | null, cb: HttpCallback): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(method, uri, true); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status < 400 && xhr.status > 0) { const result = JSON.parse(xhr.responseText || '{}'); resolve(result); return cb(null, result); } const errorMessage = 'Error connecting to Orleans Silo. Status code: ' + (xhr.status || 'NO_CONNECTION'); errorHandlers.forEach(x => x(errorMessage)); reject(errorMessage); }; xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Accept', 'application/json'); xhr.send(body); }); } type ErrorHandler = (error: string) => void; const errorHandlers: ErrorHandler[] = []; const httpModule = { get: function(url: string, cb: HttpCallback): Promise { return makeRequest('GET', url, null, cb); }, stream: function(url: string): XMLHttpRequest { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.send(); return xhr; }, onError: function(handler: ErrorHandler): void { errorHandlers.push(handler); } }; export default httpModule; ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/lib/routie.ts ================================================ interface RouteKey { name: string; optional: boolean; } interface RouteMap { [key: string]: Route; } interface NavigateOptions { silent?: boolean; } let routes: Route[] = []; let map: RouteMap = {}; const reference = 'routie'; const oldReference = (window as any)[reference]; class Route { name: string | null; path: string; keys: RouteKey[]; fns: Function[]; params: { [key: string]: string }; regex: RegExp; constructor(path: string, name: string | null) { this.name = name; this.path = path; this.keys = []; this.fns = []; this.params = {}; this.regex = pathToRegexp(this.path, this.keys, false, false); } addHandler(fn: Function): void { this.fns.push(fn); } removeHandler(fn: Function): void { for (let i = 0, c = this.fns.length; i < c; i++) { const f = this.fns[i]; if (fn == f) { this.fns.splice(i, 1); return; } } } run(params: any[]): void { for (let i = 0, c = this.fns.length; i < c; i++) { this.fns[i].apply(this, params); } } match(path: string, params: any[]): boolean { const m = this.regex.exec(path); if (!m) return false; for (let i = 1, len = m.length; i < len; ++i) { const key = this.keys[i - 1]; const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]; if (key) { this.params[key.name] = val; } params.push(val); } return true; } toURL(params: { [key: string]: string }): string { let path = this.path; for (const param in params) { path = path.replace('/:' + param, '/' + params[param]); } path = path.replace(/\/:.*\?/g, '/').replace(/\?/g, ''); if (path.indexOf(':') != -1) { throw new Error('missing parameters for url: ' + path); } return path; } } const pathToRegexp = function( path: string | RegExp | string[], keys: RouteKey[], sensitive: boolean, strict: boolean ): RegExp { if (path instanceof RegExp) return path; if (path instanceof Array) path = '(' + path.join('|') + ')'; path = (path as string) .concat(strict ? '' : '/?') .replace(/\/\(/g, '(?:/') .replace(/\+/g, '__plus__') .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function( _: string, slash: string, format: string, key: string, capture: string, optional: string ) { keys.push({ name: key, optional: !!optional }); slash = slash || ''; return ( '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || ((format && '([^/.]+?)') || '([^/]+?)')) + ')' + (optional || '') ); }) .replace(/([\/.])/g, '\\$1') .replace(/__plus__/g, '(.+)') .replace(/\*/g, '(.*)'); return new RegExp('^' + path + '$', sensitive ? '' : 'i'); }; const addHandler = function(path: string, fn: Function): void { const s = path.split(' '); const name = s.length == 2 ? s[0] : null; path = s.length == 2 ? s[1] : s[0]; if (!map[path]) { map[path] = new Route(path, name); routes.push(map[path]); } map[path].addHandler(fn); }; interface RoutieFunction { (path: string, fn: Function): void; (path: { [key: string]: Function }): void; (path: string): void; lookup: (name: string, obj: { [key: string]: string }) => string | undefined; remove: (path: string, fn: Function) => void; removeAll: () => void; navigate: (path: string, options?: NavigateOptions) => void; noConflict: () => RoutieFunction; reload: () => void; } const routie = function(path: string | { [key: string]: Function }, fn?: Function): void { if (typeof fn == 'function') { addHandler(path as string, fn); } else if (typeof path == 'object') { for (const p in path) { addHandler(p, path[p]); } } else if (typeof fn === 'undefined') { routie.navigate(path as string); } } as RoutieFunction; routie.lookup = function(name: string, obj: { [key: string]: string }): string | undefined { for (let i = 0, c = routes.length; i < c; i++) { const route = routes[i]; if (route.name == name) { return route.toURL(obj); } } }; routie.remove = function(path: string, fn: Function): void { const route = map[path]; if (!route) return; route.removeHandler(fn); }; routie.removeAll = function(): void { map = {}; routes = []; }; routie.navigate = function(path: string, options?: NavigateOptions): void { options = options || {}; const silent = options.silent || false; if (silent) { removeListener(); } setTimeout(function() { window.location.hash = path; if (silent) { setTimeout(function() { addListener(); }, 1); } }, 1); }; routie.noConflict = function(): RoutieFunction { (window as any)[reference] = oldReference; return routie; }; const getHash = function(): string { return window.location.hash.substring(1); }; const checkRoute = function(hash: string, route: Route): boolean { const params: any[] = []; if (route.match(hash, params)) { route.run(params); return true; } return false; }; const hashChanged = (routie.reload = function(): void { const hash = getHash(); for (let i = 0, c = routes.length; i < c; i++) { const route = routes[i]; if (checkRoute(hash, route)) { return; } } }); const addListener = function(): void { if (window.addEventListener) { window.addEventListener('hashchange', hashChanged, false); } else { (window as any).attachEvent('onhashchange', hashChanged); } }; const removeListener = function(): void { if (window.removeEventListener) { window.removeEventListener('hashchange', hashChanged); } else { (window as any).detachEvent('onhashchange', hashChanged); } }; addListener(); export default routie; ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/lib/storage.ts ================================================ // polyfill localStorage with temp store interface StorageInterface { [key: string]: any; removeItem?: (key: string) => void; } let store: StorageInterface = {}; try { if (typeof localStorage !== 'undefined') { store = localStorage; } } catch (e) { // noop } const storageModule = { put: (key: string, value: string): void => { store[key] = value; }, get: (key: string): string | undefined => { return store[key]; }, del: (key: string): void => { if (store.removeItem) { store.removeItem(key); } else { delete store[key]; } } }; export default storageModule; ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/lib/typeName.ts ================================================ function trimIdentifier(id: string): string { if (!id) return id; const trimmed = stripAssemblyDetails(id.trim()); const parts = trimmed.split('.'); const last = parts[parts.length - 1]; const tickIndex = last.indexOf('`'); return tickIndex !== -1 ? last.substring(0, tickIndex) : last; } function stripAssemblyDetails(value: string): string { let depth = 0; for (let i = 0; i < value.length; i++) { const ch = value[i]; if (ch === '<' || ch === '[') depth++; else if (ch === '>' || ch === ']') depth--; else if (ch === ',' && depth === 0) return value.substring(0, i).trim(); } return value.trim(); } function findMatchingCloser(value: string, start: number, opener: string, closer: string): number { let depth = 0; for (let i = start; i < value.length; i++) { const ch = value[i]; if (ch === opener) depth++; else if (ch === closer) { depth--; if (depth === 0) return i; } } return -1; } function splitTopLevelArguments(value: string): string[] { const args: string[] = []; let argStart = 0; let depth = 0; for (let i = 0; i < value.length; i++) { const ch = value[i]; if (ch === '<' || ch === '[') depth++; else if (ch === '>' || ch === ']') depth--; else if (ch === ',' && depth === 0) { args.push(value.substring(argStart, i)); argStart = i + 1; } } args.push(value.substring(argStart)); return args; } function parseSuffix(rem: string): string { if (!rem) return ''; let i = 0; while (i < rem.length && (rem[i] === '.' || rem[i] === ' ')) i++; if (i === 0) return rem; const segments: string[] = []; let segStart = i; let depth = 0; for (let j = i; j < rem.length; j++) { const ch = rem[j]; if (ch === '<' || ch === '[') depth++; else if (ch === '>' || ch === ']') depth--; else if (ch === '.' && depth === 0) { segments.push(rem.substring(segStart, j)); segStart = j + 1; } } if (segStart <= rem.length) segments.push(rem.substring(segStart)); return '.' + segments.map(seg => parseType(seg)).join('.'); } function parseType(str: string): string { if (!str) return str; const s = stripAssemblyDetails(str.trim()); if (!s) return s; // Handle assembly-qualified generic arguments wrapped in [ ... ]. if (s[0] === '[') { const wrappedEnd = findMatchingCloser(s, 0, '[', ']'); if (wrappedEnd === s.length - 1) { return parseType(stripAssemblyDetails(s.substring(1, wrappedEnd))); } } // Find first generic opener: '<' or '[' const lt = s.indexOf('<'); const lb = s.indexOf('['); let opener = ''; let openerPos = -1; let closer = ''; if (lt !== -1 && (lb === -1 || lt < lb)) { opener = '<'; closer = '>'; openerPos = lt; } else if (lb !== -1) { opener = '['; closer = ']'; openerPos = lb; } if (openerPos === -1) { return trimIdentifier(s); } const main = s.substring(0, openerPos); const end = findMatchingCloser(s, openerPos, opener, closer); if (end === -1) { return trimIdentifier(s); } const inner = s.substring(openerPos + 1, end); const remainder = s.substring(end + 1); const parsed = splitTopLevelArguments(inner).map(arg => parseType(arg.trim())); return `${trimIdentifier(main)}<${parsed.join(', ')}>${parseSuffix(remainder)}`; } export function getName(value: string): string { return parseType(value); } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/logstream/log-stream.tsx ================================================ import React from 'react'; interface LogStreamProps { xhr: XMLHttpRequest; } interface LogStreamState { log: string; filter: string; scrollEnabled: boolean; filterRegex?: RegExp; } export default class LogStream extends React.Component { private logRef: React.RefObject; constructor(props: LogStreamProps) { super(props); this.state = { log: 'Connecting...', filter: '', scrollEnabled: true }; this.logRef = React.createRef(); this.scroll = this.scroll.bind(this); this.onProgress = this.onProgress.bind(this); this.toggle = this.toggle.bind(this); this.filterChanged = this.filterChanged.bind(this); this.getFilteredLog = this.getFilteredLog.bind(this); } scroll() { if (this.logRef.current) { this.logRef.current.scrollTop = this.logRef.current.scrollHeight; } } onProgress() { if (!this.state.scrollEnabled) return; let newLog = this.props.xhr.responseText; if (this.props.xhr.status === 403) { const responseText = this.props.xhr.responseText; if (responseText) { try { const parsed = JSON.parse(responseText) as { detail?: string }; newLog = parsed.detail ?? responseText; } catch { newLog = responseText; } } } this.setState( { log: newLog }, this.scroll ); } componentDidMount() { this.props.xhr.onprogress = this.onProgress; this.props.xhr.onload = this.onProgress; this.props.xhr.onerror = this.onProgress; } componentWillUnmount() { this.props.xhr.abort(); } toggle() { this.setState({ scrollEnabled: !this.state.scrollEnabled }); } filterChanged(event: React.ChangeEvent) { this.setState({ filter: event.target.value, filterRegex: new RegExp( `[^\\s-]* (Trace|Debug|Information|Warning|Error):.*${ event.target.value }.*`, 'gmi' ) }); } getFilteredLog(): string { if (!this.state.filter) return this.state.log; const matches = this.state.log.match(this.state.filterRegex); return matches ? matches.join('\r\n') : ''; } render() { return ( ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/overview/overview.tsx ================================================ import React from 'react'; import CounterWidget from '../components/counter-widget'; import Panel from '../components/panel'; import Chart from '../components/time-series-chart'; import GrainMethodTable from '../components/grain-method-table'; interface ClusterStatsValue { count: number; elapsedTime: number; period: number | string; exceptionCount: number; } interface ClusterStats { [key: string]: ClusterStatsValue; } interface SimpleGrainStat { activationCount: number; totalSeconds: number; totalAwaitTime: number; totalCalls: number; totalExceptions: number; } interface DashboardCounters { simpleGrainStats: SimpleGrainStat[]; totalActiveHostCount: number; } interface GrainMethodStat { count: number; elapsedTime: number; exceptionCount: number; numberOfSamples: number; } interface GrainMethodStats { calls: GrainMethodStat[]; errors: GrainMethodStat[]; latency: GrainMethodStat[]; } interface OverviewProps { dashboardCounters: DashboardCounters; clusterStats: ClusterStats; grainMethodStats: GrainMethodStats; } interface ClusterGraphProps { stats: ClusterStats; } const ClusterGraph: React.FC = (props) => { const values: ClusterStatsValue[] = []; const timepoints: (number | string)[] = []; Object.keys(props.stats).forEach(key => { values.push(props.stats[key]); timepoints.push(props.stats[key].period); }); if (!values.length) { return null; } while (values.length < 100) { values.unshift({ count: 0, elapsedTime: 0, period: 0, exceptionCount: 0 }); timepoints.unshift(''); } const series0: number[] = []; const series1: number[] = []; const series2: number[] = []; values.map(z => { series0.push(z.exceptionCount); series1.push(z.count); series2.push(z.count === 0 ? 0 : z.elapsedTime / z.count); }); return (
    ); }; export default class Overview extends React.Component { render() { const stats = { totalActivationCount: 0, totalSeconds: 0, totalAwaitTime: 0, totalCalls: 0, totalExceptions: 0 }; this.props.dashboardCounters.simpleGrainStats.forEach(stat => { stats.totalActivationCount += stat.activationCount; stats.totalSeconds += stat.totalSeconds; stats.totalAwaitTime += stat.totalAwaitTime; stats.totalCalls += stat.totalCalls; stats.totalExceptions += stat.totalExceptions; }); return (
    / {' '} number of requests per second
    / {' '} failed requests
    / {' '} average latency in milliseconds
    `${(x.count / x.numberOfSamples).toFixed(2)}req/sec` } />
    `${((100 * x.exceptionCount) / x.count).toFixed(2)}%` } />
    `${(x.elapsedTime / x.count).toFixed(2)}ms/req` } />
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/reminders/reminders.tsx ================================================ import React from 'react'; import CounterWidget from '../components/counter-widget'; import ReminderTable from '../components/reminder-table'; import Panel from '../components/panel'; interface Reminder { [key: string]: any; } interface RemindersData { count: number; reminders: Reminder[]; } interface RemindersProps { remindersData: RemindersData; page: number; } export default class Reminders extends React.Component { render() { const totalPages = Math.ceil(this.props.remindersData.count / 25); const showFirst = this.props.page > 2; const showPrevious = this.props.page > 1; const showNext = totalPages > this.props.page; const showLast = totalPages > this.props.page + 1; return (
    {showFirst ? ( First ) : null} {showPrevious ? ( Previous ) : null} {showNext ? ( Next ) : null} {showLast ? ( Last ) : null}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/host-table.tsx ================================================ import React from 'react'; import SiloState from './silo-state-label'; import humanize from 'humanize-duration'; interface SimpleGrainStat { siloAddress: string; activationCount: number; } interface Silo { siloAddress: string; status: string; siloName: string; hostName: string; startTime?: string; } interface DashboardCounters { hosts?: Silo[]; simpleGrainStats: SimpleGrainStat[]; } interface HostTableProps { dashboardCounters: DashboardCounters; } function sortFunction(a: Silo, b: Silo): number { const nameA = a.siloAddress.toUpperCase(); // ignore upper and lowercase const nameB = b.siloAddress.toUpperCase(); // ignore upper and lowercase if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; } export default class HostTable extends React.Component { constructor(props: HostTableProps) { super(props); this.renderHost = this.renderHost.bind(this); } renderHost(host: string, silo: Silo) { let subTotal = 0; this.props.dashboardCounters.simpleGrainStats.forEach(function(stat) { if (stat.siloAddress.toLowerCase() === host.toLowerCase()) subTotal += stat.activationCount; }); return ( {host} {silo.siloName} {silo.hostName} {silo.startTime ? ( up for{' '} {humanize( new Date().getTime() - new Date(silo.startTime).getTime(), { round: true, largest: 2 } )} ) : ( uptime is not available )} {subTotal} activations ); } render() { if (!this.props.dashboardCounters.hosts) return null; return ( {this.props.dashboardCounters.hosts .sort(sortFunction) .map(function(silo) { return this.renderHost(silo.siloAddress, silo); }, this)}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/silo-counters.tsx ================================================ import React from 'react'; import Panel from '../components/panel'; interface Counter { name: string; value: string | number; delta: string | number | null; } interface SiloCountersProps { counters: Counter[]; } export default class SiloCounters extends React.Component { renderItem(item: Counter) { return ( {item.name} {item.value} {item.delta === null ? '' : Δ {item.delta}} ); } render() { return (
    {this.props.counters.map(item => this.renderItem(item))}
    {this.props.counters.length === 0 ? (

    No counters available.

    It may take a few minutes for data to be published.
    ) : null}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/silo-grid.tsx ================================================ import React from 'react'; import SiloState from './silo-state-label'; interface Host { siloAddress: string; status: string; updateZone: number; faultZone: number; } interface DashboardCounters { hosts?: Host[]; } interface SiloGridProps { dashboardCounters: DashboardCounters; } export default class SiloGrid extends React.Component { constructor(props: SiloGridProps) { super(props); this.renderSilo = this.renderSilo.bind(this); this.renderZone = this.renderZone.bind(this); } renderSilo(silo: Host) { return ( ); } renderZone(updateZone: number, faultZone: number) { const matchingSilos = (this.props.dashboardCounters.hosts || []).filter( x => x.updateZone === updateZone && x.faultZone === faultZone ); return {matchingSilos.map(this.renderSilo)}; } render() { const hosts = this.props.dashboardCounters.hosts || []; if (hosts.length === 0) return no data; const updateZones = hosts .map(x => x.updateZone) .sort() .filter((v, i, a) => a.indexOf(v) === i); const faultZones = hosts .map(x => x.faultZone) .sort() .filter((v, i, a) => a.indexOf(v) === i); return (
    ; })} {updateZones.map(updateZone => { return ( {faultZones.map(faultZone => { return ( ); })} ); })}
    {faultZones.map(faultZone => { return Fault Zone {faultZone}
    Update Zone {updateZone} {this.renderZone(updateZone, faultZone)}
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/silo-state-label.tsx ================================================ import React from 'react'; type SiloStatus = 'Created' | 'Joining' | 'Active' | 'ShuttingDown' | 'Stopping' | 'Dead'; const labelClassMapper: { [key in SiloStatus]: string } = { Created: 'info', Joining: 'info', Active: 'success', ShuttingDown: 'warning', Stopping: 'warning', Dead: 'danger' }; interface SiloStateLabelProps { status: string; } export default class SiloStateLabel extends React.Component { render() { const status = this.props.status as SiloStatus; return ( {this.props.status || 'unknown'} ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/silo.tsx ================================================ import React from 'react'; import Gauge from '../components/gauge-widget'; import PropertiesWidget from '../components/properties-widget'; import GrainBreakdown from '../components/grain-table'; import ChartWidget from '../components/multi-series-chart-widget'; import Panel from '../components/panel'; import Chart from '../components/time-series-chart'; interface SiloDataPoint { count: number; elapsedTime: number; period: number | string; exceptionCount: number; cpuUsage?: number; totalPhysicalMemory?: number; availableMemory?: number; activationCount?: number; recentlyUsedActivationCount?: number; clientCount?: number; receivedMessages?: number; sentMessages?: number; receiveQueueLength?: number; requestQueueLength?: number; sendQueueLength?: number; } interface SiloStatsData { [key: string]: SiloDataPoint; } interface SimpleGrainStat { siloAddress: string; [key: string]: any; } interface Host { siloAddress: string; hostName?: string; roleName?: string; siloName?: string; proxyPort?: number; updateZone?: number; faultZone?: number; } interface DashboardCounters { simpleGrainStats?: SimpleGrainStat[]; hosts: Host[]; } interface SiloProperties { orleansVersion?: string; hostVersion?: string; } interface SiloMetadata { [key: string]: string; } interface SiloProps { silo: string; data: (SiloDataPoint | null)[]; siloProperties: SiloProperties; siloMetadata: (SiloMetadata | null)[]; dashboardCounters: DashboardCounters; siloStats: SiloStatsData; } interface SiloGraphProps { stats: SiloStatsData; } const SiloGraph: React.FC = (props) => { const values: SiloDataPoint[] = []; const timepoints: (number | string)[] = []; Object.keys(props.stats).forEach(key => { values.push(props.stats[key]); timepoints.push(props.stats[key].period); }); if (!values.length) { return null; } while (values.length < 100) { values.unshift({ count: 0, elapsedTime: 0, period: 0, exceptionCount: 0 }); timepoints.unshift(''); } return (
    z.exceptionCount), values.map(z => z.count), values.map(z => (z.count === 0 ? 0 : z.elapsedTime / z.count)) ]} />
    ); }; export default class Silo extends React.Component { hasData(value: (SiloDataPoint | null)[]): boolean { for (let i = 0; i < value.length; i++) { if (value[i] !== null) return true; } return false; } querySeries(lambda: (x: SiloDataPoint) => number): number[] { return this.props.data.map(function(x) { if (!x) return 0; return lambda(x); }); } hasSeries(lambda: (x: SiloDataPoint) => boolean): boolean { let hasValue = false; for (const key in this.props.data) { const value = this.props.data[key]; if (value && lambda(value)) { hasValue = true; } } return hasValue; } render() { if (!this.hasData(this.props.data)) { return (

    No data available for this silo

    Show all silos

    ); } const last = this.props.data[this.props.data.length - 1]!; const properties: { [key: string]: string | number } = { Clients: last.clientCount || '0', 'Messages received': last.receivedMessages || '0', 'Messages sent': last.sentMessages || '0', 'Receive queue': last.receiveQueueLength || '0', 'Request queue': last.requestQueueLength || '0', 'Send queue': last.sendQueueLength || '0' }; const grainStats = ( this.props.dashboardCounters.simpleGrainStats || [] ).filter(function(x) { return x.siloAddress === this.props.silo; }, this); const silo = this.props.dashboardCounters.hosts.filter( x => x.siloAddress === this.props.silo )[0] || {}; const configuration: { [key: string]: string | number | undefined } = { 'Host name': silo.hostName, 'Role name': silo.roleName, 'Silo name': silo.siloName, 'Proxy port': silo.proxyPort, 'Update zone': silo.updateZone, 'Fault zone': silo.faultZone }; if (this.props.siloProperties.orleansVersion) { configuration['Orleans version'] = this.props.siloProperties.orleansVersion; } if (this.props.siloProperties.hostVersion) { configuration['Host version'] = this.props.siloProperties.hostVersion; } let cpuGauge: React.ReactNode; let memGauge: React.ReactNode; let metadata: React.ReactNode; if (this.hasSeries(x => (x.cpuUsage || 0) > 0)) { cpuGauge = (
    x.cpuUsage || 0)]} />
    ); } else { cpuGauge = (

    CPU Usage

    No data available
    ); } if (this.hasSeries(x => (x.totalPhysicalMemory || 0) - (x.availableMemory || 0) > 0)) { memGauge = (
    ((x.totalPhysicalMemory || 0) - (x.availableMemory || 0)) / (1024 * 1024) ) ]} />
    ); } else { memGauge = (

    Memory Usage

    No data available
    ); } return (
    {cpuGauge}
    {memGauge}
    x.activationCount || 0), this.querySeries(x => x.recentlyUsedActivationCount || 0) ]} />
    /{' '} number of requests per second
    / {' '} failed requests
    /{' '} average latency in milliseconds
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/silos/silos.tsx ================================================ import React from 'react'; import CounterWidget from '../components/counter-widget'; import ChartWidget from '../components/multi-series-chart-widget'; import HostsWidget from './host-table'; import SiloGrid from './silo-grid'; import Panel from '../components/panel'; interface DashboardCounters { totalActiveHostCount: number; totalActiveHostCountHistory: number[]; [key: string]: any; } interface SilosProps { dashboardCounters: DashboardCounters; } export default class Silos extends React.Component { render() { return (
    ); } } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/skin-purple.css ================================================ /* Custom purple skin for Orleans Dashboard (compatible with AdminLTE v3) */ /* Sidebar styling */ .skin-purple .main-sidebar, .skin-purple .sidebar { background-color: #222d32; } .skin-purple .nav-sidebar .nav-link { color: #b8c7ce; border-left: 3px solid transparent; } .skin-purple .nav-sidebar .nav-link:hover { color: #fff; background: #1e282c; } .skin-purple .nav-sidebar .nav-link.active, .skin-purple .nav-sidebar .nav-item.menu-open > .nav-link { color: #fff; background: #1e282c; border-left-color: #663399; } .skin-purple .nav-sidebar .nav-link p { color: inherit; } /* Info box styling for purple theme */ .skin-purple .info-box { box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2); margin-bottom: 1rem; } .skin-purple .info-box-icon { border-radius: .25rem 0 0 .25rem; display: flex; align-items: center; justify-content: center; } .skin-purple .bg-purple { background-color: #663399 !important; color: #fff; } /* Panel/Box styling */ .skin-purple .box, .skin-purple .card { box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2); } /* Ensure proper spacing */ .skin-purple .content-wrapper { min-height: 100vh; } /* Fix for sidebar menu items */ .skin-purple .sidebar-menu > li.header { color: #4b646f; background: #1a2226; } .skin-purple .sidebar a:hover { text-decoration: none; } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/styles.css ================================================ /* Import Bootstrap */ @import 'bootstrap/dist/css/bootstrap.css'; /* Import Font Awesome */ @import '@fortawesome/fontawesome-free/css/all.css'; /* Import AdminLTE core styles */ @import 'admin-lte/dist/css/adminlte.css'; /* Import custom purple skin theme */ @import './skin-purple.css'; /* Import theme system (light/dark) */ @import './themes.css'; /* Import custom Orleans Dashboard styles */ @import './custom.css'; ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/themes.css ================================================ /* Theme system using CSS custom properties */ /* Light theme (default) */ :root { --log-bg: #fff; --log-border: #ddd; --log-color: #333; --form-control-bg: #fff; --form-control-border: #ccc; --form-control-color: #333; --log-filter-bg: #f5f5f5; --log-filter-shadow: #d8d9da; --app-main-bg: #fff; --app-main-color: #333; --app-content-header-bg: #fff; --app-content-header-color: #333; --info-box-bg: #fff; --info-box-color: #333; --info-box-text-color: #333; --card-bg: #fff; --card-color: #333; --card-border-top: #663399; --card-header-bg: #f5f5f5; --card-header-color: #333; --card-header-border: rgba(0,0,0,.125); --card-body-bg: #fff; --card-body-color: #333; --table-border: #dee2e6; --table-color: #333; --table-striped-bg: rgba(0,0,0,.05); --well-bg: #f5f5f5; --well-border: #e3e3e3; --well-color: #333; --small-color: #6c757d; --btn-default-bg: #fff; --btn-default-border: #ccc; --btn-default-color: #333; --btn-default-hover-bg: #e6e6e6; --btn-default-hover-border: #adadad; --btn-primary-bg: #663399; --btn-primary-border: #663399; --btn-primary-color: #ffffff; --btn-primary-hover-bg: #552d80; --btn-primary-hover-border: #4d2873; --link-color: #7952b3; --link-hover-color: #663399; --sidebar-bg: #f8f9fa; --sidebar-nav-link-color: #495057; --sidebar-nav-link-hover-bg: #e9ecef; --sidebar-nav-link-hover-color: #212529; --sidebar-nav-link-active-bg: #663399; --sidebar-nav-link-active-color: #ffffff; --sidebar-nav-icon-color: #6c757d; --sidebar-nav-icon-active-color: #ffffff; --sidebar-brand-bg: #ffffff; --sidebar-brand-border: #dee2e6; --sidebar-brand-title-color: #212529; --sidebar-brand-version-color: #6c757d; } /* Override Bootstrap 5 CSS variables for light theme */ [data-bs-theme="light"] { --bs-body-bg: #fff; --bs-body-color: #333; --bs-table-bg: transparent; --bs-table-color: #333; --bs-card-bg: #fff; --bs-card-color: #333; --bs-border-color: #dee2e6; --bs-primary: #663399; --bs-primary-rgb: 102, 51, 153; --bs-link-color: #7952b3; --bs-link-hover-color: #663399; } /* Dark theme */ body.dark-mode { --log-bg: #1e282c; --log-border: #333; --log-color: silver; --form-control-bg: #222d32; --form-control-border: #333; --form-control-color: #c2c7d0; --log-filter-bg: #222d32; --log-filter-shadow: #444c52; --app-main-bg: #1e282c; --app-main-color: #c2c7d0; --app-content-header-bg: #1e282c; --app-content-header-color: #c2c7d0; --info-box-bg: #343a40; --info-box-color: #c2c7d0; --info-box-text-color: #c2c7d0; --card-bg: #343a40; --card-color: #c2c7d0; --card-border-top: #8b6bb3; --card-header-bg: #3d444b; --card-header-color: #c2c7d0; --card-header-border: #6c757d; --card-body-bg: #343a40; --card-body-color: #c2c7d0; --table-border: #454d55; --table-color: #c2c7d0; --table-striped-bg: rgba(255,255,255,.05); --well-bg: #343a40; --well-border: #6c757d; --well-color: #c2c7d0; --small-color: #adb5bd; --btn-default-bg: #454d55; --btn-default-border: #6c757d; --btn-default-color: #c2c7d0; --btn-default-hover-bg: #545b62; --btn-default-hover-border: #6c757d; --btn-primary-bg: #663399; --btn-primary-border: #663399; --btn-primary-color: #ffffff; --btn-primary-hover-bg: #552d80; --btn-primary-hover-border: #4d2873; --link-color: #a78bfa; --link-hover-color: #c4b5fd; --sidebar-bg: #343a40; --sidebar-nav-link-color: #c2c7d0; --sidebar-nav-link-hover-bg: #3d444b; --sidebar-nav-link-hover-color: #ffffff; --sidebar-nav-link-active-bg: #663399; --sidebar-nav-link-active-color: #ffffff; --sidebar-nav-icon-color: #c2c7d0; --sidebar-nav-icon-active-color: #ffffff; --sidebar-brand-bg: #343a40; --sidebar-brand-border: #4b545c; --sidebar-brand-title-color: #b8c7ce; --sidebar-brand-version-color: #b8c7ce; } /* Override Bootstrap 5 CSS variables for dark theme */ [data-bs-theme="dark"] { --bs-body-bg: #1e282c; --bs-body-color: #c2c7d0; --bs-table-bg: transparent; --bs-table-color: #c2c7d0; --bs-card-bg: #343a40; --bs-card-color: #c2c7d0; --bs-border-color: #454d55; --bs-primary: #663399; --bs-primary-rgb: 102, 51, 153; --bs-link-color: #a78bfa; --bs-link-hover-color: #c4b5fd; } /* Apply theme variables to components */ .log { background: var(--log-bg) !important; border: 1px solid var(--log-border) !important; color: var(--log-color) !important; } .form-control { background: var(--form-control-bg) !important; border: 1px solid var(--form-control-border) !important; color: var(--form-control-color) !important; } .log-filter { background: var(--log-filter-bg) !important; box-shadow: 4px -2px 5px 5px var(--log-filter-shadow) !important; } .app-main { background: var(--app-main-bg) !important; color: var(--app-main-color) !important; } .app-content { background: var(--app-main-bg) !important; color: var(--app-main-color) !important; } .app-content-header { background: var(--app-content-header-bg) !important; color: var(--app-content-header-color) !important; } .info-box { background: var(--info-box-bg) !important; color: var(--info-box-color) !important; } .info-box-text, .info-box-number { color: var(--info-box-text-color) !important; } .card, .box { background: var(--card-bg) !important; color: var(--card-color) !important; border-top-color: var(--card-border-top) !important; } .card-header, .box-header { background: var(--card-header-bg) !important; color: var(--card-header-color) !important; border-bottom: 1px solid var(--card-header-border) !important; } .card-title, .box-title { color: var(--card-header-color) !important; } .card-body, .box-body { background: var(--card-body-bg) !important; color: var(--card-body-color) !important; } .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { border-top: 1px solid var(--table-border) !important; border-bottom: 1px solid var(--table-border) !important; color: var(--table-color) !important; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { border: 1px solid var(--table-border) !important; } .table-striped tbody tr:nth-of-type(odd) { background-color: var(--table-striped-bg) !important; } .well { background: var(--well-bg) !important; border: 1px solid var(--well-border) !important; color: var(--well-color) !important; } small { color: var(--small-color) !important; } .btn-default { background-color: var(--btn-default-bg) !important; border-color: var(--btn-default-border) !important; color: var(--btn-default-color) !important; } .btn-default:hover { background-color: var(--btn-default-hover-bg) !important; border-color: var(--btn-default-hover-border) !important; } .btn-primary { background-color: var(--btn-primary-bg) !important; border-color: var(--btn-primary-border) !important; color: var(--btn-primary-color) !important; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active { background-color: var(--btn-primary-hover-bg) !important; border-color: var(--btn-primary-hover-border) !important; color: var(--btn-primary-color) !important; } a { color: var(--link-color) !important; } a:hover { color: var(--link-hover-color) !important; } /* Sidebar theming */ .app-sidebar { background-color: var(--sidebar-bg) !important; } .app-sidebar .sidebar { background-color: var(--sidebar-bg) !important; } .nav-sidebar .nav-link { color: var(--sidebar-nav-link-color) !important; } .nav-sidebar .nav-link:hover { background-color: var(--sidebar-nav-link-hover-bg) !important; color: var(--sidebar-nav-link-hover-color) !important; } .nav-sidebar .nav-link.active { background-color: var(--sidebar-nav-link-active-bg) !important; color: var(--sidebar-nav-link-active-color) !important; margin-left: 0.5rem !important; margin-right: 0.5rem !important; border-radius: 0.25rem !important; } .nav-sidebar .nav-icon { color: var(--sidebar-nav-icon-color) !important; } .nav-sidebar .nav-link.active .nav-icon { color: var(--sidebar-nav-icon-active-color) !important; } .app-sidebar .brand-link { background-color: var(--sidebar-brand-bg) !important; border-bottom: 1px solid var(--sidebar-brand-border) !important; padding: 15px !important; } .brand-title { color: var(--sidebar-brand-title-color) !important; font-weight: 500 !important; margin-top: 5px !important; margin-bottom: 0 !important; font-size: 26px !important; } .brand-version { color: var(--sidebar-brand-version-color) !important; margin-top: 5px !important; margin-bottom: 25px !important; } /* Bootstrap label/badge components - use purple theme */ .label, .badge { display: inline-block; padding: 0.25em 0.6em; font-size: 0.75rem; font-weight: 600; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.375rem; } .label-primary, .badge-primary { background-color: #663399 !important; color: #ffffff !important; } /* Bootstrap alert components */ .alert-info { background-color: #e7ddf7 !important; border-color: #c4b5fd !important; color: #4c2a85 !important; } /* Progress bars */ .progress-bar-primary, .bg-primary { background-color: #663399 !important; } /* Bootstrap 3 legacy classes for backwards compatibility */ .pull-right { float: right !important; } .pull-left { float: left !important; } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/src/vite-env.d.ts ================================================ /// declare module '*.png' { const value: string; export default value; } declare module '*.jpg' { const value: string; export default value; } declare module '*.jpeg' { const value: string; export default value; } declare module '*.svg' { const value: string; export default value; } declare module '*.gif' { const value: string; export default value; } declare module '*.webp' { const value: string; export default value; } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, /* Paths */ "baseUrl": ".", "paths": { "@/*": ["./src/*"] } }, "include": ["src"] } ================================================ FILE: src/Dashboard/Orleans.Dashboard.App/vite.config.ts ================================================ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import commonjs from "vite-plugin-commonjs"; import path from "path"; // https://vite.dev/config/ export default defineConfig({ plugins: [commonjs(), react()], base: "./", resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, optimizeDeps: { include: ["react", "react-dom"], }, build: { emptyOutDir: true, sourcemap: true, commonjsOptions: { include: [/node_modules/, /src/], transformMixedEsModules: true, }, rollupOptions: { output: { manualChunks: undefined, inlineDynamicImports: true, entryFileNames: "index.min.js", assetFileNames: (assetInfo) => { if (assetInfo.name?.match(/\.(woff|woff2|ttf|otf|eot)$/)) { return "fonts/[name][extname]"; } if (assetInfo.name?.match(/\.(svg|png|jpg|jpeg|gif|webp|ico)$/)) { return "img/[name][extname]"; } return "[name][extname]"; }, }, }, }, }); ================================================ FILE: src/Directory.Build.props ================================================ <_ParentDirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildPropsFile)')) netstandard2.0 net8.0;net10.0 true $(MSBuildProjectDirectory)\README.md true README.md true https://github.com/dotnet/orleans ================================================ FILE: src/Directory.Build.targets ================================================ <_ParentDirectoryBuildTargetsPath Condition="'$(_DirectoryBuildTargetsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildTargetsFile)')) false <_TranslateUrlPattern>(https://.*\.visualstudio\.com/.*/_git/[^/]*)|(https://dev\.azure\.com/.*/_git/[^/]*) <_TranslateUrlReplacement>$(PublicRepositoryUrl) $([System.Text.RegularExpressions.Regex]::Replace($(ScmRepositoryUrl), $(_TranslateUrlPattern), $(_TranslateUrlReplacement))) $([System.Text.RegularExpressions.Regex]::Replace(%(SourceRoot.ScmRepositoryUrl), $(_TranslateUrlPattern), $(_TranslateUrlReplacement))) $(MSBuildThisFileDirectory)/api/$([MSBuild]::MakeRelative($(MSBuildThisFileDirectory), $(MSBuildProjectDirectory)))/ $(GenAPITargetDirectory)$(AssemblyName).cs ================================================ FILE: src/Orleans.Analyzers/AbstractPropertiesCannotBeSerializedAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using System.Collections.Immutable; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AbstractPropertiesCannotBeSerializedAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0006"; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AbstractOrStaticMembersCannotBeSerializedTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AbstractOrStaticMembersCannotBeSerializedMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var idAttribute = context.Compilation.GetTypeByMetadataName("Orleans.IdAttribute"); var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); if (idAttribute is null || generateSerializerAttributeSymbol is null) { return; } context.RegisterSymbolStartAction(context => { if (SerializationAttributesHelper.ShouldGenerateSerializer((INamedTypeSymbol)context.Symbol, generateSerializerAttributeSymbol)) { context.RegisterOperationAction(context => AnalyzeAttribute(context, idAttribute), OperationKind.Attribute); } }, SymbolKind.NamedType); }); } private static void AnalyzeAttribute(OperationAnalysisContext context, INamedTypeSymbol idAttribute) { var attributeOperation = (IAttributeOperation)context.Operation; string modifier; if (context.ContainingSymbol.IsAbstract) { modifier = "abstract"; } else if (context.ContainingSymbol.IsStatic) { modifier = "static"; } else { return; } if (attributeOperation.Operation is IObjectCreationOperation objectCreationOperation && idAttribute.Equals(objectCreationOperation.Constructor.ContainingType, SymbolEqualityComparer.Default)) { context.ReportDiagnostic(Diagnostic.Create(Rule, attributeOperation.Syntax.GetLocation(), context.ContainingSymbol.Name, modifier)); } } } } ================================================ FILE: src/Orleans.Analyzers/AliasClashAttributeAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Globalization; namespace Orleans.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AliasClashAttributeAnalyzer : DiagnosticAnalyzer { private readonly record struct TypeAliasInfo(string TypeName, Location Location); public const string RuleId = "ORLEANS0011"; private static readonly DiagnosticDescriptor Rule = new( id: RuleId, category: "Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, title: new LocalizableResourceString(nameof(Resources.AliasClashDetectedTitle), Resources.ResourceManager, typeof(Resources)), messageFormat: new LocalizableResourceString(nameof(Resources.AliasClashDetectedMessageFormat), Resources.ResourceManager, typeof(Resources)), description: new LocalizableResourceString(nameof(Resources.AliasClashDetectedDescription), Resources.ResourceManager, typeof(Resources)), helpLinkUri: null, customTags: [WellKnownDiagnosticTags.CompilationEnd]); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(context => { var aliasMap = new ConcurrentDictionary>(); var aliasAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.AliasAttribute"); context.RegisterSymbolAction( context => CollectTypeAliases(context, aliasMap, aliasAttributeSymbol), SymbolKind.NamedType); // We can immediately check duplicate method‐aliases in grain interfaces. context.RegisterSymbolAction( context => CheckMethodAliases(context, aliasMap, aliasAttributeSymbol), SymbolKind.NamedType); // Only at the very end, we do one single‐threaded scan for type‐alias clashes only. context.RegisterCompilationEndAction(context => { foreach (var kvp in aliasMap) { var alias = kvp.Key; var infos = kvp.Value; var distinctTypes = infos .Select(i => i.TypeName) .Distinct() .ToList(); if (distinctTypes.Count <= 1) { continue; // If more than one different type claimed it. } var firstType = distinctTypes[0]; foreach (var info in infos.Where(i => i.TypeName != firstType)) { context.ReportDiagnostic(Diagnostic.Create(Rule, info.Location, alias, firstType)); } } }); }); } private static void CollectTypeAliases( SymbolAnalysisContext context, ConcurrentDictionary> aliasMap, INamedTypeSymbol aliasAttributeSymbol) { var typeSymbol = (INamedTypeSymbol)context.Symbol; if (typeSymbol.TypeKind == TypeKind.Interface && !typeSymbol.ExtendsGrainInterface()) { return; // Skip interfaces that dont extend IAddressable } foreach (var attr in typeSymbol.GetAttributes()) { if (!aliasAttributeSymbol.Equals(attr.AttributeClass, SymbolEqualityComparer.Default)) continue; var alias = attr.ConstructorArguments.FirstOrDefault().Value as string; if (string.IsNullOrEmpty(alias)) continue; var info = new TypeAliasInfo(typeSymbol.ToDisplayString(), attr.ApplicationSyntaxReference.GetSyntax().GetLocation()); aliasMap.AddOrUpdate( key: alias, addValueFactory: _ => new ConcurrentBag([info]), updateValueFactory: (_, bag) => { bag.Add(info); return bag; }); } } private static void CheckMethodAliases( SymbolAnalysisContext context, ConcurrentDictionary> aliasMap, INamedTypeSymbol aliasAttributeSymbol) { if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Interface } interfaceSymbol) { return; } var methodBags = new List<(string Alias, Location Location)>(); foreach (var method in interfaceSymbol.GetMembers().OfType()) { foreach (var attr in method.GetAttributes()) { if (!aliasAttributeSymbol.Equals(attr.AttributeClass, SymbolEqualityComparer.Default)) { continue; } var alias = attr.ConstructorArguments.FirstOrDefault().Value as string; if (!string.IsNullOrEmpty(alias)) { methodBags.Add((alias, attr.ApplicationSyntaxReference.GetSyntax().GetLocation())); } } } // Find duplicate aliases within the interface's methods. var duplicateMethodAliases = methodBags .GroupBy(x => x.Alias) .Where(g => g.Count() > 1); foreach (var group in duplicateMethodAliases) { var duplicates = group.Skip(1).ToList(); var (prefix, suffix) = ParsePrefixAndNumericSuffix(group.Key); foreach (var duplicate in duplicates) { string newAlias; do { suffix++; newAlias = $"{prefix}{suffix}"; } while (aliasMap.ContainsKey(newAlias) || methodBags.Any(b => b.Alias == newAlias)); var properties = ImmutableDictionary.CreateBuilder(); properties.Add("AliasName", prefix); properties.Add("AliasSuffix", suffix.ToString(CultureInfo.InvariantCulture)); context.ReportDiagnostic(Diagnostic.Create(Rule, duplicate.Location, properties, group.Key)); } } } private static (string Prefix, ulong Suffix) ParsePrefixAndNumericSuffix(string input) { var suffixLength = GetNumericSuffixLength(input); if (suffixLength == 0) { return (input, 0); } return ( input.Substring(0, input.Length - suffixLength), ulong.Parse(input.Substring(input.Length - suffixLength), CultureInfo.InvariantCulture) ); } private static int GetNumericSuffixLength(string input) { var suffixLength = 0; for (var i = input.Length - 1; i >= 0; --i) { if (!char.IsDigit(input[i])) break; suffixLength++; } return suffixLength; } } ================================================ FILE: src/Orleans.Analyzers/AliasClashAttributeCodeFix.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; using System.Threading; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GenerateAliasAttributesCodeFix)), Shared] public class AliasClashAttributeCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(AliasClashAttributeAnalyzer.RuleId); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); if (root.FindNode(diagnostic.Location.SourceSpan) is not AttributeSyntax attribute) { return; } if (!diagnostic.Properties.TryGetValue("AliasName", out var aliasName)) { return; } if (!diagnostic.Properties.TryGetValue("AliasSuffix", out var aliasSuffix)) { return; } context.RegisterCodeFix( CodeAction.Create( Resources.AliasClashDetectedTitle, createChangedDocument: _ => { var newAliasName = $"{aliasName}{aliasSuffix}"; var newAttribute = attribute.ReplaceNode( attribute.ArgumentList.Arguments[0].Expression, LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(newAliasName))); var newRoot = root.ReplaceNode(attribute, newAttribute); var newDocument = context.Document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument); }, equivalenceKey: AliasClashAttributeAnalyzer.RuleId), diagnostic); } } ================================================ FILE: src/Orleans.Analyzers/AlwaysInterleaveDiagnosticAnalyzer.cs ================================================ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AlwaysInterleaveDiagnosticAnalyzer : DiagnosticAnalyzer { private const string AlwaysInterleaveAttributeName = "Orleans.Concurrency.AlwaysInterleaveAttribute"; public const string DiagnosticId = "ORLEANS0001"; public const string Title = "[AlwaysInterleave] must only be used on the grain interface method and not the grain class method"; public const string MessageFormat = Title; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(context => { var alwaysInterleaveAttributeSymbol = context.Compilation.GetTypeByMetadataName(AlwaysInterleaveAttributeName); if (alwaysInterleaveAttributeSymbol is not null) { context.RegisterSymbolAction(context => AnalyzeMethod(context, alwaysInterleaveAttributeSymbol), SymbolKind.Method); } }); } private static void AnalyzeMethod(SymbolAnalysisContext context, INamedTypeSymbol alwaysInterleaveAttribute) { var methodSymbol = (IMethodSymbol)context.Symbol; if (methodSymbol.ContainingType.TypeKind == TypeKind.Interface) { // TODO: Check that interface inherits from IGrain return; } foreach (var attribute in methodSymbol.GetAttributes()) { if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, alwaysInterleaveAttribute)) { return; } var syntaxReference = attribute.ApplicationSyntaxReference; context.ReportDiagnostic( Diagnostic.Create(Rule, Location.Create(syntaxReference.SyntaxTree, syntaxReference.Span))); } } } } ================================================ FILE: src/Orleans.Analyzers/AnalyzerReleases.Shipped.md ================================================ ## Release 3.3.0 ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- ORLEANS0003 | Usage | Error | Inherit from Grain ## Release 7.0.0 ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- ORLEANS0001 | Usage | Error | [AlwaysInterleave] must only be used on the grain interface method and not the grain class method ORLEANS0002 | Usage | Error | Reference parameter modifiers are not allowed ORLEANS0004 | Usage | Error | Add serialization [Id] and [NonSerialized] attributes ORLEANS0005 | Usage | Info | Add [GenerateSerializer] attribute to [Serializable] type ORLEANS0006 | Usage | Error | Abstract/serialized properties cannot be serialized ORLEANS0007 | Usage | Error | ORLEANS0008 | Usage | Error | Grain interfaces cannot have properties ORLEANS0009 | Usage | Error | Grain interface methods must return a compatible type ORLEANS0010 | Usage | Info | Add missing [Alias] attribute ORLEANS0011 | Usage | Error | The [Alias] attribute must be unique to the declaring type ORLEANS0012 | Usage | Error | The [Id] attribute must be unique to each members of the declaring type ORLEANS0013 | Usage | Error | This attribute should not be used on grain implementations ### Removed Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- ORLEANS0003 | Usage | Error | Inherit from Grain ================================================ FILE: src/Orleans.Analyzers/AnalyzerReleases.Unshipped.md ================================================ ; Please do not edit this file manually, it should only be updated through code fix application. ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- ORLEANS0014 | Usage | Warning | ConfigureAwaitAnalyzer, Grain code should not use ConfigureAwait(false) or ConfigureAwait without ContinueOnCapturedContext ================================================ FILE: src/Orleans.Analyzers/AtMostOneOrleansConstructorAnalyzer.cs ================================================ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AtMostOneOrleansConstructorAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0007"; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AtMostOneOrleansConstructorTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AtMostOneOrleansConstructorMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); if (generateSerializerAttributeSymbol is not null) { context.RegisterSymbolAction(context => AnalyzeNamedType(context, generateSerializerAttributeSymbol), SymbolKind.NamedType); } }); } private void AnalyzeNamedType(SymbolAnalysisContext context, INamedTypeSymbol generateSerializerAttributeSymbol) { var symbol = (INamedTypeSymbol)context.Symbol; if (SerializationAttributesHelper.ShouldGenerateSerializer(symbol, generateSerializerAttributeSymbol)) { var foundAttribute = false; foreach (var constructor in symbol.Constructors) { if (constructor.HasAttribute(generateSerializerAttributeSymbol)) { if (foundAttribute) { context.ReportDiagnostic(Diagnostic.Create(Rule, symbol.Locations[0])); return; } foundAttribute = true; } } } } } } ================================================ FILE: src/Orleans.Analyzers/ConfigureAwaitAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System; using System.Collections.Immutable; namespace Orleans.Analyzers; /// /// An analyzer that warns when grain code uses ConfigureAwait(false) or ConfigureAwait(ConfigureAwaitOptions) /// without the ContinueOnCapturedContext flag. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ConfigureAwaitAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0014"; private static readonly LocalizableString Title = new LocalizableResourceString( nameof(Resources.AvoidConfigureAwaitFalseInGrainTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString( nameof(Resources.AvoidConfigureAwaitFalseInGrainMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString( nameof(Resources.AvoidConfigureAwaitFalseInGrainDescription), Resources.ResourceManager, typeof(Resources)); private static readonly DiagnosticDescriptor Rule = new( id: RuleId, title: Title, messageFormat: MessageFormat, category: "Usage", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression); } private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; // Check if this is a ConfigureAwait call if (!IsConfigureAwaitCall(invocation, out var methodName)) { return; } // Check if this code is inside a grain class if (!IsInsideGrainClass(invocation, context.SemanticModel)) { return; } // Get the symbol for the invocation to analyze the argument var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken); if (symbolInfo.Symbol is not IMethodSymbol methodSymbol) { return; } // Only check ConfigureAwait method if (!string.Equals(methodSymbol.Name, "ConfigureAwait", StringComparison.Ordinal)) { return; } // Check if it's a ConfigureAwait method on a Task-like type var containingType = methodSymbol.ContainingType; if (!IsTaskLikeType(containingType)) { return; } // Get the arguments var arguments = invocation.ArgumentList?.Arguments; if (arguments is null || arguments.Value.Count == 0) { return; } var firstArgument = arguments.Value[0]; var argumentType = context.SemanticModel.GetTypeInfo(firstArgument.Expression, context.CancellationToken).Type; if (argumentType is null) { return; } // Check for ConfigureAwait(bool) overload if (argumentType.SpecialType == SpecialType.System_Boolean) { var constantValue = context.SemanticModel.GetConstantValue(firstArgument.Expression, context.CancellationToken); if (constantValue.HasValue && constantValue.Value is false) { // ConfigureAwait(false) is not allowed context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation())); } return; } // Check for ConfigureAwait(ConfigureAwaitOptions) overload if (IsConfigureAwaitOptionsType(argumentType)) { if (!HasContinueOnCapturedContextFlag(firstArgument.Expression, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation())); } } } private static bool IsConfigureAwaitCall(InvocationExpressionSyntax invocation, out string methodName) { methodName = null; if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) { methodName = memberAccess.Name.Identifier.Text; return string.Equals(methodName, "ConfigureAwait", StringComparison.Ordinal); } return false; } private static bool IsInsideGrainClass(SyntaxNode node, SemanticModel semanticModel) { // Walk up to find the containing type declaration var current = node.Parent; while (current is not null) { if (current is ClassDeclarationSyntax classDeclaration) { var typeSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGrainClass()) { return true; } } else if (current is StructDeclarationSyntax or RecordDeclarationSyntax) { // If we hit a struct or record before finding a grain class, we're not in a grain // (structs and records can't be grains) return false; } current = current.Parent; } return false; } private static bool IsTaskLikeType(INamedTypeSymbol type) { if (type is null) { return false; } var fullName = type.ToDisplayString(NullableFlowState.None); // Check for common task-like types that have ConfigureAwait return fullName.StartsWith("System.Threading.Tasks.Task", StringComparison.Ordinal) || fullName.StartsWith("System.Threading.Tasks.ValueTask", StringComparison.Ordinal) || fullName.StartsWith("System.Runtime.CompilerServices.ConfiguredTaskAwaitable", StringComparison.Ordinal) || fullName.StartsWith("System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable", StringComparison.Ordinal) || fullName.StartsWith("System.Collections.Generic.IAsyncEnumerable", StringComparison.Ordinal) || fullName.StartsWith("System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable", StringComparison.Ordinal); } private static bool IsConfigureAwaitOptionsType(ITypeSymbol type) { if (type is null) { return false; } return string.Equals( type.ToDisplayString(NullableFlowState.None), "System.Threading.Tasks.ConfigureAwaitOptions", StringComparison.Ordinal); } private static bool HasContinueOnCapturedContextFlag(ExpressionSyntax expression, SemanticModel semanticModel, System.Threading.CancellationToken cancellationToken) { // ConfigureAwaitOptions.ContinueOnCapturedContext has value 1 const int ContinueOnCapturedContextValue = 1; // Try to get the constant value var constantValue = semanticModel.GetConstantValue(expression, cancellationToken); if (constantValue.HasValue && constantValue.Value is int intValue) { // Check if ContinueOnCapturedContext flag (value 1) is set return (intValue & ContinueOnCapturedContextValue) != 0; } // If we can't determine the value at compile time, we need to analyze the expression // to check if it includes ContinueOnCapturedContext return ExpressionIncludesContinueOnCapturedContext(expression, semanticModel, cancellationToken); } private static bool ExpressionIncludesContinueOnCapturedContext(ExpressionSyntax expression, SemanticModel semanticModel, System.Threading.CancellationToken cancellationToken) { // Handle member access like ConfigureAwaitOptions.ContinueOnCapturedContext if (expression is MemberAccessExpressionSyntax memberAccess) { var memberName = memberAccess.Name.Identifier.Text; if (string.Equals(memberName, "ContinueOnCapturedContext", StringComparison.Ordinal)) { return true; } } // Handle binary OR expressions like ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding if (expression is BinaryExpressionSyntax binaryExpression && binaryExpression.IsKind(SyntaxKind.BitwiseOrExpression)) { return ExpressionIncludesContinueOnCapturedContext(binaryExpression.Left, semanticModel, cancellationToken) || ExpressionIncludesContinueOnCapturedContext(binaryExpression.Right, semanticModel, cancellationToken); } // Handle parenthesized expressions if (expression is ParenthesizedExpressionSyntax parenthesized) { return ExpressionIncludesContinueOnCapturedContext(parenthesized.Expression, semanticModel, cancellationToken); } // Handle cast expressions if (expression is CastExpressionSyntax castExpression) { return ExpressionIncludesContinueOnCapturedContext(castExpression.Expression, semanticModel, cancellationToken); } // If we encounter a variable or method call, we can't statically determine the flags // In this case, we give the benefit of the doubt and don't report if (expression is IdentifierNameSyntax or InvocationExpressionSyntax) { // Try to get the constant value as a fallback var constantValue = semanticModel.GetConstantValue(expression, cancellationToken); if (constantValue.HasValue && constantValue.Value is int intValue) { const int ContinueOnCapturedContextValue = 1; return (intValue & ContinueOnCapturedContextValue) != 0; } // Can't determine - don't report false positives return true; } return false; } } ================================================ FILE: src/Orleans.Analyzers/ConfigureAwaitCodeFix.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers; /// /// A code fix provider that converts ConfigureAwait(false) to ConfigureAwait(true) and /// adds ContinueOnCapturedContext to ConfigureAwait(ConfigureAwaitOptions) calls. /// [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ConfigureAwaitCodeFix)), Shared] public class ConfigureAwaitCodeFix : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ConfigureAwaitAnalyzer.RuleId); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; // Find the invocation expression identified by the diagnostic var node = root.FindNode(diagnosticSpan); var invocation = node.FirstAncestorOrSelf(); if (invocation is null) { return; } // Get semantic model to determine which fix to apply var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); var symbolInfo = semanticModel.GetSymbolInfo(invocation, context.CancellationToken); if (symbolInfo.Symbol is not IMethodSymbol methodSymbol) { return; } // Check the parameter type to determine which fix to apply if (methodSymbol.Parameters.Length == 1) { var parameterType = methodSymbol.Parameters[0].Type; if (parameterType.SpecialType == SpecialType.System_Boolean) { // Fix for ConfigureAwait(bool) - change false to true context.RegisterCodeFix( CodeAction.Create( title: Resources.ConfigureAwaitCodeFixTitle, createChangedDocument: ct => FixConfigureAwaitBoolAsync(context.Document, invocation, ct), equivalenceKey: ConfigureAwaitAnalyzer.RuleId + "_Bool"), diagnostic); } else if (string.Equals(parameterType.ToDisplayString(), "System.Threading.Tasks.ConfigureAwaitOptions", StringComparison.Ordinal)) { // Fix for ConfigureAwait(ConfigureAwaitOptions) - add ContinueOnCapturedContext flag context.RegisterCodeFix( CodeAction.Create( title: Resources.ConfigureAwaitCodeFixTitle, createChangedDocument: ct => FixConfigureAwaitOptionsAsync(context.Document, invocation, semanticModel, ct), equivalenceKey: ConfigureAwaitAnalyzer.RuleId + "_Options"), diagnostic); } } } private static async Task FixConfigureAwaitBoolAsync( Document document, InvocationExpressionSyntax invocation, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Create new argument with 'true' instead of 'false' var newArgument = Argument(LiteralExpression(SyntaxKind.TrueLiteralExpression)); var newArgumentList = ArgumentList(SingletonSeparatedList(newArgument)); // Replace the argument list var newInvocation = invocation.WithArgumentList(newArgumentList); var newRoot = root.ReplaceNode(invocation, newInvocation); return document.WithSyntaxRoot(newRoot); } private static async Task FixConfigureAwaitOptionsAsync( Document document, InvocationExpressionSyntax invocation, SemanticModel semanticModel, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var arguments = invocation.ArgumentList?.Arguments; if (arguments is null || arguments.Value.Count == 0) { return document; } var existingArgument = arguments.Value[0].Expression; // Check if the existing argument is ConfigureAwaitOptions.None var constantValue = semanticModel.GetConstantValue(existingArgument, cancellationToken); ExpressionSyntax newExpression; if (constantValue.HasValue && constantValue.Value is int intValue && intValue == 0) { // If it's None (0), just replace with ContinueOnCapturedContext newExpression = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("ConfigureAwaitOptions"), IdentifierName("ContinueOnCapturedContext")); } else { // Otherwise, add ContinueOnCapturedContext using bitwise OR var continueOnCapturedContext = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("ConfigureAwaitOptions"), IdentifierName("ContinueOnCapturedContext")); newExpression = BinaryExpression( SyntaxKind.BitwiseOrExpression, existingArgument.WithoutTrivia(), continueOnCapturedContext); } var newArgument = Argument(newExpression); var newArgumentList = ArgumentList(SingletonSeparatedList(newArgument)); var newInvocation = invocation.WithArgumentList(newArgumentList); var newRoot = root.ReplaceNode(invocation, newInvocation); return document.WithSyntaxRoot(newRoot); } } ================================================ FILE: src/Orleans.Analyzers/Constants.cs ================================================ namespace Orleans.Analyzers { internal static class Constants { public const string SystemNamespace = "System"; public const string IAddressibleFullyQualifiedName = "Orleans.Runtime.IAddressable"; public const string GrainBaseFullyQualifiedName = "Orleans.Grain"; public const string IGrainBaseFullyQualifiedName = "Orleans.IGrainBase"; public const string IGrainFullyQualifiedName = "Orleans.IGrain"; public const string ISystemTargetFullyQualifiedName = "Orleans.ISystemTarget"; public const string IdAttributeName = "Id"; public const string IdAttributeFullyQualifiedName = "global::Orleans.IdAttribute"; public const string GenerateSerializerAttributeName = "GenerateSerializer"; public const string GenerateSerializerAttributeFullyQualifiedName = "global::Orleans.GenerateSerializerAttribute"; public const string SerializableAttributeName = "Serializable"; public const string NonSerializedAttribute = "NonSerialized"; public const string NonSerializedAttributeFullyQualifiedName = "global::System.NonSerializedAttribute"; public const string AliasAttributeName = "Alias"; public const string AliasAttributeFullyQualifiedName = "global::Orleans.AliasAttribute"; } } ================================================ FILE: src/Orleans.Analyzers/GenerateAliasAttributesAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; namespace Orleans.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class GenerateAliasAttributesAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0010"; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AddAliasAttributesTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AddAliasMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AddAliasAttributesDescription), Resources.ResourceManager, typeof(Resources)); private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Info, isEnabledByDefault: true, description: Description); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var aliasAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.AliasAttribute"); var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); var grainSymbol = context.Compilation.GetTypeByMetadataName("Orleans.Grain"); if (aliasAttributeSymbol is not null && generateSerializerAttributeSymbol is not null) { context.RegisterSymbolAction(context => AnalyzeNamedType(context, aliasAttributeSymbol, generateSerializerAttributeSymbol, grainSymbol), SymbolKind.NamedType); } }); } private void AnalyzeNamedType( SymbolAnalysisContext context, INamedTypeSymbol aliasAttributeSymbol, INamedTypeSymbol generateSerializerAttributeSymbol, INamedTypeSymbol grainSymbol) { var symbol = (INamedTypeSymbol)context.Symbol; // Interface types and their methods if (symbol.TypeKind == TypeKind.Interface) { if (!symbol.ExtendsGrainInterface()) { return; } if (!symbol.HasAttribute(aliasAttributeSymbol)) { if (!TryGetDeclarationSyntax(symbol, out InterfaceDeclarationSyntax interfaceDeclaration)) { return; } ReportFor( context, interfaceDeclaration.GetLocation(), interfaceDeclaration.Identifier.ToString(), GetArity(interfaceDeclaration), GetNamespaceAndNesting(interfaceDeclaration)); } foreach (var methodSymbol in symbol.GetMembers().OfType()) { if (methodSymbol.IsStatic) { continue; } if (!methodSymbol.HasAttribute(aliasAttributeSymbol)) { if (!TryGetDeclarationSyntax(methodSymbol, out MethodDeclarationSyntax methodDeclaration)) { continue; } ReportFor(context, methodDeclaration.GetLocation(), methodSymbol.Name, arity: 0, namespaceAndNesting: null); } } return; } // Rest of types: class, struct, record if (symbol.TypeKind is TypeKind.Class or TypeKind.Struct) { if (symbol.DerivesFrom(grainSymbol)) { return; } if (!symbol.HasAttribute(generateSerializerAttributeSymbol)) { return; } if (symbol.HasAttribute(aliasAttributeSymbol)) { return; } if (!TryGetDeclarationSyntax(symbol, out TypeDeclarationSyntax typeDeclaration)) { return; } ReportFor( context, typeDeclaration.GetLocation(), typeDeclaration.Identifier.ToString(), GetArity(typeDeclaration), GetNamespaceAndNesting(typeDeclaration)); } } private static int GetArity(TypeDeclarationSyntax typeDeclarationSyntax) { var node = typeDeclarationSyntax; int arity = 0; while (node is TypeDeclarationSyntax type) { arity += type.Arity; node = type.Parent as TypeDeclarationSyntax; } return arity; } private static string GetNamespaceAndNesting(TypeDeclarationSyntax typeDeclarationSyntax) { SyntaxNode node = typeDeclarationSyntax.Parent; StringBuilder sb = new(); Stack segments = new(); while (node is not null) { if (node is TypeDeclarationSyntax type) { segments.Push(type.Identifier.ToString()); } else if (node is BaseNamespaceDeclarationSyntax ns) { segments.Push(ns.Name.ToString()); } node = node.Parent; } foreach (var segment in segments) { if (sb.Length > 0) { sb.Append('.'); } sb.Append(segment); } return sb.ToString(); } private static bool TryGetDeclarationSyntax(ISymbol symbol, out TSyntax syntax) where TSyntax : SyntaxNode { syntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as TSyntax; return syntax is not null; } private static void ReportFor(SymbolAnalysisContext context, Location location, string typeName, int arity, string namespaceAndNesting) { var builder = ImmutableDictionary.CreateBuilder(); builder.Add("TypeName", typeName); builder.Add("NamespaceAndNesting", namespaceAndNesting); builder.Add("Arity", arity.ToString(System.Globalization.CultureInfo.InvariantCulture)); context.ReportDiagnostic(Diagnostic.Create( descriptor: Rule, location: location, properties: builder.ToImmutable())); } } ================================================ FILE: src/Orleans.Analyzers/GenerateAliasAttributesCodeFix.cs ================================================ using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using System.Composition; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Simplification; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GenerateAliasAttributesCodeFix)), Shared] public class GenerateAliasAttributesCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GenerateAliasAttributesAnalyzer.RuleId); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); foreach (var diagnostic in context.Diagnostics) { var node = root.FindNode(diagnostic.Location.SourceSpan); if (node != null) { // Check if its an interface method var methodDeclaration = node.FirstAncestorOrSelf(); if (methodDeclaration != null) { await FixFor(context, diagnostic, methodDeclaration); continue; } // Check if its a type declaration (interface itself, class, struct, record) var typeDeclaration = node.FirstAncestorOrSelf(); if (typeDeclaration != null) { await FixFor(context, diagnostic, typeDeclaration); continue; } } } } private static async Task FixFor(CodeFixContext context, Diagnostic diagnostic, SyntaxNode declaration) { var documentEditor = await DocumentEditor.CreateAsync(context.Document, context.CancellationToken); var arityString = diagnostic.Properties["Arity"] switch { null or "0" => "", string value => $"`{value}" }; var typeName = diagnostic.Properties["TypeName"]; var ns = diagnostic.Properties["NamespaceAndNesting"] switch { { Length: > 0 } value => $"{value}.", _ => "" }; var aliasAttribute = Attribute( ParseName(Constants.AliasAttributeFullyQualifiedName)) .WithArgumentList( ParseAttributeArgumentList($"(\"{ns}{typeName}{arityString}\")")) .WithAdditionalAnnotations(Simplifier.Annotation); documentEditor.AddAttribute(declaration, aliasAttribute); var updatedDocument = documentEditor.GetChangedDocument(); context.RegisterCodeFix( action: CodeAction.Create( Resources.AddAliasAttributesTitle, createChangedDocument: ct => Task.FromResult(updatedDocument), equivalenceKey: GenerateAliasAttributesAnalyzer.RuleId), diagnostic: diagnostic); } } ================================================ FILE: src/Orleans.Analyzers/GenerateGenerateSerializerAttributeAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class GenerateGenerateSerializerAttributeAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0005"; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AddGenerateSerializerAttributesTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AddGenerateSerializerAttributeMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AddGenerateSerializerAttributeDescription), Resources.ResourceManager, typeof(Resources)); internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Info, isEnabledByDefault: true, description: Description); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var serializableAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.SerializableAttribute"); var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); if (serializableAttributeSymbol is not null && generateSerializerAttributeSymbol is not null) { context.RegisterSymbolAction(context => AnalyzeNamedType(context, serializableAttributeSymbol, generateSerializerAttributeSymbol), SymbolKind.NamedType); } }); } private static void AnalyzeNamedType(SymbolAnalysisContext context, INamedTypeSymbol serializableAttributeSymbol, INamedTypeSymbol generateSerializerAttributeSymbol) { var symbol = (INamedTypeSymbol)context.Symbol; if (!symbol.IsStatic) { if (symbol.HasAttribute(serializableAttributeSymbol) && !symbol.HasAttribute(generateSerializerAttributeSymbol)) { context.ReportDiagnostic(Diagnostic.Create(Rule, symbol.Locations[0], symbol.Name)); } } } } } ================================================ FILE: src/Orleans.Analyzers/GenerateSerializationAttributesAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class GenerateSerializationAttributesAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0004"; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AddSerializationAttributesTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AddSerializationAttributesMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AddSerializationAttributesDescription), Resources.ResourceManager, typeof(Resources)); private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); context.RegisterSyntaxNodeAction(CheckSyntaxNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); } private void CheckSyntaxNode(SyntaxNodeAnalysisContext context) { if (context.Node is TypeDeclarationSyntax declaration && !declaration.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) { if (declaration.TryGetAttribute(Constants.GenerateSerializerAttributeName, out var attribute)) { var analysis = SerializationAttributesHelper.AnalyzeTypeDeclaration(declaration); if (analysis.UnannotatedMembers.Count > 0) { // Check if GenerateFieldIds is set to PublicProperties var generateFieldIds = GetGenerateFieldIdsValue(attribute); if (generateFieldIds != GenerateFieldIds.PublicProperties) { context.ReportDiagnostic(Diagnostic.Create(Rule, attribute.GetLocation())); } } } } } private static GenerateFieldIds GetGenerateFieldIdsValue(AttributeSyntax attribute) { if (attribute.ArgumentList == null) { return GenerateFieldIds.None; } foreach (var argument in attribute.ArgumentList.Arguments) { if (argument.NameEquals?.Name.Identifier.Text == "GenerateFieldIds") { if (argument.Expression is MemberAccessExpressionSyntax memberAccess) { var memberName = memberAccess.Name.Identifier.Text; if (memberName == "PublicProperties") { return GenerateFieldIds.PublicProperties; } } } } return GenerateFieldIds.None; } private enum GenerateFieldIds { None, PublicProperties } } } ================================================ FILE: src/Orleans.Analyzers/GenerateSerializationAttributesCodeFix.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Simplification; using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers { [ExportCodeFixProvider(LanguageNames.CSharp)] public class GenerateOrleansSerializationAttributesCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GenerateSerializationAttributesAnalyzer.RuleId, GenerateGenerateSerializerAttributeAnalyzer.RuleId); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken); var declaration = root.FindNode(context.Span).FirstAncestorOrSelf(); foreach (var diagnostic in context.Diagnostics) { switch (diagnostic.Id) { case GenerateSerializationAttributesAnalyzer.RuleId: context.RegisterCodeFix( CodeAction.Create("Generate serialization attributes", cancellationToken => AddSerializationAttributes(declaration, context, cancellationToken), equivalenceKey: GenerateSerializationAttributesAnalyzer.RuleId), diagnostic); context.RegisterCodeFix( CodeAction.Create("Mark properties and fields [NonSerialized]", cancellationToken => AddNonSerializedAttributes(root, declaration, context, cancellationToken), equivalenceKey: GenerateSerializationAttributesAnalyzer.RuleId + "NonSerialized"), diagnostic); break; case GenerateGenerateSerializerAttributeAnalyzer.RuleId: context.RegisterCodeFix( CodeAction.Create("Add [GenerateSerializer] attribute", cancellationToken => AddGenerateSerializerAttribute(declaration, context, cancellationToken), equivalenceKey: GenerateGenerateSerializerAttributeAnalyzer.RuleId), diagnostic); break; } } } private static async Task AddGenerateSerializerAttribute(TypeDeclarationSyntax declaration, CodeFixContext context, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); // Add the [GenerateSerializer] attribute var attribute = Attribute(ParseName(Constants.GenerateSerializerAttributeFullyQualifiedName)) .WithAdditionalAnnotations(Simplifier.Annotation); editor.AddAttribute(declaration, attribute); return editor.GetChangedDocument(); } private static async Task AddSerializationAttributes(TypeDeclarationSyntax declaration, CodeFixContext context, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); var analysis = SerializationAttributesHelper.AnalyzeTypeDeclaration(declaration); var nextId = analysis.NextAvailableId; foreach (var member in analysis.UnannotatedMembers) { // Add the [Id(x)] attribute var attribute = Attribute(ParseName(Constants.IdAttributeFullyQualifiedName)) .AddArgumentListArguments(AttributeArgument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal((int)nextId++)))) .WithAdditionalAnnotations(Simplifier.Annotation); editor.AddAttribute(member, attribute); } return editor.GetChangedDocument(); } private static async Task AddNonSerializedAttributes(SyntaxNode root, TypeDeclarationSyntax declaration, CodeFixContext context, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); var analysis = SerializationAttributesHelper.AnalyzeTypeDeclaration(declaration); foreach (var member in analysis.UnannotatedMembers) { // Add the [NonSerialized] attribute var attribute = AttributeList().AddAttributes(Attribute(ParseName(Constants.NonSerializedAttributeFullyQualifiedName)).WithAdditionalAnnotations(Simplifier.Annotation)); // Since [NonSerialized] is a field-only attribute, add the field target specifier. if (member is PropertyDeclarationSyntax) { attribute = attribute.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.FieldKeyword))); } editor.AddAttribute(member, attribute); } var document = editor.GetChangedDocument(); root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var insertUsingDirective = true; if (root is CompilationUnitSyntax rootCompilationUnit) { foreach (var directive in rootCompilationUnit.Usings) { if (string.Equals(directive.Name.ToString(), Constants.SystemNamespace, StringComparison.Ordinal)) { insertUsingDirective = false; break; } } if (insertUsingDirective) { var usingDirective = UsingDirective(IdentifierName(Constants.SystemNamespace)).WithTrailingTrivia(EndOfLine("\r\n")); if (root is CompilationUnitSyntax compilationUnit) { root = compilationUnit.AddUsings(usingDirective); } } } return document.WithSyntaxRoot(root); } } } ================================================ FILE: src/Orleans.Analyzers/GrainInterfaceMethodReturnTypeDiagnosticAnalyzer.cs ================================================ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class GrainInterfaceMethodReturnTypeDiagnosticAnalyzer : DiagnosticAnalyzer { private const string BaseInterfaceName = "Orleans.Runtime.IAddressable"; public const string DiagnosticId = "ORLEANS0009"; public const string Title = "Grain interfaces methods must return a compatible type"; public const string MessageFormat = $"Grain interfaces methods must return a compatible type, such as Task, Task, ValueTask, ValueTask, or void"; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(context => { if (context.Compilation.GetTypeByMetadataName(BaseInterfaceName) is not { } baseInterface) { return; } var builder = ImmutableHashSet.CreateBuilder(SymbolEqualityComparer.Default); AddIfNotNull(builder, context.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")); AddIfNotNull(builder, context.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task`1")); AddIfNotNull(builder, context.Compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask")); AddIfNotNull(builder, context.Compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask`1")); AddIfNotNull(builder, context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IAsyncEnumerable`1")); AddIfNotNull(builder, context.Compilation.GetSpecialType(SpecialType.System_Void)); context.RegisterSymbolAction(context => AnalyzeMethod(context, baseInterface, builder.ToImmutable()), SymbolKind.Method); }); static void AddIfNotNull(ImmutableHashSet.Builder builder, INamedTypeSymbol symbol) { if (symbol is not null) { builder.Add(symbol); } } } private static void AnalyzeMethod(SymbolAnalysisContext context, INamedTypeSymbol baseInterface, ImmutableHashSet supportedTypes) { var symbol = (IMethodSymbol)context.Symbol; if (symbol.ContainingType.TypeKind != TypeKind.Interface) return; // allow static interface methods to return any type if (symbol.IsStatic) return; var isIAddressableInterface = false; foreach (var implementedInterface in symbol.ContainingType.AllInterfaces) { if (implementedInterface.Equals(baseInterface, SymbolEqualityComparer.Default)) { isIAddressableInterface = true; break; } } if (!isIAddressableInterface || supportedTypes.Contains(symbol.ReturnType.OriginalDefinition)) return; var syntaxReference = symbol.DeclaringSyntaxReferences; context.ReportDiagnostic(Diagnostic.Create(Rule, Location.Create(syntaxReference[0].SyntaxTree, syntaxReference[0].Span))); } } } ================================================ FILE: src/Orleans.Analyzers/GrainInterfacePropertyDiagnosticAnalyzer.cs ================================================ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class GrainInterfacePropertyDiagnosticAnalyzer : DiagnosticAnalyzer { private const string BaseInterfaceName = "Orleans.Runtime.IAddressable"; public const string DiagnosticId = "ORLEANS0008"; public const string Title = "Grain interfaces must not contain properties"; public const string MessageFormat = Title; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.PropertyDeclaration); } private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { if (context.Node is not PropertyDeclarationSyntax syntax) return; var symbol = context.SemanticModel.GetDeclaredSymbol(syntax, context.CancellationToken); if (symbol.ContainingType.TypeKind != TypeKind.Interface) return; // ignore static members if (symbol.IsStatic) return; var isIAddressableInterface = false; foreach (var implementedInterface in symbol.ContainingType.AllInterfaces) { if (BaseInterfaceName.Equals(implementedInterface.ToDisplayString(NullableFlowState.None), System.StringComparison.Ordinal)) { isIAddressableInterface = true; break; } } if (!isIAddressableInterface) return; var syntaxReference = symbol.DeclaringSyntaxReferences; context.ReportDiagnostic(Diagnostic.Create(Rule, Location.Create(syntaxReference[0].SyntaxTree, syntaxReference[0].Span))); } } } ================================================ FILE: src/Orleans.Analyzers/IdClashAttributeAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; namespace Orleans.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class IdClashAttributeAnalyzer : DiagnosticAnalyzer { private readonly record struct AliasBag(string Name, Location Location); public const string RuleId = "ORLEANS0012"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( id: RuleId, category: "Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, title: new LocalizableResourceString(nameof(Resources.IdClashDetectedTitle), Resources.ResourceManager, typeof(Resources)), messageFormat: new LocalizableResourceString(nameof(Resources.IdClashDetectedMessageFormat), Resources.ResourceManager, typeof(Resources)), description: new LocalizableResourceString(nameof(Resources.IdClashDetectedDescription), Resources.ResourceManager, typeof(Resources))); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var generateSerializerAttribute = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); var idAttribute = context.Compilation.GetTypeByMetadataName("Orleans.IdAttribute"); if (generateSerializerAttribute is not null && idAttribute is not null) { context.RegisterSymbolAction(context => AnalyzeNamedType(context, generateSerializerAttribute, idAttribute), SymbolKind.NamedType); } }); } private static void AnalyzeNamedType(SymbolAnalysisContext context, INamedTypeSymbol generateSerializerAttribute, INamedTypeSymbol idAttribute) { var typeSymbol = (INamedTypeSymbol)context.Symbol; if (!typeSymbol.HasAttribute(generateSerializerAttribute)) { return; } List> bags = []; foreach (var member in typeSymbol.GetMembers()) { foreach (var attribute in member.GetAttributes()) { if (!idAttribute.Equals(attribute.AttributeClass, SymbolEqualityComparer.Default)) { continue; } if (attribute.ConstructorArguments.Length == 1 && attribute.ConstructorArguments[0].Value is uint idValue) { var attributeSyntax = (AttributeSyntax)attribute.ApplicationSyntaxReference.GetSyntax(); bags.Add(new AttributeArgumentBag(idValue, attributeSyntax.GetLocation())); } } } var duplicateIds = bags .GroupBy(id => id.Value) .Where(group => group.Count() > 1) .Select(group => group.Key); if (!duplicateIds.Any()) { return; } foreach (var duplicateId in duplicateIds) { var filteredBags = bags.Where(x => x.Value == duplicateId); var duplicateCount = filteredBags.Count(); if (duplicateCount > 1) { foreach (var bag in filteredBags) { var builder = ImmutableDictionary.CreateBuilder(); builder.Add("IdValue", bag.Value.ToString()); context.ReportDiagnostic(Diagnostic.Create( descriptor: Rule, location: bag.Location, properties: builder.ToImmutable())); } } } } } ================================================ FILE: src/Orleans.Analyzers/IdClashAttributeCodeFix.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(IdClashAttributeCodeFix)), Shared] public class IdClashAttributeCodeFix : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IdClashAttributeAnalyzer.RuleId); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); if (root.FindNode(diagnostic.Location.SourceSpan) is not AttributeSyntax attribute) { return; } var idValue = diagnostic.Properties["IdValue"]; context.RegisterCodeFix( CodeAction.Create( Resources.IdClashDetectedTitle, createChangedDocument: _ => { var newIdValue = root .DescendantNodes() .OfType() .Where(a => a.IsAttribute(Constants.IdAttributeName)) .Select(a => int.Parse(a.ArgumentList.Arguments.Single().ToString())) .Max() + 1; var newAttribute = attribute.ReplaceNode( attribute.ArgumentList.Arguments[0].Expression, LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(newIdValue))); var newRoot = root.ReplaceNode(attribute, newAttribute); var newDocument = context.Document.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument); }, equivalenceKey: IdClashAttributeAnalyzer.RuleId), diagnostic); } } ================================================ FILE: src/Orleans.Analyzers/IncorrectAttributeUseAnalyzer.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; namespace Orleans.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class IncorrectAttributeUseAnalyzer : DiagnosticAnalyzer { public const string RuleId = "ORLEANS0013"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( id: RuleId, category: "Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, title: new LocalizableResourceString(nameof(Resources.IncorrectAttributeUseTitle), Resources.ResourceManager, typeof(Resources)), messageFormat: new LocalizableResourceString(nameof(Resources.IncorrectAttributeUseMessageFormat), Resources.ResourceManager, typeof(Resources)), description: new LocalizableResourceString(nameof(Resources.IncorrectAttributeUseTitleDescription), Resources.ResourceManager, typeof(Resources))); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var aliasAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.AliasAttribute"); var grainSymbol = context.Compilation.GetTypeByMetadataName("Orleans.Grain"); var generateSerializerAttributeSymbol = context.Compilation.GetTypeByMetadataName("Orleans.GenerateSerializerAttribute"); if (aliasAttributeSymbol is not null && grainSymbol is not null) { context.RegisterSymbolAction(context => AnalyzeNamedType(context, aliasAttributeSymbol, grainSymbol, generateSerializerAttributeSymbol), SymbolKind.NamedType); } }); } private static void AnalyzeNamedType(SymbolAnalysisContext context, INamedTypeSymbol aliasAttributeSymbol, INamedTypeSymbol grainSymbol, INamedTypeSymbol generateSerializerAttributeSymbol) { var symbol = (INamedTypeSymbol)context.Symbol; if (!symbol.DerivesFrom(grainSymbol)) { return; } TryReportFor(aliasAttributeSymbol, context, symbol); TryReportFor(generateSerializerAttributeSymbol, context, symbol); } private static void TryReportFor(INamedTypeSymbol attributeSymbol, SymbolAnalysisContext context, INamedTypeSymbol symbol) { if (symbol.HasAttribute(attributeSymbol, out var location)) { context.ReportDiagnostic(Diagnostic.Create( descriptor: Rule, location: location, messageArgs: new object[] { attributeSymbol.Name })); } } } ================================================ FILE: src/Orleans.Analyzers/IncorrectAttributeUseCodeFix.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using System.Collections.Immutable; using System.Threading.Tasks; using System.Composition; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; namespace Orleans.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(IncorrectAttributeUseCodeFix)), Shared] public class IncorrectAttributeUseCodeFix : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IncorrectAttributeUseAnalyzer.RuleId); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); if (root.FindNode(diagnostic.Location.SourceSpan) is not AttributeSyntax node) { return; } context.RegisterCodeFix( CodeAction.Create( title: Resources.IncorrectAttributeUseTitle, createChangedDocument: token => { var newRoot = root.RemoveNode(node.Parent, SyntaxRemoveOptions.KeepEndOfLine); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, equivalenceKey: IncorrectAttributeUseAnalyzer.RuleId), diagnostic); } } ================================================ FILE: src/Orleans.Analyzers/NoRefParamsDiagnosticAnalyzer.cs ================================================ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Orleans.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class NoRefParamsDiagnosticAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "ORLEANS0002"; public const string Title = "Reference parameter modifiers are not allowed"; public const string MessageFormat = Title; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(context => { var baseInterface = context.Compilation.GetTypeByMetadataName("Orleans.Runtime.IAddressable"); if (baseInterface is not null) { context.RegisterSymbolAction(context => AnalyzeMethodSymbol(context, baseInterface), SymbolKind.Method); } }); } private static void AnalyzeMethodSymbol(SymbolAnalysisContext context, INamedTypeSymbol baseInterface) { var symbol = (IMethodSymbol)context.Symbol; if (symbol.ContainingType.TypeKind != TypeKind.Interface) return; // ignore static members if (symbol.IsStatic) return; var implementedInterfaces = symbol.ContainingType .AllInterfaces .Select(interfaceDef => interfaceDef.Name); if (!symbol.ContainingType.AllInterfaces.Contains(baseInterface)) return; foreach(var param in symbol.Parameters) { if (param.RefKind == RefKind.None) continue; var syntaxReference = param.DeclaringSyntaxReferences; context.ReportDiagnostic( Diagnostic.Create(Rule, Location.Create(syntaxReference[0].SyntaxTree, syntaxReference[0].Span))); } } } } ================================================ FILE: src/Orleans.Analyzers/Orleans.Analyzers.csproj ================================================ Microsoft.Orleans.Analyzers Microsoft Orleans Analyzers C# Analyzers for Microsoft Orleans. netstandard2.0 true false true false true $(NoWarn);RS1038 True True Resources.resx ResXFileCodeGenerator Resources.Designer.cs ================================================ FILE: src/Orleans.Analyzers/Properties/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } ================================================ FILE: src/Orleans.Analyzers/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Analyzers { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Orleans.Analyzers.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to The member "{0}" is marked as {1} and therefore cannot be serialized. /// internal static string AbstractOrStaticMembersCannotBeSerializedMessageFormat { get { return ResourceManager.GetString("AbstractOrStaticMembersCannotBeSerializedMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Members which are static or abstract cannot be serialized. /// internal static string AbstractOrStaticMembersCannotBeSerializedTitle { get { return ResourceManager.GetString("AbstractOrStaticMembersCannotBeSerializedTitle", resourceCulture); } } /// /// Looks up a localized string similar to Add [Alias] to specify well-known names that can be used to identify types or methods.. /// internal static string AddAliasAttributesDescription { get { return ResourceManager.GetString("AddAliasAttributesDescription", resourceCulture); } } /// /// Looks up a localized string similar to Add missing [Alias]. /// internal static string AddAliasAttributesTitle { get { return ResourceManager.GetString("AddAliasAttributesTitle", resourceCulture); } } /// /// Looks up a localized string similar to Add missing [Alias]. /// internal static string AddAliasMessageFormat { get { return ResourceManager.GetString("AddAliasMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Add the [GenerateSerializer] attribute to serializable types in your application.. /// internal static string AddGenerateSerializerAttributeDescription { get { return ResourceManager.GetString("AddGenerateSerializerAttributeDescription", resourceCulture); } } /// /// Looks up a localized string similar to The type "{0}" has the [Serializable] attribute but not the [GenerateSerializer] attribute. /// internal static string AddGenerateSerializerAttributeMessageFormat { get { return ResourceManager.GetString("AddGenerateSerializerAttributeMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Add the [GenerateSerializer] attribute. /// internal static string AddGenerateSerializerAttributesTitle { get { return ResourceManager.GetString("AddGenerateSerializerAttributesTitle", resourceCulture); } } /// /// Looks up a localized string similar to Add attributes to properties and fields to direct serializer generation.. /// internal static string AddSerializationAttributesDescription { get { return ResourceManager.GetString("AddSerializationAttributesDescription", resourceCulture); } } /// /// Looks up a localized string similar to Add missing serialization attributes. /// internal static string AddSerializationAttributesMessageFormat { get { return ResourceManager.GetString("AddSerializationAttributesMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Add missing serialization attributes. /// internal static string AddSerializationAttributesTitle { get { return ResourceManager.GetString("AddSerializationAttributesTitle", resourceCulture); } } /// /// Looks up a localized string similar to The [Alias] attribute must be unique to the declaring type.. /// internal static string AliasClashDetectedDescription { get { return ResourceManager.GetString("AliasClashDetectedDescription", resourceCulture); } } /// /// Looks up a localized string similar to Rename duplicated [Alias]. /// internal static string AliasClashDetectedMessageFormat { get { return ResourceManager.GetString("AliasClashDetectedMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Rename duplicated [Alias]. /// internal static string AliasClashDetectedTitle { get { return ResourceManager.GetString("AliasClashDetectedTitle", resourceCulture); } } /// /// Looks up a localized string similar to A single type is not allowed to have multiple constructors annotated with the [OrleansConstructor] attribute. /// internal static string AtMostOneOrleansConstructorMessageFormat { get { return ResourceManager.GetString("AtMostOneOrleansConstructorMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to At most one constructor can be annotated with the [OrleansConstructor] attribute. /// internal static string AtMostOneOrleansConstructorTitle { get { return ResourceManager.GetString("AtMostOneOrleansConstructorTitle", resourceCulture); } } /// /// Looks up a localized string similar to The [Id] attribute must be unique to each members of the declaring type.. /// internal static string IdClashDetectedDescription { get { return ResourceManager.GetString("IdClashDetectedDescription", resourceCulture); } } /// /// Looks up a localized string similar to Change duplicated [Id]. /// internal static string IdClashDetectedMessageFormat { get { return ResourceManager.GetString("IdClashDetectedMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Change duplicated [Id]. /// internal static string IdClashDetectedTitle { get { return ResourceManager.GetString("IdClashDetectedTitle", resourceCulture); } } /// /// Looks up a localized string similar to Remove attribute [{0}]. /// internal static string IncorrectAttributeUseMessageFormat { get { return ResourceManager.GetString("IncorrectAttributeUseMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Remove attribute. /// internal static string IncorrectAttributeUseTitle { get { return ResourceManager.GetString("IncorrectAttributeUseTitle", resourceCulture); } } /// /// Looks up a localized string similar to This attribute should not be used on grain implementations.. /// internal static string IncorrectAttributeUseTitleDescription { get { return ResourceManager.GetString("IncorrectAttributeUseTitleDescription", resourceCulture); } } /// /// Looks up a localized string similar to Grain code must maintain the grain's synchronization context... /// internal static string AvoidConfigureAwaitFalseInGrainDescription { get { return ResourceManager.GetString("AvoidConfigureAwaitFalseInGrainDescription", resourceCulture); } } /// /// Looks up a localized string similar to ConfigureAwait in grain code must not use 'false' and must include ConfigureAwaitOptions.ContinueOnCapturedContext. /// internal static string AvoidConfigureAwaitFalseInGrainMessageFormat { get { return ResourceManager.GetString("AvoidConfigureAwaitFalseInGrainMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Avoid ConfigureAwait(false) or ConfigureAwait not specifying ContinueOnCapturedContext in grain code. /// internal static string AvoidConfigureAwaitFalseInGrainTitle { get { return ResourceManager.GetString("AvoidConfigureAwaitFalseInGrainTitle", resourceCulture); } } /// /// Looks up a localized string similar to Use ConfigureAwait with ContinueOnCapturedContext. /// internal static string ConfigureAwaitCodeFixTitle { get { return ResourceManager.GetString("ConfigureAwaitCodeFixTitle", resourceCulture); } } } } ================================================ FILE: src/Orleans.Analyzers/Resources.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 The member "{0}" is marked as {1} and therefore cannot be serialized Members which are static or abstract cannot be serialized Add the [GenerateSerializer] attribute to serializable types in your application. The type "{0}" has the [Serializable] attribute but not the [GenerateSerializer] attribute Add the [GenerateSerializer] attribute Add attributes to properties and fields to direct serializer generation. Add missing serialization attributes Add missing serialization attributes At most one constructor can be annotated with the [OrleansConstructor] attribute A single type is not allowed to have multiple constructors annotated with the [OrleansConstructor] attribute Add [Alias] to specify well-known names that can be used to identify types or methods. Add missing [Alias] Add missing [Alias] The [Alias] attribute must be unique to the declaring type. Rename duplicated [Alias] Rename duplicated [Alias] The [Id] attribute must be unique to each members of the declaring type. Change duplicated [Id] Change duplicated [Id] Remove attribute [{0}] Remove attribute This attribute should not be used on grain implementations. Avoid ConfigureAwait(false) or ConfigureAwait not specifying ContinueOnCapturedContext in grain code ConfigureAwait in grain code must not use 'false' and must include ConfigureAwaitOptions.ContinueOnCapturedContext Grain code must maintain the grain's execution context. Using ConfigureAwait(false) or ConfigureAwait without ContinueOnCapturedContext can cause the continuation to run outside the grain's context, leading to concurrency issues and loss of grain identity. Use ConfigureAwait with ContinueOnCapturedContext ================================================ FILE: src/Orleans.Analyzers/SerializationAttributesHelper.cs ================================================ #nullable enable using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Linq; namespace Orleans.Analyzers { internal static class SerializationAttributesHelper { public static bool ShouldGenerateSerializer(INamedTypeSymbol symbol, INamedTypeSymbol generateSerializerAttributeSymbol) { if (!symbol.IsStatic && symbol.HasAttribute(generateSerializerAttributeSymbol)) { return true; } return false; } public readonly record struct TypeAnalysis { public List UnannotatedMembers { get; init; } public uint NextAvailableId { get; init; } } public static TypeAnalysis AnalyzeTypeDeclaration(TypeDeclarationSyntax declaration) { uint nextId = 0; var unannotatedSerializableMembers = new List(); foreach (var member in declaration.Members) { // Skip members with existing [Id(x)] attributes, but record the highest value of x so that newly added attributes can begin from that value. if (member.TryGetAttribute(Constants.IdAttributeName, out var attribute)) { var args = attribute.ArgumentList?.Arguments; if (args.HasValue) { if (args.Value.Count > 0) { var idArg = args.Value[0]; if (idArg.Expression is LiteralExpressionSyntax literalExpression && uint.TryParse(literalExpression.Token.ValueText, out var value) && value >= nextId) { nextId = value + 1; } } } continue; } if (member is ConstructorDeclarationSyntax constructorDeclaration && constructorDeclaration.HasAttribute(Constants.GenerateSerializerAttributeName)) { continue; } if (!member.IsInstanceMember() || !member.IsFieldOrAutoProperty() || member.HasAttribute(Constants.NonSerializedAttribute) || member.IsAbstract()) { // No need to add any attribute. continue; } unannotatedSerializableMembers.Add(member); } return new TypeAnalysis { UnannotatedMembers = unannotatedSerializableMembers, NextAvailableId = nextId, }; } } } ================================================ FILE: src/Orleans.Analyzers/SymbolHelpers.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.Analyzers { internal static class SymbolHelpers { public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeSymbol, out Location location) { foreach (var attribute in symbol.GetAttributes()) { if (attributeSymbol.Equals(attribute.AttributeClass, SymbolEqualityComparer.Default)) { location = attribute.ApplicationSyntaxReference.GetSyntax().GetLocation(); return true; } } location = null; return false; } public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeSymbol) => symbol.HasAttribute(attributeSymbol, out _); public static bool DerivesFrom(this ITypeSymbol symbol, ITypeSymbol candidateBaseType) { var baseType = symbol.BaseType; while (baseType is not null) { if (baseType.Equals(candidateBaseType, SymbolEqualityComparer.Default)) { return true; } baseType = baseType.BaseType; } return false; } } } ================================================ FILE: src/Orleans.Analyzers/SyntaxHelpers.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.Linq; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.Analyzers { internal readonly record struct AttributeArgumentBag(T Value, Location Location); internal static class SyntaxHelpers { private static bool TryGetTypeName(this AttributeSyntax attributeSyntax, out string typeName) { typeName = attributeSyntax.Name switch { IdentifierNameSyntax id => id.Identifier.Text, QualifiedNameSyntax qualified => qualified.Right.Identifier.Text, GenericNameSyntax generic => generic.Identifier.Text, AliasQualifiedNameSyntax aliased => aliased.Name.Identifier.Text, _ => null }; return typeName != null; } public static bool IsAttribute(this AttributeSyntax attributeSyntax, string attributeName) => attributeSyntax.TryGetTypeName(out var name) && (string.Equals(name, attributeName, StringComparison.Ordinal) || (name.StartsWith(attributeName, StringComparison.Ordinal) && name.EndsWith(nameof(Attribute), StringComparison.Ordinal) && name.Length == attributeName.Length + nameof(Attribute).Length)); public static bool HasAttribute(this MemberDeclarationSyntax member, string attributeName) { foreach (var list in member.AttributeLists) { foreach (var attr in list.Attributes) { if (attr.IsAttribute(attributeName)) { return true; } } } return false; } public static bool TryGetAttribute(this MemberDeclarationSyntax member, string attributeName, out AttributeSyntax attribute) { foreach (var list in member.AttributeLists) { foreach (var attr in list.Attributes) { if (attr.IsAttribute(attributeName)) { attribute = attr; return true; } } } attribute = default; return false; } public static bool IsAbstract(this MemberDeclarationSyntax member) => member.HasModifier(SyntaxKind.AbstractKeyword); private static bool HasModifier(this MemberDeclarationSyntax member, SyntaxKind modifierKind) { foreach (var modifier in member.Modifiers) { var kind = modifier.Kind(); if (kind == modifierKind) { return true; } } return false; } public static bool IsInstanceMember(this MemberDeclarationSyntax member) { foreach (var modifier in member.Modifiers) { var kind = modifier.Kind(); if (kind == SyntaxKind.StaticKeyword || kind == SyntaxKind.ConstKeyword) { return false; } } return true; } public static bool IsFieldOrAutoProperty(this MemberDeclarationSyntax member) { bool isFieldOrAutoProperty = false; switch (member) { case FieldDeclarationSyntax: isFieldOrAutoProperty = true; break; case PropertyDeclarationSyntax property: { bool hasBody = property.ExpressionBody is not null; var accessors = property.AccessorList?.Accessors; if (!hasBody && accessors.HasValue) { foreach (var accessor in accessors) { if (accessor.ExpressionBody is not null || accessor.Body is not null) { hasBody = true; break; } } } if (!hasBody) { isFieldOrAutoProperty = true; } break; } } return isFieldOrAutoProperty; } public static bool ExtendsGrainInterface(this INamedTypeSymbol symbol) { if (symbol is null || symbol.TypeKind != TypeKind.Interface) { return false; } foreach (var interfaceSymbol in symbol.AllInterfaces) { if (Constants.IAddressibleFullyQualifiedName.Equals(interfaceSymbol.ToDisplayString(NullableFlowState.None), StringComparison.Ordinal)) { return true; } } return false; } public static bool InheritsGrainClass(this ClassDeclarationSyntax declaration, SemanticModel semanticModel) { var baseTypes = declaration.BaseList?.Types; if (baseTypes is null) { return false; } foreach (var baseTypeSyntax in baseTypes) { var baseTypeSymbol = semanticModel.GetTypeInfo(baseTypeSyntax.Type).Type; if (baseTypeSymbol is INamedTypeSymbol currentTypeSymbol) { if (currentTypeSymbol.IsGenericType && currentTypeSymbol.TypeParameters.Length == 1 && currentTypeSymbol.BaseType is { } baseBaseTypeSymbol) { currentTypeSymbol = baseBaseTypeSymbol; } if (Constants.GrainBaseFullyQualifiedName.Equals(currentTypeSymbol.ToDisplayString(NullableFlowState.None), StringComparison.Ordinal)) { return true; } } } return false; } public static bool IsGrainClass(this INamedTypeSymbol typeSymbol) { if (typeSymbol is null || typeSymbol.TypeKind != TypeKind.Class) { return false; } // Check if the type implements IGrain or ISystemTarget interface foreach (var interfaceSymbol in typeSymbol.AllInterfaces) { var interfaceName = interfaceSymbol.ToDisplayString(NullableFlowState.None); if (Constants.IGrainFullyQualifiedName.Equals(interfaceName, StringComparison.Ordinal) || Constants.ISystemTargetFullyQualifiedName.Equals(interfaceName, StringComparison.Ordinal)) { return true; } } return false; } public static AttributeArgumentBag GetArgumentBag(this AttributeSyntax attribute, SemanticModel semanticModel) { if (attribute is null) { return default; } var argument = attribute.ArgumentList?.Arguments.FirstOrDefault(); if (argument is null || argument.Expression is not { } expression) { return default; } var constantValue = semanticModel.GetConstantValue(expression); return constantValue.HasValue && constantValue.Value is T value ? new(value, attribute.GetLocation()) : default; } public static IEnumerable GetAttributeSyntaxes(this SyntaxList attributeLists, string attributeName) => attributeLists .SelectMany(attributeList => attributeList.Attributes) .Where(attribute => attribute.IsAttribute(attributeName)); public static string GetArgumentValue(this AttributeSyntax attribute, SemanticModel semanticModel) { if (attribute?.ArgumentList == null || attribute.ArgumentList.Arguments.Count == 0) { return null; } var symbolInfo = semanticModel.GetSymbolInfo(attribute); if (symbolInfo.Symbol == null && symbolInfo.CandidateSymbols.Length == 0) { return null; } var argumentExpression = attribute.ArgumentList.Arguments[0].Expression; var constant = semanticModel.GetConstantValue(argumentExpression); return constant.HasValue ? constant.Value?.ToString() : null; } } } ================================================ FILE: src/Orleans.BroadcastChannel/BroadcastChannelConsumerExtension.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.BroadcastChannel { internal interface IBroadcastChannelConsumerExtension : IGrainExtension { Task OnError(InternalChannelId streamId, Exception exception); Task OnPublished(InternalChannelId streamId, object item); } internal class BroadcastChannelConsumerExtension : IBroadcastChannelConsumerExtension { private readonly ConcurrentDictionary _handlers = new(); private readonly IOnBroadcastChannelSubscribed _subscriptionObserver; private readonly AsyncLock _lock = new AsyncLock(); private interface ICallback { Task OnError(Exception exception); Task OnPublished(object item); } private class Callback : ICallback { private readonly Func _onPublished; private readonly Func _onError; private static Task NoOp(Exception _) => Task.CompletedTask; public Callback(Func onPublished, Func onError) { _onPublished = onPublished; _onError = onError ?? NoOp; } public Task OnError(Exception exception) => _onError(exception); public Task OnPublished(object item) { return item is T typedItem ? _onPublished(typedItem) : _onError(new InvalidCastException($"Received an item of type {item.GetType().Name}, expected {typeof(T).FullName}")); } } public BroadcastChannelConsumerExtension(IGrainContextAccessor grainContextAccessor) { _subscriptionObserver = grainContextAccessor.GrainContext?.GrainInstance as IOnBroadcastChannelSubscribed; if (_subscriptionObserver == null) { throw new ArgumentException($"The grain doesn't implement interface {nameof(IOnBroadcastChannelSubscribed)}"); } } public async Task OnError(InternalChannelId streamId, Exception exception) { var callback = await GetStreamCallback(streamId); if (callback != default) { await callback.OnError(exception); } } public async Task OnPublished(InternalChannelId streamId, object item) { var callback = await GetStreamCallback(streamId); if (callback != default) { await callback.OnPublished(item); } } public void Attach(InternalChannelId streamId, Func onPublished, Func onError) { _handlers.TryAdd(streamId, new Callback(onPublished, onError)); } private async ValueTask GetStreamCallback(InternalChannelId streamId) { ICallback callback; if (_handlers.TryGetValue(streamId, out callback)) { return callback; } using (await _lock.LockAsync()) { if (_handlers.TryGetValue(streamId, out callback)) { return callback; } // Give a chance to the grain to attach a handler for this streamId var subscription = new BroadcastChannelSubscription(this, streamId); await _subscriptionObserver.OnSubscribed(subscription); } _handlers.TryGetValue(streamId, out callback); return callback; } } } ================================================ FILE: src/Orleans.BroadcastChannel/BroadcastChannelOptions.cs ================================================ namespace Orleans.BroadcastChannel { /// /// Configuration options for broadcast channel /// public class BroadcastChannelOptions { /// /// If set to true, the provider will not await calls to subscriber. /// public bool FireAndForgetDelivery { get; set; } = true; } } ================================================ FILE: src/Orleans.BroadcastChannel/BroadcastChannelProvider.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.BroadcastChannel.SubscriberTable; namespace Orleans.BroadcastChannel { /// /// Functionality for providing broadcast channel to producers. /// public interface IBroadcastChannelProvider { /// /// Get a writer to a channel. /// /// The channel element type. /// The channel identifier. /// IBroadcastChannelWriter GetChannelWriter(ChannelId streamId); } internal class BroadcastChannelProvider : IBroadcastChannelProvider { private readonly string _providerName; private readonly BroadcastChannelOptions _options; private readonly IGrainFactory _grainFactory; private readonly ImplicitChannelSubscriberTable _subscriberTable; private readonly ILoggerFactory _loggerFactory; public BroadcastChannelProvider( string providerName, BroadcastChannelOptions options, IGrainFactory grainFactory, ImplicitChannelSubscriberTable subscriberTable, ILoggerFactory loggerFactory) { _providerName = providerName; _options = options; _grainFactory = grainFactory; _subscriberTable = subscriberTable; _loggerFactory = loggerFactory; } /// public IBroadcastChannelWriter GetChannelWriter(ChannelId streamId) { return new BroadcastChannelWriter( new InternalChannelId(_providerName, streamId), _grainFactory, _subscriberTable, _options.FireAndForgetDelivery, _loggerFactory); } /// /// Create a new channel provider. /// /// The service provider. /// The name of the provider. /// The named channel provider. public static IBroadcastChannelProvider Create(IServiceProvider sp, string name) { var opt = sp.GetOptionsByName(name); return ActivatorUtilities.CreateInstance(sp, name, opt); } } } ================================================ FILE: src/Orleans.BroadcastChannel/BroadcastChannelSubscription.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.BroadcastChannel { public interface IBroadcastChannelSubscription { public ChannelId ChannelId { get; } public string ProviderName { get; } Task Attach(Func onPublished, Func onError = null); } public interface IOnBroadcastChannelSubscribed { public Task OnSubscribed(IBroadcastChannelSubscription streamSubscription); } internal class BroadcastChannelSubscription : IBroadcastChannelSubscription { private readonly BroadcastChannelConsumerExtension _consumerExtension; private readonly InternalChannelId _streamId; public ChannelId ChannelId => _streamId.ChannelId; public string ProviderName => _streamId.ProviderName; public BroadcastChannelSubscription(BroadcastChannelConsumerExtension consumerExtension, InternalChannelId streamId) { _consumerExtension = consumerExtension; _streamId = streamId; } public Task Attach(Func onPublished, Func onError = null) { _consumerExtension.Attach(_streamId, onPublished, onError); return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.BroadcastChannel/BroadcastChannelWriter.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.BroadcastChannel.SubscriberTable; using Orleans.Runtime; namespace Orleans.BroadcastChannel { /// /// Interface to allow writing to a channel. /// /// The channel element type. public interface IBroadcastChannelWriter { /// /// Publish an element to the channel. /// /// The element to publish. Task Publish(T item); } /// internal partial class BroadcastChannelWriter : IBroadcastChannelWriter { private static readonly string LoggingCategory = typeof(BroadcastChannelWriter<>).FullName; private readonly InternalChannelId _channelId; private readonly IGrainFactory _grainFactory; private readonly ImplicitChannelSubscriberTable _subscriberTable; private readonly bool _fireAndForgetDelivery; private readonly ILogger _logger; public BroadcastChannelWriter( InternalChannelId channelId, IGrainFactory grainFactory, ImplicitChannelSubscriberTable subscriberTable, bool fireAndForgetDelivery, ILoggerFactory loggerFactory) { _channelId = channelId; _grainFactory = grainFactory; _subscriberTable = subscriberTable; _fireAndForgetDelivery = fireAndForgetDelivery; _logger = loggerFactory.CreateLogger(LoggingCategory); } /// public async Task Publish(T item) { var subscribers = _subscriberTable.GetImplicitSubscribers(_channelId, _grainFactory); if (subscribers.Count == 0) { LogDebugNoConsumerFound(_logger, item); return; } LogDebugPublishingItem(_logger, item, subscribers.Count); if (_fireAndForgetDelivery) { foreach (var sub in subscribers) { PublishToSubscriber(sub.Value, item).Ignore(); } } else { var tasks = new List(); foreach (var sub in subscribers) { tasks.Add(PublishToSubscriber(sub.Value, item)); } try { await Task.WhenAll(tasks); } catch (Exception) { throw new AggregateException(tasks.Select(t => t.Exception).Where(ex => ex != null)); } } } private async Task PublishToSubscriber(IBroadcastChannelConsumerExtension consumer, T item) { try { await consumer.OnPublished(_channelId, item); } catch (Exception ex) { LogErrorExceptionWhenSendingItem(_logger, ex, consumer.GetGrainId()); if (!_fireAndForgetDelivery) { throw; } } } [LoggerMessage( Level = LogLevel.Debug, Message = "No consumer found for {Item}" )] private static partial void LogDebugNoConsumerFound(ILogger logger, T item); [LoggerMessage( Level = LogLevel.Debug, Message = "Publishing item {Item} to {ConsumerCount} consumers" )] private static partial void LogDebugPublishingItem(ILogger logger, T item, int consumerCount); [LoggerMessage( Level = LogLevel.Error, Message = "Exception when sending item to {GrainId}" )] private static partial void LogErrorExceptionWhenSendingItem(ILogger logger, Exception exception, GrainId grainId); } } ================================================ FILE: src/Orleans.BroadcastChannel/ChannelId.cs ================================================ using System; using System.Buffers.Text; using System.Diagnostics; using System.Runtime.Serialization; using System.Text; using Orleans.Runtime; #nullable enable namespace Orleans.BroadcastChannel { /// /// Identifies a Channel within a provider /// [Serializable, GenerateSerializer, Immutable] public readonly struct ChannelId : IEquatable, IComparable, ISerializable, ISpanFormattable { [Id(0)] private readonly byte[] fullKey; [Id(1)] private readonly ushort keyIndex; [Id(2)] private readonly int hash; /// /// Gets the full key. /// /// The full key. public ReadOnlyMemory FullKey => fullKey; /// /// Gets the namespace. /// /// The namespace. public ReadOnlyMemory Namespace => fullKey.AsMemory(0, this.keyIndex); /// /// Gets the key. /// /// The key. public ReadOnlyMemory Key => fullKey.AsMemory(this.keyIndex); private ChannelId(byte[] fullKey, ushort keyIndex, int hash) { this.fullKey = fullKey; this.keyIndex = keyIndex; this.hash = hash; } internal ChannelId(byte[] fullKey, ushort keyIndex) : this(fullKey, keyIndex, (int)StableHash.ComputeHash(fullKey)) { } private ChannelId(SerializationInfo info, StreamingContext context) { fullKey = (byte[])info.GetValue("fk", typeof(byte[]))!; this.keyIndex = info.GetUInt16("ki"); this.hash = info.GetInt32("fh"); } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static ChannelId Create(ReadOnlySpan ns, ReadOnlySpan key) { if (key.IsEmpty) throw new ArgumentNullException(nameof(key)); if (!ns.IsEmpty) { var fullKeysBytes = new byte[ns.Length + key.Length]; ns.CopyTo(fullKeysBytes.AsSpan()); key.CopyTo(fullKeysBytes.AsSpan(ns.Length)); return new(fullKeysBytes, (ushort)ns.Length); } else { return new(key.ToArray(), 0); } } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static ChannelId Create(string ns, Guid key) { if (ns is null) { var buf = new byte[32]; Utf8Formatter.TryFormat(key, buf, out var len, 'N'); Debug.Assert(len == 32); return new ChannelId(buf, 0); } else { var nsLen = Encoding.UTF8.GetByteCount(ns); var buf = new byte[nsLen + 32]; Encoding.UTF8.GetBytes(ns, 0, ns.Length, buf, 0); Utf8Formatter.TryFormat(key, buf.AsSpan(nsLen), out var len, 'N'); Debug.Assert(len == 32); return new ChannelId(buf, (ushort)nsLen); } } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static ChannelId Create(string ns, string key) { if (ns is null) return new ChannelId(Encoding.UTF8.GetBytes(key), 0); var nsLen = Encoding.UTF8.GetByteCount(ns); var keyLen = Encoding.UTF8.GetByteCount(key); var buf = new byte[nsLen + keyLen]; Encoding.UTF8.GetBytes(ns, 0, ns.Length, buf, 0); Encoding.UTF8.GetBytes(key, 0, key.Length, buf, nsLen); return new ChannelId(buf, (ushort)nsLen); } /// public int CompareTo(ChannelId other) => fullKey.AsSpan().SequenceCompareTo(other.fullKey); /// public bool Equals(ChannelId other) => fullKey.AsSpan().SequenceEqual(other.fullKey); /// public override bool Equals(object? obj) => obj is ChannelId other ? this.Equals(other) : false; /// /// Compares two instances for equality. /// /// The first stream identity. /// The second stream identity. /// The result of the operator. public static bool operator ==(ChannelId s1, ChannelId s2) => s1.Equals(s2); /// /// Compares two instances for equality. /// /// The first stream identity. /// The second stream identity. /// The result of the operator. public static bool operator !=(ChannelId s1, ChannelId s2) => !s2.Equals(s1); /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("fk", fullKey); info.AddValue("ki", this.keyIndex); info.AddValue("fh", this.hash); } /// public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { var len = Encoding.UTF8.GetCharCount(fullKey); if (keyIndex == 0) { if (destination.Length >= len + 5) { "null/".CopyTo(destination); charsWritten = Encoding.UTF8.GetChars(fullKey, destination[5..]) + 5; return true; } } else if (destination.Length > len) { len = Encoding.UTF8.GetChars(fullKey.AsSpan(0, keyIndex), destination); destination[len++] = '/'; charsWritten = Encoding.UTF8.GetChars(fullKey.AsSpan(keyIndex), destination[len..]) + len; return true; } charsWritten = 0; return false; } /// public override int GetHashCode() => this.hash; internal uint GetUniformHashCode() => (uint)hash; internal uint GetKeyIndex() => keyIndex; /// /// Returns the component of this instance as a string. /// /// The key component of this instance. public string GetKeyAsString() => Encoding.UTF8.GetString(fullKey, keyIndex, fullKey.Length - keyIndex); /// /// Returns the component of this instance as a string. /// /// The namespace component of this instance. public string? GetNamespace() => keyIndex == 0 ? null : Encoding.UTF8.GetString(fullKey, 0, keyIndex); internal IdSpan GetKeyIdSpan() => keyIndex == 0 ? IdSpan.UnsafeCreate(fullKey, hash) : new(fullKey.AsSpan(keyIndex).ToArray()); } [Serializable, GenerateSerializer, Immutable] internal readonly struct InternalChannelId : IEquatable, IComparable, ISerializable, ISpanFormattable { [Id(0)] public readonly ChannelId ChannelId; [Id(1)] public readonly string ProviderName; public InternalChannelId(string providerName, ChannelId streamId) { ProviderName = providerName; ChannelId = streamId; } private InternalChannelId(SerializationInfo info, StreamingContext context) { ProviderName = info.GetString("pvn")!; ChannelId = (ChannelId)info.GetValue("sid", typeof(ChannelId))!; } public static implicit operator ChannelId(InternalChannelId internalStreamId) => internalStreamId.ChannelId; public bool Equals(InternalChannelId other) => ChannelId.Equals(other) && ProviderName.Equals(other.ProviderName); public override bool Equals(object? obj) => obj is InternalChannelId other ? this.Equals(other) : false; public static bool operator ==(InternalChannelId s1, InternalChannelId s2) => s1.Equals(s2); public static bool operator !=(InternalChannelId s1, InternalChannelId s2) => !s2.Equals(s1); public int CompareTo(InternalChannelId other) => ChannelId.CompareTo(other.ChannelId); public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("pvn", ProviderName); info.AddValue("sid", ChannelId, typeof(ChannelId)); } public override int GetHashCode() => HashCode.Combine(ProviderName, ChannelId); public override string ToString() => $"{ProviderName}/{ChannelId}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"{ProviderName}/{ChannelId}", out charsWritten); internal string? GetNamespace() => ChannelId.GetNamespace(); } } ================================================ FILE: src/Orleans.BroadcastChannel/Hosting/BroadcastChannelProviderBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("Default", "BroadcastChannel", "Client", typeof(BroadcastChannelProviderBuilder))] [assembly: RegisterProvider("Default", "BroadcastChannel", "Silo", typeof(BroadcastChannelProviderBuilder))] namespace Orleans.Providers; internal sealed class BroadcastChannelProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddBroadcastChannel(name, options => options.Bind(configurationSection)); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddBroadcastChannel(name, options => options.Bind(configurationSection)); } } ================================================ FILE: src/Orleans.BroadcastChannel/Hosting/ChannelHostingExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.BroadcastChannel; using Orleans.BroadcastChannel.SubscriberTable; using Orleans.Configuration; namespace Orleans.Hosting { public static class ChannelHostingExtensions { /// /// Add a new broadcast channel to the silo. /// /// The builder. /// The name of the provider /// The configuration delegate. /// public static ISiloBuilder AddBroadcastChannel(this ISiloBuilder @this, string name, Action configureOptions) { @this.Services.AddBroadcastChannel(name, ob => ob.Configure(configureOptions)); @this.AddGrainExtension(); return @this; } /// /// Add a new broadcast channel to the silo. /// /// The builder. /// The name of the provider /// The configuration delegate. public static ISiloBuilder AddBroadcastChannel(this ISiloBuilder @this, string name, Action> configureOptions = null) { @this.Services.AddBroadcastChannel(name, configureOptions); @this.AddGrainExtension(); return @this; } /// /// Add a new broadcast channel to the client. /// /// The builder. /// The name of the provider /// The configuration delegate. public static IClientBuilder AddBroadcastChannel(this IClientBuilder @this, string name, Action configureOptions) { @this.Services.AddBroadcastChannel(name, ob => ob.Configure(configureOptions)); return @this; } /// /// Add a new broadcast channel to the client. /// /// The builder. /// The name of the provider /// The configuration delegate. public static IClientBuilder AddBroadcastChannel(this IClientBuilder @this, string name, Action> configureOptions = null) { @this.Services.AddBroadcastChannel(name, configureOptions); return @this; } /// /// Get the named broadcast channel provided. /// /// The client. /// The name of the provider public static IBroadcastChannelProvider GetBroadcastChannelProvider(this IClusterClient @this, string name) => @this.ServiceProvider.GetRequiredKeyedService(name); private static void AddBroadcastChannel(this IServiceCollection services, string name, Action> configureOptions) { configureOptions?.Invoke(services.AddOptions(name)); services.ConfigureNamedOptionForLogging(name); services .AddSingleton() .AddSingleton() .AddSingleton() .AddKeyedSingleton(DefaultChannelIdMapper.Name) .AddKeyedSingleton(name, (sp, key) => BroadcastChannelProvider.Create(sp, key as string)); } } } ================================================ FILE: src/Orleans.BroadcastChannel/IdMapping/DefaultChannelIdMapper.cs ================================================ using System; using System.Buffers.Text; using Orleans.Metadata; using Orleans.Runtime; #nullable enable namespace Orleans.BroadcastChannel { /// /// The default implementation. /// public sealed class DefaultChannelIdMapper : IChannelIdMapper { /// /// The name of this stream identity mapper. /// public const string Name = "default"; /// public IdSpan GetGrainKeyId(GrainBindings grainBindings, ChannelId streamId) { string? keyType = null; bool includeNamespaceInGrainId = false; foreach (var grainBinding in grainBindings.Bindings) { if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type) || !string.Equals(type, WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue, StringComparison.Ordinal)) { continue; } if (grainBinding.TryGetValue(WellKnownGrainTypeProperties.LegacyGrainKeyType, out keyType)) { if (grainBinding.TryGetValue(WellKnownGrainTypeProperties.StreamBindingIncludeNamespaceKey, out var value) && string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) { includeNamespaceInGrainId = true; } } } return keyType switch { nameof(Guid) => GetGuidKey(streamId, includeNamespaceInGrainId), nameof(Int64) => GetIntegerKey(streamId, includeNamespaceInGrainId), _ => streamId.GetKeyIdSpan(), // null or string }; } private static IdSpan GetGuidKey(ChannelId streamId, bool includeNamespaceInGrainId) { var key = streamId.Key.Span; if (!Utf8Parser.TryParse(key, out Guid guidKey, out var len, 'N') || len < key.Length) throw new ArgumentException(nameof(streamId)); if (!includeNamespaceInGrainId) return streamId.GetKeyIdSpan(); var ns = streamId.Namespace.Span; return ns.IsEmpty ? streamId.GetKeyIdSpan() : GrainIdKeyExtensions.CreateGuidKey(guidKey, ns); } private static IdSpan GetIntegerKey(ChannelId streamId, bool includeNamespaceInGrainId) { var key = streamId.Key.Span; if (!Utf8Parser.TryParse(key, out int intKey, out var len) || len < key.Length) throw new ArgumentException(nameof(streamId)); return includeNamespaceInGrainId ? GrainIdKeyExtensions.CreateIntegerKey(intKey, streamId.Namespace.Span) : GrainIdKeyExtensions.CreateIntegerKey(intKey); } } } ================================================ FILE: src/Orleans.BroadcastChannel/IdMapping/IChannelIdMapper.cs ================================================ using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.BroadcastChannel { /// /// Common interface for component that map a to a /// public interface IChannelIdMapper { /// /// Get the which maps to the provided /// /// The grain bindings. /// The stream identifier. /// The component. IdSpan GetGrainKeyId(GrainBindings grainBindings, ChannelId streamId); } } ================================================ FILE: src/Orleans.BroadcastChannel/Orleans.BroadcastChannel.csproj ================================================ Microsoft.Orleans.BroadcastChannel Microsoft Orleans Broadcast Channel Library Broadcast Channel library for Microsoft Orleans used both on the client and server. $(DefaultTargetFrameworks) true false ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/ImplicitChannelSubscriberTable.cs ================================================ using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.BroadcastChannel.SubscriberTable { internal class ImplicitChannelSubscriberTable { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly GrainBindingsResolver _bindings; private readonly IChannelNamespacePredicateProvider[] _providers; private readonly IServiceProvider _serviceProvider; private Cache _cache; public ImplicitChannelSubscriberTable( GrainBindingsResolver bindings, IEnumerable providers, IServiceProvider serviceProvider) { _bindings = bindings; var initialBindings = bindings.GetAllBindings(); _providers = providers.ToArray(); _serviceProvider = serviceProvider; _cache = BuildCache(initialBindings.Version, initialBindings.Bindings); } private Cache GetCache() { var cache = _cache; var bindings = _bindings.GetAllBindings(); if (bindings.Version == cache.Version) { return cache; } lock (_lockObj) { bindings = _bindings.GetAllBindings(); if (bindings.Version == cache.Version) { return cache; } return _cache = BuildCache(bindings.Version, bindings.Bindings); } } private Cache BuildCache(MajorMinorVersion version, ImmutableDictionary bindings) { var newPredicates = new List(); foreach (var binding in bindings.Values) { foreach (var grainBinding in binding.Bindings) { if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type) || type != WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue) { continue; } if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey, out var pattern)) { throw new KeyNotFoundException( $"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}\" value"); } IChannelNamespacePredicate predicate = null; foreach (var provider in _providers) { if (provider.TryGetPredicate(pattern, out predicate)) break; } if (predicate is null) { throw new KeyNotFoundException( $"Could not find an {nameof(IChannelNamespacePredicate)} for the pattern \"{pattern}\"." + $" Ensure that a corresponding {nameof(IChannelNamespacePredicateProvider)} is registered"); } if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.ChannelIdMapperKey, out var mapperName)) { throw new KeyNotFoundException( $"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.ChannelIdMapperKey}\" value"); } var channelIdMapper = _serviceProvider.GetKeyedService(string.IsNullOrWhiteSpace(mapperName) ? DefaultChannelIdMapper.Name : mapperName); var subscriber = new BroadcastChannelSubscriber(binding, channelIdMapper); newPredicates.Add(new BroadcastChannelSubscriberPredicate(subscriber, predicate)); } } return new Cache(version, newPredicates); } /// /// Retrieve a map of implicit subscriptionsIds to implicit subscribers, given a channel ID. This method throws an exception if there's no namespace associated with the channel ID. /// /// A channel ID. /// The grain factory used to get consumer references. /// A set of references to implicitly subscribed grains. They are expected to support the broadcast channel consumer extension. /// The channel ID doesn't have an associated namespace. /// Internal invariant violation. internal Dictionary GetImplicitSubscribers(InternalChannelId channelId, IGrainFactory grainFactory) { var channelNamespace = channelId.GetNamespace(); if (string.IsNullOrWhiteSpace(channelNamespace)) { throw new ArgumentException("The channel ID doesn't have an associated namespace.", nameof(channelId)); } var entries = GetOrAddImplicitSubscribers(channelNamespace); var result = new Dictionary(); foreach (var entry in entries) { var consumer = MakeConsumerReference(grainFactory, channelId, entry); var subscriptionGuid = MakeSubscriptionGuid(entry.GrainType, channelId); CollectionsMarshal.GetValueRefOrAddDefault(result, subscriptionGuid, out var duplicate) = consumer; if (duplicate) { throw new InvalidOperationException( $"Internal invariant violation: generated duplicate subscriber reference: {consumer}, subscriptionId: {subscriptionGuid}"); } } return result; } private HashSet GetOrAddImplicitSubscribers(string channelNamespace) { var cache = GetCache(); if (cache.Namespaces.TryGetValue(channelNamespace, out var result)) { return result; } return cache.Namespaces.GetOrAdd(channelNamespace, FindImplicitSubscribers(channelNamespace, cache.Predicates)); } /// /// Create a subscriptionId that is unique per grainId, grainType, namespace combination. /// private Guid MakeSubscriptionGuid(GrainType grainType, InternalChannelId channelId) { Span bytes = stackalloc byte[16]; BinaryPrimitives.WriteUInt32LittleEndian(bytes, grainType.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[4..], channelId.ChannelId.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[8..], channelId.ChannelId.GetKeyIndex()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[12..], StableHash.ComputeHash(channelId.ProviderName)); bytes[15] |= 0x80; // set high bit of last byte (implicit subscription) return new(bytes); } /// /// Finds all implicit subscribers for the given channel namespace. /// private static HashSet FindImplicitSubscribers(string channelNamespace, List predicates) { var result = new HashSet(); foreach (var predicate in predicates) { if (predicate.Predicate.IsMatch(channelNamespace)) { result.Add(predicate.Subscriber); } } return result; } /// /// Create a reference to a grain that we expect to support the broadcast channel consumer extension. /// /// The grain factory used to get consumer references. /// The channel ID to use for the grain ID construction. /// The GrainBindings for the grain to create /// private static IBroadcastChannelConsumerExtension MakeConsumerReference( IGrainFactory grainFactory, InternalChannelId channelId, BroadcastChannelSubscriber channelSubscriber) { var grainId = channelSubscriber.GetGrainId(channelId); return grainFactory.GetGrain(grainId); } private class BroadcastChannelSubscriberPredicate { public BroadcastChannelSubscriberPredicate(BroadcastChannelSubscriber subscriber, IChannelNamespacePredicate predicate) { Subscriber = subscriber; Predicate = predicate; } public BroadcastChannelSubscriber Subscriber { get; } public IChannelNamespacePredicate Predicate { get; } } private sealed class BroadcastChannelSubscriber : IEquatable { public BroadcastChannelSubscriber(GrainBindings grainBindings, IChannelIdMapper channelIdMapper) { GrainBindings = grainBindings; this.channelIdMapper = channelIdMapper; } public GrainType GrainType => GrainBindings.GrainType; private GrainBindings GrainBindings { get; } private IChannelIdMapper channelIdMapper { get; } public override bool Equals(object obj) => Equals(obj as BroadcastChannelSubscriber); public bool Equals(BroadcastChannelSubscriber other) => other != null && GrainType.Equals(other.GrainType); public override int GetHashCode() => GrainType.GetHashCode(); internal GrainId GetGrainId(InternalChannelId channelId) { var grainKeyId = channelIdMapper.GetGrainKeyId(GrainBindings, channelId); return GrainId.Create(GrainType, grainKeyId); } } private class Cache { public Cache(MajorMinorVersion version, List predicates) { Version = version; Predicates = predicates; Namespaces = new ConcurrentDictionary>(); } public MajorMinorVersion Version { get; } public ConcurrentDictionary> Namespaces { get; } public List Predicates { get; } } } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/AllStreamNamespacesPredicate.cs ================================================ namespace Orleans.BroadcastChannel { /// /// A stream namespace predicate which matches all namespaces. /// internal class AllStreamNamespacesPredicate : IChannelNamespacePredicate { /// public string PredicatePattern => "*"; /// public bool IsMatch(string streamNamespace) { return true; } } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/DefaultStreamNamespacePredicateProvider.cs ================================================ using System; using Orleans.Serialization.TypeSystem; namespace Orleans.BroadcastChannel { /// /// Default implementation of for internally supported stream predicates. /// public class DefaultChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider { /// public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate) { switch (predicatePattern) { case "*": predicate = new AllStreamNamespacesPredicate(); return true; case var regex when regex.StartsWith(RegexChannelNamespacePredicate.Prefix, StringComparison.Ordinal): predicate = new RegexChannelNamespacePredicate(regex[RegexChannelNamespacePredicate.Prefix.Length..]); return true; case var ns when ns.StartsWith(ExactMatchChannelNamespacePredicate.Prefix, StringComparison.Ordinal): predicate = new ExactMatchChannelNamespacePredicate(ns[ExactMatchChannelNamespacePredicate.Prefix.Length..]); return true; } predicate = null; return false; } } /// /// Stream namespace predicate provider which supports objects which can be constructed and optionally accept a string as a constructor argument. /// public class ConstructorChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider { /// /// The prefix used to identify this predicate provider. /// public const string Prefix = "ctor"; /// /// Formats a stream namespace predicate which indicates a concrete type to be constructed, along with an optional argument. /// public static string FormatPattern(Type predicateType, string constructorArgument) { if (constructorArgument is null) { return $"{Prefix}:{RuntimeTypeNameFormatter.Format(predicateType)}"; } return $"{Prefix}:{RuntimeTypeNameFormatter.Format(predicateType)}:{constructorArgument}"; } /// public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate) { if (!predicatePattern.StartsWith(Prefix, StringComparison.Ordinal)) { predicate = null; return false; } var start = Prefix.Length + 1; string typeName; string arg; var index = predicatePattern.IndexOf(':', start); if (index < 0) { typeName = predicatePattern[start..]; arg = null; } else { typeName = predicatePattern[start..index]; arg = predicatePattern[(index + 1)..]; } var type = Type.GetType(typeName, throwOnError: true); if (string.IsNullOrEmpty(arg)) { predicate = (IChannelNamespacePredicate)Activator.CreateInstance(type); } else { predicate = (IChannelNamespacePredicate)Activator.CreateInstance(type, arg); } return true; } } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/ExactMatchStreamNamespacePredicate.cs ================================================ using System; namespace Orleans.BroadcastChannel { /// /// Stream namespace predicate which matches exactly one, specified /// [Serializable, GenerateSerializer, Immutable] internal sealed class ExactMatchChannelNamespacePredicate : IChannelNamespacePredicate { internal const string Prefix = "namespace:"; [Id(0)] private readonly string targetStreamNamespace; /// /// Initializes a new instance of the class. /// /// The target stream namespace. public ExactMatchChannelNamespacePredicate(string targetStreamNamespace) { this.targetStreamNamespace = targetStreamNamespace; } /// public string PredicatePattern => $"{Prefix}{this.targetStreamNamespace}"; /// public bool IsMatch(string streamNamespace) { return string.Equals(targetStreamNamespace, streamNamespace?.Trim()); } } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/IChannelNamespacePredicate.cs ================================================ namespace Orleans.BroadcastChannel { /// /// Stream namespace predicate used for filtering implicit subscriptions using /// . /// /// All implementations must be serializable. public interface IChannelNamespacePredicate { /// /// Defines if the consumer grain should subscribe to the specified namespace. /// /// The target stream namespace to check. /// true, if the grain should subscribe to the specified namespace; false, otherwise. /// bool IsMatch(string streamNamespace); /// /// Gets a pattern to describe this predicate. This value is passed to instances of to recreate this predicate. /// string PredicatePattern { get; } } /// /// Converts predicate pattern strings to instances. /// /// public interface IChannelNamespacePredicateProvider { /// /// Get the predicate matching the provided pattern. Returns if this provider cannot match the predicate. /// bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate); } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/ImplicitChannelSubscriptionAttribute.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Orleans.BroadcastChannel; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans { /// /// The [Orleans.ImplicitStreamSubscription] attribute is used to mark grains as implicit stream subscriptions. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ImplicitChannelSubscriptionAttribute : Attribute, IGrainBindingsProviderAttribute { /// /// Gets the stream namespace filter predicate. /// public IChannelNamespacePredicate Predicate { get; } /// /// Gets the name of the channel identifier mapper. /// /// The name of the channel identifier mapper. public string ChannelIdMapper { get; } /// /// Used to subscribe to all stream namespaces. /// public ImplicitChannelSubscriptionAttribute() { Predicate = new AllStreamNamespacesPredicate(); } /// /// Used to subscribe to the specified stream namespace. /// /// The stream namespace to subscribe. /// The name of the stream identity mapper. public ImplicitChannelSubscriptionAttribute(string streamNamespace, string channelIdMapper = null) { Predicate = new ExactMatchChannelNamespacePredicate(streamNamespace.Trim()); ChannelIdMapper = channelIdMapper; } /// /// Allows to pass an arbitrary predicate type to filter stream namespaces to subscribe. The predicate type /// must have a constructor without parameters. /// /// The stream namespace predicate type. /// The name of the stream identity mapper. public ImplicitChannelSubscriptionAttribute(Type predicateType, string channelIdMapper = null) { Predicate = (IChannelNamespacePredicate) Activator.CreateInstance(predicateType); ChannelIdMapper = channelIdMapper; } /// /// Allows to pass an instance of the stream namespace predicate. To be used mainly as an extensibility point /// via inheriting attributes. /// /// The stream namespace predicate. /// The name of the stream identity mapper. public ImplicitChannelSubscriptionAttribute(IChannelNamespacePredicate predicate, string channelIdMapper = null) { Predicate = predicate; ChannelIdMapper = channelIdMapper; } /// public IEnumerable> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType) { var binding = new Dictionary { [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue, [WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey] = this.Predicate.PredicatePattern, [WellKnownGrainTypeProperties.ChannelIdMapperKey] = this.ChannelIdMapper, }; if (LegacyGrainId.IsLegacyGrainType(grainClass)) { string keyType; if (typeof(IGrainWithGuidKey).IsAssignableFrom(grainClass) || typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(grainClass)) keyType = nameof(Guid); else if (typeof(IGrainWithIntegerKey).IsAssignableFrom(grainClass) || typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(grainClass)) keyType = nameof(Int64); else // fallback to string keyType = nameof(String); binding[WellKnownGrainTypeProperties.LegacyGrainKeyType] = keyType; } if (LegacyGrainId.IsLegacyKeyExtGrainType(grainClass)) { binding[WellKnownGrainTypeProperties.StreamBindingIncludeNamespaceKey] = "true"; } yield return binding; } } /// /// The [Orleans.RegexImplicitStreamSubscription] attribute is used to mark grains as implicit stream /// subscriptions by filtering stream namespaces to subscribe using a regular expression. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class RegexImplicitChannelSubscriptionAttribute : ImplicitChannelSubscriptionAttribute { /// /// Allows to pass a regular expression to filter stream namespaces to subscribe to. /// /// The stream namespace regular expression filter. public RegexImplicitChannelSubscriptionAttribute([StringSyntax(StringSyntaxAttribute.Regex)] string pattern) : base(new RegexChannelNamespacePredicate(pattern)) { } } } ================================================ FILE: src/Orleans.BroadcastChannel/SubscriberTable/Predicates/RegexChannelNamespacePredicate.cs ================================================ using System; using System.Text.RegularExpressions; namespace Orleans.BroadcastChannel { /// /// implementation allowing to filter stream namespaces by regular /// expression. /// public class RegexChannelNamespacePredicate : IChannelNamespacePredicate { internal const string Prefix = "regex:"; private readonly Regex regex; /// /// Returns a pattern used to describe this instance. The pattern will be parsed by an instance on each node. /// public string PredicatePattern => $"{Prefix}{regex}"; /// /// Creates an instance of with the specified regular expression. /// /// The stream namespace regular expression. public RegexChannelNamespacePredicate(string regex) { if (regex is null) throw new ArgumentNullException(nameof(regex)); this.regex = new Regex(regex, RegexOptions.Compiled); } /// public bool IsMatch(string streamNameSpace) { return regex.IsMatch(streamNameSpace); } } } ================================================ FILE: src/Orleans.Client/Orleans.Client.csproj ================================================ Microsoft.Orleans.Client Microsoft Orleans Client Libraries Collection of Microsoft Orleans libraries and files needed on the client. false true false false $(DefaultTargetFrameworks) README.md ================================================ FILE: src/Orleans.Client/README.md ================================================ # Microsoft Orleans Client ## Introduction Microsoft Orleans Client is a metapackage that includes all the necessary components to connect to an Orleans cluster from a client application. It provides a simplified way to set up an Orleans client by providing a single package reference rather than requiring you to reference multiple packages individually. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Client ``` ## Example - Creating an Orleans Client ```csharp using Microsoft.Extensions.Hosting; using Orleans; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace ExampleGrains; // Define a grain interface public interface IMyGrain : IGrainWithStringKey { Task DoSomething(); } // Create a client var builder = Host.CreateApplicationBuilder(args) .UseOrleansClient(client => { client.UseLocalhostClustering(); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("my-grain-id"); var result = await grain.DoSomething(); // Print the result Console.WriteLine($"Result: {result}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans client configuration](https://learn.microsoft.com/en-us/dotnet/orleans/host/client) - [Grain references](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-references) - [Orleans request context](https://learn.microsoft.com/en-us/dotnet/orleans/grains/request-context) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Clustering.Consul/ConsulBasedMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Consul; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime.Host; namespace Orleans.Runtime.Membership { /// /// A Membership Table implementation using Consul 0.6.0 https://consul.io/ /// public partial class ConsulBasedMembershipTable : IMembershipTable { private static readonly TableVersion NotFoundTableVersion = new TableVersion(0, "0"); private readonly ILogger _logger; private readonly IConsulClient _consulClient; private readonly ConsulClusteringOptions clusteringSiloTableOptions; private readonly string clusterId; private readonly string kvRootFolder; private readonly string versionKey; public ConsulBasedMembershipTable( ILogger logger, IOptions membershipTableOptions, IOptions clusterOptions) { this.clusterId = clusterOptions.Value.ClusterId; this.kvRootFolder = membershipTableOptions.Value.KvRootFolder; this._logger = logger; this.clusteringSiloTableOptions = membershipTableOptions.Value; this._consulClient = this.clusteringSiloTableOptions.CreateClient(); versionKey = ConsulSiloRegistrationAssembler.FormatVersionKey(clusterId, kvRootFolder); } /// /// Initializes the Consul based membership table. /// /// Will be ignored: Consul does not support the extended Membership Protocol TableVersion /// /// /// Consul Membership Provider does not support the extended Membership Protocol, /// therefore there is no MembershipTable to Initialize /// public Task InitializeMembershipTable(bool tryInitTableVersion) { return Task.CompletedTask; } public async Task ReadRow(SiloAddress siloAddress) { var (siloRegistration, tableVersion) = await GetConsulSiloRegistration(siloAddress); return AssembleMembershipTableData(tableVersion, siloRegistration); } public Task ReadAll() { return ReadAll(this._consulClient, this.clusterId, this.kvRootFolder, this._logger, this.versionKey); } public static async Task ReadAll(IConsulClient consulClient, string clusterId, string kvRootFolder, ILogger logger, string versionKey) { var deploymentKVAddresses = await consulClient.KV.List(ConsulSiloRegistrationAssembler.FormatDeploymentKVPrefix(clusterId, kvRootFolder)); if (deploymentKVAddresses.Response == null) { LogDebugCouldNotFindSiloRegistrations(logger, clusterId); return new MembershipTableData(NotFoundTableVersion); } var allSiloRegistrations = deploymentKVAddresses.Response .Where(siloKV => !siloKV.Key.EndsWith(ConsulSiloRegistrationAssembler.SiloIAmAliveSuffix, StringComparison.OrdinalIgnoreCase) && !siloKV.Key.EndsWith(ConsulSiloRegistrationAssembler.VersionSuffix, StringComparison.OrdinalIgnoreCase)) .Select(siloKV => { var iAmAliveKV = deploymentKVAddresses.Response.SingleOrDefault(kv => kv.Key.Equals(ConsulSiloRegistrationAssembler.FormatSiloIAmAliveKey(siloKV.Key), StringComparison.OrdinalIgnoreCase)); return ConsulSiloRegistrationAssembler.FromKVPairs(clusterId, siloKV, iAmAliveKV); }).ToArray(); var tableVersion = GetTableVersion(versionKey, deploymentKVAddresses); return AssembleMembershipTableData(tableVersion, allSiloRegistrations); } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { try { //Use "0" as the eTag then Consul KV CAS will treat the operation as an insert and return false if the KV already exiats. var siloRegistration = ConsulSiloRegistrationAssembler.FromMembershipEntry(this.clusterId, entry, "0"); var insertKV = ConsulSiloRegistrationAssembler.ToKVPair(siloRegistration, this.kvRootFolder); var rowInsert = new KVTxnOp(insertKV.Key, KVTxnVerb.CAS) { Index = siloRegistration.LastIndex, Value = insertKV.Value }; var versionUpdate = this.GetVersionRowUpdate(tableVersion); var responses = await _consulClient.KV.Txn(new List { rowInsert, versionUpdate }); if (!responses.Response.Success) { LogDebugConsulMembershipProviderFailedToInsertRow(entry.SiloAddress); return false; } return true; } catch (Exception ex) { LogInformationConsulMembershipProviderFailedToInsertRegistration(ex, entry.SiloAddress); throw; } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { //Update Silo Liveness try { var siloRegistration = ConsulSiloRegistrationAssembler.FromMembershipEntry(this.clusterId, entry, etag); var updateKV = ConsulSiloRegistrationAssembler.ToKVPair(siloRegistration, this.kvRootFolder); var rowUpdate = new KVTxnOp(updateKV.Key, KVTxnVerb.CAS) { Index = siloRegistration.LastIndex, Value = updateKV.Value }; var versionUpdate = this.GetVersionRowUpdate(tableVersion); var responses = await _consulClient.KV.Txn(new List { rowUpdate, versionUpdate }); if (!responses.Response.Success) { LogDebugConsulMembershipProviderFailedCASCheck(entry.SiloAddress); return false; } return true; } catch (Exception ex) { LogInformationConsulMembershipProviderFailedToUpdateRegistration(ex, entry.SiloAddress); throw; } } public async Task UpdateIAmAlive(MembershipEntry entry) { var iAmAliveKV = ConsulSiloRegistrationAssembler.ToIAmAliveKVPair(this.clusterId, this.kvRootFolder, entry.SiloAddress, entry.IAmAliveTime); await _consulClient.KV.Put(iAmAliveKV); } public async Task DeleteMembershipTableEntries(string clusterId) { await _consulClient.KV.DeleteTree(ConsulSiloRegistrationAssembler.FormatDeploymentKVPrefix(this.clusterId, this.kvRootFolder)); } private static TableVersion GetTableVersion(string versionKey, QueryResult entries) { TableVersion tableVersion; var tableVersionEntry = entries?.Response?.FirstOrDefault(kv => kv.Key.Equals(versionKey ?? string.Empty, StringComparison.OrdinalIgnoreCase)); if (tableVersionEntry != null) { var versionNumber = 0; if (tableVersionEntry.Value is byte[] versionData && versionData.Length > 0) { int.TryParse(Encoding.UTF8.GetString(tableVersionEntry.Value), out versionNumber); } tableVersion = new TableVersion(versionNumber, tableVersionEntry.ModifyIndex.ToString(CultureInfo.InvariantCulture)); } else { tableVersion = NotFoundTableVersion; } return tableVersion; } private KVTxnOp GetVersionRowUpdate(TableVersion version) { ulong.TryParse(version.VersionEtag, out var index); var versionBytes = Encoding.UTF8.GetBytes(version.Version.ToString(CultureInfo.InvariantCulture)); return new KVTxnOp(this.versionKey, KVTxnVerb.CAS) { Index = index, Value = versionBytes }; } private async Task<(ConsulSiloRegistration, TableVersion)> GetConsulSiloRegistration(SiloAddress siloAddress) { var deploymentKey = ConsulSiloRegistrationAssembler.FormatDeploymentKVPrefix(this.clusterId, this.kvRootFolder); var siloKey = ConsulSiloRegistrationAssembler.FormatDeploymentSiloKey(this.clusterId, this.kvRootFolder, siloAddress); var entries = await _consulClient.KV.List(deploymentKey); if (entries.Response == null) return (null, NotFoundTableVersion); var siloKV = entries.Response.Single(KV => KV.Key.Equals(siloKey, StringComparison.OrdinalIgnoreCase)); var iAmAliveKV = entries.Response.SingleOrDefault(KV => KV.Key.Equals(ConsulSiloRegistrationAssembler.FormatSiloIAmAliveKey(siloKey), StringComparison.OrdinalIgnoreCase)); var tableVersion = GetTableVersion(versionKey: versionKey, entries: entries); var siloRegistration = ConsulSiloRegistrationAssembler.FromKVPairs(this.clusterId, siloKV, iAmAliveKV); return (siloRegistration, tableVersion); } private static MembershipTableData AssembleMembershipTableData(TableVersion tableVersion, params ConsulSiloRegistration[] silos) { var membershipEntries = silos .Where(silo => silo != null) .Select(silo => ConsulSiloRegistrationAssembler.ToMembershipEntry(silo)) .ToList(); return new MembershipTableData(membershipEntries, tableVersion); } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { var allKVs = await _consulClient.KV.List(ConsulSiloRegistrationAssembler.FormatDeploymentKVPrefix(this.clusterId, this.kvRootFolder)); if (allKVs.Response == null) { LogDebugCouldNotFindSiloRegistrationsForCleanup(this.clusterId); return; } var allRegistrations = allKVs.Response .Where(siloKV => !siloKV.Key.EndsWith(ConsulSiloRegistrationAssembler.SiloIAmAliveSuffix, StringComparison.OrdinalIgnoreCase) && !siloKV.Key.EndsWith(ConsulSiloRegistrationAssembler.VersionSuffix, StringComparison.OrdinalIgnoreCase)) .Select(siloKV => { var iAmAliveKV = allKVs.Response.SingleOrDefault(kv => kv.Key.Equals(ConsulSiloRegistrationAssembler.FormatSiloIAmAliveKey(siloKV.Key), StringComparison.OrdinalIgnoreCase)); return new { RegistrationKey = siloKV.Key, Registration = ConsulSiloRegistrationAssembler.FromKVPairs(clusterId, siloKV, iAmAliveKV) }; }).ToArray(); foreach (var entry in allRegistrations) { if (entry.Registration.IAmAliveTime < beforeDate && entry.Registration.Status != SiloStatus.Active) { await _consulClient.KV.DeleteTree(entry.RegistrationKey); } } } [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Debug, Message = "Could not find any silo registrations for deployment {ClusterId}." )] private static partial void LogDebugCouldNotFindSiloRegistrations(ILogger logger, string clusterId); [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Debug, Message = "ConsulMembershipProvider failed to insert the row {SiloAddress}." )] private partial void LogDebugConsulMembershipProviderFailedToInsertRow(SiloAddress siloAddress); [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Information, Message = "ConsulMembershipProvider failed to insert registration for silo {SiloAddress}" )] private partial void LogInformationConsulMembershipProviderFailedToInsertRegistration(Exception ex, SiloAddress siloAddress); [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Debug, Message = "ConsulMembershipProvider failed the CAS check when updating the registration for silo {SiloAddress}." )] private partial void LogDebugConsulMembershipProviderFailedCASCheck(SiloAddress siloAddress); [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Information, Message = "ConsulMembershipProvider failed to update the registration for silo {SiloAddress}" )] private partial void LogInformationConsulMembershipProviderFailedToUpdateRegistration(Exception ex, SiloAddress siloAddress); [LoggerMessage( Level = Microsoft.Extensions.Logging.LogLevel.Debug, Message = "Could not find any silo registrations for deployment {ClusterId}." )] private partial void LogDebugCouldNotFindSiloRegistrationsForCleanup(string clusterId); } } ================================================ FILE: src/Orleans.Clustering.Consul/ConsulClusteringProviderBuilder.cs ================================================ using System; using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; [assembly: RegisterProvider("Consul", "Clustering", "Client", typeof(ConsulClusteringProviderBuilder))] [assembly: RegisterProvider("Consul", "Clustering", "Silo", typeof(ConsulClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class ConsulClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseConsulSiloClustering(options => options.Bind(configurationSection)); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseConsulClientClustering(options => options.Bind(configurationSection)); } } ================================================ FILE: src/Orleans.Clustering.Consul/ConsulGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Consul; using Orleans.Messaging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Membership { public class ConsulGatewayListProvider : IGatewayListProvider { private IConsulClient consulClient; private readonly string clusterId; private readonly ILogger logger; private readonly ConsulClusteringOptions options; private readonly TimeSpan maxStaleness; private readonly string kvRootFolder; public ConsulGatewayListProvider( ILogger logger, IOptions options, IOptions gatewayOptions, IOptions clusterOptions) { this.logger = logger; this.clusterId = clusterOptions.Value.ClusterId; this.maxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; this.options = options.Value; this.kvRootFolder = options.Value.KvRootFolder; } public TimeSpan MaxStaleness { get { return this.maxStaleness; } } public bool IsUpdatable { get { return true; } } public Task InitializeGatewayListProvider() { consulClient = options.CreateClient(); return Task.CompletedTask; } public async Task> GetGateways() { var membershipTableData = await ConsulBasedMembershipTable.ReadAll(this.consulClient, this.clusterId, this.kvRootFolder, this.logger, null); if (membershipTableData == null) return new List(); return membershipTableData.Members.Select(e => e.Item1). Where(m => m.Status == SiloStatus.Active && m.ProxyPort != 0). Select(m => { var gatewayAddress = SiloAddress.New(m.SiloAddress.Endpoint.Address, m.ProxyPort, m.SiloAddress.Generation); return gatewayAddress.ToGatewayUri(); }).ToList(); } } } ================================================ FILE: src/Orleans.Clustering.Consul/ConsulUtilsHostingExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Messaging; using Orleans.Runtime.Membership; using Orleans.Configuration; namespace Orleans.Hosting { public static class ConsulUtilsHostingExtensions { /// /// Configures the silo to use Consul for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseConsulSiloClustering( this ISiloBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configures the silo to use Consul for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseConsulSiloClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } /// /// Configures the client to use Consul for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseConsulClientClustering( this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices(services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configures the client to use Consul for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseConsulClientClustering( this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } } } ================================================ FILE: src/Orleans.Clustering.Consul/Options/ConsulClusteringOptions.cs ================================================ using System; using Consul; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Base class for consul-cluster-options. /// public class ConsulClusteringOptions { /// /// Consul KV root folder name. /// public string KvRootFolder { get; set; } /// /// Factory for the used Consul-Client. /// public Func CreateClient { get; private set; } /// /// Configures the using the provided callback. /// public void ConfigureConsulClient(Func createClientCallback) { CreateClient = createClientCallback ?? throw new ArgumentNullException(nameof(createClientCallback)); } /// /// Configures the using the consul-address and a acl-token. /// public void ConfigureConsulClient(Uri address, string aclClientToken = null) { if (address is null) throw new ArgumentNullException(nameof(address)); CreateClient = () => new ConsulClient(config => { config.Address = address; config.Token = aclClientToken; }); } public ConsulClusteringOptions() { this.CreateClient = () => new ConsulClient(); } internal void Validate(string name) { if (CreateClient is null) { throw new OrleansConfigurationException($"No callback specified. Use the {GetType().Name}.{nameof(ConsulClusteringOptions.ConfigureConsulClient)} method to configure the consul client."); } } } public class ConsulClusteringOptionsValidator : IConfigurationValidator where TOptions : ConsulClusteringOptions { public ConsulClusteringOptionsValidator(TOptions options, string name = null) { Options = options; Name = name; } public TOptions Options { get; } public string Name { get; } public virtual void ValidateConfiguration() { Options.Validate(Name); } } } ================================================ FILE: src/Orleans.Clustering.Consul/Orleans.Clustering.Consul.csproj ================================================ Microsoft.Orleans.Clustering.Consul Microsoft Orleans clustering provider for Consul Microsoft Orleans clustering provider backed by Consul $(PackageTags) Consul $(DefaultTargetFrameworks) Orleans.Clustering.Consul true ================================================ FILE: src/Orleans.Clustering.Consul/SerializableMembershipTypes.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using Consul; using Newtonsoft.Json; namespace Orleans.Runtime.Host { /// /// JSON Serializable Object that when serialized and Base64 encoded, forms the Value part of a Silo's Consul KVPair /// [JsonObject] public class ConsulSiloRegistration { /// /// Persisted as part of the KV Key therefore not serialised. /// [JsonIgnore] internal string DeploymentId { get; set; } /// /// Persisted as part of the KV Key therefore not serialised. /// [JsonIgnore] internal SiloAddress Address { get; set; } /// /// Persisted in a separate KV Subkey, therefore not serialised but held here to enable cleaner assembly to MembershipEntry. /// /// /// Stored in a separate KV otherwise the regular updates to IAmAlive cause the Silo's KV.ModifyIndex to change /// which in turn cause UpdateRow operations to fail. /// [JsonIgnore] internal DateTime IAmAliveTime { get; set; } /// /// Used to compare CAS value on update, persisted as KV.ModifyIndex therefore not serialised. /// [JsonIgnore] internal ulong LastIndex { get; set; } //Public properties are serialized to the KV.Value [JsonProperty] public string Hostname { get; set; } [JsonProperty] public int ProxyPort { get; set; } [JsonProperty] public DateTime StartTime { get; set; } [JsonProperty] public SiloStatus Status { get; set; } [JsonProperty] public string SiloName { get; set; } [JsonProperty] public List SuspectingSilos { get; set; } [JsonConstructor] internal ConsulSiloRegistration() { SuspectingSilos = new List(); } } /// /// JSON Serializable Object that when serialized and Base64 encoded, forms each entry in the SuspectingSilos list /// [JsonObject] public class SuspectingSilo { [JsonProperty] public string Id { get; set; } [JsonProperty] public DateTime Time { get; set; } } /// /// Contains methods for converting a Consul KVPair to and from a MembershipEntry. /// This uses ConsulSiloRegistration objects as the serializable KV.Value and minimises conversion operations. /// internal class ConsulSiloRegistrationAssembler { private const string DeploymentKVPrefix = "orleans"; //Ensures a root KV namespace for orleans in Consul private const char KeySeparator = '/'; internal const string SiloIAmAliveSuffix = "iamalive"; internal const string VersionSuffix = "version"; internal static string FormatVersionKey(string deploymentId, string rootKvFolder) => $"{FormatDeploymentKVPrefix(deploymentId, rootKvFolder)}{KeySeparator}{VersionSuffix}"; internal static string FormatDeploymentKVPrefix(string deploymentId, string rootKvFolder) { //Backward compatible if (string.IsNullOrEmpty(rootKvFolder)) { return $"{DeploymentKVPrefix}{KeySeparator}{deploymentId}"; } else { return $"{rootKvFolder}{KeySeparator}{DeploymentKVPrefix}{KeySeparator}{deploymentId}"; } } internal static string FormatDeploymentSiloKey(string deploymentId, string rootKvFolder, SiloAddress siloAddress) { return $"{FormatDeploymentKVPrefix(deploymentId, rootKvFolder)}{KeySeparator}{siloAddress.ToParsableString()}"; } internal static string FormatSiloIAmAliveKey(string siloKey) { return $"{siloKey}{KeySeparator}{SiloIAmAliveSuffix}"; } internal static string FormatSiloIAmAliveKey(string deploymentId, string rootKvFolder, SiloAddress siloAddress) { return FormatSiloIAmAliveKey(FormatDeploymentSiloKey(deploymentId, rootKvFolder, siloAddress)); } internal static ConsulSiloRegistration FromKVPairs(string deploymentId, KVPair siloKV, KVPair iAmAliveKV) { var ret = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(siloKV.Value)); var keyParts = siloKV.Key.Split(KeySeparator); ret.Address = SiloAddress.FromParsableString(keyParts[^1]); ret.DeploymentId = deploymentId; ret.LastIndex = siloKV.ModifyIndex; if (iAmAliveKV == null) ret.IAmAliveTime = ret.StartTime; else ret.IAmAliveTime = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(iAmAliveKV.Value)); return ret; } internal static ConsulSiloRegistration FromMembershipEntry(string deploymentId, MembershipEntry entry, string etag) { var ret = new ConsulSiloRegistration { DeploymentId = deploymentId, Address = entry.SiloAddress, IAmAliveTime = entry.IAmAliveTime, LastIndex = Convert.ToUInt64(etag), Hostname = entry.HostName, ProxyPort = entry.ProxyPort, StartTime = entry.StartTime, Status = entry.Status, SiloName = entry.SiloName, SuspectingSilos = entry.SuspectTimes?.Select(silo => new SuspectingSilo { Id = silo.Item1.ToParsableString(), Time = silo.Item2 }).ToList() }; return ret; } internal static KVPair ToKVPair(ConsulSiloRegistration siloRegistration, string rootKvFolder) { var ret = new KVPair(ConsulSiloRegistrationAssembler.FormatDeploymentSiloKey(siloRegistration.DeploymentId, rootKvFolder, siloRegistration.Address)); ret.ModifyIndex = siloRegistration.LastIndex; ret.Value = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(siloRegistration)); return ret; } internal static KVPair ToIAmAliveKVPair(string deploymentId, string rootKvFolder, SiloAddress siloAddress, DateTime iAmAliveTime) { var ret = new KVPair(ConsulSiloRegistrationAssembler.FormatSiloIAmAliveKey(deploymentId, rootKvFolder, siloAddress)); ret.Value = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(iAmAliveTime)); return ret; } internal static Tuple ToMembershipEntry(ConsulSiloRegistration siloRegistration) { var entry = new MembershipEntry { SiloAddress = siloRegistration.Address, HostName = siloRegistration.Hostname, Status = siloRegistration.Status, ProxyPort = siloRegistration.ProxyPort, StartTime = siloRegistration.StartTime, SuspectTimes = siloRegistration.SuspectingSilos?.Select(silo => new Tuple(SiloAddress.FromParsableString(silo.Id), silo.Time)).ToList(), IAmAliveTime = siloRegistration.IAmAliveTime, SiloName = siloRegistration.SiloName, // Optional - only for Azure role so initialised here RoleName = string.Empty, UpdateZone = 0, FaultZone = 0 }; return new Tuple(entry, siloRegistration.LastIndex.ToString()); } } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/MembershipSerializerSettings.cs ================================================ using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; namespace Orleans.Runtime.Host { internal class MembershipSerializerSettings : JsonSerializerSettings { public static readonly MembershipSerializerSettings Instance = new MembershipSerializerSettings(); private MembershipSerializerSettings() { Converters.Add(new SiloAddressConverter()); Converters.Add(new MembershipEntryConverter()); Converters.Add(new StringEnumConverter()); } private class MembershipEntryConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(MembershipEntry)); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { MembershipEntry me = (MembershipEntry)value; writer.WriteStartObject(); writer.WritePropertyName("SiloAddress"); serializer.Serialize(writer, me.SiloAddress); writer.WritePropertyName("HostName"); writer.WriteValue(me.HostName); writer.WritePropertyName("SiloName"); writer.WriteValue(me.SiloName); writer.WritePropertyName("InstanceName"); writer.WriteValue(me.SiloName); writer.WritePropertyName("Status"); serializer.Serialize(writer, me.Status); writer.WritePropertyName("ProxyPort"); writer.WriteValue(me.ProxyPort); writer.WritePropertyName("StartTime"); writer.WriteValue(me.StartTime); writer.WritePropertyName("SuspectTimes"); serializer.Serialize(writer, me.SuspectTimes); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); return new MembershipEntry { SiloAddress = jo["SiloAddress"].ToObject(serializer), HostName = jo["HostName"].ToObject(), SiloName = (jo["SiloName"] ?? jo["InstanceName"]).ToObject(), Status = jo["Status"].ToObject(serializer), ProxyPort = jo["ProxyPort"].Value(), StartTime = jo["StartTime"].Value(), SuspectTimes = jo["SuspectTimes"].ToObject>>(serializer) }; } } private class SiloAddressConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(SiloAddress)); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { SiloAddress se = (SiloAddress)value; writer.WriteStartObject(); writer.WritePropertyName("SiloAddress"); writer.WriteValue(se.ToParsableString()); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); string seStr = jo["SiloAddress"].ToObject(serializer); return SiloAddress.FromParsableString(seStr); } } } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/Options/ZooKeeperClusteringSiloOptions.cs ================================================ namespace Orleans.Configuration { /// /// Option to configure ZooKeeperMembership /// public class ZooKeeperClusteringSiloOptions { /// /// Connection string for ZooKeeper Storage /// [Redact] public string ConnectionString { get; set; } } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/Options/ZooKeeperGatewayListProviderOptions.cs ================================================ namespace Orleans.Configuration { public class ZooKeeperGatewayListProviderOptions { /// /// Connection string for ZooKeeper storage /// [Redact] public string ConnectionString { get; set; } } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/Orleans.Clustering.ZooKeeper.csproj ================================================ Microsoft.Orleans.Clustering.ZooKeeper Microsoft Orleans clustering provider for ZooKeeper Microsoft Orleans clustering provider backed by ZooKeeper $(PackageTags) ZooKeeper $(DefaultTargetFrameworks) Orleans.Clustering.ZooKeeper true ================================================ FILE: src/Orleans.Clustering.ZooKeeper/ZooKeeperBasedMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using org.apache.zookeeper; using org.apache.zookeeper.data; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime.Host; namespace Orleans.Runtime.Membership { /// /// A Membership Table implementation using Apache Zookeeper 3.4.6 https://zookeeper.apache.org/doc/r3.4.6/ /// /// /// A brief overview of ZK features used: The data is represented by a tree of nodes (similar to a file system). /// Every node is addressed by a path and can hold data as a byte array and has a version. When a node is created, /// its version is 0. Upon updates, the version is atomically incremented. An update can also be conditional on an /// expected current version. A transaction can hold several operations, which succeed or fail atomically. /// when creating a zookeeper client, one can set a base path where all operations are relative to. /// /// In this implementation: /// Every Orleans deployment has a node /UniqueDeploymentId /// Every Silo's state is saved in /UniqueDeploymentId/IP:Port@Gen /// Every Silo's IAmAlive is saved in /UniqueDeploymentId/IP:Port@Gen/IAmAlive /// IAmAlive is saved in a separate node because its updates are unconditional. /// /// a node's ZK version is its ETag: /// the table version is the version of /UniqueDeploymentId /// the silo entry version is the version of /UniqueDeploymentId/IP:Port@Gen /// public partial class ZooKeeperBasedMembershipTable : IMembershipTable { private readonly ILogger logger; private const int ZOOKEEPER_CONNECTION_TIMEOUT = 2000; private readonly ZooKeeperWatcher watcher; /// /// The deployment connection string. for eg. "192.168.1.1,192.168.1.2/ClusterId" /// private readonly string deploymentConnectionString; /// /// the node name for this deployment. for eg. /ClusterId /// private readonly string clusterPath; /// /// The root connection string. for eg. "192.168.1.1,192.168.1.2" /// private readonly string rootConnectionString; public ZooKeeperBasedMembershipTable( ILogger logger, IOptions membershipTableOptions, IOptions clusterOptions) { this.logger = logger; var options = membershipTableOptions.Value; watcher = new ZooKeeperWatcher(logger); this.clusterPath = "/" + clusterOptions.Value.ClusterId; rootConnectionString = options.ConnectionString; deploymentConnectionString = options.ConnectionString + this.clusterPath; } /// /// Initializes the ZooKeeper based membership table. /// /// if set to true, we'll try to create a node named "/ClusterId" /// public async Task InitializeMembershipTable(bool tryInitPath) { // even if I am not the one who created the path, // try to insert an initial path if it is not already there, // so we always have the path, before this silo starts working. // note that when a zookeeper connection adds /ClusterId to the connection string, the nodes are relative await UsingZookeeper(rootConnectionString, async zk => { try { await zk.createAsync(this.clusterPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); await zk.sync(this.clusterPath); //if we got here we know that we've just created the deployment path with version=0 LogInformationCreatedNewDeploymentPath(this.clusterPath); } catch (KeeperException.NodeExistsException) { LogDebugDeploymentPathAlreadyExists(this.clusterPath); } }); } /// /// Atomically reads the Membership Table information about a given silo. /// The returned MembershipTableData includes one MembershipEntry entry for a given silo and the /// TableVersion for this table. The MembershipEntry and the TableVersion have to be read atomically. /// /// The address of the silo whose membership information needs to be read. /// The membership information for a given silo: MembershipTableData consisting one MembershipEntry entry and /// TableVersion, read atomically. public Task ReadRow(SiloAddress siloAddress) { return UsingZookeeper(async zk => { var getRowTask = GetRow(zk, siloAddress); var getTableNodeTask = zk.getDataAsync("/");//get the current table version List> rows = new List>(1); try { await Task.WhenAll(getRowTask, getTableNodeTask); rows.Add(await getRowTask); } catch (KeeperException.NoNodeException) { //that's ok because orleans expects an empty list in case of a missing row } var tableVersion = ConvertToTableVersion((await getTableNodeTask).Stat); return new MembershipTableData(rows, tableVersion); }, this.deploymentConnectionString, this.watcher, true); } /// /// Atomically reads the full content of the Membership Table. /// The returned MembershipTableData includes all MembershipEntry entry for all silos in the table and the /// TableVersion for this table. The MembershipEntries and the TableVersion have to be read atomically. /// /// The membership information for a given table: MembershipTableData consisting multiple MembershipEntry entries and /// TableVersion, all read atomically. public Task ReadAll() { return ReadAll(this.deploymentConnectionString, this.watcher); } internal static Task ReadAll(string deploymentConnectionString, ZooKeeperWatcher watcher) { return UsingZookeeper(async zk => { var childrenResult = await zk.getChildrenAsync("/");//get all the child nodes (without the data) var childrenTasks = //get the data from each child node childrenResult.Children.Select(child => GetRow(zk, SiloAddress.FromParsableString(child))).ToList(); var childrenTaskResults = await Task.WhenAll(childrenTasks); var tableVersion = ConvertToTableVersion(childrenResult.Stat);//this is the current table version return new MembershipTableData(childrenTaskResults.ToList(), tableVersion); }, deploymentConnectionString, watcher, true); } /// /// Atomically tries to insert (add) a new MembershipEntry for one silo and also update the TableVersion. /// If operation succeeds, the following changes would be made to the table: /// 1) New MembershipEntry will be added to the table. /// 2) The newly added MembershipEntry will also be added with the new unique automatically generated eTag. /// 3) TableVersion.Version in the table will be updated to the new TableVersion.Version. /// 4) TableVersion etag in the table will be updated to the new unique automatically generated eTag. /// All those changes to the table, insert of a new row and update of the table version and the associated etags, should happen atomically, or fail atomically with no side effects. /// The operation should fail in each of the following conditions: /// 1) A MembershipEntry for a given silo already exist in the table /// 2) Update of the TableVersion failed since the given TableVersion etag (as specified by the TableVersion.VersionEtag property) did not match the TableVersion etag in the table. /// /// MembershipEntry to be inserted. /// The new TableVersion for this table, along with its etag. /// True if the insert operation succeeded and false otherwise. public Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { string rowPath = ConvertToRowPath(entry.SiloAddress); string rowIAmAlivePath = ConvertToRowIAmAlivePath(entry.SiloAddress); byte[] newRowData = Serialize(entry); byte[] newRowIAmAliveData = Serialize(entry.IAmAliveTime); int expectedTableVersion = int.Parse(tableVersion.VersionEtag, CultureInfo.InvariantCulture); return TryTransaction(t => t .setData("/", null, expectedTableVersion)//increments the version of node "/" .create(rowPath, newRowData, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) .create(rowIAmAlivePath, newRowIAmAliveData, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)); } /// /// Atomically tries to update the MembershipEntry for one silo and also update the TableVersion. /// If operation succeeds, the following changes would be made to the table: /// 1) The MembershipEntry for this silo will be updated to the new MembershipEntry (the old entry will be fully substituted by the new entry) /// 2) The eTag for the updated MembershipEntry will also be eTag with the new unique automatically generated eTag. /// 3) TableVersion.Version in the table will be updated to the new TableVersion.Version. /// 4) TableVersion etag in the table will be updated to the new unique automatically generated eTag. /// All those changes to the table, update of a new row and update of the table version and the associated etags, should happen atomically, or fail atomically with no side effects. /// The operation should fail in each of the following conditions: /// 1) A MembershipEntry for a given silo does not exist in the table /// 2) A MembershipEntry for a given silo exist in the table but its etag in the table does not match the provided etag. /// 3) Update of the TableVersion failed since the given TableVersion etag (as specified by the TableVersion.VersionEtag property) did not match the TableVersion etag in the table. /// /// MembershipEntry to be updated. /// The etag for the given MembershipEntry. /// The new TableVersion for this table, along with its etag. /// True if the update operation succeeded and false otherwise. public Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { string rowPath = ConvertToRowPath(entry.SiloAddress); string rowIAmAlivePath = ConvertToRowIAmAlivePath(entry.SiloAddress); var newRowData = Serialize(entry); var newRowIAmAliveData = Serialize(entry.IAmAliveTime); int expectedTableVersion = int.Parse(tableVersion.VersionEtag, CultureInfo.InvariantCulture); int expectedRowVersion = int.Parse(etag, CultureInfo.InvariantCulture); return TryTransaction(t => t .setData("/", null, expectedTableVersion)//increments the version of node "/" .setData(rowPath, newRowData, expectedRowVersion)//increments the version of node "/IP:Port@Gen" .setData(rowIAmAlivePath, newRowIAmAliveData)); } /// /// Updates the IAmAlive part (column) of the MembershipEntry for this silo. /// This operation should only update the IAmAlive column and not change other columns. /// This operation is a "dirty write" or "in place update" and is performed without etag validation. /// With regards to eTags update: /// This operation may automatically update the eTag associated with the given silo row, but it does not have to. It can also leave the etag not changed ("dirty write"). /// With regards to TableVersion: /// this operation should not change the TableVersion of the table. It should leave it untouched. /// There is no scenario where this operation could fail due to table semantical reasons. It can only fail due to network problems or table unavailability. /// /// The target MembershipEntry tp update /// Task representing the successful execution of this operation. public Task UpdateIAmAlive(MembershipEntry entry) { string rowIAmAlivePath = ConvertToRowIAmAlivePath(entry.SiloAddress); byte[] newRowIAmAliveData = Serialize(entry.IAmAliveTime); //update the data for IAmAlive unconditionally return UsingZookeeper(zk => zk.setDataAsync(rowIAmAlivePath, newRowIAmAliveData), this.deploymentConnectionString, this.watcher); } /// /// Deletes all table entries of the given clusterId /// public Task DeleteMembershipTableEntries(string clusterId) { string pathToDelete = "/" + clusterId; return UsingZookeeper(rootConnectionString, async zk => { await ZKUtil.deleteRecursiveAsync(zk, pathToDelete); await zk.sync(pathToDelete); }); } private async Task TryTransaction(Func transactionFunc) { try { await UsingZookeeper(zk => transactionFunc(zk.transaction()).commitAsync(), this.deploymentConnectionString, this.watcher); return true; } catch (KeeperException e) { //these exceptions are thrown when the transaction fails to commit due to semantical reasons if (e is KeeperException.NodeExistsException || e is KeeperException.NoNodeException || e is KeeperException.BadVersionException) { return false; } throw; } } /// /// Reads the nodes /IP:Port@Gen and /IP:Port@Gen/IAmAlive (which together is one row) /// /// The zookeeper instance used for the read /// The silo address. private static async Task> GetRow(ZooKeeper zk, SiloAddress siloAddress) { string rowPath = ConvertToRowPath(siloAddress); string rowIAmAlivePath = ConvertToRowIAmAlivePath(siloAddress); var rowDataTask = zk.getDataAsync(rowPath); var rowIAmAliveDataTask = zk.getDataAsync(rowIAmAlivePath); await Task.WhenAll(rowDataTask, rowIAmAliveDataTask); MembershipEntry me = Deserialize((await rowDataTask).Data); me.IAmAliveTime = Deserialize((await rowIAmAliveDataTask).Data); int rowVersion = (await rowDataTask).Stat.getVersion(); return new Tuple(me, rowVersion.ToString(CultureInfo.InvariantCulture)); } private static Task UsingZookeeper(Func> zkMethod, string deploymentConnectionString, ZooKeeperWatcher watcher, bool canBeReadOnly = false) { return ZooKeeper.Using(deploymentConnectionString, ZOOKEEPER_CONNECTION_TIMEOUT, watcher, zkMethod, canBeReadOnly); } private Task UsingZookeeper(string connectString, Func zkMethod) { return ZooKeeper.Using(connectString, ZOOKEEPER_CONNECTION_TIMEOUT, watcher, zkMethod); } private static string ConvertToRowPath(SiloAddress siloAddress) { return "/" + siloAddress.ToParsableString(); } private static string ConvertToRowIAmAlivePath(SiloAddress siloAddress) { return ConvertToRowPath(siloAddress) + "/IAmAlive"; } private static TableVersion ConvertToTableVersion(Stat stat) { int version = stat.getVersion(); return new TableVersion(version, version.ToString(CultureInfo.InvariantCulture)); } private static byte[] Serialize(object obj) { return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj, Formatting.None, MembershipSerializerSettings.Instance)); } private static T Deserialize(byte[] data) { return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data), MembershipSerializerSettings.Instance); } public Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { throw new NotImplementedException(); } [LoggerMessage( Level = LogLevel.Information, Message = "Created new deployment path: {DeploymentPath}" )] private partial void LogInformationCreatedNewDeploymentPath(string deploymentPath); [LoggerMessage( Level = LogLevel.Debug, Message = "Deployment path already exists: {DeploymentPath}" )] private partial void LogDebugDeploymentPathAlreadyExists(string deploymentPath); } /// /// the state of every ZooKeeper client and its push notifications are published using watchers. /// in orleans the watcher is only for debugging purposes /// internal partial class ZooKeeperWatcher : Watcher { private readonly ILogger logger; public ZooKeeperWatcher(ILogger logger) { this.logger = logger; } public override Task process(WatchedEvent @event) { LogDebugWatchedEvent(@event); return Task.CompletedTask; } [LoggerMessage( Level = LogLevel.Debug, Message = "{EventString}" )] private partial void LogDebugWatchedEvent(WatchedEvent eventString); } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/ZooKeeperClusteringProviderBuilder.cs ================================================ using System; using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; [assembly: RegisterProvider("ZooKeeper", "Clustering", "Client", typeof(ZooKeeperClusteringProviderBuilder))] [assembly: RegisterProvider("ZooKeeper", "Clustering", "Silo", typeof(ZooKeeperClusteringProviderBuilder))] namespace Orleans.Hosting; internal sealed class ZooKeeperClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseZooKeeperClustering(options => options.Bind(configurationSection)); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseZooKeeperClustering(options => options.Bind(configurationSection)); } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/ZooKeeperGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Messaging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Membership { public class ZooKeeperGatewayListProvider : IGatewayListProvider { private readonly ZooKeeperWatcher _watcher; /// /// the node name for this deployment. for eg. /ClusterId /// private readonly string _deploymentPath; /// /// The deployment connection string. for eg. "192.168.1.1,192.168.1.2/ClusterId" /// private readonly string _deploymentConnectionString; private readonly TimeSpan _maxStaleness; public ZooKeeperGatewayListProvider( ILogger logger, IOptions options, IOptions gatewayOptions, IOptions clusterOptions) { _watcher = new ZooKeeperWatcher(logger); _deploymentPath = "/" + clusterOptions.Value.ClusterId; _deploymentConnectionString = options.Value.ConnectionString + _deploymentPath; _maxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; } /// /// Initializes the ZooKeeper based gateway provider /// public Task InitializeGatewayListProvider() => Task.CompletedTask; /// /// Returns the list of gateways (silos) that can be used by a client to connect to Orleans cluster. /// The Uri is in the form of: "gwy.tcp://IP:port/Generation". See Utils.ToGatewayUri and Utils.ToSiloAddress for more details about Uri format. /// public async Task> GetGateways() { var membershipTableData = await ZooKeeperBasedMembershipTable.ReadAll(this._deploymentConnectionString, this._watcher); return membershipTableData.Members.Select(e => e.Item1). Where(m => m.Status == SiloStatus.Active && m.ProxyPort != 0). Select(m => { var gatewayAddress = SiloAddress.New(m.SiloAddress.Endpoint.Address, m.ProxyPort, m.SiloAddress.Generation); return gatewayAddress.ToGatewayUri(); }).ToList(); } /// /// Specifies how often this IGatewayListProvider is refreshed, to have a bound on max staleness of its returned information. /// public TimeSpan MaxStaleness => _maxStaleness; /// /// Specifies whether this IGatewayListProvider ever refreshes its returned information, or always returns the same gw list. /// (currently only the static config based StaticGatewayListProvider is not updatable. All others are.) /// public bool IsUpdatable => true; } } ================================================ FILE: src/Orleans.Clustering.ZooKeeper/ZooKeeperHostingExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Messaging; using Orleans.Runtime.Membership; using Orleans.Configuration; namespace Orleans.Hosting { public static class ZooKeeperHostingExtensions { /// /// Configures the silo to use ZooKeeper for cluster membership. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseZooKeeperClustering( this ISiloBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configures the silo to use ZooKeeper for cluster membership. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static ISiloBuilder UseZooKeeperClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } /// /// Configure the client to use ZooKeeper for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseZooKeeperClustering( this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices( services => { if (configureOptions != null) { services.Configure(configureOptions); } services.AddSingleton(); }); } /// /// Configure the client to use ZooKeeper for clustering. /// /// /// The builder. /// /// /// The configuration delegate. /// /// /// The provided . /// public static IClientBuilder UseZooKeeperClustering( this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.AddSingleton(); }); } } } ================================================ FILE: src/Orleans.CodeGenerator/ActivatorGenerator.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using System.Collections.Generic; namespace Orleans.CodeGenerator { internal class ActivatorGenerator { private readonly CodeGenerator _codeGenerator; private struct ConstructorArgument { public TypeSyntax Type { get; set; } public string FieldName { get; set; } public string ParameterName { get; set; } } public ActivatorGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } public ClassDeclarationSyntax GenerateActivator(ISerializableTypeDescription type) { var simpleClassName = GetSimpleClassName(type); var baseInterface = _codeGenerator.LibraryTypes.IActivator_1.ToTypeSyntax(type.TypeSyntax); var orderedFields = new List(); var index = 0; if (type.ActivatorConstructorParameters is { Count: > 0 } parameters) { foreach (var arg in parameters) { orderedFields.Add(new ConstructorArgument { Type = arg, FieldName = $"_arg{index}", ParameterName = $"arg{index}" }); index++; } } var members = new List(); foreach (var field in orderedFields) { members.Add( FieldDeclaration(VariableDeclaration(field.Type, SingletonSeparatedList(VariableDeclarator(field.FieldName)))) .AddModifiers( Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword))); } if (orderedFields.Count > 0) members.Add(GenerateConstructor(simpleClassName, orderedFields)); members.Add(GenerateCreateMethod(type, orderedFields)); var classDeclaration = ClassDeclaration(simpleClassName) .AddBaseListTypes(SimpleBaseType(baseInterface)) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()) .AddMembers(members.ToArray()); if (type.IsGenericType) { classDeclaration = SyntaxFactoryUtility.AddGenericTypeParameters(classDeclaration, type.TypeParameters); } return classDeclaration; } public static string GetSimpleClassName(ISerializableTypeDescription serializableType) => $"Activator_{serializableType.Name}"; private ConstructorDeclarationSyntax GenerateConstructor( string simpleClassName, List orderedFields) { var parameters = new List(); var body = new List(); foreach (var field in orderedFields) { parameters.Add(Parameter(field.ParameterName.ToIdentifier()).WithType(field.Type)); body.Add(ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, field.FieldName.ToIdentifierName(), Unwrapped(field.ParameterName.ToIdentifierName())))); } var constructorDeclaration = ConstructorDeclaration(simpleClassName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters.ToArray()) .AddBodyStatements(body.ToArray()); return constructorDeclaration; static ExpressionSyntax Unwrapped(ExpressionSyntax expr) { return InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("OrleansGeneratedCodeHelper"), IdentifierName("UnwrapService")), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(expr) }))); } } private MemberDeclarationSyntax GenerateCreateMethod(ISerializableTypeDescription type, List orderedFields) { ExpressionSyntax createObject; if (type.ActivatorConstructorParameters is { Count: > 0 }) { var argList = new List(); foreach (var field in orderedFields) { argList.Add(Argument(field.FieldName.ToIdentifierName())); } createObject = ObjectCreationExpression(type.TypeSyntax).WithArgumentList(ArgumentList(SeparatedList(argList))); } else { createObject = type.GetObjectCreationExpression(); } return MethodDeclaration(type.TypeSyntax, "Create") .WithExpressionBody(ArrowExpressionClause(createObject)) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .AddModifiers(Token(SyntaxKind.PublicKeyword)); } } } ================================================ FILE: src/Orleans.CodeGenerator/AnalyzerReleases.Shipped.md ================================================ ## Release 7.0.0 ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- ORLEANS0101 | Usage | Error | Serializable properties with bodies must be settable ORLEANS0102 | Usage | Error | Invalid return type for RPC interface method ORLEANS0103 | Usage | Error | An unhandled source generation exception occurred ORLEANS0104 | Usage | Error | The proxy base class specified is not a valid proxy base class ORLEANS0105 | Usage | Error | RPC interfaces must not contain properties ORLEANS0106 | Usage | Error | An error is preventing implicit field identifier generation ORLEANS0107 | Usage | Error | Serializable type is not accessible from generated code ORLEANS0108 | Usage | Error | No declaring assembly for type passed to GenerateCodeForDeclaringAssemblyAttribute ================================================ FILE: src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md ================================================ ; Unshipped analyzer release ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- ORLEANS0109 | Usage | Error | Method has multiple CancellationToken parameters ORLEANS0110 | Usage | Error | ReferenceAssemblyWithGenerateSerializerDiagnostic ================================================ FILE: src/Orleans.CodeGenerator/ApplicationPartAttributeGenerator.cs ================================================ using System.Collections.Generic; using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal static class ApplicationPartAttributeGenerator { public static List GenerateSyntax(LibraryTypes wellKnownTypes, MetadataModel model) { var attributes = new List(); foreach (var assemblyName in model.ApplicationParts) { // Generate an assembly-level attribute with an instance of that class. var attribute = AttributeList( AttributeTargetSpecifier(Token(SyntaxKind.AssemblyKeyword)), SingletonSeparatedList( Attribute(wellKnownTypes.ApplicationPartAttribute.ToNameSyntax()) .AddArgumentListArguments(AttributeArgument(assemblyName.GetLiteralExpression())))); attributes.Add(attribute); } return attributes; } } } ================================================ FILE: src/Orleans.CodeGenerator/CodeGenerator.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.Diagnostics; using Orleans.CodeGenerator.Hashing; using Orleans.CodeGenerator.Model; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Orleans.CodeGenerator.SyntaxGeneration.SymbolExtensions; namespace Orleans.CodeGenerator { public class CodeGeneratorOptions { public const string IdAttribute = "Orleans.IdAttribute"; public const string AliasAttribute = "Orleans.AliasAttribute"; public const string ImmutableAttribute = "Orleans.ImmutableAttribute"; public static readonly IReadOnlyList ConstructorAttributes = ["Orleans.OrleansConstructorAttribute", "Microsoft.Extensions.DependencyInjection.ActivatorUtilitiesConstructorAttribute"]; public GenerateFieldIds GenerateFieldIds { get; set; } public bool GenerateCompatibilityInvokers { get; set; } } public class CodeGenerator { internal const string CodeGeneratorName = "OrleansCodeGen"; private readonly Dictionary> _namespacedMembers = new(); private readonly Dictionary _invokableMethodDescriptions = new(); private readonly HashSet _visitedInterfaces = new(SymbolEqualityComparer.Default); private readonly List DisabledWarnings = new() { "CS1591", "RS0016", "RS0041" }; public CodeGenerator(Compilation compilation, CodeGeneratorOptions options) { Compilation = compilation; Options = options; LibraryTypes = LibraryTypes.FromCompilation(compilation, options); MetadataModel = new MetadataModel(); CopierGenerator = new CopierGenerator(this); SerializerGenerator = new SerializerGenerator(this); ProxyGenerator = new ProxyGenerator(this); InvokableGenerator = new InvokableGenerator(this); MetadataGenerator = new MetadataGenerator(this); ActivatorGenerator = new ActivatorGenerator(this); } public Compilation Compilation { get; } public CodeGeneratorOptions Options { get; } internal LibraryTypes LibraryTypes { get; } internal MetadataModel MetadataModel { get; } internal CopierGenerator CopierGenerator { get; } internal SerializerGenerator SerializerGenerator { get; } internal ProxyGenerator ProxyGenerator { get; } internal InvokableGenerator InvokableGenerator { get; } internal MetadataGenerator MetadataGenerator { get; } internal ActivatorGenerator ActivatorGenerator { get; } public CompilationUnitSyntax GenerateCode(CancellationToken cancellationToken) { var assembliesToExamine = new HashSet(SymbolEqualityComparer.Default); var compilationAsm = LibraryTypes.Compilation.Assembly; ComputeAssembliesToExamine(compilationAsm, assembliesToExamine); // Expand the set of referenced assemblies MetadataModel.ApplicationParts.Add(compilationAsm.MetadataName); foreach (var reference in LibraryTypes.Compilation.References) { if (LibraryTypes.Compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol asm) { continue; } if (asm.GetAttributes(LibraryTypes.ApplicationPartAttribute, out var attrs)) { MetadataModel.ApplicationParts.Add(asm.MetadataName); foreach (var attr in attrs) { MetadataModel.ApplicationParts.Add((string)attr.ConstructorArguments.First().Value); } } } // The mapping of proxy base types to a mapping of return types to invokable base types. Used to set default invokable base types for each proxy base type. var proxyBaseTypeInvokableBaseTypes = new Dictionary>(SymbolEqualityComparer.Default); foreach (var asm in assembliesToExamine) { var containingAssemblyAttributes = asm.GetAttributes(); foreach (var symbol in asm.GetDeclaredTypes()) { if (GetWellKnownTypeId(symbol) is uint wellKnownTypeId) { MetadataModel.WellKnownTypeIds.Add((symbol.ToOpenTypeSyntax(), wellKnownTypeId)); } if (GetAlias(symbol) is string typeAlias) { MetadataModel.TypeAliases.Add((symbol.ToOpenTypeSyntax(), typeAlias)); } if (GetCompoundTypeAlias(symbol) is CompoundTypeAliasComponent[] compoundTypeAlias) { MetadataModel.CompoundTypeAliases.Add(compoundTypeAlias, symbol.ToOpenTypeSyntax()); } if (FSharpUtilities.IsUnionCase(LibraryTypes, symbol, out var sumType) && ShouldGenerateSerializer(sumType)) { if (!Compilation.IsSymbolAccessibleWithin(sumType, Compilation.Assembly)) { throw new OrleansGeneratorDiagnosticAnalysisException(InaccessibleSerializableTypeDiagnostic.CreateDiagnostic(sumType)); } var typeDescription = new FSharpUtilities.FSharpUnionCaseTypeDescription(Compilation, symbol, LibraryTypes); MetadataModel.SerializableTypes.Add(typeDescription); } else if (ShouldGenerateSerializer(symbol)) { // https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.referenceassemblyattribute if (containingAssemblyAttributes.Any(attributeData => attributeData.AttributeClass is { Name: "ReferenceAssemblyAttribute", ContainingNamespace: { Name: "CompilerServices", ContainingNamespace: { Name: "Runtime", ContainingNamespace: { Name: "System", ContainingNamespace.IsGlobalNamespace: true } } } })) { // not ALWAYS will be properly processed, therefore emit a warning throw new OrleansGeneratorDiagnosticAnalysisException(ReferenceAssemblyWithGenerateSerializerDiagnostic.CreateDiagnostic(symbol)); } if (!Compilation.IsSymbolAccessibleWithin(symbol, Compilation.Assembly)) { throw new OrleansGeneratorDiagnosticAnalysisException(InaccessibleSerializableTypeDiagnostic.CreateDiagnostic(symbol)); } if (FSharpUtilities.IsRecord(LibraryTypes, symbol)) { var typeDescription = new FSharpUtilities.FSharpRecordTypeDescription(Compilation, symbol, LibraryTypes); MetadataModel.SerializableTypes.Add(typeDescription); } else { // Regular type var includePrimaryConstructorParameters = ShouldIncludePrimaryConstructorParameters(symbol); var constructorParameters = ImmutableArray.Empty; if (includePrimaryConstructorParameters) { if (symbol.IsRecord) { // If there is a primary constructor then that will be declared before the copy constructor // A record always generates a copy constructor and marks it as compiler generated // todo: find an alternative to this magic var potentialPrimaryConstructor = symbol.Constructors[0]; if (!potentialPrimaryConstructor.IsImplicitlyDeclared && !potentialPrimaryConstructor.IsCompilerGenerated()) { constructorParameters = potentialPrimaryConstructor.Parameters; } } else { var annotatedConstructors = symbol.Constructors.Where(ctor => ctor.HasAnyAttribute(LibraryTypes.ConstructorAttributeTypes)).ToList(); if (annotatedConstructors.Count == 1) { constructorParameters = annotatedConstructors[0].Parameters; } else { // record structs from referenced assemblies do not return IsRecord=true // above. See https://github.com/dotnet/roslyn/issues/69326 // So we implement the same heuristics from ShouldIncludePrimaryConstructorParameters // to detect a primary constructor. var properties = symbol.GetMembers().OfType().ToImmutableArray(); var primaryConstructor = symbol.GetMembers() .OfType() .Where(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0) // Check for a ctor where all parameters have a corresponding compiler-generated prop. .FirstOrDefault(ctor => ctor.Parameters.All(prm => properties.Any(prop => prop.Name.Equals(prm.Name, StringComparison.Ordinal) && prop.IsCompilerGenerated()))); if (primaryConstructor != null) constructorParameters = primaryConstructor.Parameters; } } } var implicitMemberSelectionStrategy = (Options.GenerateFieldIds, GetGenerateFieldIdsOptionFromType(symbol)) switch { (_, GenerateFieldIds.PublicProperties) => GenerateFieldIds.PublicProperties, (GenerateFieldIds.PublicProperties, _) => GenerateFieldIds.PublicProperties, _ => GenerateFieldIds.None }; var fieldIdAssignmentHelper = new FieldIdAssignmentHelper(symbol, constructorParameters, implicitMemberSelectionStrategy, LibraryTypes); if (!fieldIdAssignmentHelper.IsValidForSerialization) { throw new OrleansGeneratorDiagnosticAnalysisException(CanNotGenerateImplicitFieldIdsDiagnostic.CreateDiagnostic(symbol, fieldIdAssignmentHelper.FailureReason)); } var typeDescription = new SerializableTypeDescription(Compilation, symbol, includePrimaryConstructorParameters, GetDataMembers(fieldIdAssignmentHelper), LibraryTypes); MetadataModel.SerializableTypes.Add(typeDescription); } } if (symbol.TypeKind == TypeKind.Interface) { VisitInterface(symbol.OriginalDefinition); } if ((symbol.TypeKind == TypeKind.Class || symbol.TypeKind == TypeKind.Struct) && !symbol.IsAbstract && (symbol.DeclaredAccessibility == Accessibility.Public || symbol.DeclaredAccessibility == Accessibility.Internal)) { if (symbol.HasAttribute(LibraryTypes.RegisterSerializerAttribute)) { MetadataModel.DetectedSerializers.Add(symbol); } if (symbol.HasAttribute(LibraryTypes.RegisterActivatorAttribute)) { MetadataModel.DetectedActivators.Add(symbol); } if (symbol.HasAttribute(LibraryTypes.RegisterCopierAttribute)) { MetadataModel.DetectedCopiers.Add(symbol); } if (symbol.HasAttribute(LibraryTypes.RegisterConverterAttribute)) { MetadataModel.DetectedConverters.Add(symbol); } // Find all implementations of invokable interfaces foreach (var iface in symbol.AllInterfaces) { var attribute = iface.GetAttribute( LibraryTypes.GenerateMethodSerializersAttribute, inherited: true); if (attribute != null) { MetadataModel.InvokableInterfaceImplementations.Add(symbol); break; } } } GenerateFieldIds GetGenerateFieldIdsOptionFromType(INamedTypeSymbol t) { var attribute = t.GetAttribute(LibraryTypes.GenerateSerializerAttribute); if (attribute is null) return GenerateFieldIds.None; foreach (var namedArgument in attribute.NamedArguments) { if (namedArgument.Key == "GenerateFieldIds") { var value = namedArgument.Value.Value; return value == null ? GenerateFieldIds.None : (GenerateFieldIds)(int)value; } } return GenerateFieldIds.None; } bool ShouldGenerateSerializer(INamedTypeSymbol t) => t.HasAttribute(LibraryTypes.GenerateSerializerAttribute); bool ShouldIncludePrimaryConstructorParameters(INamedTypeSymbol t) { static bool? TestGenerateSerializerAttribute(INamedTypeSymbol t, INamedTypeSymbol at) { var attribute = t.GetAttribute(at); if (attribute != null) { foreach (var namedArgument in attribute.NamedArguments) { if (namedArgument.Key == "IncludePrimaryConstructorParameters") { if (namedArgument.Value.Kind == TypedConstantKind.Primitive && namedArgument.Value.Value is bool b) { return b; } } } } // If there is no such named argument, return null so that other attributes have a chance to apply and defaults can be applied. return null; } if (TestGenerateSerializerAttribute(t, LibraryTypes.GenerateSerializerAttribute) is bool res) { return res; } // Default to true for records. if (t.IsRecord) return true; var properties = t.GetMembers().OfType().ToImmutableArray(); return t.GetMembers() .OfType() .Where(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0) // Check for a ctor where all parameters have a corresponding compiler-generated prop. .Any(ctor => ctor.Parameters.All(prm => properties.Any(prop => prop.Name.Equals(prm.Name, StringComparison.Ordinal) && prop.IsCompilerGenerated()))); } } } // Generate serializers. foreach (var type in MetadataModel.SerializableTypes) { string ns = type.GeneratedNamespace; // Generate a partial serializer class for each serializable type. var serializer = SerializerGenerator.Generate(type); AddMember(ns, serializer); // Generate a copier for each serializable type. if (CopierGenerator.GenerateCopier(type, MetadataModel.DefaultCopiers) is { } copier) AddMember(ns, copier); if (!type.IsAbstractType && !type.IsEnumType && (!type.IsValueType && type.IsEmptyConstructable && !type.UseActivator && type is not GeneratedInvokableDescription || type.HasActivatorConstructor)) { MetadataModel.ActivatableTypes.Add(type); // Generate an activator class for types with default constructor or activator constructor. var activator = ActivatorGenerator.GenerateActivator(type); AddMember(ns, activator); } } // Generate metadata. var metadataClassNamespace = CodeGeneratorName + "." + SyntaxGeneration.Identifier.SanitizeIdentifierName(Compilation.AssemblyName); var metadataClass = MetadataGenerator.GenerateMetadata(); AddMember(ns: metadataClassNamespace, member: metadataClass); var metadataAttribute = AttributeList() .WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.AssemblyKeyword))) .WithAttributes( SingletonSeparatedList( Attribute(LibraryTypes.TypeManifestProviderAttribute.ToNameSyntax()) .AddArgumentListArguments(AttributeArgument(TypeOfExpression(QualifiedName(IdentifierName(metadataClassNamespace), IdentifierName(metadataClass.Identifier.Text))))))); var assemblyAttributes = ApplicationPartAttributeGenerator.GenerateSyntax(LibraryTypes, MetadataModel); assemblyAttributes.Add(metadataAttribute); if (assemblyAttributes.Count > 0) { assemblyAttributes[0] = assemblyAttributes[0] .WithLeadingTrivia( SyntaxFactory.TriviaList( new List { Trivia( PragmaWarningDirectiveTrivia( Token(SyntaxKind.DisableKeyword), SeparatedList(DisabledWarnings.Select(str => { var syntaxToken = SyntaxFactory.Literal( SyntaxFactory.TriviaList(), str, str, SyntaxFactory.TriviaList()); return (ExpressionSyntax)SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, syntaxToken); })), isActive: true)), })); } var usings = List(new[] { UsingDirective(ParseName("global::Orleans.Serialization.Codecs")), UsingDirective(ParseName("global::Orleans.Serialization.GeneratedCodeHelpers")) }); var namespaces = new List(_namespacedMembers.Count); foreach (var pair in _namespacedMembers) { var ns = pair.Key; var member = pair.Value; namespaces.Add(NamespaceDeclaration(ParseName(ns)).WithMembers(List(member)).WithUsings(usings)); } if (namespaces.Count > 0) { namespaces[namespaces.Count - 1] = namespaces[namespaces.Count - 1] .WithTrailingTrivia( SyntaxFactory.TriviaList( new List { Trivia( PragmaWarningDirectiveTrivia( Token(SyntaxKind.RestoreKeyword), SeparatedList(DisabledWarnings.Select(str => { var syntaxToken = SyntaxFactory.Literal( SyntaxFactory.TriviaList(), str, str, SyntaxFactory.TriviaList()); return (ExpressionSyntax)SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, syntaxToken); })), isActive: true)), })); } return CompilationUnit() .WithAttributeLists(List(assemblyAttributes)) .WithMembers(List(namespaces)); } public static string GetGeneratedNamespaceName(ITypeSymbol type) => type.GetNamespaceAndNesting() switch { { Length: > 0 } ns => $"{CodeGeneratorName}.{ns}", _ => CodeGeneratorName }; public void AddMember(string ns, MemberDeclarationSyntax member) { if (!_namespacedMembers.TryGetValue(ns, out var existing)) { existing = _namespacedMembers[ns] = new List(); } existing.Add(member); } private void ComputeAssembliesToExamine(IAssemblySymbol asm, HashSet expandedAssemblies) { if (!expandedAssemblies.Add(asm)) { return; } if (!asm.GetAttributes(LibraryTypes.GenerateCodeForDeclaringAssemblyAttribute, out var attrs)) return; foreach (var attr in attrs) { var param = attr.ConstructorArguments.First(); if (param.Kind != TypedConstantKind.Type) { throw new ArgumentException($"Unrecognized argument type in attribute [{attr.AttributeClass.Name}({param.ToCSharpString()})]"); } var type = (ITypeSymbol)param.Value; // Recurse on the assemblies which the type was declared in. var declaringAsm = type.OriginalDefinition.ContainingAssembly; if (declaringAsm is null) { var diagnostic = GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly_Diagnostic.CreateDiagnostic(attr, type); throw new OrleansGeneratorDiagnosticAnalysisException(diagnostic); } else { ComputeAssembliesToExamine(declaringAsm, expandedAssemblies); } } } // Returns descriptions of all data members (fields and properties) private static IEnumerable GetDataMembers(FieldIdAssignmentHelper fieldIdAssignmentHelper) { var members = new Dictionary<(uint, bool), IMemberDescription>(); foreach (var member in fieldIdAssignmentHelper.Members) { if (!fieldIdAssignmentHelper.TryGetSymbolKey(member, out var key)) continue; var (id, isConstructorParameter) = key; // FieldDescription takes precedence over PropertyDescription (never replace) if (member is IPropertySymbol property && !members.TryGetValue((id, isConstructorParameter), out _)) { members[(id, isConstructorParameter)] = new PropertyDescription(id, isConstructorParameter, property); } if (member is IFieldSymbol field) { // FieldDescription takes precedence over PropertyDescription (add or replace) if (!members.TryGetValue((id, isConstructorParameter), out var existing) || existing is PropertyDescription) { members[(id, isConstructorParameter)] = new FieldDescription(id, isConstructorParameter, field); } } } return members.Values; } public uint? GetId(ISymbol memberSymbol) => GetId(LibraryTypes, memberSymbol); internal static uint? GetId(LibraryTypes libraryTypes, ISymbol memberSymbol) { return memberSymbol.GetAttribute(libraryTypes.IdAttributeType) is { } attr ? (uint)attr.ConstructorArguments.First().Value : null; } internal static string CreateHashedMethodId(IMethodSymbol methodSymbol) { var methodSignature = Format(methodSymbol); var hash = XxHash32.Hash(Encoding.UTF8.GetBytes(methodSignature)); return $"{HexConverter.ToString(hash)}"; static string Format(IMethodSymbol methodInfo) { var result = new StringBuilder(); result.Append(methodInfo.ContainingType.ToDisplayName()); result.Append('.'); result.Append(methodInfo.Name); if (methodInfo.IsGenericMethod) { result.Append('<'); var first = true; foreach (var typeArgument in methodInfo.TypeArguments) { if (!first) result.Append(','); else first = false; result.Append(typeArgument.Name); } result.Append('>'); } { result.Append('('); var parameters = methodInfo.Parameters; var first = true; foreach (var parameter in parameters) { if (!first) { result.Append(','); } var parameterType = parameter.Type; switch (parameterType) { case ITypeParameterSymbol _: result.Append(parameterType.Name); break; default: result.Append(parameterType.ToDisplayName()); break; } first = false; } } result.Append(')'); return result.ToString(); } } private uint? GetWellKnownTypeId(ISymbol symbol) => GetId(symbol); public string GetAlias(ISymbol symbol) => (string)symbol.GetAttribute(LibraryTypes.AliasAttribute)?.ConstructorArguments.First().Value; private CompoundTypeAliasComponent[] GetCompoundTypeAlias(ISymbol symbol) { var attr = symbol.GetAttribute(LibraryTypes.CompoundTypeAliasAttribute); if (attr is null) { return null; } var allArgs = attr.ConstructorArguments; if (allArgs.Length != 1 || allArgs[0].Values.Length == 0) { throw new ArgumentException($"Unsupported arguments in attribute [{attr.AttributeClass.Name}({string.Join(", ", allArgs.Select(a => a.ToCSharpString()))})]"); } var args = allArgs[0].Values; var result = new CompoundTypeAliasComponent[args.Length]; for (var i = 0; i < args.Length; i++) { var arg = args[i]; if (arg.IsNull) { throw new ArgumentNullException($"Unsupported null argument in attribute [{attr.AttributeClass.Name}({string.Join(", ", allArgs.Select(a => a.ToCSharpString()))})]"); } result[i] = arg.Value switch { ITypeSymbol type => new CompoundTypeAliasComponent(type), string str => new CompoundTypeAliasComponent(str), _ => throw new ArgumentException($"Unrecognized argument type for argument {arg.ToCSharpString()} in attribute [{attr.AttributeClass.Name}({string.Join(", ", allArgs.Select(a => a.ToCSharpString()))})]"), }; } return result; } internal static AttributeListSyntax GetGeneratedCodeAttributes() => GeneratedCodeAttributeSyntax; private static readonly AttributeListSyntax GeneratedCodeAttributeSyntax = AttributeList().AddAttributes( Attribute(ParseName("global::System.CodeDom.Compiler.GeneratedCodeAttribute")) .AddArgumentListArguments( AttributeArgument(CodeGeneratorName.GetLiteralExpression()), AttributeArgument(typeof(CodeGenerator).Assembly.GetName().Version.ToString().GetLiteralExpression())), Attribute(ParseName("global::System.ComponentModel.EditorBrowsableAttribute")) .AddArgumentListArguments( AttributeArgument(ParseName("global::System.ComponentModel.EditorBrowsableState").Member("Never"))), Attribute(ParseName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")) ); internal static AttributeSyntax GetMethodImplAttributeSyntax() => MethodImplAttributeSyntax; private static readonly AttributeSyntax MethodImplAttributeSyntax = Attribute(ParseName("global::System.Runtime.CompilerServices.MethodImplAttribute")) .AddArgumentListArguments(AttributeArgument(ParseName("global::System.Runtime.CompilerServices.MethodImplOptions").Member("AggressiveInlining"))); internal void VisitInterface(INamedTypeSymbol interfaceType) { // Get or generate an invokable for the original method definition. if (!SymbolEqualityComparer.Default.Equals(interfaceType, interfaceType.OriginalDefinition)) { interfaceType = interfaceType.OriginalDefinition; } if (!_visitedInterfaces.Add(interfaceType)) { return; } foreach (var proxyBase in GetProxyBases(interfaceType)) { _ = GetInvokableInterfaceDescription(proxyBase.ProxyBaseType, interfaceType); } /* foreach (var baseInterface in interfaceType.AllInterfaces) { VisitInterface(baseInterface); } */ } internal bool TryGetInvokableInterfaceDescription(INamedTypeSymbol interfaceType, out ProxyInterfaceDescription result) { if (!TryGetProxyBaseDescription(interfaceType, out var description)) { result = null; return false; } result = GetInvokableInterfaceDescription(description.ProxyBaseType, interfaceType); return true; } private readonly Dictionary> _interfaceProxyBases = new(SymbolEqualityComparer.Default); internal List GetProxyBases(INamedTypeSymbol interfaceType) { if (_interfaceProxyBases.TryGetValue(interfaceType, out var result)) { return result; } result = new List(); if (interfaceType.GetAttributes(LibraryTypes.GenerateMethodSerializersAttribute, out var attributes, inherited: true)) { foreach (var attribute in attributes) { var proxyBase = GetProxyBaseDescription(attribute); if (!result.Contains(proxyBase)) { result.Add(proxyBase); } } } return result; } internal bool TryGetProxyBaseDescription(INamedTypeSymbol interfaceType, out InvokableMethodProxyBase result) { var attribute = interfaceType.GetAttribute(LibraryTypes.GenerateMethodSerializersAttribute, inherited: true); if (attribute == null) { result = null; return false; } result = GetProxyBaseDescription(attribute); return true; } private InvokableMethodProxyBase GetProxyBaseDescription(AttributeData attribute) { var proxyBaseType = ((INamedTypeSymbol)attribute.ConstructorArguments[0].Value).OriginalDefinition; var isExtension = (bool)attribute.ConstructorArguments[1].Value; var invokableBaseTypes = GetInvokableBaseTypes(proxyBaseType); var descriptor = new InvokableMethodProxyBaseId(proxyBaseType, isExtension); var description = new InvokableMethodProxyBase(this, descriptor, invokableBaseTypes); return description; Dictionary GetInvokableBaseTypes(INamedTypeSymbol baseClass) { // Set the base invokable types which are used if attributes on individual methods do not override them. if (!MetadataModel.ProxyBaseTypeInvokableBaseTypes.TryGetValue(baseClass, out var invokableBaseTypes)) { invokableBaseTypes = new Dictionary(SymbolEqualityComparer.Default); if (baseClass.GetAttributes(LibraryTypes.DefaultInvokableBaseTypeAttribute, out var invokableBaseTypeAttributes)) { foreach (var attr in invokableBaseTypeAttributes) { var ctorArgs = attr.ConstructorArguments; var returnType = (INamedTypeSymbol)ctorArgs[0].Value; var invokableBaseType = (INamedTypeSymbol)ctorArgs[1].Value; invokableBaseTypes[returnType] = invokableBaseType; } } MetadataModel.ProxyBaseTypeInvokableBaseTypes[baseClass] = invokableBaseTypes; } return invokableBaseTypes; } } internal InvokableMethodProxyBase GetProxyBase(INamedTypeSymbol interfaceType) { if (!TryGetProxyBaseDescription(interfaceType, out var result)) { throw new InvalidOperationException($"Cannot get proxy base description for a type which does not have or inherit [{nameof(LibraryTypes.GenerateMethodSerializersAttribute)}]"); } return result; } private ProxyInterfaceDescription GetInvokableInterfaceDescription(INamedTypeSymbol proxyBaseType, INamedTypeSymbol interfaceType) { var originalInterface = interfaceType.OriginalDefinition; if (MetadataModel.InvokableInterfaces.TryGetValue(originalInterface, out var description)) { return description; } description = new ProxyInterfaceDescription(this, proxyBaseType, originalInterface); MetadataModel.InvokableInterfaces.Add(originalInterface, description); // Generate a proxy. var (generatedClass, proxyDescription) = ProxyGenerator.Generate(description); // Emit the generated proxy if (Compilation.GetTypeByMetadataName(proxyDescription.MetadataName) == null) { AddMember(proxyDescription.InterfaceDescription.GeneratedNamespace, generatedClass); } MetadataModel.GeneratedProxies.Add(proxyDescription); return description; } internal ProxyMethodDescription GetProxyMethodDescription(INamedTypeSymbol interfaceType, IMethodSymbol method) { var originalMethod = method.OriginalDefinition; var proxyBaseInfo = GetProxyBase(interfaceType); // For extensions, we want to ensure that the containing type is always the extension. // This ensures that we will always know which 'component' to get in our SetTarget method. // If the type is not an extension, use the original method definition's containing type. // This is the interface where the type was originally defined. var containingType = proxyBaseInfo.IsExtension ? interfaceType : originalMethod.ContainingType; var invokableId = new InvokableMethodId(proxyBaseInfo, containingType, originalMethod); var interfaceDescription = GetInvokableInterfaceDescription(invokableId.ProxyBase.ProxyBaseType, interfaceType); // Get or generate an invokable for the original method definition. if (!MetadataModel.GeneratedInvokables.TryGetValue(invokableId, out var generatedInvokable)) { if (!_invokableMethodDescriptions.TryGetValue(invokableId, out var methodDescription)) { methodDescription = _invokableMethodDescriptions[invokableId] = InvokableMethodDescription.Create(invokableId, containingType); } generatedInvokable = MetadataModel.GeneratedInvokables[invokableId] = InvokableGenerator.Generate(methodDescription); if (Compilation.GetTypeByMetadataName(generatedInvokable.MetadataName) == null) { // Emit the generated code on-demand. AddMember(generatedInvokable.GeneratedNamespace, generatedInvokable.ClassDeclarationSyntax); // Ensure the type will have a serializer generated for it. MetadataModel.SerializableTypes.Add(generatedInvokable); foreach (var alias in generatedInvokable.CompoundTypeAliases) { MetadataModel.CompoundTypeAliases.Add(alias, generatedInvokable.OpenTypeSyntax); } } } var proxyMethodDescription = ProxyMethodDescription.Create(interfaceDescription, generatedInvokable, method); // For backwards compatibility, generate invokers for the specific implementation types as well, where they differ. if (Options.GenerateCompatibilityInvokers && !SymbolEqualityComparer.Default.Equals(method.OriginalDefinition.ContainingType, interfaceType)) { var compatInvokableId = new InvokableMethodId(proxyBaseInfo, interfaceType, method); var compatMethodDescription = InvokableMethodDescription.Create(compatInvokableId, interfaceType); var compatInvokable = InvokableGenerator.Generate(compatMethodDescription); AddMember(compatInvokable.GeneratedNamespace, compatInvokable.ClassDeclarationSyntax); var alias = InvokableGenerator.GetCompoundTypeAliasComponents( compatInvokableId, interfaceType, compatMethodDescription.GeneratedMethodId); MetadataModel.CompoundTypeAliases.Add(alias, compatInvokable.OpenTypeSyntax); } return proxyMethodDescription; } } } ================================================ FILE: src/Orleans.CodeGenerator/CopierGenerator.cs ================================================ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Orleans.CodeGenerator.InvokableGenerator; using static Orleans.CodeGenerator.SerializerGenerator; namespace Orleans.CodeGenerator { internal class CopierGenerator { private const string BaseTypeCopierFieldName = "_baseTypeCopier"; private const string ActivatorFieldName = "_activator"; private const string DeepCopyMethodName = "DeepCopy"; private readonly CodeGenerator _codeGenerator; public CopierGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } private LibraryTypes LibraryTypes => _codeGenerator.LibraryTypes; public ClassDeclarationSyntax GenerateCopier( ISerializableTypeDescription type, Dictionary defaultCopiers) { var isShallowCopyable = type.IsShallowCopyable; if (isShallowCopyable && !type.IsGenericType) { defaultCopiers.Add(type, LibraryTypes.ShallowCopier.ToTypeSyntax(type.TypeSyntax)); return null; } var simpleClassName = GetSimpleClassName(type); var members = new List(); foreach (var member in type.Members) { if (!member.IsCopyable) { continue; } if (member is ISerializableMember serializable) { members.Add(serializable); } else if (member is IFieldDescription or IPropertyDescription) { members.Add(new SerializableMember(_codeGenerator, member, members.Count)); } else if (member is MethodParameterFieldDescription methodParameter) { members.Add(new SerializableMethodMember(methodParameter)); } } var accessibility = type.Accessibility switch { Accessibility.Public => SyntaxKind.PublicKeyword, _ => SyntaxKind.InternalKeyword, }; var isExceptionType = type.IsExceptionType && type.SerializationHooks.Count == 0; var baseType = isExceptionType ? QualifiedName(AliasQualifiedName("global", IdentifierName("Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper")), GenericName(Identifier("ExceptionCopier"), TypeArgumentList(SeparatedList(new[] { type.TypeSyntax, type.BaseType.ToTypeSyntax() })))) : (isShallowCopyable ? LibraryTypes.ShallowCopier : LibraryTypes.DeepCopier_1).ToTypeSyntax(type.TypeSyntax); var classDeclaration = ClassDeclaration(simpleClassName) .AddBaseListTypes(SimpleBaseType(baseType)) .AddModifiers(Token(accessibility), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()); if (!isShallowCopyable) { var fieldDescriptions = GetFieldDescriptions(type, members, isExceptionType, out var onlyDeepFields); var fieldDeclarations = GetFieldDeclarations(fieldDescriptions); var ctor = GenerateConstructor(simpleClassName, fieldDescriptions, isExceptionType); classDeclaration = classDeclaration.AddMembers(fieldDeclarations); if (!isExceptionType) { var copyMethod = GenerateMemberwiseDeepCopyMethod(type, fieldDescriptions, members, onlyDeepFields); classDeclaration = classDeclaration.AddMembers(copyMethod); } if (ctor != null) classDeclaration = classDeclaration.AddMembers(ctor); if (isExceptionType || !type.IsSealedType) { if (GenerateBaseCopierDeepCopyMethod(type, fieldDescriptions, members, isExceptionType) is { } baseCopier) classDeclaration = classDeclaration.AddMembers(baseCopier); if (!isExceptionType) classDeclaration = classDeclaration.AddBaseListTypes(SimpleBaseType(LibraryTypes.BaseCopier_1.ToTypeSyntax(type.TypeSyntax))); } } if (type.IsGenericType) { classDeclaration = SyntaxFactoryUtility.AddGenericTypeParameters(classDeclaration, type.TypeParameters); } return classDeclaration; } public static string GetSimpleClassName(ISerializableTypeDescription serializableType) => GetSimpleClassName(serializableType.Name); public static string GetSimpleClassName(string name) => $"Copier_{name}"; private MemberDeclarationSyntax[] GetFieldDeclarations(List fieldDescriptions) { return fieldDescriptions.Select(GetFieldDeclaration).ToArray(); static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription description) { switch (description) { case FieldAccessorDescription accessor when accessor.InitializationSyntax != null: return FieldDeclaration(VariableDeclaration(accessor.FieldType, SingletonSeparatedList(VariableDeclarator(accessor.FieldName).WithInitializer(EqualsValueClause(accessor.InitializationSyntax))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case FieldAccessorDescription accessor when accessor.InitializationSyntax == null: //[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_Amount")] //extern static void SetAmount(External instance, int value); return MethodDeclaration( PredefinedType(Token(SyntaxKind.VoidKeyword)), accessor.AccessorName) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ExternKeyword), Token(SyntaxKind.StaticKeyword)) .AddAttributeLists(AttributeList(SingletonSeparatedList( Attribute(IdentifierName("System.Runtime.CompilerServices.UnsafeAccessor")) .AddArgumentListArguments( AttributeArgument( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("System.Runtime.CompilerServices.UnsafeAccessorKind"), IdentifierName("Method"))), AttributeArgument( LiteralExpression( SyntaxKind.StringLiteralExpression, Literal($"set_{accessor.FieldName}"))) .WithNameEquals(NameEquals("Name")))))) .WithParameterList( ParameterList(SeparatedList(new[] { Parameter(Identifier("instance")).WithType(accessor.ContainingType), Parameter(Identifier("value")).WithType(description.FieldType) }))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); default: return FieldDeclaration(VariableDeclaration(description.FieldType, SingletonSeparatedList(VariableDeclarator(description.FieldName)))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)); } } } private ConstructorDeclarationSyntax GenerateConstructor(string simpleClassName, List fieldDescriptions, bool isExceptionType) { var codecProviderAdded = false; var parameters = new List(); var statements = new List(); if (isExceptionType) { parameters.Add(Parameter(Identifier("codecProvider")).WithType(LibraryTypes.ICodecProvider.ToTypeSyntax())); codecProviderAdded = true; } foreach (var field in fieldDescriptions) { switch (field) { case GeneratedFieldDescription _ when field.IsInjected: parameters.Add(Parameter(field.FieldName.ToIdentifier()).WithType(field.FieldType)); statements.Add(ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, ThisExpression().Member(field.FieldName.ToIdentifierName()), Unwrapped(field.FieldName.ToIdentifierName())))); break; case CopierFieldDescription or BaseCopierFieldDescription when !field.IsInjected: if (!codecProviderAdded) { parameters.Add(Parameter(Identifier("codecProvider")).WithType(LibraryTypes.ICodecProvider.ToTypeSyntax())); codecProviderAdded = true; } var copier = InvocationExpression( IdentifierName("OrleansGeneratedCodeHelper").Member(GenericName(Identifier("GetService"), TypeArgumentList(SingletonSeparatedList(field.FieldType)))), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(IdentifierName("codecProvider")) }))); statements.Add(ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, field.FieldName.ToIdentifierName(), copier))); break; } } return statements.Count == 0 && !isExceptionType ? null : ConstructorDeclaration(simpleClassName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters.ToArray()) .AddBodyStatements(statements.ToArray()) .WithInitializer(isExceptionType ? ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList(SingletonSeparatedList(Argument(IdentifierName("codecProvider"))))) : null); static ExpressionSyntax Unwrapped(ExpressionSyntax expr) { return InvocationExpression( IdentifierName("OrleansGeneratedCodeHelper").Member("UnwrapService"), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(expr) }))); } } private List GetFieldDescriptions( ISerializableTypeDescription serializableTypeDescription, List members, bool isExceptionType, out bool onlyDeepFields) { var serializationHooks = serializableTypeDescription.SerializationHooks; onlyDeepFields = serializableTypeDescription.IsValueType && serializationHooks.Count == 0; var fields = new List(); if (!isExceptionType && serializableTypeDescription.HasComplexBaseType) { fields.Add(GetBaseTypeField(serializableTypeDescription)); } if (!serializableTypeDescription.IsImmutable) { if (!isExceptionType && serializableTypeDescription.UseActivator && !serializableTypeDescription.IsAbstractType) { onlyDeepFields = false; fields.Add(new ActivatorFieldDescription(LibraryTypes.IActivator_1.ToTypeSyntax(serializableTypeDescription.TypeSyntax), ActivatorFieldName)); } // Add a copier field for any field in the target which does not have a static copier. GetCopierFieldDescriptions(serializableTypeDescription.Members, fields); } foreach (var member in members) { if (onlyDeepFields && member.IsShallowCopyable) continue; if (member.GetGetterFieldDescription() is { } getterFieldDescription) { fields.Add(getterFieldDescription); } if (member.GetSetterFieldDescription() is { } setterFieldDescription) { fields.Add(setterFieldDescription); } } for (var hookIndex = 0; hookIndex < serializationHooks.Count; ++hookIndex) { var hookType = serializationHooks[hookIndex]; fields.Add(new SerializationHookFieldDescription(hookType.ToTypeSyntax(), $"_hook{hookIndex}")); } return fields; } private BaseCopierFieldDescription GetBaseTypeField(ISerializableTypeDescription serializableTypeDescription) { var baseType = serializableTypeDescription.BaseType; if (baseType.HasAttribute(LibraryTypes.GenerateSerializerAttribute) && (SymbolEqualityComparer.Default.Equals(baseType.ContainingAssembly, LibraryTypes.Compilation.Assembly) || baseType.ContainingAssembly.HasAttribute(LibraryTypes.TypeManifestProviderAttribute)) && baseType is not INamedTypeSymbol { IsGenericType: true }) { // Use the concrete generated type and avoid expensive interface dispatch (except for generic types that will fall back to IBaseCopier) return new(QualifiedName(ParseName(GetGeneratedNamespaceName(baseType)), IdentifierName(GetSimpleClassName(baseType.Name))), true); } return new(LibraryTypes.BaseCopier_1.ToTypeSyntax(serializableTypeDescription.BaseTypeSyntax)); } public void GetCopierFieldDescriptions(IEnumerable members, List fields) { var fieldIndex = 0; var uniqueTypes = new HashSet(MemberDescriptionTypeComparer.Default); foreach (var member in members) { if (!member.IsCopyable) { continue; } var t = member.Type; if (LibraryTypes.IsShallowCopyable(t)) continue; foreach (var c in LibraryTypes.StaticCopiers) if (SymbolEqualityComparer.Default.Equals(c.UnderlyingType, t)) goto skip; if (member.Symbol.HasAttribute(LibraryTypes.ImmutableAttribute)) continue; if (!uniqueTypes.Add(member)) continue; TypeSyntax copierType; if (t.HasAttribute(LibraryTypes.GenerateSerializerAttribute) && (SymbolEqualityComparer.Default.Equals(t.ContainingAssembly, LibraryTypes.Compilation.Assembly) || t.ContainingAssembly.HasAttribute(LibraryTypes.TypeManifestProviderAttribute)) && t is not INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 0 }) { // Use the concrete generated type and avoid expensive interface dispatch (except for complex nested cases that will fall back to IDeepCopier) SimpleNameSyntax name; if (t is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType) { // Construct the full generic type name name = GenericName(Identifier(GetSimpleClassName(t.Name)), TypeArgumentList(SeparatedList(namedTypeSymbol.TypeArguments.Select(arg => arg.ToTypeSyntax())))); } else { name = IdentifierName(GetSimpleClassName(t.Name)); } copierType = QualifiedName(ParseName(GetGeneratedNamespaceName(t)), name); } else if (t is IArrayTypeSymbol { IsSZArray: true } array) { copierType = LibraryTypes.ArrayCopier.Construct(array.ElementType).ToTypeSyntax(); } else if (LibraryTypes.WellKnownCopiers.FindByUnderlyingType(t) is { } copier) { // The copier is not a static copier and is also not a generic copiers. copierType = copier.CopierType.ToTypeSyntax(); } else if (t is INamedTypeSymbol { ConstructedFrom: ISymbol unboundFieldType } named && LibraryTypes.WellKnownCopiers.FindByUnderlyingType(unboundFieldType) is { } genericCopier) { // Construct the generic copier type using the field's type arguments. copierType = genericCopier.CopierType.Construct(named.TypeArguments.ToArray()).ToTypeSyntax(); } else { // Use the IDeepCopier interface copierType = LibraryTypes.DeepCopier_1.ToTypeSyntax(member.TypeSyntax); } fields.Add(new CopierFieldDescription(copierType, $"_copier{fieldIndex++}", t)); skip:; } } private MemberDeclarationSyntax GenerateMemberwiseDeepCopyMethod( ISerializableTypeDescription type, List copierFields, List members, bool onlyDeepFields) { var returnType = type.TypeSyntax; var originalParam = "original".ToIdentifierName(); var contextParam = "context".ToIdentifierName(); var resultVar = "result".ToIdentifierName(); var body = new List(); var membersCopied = false; if (type.IsAbstractType) { // C#: return context.DeepCopy(original) body.Add(ReturnStatement(InvocationExpression(contextParam.Member("DeepCopy"), ArgumentList(SingletonSeparatedList(Argument(originalParam)))))); membersCopied = true; } else if (type.IsUnsealedImmutable) { // C#: return original is null || original.GetType() == typeof(T) ? original : context.DeepCopy(original); var exactTypeMatch = BinaryExpression(SyntaxKind.EqualsExpression, InvocationExpression(originalParam.Member("GetType")), TypeOfExpression(type.TypeSyntax)); var nullOrTypeMatch = BinaryExpression(SyntaxKind.LogicalOrExpression, BinaryExpression(SyntaxKind.IsExpression, originalParam, LiteralExpression(SyntaxKind.NullLiteralExpression)), exactTypeMatch); var contextCopy = InvocationExpression(contextParam.Member("DeepCopy"), ArgumentList(SingletonSeparatedList(Argument(originalParam)))); body.Add(ReturnStatement(ConditionalExpression(nullOrTypeMatch, originalParam, contextCopy))); membersCopied = true; } else if (!type.IsValueType) { if (type.TrackReferences) { // C#: if (context.TryGetCopy(original, out T existing)) return existing; var tryGetCopy = InvocationExpression( contextParam.Member("TryGetCopy"), ArgumentList(SeparatedList(new[] { Argument(originalParam), Argument(DeclarationExpression( type.TypeSyntax, SingleVariableDesignation(Identifier("existing")))) .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)) }))); body.Add(IfStatement(tryGetCopy, ReturnStatement("existing".ToIdentifierName()))); } else { // C#: if (original is null) return null; body.Add(IfStatement(BinaryExpression(SyntaxKind.IsExpression, originalParam, LiteralExpression(SyntaxKind.NullLiteralExpression)), ReturnStatement(LiteralExpression(SyntaxKind.NullLiteralExpression)))); } if (!type.IsSealedType) { // C#: if (original.GetType() != typeof(T)) { return context.DeepCopy(original); } var exactTypeMatch = BinaryExpression(SyntaxKind.NotEqualsExpression, InvocationExpression(originalParam.Member("GetType")), TypeOfExpression(type.TypeSyntax)); var contextCopy = InvocationExpression(contextParam.Member("DeepCopy"), ArgumentList(SingletonSeparatedList(Argument(originalParam)))); body.Add(IfStatement(exactTypeMatch, ReturnStatement(contextCopy))); } // C#: var result = _activator.Create(); body.Add(LocalDeclarationStatement( VariableDeclaration( IdentifierName("var"), SingletonSeparatedList(VariableDeclarator( resultVar.Identifier, argumentList: null, initializer: EqualsValueClause(GetCreateValueExpression(type, copierFields))))))); if (type.TrackReferences) { // C#: context.RecordCopy(original, result); body.Add(ExpressionStatement(InvocationExpression(contextParam.Member("RecordCopy"), ArgumentList(SeparatedList(new[] { Argument(originalParam), Argument(resultVar) }))))); } if (!type.IsSealedType) { // C#: DeepCopy(original, result, context); body.Add(ExpressionStatement(InvocationExpression(IdentifierName("DeepCopy"), ArgumentList(SeparatedList(new[] { Argument(originalParam), Argument(resultVar), Argument(contextParam) }))))); body.Add(ReturnStatement(resultVar)); membersCopied = true; } else if (type.HasComplexBaseType) { // C#: _baseTypeCopier.DeepCopy(original, result, context); body.Add( ExpressionStatement( InvocationExpression( BaseTypeCopierFieldName.ToIdentifierName().Member(DeepCopyMethodName), ArgumentList(SeparatedList(new[] { Argument(originalParam), Argument(resultVar), Argument(contextParam) }))))); } } else if (!onlyDeepFields) { // C#: var result = _activator.Create(); // or C#: var result = new TField(); // or C#: var result = default(TField); body.Add(LocalDeclarationStatement( VariableDeclaration( IdentifierName("var"), SingletonSeparatedList(VariableDeclarator(resultVar.Identifier) .WithInitializer(EqualsValueClause(GetCreateValueExpression(type, copierFields))))))); } else { originalParam = resultVar; } if (!membersCopied) { GenerateMemberwiseCopy(type, copierFields, members, originalParam, contextParam, resultVar, body, onlyDeepFields); body.Add(ReturnStatement(resultVar)); } var parameters = new[] { Parameter(originalParam.Identifier).WithType(type.TypeSyntax), Parameter(contextParam.Identifier).WithType(LibraryTypes.CopyContext.ToTypeSyntax()) }; return MethodDeclaration(returnType, DeepCopyMethodName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); } private ExpressionSyntax GetCreateValueExpression(ISerializableTypeDescription type, List copierFields) { return type.UseActivator switch { true => InvocationExpression(copierFields.Find(f => f is ActivatorFieldDescription).FieldName.ToIdentifierName().Member("Create")), false => type.GetObjectCreationExpression() }; } private MemberDeclarationSyntax GenerateBaseCopierDeepCopyMethod( ISerializableTypeDescription type, List copierFields, List members, bool isExceptionType) { var inputParam = "input".ToIdentifierName(); var resultParam = "output".ToIdentifierName(); var contextParam = "context".ToIdentifierName(); var body = new List(); if (type.HasComplexBaseType) { // C#: _baseTypeCopier.DeepCopy(original, result, context); body.Add( ExpressionStatement( InvocationExpression( (isExceptionType ? (ExpressionSyntax)BaseExpression() : IdentifierName(BaseTypeCopierFieldName)).Member(DeepCopyMethodName), ArgumentList(SeparatedList(new[] { Argument(inputParam), Argument(resultParam), Argument(contextParam) }))))); } var emptyBodyCount = body.Count; GenerateMemberwiseCopy( type, copierFields, members, inputParam, contextParam, resultParam, body); if (isExceptionType && body.Count == emptyBodyCount) return null; var parameters = new[] { Parameter(inputParam.Identifier).WithType(type.TypeSyntax), Parameter(resultParam.Identifier).WithType(type.TypeSyntax), Parameter(contextParam.Identifier).WithType(LibraryTypes.CopyContext.ToTypeSyntax()) }; var method = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), DeepCopyMethodName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); if (isExceptionType) method = method.AddModifiers(Token(SyntaxKind.OverrideKeyword)); return method; } private void GenerateMemberwiseCopy( ISerializableTypeDescription type, List copierFields, List members, IdentifierNameSyntax sourceVar, IdentifierNameSyntax contextVar, IdentifierNameSyntax destinationVar, List body, bool onlyDeepFields = false) { AddSerializationCallbacks(type, sourceVar, destinationVar, "OnCopying", body); var copiers = type.IsUnsealedImmutable ? null : copierFields.OfType() .Concat(LibraryTypes.StaticCopiers) .ToList(); var orderedMembers = members.OrderBy(m => m.Member.FieldId); foreach (var member in orderedMembers) { if (onlyDeepFields && member.IsShallowCopyable) continue; var getValueExpression = GenerateMemberCopy( copierFields, inputValue: member.GetGetter(sourceVar), contextVar, copiers, member); var memberAssignment = ExpressionStatement(member.GetSetter(destinationVar, getValueExpression)); body.Add(memberAssignment); } AddSerializationCallbacks(type, sourceVar, destinationVar, "OnCopied", body); } public ExpressionSyntax GenerateMemberCopy( List copierFields, ExpressionSyntax inputValue, ExpressionSyntax copyContextVar, List copiers, ISerializableMember member) { if (copiers is null || member.IsShallowCopyable) return inputValue; var description = member.Member; // Copiers can either be static classes or injected into the constructor. // Either way, the member signatures are the same. var memberType = description.Type; var copier = copiers.Find(f => SymbolEqualityComparer.Default.Equals(f.UnderlyingType, memberType)); ExpressionSyntax getValueExpression; if (copier is null) { getValueExpression = InvocationExpression( copyContextVar.Member(DeepCopyMethodName), ArgumentList(SeparatedList(new[] { Argument(inputValue) }))); } else { ExpressionSyntax copierExpression; var staticCopier = LibraryTypes.StaticCopiers.FindByUnderlyingType(memberType); if (staticCopier != null) { copierExpression = staticCopier.CopierType.ToNameSyntax(); } else { var instanceCopier = copierFields.First(f => f is CopierFieldDescription cf && SymbolEqualityComparer.Default.Equals(cf.UnderlyingType, memberType)); copierExpression = IdentifierName(instanceCopier.FieldName); } getValueExpression = InvocationExpression( copierExpression.Member(DeepCopyMethodName), ArgumentList(SeparatedList(new[] { Argument(inputValue), Argument(copyContextVar) }))); if (!SymbolEqualityComparer.Default.Equals(copier.UnderlyingType, member.Member.Type)) { // If the member type type differs from the copier type (eg because the member is an array), cast the result. getValueExpression = CastExpression(description.TypeSyntax, getValueExpression); } } return getValueExpression; } private void AddSerializationCallbacks(ISerializableTypeDescription type, IdentifierNameSyntax originalInstance, IdentifierNameSyntax resultInstance, string callbackMethodName, List body) { var serializationHooks = type.SerializationHooks; for (var hookIndex = 0; hookIndex < serializationHooks.Count; ++hookIndex) { var hookType = serializationHooks[hookIndex]; var member = hookType.GetAllMembers(callbackMethodName, Accessibility.Public).FirstOrDefault(); if (member is null || member.Parameters.Length != 2) { continue; } var originalArgument = Argument(originalInstance); if (member.Parameters[0].RefKind == RefKind.Ref) { originalArgument = originalArgument.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } var resultArgument = Argument(resultInstance); if (member.Parameters[1].RefKind == RefKind.Ref) { resultArgument = resultArgument.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } body.Add(ExpressionStatement(InvocationExpression( IdentifierName($"_hook{hookIndex}").Member(callbackMethodName), ArgumentList(SeparatedList(new[] { originalArgument, resultArgument }))))); } } internal sealed class BaseCopierFieldDescription : GeneratedFieldDescription { public BaseCopierFieldDescription(TypeSyntax fieldType, bool concreteType = false) : base(fieldType, BaseTypeCopierFieldName) => IsInjected = !concreteType; public override bool IsInjected { get; } } internal sealed class CopierFieldDescription : GeneratedFieldDescription, ICopierDescription { public CopierFieldDescription(TypeSyntax fieldType, string fieldName, ITypeSymbol underlyingType) : base(fieldType, fieldName) { UnderlyingType = underlyingType; } public ITypeSymbol UnderlyingType { get; } public override bool IsInjected => false; } } } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/CanNotGenerateImplicitFieldIdsDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class CanNotGenerateImplicitFieldIdsDiagnostic { public const string DiagnosticId = DiagnosticRuleId.CanNotGenerateImplicitFieldIds; public const string Title = "Implicit field identifiers could not be generated"; public const string MessageFormat = "Could not generate implicit field identifiers for the type {0}: {reason}"; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); internal static Diagnostic CreateDiagnostic(ISymbol symbol, string reason) => Diagnostic.Create(Rule, symbol.Locations.First(), symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), reason); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs ================================================ // Centralized diagnostic rule IDs for Orleans.CodeGenerator namespace Orleans.CodeGenerator.Diagnostics; internal static class DiagnosticRuleId { public const string InaccessibleSetter = "ORLEANS0101"; public const string InvalidRpcMethodReturnType = "ORLEANS0102"; public const string UnhandledCodeGenerationException = "ORLEANS0103"; public const string IncorrectProxyBaseClassSpecification = "ORLEANS0104"; public const string RpcInterfaceProperty = "ORLEANS0105"; public const string CanNotGenerateImplicitFieldIds = "ORLEANS0106"; public const string InaccessibleSerializableType = "ORLEANS0107"; public const string GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly = "ORLEANS0108"; public const string MultipleCancellationTokenParameters = "ORLEANS0109"; public const string ReferenceAssemblyWithGenerateSerializer = "ORLEANS0110"; } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly_Diagnostic.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly_Diagnostic { public const string DiagnosticId = DiagnosticRuleId.GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly; public const string Title = "Types passed to GenerateCodeForDeclaringAssemblyAttribute must have a declaring assembly"; public const string MessageFormat = "The type {0} provided as an argument to {1} does not have a declaring assembly"; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); internal static Diagnostic CreateDiagnostic(AttributeData attribute, ITypeSymbol type) => Diagnostic.Create(Rule, attribute.ApplicationSyntaxReference.SyntaxTree.GetLocation(attribute.ApplicationSyntaxReference.Span), type.ToDisplayString(), attribute.ToString()); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/InaccessibleSerializableTypeDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class InaccessibleSerializableTypeDiagnostic { public const string RuleId = DiagnosticRuleId.InaccessibleSerializableType; public const string Title = "Serializable type must be accessible from generated code"; public const string MessageFormat = "The type {0} is marked as being serializable but it is inaccessible from generated code"; public const string Descsription = "Source generation requires that all types marked as serializable are accessible from generated code. Either make the type public or make it internal and ensure that internals are visible to the generated code."; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Descsription); internal static Diagnostic CreateDiagnostic(ISymbol symbol) => Diagnostic.Create(Rule, symbol.Locations.First(), symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/InaccessibleSetterDiagnostic.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class InaccessibleSetterDiagnostic { public const string RuleId = DiagnosticRuleId.InaccessibleSetter; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.InaccessibleSetterTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.InaccessibleSetterMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.InaccessibleSetterDescription), Resources.ResourceManager, typeof(Resources)); internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public static Diagnostic CreateDiagnostic(Location location, string identifier) => Diagnostic.Create(Rule, location, identifier); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/IncorrectProxyBaseClassSpecificationDiagnostic.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class IncorrectProxyBaseClassSpecificationDiagnostic { public const string RuleId = DiagnosticRuleId.IncorrectProxyBaseClassSpecification; private const string Category = "Usage"; private static readonly LocalizableString Title = "The proxy base class specified is not a valid proxy base class"; private static readonly LocalizableString MessageFormat = "Proxy base class {0} does not conform to requirements: {1}"; private static readonly LocalizableString Description = "Proxy base clases must conform. Please report this bug by opening an issue https://github.com/dotnet/orleans/issues/new."; internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); internal static Diagnostic CreateDiagnostic(INamedTypeSymbol baseClass, Location location, string complaint) => Diagnostic.Create(Rule, location, baseClass.ToDisplayString(), complaint); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/InvalidRpcMethodReturnTypeDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class InvalidRpcMethodReturnTypeDiagnostic { public const string RuleId = DiagnosticRuleId.InvalidRpcMethodReturnType; private const string Category = "Usage"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.InvalidRpcMethodReturnTypeTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.InvalidRpcMethodReturnTypeMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.InvalidRpcMethodReturnTypeDescription), Resources.ResourceManager, typeof(Resources)); internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public static Diagnostic CreateDiagnostic(Location location, string returnType, string methodIdentifier, string supportedReturnTypeList) => Diagnostic.Create(Rule, location, returnType, methodIdentifier, supportedReturnTypeList); internal static Diagnostic CreateDiagnostic(InvokableMethodDescription methodDescription) { var methodReturnType = methodDescription.Method.ReturnType; var diagnostic = CreateDiagnostic( methodDescription.Method.OriginalDefinition.Locations.FirstOrDefault(), methodReturnType.ToDisplayString(), methodDescription.Method.ToDisplayString(), string.Join(", ", methodDescription.InvokableBaseTypes.Keys.Select(v => v.ToDisplayString()))); return diagnostic; } } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/MultipleCancellationTokenParametersDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class MultipleCancellationTokenParametersDiagnostic { public const string DiagnosticId = DiagnosticRuleId.MultipleCancellationTokenParameters; public const string Title = "Grain method has multiple parameters of type CancellationToken"; public const string MessageFormat = "The type {0} contains method {1} which has multiple CancellationToken parameters. Only a single CancellationToken parameter is supported."; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); internal static Diagnostic CreateDiagnostic(IMethodSymbol symbol) => Diagnostic.Create(Rule, symbol.Locations.First(), symbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), symbol.Name); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/ReferenceAssemblyWithGenerateSerializerDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class ReferenceAssemblyWithGenerateSerializerDiagnostic { public const string DiagnosticId = DiagnosticRuleId.ReferenceAssemblyWithGenerateSerializer; public const string Title = "[GenerateSerializer] used in a reference assembly"; public const string MessageFormat = "The type {0} is marked with [GenerateSerializer] in a reference assembly"; public const string Description = "The type {0} is marked with [GenerateSerializer] in a reference assembly. Serialization is likely to fail. Options: (1) Enable code generation on the target project directly; (2) Disable reference assemblies using false in the codegen project; (3) Use a different serializer or create surrogates. See https://aka.ms/orleans-serialization for details."; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); internal static Diagnostic CreateDiagnostic(ISymbol symbol) => Diagnostic.Create(Rule, symbol.Locations.First(), symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/RpcInterfacePropertyDiagnostic.cs ================================================ using System.Linq; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class RpcInterfacePropertyDiagnostic { public const string DiagnosticId = DiagnosticRuleId.RpcInterfaceProperty; public const string Title = "RPC interfaces must not contain properties"; public const string MessageFormat = "The interface {0} contains a property {1}. RPC interfaces must not contain properties."; public const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true); internal static Diagnostic CreateDiagnostic(INamedTypeSymbol interfaceSymbol, IPropertySymbol property) => Diagnostic.Create(Rule, property.Locations.First(), interfaceSymbol.ToDisplayString(), property.ToDisplayString()); } ================================================ FILE: src/Orleans.CodeGenerator/Diagnostics/UnhandledCodeGenerationExceptionDiagnostic.cs ================================================ using System; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.Diagnostics; public static class UnhandledCodeGenerationExceptionDiagnostic { public const string RuleId = DiagnosticRuleId.UnhandledCodeGenerationException; private const string Category = "Usage"; private static readonly LocalizableString Title = "An unhandled source generation exception occurred"; private static readonly LocalizableString MessageFormat = "An unhandled exception occurred while generating source for your project: {0} {1}"; private static readonly LocalizableString Description = "Please report this bug by opening an issue https://github.com/dotnet/orleans/issues/new."; internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); internal static Diagnostic CreateDiagnostic(Exception exception) => Diagnostic.Create(Rule, location: null, messageArgs: new[] { exception.ToString(), exception.StackTrace }); } ================================================ FILE: src/Orleans.CodeGenerator/FieldIdAssignmentHelper.cs ================================================ #nullable enable using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.InteropServices; using System.Text; using Microsoft.CodeAnalysis; using Orleans.CodeGenerator.Hashing; using Orleans.CodeGenerator.Model; using Orleans.CodeGenerator.SyntaxGeneration; namespace Orleans.CodeGenerator; internal class FieldIdAssignmentHelper { private readonly GenerateFieldIds _implicitMemberSelectionStrategy; private readonly ImmutableArray _constructorParameters; private readonly LibraryTypes _libraryTypes; private readonly ISymbol[] _memberSymbols; private readonly Dictionary _symbols = new(SymbolEqualityComparer.Default); public bool IsValidForSerialization { get; } public string? FailureReason { get; private set; } public IEnumerable Members => _memberSymbols; public FieldIdAssignmentHelper(INamedTypeSymbol typeSymbol, ImmutableArray constructorParameters, GenerateFieldIds implicitMemberSelectionStrategy, LibraryTypes libraryTypes) { _constructorParameters = constructorParameters; _implicitMemberSelectionStrategy = implicitMemberSelectionStrategy; _libraryTypes = libraryTypes; _memberSymbols = GetMembers(typeSymbol).ToArray(); IsValidForSerialization = _implicitMemberSelectionStrategy != GenerateFieldIds.None && !HasMemberWithIdAnnotation() ? GenerateImplicitFieldIds() : ExtractFieldIdAnnotations(); } public bool TryGetSymbolKey(ISymbol symbol, out (uint, bool) key) => _symbols.TryGetValue(symbol, out key); private bool HasMemberWithIdAnnotation() => Array.Exists(_memberSymbols, member => member.HasAttribute(_libraryTypes.IdAttributeType)); private IEnumerable GetMembers(INamedTypeSymbol symbol) { foreach (var member in symbol.GetMembers().OrderBy(m => m.MetadataName)) { if (member.IsStatic || member.IsAbstract) { continue; } if (member is not (IFieldSymbol or IPropertySymbol)) { continue; } if (member.HasAttribute(_libraryTypes.NonSerializedAttribute)) { continue; } yield return member; } } private bool ExtractFieldIdAnnotations() { foreach (var member in _memberSymbols) { if (member is IPropertySymbol prop) { var id = CodeGenerator.GetId(_libraryTypes, prop); if (id.HasValue) { _symbols[member] = (id.Value, false); } else if (PropertyUtility.GetMatchingPrimaryConstructorParameter(prop, _constructorParameters) is { } prm) { id = CodeGenerator.GetId(_libraryTypes, prop); if (id.HasValue) { _symbols[member] = (id.Value, true); } else { _symbols[member] = ((uint)_constructorParameters.IndexOf(prm), true); } } } if (member is IFieldSymbol field) { var id = CodeGenerator.GetId(_libraryTypes, field); var isConstructorParameter = false; if (!id.HasValue) { var property = PropertyUtility.GetMatchingProperty(field, _memberSymbols); if (property is null) { continue; } id = CodeGenerator.GetId(_libraryTypes, property); if (!id.HasValue) { var constructorParameter = _constructorParameters.FirstOrDefault(x => x.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase)); if (constructorParameter is not null) { id = (uint)_constructorParameters.IndexOf(constructorParameter); isConstructorParameter = true; } } } if (id.HasValue) { _symbols[member] = (id.Value, isConstructorParameter); } } } return true; } private static (string, uint) GetCanonicalNameAndFieldId(ITypeSymbol typeSymbol, string name) { name = PropertyUtility.GetCanonicalName(name); // compute the hash from the type name (without namespace, to allow it to move around) and name var typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var hashCode = ComputeHash($"{typeName}%{name}"); return (name, (uint)hashCode); static unsafe uint ComputeHash(string data) { uint hash; var input = BitConverter.IsLittleEndian ? MemoryMarshal.AsBytes(data.AsSpan()) : Encoding.Unicode.GetBytes(data); XxHash32.TryHash(input, new Span((byte*)&hash, sizeof(uint)), out _); return BitConverter.IsLittleEndian ? hash : BinaryPrimitives.ReverseEndianness(hash); } } private bool GenerateImplicitFieldIds() { var constructorFieldIds = new Dictionary(); foreach (var parameter in _constructorParameters) { var (canonicalName, fieldId) = GetCanonicalNameAndFieldId(parameter.Type, parameter.Name); constructorFieldIds[canonicalName] = fieldId; } var success = _implicitMemberSelectionStrategy switch { GenerateFieldIds.PublicProperties => GenerateFromProperties(_memberSymbols.OfType()), _ => false }; // validate - we can only use generated field ids if there were no collisions if (success && _symbols.Values.Distinct().Count() != _symbols.Count) { FailureReason = "hash collision (consider using explicit [Id] annotations for this type)"; return false; } return success; bool GenerateFromProperties(IEnumerable properties) { foreach (var property in properties) { var (canonicalName, fieldId) = GetCanonicalNameAndFieldId(property.Type, property.Name); var isConstructorParameter = constructorFieldIds.TryGetValue(canonicalName, out var constructorFieldId); // abort when inconsistencies are detected if (isConstructorParameter && fieldId != constructorFieldId) { FailureReason = $"type mismatch for property {property.Name} and its corresponding constructor parameter"; return false; } // for immutable types we must currently use the backing field of the public property, as the serialization // engine does not call a custom constructor to recreate the instance var mustUseField = property.SetMethod == null || property.IsReadOnly || property.SetMethod.IsInitOnly || property.IsStatic || property.SetMethod.IsAbstract; ISymbol? symbol = mustUseField ? PropertyUtility.GetMatchingField(property, _memberSymbols) : property; if (symbol == null) continue; _symbols[symbol] = (fieldId, isConstructorParameter); } return _symbols.Count > 0; } } } ================================================ FILE: src/Orleans.CodeGenerator/Hashing/BitOperations.cs ================================================ #nullable enable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; namespace Orleans.CodeGenerator.Hashing { internal static class BitOperations { /// /// Rotates the specified value left by the specified number of bits. /// Similar in behavior to the x86 instruction ROL. /// /// The value to rotate. /// The number of bits to rotate by. /// Any value outside the range [0..31] is treated as congruent mod 32. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint RotateLeft(uint value, int offset) => (value << offset) | (value >> (32 - offset)); } } ================================================ FILE: src/Orleans.CodeGenerator/Hashing/HexConverter.cs ================================================ #nullable enable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Runtime.CompilerServices; namespace Orleans.CodeGenerator.Hashing { internal static class HexConverter { public static unsafe string ToString(ReadOnlySpan bytes) { // Adapted from: https://github.com/dotnet/runtime/blob/f156fb9dcf121e536b93ae90bcc5e8e6d5336062/src/libraries/Common/src/System/HexConverter.cs#L196 Span result = bytes.Length > 16 ? new char[bytes.Length * 2].AsSpan() : stackalloc char[bytes.Length * 2]; int pos = 0; foreach (byte b in bytes) { ToCharsBuffer(b, result, pos); pos += 2; } return result.ToString(); [MethodImpl(MethodImplOptions.AggressiveInlining)] static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0) { var difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; var packedResult = (((uint)-(int)difference & 0x7070U) >> 4) + difference + 0xB9B9U; buffer[startingIndex + 1] = (char)(packedResult & 0xFF); buffer[startingIndex] = (char)(packedResult >> 8); } } } } ================================================ FILE: src/Orleans.CodeGenerator/Hashing/NonCryptographicHashAlgorithm.cs ================================================ #nullable enable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; namespace Orleans.CodeGenerator.Hashing { /// /// Represents a non-cryptographic hash algorithm. /// internal abstract class NonCryptographicHashAlgorithm { /// /// Gets the number of bytes produced from this hash algorithm. /// /// The number of bytes produced from this hash algorithm. public int HashLengthInBytes { get; } /// /// Called from constructors in derived classes to initialize the /// class. /// /// /// The number of bytes produced from this hash algorithm. /// /// /// is less than 1. /// protected NonCryptographicHashAlgorithm(int hashLengthInBytes) { if (hashLengthInBytes < 1) throw new ArgumentOutOfRangeException(nameof(hashLengthInBytes)); HashLengthInBytes = hashLengthInBytes; } /// /// When overridden in a derived class, /// appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. public abstract void Append(ReadOnlySpan source); /// /// When overridden in a derived class, /// resets the hash computation to the initial state. /// public abstract void Reset(); /// /// When overridden in a derived class, /// writes the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. /// /// /// Implementations of this method must write exactly /// bytes to . /// Do not assume that the buffer was zero-initialized. /// /// /// The class validates the /// size of the buffer before calling this method, and slices the span /// down to be exactly in length. /// /// protected abstract void GetCurrentHashCore(Span destination); /// /// Appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. /// /// is . /// public void Append(byte[] source) { if (source is null) { throw new ArgumentNullException(nameof(source)); } Append(new ReadOnlySpan(source)); } /// /// Appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. /// /// is . /// /// public void Append(Stream stream) { if (stream is null) { throw new ArgumentNullException(nameof(stream)); } byte[] buffer = ArrayPool.Shared.Rent(4096); while (true) { int read = stream.Read(buffer, 0, buffer.Length); if (read == 0) { break; } Append(new ReadOnlySpan(buffer, 0, read)); } ArrayPool.Shared.Return(buffer); } /// /// Asychronously reads the contents of /// and appends them to the data already /// processed for the current hash computation. /// /// The data to process. /// /// The token to monitor for cancellation requests. /// The default value is . /// /// /// A task that represents the asynchronous append operation. /// /// /// is . /// public Task AppendAsync(Stream stream, CancellationToken cancellationToken = default) { if (stream is null) { throw new ArgumentNullException(nameof(stream)); } return AppendAsyncCore(stream, cancellationToken); } private async Task AppendAsyncCore(Stream stream, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(4096); while (true) { #if NETCOREAPP int read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); #else int read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); #endif if (read == 0) { break; } Append(new ReadOnlySpan(buffer, 0, read)); } ArrayPool.Shared.Return(buffer); } /// /// Gets the current computed hash value without modifying accumulated state. /// /// /// The hash value for the data already provided. /// public byte[] GetCurrentHash() { byte[] ret = new byte[HashLengthInBytes]; GetCurrentHashCore(ret); return ret; } /// /// Attempts to write the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. /// /// On success, receives the number of bytes written to . /// /// /// if is long enough to receive /// the computed hash value; otherwise, . /// public bool TryGetCurrentHash(Span destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// /// Writes the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. /// /// The number of bytes written to , /// which is always . /// /// /// is shorter than . /// public int GetCurrentHash(Span destination) { if (destination.Length < HashLengthInBytes) { throw new ArgumentException(@"destination too short", nameof(destination)); } GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// /// Gets the current computed hash value and clears the accumulated state. /// /// /// The hash value for the data already provided. /// public byte[] GetHashAndReset() { byte[] ret = new byte[HashLengthInBytes]; GetHashAndResetCore(ret); return ret; } /// /// Attempts to write the computed hash value to . /// If successful, clears the accumulated state. /// /// The buffer that receives the computed hash value. /// /// On success, receives the number of bytes written to . /// /// /// and clears the accumulated state /// if is long enough to receive /// the computed hash value; otherwise, . /// public bool TryGetHashAndReset(Span destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// /// Writes the computed hash value to /// then clears the accumulated state. /// /// The buffer that receives the computed hash value. /// /// The number of bytes written to , /// which is always . /// /// /// is shorter than . /// public int GetHashAndReset(Span destination) { if (destination.Length < HashLengthInBytes) { throw new ArgumentException(@"destination too short", nameof(destination)); } GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// /// Writes the computed hash value to /// then clears the accumulated state. /// /// The buffer that receives the computed hash value. /// /// /// Implementations of this method must write exactly /// bytes to . /// Do not assume that the buffer was zero-initialized. /// /// /// The class validates the /// size of the buffer before calling this method, and slices the span /// down to be exactly in length. /// /// /// The default implementation of this method calls /// followed by . /// Overrides of this method do not need to call either of those methods, /// but must ensure that the caller cannot observe a difference in behavior. /// /// protected virtual void GetHashAndResetCore(Span destination) { Debug.Assert(destination.Length == HashLengthInBytes); GetCurrentHashCore(destination); Reset(); } /// /// This method is not supported and should not be called. /// Call or /// instead. /// /// This method will always throw a . /// In all cases. [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use GetCurrentHash() to retrieve the computed hash code.", true)] #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member public override int GetHashCode() #pragma warning restore CS0809 // Obsolete member overrides non-obsolete member { throw new NotSupportedException("GetHashCode"); } } } ================================================ FILE: src/Orleans.CodeGenerator/Hashing/XxHash32.State.cs ================================================ #nullable enable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers.Binary; using System.Diagnostics; using System.Runtime.CompilerServices; // Implemented from the specification at // https://github.com/Cyan4973/xxHash/blob/f9155bd4c57e2270a4ffbb176485e5d713de1c9b/doc/xxhash_spec.md namespace Orleans.CodeGenerator.Hashing { internal sealed partial class XxHash32 { private struct State { private const uint Prime32_1 = 0x9E3779B1; private const uint Prime32_2 = 0x85EBCA77; private const uint Prime32_3 = 0xC2B2AE3D; private const uint Prime32_4 = 0x27D4EB2F; private const uint Prime32_5 = 0x165667B1; private uint _acc1; private uint _acc2; private uint _acc3; private uint _acc4; private readonly uint _smallAcc; private bool _hadFullStripe; internal State(uint seed) { _acc1 = seed + unchecked(Prime32_1 + Prime32_2); _acc2 = seed + Prime32_2; _acc3 = seed; _acc4 = seed - Prime32_1; _smallAcc = seed + Prime32_5; _hadFullStripe = false; } internal void ProcessStripe(ReadOnlySpan source) { Debug.Assert(source.Length >= StripeSize); source = source.Slice(0, StripeSize); _acc1 = ApplyRound(_acc1, source); _acc2 = ApplyRound(_acc2, source.Slice(sizeof(uint))); _acc3 = ApplyRound(_acc3, source.Slice(2 * sizeof(uint))); _acc4 = ApplyRound(_acc4, source.Slice(3 * sizeof(uint))); _hadFullStripe = true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private readonly uint Converge() { return BitOperations.RotateLeft(_acc1, 1) + BitOperations.RotateLeft(_acc2, 7) + BitOperations.RotateLeft(_acc3, 12) + BitOperations.RotateLeft(_acc4, 18); } private static uint ApplyRound(uint acc, ReadOnlySpan lane) { acc += BinaryPrimitives.ReadUInt32LittleEndian(lane) * Prime32_2; acc = BitOperations.RotateLeft(acc, 13); acc *= Prime32_1; return acc; } internal readonly uint Complete(int length, ReadOnlySpan remaining) { uint acc = _hadFullStripe ? Converge() : _smallAcc; acc += (uint)length; while (remaining.Length >= sizeof(uint)) { uint lane = BinaryPrimitives.ReadUInt32LittleEndian(remaining); acc += lane * Prime32_3; acc = BitOperations.RotateLeft(acc, 17); acc *= Prime32_4; remaining = remaining.Slice(sizeof(uint)); } for (int i = 0; i < remaining.Length; i++) { uint lane = remaining[i]; acc += lane * Prime32_5; acc = BitOperations.RotateLeft(acc, 11); acc *= Prime32_1; } acc ^= (acc >> 15); acc *= Prime32_2; acc ^= (acc >> 13); acc *= Prime32_3; acc ^= (acc >> 16); return acc; } } } } ================================================ FILE: src/Orleans.CodeGenerator/Hashing/XxHash32.cs ================================================ #nullable enable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers.Binary; // Implemented from the specification at // https://github.com/Cyan4973/xxHash/blob/f9155bd4c57e2270a4ffbb176485e5d713de1c9b/doc/xxhash_spec.md namespace Orleans.CodeGenerator.Hashing { /// /// Provides an implementation of the XxHash32 algorithm. /// internal sealed partial class XxHash32 : NonCryptographicHashAlgorithm { private const int HashSize = sizeof(uint); private const int StripeSize = 4 * sizeof(uint); private readonly uint _seed; private State _state; private byte[]? _holdback; private int _length; /// /// Initializes a new instance of the class. /// /// /// The XxHash32 algorithm supports an optional seed value. /// Instances created with this constructor use the default seed, zero. /// public XxHash32() : this(0) { } /// /// Initializes a new instance of the class with /// a specified seed. /// /// /// The hash seed value for computations from this instance. /// public XxHash32(int seed) : base(HashSize) { _seed = (uint)seed; Reset(); } /// /// Resets the hash computation to the initial state. /// public override void Reset() { _state = new State(_seed); _length = 0; } /// /// Appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. public override void Append(ReadOnlySpan source) { // Every time we've read 16 bytes, process the stripe. // Data that isn't perfectly mod-16 gets stored in a holdback // buffer. int held = _length & 0x0F; if (held != 0) { int remain = StripeSize - held; if (source.Length >= remain) { source.Slice(0, remain).CopyTo(_holdback.AsSpan(held)); _state.ProcessStripe(_holdback); source = source.Slice(remain); _length += remain; } else { source.CopyTo(_holdback.AsSpan(held)); _length += source.Length; return; } } while (source.Length >= StripeSize) { _state.ProcessStripe(source); source = source.Slice(StripeSize); _length += StripeSize; } if (source.Length > 0) { _holdback ??= new byte[StripeSize]; source.CopyTo(_holdback); _length += source.Length; } } /// /// Writes the computed hash value to /// without modifying accumulated state. /// protected override void GetCurrentHashCore(Span destination) { int remainingLength = _length & 0x0F; ReadOnlySpan remaining = ReadOnlySpan.Empty; if (remainingLength > 0) { remaining = new ReadOnlySpan(_holdback, 0, remainingLength); } uint acc = _state.Complete(_length, remaining); BinaryPrimitives.WriteUInt32BigEndian(destination, acc); } /// /// Computes the XxHash32 hash of the provided data. /// /// The data to hash. /// The XxHash32 hash of the provided data. /// /// is . /// public static byte[] Hash(byte[] source) { if (source is null) { throw new ArgumentNullException(nameof(source)); } return Hash(new ReadOnlySpan(source)); } /// /// Computes the XxHash32 hash of the provided data using the provided seed. /// /// The data to hash. /// The seed value for this hash computation. /// The XxHash32 hash of the provided data. /// /// is . /// public static byte[] Hash(byte[] source, int seed) { if (source is null) { throw new ArgumentNullException(nameof(source)); } return Hash(new ReadOnlySpan(source), seed); } /// /// Computes the XxHash32 hash of the provided data. /// /// The data to hash. /// The seed value for this hash computation. The default is zero. /// The XxHash32 hash of the provided data. public static byte[] Hash(ReadOnlySpan source, int seed = 0) { byte[] ret = new byte[HashSize]; StaticHash(source, ret, seed); return ret; } /// /// Attempts to compute the XxHash32 hash of the provided data into the provided destination. /// /// The data to hash. /// The buffer that receives the computed hash value. /// /// On success, receives the number of bytes written to . /// /// The seed value for this hash computation. The default is zero. /// /// if is long enough to receive /// the computed hash value (4 bytes); otherwise, . /// public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten, int seed = 0) { if (destination.Length < HashSize) { bytesWritten = 0; return false; } bytesWritten = StaticHash(source, destination, seed); return true; } /// /// Computes the XxHash32 hash of the provided data into the provided destination. /// /// The data to hash. /// The buffer that receives the computed hash value. /// The seed value for this hash computation. The default is zero. /// /// The number of bytes written to . /// public static int Hash(ReadOnlySpan source, Span destination, int seed = 0) { if (destination.Length < HashSize) throw new ArgumentException(@"destination too short", nameof(destination)); return StaticHash(source, destination, seed); } private static int StaticHash(ReadOnlySpan source, Span destination, int seed) { int totalLength = source.Length; State state = new State((uint)seed); while (source.Length >= StripeSize) { state.ProcessStripe(source); source = source.Slice(StripeSize); } uint val = state.Complete(totalLength, source); BinaryPrimitives.WriteUInt32BigEndian(destination, val); return HashSize; } } } ================================================ FILE: src/Orleans.CodeGenerator/InvokableGenerator.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.Linq; using Orleans.CodeGenerator.Diagnostics; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using System.Linq.Expressions; namespace Orleans.CodeGenerator { /// /// Generates RPC stub objects called invokers. /// internal class InvokableGenerator { private readonly CodeGenerator _codeGenerator; public InvokableGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } private LibraryTypes LibraryTypes => _codeGenerator.LibraryTypes; public GeneratedInvokableDescription Generate(InvokableMethodDescription invokableMethodInfo) { var method = invokableMethodInfo.Method; var generatedClassName = GetSimpleClassName(invokableMethodInfo); var baseClassType = GetBaseClassType(invokableMethodInfo); var fieldDescriptions = GetFieldDescriptions(invokableMethodInfo); var fields = GetFieldDeclarations(invokableMethodInfo, fieldDescriptions); var (ctor, ctorArgs) = GenerateConstructor(generatedClassName, invokableMethodInfo, baseClassType); var accessibility = GetAccessibility(method); var compoundTypeAliases = GetCompoundTypeAliasAttributeArguments(invokableMethodInfo, invokableMethodInfo.Key); List serializationHooks = new(); if (baseClassType.GetAttributes(LibraryTypes.SerializationCallbacksAttribute, out var hookAttributes)) { foreach (var hookAttribute in hookAttributes) { var hookType = (INamedTypeSymbol)hookAttribute.ConstructorArguments[0].Value; serializationHooks.Add(hookType); } } var targetField = fieldDescriptions.OfType().Single(); var accessibilityKind = accessibility switch { Accessibility.Public => SyntaxKind.PublicKeyword, _ => SyntaxKind.InternalKeyword, }; var classDeclaration = GetClassDeclarationSyntax( invokableMethodInfo, generatedClassName, baseClassType, fieldDescriptions, fields, ctor, compoundTypeAliases, targetField, accessibilityKind); string returnValueInitializerMethod = null; if (baseClassType.GetAttribute(LibraryTypes.ReturnValueProxyAttribute) is { ConstructorArguments: { Length: > 0 } attrArgs }) { returnValueInitializerMethod = (string)attrArgs[0].Value; } while (baseClassType.HasAttribute(LibraryTypes.SerializerTransparentAttribute)) { baseClassType = baseClassType.BaseType; } var invokerDescription = new GeneratedInvokableDescription( invokableMethodInfo, accessibility, generatedClassName, CodeGenerator.GetGeneratedNamespaceName(invokableMethodInfo.ContainingInterface), fieldDescriptions.OfType().ToList(), serializationHooks, baseClassType, ctorArgs, compoundTypeAliases, returnValueInitializerMethod, classDeclaration); return invokerDescription; static Accessibility GetAccessibility(IMethodSymbol methodSymbol) { Accessibility accessibility = methodSymbol.DeclaredAccessibility; var t = methodSymbol.ContainingType; while (t is not null) { if ((int)t.DeclaredAccessibility < (int)accessibility) { accessibility = t.DeclaredAccessibility; } t = t.ContainingType; } return accessibility; } } private ClassDeclarationSyntax GetClassDeclarationSyntax( InvokableMethodDescription method, string generatedClassName, INamedTypeSymbol baseClassType, List fieldDescriptions, MemberDeclarationSyntax[] fields, ConstructorDeclarationSyntax ctor, List compoundTypeAliases, TargetFieldDescription targetField, SyntaxKind accessibilityKind) { var classDeclaration = ClassDeclaration(generatedClassName) .AddBaseListTypes(SimpleBaseType(baseClassType.ToTypeSyntax(method.TypeParameterSubstitutions))) .AddModifiers(Token(accessibilityKind), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()) .AddMembers(fields); foreach (var alias in compoundTypeAliases) { classDeclaration = classDeclaration.AddAttributeLists( AttributeList(SingletonSeparatedList(GetCompoundTypeAliasAttribute(alias)))); } if (ctor != null) { classDeclaration = classDeclaration.AddMembers(ctor); } if (method.ResponseTimeoutTicks.HasValue) { classDeclaration = classDeclaration.AddMembers(GenerateResponseTimeoutPropertyMembers(method.ResponseTimeoutTicks.Value)); } classDeclaration = AddOptionalMembers(classDeclaration, GenerateGetArgumentCount(method), GenerateGetMethodName(method), GenerateGetInterfaceName(method), GenerateGetActivityName(method), GenerateGetInterfaceType(method), GenerateGetMethod(), GenerateSetTargetMethod(method, targetField, fieldDescriptions), GenerateGetTargetMethod(targetField), GenerateDisposeMethod(fieldDescriptions, baseClassType), GenerateGetArgumentMethod(method, fieldDescriptions), GenerateSetArgumentMethod(method, fieldDescriptions), GenerateInvokeInnerMethod(method, fieldDescriptions, targetField), GenerateGetCancellationTokenMethod(method, fieldDescriptions), GenerateTryCancelMethod(method, fieldDescriptions), GenerateIsCancellableProperty(method)); if (method.AllTypeParameters.Count > 0) { classDeclaration = SyntaxFactoryUtility.AddGenericTypeParameters(classDeclaration, method.AllTypeParameters); } return classDeclaration; } private MemberDeclarationSyntax[] GenerateResponseTimeoutPropertyMembers(long value) { var timespanField = FieldDeclaration( VariableDeclaration( LibraryTypes.TimeSpan.ToTypeSyntax(), SingletonSeparatedList(VariableDeclarator("_responseTimeoutValue") .WithInitializer(EqualsValueClause( InvocationExpression( IdentifierName("global::System.TimeSpan").Member("FromTicks"), ArgumentList(SeparatedList(new[] { Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(value))) })))))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); var responseTimeoutProperty = MethodDeclaration(NullableType(LibraryTypes.TimeSpan.ToTypeSyntax()), "GetDefaultResponseTimeout") .WithExpressionBody(ArrowExpressionClause(IdentifierName("_responseTimeoutValue"))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)); return new MemberDeclarationSyntax[] { timespanField, responseTimeoutProperty }; } private ClassDeclarationSyntax AddOptionalMembers(ClassDeclarationSyntax decl, params MemberDeclarationSyntax[] items) => decl.WithMembers(decl.Members.AddRange(items.Where(i => i != null))); internal AttributeSyntax GetCompoundTypeAliasAttribute(CompoundTypeAliasComponent[] argValues) { var args = new AttributeArgumentSyntax[argValues.Length]; for (var i = 0; i < argValues.Length; i++) { ExpressionSyntax value; value = argValues[i].Value switch { string stringValue => LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(stringValue)), ITypeSymbol typeValue => TypeOfExpression(typeValue.ToOpenTypeSyntax()), _ => throw new InvalidOperationException($"Unsupported value") }; args[i] = AttributeArgument(value); } return Attribute(LibraryTypes.CompoundTypeAliasAttribute.ToNameSyntax()).AddArgumentListArguments(args); } internal static List GetCompoundTypeAliasAttributeArguments(InvokableMethodDescription methodDescription, InvokableMethodId invokableId) { var result = new List(2); var containingInterface = methodDescription.ContainingInterface; if (methodDescription.HasAlias) { result.Add(GetCompoundTypeAliasComponents(invokableId, containingInterface, methodDescription.MethodId)); } result.Add(GetCompoundTypeAliasComponents(invokableId, containingInterface, methodDescription.GeneratedMethodId)); return result; } public static CompoundTypeAliasComponent[] GetCompoundTypeAliasComponents( InvokableMethodId invokableId, INamedTypeSymbol containingInterface, string methodId) { var proxyBase = invokableId.ProxyBase; var proxyBaseComponents = proxyBase.CompositeAliasComponents; var extensionArgCount = proxyBase.IsExtension ? 1 : 0; var alias = new CompoundTypeAliasComponent[1 + proxyBaseComponents.Length + extensionArgCount + 2]; alias[0] = new("inv"); for (var i = 0; i < proxyBaseComponents.Length; i++) { alias[i + 1] = proxyBaseComponents[i]; } alias[1 + proxyBaseComponents.Length] = new(containingInterface); // For grain extensions, also explicitly include the method's containing type. // This is to distinguish between different extension methods with the same id (eg, alias) but different containing types. if (proxyBase.IsExtension) { alias[1 + proxyBaseComponents.Length + 1] = new(invokableId.Method.ContainingType); } alias[1 + proxyBaseComponents.Length + extensionArgCount + 1] = new(methodId); return alias; } private INamedTypeSymbol GetBaseClassType(InvokableMethodDescription method) { var methodReturnType = method.Method.ReturnType; if (methodReturnType is not INamedTypeSymbol namedMethodReturnType) { throw new OrleansGeneratorDiagnosticAnalysisException(InvalidRpcMethodReturnTypeDiagnostic.CreateDiagnostic(method)); } if (method.InvokableBaseTypes.TryGetValue(namedMethodReturnType, out var baseClassType)) { return baseClassType; } if (namedMethodReturnType.ConstructedFrom is { IsGenericType: true, IsUnboundGenericType: false } constructedFrom) { var unbound = constructedFrom.ConstructUnboundGenericType(); if (method.InvokableBaseTypes.TryGetValue(unbound, out baseClassType)) { return baseClassType.ConstructedFrom.Construct(namedMethodReturnType.TypeArguments.ToArray()); } } throw new OrleansGeneratorDiagnosticAnalysisException(InvalidRpcMethodReturnTypeDiagnostic.CreateDiagnostic(method)); } private MemberDeclarationSyntax GenerateSetTargetMethod( InvokableMethodDescription methodDescription, TargetFieldDescription targetField, List fieldDescriptions) { var holder = IdentifierName("holder"); var holderParameter = holder.Identifier; var containingInterface = methodDescription.ContainingInterface; var targetType = containingInterface.ToTypeSyntax(); var isExtension = methodDescription.Key.ProxyBase.IsExtension; var (name, args) = isExtension switch { true => ("GetComponent", SingletonSeparatedList(Argument(TypeOfExpression(targetType)))), _ => ("GetTarget", SeparatedList()) }; var getTarget = CastExpression( targetType, InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, holder, IdentifierName(name)), ArgumentList(args))); var member = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "SetTarget") .WithParameterList(ParameterList(SingletonSeparatedList(Parameter(holderParameter).WithType(LibraryTypes.ITargetHolder.ToTypeSyntax())))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))); var assignmentExpression = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(targetField.FieldName), getTarget); if (!methodDescription.IsCancellable) { return member.WithExpressionBody(ArrowExpressionClause(assignmentExpression)).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); } else { var ctsField = fieldDescriptions.First(f => f is CancellationTokenSourceFieldDescription); var cancellationTokenType = LibraryTypes.CancellationToken.ToTypeSyntax(); var ctField = fieldDescriptions.First(f => SymbolEqualityComparer.Default.Equals(LibraryTypes.CancellationToken, f.FieldType)); return member.WithBody(Block( new List() { ExpressionStatement(assignmentExpression), ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(ctsField.FieldName), ImplicitObjectCreationExpression())), ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(ctField.FieldName), IdentifierName(ctsField.FieldName).Member("Token"))) })); } } private static MethodDeclarationSyntax GenerateGetTargetMethod(TargetFieldDescription targetField) { return MethodDeclaration(PredefinedType(Token(SyntaxKind.ObjectKeyword)), "GetTarget") .WithParameterList(ParameterList()) .WithExpressionBody(ArrowExpressionClause(IdentifierName(targetField.FieldName))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))); } private MemberDeclarationSyntax GenerateGetCancellationTokenMethod(InvokableMethodDescription method, List fields) { if (!method.IsCancellable) { return null; } // Method to get the cancellationToken argument // C#: CancellationToken GetCancellationToken() => var cancellationTokenType = LibraryTypes.CancellationToken.ToTypeSyntax(); var cancellationTokenField = fields.First(f => SymbolEqualityComparer.Default.Equals(LibraryTypes.CancellationToken, f.FieldType)); var member = MethodDeclaration(cancellationTokenType, "GetCancellationToken") .WithExpressionBody(ArrowExpressionClause(cancellationTokenField.FieldName.ToIdentifierName())) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); return member; } private MemberDeclarationSyntax GenerateIsCancellableProperty(InvokableMethodDescription method) { if (!method.IsCancellable) { return null; } // Property to indicate if the invokable is cancellable // C#: public override bool IsCancellable => true; var member = PropertyDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "IsCancellable") .WithExpressionBody(ArrowExpressionClause(LiteralExpression(SyntaxKind.TrueLiteralExpression))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)); return member; } private MemberDeclarationSyntax GenerateTryCancelMethod(InvokableMethodDescription method, List fields) { if (!method.IsCancellable) { return null; } // Method to set the CancellationToken argument. // C#: // TryCancel() // { // if (_cts is { } cts) // { // cts.Cancel(false); // return true; // } // return false; // } var cancellationTokenField = fields.First(f => f is CancellationTokenSourceFieldDescription); var member = MethodDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "TryCancel") .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)) .WithBody(Block( IfStatement( IsPatternExpression( IdentifierName(cancellationTokenField.FieldName), RecursivePattern() .WithPropertyPatternClause(PropertyPatternClause()) .WithDesignation(SingleVariableDesignation(Identifier("cts")))), Block( ExpressionStatement(InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("cts"), IdentifierName("Cancel"))) .WithArgumentList(ArgumentList(SeparatedList([Argument(LiteralExpression(SyntaxKind.FalseLiteralExpression))])))), ReturnStatement(LiteralExpression(SyntaxKind.TrueLiteralExpression)))), ReturnStatement(LiteralExpression(SyntaxKind.FalseLiteralExpression)))); return member; } private MemberDeclarationSyntax GenerateGetArgumentMethod( InvokableMethodDescription methodDescription, List fields) { if (methodDescription.Method.Parameters.Length == 0) return null; var index = IdentifierName("index"); var cases = new List(); foreach (var field in fields) { if (field is not MethodParameterFieldDescription parameter) { continue; } // C#: case {index}: return {field} var label = CaseSwitchLabel( LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(parameter.ParameterOrdinal))); cases.Add( SwitchSection( SingletonList(label), new SyntaxList( ReturnStatement( IdentifierName(parameter.FieldName))))); } // C#: default: return OrleansGeneratedCodeHelper.InvokableThrowArgumentOutOfRange(index, {maxArgs}) var throwHelperMethod = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("OrleansGeneratedCodeHelper"), IdentifierName("InvokableThrowArgumentOutOfRange")); cases.Add( SwitchSection( SingletonList(DefaultSwitchLabel()), new SyntaxList( ReturnStatement( InvocationExpression( throwHelperMethod, ArgumentList( SeparatedList( new[] { Argument(index), Argument( LiteralExpression( SyntaxKind.NumericLiteralExpression, Literal( Math.Max(0, methodDescription.Method.Parameters.Length - 1)))) }))))))); var body = SwitchStatement(index, new SyntaxList(cases)); return MethodDeclaration(PredefinedType(Token(SyntaxKind.ObjectKeyword)), "GetArgument") .WithParameterList( ParameterList( SingletonSeparatedList( Parameter(Identifier("index")).WithType(PredefinedType(Token(SyntaxKind.IntKeyword)))))) .WithBody(Block(body)) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))); } private MemberDeclarationSyntax GenerateSetArgumentMethod( InvokableMethodDescription methodDescription, List fields) { if (methodDescription.Method.Parameters.Length == 0) return null; var index = IdentifierName("index"); var value = IdentifierName("value"); var cases = new List(); foreach (var field in fields) { if (field is not MethodParameterFieldDescription parameter) { continue; } // C#: case {index}: {field} = (TField)value; return; var label = CaseSwitchLabel( LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(parameter.ParameterOrdinal))); cases.Add( SwitchSection( SingletonList(label), new SyntaxList( new StatementSyntax[] { ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(parameter.FieldName), CastExpression(parameter.FieldType.ToTypeSyntax(methodDescription.TypeParameterSubstitutions), value))), ReturnStatement() }))); } // C#: default: OrleansGeneratedCodeHelper.InvokableThrowArgumentOutOfRange(index, {maxArgs}) var maxArgs = Math.Max(0, methodDescription.Method.Parameters.Length - 1); var throwHelperMethod = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("OrleansGeneratedCodeHelper"), IdentifierName("InvokableThrowArgumentOutOfRange")); cases.Add( SwitchSection( SingletonList(DefaultSwitchLabel()), new SyntaxList( new StatementSyntax[] { ExpressionStatement( InvocationExpression( throwHelperMethod, ArgumentList( SeparatedList( new[] { Argument(index), Argument( LiteralExpression( SyntaxKind.NumericLiteralExpression, Literal(maxArgs))) })))), ReturnStatement() }))); var body = SwitchStatement(index, new SyntaxList(cases)); return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "SetArgument") .WithParameterList( ParameterList( SeparatedList( new[] { Parameter(Identifier("index")).WithType(PredefinedType(Token(SyntaxKind.IntKeyword))), Parameter(Identifier("value")).WithType(PredefinedType(Token(SyntaxKind.ObjectKeyword))) } ))) .WithBody(Block(body)) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))); } private MemberDeclarationSyntax GenerateInvokeInnerMethod( InvokableMethodDescription method, List fields, TargetFieldDescription target) { var resultTask = IdentifierName("resultTask"); // C# var resultTask = this.target.{Method}({params}); var args = SeparatedList( fields.OfType() .OrderBy(p => p.ParameterOrdinal) .Select(p => Argument(IdentifierName(p.FieldName)))); ExpressionSyntax methodCall; if (method.MethodTypeParameters.Count > 0) { methodCall = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName(target.FieldName), GenericName( Identifier(method.Method.Name), TypeArgumentList( SeparatedList( method.MethodTypeParameters.Select(p => IdentifierName(p.Name)))))); } else { methodCall = IdentifierName(target.FieldName).Member(method.Method.Name); } return MethodDeclaration(method.Method.ReturnType.ToTypeSyntax(method.TypeParameterSubstitutions), "InvokeInner") .WithParameterList(ParameterList()) .WithExpressionBody(ArrowExpressionClause(InvocationExpression(methodCall, ArgumentList(args)))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .WithModifiers(TokenList(Token(SyntaxKind.ProtectedKeyword), Token(SyntaxKind.OverrideKeyword))); } private MemberDeclarationSyntax GenerateDisposeMethod( List fields, INamedTypeSymbol baseClassType) { var body = new List(); foreach (var field in fields) { if (field is CancellationTokenSourceFieldDescription ctsField) { // C# // _cts?.Dispose(); body.Add( ExpressionStatement( ConditionalAccessExpression( ctsField.FieldName.ToIdentifierName(), InvocationExpression( MemberBindingExpression(IdentifierName("Dispose")))))); } if (field.IsInstanceField) { body.Add( ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(field.FieldName), LiteralExpression(SyntaxKind.DefaultLiteralExpression)))); } } // C# base.Dispose(); if (baseClassType is { } && baseClassType.AllInterfaces.Any(i => i.SpecialType == SpecialType.System_IDisposable) && baseClassType.GetAllMembers("Dispose").FirstOrDefault(m => !m.IsAbstract && m.DeclaredAccessibility != Accessibility.Private) is { }) { body.Add(ExpressionStatement(InvocationExpression(BaseExpression().Member("Dispose")).WithArgumentList(ArgumentList()))); } return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Dispose") .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithBody(Block(body)); } private MemberDeclarationSyntax GenerateGetArgumentCount(InvokableMethodDescription methodDescription) => methodDescription.Method.Parameters.Length is var count and not 0 ? MethodDeclaration(PredefinedType(Token(SyntaxKind.IntKeyword)), "GetArgumentCount") .WithExpressionBody(ArrowExpressionClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(count)))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) : null; private MemberDeclarationSyntax GenerateGetActivityName(InvokableMethodDescription methodDescription) { // This property is intended to contain a value suitable for use as an OpenTelemetry Span Name for RPC calls. // Therefore, the interface name and method name components must not include periods or slashes. // In order to avoid that, we omit the namespace from the interface name. // See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md var interfaceName = methodDescription.Method.ContainingType.ToDisplayName(methodDescription.TypeParameterSubstitutions, includeGlobalSpecifier: false, includeNamespace: false); var methodName = methodDescription.Method.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var activityName = $"{interfaceName}/{methodName}"; return MethodDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), "GetActivityName") .WithExpressionBody( ArrowExpressionClause( LiteralExpression( SyntaxKind.NumericLiteralExpression, Literal(activityName)))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); } private MemberDeclarationSyntax GenerateGetMethodName( InvokableMethodDescription methodDescription) => MethodDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), "GetMethodName") .WithExpressionBody( ArrowExpressionClause( LiteralExpression( SyntaxKind.NumericLiteralExpression, Literal(methodDescription.Method.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); private MemberDeclarationSyntax GenerateGetInterfaceName( InvokableMethodDescription methodDescription) => MethodDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), "GetInterfaceName") .WithExpressionBody( ArrowExpressionClause( LiteralExpression( SyntaxKind.NumericLiteralExpression, Literal(methodDescription.Method.ContainingType.ToDisplayName(methodDescription.TypeParameterSubstitutions, includeGlobalSpecifier: false))))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); private MemberDeclarationSyntax GenerateGetInterfaceType( InvokableMethodDescription methodDescription) => MethodDeclaration(LibraryTypes.Type.ToTypeSyntax(), "GetInterfaceType") .WithExpressionBody( ArrowExpressionClause( TypeOfExpression(methodDescription.Method.ContainingType.ToTypeSyntax(methodDescription.TypeParameterSubstitutions)))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); private MemberDeclarationSyntax GenerateGetMethod() => MethodDeclaration(LibraryTypes.MethodInfo.ToTypeSyntax(), "GetMethod") .WithExpressionBody(ArrowExpressionClause(IdentifierName("MethodBackingField"))) .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); public static string GetSimpleClassName(InvokableMethodDescription method) { var genericArity = method.AllTypeParameters.Count; var typeArgs = genericArity > 0 ? "_" + genericArity : string.Empty; var proxyKey = method.ProxyBase.Key.GeneratedClassNameComponent; return $"Invokable_{method.ContainingInterface.Name}_{proxyKey}_{method.GeneratedMethodId}{typeArgs}"; } private MemberDeclarationSyntax[] GetFieldDeclarations( InvokableMethodDescription method, List fieldDescriptions) { return fieldDescriptions.Select(GetFieldDeclaration).ToArray(); MemberDeclarationSyntax GetFieldDeclaration(InvokerFieldDescription description) { FieldDeclarationSyntax field; if (description is MethodInfoFieldDescription methodInfo) { var methodTypeArguments = GetTypesArray(method, method.MethodTypeParameters.Select(p => p.Parameter)); var parameterTypes = GetTypesArray(method, method.Method.Parameters.Select(p => p.Type)); field = FieldDeclaration( VariableDeclaration( LibraryTypes.MethodInfo.ToTypeSyntax(), SingletonSeparatedList(VariableDeclarator(description.FieldName) .WithInitializer(EqualsValueClause( InvocationExpression( IdentifierName("OrleansGeneratedCodeHelper").Member("GetMethodInfoOrDefault"), ArgumentList(SeparatedList(new[] { Argument(TypeOfExpression(method.Method.ContainingType.ToTypeSyntax(method.TypeParameterSubstitutions))), Argument(method.Method.Name.GetLiteralExpression()), Argument(methodTypeArguments), Argument(parameterTypes), })))))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); } else { field = FieldDeclaration( VariableDeclaration( description.FieldType.ToTypeSyntax(method.TypeParameterSubstitutions), SingletonSeparatedList(VariableDeclarator(description.FieldName)))); } switch (description) { case MethodParameterFieldDescription _: field = field.AddModifiers(Token(SyntaxKind.PublicKeyword)); break; } return field; } } private ExpressionSyntax GetTypesArray(InvokableMethodDescription method, IEnumerable typeSymbols) { var types = typeSymbols.ToArray(); return types.Length == 0 ? LiteralExpression(SyntaxKind.NullLiteralExpression) : ImplicitArrayCreationExpression(InitializerExpression(SyntaxKind.ArrayInitializerExpression, SeparatedList( types.Select(t => TypeOfExpression(t.ToTypeSyntax(method.TypeParameterSubstitutions)))))); } private (ConstructorDeclarationSyntax Constructor, List ConstructorArguments) GenerateConstructor( string simpleClassName, InvokableMethodDescription method, INamedTypeSymbol baseClassType) { var parameters = new List(); var body = new List(); List constructorArgumentTypes = new(); List baseConstructorArguments = new(); foreach (var constructor in baseClassType.GetAllMembers()) { if (constructor.MethodKind != MethodKind.Constructor || constructor.DeclaredAccessibility == Accessibility.Private || constructor.IsImplicitlyDeclared) { continue; } if (constructor.HasAttribute(LibraryTypes.GeneratedActivatorConstructorAttribute)) { var index = 0; foreach (var parameter in constructor.Parameters) { var identifier = $"base{index}"; var argumentType = parameter.Type.ToTypeSyntax(method.TypeParameterSubstitutions); constructorArgumentTypes.Add(argumentType); parameters.Add(Parameter(identifier.ToIdentifier()).WithType(argumentType)); baseConstructorArguments.Add(Argument(identifier.ToIdentifierName())); index++; } break; } } foreach (var (methodName, methodArgument) in method.CustomInitializerMethods) { var argumentExpression = methodArgument.ToExpression(); body.Add(ExpressionStatement(InvocationExpression(IdentifierName(methodName), ArgumentList(SeparatedList(new[] { Argument(argumentExpression) }))))); } if (body.Count == 0 && parameters.Count == 0) return default; var constructorDeclaration = ConstructorDeclaration(simpleClassName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters.ToArray()) .WithInitializer( ConstructorInitializer( SyntaxKind.BaseConstructorInitializer, ArgumentList(SeparatedList(baseConstructorArguments)))) .AddBodyStatements(body.ToArray()); return (constructorDeclaration, constructorArgumentTypes); } private List GetFieldDescriptions(InvokableMethodDescription method) { var fields = new List(); uint fieldId = 0; foreach (var parameter in method.Method.Parameters) { var isSerializable = !SymbolEqualityComparer.Default.Equals(LibraryTypes.CancellationToken, parameter.Type); fields.Add(new MethodParameterFieldDescription(method.CodeGenerator, parameter, $"arg{fieldId}", fieldId, method.TypeParameterSubstitutions, isSerializable)); fieldId++; } fields.Add(new TargetFieldDescription(method.Method.ContainingType)); fields.Add(new MethodInfoFieldDescription(LibraryTypes.MethodInfo, "MethodBackingField")); if (method.IsCancellable) { fields.Add(new CancellationTokenSourceFieldDescription(LibraryTypes)); } return fields; } internal abstract class InvokerFieldDescription { protected InvokerFieldDescription(ITypeSymbol fieldType, string fieldName) { FieldType = fieldType; FieldName = fieldName; } public ITypeSymbol FieldType { get; } public string FieldName { get; } public abstract bool IsSerializable { get; } public abstract bool IsInstanceField { get; } } internal sealed class TargetFieldDescription : InvokerFieldDescription { public TargetFieldDescription(ITypeSymbol fieldType) : base(fieldType, "_target") { } public override bool IsSerializable => false; public override bool IsInstanceField => true; } internal sealed class CancellationTokenSourceFieldDescription(LibraryTypes libraryTypes) : InvokerFieldDescription(libraryTypes.CancellationTokenSource, "_cts") { public override bool IsSerializable => false; public override bool IsInstanceField => true; } internal sealed class CancellationTokenFieldDescription(LibraryTypes libraryTypes) : InvokerFieldDescription(libraryTypes.CancellationToken, "_ct") { public override bool IsSerializable => false; public override bool IsInstanceField => true; } internal class MethodParameterFieldDescription : InvokerFieldDescription, IMemberDescription { public MethodParameterFieldDescription( CodeGenerator codeGenerator, IParameterSymbol parameter, string fieldName, uint fieldId, Dictionary typeParameterSubstitutions, bool isSerializable) : base(parameter.Type, fieldName) { TypeParameterSubstitutions = typeParameterSubstitutions; FieldId = fieldId; CodeGenerator = codeGenerator; Parameter = parameter; if (parameter.Type.TypeKind == TypeKind.Dynamic) { TypeSyntax = PredefinedType(Token(SyntaxKind.ObjectKeyword)); TypeName = "dynamic"; } else { TypeName = Type.ToDisplayName(TypeParameterSubstitutions); TypeSyntax = Type.ToTypeSyntax(TypeParameterSubstitutions); } Symbol = parameter; IsSerializable = isSerializable; } public CodeGenerator CodeGenerator { get; } public ISymbol Symbol { get; } public Dictionary TypeParameterSubstitutions { get; } public int ParameterOrdinal => Parameter.Ordinal; public uint FieldId { get; } public ISymbol Member => Parameter; public ITypeSymbol Type => FieldType; public INamedTypeSymbol ContainingType => Parameter.ContainingType; public TypeSyntax TypeSyntax { get; } public IParameterSymbol Parameter { get; } public override bool IsSerializable { get; } public bool IsCopyable => true; public override bool IsInstanceField => true; public string AssemblyName => Parameter.Type.ContainingAssembly.ToDisplayName(); public string TypeName { get; } public string TypeNameIdentifier { get { if (Type is ITypeParameterSymbol tp && TypeParameterSubstitutions.TryGetValue(tp, out var name)) { return name; } return Type.GetValidIdentifier(); } } public bool IsPrimaryConstructorParameter => false; public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol) => typeSymbol.ToTypeSyntax(TypeParameterSubstitutions); } internal sealed class MethodInfoFieldDescription : InvokerFieldDescription { public MethodInfoFieldDescription(ITypeSymbol fieldType, string fieldName) : base(fieldType, fieldName) { } public override bool IsSerializable => false; public override bool IsInstanceField => false; } } } ================================================ FILE: src/Orleans.CodeGenerator/LibraryTypes.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Orleans.CodeGenerator.SyntaxGeneration; namespace Orleans.CodeGenerator { internal sealed class LibraryTypes { private readonly ConcurrentDictionary _shallowCopyableTypes = new(SymbolEqualityComparer.Default); public static LibraryTypes FromCompilation(Compilation compilation, CodeGeneratorOptions options) => new LibraryTypes(compilation, options); private LibraryTypes(Compilation compilation, CodeGeneratorOptions options) { Compilation = compilation; ApplicationPartAttribute = Type("Orleans.ApplicationPartAttribute"); Action_2 = Type("System.Action`2"); TypeManifestProviderBase = Type("Orleans.Serialization.Configuration.TypeManifestProviderBase"); Field = Type("Orleans.Serialization.WireProtocol.Field"); FieldCodec_1 = Type("Orleans.Serialization.Codecs.IFieldCodec`1"); AbstractTypeSerializer = Type("Orleans.Serialization.Serializers.AbstractTypeSerializer`1"); DeepCopier_1 = Type("Orleans.Serialization.Cloning.IDeepCopier`1"); ShallowCopier = Type("Orleans.Serialization.Cloning.ShallowCopier`1"); CompoundTypeAliasAttribute = Type("Orleans.CompoundTypeAliasAttribute"); CopyContext = Type("Orleans.Serialization.Cloning.CopyContext"); MethodInfo = Type("System.Reflection.MethodInfo"); Func_2 = Type("System.Func`2"); GenerateMethodSerializersAttribute = Type("Orleans.GenerateMethodSerializersAttribute"); GenerateSerializerAttribute = Type("Orleans.GenerateSerializerAttribute"); SerializationCallbacksAttribute = Type("Orleans.SerializationCallbacksAttribute"); IActivator_1 = Type("Orleans.Serialization.Activators.IActivator`1"); IBufferWriter = Type("System.Buffers.IBufferWriter`1"); IdAttributeType = Type(CodeGeneratorOptions.IdAttribute); ConstructorAttributeTypes = CodeGeneratorOptions.ConstructorAttributes.Select(Type).ToArray(); AliasAttribute = Type("Orleans.AliasAttribute"); IInvokable = Type("Orleans.Serialization.Invocation.IInvokable"); InvokeMethodNameAttribute = Type("Orleans.InvokeMethodNameAttribute"); RuntimeHelpers = Type("System.Runtime.CompilerServices.RuntimeHelpers"); InvokableCustomInitializerAttribute = Type("Orleans.InvokableCustomInitializerAttribute"); DefaultInvokableBaseTypeAttribute = Type("Orleans.DefaultInvokableBaseTypeAttribute"); GenerateCodeForDeclaringAssemblyAttribute = Type("Orleans.GenerateCodeForDeclaringAssemblyAttribute"); InvokableBaseTypeAttribute = Type("Orleans.InvokableBaseTypeAttribute"); ReturnValueProxyAttribute = Type("Orleans.Invocation.ReturnValueProxyAttribute"); RegisterSerializerAttribute = Type("Orleans.RegisterSerializerAttribute"); ResponseTimeoutAttribute = Type("Orleans.ResponseTimeoutAttribute"); GeneratedActivatorConstructorAttribute = Type("Orleans.GeneratedActivatorConstructorAttribute"); SerializerTransparentAttribute = Type("Orleans.SerializerTransparentAttribute"); RegisterActivatorAttribute = Type("Orleans.RegisterActivatorAttribute"); RegisterConverterAttribute = Type("Orleans.RegisterConverterAttribute"); RegisterCopierAttribute = Type("Orleans.RegisterCopierAttribute"); UseActivatorAttribute = Type("Orleans.UseActivatorAttribute"); SuppressReferenceTrackingAttribute = Type("Orleans.SuppressReferenceTrackingAttribute"); OmitDefaultMemberValuesAttribute = Type("Orleans.OmitDefaultMemberValuesAttribute"); ITargetHolder = Type("Orleans.Serialization.Invocation.ITargetHolder"); TypeManifestProviderAttribute = Type("Orleans.Serialization.Configuration.TypeManifestProviderAttribute"); NonSerializedAttribute = Type("System.NonSerializedAttribute"); ObsoleteAttribute = Type("System.ObsoleteAttribute"); BaseCodec_1 = Type("Orleans.Serialization.Serializers.IBaseCodec`1"); BaseCopier_1 = Type("Orleans.Serialization.Cloning.IBaseCopier`1"); ArrayCodec = Type("Orleans.Serialization.Codecs.ArrayCodec`1"); ArrayCopier = Type("Orleans.Serialization.Codecs.ArrayCopier`1"); Reader = Type("Orleans.Serialization.Buffers.Reader`1"); TypeManifestOptions = Type("Orleans.Serialization.Configuration.TypeManifestOptions"); Task = Type("System.Threading.Tasks.Task"); Task_1 = Type("System.Threading.Tasks.Task`1"); this.Type = Type("System.Type"); _uri = Type("System.Uri"); _int128 = TypeOrDefault("System.Int128"); _uInt128 = TypeOrDefault("System.UInt128"); _half = TypeOrDefault("System.Half"); _dateOnly = TypeOrDefault("System.DateOnly"); _dateTimeOffset = Type("System.DateTimeOffset"); _bitVector32 = Type("System.Collections.Specialized.BitVector32"); _compareInfo = Type("System.Globalization.CompareInfo"); _cultureInfo = Type("System.Globalization.CultureInfo"); _version = Type("System.Version"); _timeOnly = TypeOrDefault("System.TimeOnly"); Guid = Type("System.Guid"); ICodecProvider = Type("Orleans.Serialization.Serializers.ICodecProvider"); ValueSerializer = Type("Orleans.Serialization.Serializers.IValueSerializer`1"); ValueTask = Type("System.Threading.Tasks.ValueTask"); ValueTask_1 = Type("System.Threading.Tasks.ValueTask`1"); ValueTypeGetter_2 = Type("Orleans.Serialization.Utilities.ValueTypeGetter`2"); ValueTypeSetter_2 = Type("Orleans.Serialization.Utilities.ValueTypeSetter`2"); Writer = Type("Orleans.Serialization.Buffers.Writer`1"); FSharpSourceConstructFlagsOrDefault = TypeOrDefault("Microsoft.FSharp.Core.SourceConstructFlags"); FSharpCompilationMappingAttributeOrDefault = TypeOrDefault("Microsoft.FSharp.Core.CompilationMappingAttribute"); StaticCodecs = new List { new(compilation.GetSpecialType(SpecialType.System_Object), Type("Orleans.Serialization.Codecs.ObjectCodec")), new(compilation.GetSpecialType(SpecialType.System_Boolean), Type("Orleans.Serialization.Codecs.BoolCodec")), new(compilation.GetSpecialType(SpecialType.System_Char), Type("Orleans.Serialization.Codecs.CharCodec")), new(compilation.GetSpecialType(SpecialType.System_Byte), Type("Orleans.Serialization.Codecs.ByteCodec")), new(compilation.GetSpecialType(SpecialType.System_SByte), Type("Orleans.Serialization.Codecs.SByteCodec")), new(compilation.GetSpecialType(SpecialType.System_Int16), Type("Orleans.Serialization.Codecs.Int16Codec")), new(compilation.GetSpecialType(SpecialType.System_Int32), Type("Orleans.Serialization.Codecs.Int32Codec")), new(compilation.GetSpecialType(SpecialType.System_Int64), Type("Orleans.Serialization.Codecs.Int64Codec")), new(compilation.GetSpecialType(SpecialType.System_UInt16), Type("Orleans.Serialization.Codecs.UInt16Codec")), new(compilation.GetSpecialType(SpecialType.System_UInt32), Type("Orleans.Serialization.Codecs.UInt32Codec")), new(compilation.GetSpecialType(SpecialType.System_UInt64), Type("Orleans.Serialization.Codecs.UInt64Codec")), new(compilation.GetSpecialType(SpecialType.System_String), Type("Orleans.Serialization.Codecs.StringCodec")), new(compilation.CreateArrayTypeSymbol(compilation.GetSpecialType(SpecialType.System_Byte), 1), Type("Orleans.Serialization.Codecs.ByteArrayCodec")), new(compilation.GetSpecialType(SpecialType.System_Single), Type("Orleans.Serialization.Codecs.FloatCodec")), new(compilation.GetSpecialType(SpecialType.System_Double), Type("Orleans.Serialization.Codecs.DoubleCodec")), new(compilation.GetSpecialType(SpecialType.System_Decimal), Type("Orleans.Serialization.Codecs.DecimalCodec")), new(compilation.GetSpecialType(SpecialType.System_DateTime), Type("Orleans.Serialization.Codecs.DateTimeCodec")), new(Type("System.TimeSpan"), Type("Orleans.Serialization.Codecs.TimeSpanCodec")), new(Type("System.DateTimeOffset"), Type("Orleans.Serialization.Codecs.DateTimeOffsetCodec")), new(TypeOrDefault("System.DateOnly"), TypeOrDefault("Orleans.Serialization.Codecs.DateOnlyCodec")), new(TypeOrDefault("System.TimeOnly"), TypeOrDefault("Orleans.Serialization.Codecs.TimeOnlyCodec")), new(Type("System.Guid"), Type("Orleans.Serialization.Codecs.GuidCodec")), new(Type("System.Type"), Type("Orleans.Serialization.Codecs.TypeSerializerCodec")), new(Type("System.ReadOnlyMemory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.ReadOnlyMemoryOfByteCodec")), new(Type("System.Memory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.MemoryOfByteCodec")), new(Type("System.Net.IPAddress"), Type("Orleans.Serialization.Codecs.IPAddressCodec")), new(Type("System.Net.IPEndPoint"), Type("Orleans.Serialization.Codecs.IPEndPointCodec")), new(TypeOrDefault("System.UInt128"), TypeOrDefault("Orleans.Serialization.Codecs.UInt128Codec")), new(TypeOrDefault("System.Int128"), TypeOrDefault("Orleans.Serialization.Codecs.Int128Codec")), new(TypeOrDefault("System.Half"), TypeOrDefault("Orleans.Serialization.Codecs.HalfCodec")), new(Type("System.Uri"), Type("Orleans.Serialization.Codecs.UriCodec")), }.Where(desc => desc.UnderlyingType is { } && desc.CodecType is { }).ToArray(); WellKnownCodecs = new WellKnownCodecDescription[] { new(Type("System.Exception"), Type("Orleans.Serialization.ExceptionCodec")), new(Type("System.Collections.Generic.Dictionary`2"), Type("Orleans.Serialization.Codecs.DictionaryCodec`2")), new(Type("System.Collections.Generic.List`1"), Type("Orleans.Serialization.Codecs.ListCodec`1")), new(Type("System.Collections.Generic.HashSet`1"), Type("Orleans.Serialization.Codecs.HashSetCodec`1")), new(compilation.GetSpecialType(SpecialType.System_Nullable_T), Type("Orleans.Serialization.Codecs.NullableCodec`1")), }; StaticCopiers = new WellKnownCopierDescription[] { new(compilation.GetSpecialType(SpecialType.System_Object), Type("Orleans.Serialization.Codecs.ObjectCopier")), new(compilation.CreateArrayTypeSymbol(compilation.GetSpecialType(SpecialType.System_Byte), 1), Type("Orleans.Serialization.Codecs.ByteArrayCopier")), new(Type("System.ReadOnlyMemory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.ReadOnlyMemoryOfByteCopier")), new(Type("System.Memory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.MemoryOfByteCopier")), }; WellKnownCopiers = new WellKnownCopierDescription[] { new(Type("System.Exception"), Type("Orleans.Serialization.ExceptionCodec")), new(Type("System.Collections.Generic.Dictionary`2"), Type("Orleans.Serialization.Codecs.DictionaryCopier`2")), new(Type("System.Collections.Generic.List`1"), Type("Orleans.Serialization.Codecs.ListCopier`1")), new(Type("System.Collections.Generic.HashSet`1"), Type("Orleans.Serialization.Codecs.HashSetCopier`1")), new(compilation.GetSpecialType(SpecialType.System_Nullable_T), Type("Orleans.Serialization.Codecs.NullableCopier`1")), }; Exception = Type("System.Exception"); ImmutableAttribute = Type(CodeGeneratorOptions.ImmutableAttribute); TimeSpan = Type("System.TimeSpan"); _ipAddress = Type("System.Net.IPAddress"); _ipEndPoint = Type("System.Net.IPEndPoint"); CancellationToken = Type("System.Threading.CancellationToken"); CancellationTokenSource = Type("System.Threading.CancellationTokenSource"); _immutableContainerTypes = new[] { compilation.GetSpecialType(SpecialType.System_Nullable_T), Type("System.Tuple`1"), Type("System.Tuple`2"), Type("System.Tuple`3"), Type("System.Tuple`4"), Type("System.Tuple`5"), Type("System.Tuple`6"), Type("System.Tuple`7"), Type("System.Tuple`8"), Type("System.ValueTuple`1"), Type("System.ValueTuple`2"), Type("System.ValueTuple`3"), Type("System.ValueTuple`4"), Type("System.ValueTuple`5"), Type("System.ValueTuple`6"), Type("System.ValueTuple`7"), Type("System.ValueTuple`8"), Type("System.Collections.Immutable.ImmutableArray`1"), Type("System.Collections.Immutable.ImmutableDictionary`2"), Type("System.Collections.Immutable.ImmutableHashSet`1"), Type("System.Collections.Immutable.ImmutableList`1"), Type("System.Collections.Immutable.ImmutableQueue`1"), Type("System.Collections.Immutable.ImmutableSortedDictionary`2"), Type("System.Collections.Immutable.ImmutableSortedSet`1"), Type("System.Collections.Immutable.ImmutableStack`1"), }; LanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion; INamedTypeSymbol Type(string metadataName) { var result = compilation.GetTypeByMetadataName(metadataName); if (result is null) { throw new InvalidOperationException("Cannot find type with metadata name " + metadataName); } return result; } INamedTypeSymbol? TypeOrDefault(string metadataName) { var result = compilation.GetTypeByMetadataName(metadataName); return result; } } public INamedTypeSymbol Action_2 { get; private set; } public INamedTypeSymbol TypeManifestProviderBase { get; private set; } public INamedTypeSymbol Field { get; private set; } public INamedTypeSymbol DeepCopier_1 { get; private set; } public INamedTypeSymbol ShallowCopier { get; private set; } public INamedTypeSymbol FieldCodec_1 { get; private set; } public INamedTypeSymbol AbstractTypeSerializer { get; private set; } public INamedTypeSymbol Func_2 { get; private set; } public INamedTypeSymbol CompoundTypeAliasAttribute { get; private set; } public INamedTypeSymbol GenerateMethodSerializersAttribute { get; private set; } public INamedTypeSymbol GenerateSerializerAttribute { get; private set; } public INamedTypeSymbol IActivator_1 { get; private set; } public INamedTypeSymbol IBufferWriter { get; private set; } public INamedTypeSymbol IInvokable { get; private set; } public INamedTypeSymbol ITargetHolder { get; private set; } public INamedTypeSymbol TypeManifestProviderAttribute { get; private set; } public INamedTypeSymbol NonSerializedAttribute { get; private set; } public INamedTypeSymbol ObsoleteAttribute { get; private set; } public INamedTypeSymbol BaseCodec_1 { get; private set; } public INamedTypeSymbol BaseCopier_1 { get; private set; } public INamedTypeSymbol ArrayCodec { get; private set; } public INamedTypeSymbol ArrayCopier { get; private set; } public INamedTypeSymbol Reader { get; private set; } public INamedTypeSymbol TypeManifestOptions { get; private set; } public INamedTypeSymbol Task { get; private set; } public INamedTypeSymbol Task_1 { get; private set; } public INamedTypeSymbol Type { get; private set; } private INamedTypeSymbol _uri; private INamedTypeSymbol? _dateOnly; private INamedTypeSymbol _dateTimeOffset; private INamedTypeSymbol? _timeOnly; public INamedTypeSymbol MethodInfo { get; private set; } public INamedTypeSymbol ICodecProvider { get; private set; } public INamedTypeSymbol ValueSerializer { get; private set; } public INamedTypeSymbol ValueTask { get; private set; } public INamedTypeSymbol ValueTask_1 { get; private set; } public INamedTypeSymbol ValueTypeGetter_2 { get; private set; } public INamedTypeSymbol ValueTypeSetter_2 { get; private set; } public INamedTypeSymbol Writer { get; private set; } public INamedTypeSymbol IdAttributeType { get; private set; } public INamedTypeSymbol[] ConstructorAttributeTypes { get; private set; } public INamedTypeSymbol AliasAttribute { get; private set; } public WellKnownCodecDescription[] StaticCodecs { get; private set; } public WellKnownCodecDescription[] WellKnownCodecs { get; private set; } public WellKnownCopierDescription[] StaticCopiers { get; private set; } public WellKnownCopierDescription[] WellKnownCopiers { get; private set; } public INamedTypeSymbol RegisterCopierAttribute { get; private set; } public INamedTypeSymbol RegisterSerializerAttribute { get; private set; } public INamedTypeSymbol ResponseTimeoutAttribute { get; private set; } public INamedTypeSymbol RegisterConverterAttribute { get; private set; } public INamedTypeSymbol RegisterActivatorAttribute { get; private set; } public INamedTypeSymbol UseActivatorAttribute { get; private set; } public INamedTypeSymbol SuppressReferenceTrackingAttribute { get; private set; } public INamedTypeSymbol OmitDefaultMemberValuesAttribute { get; private set; } public INamedTypeSymbol CopyContext { get; private set; } public INamedTypeSymbol CancellationToken { get; private set; } public INamedTypeSymbol CancellationTokenSource { get; } public INamedTypeSymbol Guid { get; private set; } public Compilation Compilation { get; private set; } public INamedTypeSymbol TimeSpan { get; private set; } private INamedTypeSymbol _ipAddress; private INamedTypeSymbol _ipEndPoint; private INamedTypeSymbol[] _immutableContainerTypes; private INamedTypeSymbol _bitVector32; private INamedTypeSymbol _compareInfo; private INamedTypeSymbol _cultureInfo; private INamedTypeSymbol _version; private INamedTypeSymbol? _int128; private INamedTypeSymbol? _uInt128; private INamedTypeSymbol? _half; private INamedTypeSymbol[]? _regularShallowCopyableTypes; private INamedTypeSymbol[] RegularShallowCopyableType => _regularShallowCopyableTypes ??= new List { TimeSpan, _dateOnly, _timeOnly, _dateTimeOffset, Guid, _bitVector32, _compareInfo, _cultureInfo, _version, _ipAddress, _ipEndPoint, CancellationToken, Type, _uri, _uInt128, _int128, _half }.Where(t => t is {}).ToArray()!; public INamedTypeSymbol ImmutableAttribute { get; private set; } public INamedTypeSymbol Exception { get; private set; } public INamedTypeSymbol ApplicationPartAttribute { get; private set; } public INamedTypeSymbol InvokeMethodNameAttribute { get; private set; } public INamedTypeSymbol InvokableCustomInitializerAttribute { get; private set; } public INamedTypeSymbol InvokableBaseTypeAttribute { get; private set; } public INamedTypeSymbol ReturnValueProxyAttribute { get; private set; } public INamedTypeSymbol DefaultInvokableBaseTypeAttribute { get; private set; } public INamedTypeSymbol GenerateCodeForDeclaringAssemblyAttribute { get; private set; } public INamedTypeSymbol SerializationCallbacksAttribute { get; private set; } public INamedTypeSymbol GeneratedActivatorConstructorAttribute { get; private set; } public INamedTypeSymbol SerializerTransparentAttribute { get; private set; } public INamedTypeSymbol? FSharpCompilationMappingAttributeOrDefault { get; private set; } public INamedTypeSymbol? FSharpSourceConstructFlagsOrDefault { get; private set; } public INamedTypeSymbol RuntimeHelpers { get; private set; } public LanguageVersion? LanguageVersion { get; private set; } public bool IsShallowCopyable(ITypeSymbol type) { switch (type.SpecialType) { case SpecialType.System_Boolean: case SpecialType.System_Char: case SpecialType.System_SByte: case SpecialType.System_Byte: case SpecialType.System_Int16: case SpecialType.System_UInt16: case SpecialType.System_Int32: case SpecialType.System_UInt32: case SpecialType.System_Int64: case SpecialType.System_UInt64: case SpecialType.System_Decimal: case SpecialType.System_Single: case SpecialType.System_Double: case SpecialType.System_String: case SpecialType.System_DateTime: return true; } if (_shallowCopyableTypes.TryGetValue(type, out var result)) { return result; } foreach (var shallowCopyable in RegularShallowCopyableType) { if (SymbolEqualityComparer.Default.Equals(shallowCopyable, type)) { return _shallowCopyableTypes[type] = true; } } if (type.IsSealed && type.HasAttribute(ImmutableAttribute)) { return _shallowCopyableTypes[type] = true; } if (type.HasBaseType(Exception)) { return _shallowCopyableTypes[type] = true; } if (!(type is INamedTypeSymbol namedType)) { return _shallowCopyableTypes[type] = false; } if (namedType.IsTupleType) { return _shallowCopyableTypes[type] = AreShallowCopyable(namedType.TupleElements); } else if (namedType.IsGenericType) { var def = namedType.ConstructedFrom; foreach (var t in _immutableContainerTypes) { if (SymbolEqualityComparer.Default.Equals(t, def)) return _shallowCopyableTypes[type] = AreShallowCopyable(namedType.TypeArguments); } } else { if (type.TypeKind == TypeKind.Enum) { return _shallowCopyableTypes[type] = true; } if (type.TypeKind == TypeKind.Struct && !namedType.IsUnboundGenericType) { return _shallowCopyableTypes[type] = IsValueTypeFieldsShallowCopyable(type); } } return _shallowCopyableTypes[type] = false; } private bool IsValueTypeFieldsShallowCopyable(ITypeSymbol type) { foreach (var field in type.GetDeclaredInstanceMembers()) { if (field.Type is not INamedTypeSymbol fieldType) { return false; } if (SymbolEqualityComparer.Default.Equals(type, fieldType)) { return false; } if (!IsShallowCopyable(fieldType)) { return false; } } return true; } private bool AreShallowCopyable(ImmutableArray types) { foreach (var t in types) if (!IsShallowCopyable(t)) return false; return true; } private bool AreShallowCopyable(ImmutableArray fields) { foreach (var f in fields) if (!IsShallowCopyable(f.Type)) return false; return true; } } internal static class LibraryExtensions { public static WellKnownCodecDescription? FindByUnderlyingType(this WellKnownCodecDescription[] values, ISymbol type) { foreach (var c in values) if (SymbolEqualityComparer.Default.Equals(c.UnderlyingType, type)) return c; return null; } public static WellKnownCopierDescription? FindByUnderlyingType(this WellKnownCopierDescription[] values, ISymbol type) { foreach (var c in values) if (SymbolEqualityComparer.Default.Equals(c.UnderlyingType, type)) return c; return null; } public static bool HasScopedKeyword(this LibraryTypes libraryTypes) => libraryTypes.LanguageVersion is null or >= LanguageVersion.CSharp11; } } ================================================ FILE: src/Orleans.CodeGenerator/MetadataGenerator.cs ================================================ using System.Collections.Generic; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using System; namespace Orleans.CodeGenerator { internal class MetadataGenerator { private readonly CodeGenerator _codeGenerator; public MetadataGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } private MetadataModel MetadataModel => _codeGenerator.MetadataModel; public ClassDeclarationSyntax GenerateMetadata() { var configParam = "config".ToIdentifierName(); var addSerializerMethod = configParam.Member("Serializers").Member("Add"); var addCopierMethod = configParam.Member("Copiers").Member("Add"); var addConverterMethod = configParam.Member("Converters").Member("Add"); var body = new List(); foreach (var type in MetadataModel.SerializableTypes) { body.Add(ExpressionStatement(InvocationExpression(addSerializerMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(GetCodecTypeName(type)))))))); } foreach (var type in MetadataModel.SerializableTypes) { if (type.IsEnumType) continue; if (!MetadataModel.DefaultCopiers.TryGetValue(type, out var typeName)) typeName = GetCopierTypeName(type); body.Add(ExpressionStatement(InvocationExpression(addCopierMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(typeName))))))); } foreach (var type in MetadataModel.DetectedCopiers) { body.Add(ExpressionStatement(InvocationExpression(addCopierMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.ToOpenTypeSyntax()))))))); } foreach (var type in MetadataModel.DetectedSerializers) { body.Add(ExpressionStatement(InvocationExpression(addSerializerMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.ToOpenTypeSyntax()))))))); } foreach (var type in MetadataModel.DetectedConverters) { body.Add(ExpressionStatement(InvocationExpression(addConverterMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.ToOpenTypeSyntax()))))))); } var addProxyMethod = configParam.Member("InterfaceProxies").Member("Add"); foreach (var type in MetadataModel.GeneratedProxies) { body.Add(ExpressionStatement(InvocationExpression(addProxyMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.TypeSyntax))))))); } var addInvokableInterfaceMethod = configParam.Member("Interfaces").Member("Add"); foreach (var type in MetadataModel.InvokableInterfaces.Values) { body.Add(ExpressionStatement(InvocationExpression(addInvokableInterfaceMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.InterfaceType.ToOpenTypeSyntax()))))))); } var addInvokableInterfaceImplementationMethod = configParam.Member("InterfaceImplementations").Member("Add"); foreach (var type in MetadataModel.InvokableInterfaceImplementations) { body.Add(ExpressionStatement(InvocationExpression(addInvokableInterfaceImplementationMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.ToOpenTypeSyntax()))))))); } var addActivatorMethod = configParam.Member("Activators").Member("Add"); foreach (var type in MetadataModel.ActivatableTypes) { body.Add(ExpressionStatement(InvocationExpression(addActivatorMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(GetActivatorTypeName(type)))))))); } foreach (var type in MetadataModel.DetectedActivators) { body.Add(ExpressionStatement(InvocationExpression(addActivatorMethod, ArgumentList(SingletonSeparatedList(Argument(TypeOfExpression(type.ToOpenTypeSyntax()))))))); } var addWellKnownTypeIdMethod = configParam.Member("WellKnownTypeIds").Member("Add"); foreach (var type in MetadataModel.WellKnownTypeIds) { body.Add(ExpressionStatement(InvocationExpression(addWellKnownTypeIdMethod, ArgumentList(SeparatedList(new[] { Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(type.Id))), Argument(TypeOfExpression(type.Type)) }))))); } var addTypeAliasMethod = configParam.Member("WellKnownTypeAliases").Member("Add"); foreach (var type in MetadataModel.TypeAliases) { body.Add(ExpressionStatement(InvocationExpression(addTypeAliasMethod, ArgumentList(SeparatedList(new[] { Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(type.Alias))), Argument(TypeOfExpression(type.Type)) }))))); } AddCompoundTypeAliases(configParam, body); var configType = _codeGenerator.LibraryTypes.TypeManifestOptions; var configureMethod = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "ConfigureInner") .AddModifiers(Token(SyntaxKind.ProtectedKeyword), Token(SyntaxKind.OverrideKeyword)) .AddParameterListParameters( Parameter(configParam.Identifier).WithType(configType.ToTypeSyntax())) .AddBodyStatements(body.ToArray()); var interfaceType = _codeGenerator.LibraryTypes.TypeManifestProviderBase; return ClassDeclaration("Metadata_" + SyntaxGeneration.Identifier.SanitizeIdentifierName(_codeGenerator.Compilation.AssemblyName)) .AddBaseListTypes(SimpleBaseType(interfaceType.ToTypeSyntax())) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()) .AddMembers(configureMethod); } private void AddCompoundTypeAliases(IdentifierNameSyntax configParam, List body) { // The goal is to emit a tree describing all of the generated invokers in the form: // ("inv", typeof(ProxyBaseType), typeof(ContainingInterface), "") // The first step is to collate the invokers into tree to ease the process of generating a tree in code. var nodeId = 0; AddCompoundTypeAliases(body, configParam.Member("CompoundTypeAliases"), MetadataModel.CompoundTypeAliases); void AddCompoundTypeAliases(List body, ExpressionSyntax tree, CompoundTypeAliasTree aliases) { ExpressionSyntax node; if (aliases.Key.IsDefault) { // At the root node, do not create a new node, just enumerate over the child nodes. node = tree; } else { var nodeName = IdentifierName($"n{++nodeId}"); node = nodeName; var valueExpression = aliases.Value switch { { } type => Argument(TypeOfExpression(type)), _ => null }; // Get the arguments for the Add call var addArguments = aliases.Key switch { { IsType: true } typeKey => valueExpression switch { // Call the two-argument Add overload to add a key and value. { } argument => new[] { Argument(TypeOfExpression(typeKey.TypeValue.ToOpenTypeSyntax())), argument }, // Call the one-argument Add overload to add only a key. _ => new[] { Argument(TypeOfExpression(typeKey.TypeValue.ToOpenTypeSyntax())) }, }, { IsString: true } stringKey => valueExpression switch { // Call the two-argument Add overload to add a key and value. { } argument => new[] { Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(stringKey.StringValue))), argument }, // Call the one-argument Add overload to add only a key. _ => new[] { Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(stringKey.StringValue))) }, }, _ => throw new InvalidOperationException("Unexpected alias key") }; if (aliases.Children is { Count: > 0 }) { // C#: var {newTree.Identifier} = {tree}.Add({addArguments}); body.Add(LocalDeclarationStatement(VariableDeclaration( ParseTypeName("var"), SingletonSeparatedList(VariableDeclarator(nodeName.Identifier).WithInitializer(EqualsValueClause(InvocationExpression( tree.Member("Add"), ArgumentList(SeparatedList(addArguments))))))))); } else { // Do not emit a variable. // C#: {tree}.Add({addArguments}); body.Add(ExpressionStatement(InvocationExpression(tree.Member("Add"), ArgumentList(SeparatedList(addArguments))))); } } if (aliases.Children is { Count: > 0 }) { foreach (var child in aliases.Children.Values) { AddCompoundTypeAliases(body, node, child); } } } } public static TypeSyntax GetCodecTypeName(ISerializableTypeDescription type) { var genericArity = type.TypeParameters.Count; var name = SerializerGenerator.GetSimpleClassName(type); if (genericArity > 0) { name = $"{name}<{new string(',', genericArity - 1)}>"; } return ParseTypeName(type.GeneratedNamespace + "." + name); } public static TypeSyntax GetCopierTypeName(ISerializableTypeDescription type) { var genericArity = type.TypeParameters.Count; var name = CopierGenerator.GetSimpleClassName(type); if (genericArity > 0) { name = $"{name}<{new string(',', genericArity - 1)}>"; } return ParseTypeName(type.GeneratedNamespace + "." + name); } public static TypeSyntax GetActivatorTypeName(ISerializableTypeDescription type) { var genericArity = type.TypeParameters.Count; var name = ActivatorGenerator.GetSimpleClassName(type); if (genericArity > 0) { name = $"{name}<{new string(',', genericArity - 1)}>"; } return ParseTypeName(type.GeneratedNamespace + "." + name); } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/FieldDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal class FieldDescription : IFieldDescription { public FieldDescription(uint fieldId, bool isPrimaryConstructorParameter, IFieldSymbol member) { FieldId = fieldId; IsPrimaryConstructorParameter = isPrimaryConstructorParameter; Field = member; Type = member.Type; ContainingType = member.ContainingType; if (Type.TypeKind == TypeKind.Dynamic) { TypeSyntax = PredefinedType(Token(SyntaxKind.ObjectKeyword)); } else { TypeSyntax = Type.ToTypeSyntax(); } } public ISymbol Symbol => Field; public IFieldSymbol Field { get; } public uint FieldId { get; } public ITypeSymbol Type { get; } public INamedTypeSymbol ContainingType { get; } public TypeSyntax TypeSyntax { get; } public string AssemblyName => Type.ContainingAssembly.ToDisplayName(); public string TypeName => Type.ToDisplayName(); public string TypeNameIdentifier => Type.GetValidIdentifier(); public bool IsPrimaryConstructorParameter { get; set; } public bool IsSerializable => true; public bool IsCopyable => true; public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol) => typeSymbol.ToTypeSyntax(); } internal interface IFieldDescription : IMemberDescription { IFieldSymbol Field { get; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/GenerateFieldIds.cs ================================================ namespace Orleans.CodeGenerator.Model; /// /// This enum provides options for controlling the field id generation logic. /// public enum GenerateFieldIds { /// /// Only members explicitly annotated with a field id will be serialized. This is the default. /// None, /// /// Field ids will be automatically assigned to eligible public properties. To qualify, a property must have an accessible getter, and either an accessible setter or a corresponding constructor parameter. /// /// /// The presence of an explicit field id annotation on any member of a type will automatically disable automatic field id generation for that type. /// PublicProperties } ================================================ FILE: src/Orleans.CodeGenerator/Model/GeneratedInvokableDescription.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { [DebuggerDisplay("{MethodDescription}")] internal sealed class GeneratedInvokableDescription : ISerializableTypeDescription { private TypeSyntax _openTypeSyntax; private TypeSyntax _typeSyntax; private TypeSyntax _baseTypeSyntax; public GeneratedInvokableDescription( InvokableMethodDescription methodDescription, Accessibility accessibility, string generatedClassName, string generatedNamespaceName, List members, List serializationHooks, INamedTypeSymbol baseType, List constructorArguments, List compoundTypeAliases, string returnValueInitializerMethod, ClassDeclarationSyntax classDeclarationSyntax) { if (methodDescription.AllTypeParameters.Count == 0) { MetadataName = $"{generatedNamespaceName}.{generatedClassName}"; } else { MetadataName = $"{generatedNamespaceName}.{generatedClassName}`{methodDescription.AllTypeParameters.Count}"; } BaseType = baseType; Name = generatedClassName; GeneratedNamespace = generatedNamespaceName; Members = members; MethodDescription = methodDescription; Accessibility = accessibility; SerializationHooks = serializationHooks; ActivatorConstructorParameters = constructorArguments; CompoundTypeAliases = compoundTypeAliases; ReturnValueInitializerMethod = returnValueInitializerMethod; ClassDeclarationSyntax = classDeclarationSyntax; } public Accessibility Accessibility { get; } public TypeSyntax TypeSyntax => _typeSyntax ??= CreateTypeSyntax(); public TypeSyntax OpenTypeSyntax => _openTypeSyntax ??= CreateOpenTypeSyntax(); public bool HasComplexBaseType => BaseType is { SpecialType: not SpecialType.System_Object }; public bool IncludePrimaryConstructorParameters => false; public INamedTypeSymbol BaseType { get; } public TypeSyntax BaseTypeSyntax => _baseTypeSyntax ??= BaseType.ToTypeSyntax(MethodDescription.TypeParameterSubstitutions); public string Namespace => GeneratedNamespace; public string GeneratedNamespace { get; } public string Name { get; } public bool IsValueType => false; public bool IsSealedType => true; public bool IsAbstractType => false; public bool IsEnumType => false; public bool IsGenericType => TypeParameters.Count > 0; public List Members { get; } public Compilation Compilation => MethodDescription.CodeGenerator.Compilation; public bool IsEmptyConstructable => ActivatorConstructorParameters is not { Count: > 0 }; public bool UseActivator => ActivatorConstructorParameters is { Count: > 0 }; public bool TrackReferences => false; public bool OmitDefaultMemberValues => false; public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters => MethodDescription.AllTypeParameters; public List SerializationHooks { get; } public bool IsShallowCopyable => false; public bool IsUnsealedImmutable => false; public bool IsImmutable => false; public bool IsExceptionType => false; public List ActivatorConstructorParameters { get; } public bool HasActivatorConstructor => UseActivator; public List CompoundTypeAliases { get; } public ClassDeclarationSyntax ClassDeclarationSyntax { get; } public string ReturnValueInitializerMethod { get; } public InvokableMethodDescription MethodDescription { get; } public string MetadataName { get; } public ExpressionSyntax GetObjectCreationExpression() => ObjectCreationExpression(TypeSyntax, ArgumentList(), null); private TypeSyntax CreateTypeSyntax() { var simpleName = InvokableGenerator.GetSimpleClassName(MethodDescription); var subs = MethodDescription.TypeParameterSubstitutions; return (TypeParameters, Namespace) switch { ({ Count: > 0 }, { Length: > 0 }) => QualifiedName(ParseName(Namespace), GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => IdentifierName(subs[p.Parameter])))))), ({ Count: > 0 }, _) => GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => IdentifierName(subs[p.Parameter]))))), (_, { Length: > 0 }) => QualifiedName(ParseName(Namespace), IdentifierName(simpleName)), _ => IdentifierName(simpleName), }; } private TypeSyntax CreateOpenTypeSyntax() { var simpleName = InvokableGenerator.GetSimpleClassName(MethodDescription); return (TypeParameters, Namespace) switch { ({ Count: > 0 }, { Length: > 0 }) => QualifiedName(ParseName(Namespace), GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => OmittedTypeArgument()))))), ({ Count: > 0 }, _) => GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => OmittedTypeArgument())))), (_, { Length: > 0 }) => QualifiedName(ParseName(Namespace), IdentifierName(simpleName)), _ => IdentifierName(simpleName), }; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/GeneratedProxyDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal class GeneratedProxyDescription { public GeneratedProxyDescription(ProxyInterfaceDescription interfaceDescription, string generatedClassName) { InterfaceDescription = interfaceDescription; GeneratedClassName = generatedClassName; TypeSyntax = GetProxyTypeName(interfaceDescription); if (InterfaceDescription.TypeParameters.Count == 0) { MetadataName = $"{InterfaceDescription.GeneratedNamespace}.{GeneratedClassName}"; } else { MetadataName = $"{InterfaceDescription.GeneratedNamespace}.{GeneratedClassName}`{InterfaceDescription.TypeParameters.Count}"; } } public TypeSyntax TypeSyntax { get; } public ProxyInterfaceDescription InterfaceDescription { get; } public string GeneratedClassName { get; } public string MetadataName { get; } private static TypeSyntax GetProxyTypeName(ProxyInterfaceDescription interfaceDescription) { var interfaceType = interfaceDescription.InterfaceType; var genericArity = interfaceType.GetAllTypeParameters().Count(); var name = ProxyGenerator.GetSimpleClassName(interfaceDescription); if (genericArity > 0) { name += $"<{new string(',', genericArity - 1)}>"; } return ParseTypeName(interfaceDescription.GeneratedNamespace + "." + name); } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/ICodecDescription.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator { internal interface ICopierDescription { ITypeSymbol UnderlyingType { get; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/IMemberDescription.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; namespace Orleans.CodeGenerator { internal interface IMemberDescription { uint FieldId { get; } ISymbol Symbol { get; } ITypeSymbol Type { get; } INamedTypeSymbol ContainingType { get; } string AssemblyName { get; } string TypeName { get; } TypeSyntax TypeSyntax { get; } string TypeNameIdentifier { get; } TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol); bool IsPrimaryConstructorParameter { get; } bool IsSerializable { get; } bool IsCopyable { get; } } internal sealed class MemberDescriptionTypeComparer : IEqualityComparer { public static MemberDescriptionTypeComparer Default { get; } = new MemberDescriptionTypeComparer(); public bool Equals(IMemberDescription x, IMemberDescription y) { if (ReferenceEquals(x, y)) { return true; } if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) { return false; } return string.Equals(x.TypeName, y.TypeName) && string.Equals(x.AssemblyName, y.AssemblyName); } public int GetHashCode(IMemberDescription obj) { int hashCode = -499943048; hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(obj.TypeName); hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(obj.AssemblyName); return hashCode; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/ISerializableTypeDescription.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; namespace Orleans.CodeGenerator { internal interface ISerializableTypeDescription { Accessibility Accessibility { get; } TypeSyntax TypeSyntax { get; } bool HasComplexBaseType { get; } bool IncludePrimaryConstructorParameters { get; } INamedTypeSymbol BaseType { get; } TypeSyntax BaseTypeSyntax { get; } string Namespace { get; } string GeneratedNamespace { get; } string Name { get; } bool IsValueType { get; } bool IsSealedType { get; } bool IsAbstractType { get; } bool IsEnumType { get; } bool IsGenericType { get; } List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters { get; } List Members { get; } Compilation Compilation { get; } bool UseActivator { get; } bool IsEmptyConstructable { get; } bool HasActivatorConstructor { get; } bool TrackReferences { get; } bool OmitDefaultMemberValues { get; } ExpressionSyntax GetObjectCreationExpression(); List SerializationHooks { get; } bool IsShallowCopyable { get; } bool IsUnsealedImmutable { get; } bool IsImmutable { get; } bool IsExceptionType { get; } List ActivatorConstructorParameters { get; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/InvokableMethodDescription.cs ================================================ using Microsoft.CodeAnalysis; using Orleans.CodeGenerator.SyntaxGeneration; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; namespace Orleans.CodeGenerator { /// /// Describes an invokable method. /// This is a method on the original interface which defined it. /// By contrast, describes a method on an interface which a proxy is being generated for, having type argument substitutions, etc. /// internal sealed class InvokableMethodDescription : IEquatable { public static InvokableMethodDescription Create(InvokableMethodId method, INamedTypeSymbol containingType) => new(method, containingType); private InvokableMethodDescription(InvokableMethodId invokableId, INamedTypeSymbol containingType) { Key = invokableId; ContainingInterface = containingType; GeneratedMethodId = CodeGenerator.CreateHashedMethodId(Method); MethodId = CodeGenerator.GetId(Method)?.ToString(CultureInfo.InvariantCulture) ?? CodeGenerator.GetAlias(Method) ?? GeneratedMethodId; MethodTypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); // Set defaults from the interface type. var invokableBaseTypes = new Dictionary(SymbolEqualityComparer.Default); foreach (var pair in ProxyBase.InvokableBaseTypes) { invokableBaseTypes[pair.Key] = pair.Value; } InvokableBaseTypes = invokableBaseTypes; foreach (var methodAttr in Method.GetAttributes()) { if (methodAttr.AttributeClass.GetAttributes(CodeGenerator.LibraryTypes.InvokableBaseTypeAttribute, out var attrs)) { foreach (var attr in attrs) { var ctorArgs = attr.ConstructorArguments; var proxyBaseType = (INamedTypeSymbol)ctorArgs[0].Value; var returnType = (INamedTypeSymbol)ctorArgs[1].Value; var invokableBaseType = (INamedTypeSymbol)ctorArgs[2].Value; if (!SymbolEqualityComparer.Default.Equals(ProxyBase.ProxyBaseType, proxyBaseType)) { // This attribute does not apply to this particular invoker, since it is for a different proxy base type. continue; } invokableBaseTypes[returnType] = invokableBaseType; } } if (methodAttr.AttributeClass.GetAttributes(CodeGenerator.LibraryTypes.InvokableCustomInitializerAttribute, out attrs)) { foreach (var attr in attrs) { var methodName = (string)attr.ConstructorArguments[0].Value; TypedConstant methodArgument; if (attr.ConstructorArguments.Length == 2) { // Take the value from the attribute directly. methodArgument = attr.ConstructorArguments[1]; } else { // Take the value from the attribute which this attribute is attached to. if (TryGetNamedArgument(attr.NamedArguments, "AttributeArgumentName", out var argNameArg) && TryGetNamedArgument(methodAttr.NamedArguments, (string)argNameArg.Value, out var namedArgument)) { methodArgument = namedArgument; } else { var index = 0; if (TryGetNamedArgument(attr.NamedArguments, "AttributeArgumentIndex", out var indexArg)) { index = (int)indexArg.Value; } methodArgument = methodAttr.ConstructorArguments[index]; } } CustomInitializerMethods.Add((methodName, methodArgument)); } } if (SymbolEqualityComparer.Default.Equals(methodAttr.AttributeClass, CodeGenerator.LibraryTypes.ResponseTimeoutAttribute)) { ResponseTimeoutTicks = TimeSpan.Parse((string)methodAttr.ConstructorArguments[0].Value).Ticks; } } AllTypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); MethodTypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); var names = new HashSet(StringComparer.Ordinal); foreach (var typeParameter in ContainingInterface.GetAllTypeParameters()) { var tpName = GetTypeParameterName(names, typeParameter); AllTypeParameters.Add((tpName, typeParameter)); } foreach (var typeParameter in Method.TypeParameters) { var tpName = GetTypeParameterName(names, typeParameter); AllTypeParameters.Add((tpName, typeParameter)); MethodTypeParameters.Add((tpName, typeParameter)); } TypeParameterSubstitutions = new(SymbolEqualityComparer.Default); foreach (var (name, parameter) in AllTypeParameters) { TypeParameterSubstitutions[parameter] = name; } static string GetTypeParameterName(HashSet names, ITypeParameterSymbol typeParameter) { var count = 0; var result = typeParameter.Name; while (names.Contains(result)) { result = $"{typeParameter.Name}_{++count}"; } names.Add(result); return result.EscapeIdentifier(); } static bool TryGetNamedArgument(ImmutableArray> arguments, string name, out TypedConstant value) { foreach (var arg in arguments) { if (string.Equals(arg.Key, name, StringComparison.Ordinal)) { value = arg.Value; return true; } } value = default; return false; } } /// /// Gets the source generator. /// public CodeGenerator CodeGenerator => ProxyBase.CodeGenerator; /// /// Gets the method identifier. /// public InvokableMethodId Key { get; } /// /// Gets the proxy base information for the method (eg, GrainReference, whether it is an extension). /// public InvokableMethodProxyBase ProxyBase => Key.ProxyBase; /// /// Gets the method symbol. /// public IMethodSymbol Method => Key.Method; /// /// Gets the dictionary of invokable base types. This indicates what invokable base type (eg, ValueTaskRequest) should be used for a given return type (eg, ValueTask). /// public IReadOnlyDictionary InvokableBaseTypes { get; } /// /// Gets the response timeout ticks, if set. /// public long? ResponseTimeoutTicks { get; } /// /// Gets the list of custom initializer method names and their corresponding argument. /// public List<(string MethodName, TypedConstant MethodArgument)> CustomInitializerMethods { get; } = new(); /// /// Gets the generated method identifier. /// public string GeneratedMethodId { get; } /// /// Gets the method identifier. /// public string MethodId { get; } public List<(string Name, ITypeParameterSymbol Parameter)> AllTypeParameters { get; } public List<(string Name, ITypeParameterSymbol Parameter)> MethodTypeParameters { get; } public Dictionary TypeParameterSubstitutions { get; } /// /// Gets a value indicating whether this method has an alias. /// public bool HasAlias => !string.Equals(MethodId, GeneratedMethodId, StringComparison.Ordinal); /// /// Gets the interface which this type is contained in. /// public INamedTypeSymbol ContainingInterface { get; } /// /// Gets a value indicating whether this method is cancellable. /// public bool IsCancellable => Method.Parameters.Any(parameterSymbol => SymbolEqualityComparer.Default.Equals(CodeGenerator.LibraryTypes.CancellationToken, parameterSymbol.Type)); public bool Equals(InvokableMethodDescription other) => Key.Equals(other.Key); public override bool Equals(object obj) => obj is InvokableMethodDescription imd && Equals(imd); public override int GetHashCode() => Key.GetHashCode(); public override string ToString() => $"{ProxyBase}/{ContainingInterface.Name}/{Method.Name}"; } } ================================================ FILE: src/Orleans.CodeGenerator/Model/InvokableMethodId.cs ================================================ using Microsoft.CodeAnalysis; using System; namespace Orleans.CodeGenerator { /// /// Identifies an invokable method. /// internal readonly struct InvokableMethodId(InvokableMethodProxyBase proxyBaseInfo, INamedTypeSymbol interfaceType, IMethodSymbol method) : IEquatable { /// /// Gets the proxy base information for the method (eg, GrainReference, whether it is an extension). /// public InvokableMethodProxyBase ProxyBase { get; } = proxyBaseInfo; /// /// Gets the method symbol. /// public IMethodSymbol Method { get; } = method; /// /// Gets the containing interface symbol. /// public INamedTypeSymbol InterfaceType { get; } = interfaceType; public bool Equals(InvokableMethodId other) => ProxyBase.Equals(other.ProxyBase) && SymbolEqualityComparer.Default.Equals(Method, other.Method) && SymbolEqualityComparer.Default.Equals(InterfaceType, other.InterfaceType); public override bool Equals(object obj) => obj is InvokableMethodId imd && Equals(imd); public override int GetHashCode() { unchecked { return ProxyBase.GetHashCode() * 17 ^ SymbolEqualityComparer.Default.GetHashCode(Method) * 17 ^ SymbolEqualityComparer.Default.GetHashCode(InterfaceType); } } public override string ToString() => $"{ProxyBase}/{InterfaceType.Name}/{Method.Name}"; } } ================================================ FILE: src/Orleans.CodeGenerator/Model/InvokableMethodProxyBase.cs ================================================ using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.CodeGenerator { /// /// Describes the proxy base for an invokable method, including whether the proxy is a grain reference or extension, and what invokable base types should be used for a given return type. /// internal sealed class InvokableMethodProxyBase : IEquatable { public InvokableMethodProxyBase(CodeGenerator codeGenerator, InvokableMethodProxyBaseId descriptor, Dictionary invokableBaseTypes) { CodeGenerator = codeGenerator; Key = descriptor; InvokableBaseTypes = invokableBaseTypes ?? throw new ArgumentNullException(nameof(invokableBaseTypes)); } /// /// Gets the source generator. /// public CodeGenerator CodeGenerator { get; } /// /// Gets the proxy base id. /// public InvokableMethodProxyBaseId Key { get; } /// /// Gets the proxy base type, eg GrainReference. /// public INamedTypeSymbol ProxyBaseType => Key.ProxyBaseType; /// /// Gets a value indicating whether this descriptor represents an extension. /// public bool IsExtension => Key.IsExtension; /// /// Gets the components of the compound type alias used to refer to this proxy base. /// public ImmutableArray CompositeAliasComponents => Key.CompositeAliasComponents; /// /// Gets the dictionary of invokable base types. This indicates what invokable base type (eg, ValueTaskRequest) should be used for a given return type (eg, ValueTask). /// public IReadOnlyDictionary InvokableBaseTypes { get; } public bool Equals(InvokableMethodProxyBase other) => other is not null && Key.Equals(other.Key); public override bool Equals(object obj) => obj is InvokableMethodProxyBase other && Equals(other); public override int GetHashCode() => Key.GetHashCode(); public override string ToString() => Key.ToString(); } } ================================================ FILE: src/Orleans.CodeGenerator/Model/InvokableMethodProxyBaseId.cs ================================================ using Microsoft.CodeAnalysis; using System; using System.Collections.Immutable; namespace Orleans.CodeGenerator { /// /// Identifies a proxy base, including whether the proxy is a grain reference or extension. /// internal readonly struct InvokableMethodProxyBaseId : IEquatable { public InvokableMethodProxyBaseId(INamedTypeSymbol type, bool isExtension) { if (!SymbolEqualityComparer.Default.Equals(type, type.OriginalDefinition)) { throw new ArgumentException("Type must be an original definition. This is a code generator bug."); } ProxyBaseType = type; IsExtension = isExtension; if (IsExtension) { CompositeAliasComponents = ImmutableArray.Create(new CompoundTypeAliasComponent[] { new(ProxyBaseType), new("Ext") }); GeneratedClassNameComponent = $"{ProxyBaseType.Name}_Ext"; } else { CompositeAliasComponents = ImmutableArray.Create(new CompoundTypeAliasComponent[] { new(ProxyBaseType) }); GeneratedClassNameComponent = ProxyBaseType.Name; } } /// /// Gets the proxy base type, eg GrainReference. /// public INamedTypeSymbol ProxyBaseType { get; } /// /// Gets a value indicating whether this descriptor represents an extension. /// public bool IsExtension { get; } /// /// Gets the components of the compound type alias used to refer to this proxy base. /// public ImmutableArray CompositeAliasComponents { get; } /// /// Gets a string used to distinguish this proxy base from others in generated class names. /// public string GeneratedClassNameComponent { get; } public bool Equals(InvokableMethodProxyBaseId other) => SymbolEqualityComparer.Default.Equals(ProxyBaseType, other.ProxyBaseType) && IsExtension == other.IsExtension; public override bool Equals(object obj) => obj is InvokableMethodProxyBaseId other && Equals(other); public override int GetHashCode() => IsExtension.GetHashCode() * 17 ^ SymbolEqualityComparer.Default.GetHashCode(ProxyBaseType); public override string ToString() => GeneratedClassNameComponent; } } ================================================ FILE: src/Orleans.CodeGenerator/Model/MetadataModel.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; namespace Orleans.CodeGenerator { internal class MetadataModel { public List SerializableTypes { get; } = new(1024); public Dictionary InvokableInterfaces { get; } = new(SymbolEqualityComparer.Default); public List InvokableInterfaceImplementations { get; } = new(1024); public Dictionary GeneratedInvokables { get; } = new(); public List GeneratedProxies { get; } = new(1024); public List ActivatableTypes { get; } = new(1024); public List DetectedSerializers { get; } = new(); public List DetectedActivators { get; } = new(); public Dictionary DefaultCopiers { get; } = new(); public List DetectedCopiers { get; } = new(); public List DetectedConverters { get; } = new(); public List<(TypeSyntax Type, string Alias)> TypeAliases { get; } = new(1024); public CompoundTypeAliasTree CompoundTypeAliases { get; } = CompoundTypeAliasTree.Create(); public List<(TypeSyntax Type, uint Id)> WellKnownTypeIds { get; } = new(1024); public HashSet ApplicationParts { get; } = new(); internal Dictionary> ProxyBaseTypeInvokableBaseTypes { get; } = new (SymbolEqualityComparer.Default); } /// /// Represents a compound type aliases as a prefix tree. /// internal sealed class CompoundTypeAliasTree { private Dictionary _children; /// /// Initializes a new instance of the class. /// private CompoundTypeAliasTree(CompoundTypeAliasComponent key, TypeSyntax value) { Key = key; Value = value; } /// /// Gets the key for this node. /// public CompoundTypeAliasComponent Key { get; } /// /// Gets the value for this node. /// public TypeSyntax Value { get; private set; } /// /// Creates a new tree with a root node which has no key or value. /// public static CompoundTypeAliasTree Create() => new(default, default); public Dictionary Children => _children; internal CompoundTypeAliasTree GetChildOrDefault(object key) { TryGetChild(key, out var result); return result; } internal bool TryGetChild(object key, out CompoundTypeAliasTree result) { if (_children is { } children) { return children.TryGetValue(key, out result); } result = default; return false; } public void Add(CompoundTypeAliasComponent[] key, TypeSyntax value) { Add(key.AsSpan(), value); } public void Add(ReadOnlySpan keys, TypeSyntax value) { if (keys.Length == 0) { throw new InvalidOperationException("No valid key specified."); } var key = keys[0]; if (keys.Length == 1) { AddInternal(key, value); } else { var childNode = GetChildOrDefault(key) ?? AddInternal(key); childNode.Add(keys.Slice(1), value); } } /// /// Adds a node to the tree. /// /// The key for the new node. public CompoundTypeAliasTree Add(ITypeSymbol key) => AddInternal(new CompoundTypeAliasComponent(key)); /// /// Adds a node to the tree. /// /// The key for the new node. public CompoundTypeAliasTree Add(string key) => AddInternal(new CompoundTypeAliasComponent(key)); /// /// Adds a node to the tree. /// /// The key for the new node. /// The value for the new node. public CompoundTypeAliasTree Add(string key, TypeSyntax value) => AddInternal(new CompoundTypeAliasComponent(key), value); /// /// Adds a node to the tree. /// /// The key for the new node. /// The value for the new node. public CompoundTypeAliasTree Add(ITypeSymbol key, TypeSyntax value) => AddInternal(new CompoundTypeAliasComponent(key), value); private CompoundTypeAliasTree AddInternal(CompoundTypeAliasComponent key) => AddInternal(key, default); private CompoundTypeAliasTree AddInternal(CompoundTypeAliasComponent key, TypeSyntax value) { _children ??= new(); if (_children.TryGetValue(key, out var existing)) { if (value is not null && existing.Value is not null) { throw new ArgumentException($"A key with the value '{key}' already exists. Existing value: '{existing.Value}', new value: '{value}'"); } existing.Value = value; return existing; } else { return _children[key] = new CompoundTypeAliasTree(key, value); } } } internal readonly struct CompoundTypeAliasComponent : IEquatable { private readonly Either _value; public CompoundTypeAliasComponent(string value) => _value = new Either(value); public CompoundTypeAliasComponent(ITypeSymbol value) => _value = new Either(value); public static CompoundTypeAliasComponent Default => new(); public bool IsDefault => _value.RawValue is null; public bool IsString => _value.IsLeft; public string StringValue => _value.LeftValue; public bool IsType => _value.IsRight; public ITypeSymbol TypeValue => _value.RightValue; public object Value => _value.RawValue; public bool Equals(CompoundTypeAliasComponent other) => (Value, other.Value) switch { (null, null) => true, (string stringValue, string otherStringValue) => string.Equals(stringValue, otherStringValue), (ITypeSymbol typeValue, ITypeSymbol otherTypeValue) => SymbolEqualityComparer.Default.Equals(typeValue, otherTypeValue), _ => false, }; public override bool Equals(object obj) => obj is CompoundTypeAliasComponent other && Equals(other); public override int GetHashCode() => _value.RawValue switch { string stringValue => stringValue.GetHashCode(), ITypeSymbol type => SymbolEqualityComparer.Default.GetHashCode(type), _ => throw new InvalidOperationException($"Unsupported type {_value.RawValue}") }; internal readonly struct EqualityComparer : IEqualityComparer { public static EqualityComparer Default => default; public bool Equals(CompoundTypeAliasComponent x, CompoundTypeAliasComponent y) => x.Equals(y); public int GetHashCode(CompoundTypeAliasComponent obj) => obj.GetHashCode(); } public override string ToString() => _value.RawValue?.ToString(); } internal readonly struct Either where T : class where U : class { private readonly bool _isLeft; private readonly object _value; public Either(T value) { _value = value; _isLeft = true; } public Either(U value) { _value = value; _isLeft = false; } public bool IsLeft => _isLeft; public bool IsRight => !IsLeft; public T LeftValue => (T)_value; public U RightValue => (U)_value; public object RawValue => _value; } } ================================================ FILE: src/Orleans.CodeGenerator/Model/MethodSignatureComparer.cs ================================================ using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; namespace Orleans.CodeGenerator { internal sealed class MethodSignatureComparer : IEqualityComparer, IComparer { public static MethodSignatureComparer Default { get; } = new(); private MethodSignatureComparer() { } public bool Equals(IMethodSymbol x, IMethodSymbol y) { if (!string.Equals(x.Name, y.Name, StringComparison.Ordinal)) { return false; } if (x.TypeArguments.Length != y.TypeArguments.Length) { return false; } for (var i = 0; i < x.TypeArguments.Length; i++) { if (!SymbolEqualityComparer.Default.Equals(x.TypeArguments[i], y.TypeArguments[i])) { return false; } } if (x.Parameters.Length != y.Parameters.Length) { return false; } for (var i = 0; i < x.Parameters.Length; i++) { if (!SymbolEqualityComparer.Default.Equals(x.Parameters[i].Type, y.Parameters[i].Type)) { return false; } } return true; } public int GetHashCode(IMethodSymbol obj) { int hashCode = -499943048; hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(obj.Name); foreach (var arg in obj.TypeArguments) { hashCode = hashCode * -1521134295 + SymbolEqualityComparer.Default.GetHashCode(arg); } foreach (var parameter in obj.Parameters) { hashCode = hashCode * -1521134295 + SymbolEqualityComparer.Default.GetHashCode(parameter.Type); } return hashCode; } public int Compare(IMethodSymbol x, IMethodSymbol y) { var result = StringComparer.Ordinal.Compare(x.Name, y.Name); if (result != 0) { return result; } result = x.TypeArguments.Length.CompareTo(y.TypeArguments.Length); if (result != 0) { return result; } for (var i = 0; i < x.TypeArguments.Length; i++) { var xh = SymbolEqualityComparer.Default.GetHashCode(x.TypeArguments[i]); var yh = SymbolEqualityComparer.Default.GetHashCode(y.TypeArguments[i]); result = xh.CompareTo(yh); if (result != 0) { return result; } } result = x.Parameters.Length.CompareTo(y.Parameters.Length); if (result != 0) { return result; } for (var i = 0; i < x.Parameters.Length; i++) { var xh = SymbolEqualityComparer.Default.GetHashCode(x.Parameters[i].Type); var yh = SymbolEqualityComparer.Default.GetHashCode(y.Parameters[i].Type); result = xh.CompareTo(yh); if (result != 0) { return result; } } return 0; } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/PropertyDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal interface IPropertyDescription : IMemberDescription { } internal class PropertyDescription : IPropertyDescription { public PropertyDescription(uint fieldId, bool isPrimaryConstructorParameter, IPropertySymbol property) { FieldId = fieldId; IsPrimaryConstructorParameter = isPrimaryConstructorParameter; Property = property; if (Type.TypeKind == TypeKind.Dynamic) { TypeSyntax = PredefinedType(Token(SyntaxKind.ObjectKeyword)); } else { TypeSyntax = Type.ToTypeSyntax(); } } public uint FieldId { get; } public ISymbol Symbol => Property; public ITypeSymbol Type => Property.Type; public INamedTypeSymbol ContainingType => Property.ContainingType; public IPropertySymbol Property { get; } public TypeSyntax TypeSyntax { get; } public string AssemblyName => Type.ContainingAssembly.ToDisplayName(); public string TypeName => Type.ToDisplayName(); public string TypeNameIdentifier => Type.GetValidIdentifier(); public bool IsPrimaryConstructorParameter { get; set; } public bool IsSerializable => true; public bool IsCopyable => true; public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol) => typeSymbol.ToTypeSyntax(); } } ================================================ FILE: src/Orleans.CodeGenerator/Model/ProxyInterfaceDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; using Orleans.CodeGenerator.Diagnostics; using System.Linq; using System.Diagnostics; namespace Orleans.CodeGenerator { [DebuggerDisplay("{InterfaceType} (proxy base {ProxyBaseType})")] internal class ProxyInterfaceDescription : IEquatable { private static readonly char[] FilteredNameChars = new char[] { '`', '.' }; private List _methods; public ProxyInterfaceDescription( CodeGenerator codeGenerator, INamedTypeSymbol proxyBaseType, INamedTypeSymbol interfaceType) { ValidateBaseClass(codeGenerator.LibraryTypes, proxyBaseType); var prop = interfaceType.GetAllMembers().FirstOrDefault(); if (prop is { }) { throw new OrleansGeneratorDiagnosticAnalysisException(RpcInterfacePropertyDiagnostic.CreateDiagnostic(interfaceType, prop)); } CodeGenerator = codeGenerator; InterfaceType = interfaceType; Name = codeGenerator.GetAlias(interfaceType) ?? interfaceType.Name; ProxyBaseType = proxyBaseType; // If the name is a user-defined name which specified a generic arity, strip the arity backtick now if (Name.IndexOfAny(FilteredNameChars) >= 0) { foreach (var c in FilteredNameChars) { Name = Name.Replace(c, '_'); } } GeneratedNamespace = InterfaceType.GetNamespaceAndNesting() switch { { Length: > 0 } ns => $"{CodeGenerator.CodeGeneratorName}.{ns}", _ => CodeGenerator.CodeGeneratorName }; var names = new HashSet(StringComparer.Ordinal); TypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); foreach (var tp in interfaceType.GetAllTypeParameters()) { var tpName = GetTypeParameterName(names, tp); TypeParameters.Add((tpName, tp)); } static string GetTypeParameterName(HashSet names, ITypeParameterSymbol tp) { var count = 0; var result = tp.Name; while (names.Contains(result)) { result = $"{tp.Name}_{++count}"; } names.Add(result); return result; } } public CodeGenerator CodeGenerator { get; } private List GetMethods() { var result = new List(); foreach (var iface in GetAllInterfaces(InterfaceType)) { foreach (var method in iface.GetDeclaredInstanceMembers()) { if (method.MethodKind == MethodKind.ExplicitInterfaceImplementation) { // Explicit implementations can be ignored when generating a proxy. // Proxies must implement every method explicitly to ensure faithful reproduction of the interface behavior. // At the calling side, the explicit implementation will be called if it was not overridden by a derived type. continue; } var methodDescription = CodeGenerator.GetProxyMethodDescription(InterfaceType, method: method); result.Add(methodDescription); } } return result; static IEnumerable GetAllInterfaces(INamedTypeSymbol s) { if (s.TypeKind == TypeKind.Interface) { yield return s; } foreach (var i in s.AllInterfaces) { yield return i; } } } public string Name { get; } public INamedTypeSymbol InterfaceType { get; } public List Methods => _methods ??= GetMethods(); public SemanticModel SemanticModel { get; } public string GeneratedNamespace { get; } public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters { get; } public INamedTypeSymbol ProxyBaseType { get; } private static void ValidateBaseClass(LibraryTypes l, INamedTypeSymbol baseClass) { ValidateGenericInvokeAsync(l, baseClass); ValidateNonGenericInvokeAsync(l, baseClass); static void ValidateGenericInvokeAsync(LibraryTypes l, INamedTypeSymbol baseClass) { var found = false; string complaint = null; ISymbol complaintMember = null; foreach (var member in baseClass.GetMembers("InvokeAsync")) { if (member is not IMethodSymbol method) { complaintMember = member; complaint = "not a method"; continue; } if (method.TypeParameters.Length != 1) { complaintMember = member; complaint = "incorrect number of type parameters (expected one type parameter)"; continue; } if (method.Parameters.Length != 1) { complaintMember = member; complaint = $"missing parameter (expected a parameter of type {l.IInvokable.ToDisplayString()})"; continue; } var paramType = method.Parameters[0].Type; if (!SymbolEqualityComparer.Default.Equals(paramType, l.IInvokable)) { var implementsIInvokable = false; foreach (var @interface in paramType.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(@interface, l.IInvokable)) { implementsIInvokable = true; break; } } if (!implementsIInvokable) { complaintMember = member; complaint = $"incorrect parameter type (found {paramType}, expected {l.IInvokable} or a type which implements {l.IInvokable})"; continue; } } var expectedReturnType = l.ValueTask_1.Construct(method.TypeParameters[0]); if (!SymbolEqualityComparer.Default.Equals(method.ReturnType, expectedReturnType)) { complaintMember = member; complaint = $"incorrect return type (found: {method.ReturnType.ToDisplayString()}, expected {expectedReturnType.ToDisplayString()})"; continue; } found = true; } if (!found) { var notFoundMessage = $"Proxy base class {baseClass} does not contain a definition for ValueTask InvokeAsync(IInvokable)"; var locationMember = complaintMember ?? baseClass; var complaintMessage = complaint switch { { Length: > 0 } => $"{notFoundMessage}. Complaint: {complaint} for symbol: {complaintMember.ToDisplayString()}", _ => notFoundMessage, }; var diagnostic = IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic(baseClass, locationMember.Locations.First(), complaintMessage); throw new OrleansGeneratorDiagnosticAnalysisException(diagnostic); } } static void ValidateNonGenericInvokeAsync(LibraryTypes l, INamedTypeSymbol baseClass) { var found = false; string complaint = null; ISymbol complaintMember = null; foreach (var member in baseClass.GetMembers("InvokeAsync")) { if (member is not IMethodSymbol method) { complaintMember = member; complaint = "not a method"; continue; } if (method.TypeParameters.Length != 0) { complaintMember = member; complaint = "incorrect number of type parameters (expected zero)"; continue; } if (method.Parameters.Length != 1) { complaintMember = member; complaint = $"missing parameter (expected a parameter of type {l.IInvokable.ToDisplayString()})"; continue; } var paramType = method.Parameters[0].Type; if (!SymbolEqualityComparer.Default.Equals(paramType, l.IInvokable)) { var implementsIInvokable = false; foreach (var @interface in paramType.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(@interface, l.IInvokable)) { implementsIInvokable = true; break; } } if (!implementsIInvokable) { complaintMember = member; complaint = $"incorrect parameter type (found {method.Parameters[0].Type}, expected {l.IInvokable})"; continue; } } if (!SymbolEqualityComparer.Default.Equals(method.ReturnType, l.ValueTask)) { complaintMember = member; complaint = $"incorrect return type (found: {method.ReturnType.ToDisplayString()}, expected {l.ValueTask.ToDisplayString()})"; continue; } found = true; } if (!found) { var notFoundMessage = $"Proxy base class {baseClass} does not contain a definition for ValueTask InvokeAsync(IInvokable)"; var locationMember = complaintMember ?? baseClass; var complaintMessage = complaint switch { { Length: > 0 } => $"{notFoundMessage}. Complaint: {complaint} for symbol: {complaintMember.ToDisplayString()}", _ => notFoundMessage, }; var diagnostic = IncorrectProxyBaseClassSpecificationDiagnostic.CreateDiagnostic(baseClass, locationMember.Locations.First(), complaintMessage); throw new OrleansGeneratorDiagnosticAnalysisException(diagnostic); } } } public bool Equals(ProxyInterfaceDescription other) => SymbolEqualityComparer.Default.Equals(InterfaceType, other.InterfaceType) && SymbolEqualityComparer.Default.Equals(ProxyBaseType, other.ProxyBaseType); public override bool Equals(object obj) => obj is ProxyInterfaceDescription other && Equals(other); public override int GetHashCode() => SymbolEqualityComparer.Default.GetHashCode(InterfaceType) * 17 ^ SymbolEqualityComparer.Default.GetHashCode(ProxyBaseType); public override string ToString() => $"Type: {InterfaceType}, ProxyBaseType: {ProxyBaseType}"; } } ================================================ FILE: src/Orleans.CodeGenerator/Model/ProxyMethodDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using Microsoft.CodeAnalysis.CSharp; namespace Orleans.CodeGenerator { /// /// Describes an invokable method on a proxy interface. /// [DebuggerDisplay("{Method} (from {ProxyInterface})")] internal class ProxyMethodDescription : IEquatable { private readonly GeneratedInvokableDescription _originalInvokable; public static ProxyMethodDescription Create( ProxyInterfaceDescription proxyInterface, GeneratedInvokableDescription generatedInvokable, IMethodSymbol method) => new(proxyInterface, generatedInvokable, method); private ProxyMethodDescription(ProxyInterfaceDescription proxyInterface, GeneratedInvokableDescription generatedInvokable, IMethodSymbol method) { _originalInvokable = generatedInvokable; Method = method; ProxyInterface = proxyInterface; TypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); MethodTypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>(); TypeParametersWithArguments = Method.ContainingType.GetAllTypeParameters().Zip(method.ContainingType.GetAllTypeArguments(), (a, b) => (a, b)).ToImmutableArray(); var names = new HashSet(StringComparer.Ordinal); foreach (var (typeParameter, typeArgument) in TypeParametersWithArguments) { var tpName = GetTypeParameterName(names, typeParameter); TypeParameters.Add((tpName, typeParameter)); } foreach (var typeParameter in Method.TypeParameters) { var tpName = GetTypeParameterName(names, typeParameter); TypeParameters.Add((tpName, typeParameter)); MethodTypeParameters.Add((tpName, typeParameter)); } TypeParameterSubstitutions = new(SymbolEqualityComparer.Default); foreach (var (name, parameter) in TypeParameters) { TypeParameterSubstitutions[parameter] = name; } foreach (var (parameter, arg) in TypeParametersWithArguments) { TypeParameterSubstitutions[parameter] = arg.ToDisplayName(); } GeneratedInvokable = new ConstructedGeneratedInvokableDescription(generatedInvokable, this); static string GetTypeParameterName(HashSet names, ITypeParameterSymbol typeParameter) { var count = 0; var result = typeParameter.Name; while (names.Contains(result)) { result = $"{typeParameter.Name}_{++count}"; } names.Add(result); return result.EscapeIdentifier(); } } public CodeGenerator CodeGenerator => InvokableMethod.CodeGenerator; public InvokableMethodDescription InvokableMethod => _originalInvokable.MethodDescription; public ConstructedGeneratedInvokableDescription GeneratedInvokable { get; } public ProxyInterfaceDescription ProxyInterface { get; } public IMethodSymbol Method { get; } public InvokableMethodId InvokableId { get; } public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters { get; } public List<(string Name, ITypeParameterSymbol Parameter)> MethodTypeParameters { get; } public ImmutableArray<(ITypeParameterSymbol Parameter, ITypeSymbol Argument)> TypeParametersWithArguments { get; } public Dictionary TypeParameterSubstitutions { get; } /// /// Mapping of method return types to invokable base type. The code generator will create a derived type with the method arguments as fields. /// public IReadOnlyDictionary InvokableBaseTypes => InvokableMethod.InvokableBaseTypes; public InvokableMethodId InvokableKey => InvokableMethod.Key; public List<(string, TypedConstant)> CustomInitializerMethods => InvokableMethod.CustomInitializerMethods; public string GeneratedMethodId => InvokableMethod.GeneratedMethodId; public string MethodId => InvokableMethod.MethodId; public bool HasAlias => InvokableMethod.HasAlias; public long? ResponseTimeoutTicks => InvokableMethod.ResponseTimeoutTicks; public override int GetHashCode() => ProxyInterface.GetHashCode() * 17 ^ InvokableMethod.GetHashCode(); public bool Equals(ProxyMethodDescription other) => other is not null && InvokableMethod.Key.Equals(other.InvokableKey) && ProxyInterface.Equals(other.ProxyInterface); public override bool Equals(object other) => other is ProxyMethodDescription imd && Equals(imd); internal sealed class ConstructedGeneratedInvokableDescription : ISerializableTypeDescription { private TypeSyntax _typeSyntax; private TypeSyntax _baseTypeSyntax; private readonly GeneratedInvokableDescription _invokableDescription; private readonly ProxyMethodDescription _proxyMethod; public ConstructedGeneratedInvokableDescription(GeneratedInvokableDescription invokableDescription, ProxyMethodDescription proxyMethod) { _invokableDescription = invokableDescription; _proxyMethod = proxyMethod; Members = new List(invokableDescription.Members.Count); var proxyMethodParameters = proxyMethod.Method.Parameters; foreach (var member in invokableDescription.Members.OfType()) { Members.Add(new InvokableGenerator.MethodParameterFieldDescription( proxyMethod.CodeGenerator, proxyMethodParameters[member.ParameterOrdinal], member.FieldName, member.FieldId, proxyMethod.TypeParameterSubstitutions, member.IsSerializable)); } } public Accessibility Accessibility => _invokableDescription.Accessibility; public TypeSyntax TypeSyntax => _typeSyntax ??= CreateTypeSyntax(); public TypeSyntax OpenTypeSyntax => _invokableDescription.OpenTypeSyntax; public bool HasComplexBaseType => BaseType is { SpecialType: not SpecialType.System_Object }; public bool IncludePrimaryConstructorParameters => false; public INamedTypeSymbol BaseType => _invokableDescription.BaseType; public TypeSyntax BaseTypeSyntax => _baseTypeSyntax ??= BaseType.ToTypeSyntax(_proxyMethod.TypeParameterSubstitutions); public string Namespace => GeneratedNamespace; public string GeneratedNamespace => _invokableDescription.GeneratedNamespace; public string Name => _invokableDescription.Name; public bool IsValueType => _invokableDescription.IsValueType; public bool IsSealedType => _invokableDescription.IsSealedType; public bool IsAbstractType => _invokableDescription.IsAbstractType; public bool IsEnumType => _invokableDescription.IsEnumType; public bool IsGenericType => TypeParameters.Count > 0; public List Members { get; } public Compilation Compilation => MethodDescription.CodeGenerator.Compilation; public bool IsEmptyConstructable => ActivatorConstructorParameters is not { Count: > 0 }; public bool UseActivator => ActivatorConstructorParameters is { Count: > 0 }; public bool TrackReferences => _invokableDescription.TrackReferences; public bool OmitDefaultMemberValues => _invokableDescription.OmitDefaultMemberValues; public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters => _proxyMethod.TypeParameters; public List SerializationHooks => _invokableDescription.SerializationHooks; public bool IsShallowCopyable => _invokableDescription.IsShallowCopyable; public bool IsUnsealedImmutable => _invokableDescription.IsUnsealedImmutable; public bool IsImmutable => _invokableDescription.IsImmutable; public bool IsExceptionType => _invokableDescription.IsExceptionType; public List ActivatorConstructorParameters => _invokableDescription.ActivatorConstructorParameters; public bool HasActivatorConstructor => UseActivator; public string ReturnValueInitializerMethod => _invokableDescription.ReturnValueInitializerMethod; public InvokableMethodDescription MethodDescription => _invokableDescription.MethodDescription; public ExpressionSyntax GetObjectCreationExpression() => ObjectCreationExpression(TypeSyntax, ArgumentList(), null); private TypeSyntax CreateTypeSyntax() { var simpleName = InvokableGenerator.GetSimpleClassName(MethodDescription); var subs = _proxyMethod.TypeParameterSubstitutions; return (TypeParameters, Namespace) switch { ({ Count: > 0 }, { Length: > 0 }) => QualifiedName(ParseName(Namespace), GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => IdentifierName(subs[p.Parameter])))))), ({ Count: > 0 }, _) => GenericName(Identifier(simpleName), TypeArgumentList(SeparatedList(TypeParameters.Select(p => IdentifierName(subs[p.Parameter]))))), (_, { Length: > 0 }) => QualifiedName(ParseName(Namespace), IdentifierName(simpleName)), _ => IdentifierName(simpleName), }; } } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/SerializableTypeDescription.cs ================================================ using Orleans.CodeGenerator.SyntaxGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.Linq; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal class SerializableTypeDescription : ISerializableTypeDescription { private readonly LibraryTypes _libraryTypes; private TypeSyntax _typeSyntax; private INamedTypeSymbol _baseType; private TypeSyntax _baseTypeSyntax; public SerializableTypeDescription(Compilation compilation, INamedTypeSymbol type, bool supportsPrimaryConstructorParameters, IEnumerable members, LibraryTypes libraryTypes) { Type = type; IncludePrimaryConstructorParameters = supportsPrimaryConstructorParameters; Members = members.ToList(); Compilation = compilation; _libraryTypes = libraryTypes; var t = type; Accessibility accessibility = t.DeclaredAccessibility; while (t is not null) { if ((int)t.DeclaredAccessibility < (int)accessibility) { accessibility = t.DeclaredAccessibility; } t = t.ContainingType; } Accessibility = accessibility; TypeParameters = new(); var names = new HashSet(StringComparer.Ordinal); foreach (var tp in type.GetAllTypeParameters()) { var tpName = GetTypeParameterName(names, tp); TypeParameters.Add((tpName, tp)); } SerializationHooks = new(); if (type.GetAttributes(libraryTypes.SerializationCallbacksAttribute, out var hookAttributes)) { foreach (var hookAttribute in hookAttributes) { var hookType = (INamedTypeSymbol)hookAttribute.ConstructorArguments[0].Value; SerializationHooks.Add(hookType); } } if (TryGetActivatorConstructor(type, _libraryTypes, out var constructorParameters)) { HasActivatorConstructor = true; ActivatorConstructorParameters = constructorParameters; } static bool TryGetActivatorConstructor(INamedTypeSymbol type, LibraryTypes libraryTypes, out List parameters) { parameters = null; if (type.IsAbstract) { return false; } foreach (var constructor in type.GetAllMembers()) { if (constructor.MethodKind != MethodKind.Constructor || constructor.DeclaredAccessibility == Accessibility.Private || constructor.IsImplicitlyDeclared) { continue; } if (constructor.HasAttribute(libraryTypes.GeneratedActivatorConstructorAttribute)) { foreach (var parameter in constructor.Parameters) { var argumentType = parameter.Type.ToTypeSyntax(); (parameters ??= new()).Add(argumentType); } break; } } return parameters is not null; } static string GetTypeParameterName(HashSet names, ITypeParameterSymbol tp) { var count = 0; var result = tp.Name; while (names.Contains(result)) { result = $"{tp.Name}_{++count}"; } names.Add(result); return result.EscapeIdentifier(); } } private INamedTypeSymbol Type { get; } public Accessibility Accessibility { get; } public TypeSyntax TypeSyntax => _typeSyntax ??= Type.ToTypeSyntax(); public TypeSyntax BaseTypeSyntax => _baseTypeSyntax ??= BaseType.ToTypeSyntax(); public bool HasComplexBaseType => !IsValueType && BaseType is { SpecialType: not SpecialType.System_Object }; public bool IncludePrimaryConstructorParameters { get; } public INamedTypeSymbol BaseType => _baseType ??= GetEffectiveBaseType(); private INamedTypeSymbol GetEffectiveBaseType() { var type = Type.EnumUnderlyingType ?? Type.BaseType; while (type != null && type.HasAttribute(_libraryTypes.SerializerTransparentAttribute)) type = type.BaseType; return type; } public string Namespace => Type.GetNamespaceAndNesting(); public string GeneratedNamespace => Namespace switch { { Length: > 0 } ns => $"{CodeGenerator.CodeGeneratorName}.{ns}", _ => CodeGenerator.CodeGeneratorName }; public string Name => Type.Name; public bool IsValueType => Type.IsValueType; public bool IsSealedType => Type.IsSealed; public bool IsAbstractType => Type.IsAbstract; public bool IsEnumType => Type.EnumUnderlyingType != null; public bool IsGenericType => Type.IsGenericType; public List<(string Name, ITypeParameterSymbol Parameter)> TypeParameters { get; } public List Members { get; } public Compilation Compilation { get; } public List ActivatorConstructorParameters { get; } public bool IsEmptyConstructable { get { if (Type.Constructors.Length == 0) { return true; } // Types which have required members are not empty constructable for Orleans, at least not yet. var t = Type; while (t != null) { foreach (var member in t.GetMembers()) { if (member is IPropertySymbol { IsRequired: true } or IFieldSymbol { IsRequired: true }) { return false; } } t = t.BaseType; } foreach (var ctor in Type.Constructors) { if (ctor.Parameters.Length != 0) { continue; } switch (ctor.DeclaredAccessibility) { case Accessibility.Public: return true; } } return false; } } public bool HasActivatorConstructor { get; } public bool UseActivator => Type.HasAttribute(_libraryTypes.UseActivatorAttribute) || !IsEmptyConstructable || HasActivatorConstructor; public bool TrackReferences => !IsValueType && !IsExceptionType && !Type.HasAttribute(_libraryTypes.SuppressReferenceTrackingAttribute); public bool OmitDefaultMemberValues => Type.HasAttribute(_libraryTypes.OmitDefaultMemberValuesAttribute); public List SerializationHooks { get; } public bool IsShallowCopyable => IsEnumType || !Type.HasBaseType(_libraryTypes.Exception) && _libraryTypes.IsShallowCopyable(Type); public bool IsUnsealedImmutable => !Type.IsSealed && IsImmutable; public bool IsImmutable => Type.HasAttribute(_libraryTypes.ImmutableAttribute); public bool IsExceptionType => Type.HasBaseType(_libraryTypes.Exception); public ExpressionSyntax GetObjectCreationExpression() { if (IsValueType) { return DefaultExpression(TypeSyntax); } var instanceConstructors = Type.InstanceConstructors; var isConstructible = false; if (!instanceConstructors.IsDefaultOrEmpty) { foreach (var ctor in instanceConstructors) { if (ctor.Parameters.IsDefaultOrEmpty) { if (ctor.IsImplicitlyDeclared || ctor.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal) { isConstructible = true; } break; } } } if (isConstructible) { return ObjectCreationExpression(TypeSyntax, ArgumentList(), null); } else { return CastExpression( TypeSyntax, InvocationExpression(_libraryTypes.RuntimeHelpers.ToTypeSyntax().Member("GetUninitializedObject")) .AddArgumentListArguments( Argument(TypeOfExpression(TypeSyntax)))); } } } } ================================================ FILE: src/Orleans.CodeGenerator/Model/WellKnownCodecDescription.cs ================================================ using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator { internal sealed class WellKnownCodecDescription { public WellKnownCodecDescription(ITypeSymbol underlyingType, INamedTypeSymbol codecType) { UnderlyingType = underlyingType; CodecType = codecType; } public readonly ITypeSymbol UnderlyingType; public readonly INamedTypeSymbol CodecType; } internal sealed class WellKnownCopierDescription : ICopierDescription { public WellKnownCopierDescription(ITypeSymbol underlyingType, INamedTypeSymbol codecType) { UnderlyingType = underlyingType; CopierType = codecType; } public ITypeSymbol UnderlyingType { get; } public INamedTypeSymbol CopierType { get; } } } ================================================ FILE: src/Orleans.CodeGenerator/Orleans.CodeGenerator.csproj ================================================  Microsoft.Orleans.CodeGenerator netstandard2.0 Code generation library for Orleans.Serialization true true false true false true true $(NoWarn);RS1038;RS1042 %(Identity) true true true %(Identity) true true %(Identity) true True True Resources.resx ResXFileCodeGenerator Resources.Designer.cs ================================================ FILE: src/Orleans.CodeGenerator/OrleansGeneratorDiagnosticAnalysisException.cs ================================================ using Microsoft.CodeAnalysis; using System; namespace Orleans.CodeGenerator { public class OrleansGeneratorDiagnosticAnalysisException : Exception { public OrleansGeneratorDiagnosticAnalysisException(Diagnostic diagnostic) : base(diagnostic.GetMessage()) { Diagnostic = diagnostic; } public Diagnostic Diagnostic { get; } } } ================================================ FILE: src/Orleans.CodeGenerator/OrleansSourceGenerator.cs ================================================ using System; using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Orleans.CodeGenerator.Diagnostics; using Orleans.CodeGenerator.Model; #pragma warning disable RS1035 // Do not use APIs banned for analyzers namespace Orleans.CodeGenerator { [Generator] public class OrleansSerializationSourceGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { try { var processName = Process.GetCurrentProcess().ProcessName.ToLowerInvariant(); if (processName.Contains("devenv") || processName.Contains("servicehub")) { return; } if (!Debugger.IsAttached && context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleans_designtimebuild", out var isDesignTimeBuild) && string.Equals("true", isDesignTimeBuild, StringComparison.OrdinalIgnoreCase)) { return; } if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleans_attachdebugger", out var attachDebuggerOption) && string.Equals("true", attachDebuggerOption, StringComparison.OrdinalIgnoreCase)) { Debugger.Launch(); } var options = new CodeGeneratorOptions(); if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleans_generatefieldids", out var generateFieldIds) && generateFieldIds is { Length: > 0 }) { if (Enum.TryParse(generateFieldIds, out GenerateFieldIds fieldIdOption)) { options.GenerateFieldIds = fieldIdOption; } } if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.orleansgeneratecompatibilityinvokers", out var generateCompatInvokersValue) && bool.TryParse(generateCompatInvokersValue, out var genCompatInvokers)) { options.GenerateCompatibilityInvokers = genCompatInvokers; } var codeGenerator = new CodeGenerator(context.Compilation, options); var syntax = codeGenerator.GenerateCode(context.CancellationToken); var sourceString = syntax.NormalizeWhitespace().ToFullString(); var sourceText = SourceText.From(sourceString, Encoding.UTF8); context.AddSource($"{context.Compilation.AssemblyName ?? "assembly"}.orleans.g.cs", sourceText); } catch (Exception exception) { if (!HandleException(context, exception)) { throw; } } static bool HandleException(GeneratorExecutionContext context, Exception exception) { if (exception is OrleansGeneratorDiagnosticAnalysisException analysisException) { context.ReportDiagnostic(analysisException.Diagnostic); return true; } context.ReportDiagnostic(UnhandledCodeGenerationExceptionDiagnostic.CreateDiagnostic(exception)); Console.WriteLine(exception); Console.WriteLine(exception.StackTrace); return false; } } public void Initialize(GeneratorInitializationContext context) { } } } #pragma warning restore RS1035 // Do not use APIs banned for analyzers ================================================ FILE: src/Orleans.CodeGenerator/Properties/launchSettings.json ================================================ { "profiles": { "Roslyn": { "commandName": "DebugRoslynComponent", "targetProject": "..\\..\\test\\Orleans.Serialization.UnitTests\\Orleans.Serialization.UnitTests.csproj" } } } ================================================ FILE: src/Orleans.CodeGenerator/PropertyUtility.cs ================================================ #nullable enable using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace Orleans.CodeGenerator { public static class PropertyUtility { private static readonly Regex PropertyMatchRegex = new("^<([^>]+)>.*$", RegexOptions.Compiled); public static IPropertySymbol? GetMatchingProperty(IFieldSymbol field) { if (field.ContainingType is null) return null; return GetMatchingProperty(field, field.ContainingType.GetMembers()); } public static bool IsCompilerGenerated(this ISymbol? symbol) => symbol?.GetAttributes().Any(a => a.AttributeClass?.Name == "CompilerGeneratedAttribute") == true; public static bool IsCompilerGenerated(this IPropertySymbol? property) => property?.GetMethod.IsCompilerGenerated() == true && property.SetMethod.IsCompilerGenerated(); public static IParameterSymbol? GetMatchingPrimaryConstructorParameter(IPropertySymbol property, IEnumerable constructorParameters) { if (!property.IsCompilerGenerated()) return null; return constructorParameters.FirstOrDefault(p => string.Equals(p.Name, property.Name, StringComparison.Ordinal) && SymbolEqualityComparer.Default.Equals(p.Type, property.Type)); } public static IPropertySymbol? GetMatchingProperty(IFieldSymbol field, IEnumerable memberSymbols) { var propertyName = PropertyMatchRegex.Match(field.Name); if (!propertyName.Success) { return null; } var name = propertyName.Groups[1].Value; var candidates = memberSymbols.OfType() .Where(property => string.Equals(name, property.Name, StringComparison.Ordinal) && SymbolEqualityComparer.Default.Equals(field.Type, property.Type)).ToArray(); return candidates.Length == 1 ? candidates[0] : null; } public static IFieldSymbol? GetMatchingField(IPropertySymbol property) { if (property.ContainingType is null) return null; return GetMatchingField(property, property.ContainingType.GetMembers()); } public static IFieldSymbol? GetMatchingField(IPropertySymbol property, IEnumerable memberSymbols) { var backingFieldName = $"<{property.Name}>k__BackingField"; var candidates = (from field in memberSymbols.OfType() where SymbolEqualityComparer.Default.Equals(field.Type, property.Type) where field.Name == backingFieldName || GetCanonicalName(field.Name) == GetCanonicalName(property.Name) select field).ToArray(); return candidates.Length == 1 ? candidates[0] : null; } public static string GetCanonicalName(string name) { name = name.TrimStart('_'); if (name.Length > 0 && char.IsUpper(name[0])) name = $"{char.ToLowerInvariant(name[0])}{name.Substring(1)}"; return name; } } } ================================================ FILE: src/Orleans.CodeGenerator/ProxyGenerator.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.Diagnostics; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Orleans.CodeGenerator.CopierGenerator; using static Orleans.CodeGenerator.InvokableGenerator; using static Orleans.CodeGenerator.SerializerGenerator; namespace Orleans.CodeGenerator { /// /// Generates RPC stub objects called invokers. /// internal class ProxyGenerator { private const string CopyContextPoolMemberName = "CopyContextPool"; private const string CodecProviderMemberName = "CodecProvider"; private readonly CodeGenerator _codeGenerator; public ProxyGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } private LibraryTypes LibraryTypes => _codeGenerator.LibraryTypes; public (ClassDeclarationSyntax, GeneratedProxyDescription) Generate(ProxyInterfaceDescription interfaceDescription) { var generatedClassName = GetSimpleClassName(interfaceDescription); var fieldDescriptions = GetFieldDescriptions(interfaceDescription); var fieldDeclarations = GetFieldDeclarations(fieldDescriptions); var proxyMethods = CreateProxyMethods(fieldDescriptions, interfaceDescription); var ctors = GenerateConstructors(generatedClassName, fieldDescriptions, interfaceDescription.ProxyBaseType); var classDeclaration = ClassDeclaration(generatedClassName) .AddBaseListTypes( SimpleBaseType(interfaceDescription.ProxyBaseType.ToTypeSyntax()), SimpleBaseType(interfaceDescription.InterfaceType.ToTypeSyntax())) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()) .AddMembers(fieldDeclarations) .AddMembers(ctors) .AddMembers(proxyMethods); var typeParameters = interfaceDescription.TypeParameters; if (typeParameters.Count > 0) { classDeclaration = SyntaxFactoryUtility.AddGenericTypeParameters(classDeclaration, typeParameters); } return (classDeclaration, new GeneratedProxyDescription(interfaceDescription, generatedClassName)); } public static string GetSimpleClassName(ProxyInterfaceDescription interfaceDescription) => $"Proxy_{SyntaxGeneration.Identifier.SanitizeIdentifierName(interfaceDescription.Name)}"; private List GetFieldDescriptions( ProxyInterfaceDescription interfaceDescription) { var fields = new List(); // Add a copier field for any method parameter which does not have a static codec. var paramCopiers = interfaceDescription.Methods .Where(method => method.MethodTypeParameters.Count == 0) .SelectMany(method => method.GeneratedInvokable.Members); _codeGenerator.CopierGenerator.GetCopierFieldDescriptions(paramCopiers, fields); return fields; } private MemberDeclarationSyntax[] GetFieldDeclarations(List fieldDescriptions) { return fieldDescriptions.Select(GetFieldDeclaration).ToArray(); static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription description) { return FieldDeclaration(VariableDeclaration(description.FieldType, SingletonSeparatedList(VariableDeclarator(description.FieldName)))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)); } } private MemberDeclarationSyntax[] CreateProxyMethods( List fieldDescriptions, ProxyInterfaceDescription interfaceDescription) { var res = new List(); foreach (var methodDescription in interfaceDescription.Methods) { res.Add(CreateProxyMethod(methodDescription)); } return res.ToArray(); MethodDeclarationSyntax CreateProxyMethod(ProxyMethodDescription methodDescription) { var (isAsync, body) = CreateAsyncProxyMethodBody(fieldDescriptions, methodDescription); var method = methodDescription.Method; var declaration = MethodDeclaration(method.ReturnType.ToTypeSyntax(methodDescription.TypeParameterSubstitutions), method.Name.EscapeIdentifier()) .AddParameterListParameters(method.Parameters.Select((p, i) => GetParameterSyntax(i, p, methodDescription.TypeParameterSubstitutions)).ToArray()) .WithBody(body); if (isAsync) { declaration = declaration.WithModifiers(TokenList(Token(SyntaxKind.AsyncKeyword))); } var explicitInterfaceSpecifier = ExplicitInterfaceSpecifier(methodDescription.Method.ContainingType.ToNameSyntax()); declaration = declaration.WithExplicitInterfaceSpecifier(explicitInterfaceSpecifier); if (methodDescription.MethodTypeParameters.Count > 0) { declaration = declaration.WithTypeParameterList( TypeParameterList(SeparatedList(methodDescription.MethodTypeParameters.Select(tp => TypeParameter(tp.Name))))); } return declaration; } } private (bool IsAsync, BlockSyntax body) CreateAsyncProxyMethodBody( List fieldDescriptions, ProxyMethodDescription methodDescription) { var statements = new List(); var requestVar = IdentifierName("request"); var methodSymbol = methodDescription.Method; var invokable = methodDescription.GeneratedInvokable; ExpressionSyntax createRequestExpr = (!invokable.IsEmptyConstructable || invokable.UseActivator) switch { true => InvocationExpression(ThisExpression().Member("GetInvokable", invokable.TypeSyntax)) .WithArgumentList(ArgumentList(SeparatedList())), _ => ObjectCreationExpression(invokable.TypeSyntax).WithArgumentList(ArgumentList()) }; statements.Add( LocalDeclarationStatement( VariableDeclaration( ParseTypeName("var"), SingletonSeparatedList( VariableDeclarator( Identifier("request")) .WithInitializer( EqualsValueClause(createRequestExpr)))))); var codecs = fieldDescriptions.OfType() .Concat(_codeGenerator.LibraryTypes.StaticCopiers) .ToList(); // Set request object fields from method parameters. var parameterIndex = 0; var parameters = invokable.Members.OfType().Select(member => new SerializableMethodMember(member)); ExpressionSyntax copyContextPool = BaseExpression().Member(CopyContextPoolMemberName); ExpressionSyntax copyContextVariable = IdentifierName("copyContext"); var hasCopyContext = false; foreach (var parameter in parameters) { // Only create a copy context as needed. if (!hasCopyContext && !parameter.IsShallowCopyable) { // C#: using var copyContext = base.CopyContext.GetContext(); statements.Add( LocalDeclarationStatement( VariableDeclaration( ParseTypeName("var"), SingletonSeparatedList( VariableDeclarator(Identifier("copyContext")).WithInitializer( EqualsValueClause(InvocationExpression( copyContextPool.Member("GetContext"), ArgumentList())))))).WithUsingKeyword(Token(SyntaxKind.UsingKeyword))); hasCopyContext = true; } var valueExpression = _codeGenerator.CopierGenerator.GenerateMemberCopy( fieldDescriptions, IdentifierName($"arg{parameterIndex}"), copyContextVariable, codecs, parameter); statements.Add( ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, requestVar.Member($"arg{parameterIndex}"), valueExpression))); parameterIndex++; } string invokeMethodName = default; foreach (var attr in methodDescription.Method.GetAttributes()) { if (attr.AttributeClass.GetAttributes(LibraryTypes.InvokeMethodNameAttribute, out var attrs)) { foreach (var methodAttr in attrs) { invokeMethodName = (string)methodAttr.ConstructorArguments.First().Value; } } } var methodReturnType = methodDescription.Method.ReturnType; if (methodReturnType is not INamedTypeSymbol namedMethodReturnType) { var diagnostic = InvalidRpcMethodReturnTypeDiagnostic.CreateDiagnostic(methodDescription.InvokableMethod); throw new OrleansGeneratorDiagnosticAnalysisException(diagnostic); } ExpressionSyntax baseInvokeExpression; var isVoid = methodReturnType.SpecialType is SpecialType.System_Void; if (namedMethodReturnType.TypeArguments.Length == 1) { // Task / ValueTask var resultType = namedMethodReturnType.TypeArguments[0]; baseInvokeExpression = BaseExpression().Member( invokeMethodName ?? "InvokeAsync", resultType.ToTypeSyntax(methodDescription.TypeParameterSubstitutions)); } else if (isVoid) { // void baseInvokeExpression = BaseExpression().Member(invokeMethodName ?? "Invoke"); } else { // Task / ValueTask baseInvokeExpression = BaseExpression().Member(invokeMethodName ?? "InvokeAsync"); } // C#: base.InvokeAsync(request); var invocationExpression = InvocationExpression( baseInvokeExpression, ArgumentList(SeparatedList(new[] { Argument(requestVar) }))); var rt = namedMethodReturnType.ConstructedFrom; bool isAsync; if (SymbolEqualityComparer.Default.Equals(rt, LibraryTypes.Task_1) || SymbolEqualityComparer.Default.Equals(methodReturnType, LibraryTypes.Task)) { // C#: return .AsTask() statements.Add(ReturnStatement(InvocationExpression(invocationExpression.Member("AsTask"), ArgumentList()))); isAsync = false; } else if (SymbolEqualityComparer.Default.Equals(rt, LibraryTypes.ValueTask_1) || SymbolEqualityComparer.Default.Equals(methodReturnType, LibraryTypes.ValueTask)) { // ValueTask / ValueTask // C#: return statements.Add(ReturnStatement(invocationExpression)); isAsync = false; } else if (invokable.ReturnValueInitializerMethod is { } returnValueInitializerMethod) { // C#: return request.(this); statements.Add(ReturnStatement(InvocationExpression(requestVar.Member(returnValueInitializerMethod), ArgumentList(SingletonSeparatedList(Argument(ThisExpression())))))); isAsync = false; } else if (isVoid) { // C#: statements.Add(ExpressionStatement(invocationExpression)); isAsync = false; } else if (rt.Arity == 0) { // C#: await statements.Add(ExpressionStatement(AwaitExpression(invocationExpression))); isAsync = true; } else { // C#: return await statements.Add(ReturnStatement(AwaitExpression(invocationExpression))); isAsync = true; } return (isAsync, Block(statements)); } private MemberDeclarationSyntax[] GenerateConstructors( string simpleClassName, List fieldDescriptions, INamedTypeSymbol baseType) { if (baseType is null) { return Array.Empty(); } var bodyStatements = GetBodyStatements(); var res = new List(); foreach (var member in baseType.GetMembers()) { if (member is not IMethodSymbol method) { continue; } if (method.MethodKind != MethodKind.Constructor) { continue; } if (method.DeclaredAccessibility == Accessibility.Private) { continue; } res.Add(CreateConstructor(method)); } return res.ToArray(); ConstructorDeclarationSyntax CreateConstructor(IMethodSymbol baseConstructor) { return ConstructorDeclaration(simpleClassName) .AddParameterListParameters(baseConstructor.Parameters.Select((p, i) => GetParameterSyntax(i, p, typeParameterSubstitutions: null)).ToArray()) .WithModifiers(TokenList(GetModifiers(baseConstructor))) .WithInitializer( ConstructorInitializer( SyntaxKind.BaseConstructorInitializer, ArgumentList( SeparatedList(baseConstructor.Parameters.Select(GetBaseInitializerArgument))))) .WithBody(Block(bodyStatements)); } static SyntaxToken[] GetModifiers(IMethodSymbol method) { switch (method.DeclaredAccessibility) { case Accessibility.Public: case Accessibility.Protected: return new[] { Token(SyntaxKind.PublicKeyword) }; case Accessibility.Internal: case Accessibility.ProtectedOrInternal: case Accessibility.ProtectedAndInternal: return new[] { Token(SyntaxKind.InternalKeyword) }; default: return Array.Empty(); } } static ArgumentSyntax GetBaseInitializerArgument(IParameterSymbol parameter, int index) { var name = $"arg{index}"; var result = Argument(IdentifierName(name)); switch (parameter.RefKind) { case RefKind.None: break; case RefKind.Ref: result = result.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); break; case RefKind.Out: result = result.WithRefOrOutKeyword(Token(SyntaxKind.OutKeyword)); break; default: break; } return result; } List GetBodyStatements() { var res = new List(); foreach (var field in fieldDescriptions) { switch (field) { case GeneratedFieldDescription _ when field.IsInjected: res.Add(ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, ThisExpression().Member(field.FieldName.ToIdentifierName()), Unwrapped(field.FieldName.ToIdentifierName())))); break; case CopierFieldDescription codec: { res.Add(ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, field.FieldName.ToIdentifierName(), GetService(field.FieldType)))); } break; } } return res; static ExpressionSyntax Unwrapped(ExpressionSyntax expr) { return InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("OrleansGeneratedCodeHelper"), IdentifierName("UnwrapService")), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(expr) }))); } static ExpressionSyntax GetService(TypeSyntax type) { return InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("OrleansGeneratedCodeHelper"), GenericName(Identifier("GetService"), TypeArgumentList(SingletonSeparatedList(type)))), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(IdentifierName(CodecProviderMemberName)) }))); } } } private ParameterSyntax GetParameterSyntax(int index, IParameterSymbol parameter, Dictionary typeParameterSubstitutions) { var result = Parameter(Identifier($"arg{index}")).WithType(parameter.Type.ToTypeSyntax(typeParameterSubstitutions)); switch (parameter.RefKind) { case RefKind.None: break; case RefKind.Ref: result = result.WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))); break; case RefKind.Out: result = result.WithModifiers(TokenList(Token(SyntaxKind.OutKeyword))); break; case RefKind.In: result = result.WithModifiers(TokenList(Token(SyntaxKind.InKeyword))); break; default: break; } return result; } } } ================================================ FILE: src/Orleans.CodeGenerator/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.CodeGenerator { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Orleans.CodeGenerator.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to Serializable properties must have accessible setters. Ensure that the property has a non-private set method.. /// internal static string InaccessibleSetterDescription { get { return ResourceManager.GetString("InaccessibleSetterDescription", resourceCulture); } } /// /// Looks up a localized string similar to Serializable property {0} does not have an accessible setter. /// internal static string InaccessibleSetterMessageFormat { get { return ResourceManager.GetString("InaccessibleSetterMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Serializable properties with bodies must be settable. /// internal static string InaccessibleSetterTitle { get { return ResourceManager.GetString("InaccessibleSetterTitle", resourceCulture); } } /// /// Looks up a localized string similar to The return type of an RPC method must conform to the list of supported types, such as Task, Task<T>, ValueTask, and ValueTask<T>.. /// internal static string InvalidRpcMethodReturnTypeDescription { get { return ResourceManager.GetString("InvalidRpcMethodReturnTypeDescription", resourceCulture); } } /// /// Looks up a localized string similar to The return type {0} for the RPC interface method {1} is unsupported and must be changed to one of the following types: {2}. /// internal static string InvalidRpcMethodReturnTypeMessageFormat { get { return ResourceManager.GetString("InvalidRpcMethodReturnTypeMessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Invalid return type for RPC interface method. /// internal static string InvalidRpcMethodReturnTypeTitle { get { return ResourceManager.GetString("InvalidRpcMethodReturnTypeTitle", resourceCulture); } } } } ================================================ FILE: src/Orleans.CodeGenerator/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Serializable properties must have accessible setters. Ensure that the property has a non-private set method. Serializable property {0} does not have an accessible setter Serializable properties with bodies must be settable The return type of an RPC method must conform to the list of supported types, such as Task, Task<T>, ValueTask, and ValueTask<T>. The return type {0} for the RPC interface method {1} is unsupported and must be changed to one of the following types: {2} Invalid return type for RPC interface method ================================================ FILE: src/Orleans.CodeGenerator/SerializerGenerator.cs ================================================ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.Diagnostics; using Orleans.CodeGenerator.SyntaxGeneration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Orleans.CodeGenerator.InvokableGenerator; namespace Orleans.CodeGenerator { internal class SerializerGenerator { private const string BaseTypeSerializerFieldName = "_baseTypeSerializer"; private const string ActivatorFieldName = "_activator"; private const string SerializeMethodName = "Serialize"; private const string DeserializeMethodName = "Deserialize"; private const string WriteFieldMethodName = "WriteField"; private const string ReadValueMethodName = "ReadValue"; private const string CodecFieldTypeFieldName = "_codecFieldType"; private readonly CodeGenerator _codeGenerator; public SerializerGenerator(CodeGenerator codeGenerator) { _codeGenerator = codeGenerator; } private LibraryTypes LibraryTypes => _codeGenerator.LibraryTypes; public ClassDeclarationSyntax Generate(ISerializableTypeDescription type) { var simpleClassName = GetSimpleClassName(type); var members = new List(); foreach (var member in type.Members) { if (!member.IsSerializable) { continue; } if (member is ISerializableMember serializable) { members.Add(serializable); } else if (member is IFieldDescription or IPropertyDescription) { members.Add(new SerializableMember(_codeGenerator, member, members.Count)); } else if (member is MethodParameterFieldDescription methodParameter) { members.Add(new SerializableMethodMember(methodParameter)); } } var fieldDescriptions = GetFieldDescriptions(type, members); var fieldDeclarations = GetFieldDeclarations(fieldDescriptions); var ctor = GenerateConstructor(simpleClassName, fieldDescriptions); var accessibility = type.Accessibility switch { Accessibility.Public => SyntaxKind.PublicKeyword, _ => SyntaxKind.InternalKeyword, }; var baseType = (type.IsAbstractType ? LibraryTypes.AbstractTypeSerializer : LibraryTypes.FieldCodec_1).ToTypeSyntax(type.TypeSyntax); var classDeclaration = ClassDeclaration(simpleClassName) .AddBaseListTypes(SimpleBaseType(baseType)) .AddModifiers(Token(accessibility), Token(SyntaxKind.SealedKeyword)) .AddAttributeLists(CodeGenerator.GetGeneratedCodeAttributes()) .AddMembers(fieldDeclarations); if (ctor != null) classDeclaration = classDeclaration.AddMembers(ctor); if (type.IsEnumType) { var writeMethod = GenerateEnumWriteMethod(type); var readMethod = GenerateEnumReadMethod(type); classDeclaration = classDeclaration.AddMembers(writeMethod, readMethod); } else { var serializeMethod = GenerateSerializeMethod(type, fieldDescriptions, members); var deserializeMethod = GenerateDeserializeMethod(type, fieldDescriptions, members); if (type.IsAbstractType) { if (serializeMethod != null) classDeclaration = classDeclaration.AddMembers(serializeMethod); if (deserializeMethod != null) classDeclaration = classDeclaration.AddMembers(deserializeMethod); } else { var writeFieldMethod = GenerateCompoundTypeWriteFieldMethod(type); var readValueMethod = GenerateCompoundTypeReadValueMethod(type, fieldDescriptions); classDeclaration = classDeclaration.AddMembers(serializeMethod, deserializeMethod, writeFieldMethod, readValueMethod); var serializerInterface = type.IsValueType ? LibraryTypes.ValueSerializer : type.IsSealedType ? null : LibraryTypes.BaseCodec_1; if (serializerInterface != null) classDeclaration = classDeclaration.AddBaseListTypes(SimpleBaseType(serializerInterface.ToTypeSyntax(type.TypeSyntax))); } } if (type.IsGenericType) { classDeclaration = SyntaxFactoryUtility.AddGenericTypeParameters(classDeclaration, type.TypeParameters); } return classDeclaration; } public static string GetSimpleClassName(ISerializableTypeDescription serializableType) => GetSimpleClassName(serializableType.Name); public static string GetSimpleClassName(string name) => $"Codec_{name}"; public static string GetGeneratedNamespaceName(ITypeSymbol type) => type.GetNamespaceAndNesting() switch { { Length: > 0 } ns => $"{CodeGenerator.CodeGeneratorName}.{ns}", _ => CodeGenerator.CodeGeneratorName }; private MemberDeclarationSyntax[] GetFieldDeclarations(List fieldDescriptions) { return fieldDescriptions.Select(GetFieldDeclaration).ToArray(); static MemberDeclarationSyntax GetFieldDeclaration(GeneratedFieldDescription description) { switch (description) { case TypeFieldDescription type: return FieldDeclaration( VariableDeclaration( type.FieldType, SingletonSeparatedList(VariableDeclarator(type.FieldName) .WithInitializer(EqualsValueClause(TypeOfExpression(type.UnderlyingTypeSyntax)))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case CodecFieldTypeFieldDescription type: return FieldDeclaration( VariableDeclaration( type.FieldType, SingletonSeparatedList(VariableDeclarator(type.FieldName) .WithInitializer(EqualsValueClause(TypeOfExpression(type.CodecFieldType)))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case FieldAccessorDescription accessor when accessor.InitializationSyntax != null: return FieldDeclaration(VariableDeclaration(accessor.FieldType, SingletonSeparatedList(VariableDeclarator(accessor.FieldName).WithInitializer(EqualsValueClause(accessor.InitializationSyntax))))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)); case FieldAccessorDescription accessor when accessor.InitializationSyntax == null: //[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_Amount")] //extern static void SetAmount(External instance, int value); return MethodDeclaration( PredefinedType(Token(SyntaxKind.VoidKeyword)), accessor.AccessorName) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ExternKeyword), Token(SyntaxKind.StaticKeyword)) .AddAttributeLists(AttributeList(SingletonSeparatedList( Attribute(IdentifierName("System.Runtime.CompilerServices.UnsafeAccessor")) .AddArgumentListArguments( AttributeArgument( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("System.Runtime.CompilerServices.UnsafeAccessorKind"), IdentifierName("Method"))), AttributeArgument( LiteralExpression( SyntaxKind.StringLiteralExpression, Literal($"set_{accessor.FieldName}"))) .WithNameEquals(NameEquals("Name")))))) .WithParameterList( ParameterList(SeparatedList(new[] { Parameter(Identifier("instance")).WithType(accessor.ContainingType), Parameter(Identifier("value")).WithType(description.FieldType) }))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); default: return FieldDeclaration(VariableDeclaration(description.FieldType, SingletonSeparatedList(VariableDeclarator(description.FieldName)))) .AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)); } } } private ConstructorDeclarationSyntax GenerateConstructor(string simpleClassName, List fieldDescriptions) { var codecProviderAdded = false; var parameters = new List(); var statements = new List(); foreach (var field in fieldDescriptions) { switch (field) { case GeneratedFieldDescription _ when field.IsInjected: parameters.Add(Parameter(field.FieldName.ToIdentifier()).WithType(field.FieldType)); statements.Add(ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, ThisExpression().Member(field.FieldName.ToIdentifierName()), Unwrapped(field.FieldName.ToIdentifierName())))); break; case CodecFieldDescription or BaseCodecFieldDescription when !field.IsInjected: if (!codecProviderAdded) { parameters.Add(Parameter(Identifier("codecProvider")).WithType(LibraryTypes.ICodecProvider.ToTypeSyntax())); codecProviderAdded = true; } var codec = InvocationExpression( IdentifierName("OrleansGeneratedCodeHelper").Member(GenericName(Identifier("GetService"), TypeArgumentList(SingletonSeparatedList(field.FieldType)))), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(IdentifierName("codecProvider")) }))); statements.Add(ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, field.FieldName.ToIdentifierName(), codec))); break; } } return statements.Count == 0 ? null : ConstructorDeclaration(simpleClassName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters.ToArray()) .AddBodyStatements(statements.ToArray()); static ExpressionSyntax Unwrapped(ExpressionSyntax expr) { return InvocationExpression( IdentifierName("OrleansGeneratedCodeHelper").Member("UnwrapService"), ArgumentList(SeparatedList(new[] { Argument(ThisExpression()), Argument(expr) }))); } } private List GetFieldDescriptions( ISerializableTypeDescription serializableTypeDescription, List members) { var fields = new List(); if (!serializableTypeDescription.IsAbstractType) { fields.Add(new CodecFieldTypeFieldDescription(LibraryTypes.Type.ToTypeSyntax(), CodecFieldTypeFieldName, serializableTypeDescription.TypeSyntax)); } if (serializableTypeDescription.HasComplexBaseType) { fields.Add(GetBaseTypeField(serializableTypeDescription)); } if (serializableTypeDescription.UseActivator && !serializableTypeDescription.IsAbstractType) { fields.Add(new ActivatorFieldDescription(LibraryTypes.IActivator_1.ToTypeSyntax(serializableTypeDescription.TypeSyntax), ActivatorFieldName)); } int typeIndex = 0; foreach (var member in serializableTypeDescription.Members.Distinct(MemberDescriptionTypeComparer.Default)) { if (!member.IsSerializable) { continue; } // Add a codec field for any field in the target which does not have a static codec. if (LibraryTypes.StaticCodecs.FindByUnderlyingType(member.Type) is not null) continue; fields.Add(new TypeFieldDescription(LibraryTypes.Type.ToTypeSyntax(), $"_type{typeIndex}", member.TypeSyntax, member.Type)); fields.Add(GetCodecDescription(member, typeIndex)); typeIndex++; } foreach (var member in members) { if (member.GetGetterFieldDescription() is { } getterFieldDescription) { fields.Add(getterFieldDescription); } if (member.GetSetterFieldDescription() is { } setterFieldDescription) { fields.Add(setterFieldDescription); } } for (var hookIndex = 0; hookIndex < serializableTypeDescription.SerializationHooks.Count; ++hookIndex) { var hookType = serializableTypeDescription.SerializationHooks[hookIndex]; fields.Add(new SerializationHookFieldDescription(hookType.ToTypeSyntax(), $"_hook{hookIndex}")); } return fields; CodecFieldDescription GetCodecDescription(IMemberDescription member, int index) { var t = member.Type; TypeSyntax codecType = null; if (t.HasAttribute(LibraryTypes.GenerateSerializerAttribute) && (SymbolEqualityComparer.Default.Equals(t.ContainingAssembly, LibraryTypes.Compilation.Assembly) || t.ContainingAssembly.HasAttribute(LibraryTypes.TypeManifestProviderAttribute)) && t is not INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 0 }) { // Use the concrete generated type and avoid expensive interface dispatch (except for complex nested cases that will fall back to IFieldCodec) SimpleNameSyntax name; if (t is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType) { // Construct the full generic type name name = GenericName(Identifier(GetSimpleClassName(t.Name)), TypeArgumentList(SeparatedList(namedTypeSymbol.TypeArguments.Select(arg => member.GetTypeSyntax(arg))))); } else { name = IdentifierName(GetSimpleClassName(t.Name)); } codecType = QualifiedName(ParseName(GetGeneratedNamespaceName(t)), name); } else if (t is IArrayTypeSymbol { IsSZArray: true } array) { codecType = LibraryTypes.ArrayCodec.Construct(array.ElementType).ToTypeSyntax(); } else if (LibraryTypes.WellKnownCodecs.FindByUnderlyingType(t) is { } codec) { // The codec is not a static codec and is also not a generic codec. codecType = codec.CodecType.ToTypeSyntax(); } else if (t is INamedTypeSymbol { ConstructedFrom: { } unboundFieldType } named && LibraryTypes.WellKnownCodecs.FindByUnderlyingType(unboundFieldType) is { } genericCodec) { // Construct the generic codec type using the field's type arguments. codecType = genericCodec.CodecType.Construct(named.TypeArguments.ToArray()).ToTypeSyntax(); } else { // Use the IFieldCodec interface codecType = LibraryTypes.FieldCodec_1.ToTypeSyntax(member.TypeSyntax); } return new CodecFieldDescription(codecType, $"_codec{index}", t); } } private BaseCodecFieldDescription GetBaseTypeField(ISerializableTypeDescription serializableTypeDescription) { var baseType = serializableTypeDescription.BaseType; if (baseType.HasAttribute(LibraryTypes.GenerateSerializerAttribute) && (SymbolEqualityComparer.Default.Equals(baseType.ContainingAssembly, LibraryTypes.Compilation.Assembly) || baseType.ContainingAssembly.HasAttribute(LibraryTypes.TypeManifestProviderAttribute)) && baseType is not INamedTypeSymbol { IsGenericType: true }) { // Use the concrete generated type and avoid expensive interface dispatch (except for generic types that will fall back to IBaseCodec) return new(QualifiedName(ParseName(GetGeneratedNamespaceName(baseType)), IdentifierName(GetSimpleClassName(baseType.Name))), true); } return new(LibraryTypes.BaseCodec_1.ToTypeSyntax(serializableTypeDescription.BaseTypeSyntax)); } private MemberDeclarationSyntax GenerateSerializeMethod( ISerializableTypeDescription type, List serializerFields, List members) { var returnType = PredefinedType(Token(SyntaxKind.VoidKeyword)); var writerParam = "writer".ToIdentifierName(); var instanceParam = "instance".ToIdentifierName(); var body = new List(); if (type.HasComplexBaseType) { body.Add( ExpressionStatement( InvocationExpression( BaseTypeSerializerFieldName.ToIdentifierName().Member(SerializeMethodName), ArgumentList(SeparatedList(new[] { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(instanceParam) }))))); body.Add(ExpressionStatement(InvocationExpression(writerParam.Member("WriteEndBase"), ArgumentList()))); } AddSerializationCallbacks(type, instanceParam, "OnSerializing", body); // Order members according to their FieldId, since fields must be serialized in order and FieldIds are serialized as deltas. var previousFieldIdVar = "previousFieldId".ToIdentifierName(); if (type.OmitDefaultMemberValues && members.Count > 0) { // C#: uint previousFieldId = 0; body.Add(LocalDeclarationStatement( VariableDeclaration( PredefinedType(Token(SyntaxKind.UIntKeyword)), SingletonSeparatedList(VariableDeclarator(previousFieldIdVar.Identifier) .WithInitializer(EqualsValueClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0U)))))))); } if (type.IncludePrimaryConstructorParameters) { AddSerializationMembers(type, serializerFields, members.Where(m => m.IsPrimaryConstructorParameter), writerParam, instanceParam, previousFieldIdVar, body); body.Add(ExpressionStatement(InvocationExpression(writerParam.Member("WriteEndBase"), ArgumentList()))); } AddSerializationMembers(type, serializerFields, members.Where(m => !m.IsPrimaryConstructorParameter), writerParam, instanceParam, previousFieldIdVar, body); AddSerializationCallbacks(type, instanceParam, "OnSerialized", body); if (body.Count == 0 && type.IsAbstractType) return null; var parameters = new[] { Parameter("writer".ToIdentifier()).WithType(LibraryTypes.Writer.ToTypeSyntax()).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter("instance".ToIdentifier()).WithType(type.TypeSyntax) }; if (type.IsValueType) { parameters[1] = parameters[1].WithModifiers(LibraryTypes.HasScopedKeyword() ? TokenList(Token(SyntaxKind.ScopedKeyword), Token(SyntaxKind.RefKeyword)) : TokenList(Token(SyntaxKind.RefKeyword))); } var res = MethodDeclaration(returnType, SerializeMethodName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddTypeParameterListParameters(TypeParameter("TBufferWriter")) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); res = type.IsAbstractType ? res.AddModifiers(Token(SyntaxKind.OverrideKeyword)) : res.AddConstraintClauses(TypeParameterConstraintClause("TBufferWriter").AddConstraints(TypeConstraint(LibraryTypes.IBufferWriter.ToTypeSyntax(PredefinedType(Token(SyntaxKind.ByteKeyword)))))); return res; } private void AddSerializationMembers(ISerializableTypeDescription type, List serializerFields, IEnumerable members, IdentifierNameSyntax writerParam, IdentifierNameSyntax instanceParam, IdentifierNameSyntax previousFieldIdVar, List body) { uint previousFieldId = 0; foreach (var member in members.OrderBy(m => m.Member.FieldId)) { var description = member.Member; ExpressionSyntax fieldIdDeltaExpr; if (type.OmitDefaultMemberValues) { // C#: - previousFieldId fieldIdDeltaExpr = BinaryExpression(SyntaxKind.SubtractExpression, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(description.FieldId)), previousFieldIdVar); } else { var fieldIdDelta = description.FieldId - previousFieldId; previousFieldId = description.FieldId; fieldIdDeltaExpr = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(fieldIdDelta)); } // Codecs can either be static classes or injected into the constructor. // Either way, the member signatures are the same. var memberType = description.Type; var staticCodec = LibraryTypes.StaticCodecs.FindByUnderlyingType(memberType); ExpressionSyntax codecExpression; if (staticCodec != null) { codecExpression = staticCodec.CodecType.ToNameSyntax(); } else { var instanceCodec = serializerFields.First(f => f is CodecFieldDescription cf && SymbolEqualityComparer.Default.Equals(cf.UnderlyingType, memberType)); codecExpression = IdentifierName(instanceCodec.FieldName); } // When a static codec is available, we can call it directly and can skip passing the expected type, // since it is known to be the static codec's field type: // C#: .WriteField(ref writer, ) // When no static codec is available: // C#: .WriteField(ref writer, , , ) var writeFieldArgs = new List { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldIdDeltaExpr) }; if (staticCodec is null) writeFieldArgs.Add(Argument(serializerFields.First(f => f is TypeFieldDescription tf && SymbolEqualityComparer.Default.Equals(tf.UnderlyingType, memberType)).FieldName.ToIdentifierName())); writeFieldArgs.Add(Argument(member.GetGetter(instanceParam))); var writeFieldExpr = ExpressionStatement(InvocationExpression(codecExpression.Member("WriteField"), ArgumentList(SeparatedList(writeFieldArgs)))); if (!type.OmitDefaultMemberValues) { body.Add(writeFieldExpr); } else { ExpressionSyntax condition = member.IsValueType switch { true => BinaryExpression(SyntaxKind.NotEqualsExpression, member.GetGetter(instanceParam), LiteralExpression(SyntaxKind.DefaultLiteralExpression)), false => IsPatternExpression(member.GetGetter(instanceParam), TypePattern(PredefinedType(Token(SyntaxKind.ObjectKeyword)))) }; body.Add(IfStatement( condition, Block( writeFieldExpr, ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, previousFieldIdVar, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(description.FieldId))))))); } } } private MemberDeclarationSyntax GenerateDeserializeMethod( ISerializableTypeDescription type, List serializerFields, List members) { var returnType = PredefinedType(Token(SyntaxKind.VoidKeyword)); var readerParam = "reader".ToIdentifierName(); var instanceParam = "instance".ToIdentifierName(); var idVar = "id".ToIdentifierName(); var headerVar = "header".ToIdentifierName(); var body = new List(); if (type.HasComplexBaseType) { // C#: _baseTypeSerializer.Deserialize(ref reader, instance); body.Add( ExpressionStatement( InvocationExpression( BaseTypeSerializerFieldName.ToIdentifierName().Member(DeserializeMethodName), ArgumentList(SeparatedList(new[] { Argument(readerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(instanceParam) }))))); } AddSerializationCallbacks(type, instanceParam, "OnDeserializing", body); int emptyBodyCount; var nonCtorMembers = type.IncludePrimaryConstructorParameters ? members.FindAll(static m => !m.IsPrimaryConstructorParameter) : members; if ((members.Count == 0 || nonCtorMembers.Count == 0) && !type.IncludePrimaryConstructorParameters) { // C#: reader.ConsumeEndBaseOrEndObject(); body.Add(ExpressionStatement(InvocationExpression(readerParam.Member("ConsumeEndBaseOrEndObject")))); emptyBodyCount = 1; } else { // C#: uint id = 0; if (members.Count > 0) { body.Add(LocalDeclarationStatement( VariableDeclaration( PredefinedType(Token(SyntaxKind.UIntKeyword)), SingletonSeparatedList(VariableDeclarator(idVar.Identifier, null, EqualsValueClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0U)))))))); } // C#: Field header = default; body.Add(LocalDeclarationStatement( VariableDeclaration( LibraryTypes.Field.ToTypeSyntax(), SingletonSeparatedList(VariableDeclarator(headerVar.Identifier, null, EqualsValueClause(LiteralExpression(SyntaxKind.DefaultLiteralExpression))))))); emptyBodyCount = 2; if (type.IncludePrimaryConstructorParameters) { var constructorParameterMembers = members.FindAll(m => m.IsPrimaryConstructorParameter); body.Add(GetDeserializerLoop(constructorParameterMembers)); if (members.Count > 0) { body.Add(ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, idVar, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0U))))); } body.Add(IfStatement(headerVar.Member("IsEndBaseFields"), GetDeserializerLoop(nonCtorMembers))); } else { body.Add(GetDeserializerLoop(nonCtorMembers)); } } AddSerializationCallbacks(type, instanceParam, "OnDeserialized", body); if (body.Count == emptyBodyCount && type.IsAbstractType) return null; var genericParam = ParseTypeName("TReaderInput"); var parameters = new[] { Parameter(readerParam.Identifier).WithType(LibraryTypes.Reader.ToTypeSyntax(genericParam)).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter(instanceParam.Identifier).WithType(type.TypeSyntax) }; if (type.IsValueType) { parameters[1] = parameters[1].WithModifiers(LibraryTypes.HasScopedKeyword() ? TokenList(Token(SyntaxKind.ScopedKeyword), Token(SyntaxKind.RefKeyword)) : TokenList(Token(SyntaxKind.RefKeyword))); } var res = MethodDeclaration(returnType, DeserializeMethodName) .AddTypeParameterListParameters(TypeParameter("TReaderInput")) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); if (type.IsAbstractType) res = res.AddModifiers(Token(SyntaxKind.OverrideKeyword)); return res; // Create the loop body. StatementSyntax GetDeserializerLoop(List members) { var refHeaderVar = ArgumentList(SingletonSeparatedList(Argument(null, Token(SyntaxKind.RefKeyword), headerVar))); if (members.Count == 0) { // C#: reader.ReadFieldHeader(ref header); // C#: reader.ConsumeEndBaseOrEndObject(ref header); return Block( ExpressionStatement(InvocationExpression(readerParam.Member("ReadFieldHeader"), refHeaderVar)), ExpressionStatement(InvocationExpression(readerParam.Member("ConsumeEndBaseOrEndObject"), refHeaderVar))); } var loopBody = new List(); // C#: reader.ReadFieldHeader(ref header); // C#: if (header.IsEndBaseOrEndObject) break; // C#: id += header.FieldIdDelta; var readFieldHeader = ExpressionStatement(InvocationExpression(readerParam.Member("ReadFieldHeader"), refHeaderVar)); var endObjectCheck = IfStatement(headerVar.Member("IsEndBaseOrEndObject"), BreakStatement()); var idUpdate = ExpressionStatement(AssignmentExpression(SyntaxKind.AddAssignmentExpression, idVar, headerVar.Member("FieldIdDelta"))); loopBody.Add(readFieldHeader); loopBody.Add(endObjectCheck); loopBody.Add(idUpdate); members.Sort((x, y) => x.Member.FieldId.CompareTo(y.Member.FieldId)); var contiguousIds = members[members.Count - 1].Member.FieldId == members.Count - 1; foreach (var member in members) { var description = member.Member; // C#: instance. = .ReadValue(ref reader, header); // Codecs can either be static classes or injected into the constructor. // Either way, the member signatures are the same. ExpressionSyntax codecExpression; if (LibraryTypes.StaticCodecs.FindByUnderlyingType(description.Type) is { } staticCodec) { codecExpression = staticCodec.CodecType.ToNameSyntax(); } else { var instanceCodec = serializerFields.Find(c => c is CodecFieldDescription f && SymbolEqualityComparer.Default.Equals(f.UnderlyingType, description.Type)); codecExpression = IdentifierName(instanceCodec.FieldName); } ExpressionSyntax readValueExpression = InvocationExpression( codecExpression.Member("ReadValue"), ArgumentList(SeparatedList(new[] { Argument(readerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(headerVar) }))); var memberAssignment = ExpressionStatement(member.GetSetter(instanceParam, readValueExpression)); BlockSyntax ifBody; if (member != members[members.Count - 1]) { ifBody = Block(memberAssignment, readFieldHeader, endObjectCheck, idUpdate); } else if (contiguousIds) { ifBody = Block(memberAssignment, readFieldHeader); } else { idUpdate = ExpressionStatement(PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, idVar)); ifBody = Block(memberAssignment, readFieldHeader, endObjectCheck, idUpdate); } // C#: if (id == ) { ... } var ifStatement = IfStatement(BinaryExpression(SyntaxKind.EqualsExpression, idVar, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(description.FieldId))), ifBody); loopBody.Add(ifStatement); } // Consume any unknown fields if (contiguousIds) { // C#: reader.ConsumeEndBaseOrEndObject(ref header); break; loopBody.Add(ExpressionStatement(InvocationExpression(readerParam.Member("ConsumeEndBaseOrEndObject"), refHeaderVar))); loopBody.Add(BreakStatement()); } else { // C#: reader.ConsumeUnknownField(ref header); loopBody.Add(ExpressionStatement(InvocationExpression(readerParam.Member("ConsumeUnknownField"), refHeaderVar))); } return WhileStatement(LiteralExpression(SyntaxKind.TrueLiteralExpression), Block(loopBody)); } } private void AddSerializationCallbacks(ISerializableTypeDescription type, IdentifierNameSyntax instanceParam, string callbackMethodName, List body) { for (var hookIndex = 0; hookIndex < type.SerializationHooks.Count; ++hookIndex) { var hookType = type.SerializationHooks[hookIndex]; var member = hookType.GetAllMembers(callbackMethodName, Accessibility.Public).FirstOrDefault(); if (member is null || member.Parameters.Length != 1) { continue; } var argument = Argument(instanceParam); if (member.Parameters[0].RefKind == RefKind.Ref) { argument = argument.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } body.Add(ExpressionStatement(InvocationExpression( IdentifierName($"_hook{hookIndex}").Member(callbackMethodName), ArgumentList(SeparatedList(new[] { argument }))))); } } private MemberDeclarationSyntax GenerateCompoundTypeWriteFieldMethod( ISerializableTypeDescription type) { var returnType = PredefinedType(Token(SyntaxKind.VoidKeyword)); var writerParam = "writer".ToIdentifierName(); var fieldIdDeltaParam = "fieldIdDelta".ToIdentifierName(); var expectedTypeParam = "expectedType".ToIdentifierName(); var valueParam = "value".ToIdentifierName(); var innerBody = new List(); if (type.IsValueType) { // C#: ReferenceCodec.MarkValueField(reader.Session); innerBody.Add(ExpressionStatement(InvocationExpression(IdentifierName("ReferenceCodec").Member("MarkValueField"), ArgumentList(SingletonSeparatedList(Argument(writerParam.Member("Session"))))))); } else { if (type.TrackReferences) { // C#: if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) return; innerBody.Add( IfStatement( InvocationExpression( IdentifierName("ReferenceCodec").Member("TryWriteReferenceField"), ArgumentList(SeparatedList(new[] { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldIdDeltaParam), Argument(expectedTypeParam), Argument(valueParam) }))), ReturnStatement()) ); } else { // C#: if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } innerBody.Add( IfStatement( IsPatternExpression(valueParam, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), Block( ExpressionStatement(InvocationExpression(IdentifierName("ReferenceCodec").Member("WriteNullReference"), ArgumentList(SeparatedList(new[] { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldIdDeltaParam) })))), ReturnStatement())) ); // C#: ReferenceCodec.MarkValueField(reader.Session); innerBody.Add(ExpressionStatement(InvocationExpression(IdentifierName("ReferenceCodec").Member("MarkValueField"), ArgumentList(SingletonSeparatedList(Argument(writerParam.Member("Session"))))))); } } // C#: writer.WriteStartObject(fieldIdDelta, expectedType, _codecFieldType); innerBody.Add( ExpressionStatement(InvocationExpression(writerParam.Member("WriteStartObject"), ArgumentList(SeparatedList(new[]{ Argument(fieldIdDeltaParam), Argument(expectedTypeParam), Argument(IdentifierName(CodecFieldTypeFieldName)) }))) )); // C#: this.Serialize(ref writer, [ref] value); var valueParamArgument = type.IsValueType switch { true => Argument(valueParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), false => Argument(valueParam) }; innerBody.Add( ExpressionStatement( InvocationExpression( IdentifierName(SerializeMethodName), ArgumentList( SeparatedList( new[] { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), valueParamArgument }))))); // C#: writer.WriteEndObject(); innerBody.Add(ExpressionStatement(InvocationExpression(writerParam.Member("WriteEndObject")))); List body; if (type.IsSealedType) { body = innerBody; } else { // For types which are not sealed/value types, add some extra logic to support sub-types: body = new() { // C#: if (value is null || value.GetType() == typeof(TField)) { } // C#: else writer.SerializeUnexpectedType(fieldIdDelta, expectedType, value); IfStatement( BinaryExpression(SyntaxKind.LogicalOrExpression, IsPatternExpression(valueParam, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), BinaryExpression(SyntaxKind.EqualsExpression, InvocationExpression(valueParam.Member("GetType")), TypeOfExpression(type.TypeSyntax))), Block(innerBody), ElseClause(ExpressionStatement( InvocationExpression( writerParam.Member("SerializeUnexpectedType"), ArgumentList( SeparatedList(new [] { Argument(fieldIdDeltaParam), Argument(expectedTypeParam), Argument(valueParam) }))) ))) }; } var parameters = new[] { Parameter("writer".ToIdentifier()).WithType(LibraryTypes.Writer.ToTypeSyntax()).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter("fieldIdDelta".ToIdentifier()).WithType(PredefinedType(Token(SyntaxKind.UIntKeyword))), Parameter("expectedType".ToIdentifier()).WithType(LibraryTypes.Type.ToTypeSyntax()), Parameter("value".ToIdentifier()).WithType(type.TypeSyntax) }; return MethodDeclaration(returnType, WriteFieldMethodName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddTypeParameterListParameters(TypeParameter("TBufferWriter")) .AddConstraintClauses(TypeParameterConstraintClause("TBufferWriter").AddConstraints(TypeConstraint(LibraryTypes.IBufferWriter.ToTypeSyntax(PredefinedType(Token(SyntaxKind.ByteKeyword)))))) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); } private MemberDeclarationSyntax GenerateCompoundTypeReadValueMethod( ISerializableTypeDescription type, List serializerFields) { var readerParam = "reader".ToIdentifierName(); var fieldParam = "field".ToIdentifierName(); var resultVar = "result".ToIdentifierName(); var readerInputTypeParam = ParseTypeName("TReaderInput"); var body = new List(); var innerBody = type.IsSealedType ? body : new List(); if (!type.IsValueType) { // C#: if (field.IsReference) return ReferenceCodec.ReadReference(ref reader, field); body.Add( IfStatement( fieldParam.Member("IsReference"), ReturnStatement(InvocationExpression( IdentifierName("ReferenceCodec").Member("ReadReference", new[] { type.TypeSyntax, readerInputTypeParam }), ArgumentList(SeparatedList(new[] { Argument(readerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldParam), }))))) ); } // C#: field.EnsureWireTypeTagDelimited(); body.Add(ExpressionStatement(InvocationExpression(fieldParam.Member("EnsureWireTypeTagDelimited")))); ExpressionSyntax createValueExpression = type.UseActivator switch { true => InvocationExpression(serializerFields.OfType().Single().FieldName.ToIdentifierName().Member("Create")), false => type.GetObjectCreationExpression() }; // C#: var result = _activator.Create(); // or C#: var result = new TField(); // or C#: var result = default(TField); innerBody.Add(LocalDeclarationStatement( VariableDeclaration( IdentifierName("var"), SingletonSeparatedList(VariableDeclarator(resultVar.Identifier) .WithInitializer(EqualsValueClause(createValueExpression)))))); if (type.TrackReferences) { // C#: ReferenceCodec.RecordObject(reader.Session, result); innerBody.Add(ExpressionStatement(InvocationExpression(IdentifierName("ReferenceCodec").Member("RecordObject"), ArgumentList(SeparatedList(new[] { Argument(readerParam.Member("Session")), Argument(resultVar) }))))); } else { // C#: ReferenceCodec.MarkValueField(reader.Session); innerBody.Add(ExpressionStatement(InvocationExpression(IdentifierName("ReferenceCodec").Member("MarkValueField"), ArgumentList(SingletonSeparatedList(Argument(readerParam.Member("Session"))))))); } // C#: this.Deserializer(ref reader, [ref] result); var resultArgument = type.IsValueType switch { true => Argument(resultVar).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), false => Argument(resultVar) }; innerBody.Add( ExpressionStatement( InvocationExpression( IdentifierName(DeserializeMethodName), ArgumentList( SeparatedList( new[] { Argument(readerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), resultArgument }))))); innerBody.Add(ReturnStatement(resultVar)); if (!type.IsSealedType) { // C#: var fieldType = field.FieldType; var valueTypeField = "valueType".ToIdentifierName(); body.Add( LocalDeclarationStatement( VariableDeclaration( LibraryTypes.Type.ToTypeSyntax(), SingletonSeparatedList(VariableDeclarator(valueTypeField.Identifier) .WithInitializer(EqualsValueClause(fieldParam.Member("FieldType"))))))); body.Add( IfStatement( BinaryExpression(SyntaxKind.LogicalOrExpression, IsPatternExpression(valueTypeField, ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))), BinaryExpression(SyntaxKind.EqualsExpression, valueTypeField, IdentifierName(CodecFieldTypeFieldName))), Block(innerBody))); body.Add(ReturnStatement( InvocationExpression( readerParam.Member("DeserializeUnexpectedType", new[] { readerInputTypeParam, type.TypeSyntax }), ArgumentList( SingletonSeparatedList(Argument(null, Token(SyntaxKind.RefKeyword), fieldParam)))))); } var parameters = new[] { Parameter(readerParam.Identifier).WithType(LibraryTypes.Reader.ToTypeSyntax(readerInputTypeParam)).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter(fieldParam.Identifier).WithType(LibraryTypes.Field.ToTypeSyntax()) }; return MethodDeclaration(type.TypeSyntax, ReadValueMethodName) .AddTypeParameterListParameters(TypeParameter("TReaderInput")) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); } private MemberDeclarationSyntax GenerateEnumWriteMethod( ISerializableTypeDescription type) { var returnType = PredefinedType(Token(SyntaxKind.VoidKeyword)); var writerParam = "writer".ToIdentifierName(); var fieldIdDeltaParam = "fieldIdDelta".ToIdentifierName(); var expectedTypeParam = "expectedType".ToIdentifierName(); var valueParam = "value".ToIdentifierName(); var body = new List(); // Codecs can either be static classes or injected into the constructor. // Either way, the member signatures are the same. var staticCodec = LibraryTypes.StaticCodecs.FindByUnderlyingType(type.BaseType); var codecExpression = staticCodec.CodecType.ToNameSyntax(); body.Add( ExpressionStatement( InvocationExpression( codecExpression.Member("WriteField"), ArgumentList( SeparatedList( new[] { Argument(writerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldIdDeltaParam), Argument(expectedTypeParam), Argument(CastExpression(type.BaseTypeSyntax, valueParam)), Argument(IdentifierName(CodecFieldTypeFieldName)) }))))); var parameters = new[] { Parameter("writer".ToIdentifier()).WithType(LibraryTypes.Writer.ToTypeSyntax()).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter("fieldIdDelta".ToIdentifier()).WithType(PredefinedType(Token(SyntaxKind.UIntKeyword))), Parameter("expectedType".ToIdentifier()).WithType(LibraryTypes.Type.ToTypeSyntax()), Parameter("value".ToIdentifier()).WithType(type.TypeSyntax) }; return MethodDeclaration(returnType, WriteFieldMethodName) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddTypeParameterListParameters(TypeParameter("TBufferWriter")) .AddConstraintClauses(TypeParameterConstraintClause("TBufferWriter").AddConstraints(TypeConstraint(LibraryTypes.IBufferWriter.ToTypeSyntax(PredefinedType(Token(SyntaxKind.ByteKeyword)))))) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); } private MemberDeclarationSyntax GenerateEnumReadMethod( ISerializableTypeDescription type) { var readerParam = "reader".ToIdentifierName(); var fieldParam = "field".ToIdentifierName(); var staticCodec = LibraryTypes.StaticCodecs.FindByUnderlyingType(type.BaseType); ExpressionSyntax codecExpression = staticCodec.CodecType.ToNameSyntax(); ExpressionSyntax readValueExpression = InvocationExpression( codecExpression.Member("ReadValue"), ArgumentList(SeparatedList(new[] { Argument(readerParam).WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)), Argument(fieldParam) }))); readValueExpression = CastExpression(type.TypeSyntax, readValueExpression); var body = new List { ReturnStatement(readValueExpression) }; var genericParam = ParseTypeName("TReaderInput"); var parameters = new[] { Parameter(readerParam.Identifier).WithType(LibraryTypes.Reader.ToTypeSyntax(genericParam)).WithModifiers(TokenList(Token(SyntaxKind.RefKeyword))), Parameter(fieldParam.Identifier).WithType(LibraryTypes.Field.ToTypeSyntax()) }; return MethodDeclaration(type.TypeSyntax, ReadValueMethodName) .AddTypeParameterListParameters(TypeParameter("TReaderInput")) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddParameterListParameters(parameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(CodeGenerator.GetMethodImplAttributeSyntax()))) .AddBodyStatements(body.ToArray()); } internal abstract class GeneratedFieldDescription { protected GeneratedFieldDescription(TypeSyntax fieldType, string fieldName) { FieldType = fieldType; FieldName = fieldName; } public readonly TypeSyntax FieldType; public readonly string FieldName; public abstract bool IsInjected { get; } } internal sealed class BaseCodecFieldDescription : GeneratedFieldDescription { public BaseCodecFieldDescription(TypeSyntax fieldType, bool concreteType = false) : base(fieldType, BaseTypeSerializerFieldName) => IsInjected = !concreteType; public override bool IsInjected { get; } } internal sealed class ActivatorFieldDescription : GeneratedFieldDescription { public ActivatorFieldDescription(TypeSyntax fieldType, string fieldName) : base(fieldType, fieldName) { } public override bool IsInjected => true; } internal sealed class CodecFieldDescription : GeneratedFieldDescription { public CodecFieldDescription(TypeSyntax fieldType, string fieldName, ITypeSymbol underlyingType) : base(fieldType, fieldName) { UnderlyingType = underlyingType; } public ITypeSymbol UnderlyingType { get; } public override bool IsInjected => false; } internal sealed class TypeFieldDescription : GeneratedFieldDescription { public TypeFieldDescription(TypeSyntax fieldType, string fieldName, TypeSyntax underlyingTypeSyntax, ITypeSymbol underlyingType) : base(fieldType, fieldName) { UnderlyingType = underlyingType; UnderlyingTypeSyntax = underlyingTypeSyntax; } public TypeSyntax UnderlyingTypeSyntax { get; } public ITypeSymbol UnderlyingType { get; } public override bool IsInjected => false; } internal sealed class CodecFieldTypeFieldDescription : GeneratedFieldDescription { public CodecFieldTypeFieldDescription(TypeSyntax fieldType, string fieldName, TypeSyntax codecFieldType) : base(fieldType, fieldName) { CodecFieldType = codecFieldType; } public TypeSyntax CodecFieldType { get; } public override bool IsInjected => false; } internal sealed class FieldAccessorDescription : GeneratedFieldDescription { public FieldAccessorDescription(TypeSyntax containingType, TypeSyntax fieldType, string fieldName, string accessorName, ExpressionSyntax initializationSyntax = null) : base(fieldType, fieldName) { ContainingType = containingType; AccessorName = accessorName; InitializationSyntax = initializationSyntax; } public override bool IsInjected => false; public readonly string AccessorName; public readonly TypeSyntax ContainingType; public readonly ExpressionSyntax InitializationSyntax; } internal sealed class SerializationHookFieldDescription : GeneratedFieldDescription { public SerializationHookFieldDescription(TypeSyntax fieldType, string fieldName) : base(fieldType, fieldName) { } public override bool IsInjected => true; } internal interface ISerializableMember { bool IsShallowCopyable { get; } bool IsValueType { get; } bool IsPrimaryConstructorParameter { get; } IMemberDescription Member { get; } /// /// Gets syntax representing the type of this field. /// TypeSyntax TypeSyntax { get; } /// /// Returns syntax for retrieving the value of this field, deep copying it if necessary. /// /// The instance of the containing type. /// Syntax for retrieving the value of this field. ExpressionSyntax GetGetter(ExpressionSyntax instance); /// /// Returns syntax for setting the value of this field. /// /// The instance of the containing type. /// Syntax for the new value. /// Syntax for setting the value of this field. ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value); FieldAccessorDescription GetGetterFieldDescription(); FieldAccessorDescription GetSetterFieldDescription(); } /// /// Represents a serializable member (field/property) of a type. /// internal class SerializableMethodMember : ISerializableMember { private readonly MethodParameterFieldDescription _member; public SerializableMethodMember(MethodParameterFieldDescription member) { _member = member; } IMemberDescription ISerializableMember.Member => _member; public MethodParameterFieldDescription Member => _member; private LibraryTypes LibraryTypes => _member.CodeGenerator.LibraryTypes; public bool IsShallowCopyable => LibraryTypes.IsShallowCopyable(_member.Parameter.Type) || _member.Parameter.HasAttribute(LibraryTypes.ImmutableAttribute); /// /// Gets syntax representing the type of this field. /// public TypeSyntax TypeSyntax => _member.TypeSyntax; public bool IsValueType => _member.Type.IsValueType; public bool IsPrimaryConstructorParameter => _member.IsPrimaryConstructorParameter; /// /// Returns syntax for retrieving the value of this field, deep copying it if necessary. /// /// The instance of the containing type. /// Syntax for retrieving the value of this field. public ExpressionSyntax GetGetter(ExpressionSyntax instance) => instance.Member(_member.FieldName); /// /// Returns syntax for setting the value of this field. /// /// The instance of the containing type. /// Syntax for the new value. /// Syntax for setting the value of this field. public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value) => AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, instance.Member(_member.FieldName), value); public FieldAccessorDescription GetGetterFieldDescription() => null; public FieldAccessorDescription GetSetterFieldDescription() => null; } /// /// Represents a serializable member (field/property) of a type. /// internal class SerializableMember : ISerializableMember { private readonly IMemberDescription _member; private readonly CodeGenerator _codeGenerator; private IPropertySymbol _property; /// /// The ordinal assigned to this field. /// private readonly int _ordinal; public SerializableMember(CodeGenerator codeGenerator, IMemberDescription member, int ordinal) { _codeGenerator = codeGenerator; _ordinal = ordinal; _member = member; } private Compilation Compilation => _codeGenerator.Compilation; private LibraryTypes LibraryTypes => _codeGenerator.LibraryTypes; public bool IsShallowCopyable => LibraryTypes.IsShallowCopyable(_member.Type) || Property is { } prop && prop.HasAttribute(LibraryTypes.ImmutableAttribute) || _member.Symbol.HasAttribute(LibraryTypes.ImmutableAttribute); public bool IsValueType => Type.IsValueType; public IMemberDescription Member => _member; /// /// Gets the underlying instance. /// private IFieldSymbol Field => (_member as IFieldDescription)?.Field; public ITypeSymbol Type => _member.Type; public INamedTypeSymbol ContainingType => _member.ContainingType; public string MemberName => Field?.Name ?? Property?.Name; /// /// Gets the name of the getter field. /// private string GetterFieldName => $"getField{_ordinal}"; /// /// Gets the name of the setter field. /// private string SetterFieldName => $"setField{_ordinal}"; /// /// Gets a value indicating if the member is a property. /// private bool IsProperty => Member.Symbol is IPropertySymbol; /// /// Gets a value indicating whether or not this member represents an accessible field. /// private bool IsGettableField => Field is { } fieldInfo && _codeGenerator.Compilation.IsSymbolAccessibleWithin(fieldInfo, Compilation.Assembly) && !IsObsolete; /// /// Gets a value indicating whether or not this member represents an accessible, mutable field. /// private bool IsSettableField => Field is { } fieldInfo && IsGettableField && !fieldInfo.IsReadOnly; /// /// Gets a value indicating whether or not this member represents a property with an accessible, non-obsolete getter. /// private bool IsGettableProperty => Property?.GetMethod is { } getMethod && Compilation.IsSymbolAccessibleWithin(getMethod, Compilation.Assembly) && !IsObsolete; /// /// Gets a value indicating whether or not this member represents a property with an accessible, non-obsolete setter. /// private bool IsSettableProperty => Property?.SetMethod is { } setMethod && Compilation.IsSymbolAccessibleWithin(setMethod, Compilation.Assembly) && !setMethod.IsInitOnly && !IsObsolete; /// /// Gets syntax representing the type of this field. /// public TypeSyntax TypeSyntax => Member.Type.TypeKind == TypeKind.Dynamic ? PredefinedType(Token(SyntaxKind.ObjectKeyword)) : _member.GetTypeSyntax(Member.Type); /// /// Gets the which this field is the backing property for, or /// if this is not the backing field of an auto-property. /// private IPropertySymbol Property => _property ??= _property = Member.Symbol as IPropertySymbol ?? PropertyUtility.GetMatchingProperty(Field); /// /// Gets a value indicating whether or not this field is obsolete. /// private bool IsObsolete => Member.Symbol.HasAttribute(LibraryTypes.ObsoleteAttribute) || Property != null && Property.HasAttribute(LibraryTypes.ObsoleteAttribute); public bool IsPrimaryConstructorParameter => _member.IsPrimaryConstructorParameter; /// /// Returns syntax for retrieving the value of this field, deep copying it if necessary. /// /// The instance of the containing type. /// Syntax for retrieving the value of this field. public ExpressionSyntax GetGetter(ExpressionSyntax instance) { // If the field is the backing field for an accessible auto-property use the property directly. ExpressionSyntax result; if (IsGettableProperty) { result = instance.Member(Property.Name); } else if (IsGettableField) { result = instance.Member(Field.Name); } else { var instanceArg = Argument(instance); if (ContainingType?.IsValueType == true) { instanceArg = instanceArg.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } // Retrieve the field using the generated getter. result = InvocationExpression(IdentifierName(GetterFieldName)) .AddArgumentListArguments(instanceArg); } return result; } /// /// Returns syntax for setting the value of this field. /// /// The instance of the containing type. /// Syntax for the new value. /// Syntax for setting the value of this field. public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value) { // If the field is the backing field for an accessible auto-property use the property directly. if (IsSettableProperty) { return AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, instance.Member(Property.Name), value); } if (IsSettableField) { return AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, instance.Member(Field.Name), value); } // If the symbol itself is a property but is not settable, then error out, since we do not know how to set it value if (IsProperty && !IsPrimaryConstructorParameter) { Location location = default; if (Member.Symbol is IPropertySymbol prop && prop.SetMethod is { } setMethod) { location = setMethod.Locations.FirstOrDefault(); } location ??= Member.Symbol.Locations.FirstOrDefault(); throw new OrleansGeneratorDiagnosticAnalysisException(InaccessibleSetterDiagnostic.CreateDiagnostic(location, Member.Symbol?.ToDisplayString() ?? $"{ContainingType.ToDisplayString()}.{MemberName}")); } var instanceArg = Argument(instance); if (ContainingType?.IsValueType == true) { instanceArg = instanceArg.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } return InvocationExpression(IdentifierName(SetterFieldName)) .AddArgumentListArguments(instanceArg, Argument(value)); } public FieldAccessorDescription GetGetterFieldDescription() { if (IsGettableField || IsGettableProperty) return null; return GetFieldAccessor(ContainingType, TypeSyntax, MemberName, GetterFieldName, LibraryTypes, false, IsPrimaryConstructorParameter && IsProperty); } public FieldAccessorDescription GetSetterFieldDescription() { if (IsSettableField || IsSettableProperty) return null; return GetFieldAccessor(ContainingType, TypeSyntax, MemberName, SetterFieldName, LibraryTypes, true, IsPrimaryConstructorParameter && IsProperty); } public static FieldAccessorDescription GetFieldAccessor(INamedTypeSymbol containingType, TypeSyntax fieldType, string fieldName, string accessorName, LibraryTypes library, bool setter, bool useUnsafeAccessor = false) { var containingTypeSyntax = containingType.ToTypeSyntax(); if (useUnsafeAccessor) return new(containingTypeSyntax, fieldType, fieldName, accessorName); var valueType = containingType.IsValueType; var delegateType = (setter ? (valueType ? library.ValueTypeSetter_2 : library.Action_2) : (valueType ? library.ValueTypeGetter_2 : library.Func_2)) .ToTypeSyntax(containingTypeSyntax, fieldType); // Generate syntax to initialize the field in the constructor var fieldAccessorUtility = AliasQualifiedName("global", IdentifierName("Orleans.Serialization")).Member("Utilities").Member("FieldAccessor"); var accessorMethod = setter ? (valueType ? "GetValueSetter" : "GetReferenceSetter") : (valueType ? "GetValueGetter" : "GetGetter"); var accessorInvoke = CastExpression(delegateType, InvocationExpression(fieldAccessorUtility.Member(accessorMethod)) .AddArgumentListArguments(Argument(TypeOfExpression(containingTypeSyntax)), Argument(fieldName.GetLiteralExpression()))); // Existing case, accessor is the field in both cases return new(containingTypeSyntax, delegateType, accessorName, accessorName, accessorInvoke); } } } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/FSharpUtils.cs ================================================ using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Orleans.CodeGenerator.SyntaxGeneration; using static Orleans.CodeGenerator.SerializerGenerator; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator { internal static class FSharpUtilities { private const int SourceConstructFlagsSumTypeValue = 1; private const int SourceConstructFlagsKindMaskValue = 31; private const int SourceConstructFlagsRecordTypeValue = 2; public static bool IsUnionCase(LibraryTypes libraryTypes, INamedTypeSymbol symbol, out INamedTypeSymbol sumType) { sumType = default; var compilationAttributeType = libraryTypes.FSharpCompilationMappingAttributeOrDefault; var sourceConstructFlagsType = libraryTypes.FSharpSourceConstructFlagsOrDefault; var baseType = symbol.BaseType; if (compilationAttributeType is null || sourceConstructFlagsType is null || baseType is null) { return false; } INamedTypeSymbol sumTypeCandidate; if (symbol.GetAttributes(compilationAttributeType, out var compilationAttributes) && compilationAttributes.Length > 0) { sumTypeCandidate = symbol; } else if (baseType.GetAttributes(compilationAttributeType, out compilationAttributes) && compilationAttributes.Length > 0) { sumTypeCandidate = baseType; } else { return false; } var compilationAttribute = compilationAttributes[0]; var foundArg = false; TypedConstant sourceConstructFlagsArgument = default; foreach (var arg in compilationAttribute.ConstructorArguments) { if (SymbolEqualityComparer.Default.Equals(arg.Type, sourceConstructFlagsType)) { sourceConstructFlagsArgument = arg; foundArg = true; break; } } if (!foundArg) { return false; } if (sourceConstructFlagsArgument.Value != null && ((int)sourceConstructFlagsArgument.Value & SourceConstructFlagsKindMaskValue) != SourceConstructFlagsSumTypeValue) { return false; } sumType = sumTypeCandidate; return true; } public static bool IsRecord(LibraryTypes libraryTypes, INamedTypeSymbol symbol) { var compilationAttributeType = libraryTypes.FSharpCompilationMappingAttributeOrDefault; var sourceConstructFlagsType = libraryTypes.FSharpSourceConstructFlagsOrDefault; if (compilationAttributeType is null || sourceConstructFlagsType is null) { return false; } if (!symbol.GetAttributes(compilationAttributeType, out var compilationAttributes) || compilationAttributes.Length == 0) { return false; } var compilationAttribute = compilationAttributes[0]; var foundArg = false; TypedConstant sourceConstructFlagsArgument = default; foreach (var arg in compilationAttribute.ConstructorArguments) { if (SymbolEqualityComparer.Default.Equals(arg.Type, sourceConstructFlagsType)) { sourceConstructFlagsArgument = arg; foundArg = true; break; } } if (!foundArg) { return false; } if ((int)sourceConstructFlagsArgument.Value != SourceConstructFlagsRecordTypeValue) { return false; } return true; } public class FSharpUnionCaseTypeDescription : SerializableTypeDescription { public FSharpUnionCaseTypeDescription(Compilation compilation, INamedTypeSymbol type, LibraryTypes libraryTypes) : base(compilation, type, false, GetUnionCaseDataMembers(libraryTypes, type), libraryTypes) { } private static IEnumerable GetUnionCaseDataMembers(LibraryTypes libraryTypes, INamedTypeSymbol symbol) { List dataMembers = new(); foreach (var field in symbol.GetDeclaredInstanceMembers()) { dataMembers.Add(field); } dataMembers.Sort(FSharpUnionCasePropertyNameComparer.Default); uint id = 0; foreach (var field in dataMembers) { yield return new FSharpUnionCaseFieldDescription(libraryTypes, field, id); id++; } } private class FSharpUnionCasePropertyNameComparer : IComparer { public static FSharpUnionCasePropertyNameComparer Default { get; } = new FSharpUnionCasePropertyNameComparer(); public int Compare(IFieldSymbol x, IFieldSymbol y) { var xName = x.Name; var yName = y.Name; if (xName.Length > yName.Length) { return 1; } if (xName.Length < yName.Length) { return -1; } return string.CompareOrdinal(xName, yName); } } private class FSharpUnionCaseFieldDescription : IMemberDescription, ISerializableMember { private readonly LibraryTypes _libraryTypes; private readonly IFieldSymbol _field; public FSharpUnionCaseFieldDescription(LibraryTypes libraryTypes, IFieldSymbol field, uint ordinal) { _libraryTypes = libraryTypes; FieldId = ordinal; _field = field; } public uint FieldId { get; } public bool IsShallowCopyable => _libraryTypes.IsShallowCopyable(Type) || _field.HasAttribute(_libraryTypes.ImmutableAttribute); public bool IsValueType => Type.IsValueType; public IMemberDescription Member => this; public ITypeSymbol Type => _field.Type; public INamedTypeSymbol ContainingType => _field.ContainingType; public ISymbol Symbol => _field; /// /// Gets the name of the setter field. /// private string SetterFieldName => "setField" + FieldId; /// /// Gets syntax representing the type of this field. /// public TypeSyntax TypeSyntax => Type.TypeKind == TypeKind.Dynamic ? PredefinedType(Token(SyntaxKind.ObjectKeyword)) : GetTypeSyntax(Type); public string AssemblyName => Type.ContainingAssembly.ToDisplayName(); public string TypeName => Type.ToDisplayName(); public string TypeNameIdentifier => Type.GetValidIdentifier(); public bool IsPrimaryConstructorParameter => false; public bool IsSerializable => true; public bool IsCopyable => true; public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol) => typeSymbol.ToTypeSyntax(); /// /// Returns syntax for retrieving the value of this field, deep copying it if necessary. /// /// The instance of the containing type. /// Syntax for retrieving the value of this field. public ExpressionSyntax GetGetter(ExpressionSyntax instance) => instance.Member(_field.Name); /// /// Returns syntax for setting the value of this field. /// /// The instance of the containing type. /// Syntax for the new value. /// Syntax for setting the value of this field. public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value) { var instanceArg = Argument(instance); if (ContainingType != null && ContainingType.IsValueType) { instanceArg = instanceArg.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } return InvocationExpression(IdentifierName(SetterFieldName)) .AddArgumentListArguments(instanceArg, Argument(value)); } public FieldAccessorDescription GetGetterFieldDescription() => null; public FieldAccessorDescription GetSetterFieldDescription() => SerializableMember.GetFieldAccessor(ContainingType, TypeSyntax, _field.Name, SetterFieldName, _libraryTypes, true); } } public class FSharpRecordTypeDescription : SerializableTypeDescription { public FSharpRecordTypeDescription(Compilation compilation, INamedTypeSymbol type, LibraryTypes libraryTypes) : base(compilation, type, false, GetRecordDataMembers(libraryTypes, type), libraryTypes) { } private static IEnumerable GetRecordDataMembers(LibraryTypes libraryTypes, INamedTypeSymbol symbol) { List<(IPropertySymbol, uint)> dataMembers = new(); foreach (var property in symbol.GetDeclaredInstanceMembers()) { var id = CodeGenerator.GetId(libraryTypes, property); if (!id.HasValue) { continue; } dataMembers.Add((property, id.Value)); } foreach (var (property, id) in dataMembers) { yield return new FSharpRecordPropertyDescription(libraryTypes, property, id); } } private class FSharpRecordPropertyDescription : IMemberDescription, ISerializableMember { private readonly LibraryTypes _libraryTypes; private readonly IPropertySymbol _property; public FSharpRecordPropertyDescription(LibraryTypes libraryTypes, IPropertySymbol property, uint ordinal) { _libraryTypes = libraryTypes; FieldId = ordinal; _property = property; } public uint FieldId { get; } public bool IsShallowCopyable => _libraryTypes.IsShallowCopyable(Type) || _property.HasAttribute(_libraryTypes.ImmutableAttribute); public bool IsValueType => Type.IsValueType; public IMemberDescription Member => this; public ITypeSymbol Type => _property.Type; public ISymbol Symbol => _property; public INamedTypeSymbol ContainingType => _property.ContainingType; public string FieldName => _property.Name + "@"; /// /// Gets the name of the setter field. /// private string SetterFieldName => "setField" + FieldId; /// /// Gets syntax representing the type of this field. /// public TypeSyntax TypeSyntax => Type.TypeKind == TypeKind.Dynamic ? PredefinedType(Token(SyntaxKind.ObjectKeyword)) : GetTypeSyntax(Type); /// /// Gets the which this field is the backing property for, or /// if this is not the backing field of an auto-property. /// private IPropertySymbol Property => _property; public string AssemblyName => Type.ContainingAssembly.ToDisplayName(); public string TypeName => Type.ToDisplayName(); public string TypeNameIdentifier => Type.GetValidIdentifier(); public bool IsPrimaryConstructorParameter => false; public bool IsSerializable => true; public bool IsCopyable => true; public TypeSyntax GetTypeSyntax(ITypeSymbol typeSymbol) => typeSymbol.ToTypeSyntax(); /// /// Returns syntax for retrieving the value of this field, deep copying it if necessary. /// /// The instance of the containing type. /// Syntax for retrieving the value of this field. public ExpressionSyntax GetGetter(ExpressionSyntax instance) => instance.Member(Property.Name); /// /// Returns syntax for setting the value of this field. /// /// The instance of the containing type. /// Syntax for the new value. /// Syntax for setting the value of this field. public ExpressionSyntax GetSetter(ExpressionSyntax instance, ExpressionSyntax value) { var instanceArg = Argument(instance); if (ContainingType != null && ContainingType.IsValueType) { instanceArg = instanceArg.WithRefOrOutKeyword(Token(SyntaxKind.RefKeyword)); } return InvocationExpression(IdentifierName(SetterFieldName)) .AddArgumentListArguments(instanceArg, Argument(value)); } public FieldAccessorDescription GetGetterFieldDescription() => null; public FieldAccessorDescription GetSetterFieldDescription() => SerializableMember.GetFieldAccessor(ContainingType, TypeSyntax, FieldName, SetterFieldName, _libraryTypes, true); } } } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/Identifier.cs ================================================ using System.Text.RegularExpressions; namespace Orleans.CodeGenerator.SyntaxGeneration { internal static class Identifier { internal static bool IsCSharpKeyword(string identifier) { switch (identifier) { case "abstract": case "add": case "alias": case "as": case "ascending": case "async": case "await": case "base": case "bool": case "break": case "byte": case "case": case "catch": case "char": case "checked": case "class": case "const": case "continue": case "decimal": case "default": case "delegate": case "descending": case "do": case "double": case "dynamic": case "else": case "enum": case "event": case "explicit": case "extern": case "false": case "finally": case "fixed": case "float": case "for": case "foreach": case "from": case "get": case "global": case "goto": case "group": case "if": case "implicit": case "in": case "int": case "interface": case "internal": case "into": case "is": case "join": case "let": case "lock": case "long": case "nameof": case "namespace": case "new": case "null": case "object": case "operator": case "orderby": case "out": case "override": case "params": case "partial": case "private": case "protected": case "public": case "readonly": case "ref": case "remove": case "return": case "sbyte": case "sealed": case "select": case "set": case "short": case "sizeof": case "stackalloc": case "static": case "string": case "struct": case "switch": case "this": case "throw": case "true": case "try": case "typeof": case "uint": case "ulong": case "unchecked": case "unsafe": case "ushort": case "using": case "value": case "var": case "virtual": case "void": case "volatile": case "when": case "where": case "while": case "yield": return true; default: return false; } } private static readonly Regex SanitizeIdentifierRegex = new("^([0-9]+)|([^0-9a-zA-Z_]+)", RegexOptions.Compiled); public static string SanitizeIdentifierName(string input) => SanitizeIdentifierRegex.Replace( input, static match => match.Value switch { // Prefix leading digits with an '_' to make them a valid identifier. { Length: > 0 } value when char.IsDigit(value[0]) => $"_{value}", // Eliminate all other matches by replacing them with an empty string. _ => "" }); } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/StringExtensions.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Orleans.CodeGenerator.SyntaxGeneration { /// /// Extensions to the class to support code generation. /// internal static class StringExtensions { /// /// Returns the provided string as a literal expression. /// /// /// The string. /// /// /// The literal expression. /// public static LiteralExpressionSyntax GetLiteralExpression(this string str) { var syntaxToken = SyntaxFactory.Literal( SyntaxFactory.TriviaList(), @"""" + str + @"""", str, SyntaxFactory.TriviaList()); return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, syntaxToken); } public static SyntaxToken ToIdentifier(this string identifier) { identifier = identifier.TrimStart('@'); if (Identifier.IsCSharpKeyword(identifier)) { return SyntaxFactory.VerbatimIdentifier( SyntaxTriviaList.Empty, identifier, identifier, SyntaxTriviaList.Empty); } return SyntaxFactory.Identifier(SyntaxTriviaList.Empty, identifier, SyntaxTriviaList.Empty); } public static string EscapeIdentifier(this string str) { if (Identifier.IsCSharpKeyword(str)) { return "@" + str; } return str; } public static IdentifierNameSyntax ToIdentifierName(this string identifier) => SyntaxFactory.IdentifierName(identifier.ToIdentifier()); } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/SymbolExtensions.cs ================================================ #nullable enable using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator.SyntaxGeneration { internal static class SymbolExtensions { private static readonly ConcurrentDictionary TypeCache = new(SymbolEqualityComparer.Default); private static readonly ConcurrentDictionary NameCache = new(SymbolEqualityComparer.Default); public struct DisplayNameOptions { public DisplayNameOptions() { Substitutions = null; } public Dictionary? Substitutions { get; set; } public bool IncludeGlobalSpecifier { get; set; } = true; public bool IncludeNamespace { get; set; } = true; } public static bool HasAttribute(this INamedTypeSymbol symbol, INamedTypeSymbol attributeType, bool inherited) => GetAttribute(symbol, attributeType, inherited) is not null; public static AttributeData? GetAttribute(this INamedTypeSymbol symbol, INamedTypeSymbol attributeType, bool inherited) { var s = symbol; if (s.GetAttribute(attributeType) is { } attribute) { return attribute; } if (inherited) { foreach (var iface in symbol.AllInterfaces) { if (iface.GetAttribute(attributeType) is { } iattr) { return iattr; } } while ((s = s.BaseType) != null) { if (s.GetAttribute(attributeType) is { } attr) { return attr; } } } return null; } public static TypeSyntax ToTypeSyntax(this ITypeSymbol typeSymbol) { if (typeSymbol.SpecialType == SpecialType.System_Void) { return PredefinedType(Token(SyntaxKind.VoidKeyword)); } if (!TypeCache.TryGetValue(typeSymbol, out var result)) { result = TypeCache[typeSymbol] = ParseTypeName(typeSymbol.ToDisplayName()); } return result; } public static TypeSyntax ToTypeSyntax(this ITypeSymbol typeSymbol, Dictionary substitutions) { if (substitutions is null or { Count: 0 }) { return typeSymbol.ToTypeSyntax(); } if (typeSymbol.SpecialType == SpecialType.System_Void) { return PredefinedType(Token(SyntaxKind.VoidKeyword)); } var res = new StringBuilder(); var options = new DisplayNameOptions { Substitutions = substitutions, }; ToTypeSyntaxInner(typeSymbol, res, options); var result = ParseTypeName(res.ToString()); return result; } public static string ToDisplayName(this ITypeSymbol typeSymbol, Dictionary? substitutions, bool includeGlobalSpecifier = true, bool includeNamespace = true) { return ToDisplayName(typeSymbol, new DisplayNameOptions { Substitutions = substitutions, IncludeGlobalSpecifier = includeGlobalSpecifier, IncludeNamespace = includeNamespace }); } public static string ToDisplayName(this ITypeSymbol typeSymbol, DisplayNameOptions options) { if (typeSymbol.SpecialType == SpecialType.System_Void) { return "void"; } var result = new StringBuilder(); ToTypeSyntaxInner(typeSymbol, result, options); return result.ToString(); } public static string ToDisplayName(this ITypeSymbol typeSymbol) { if (typeSymbol.SpecialType == SpecialType.System_Void) { return "void"; } if (!NameCache.TryGetValue(typeSymbol, out var result)) { result = NameCache[typeSymbol] = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); } return result; } public static string ToDisplayName(this IAssemblySymbol assemblySymbol) { if (assemblySymbol is null) { return string.Empty; } if (!NameCache.TryGetValue(assemblySymbol, out var result)) { result = NameCache[assemblySymbol] = assemblySymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); } return result; } private static void ToTypeSyntaxInner(ITypeSymbol typeSymbol, StringBuilder res, DisplayNameOptions options) { switch (typeSymbol) { case IDynamicTypeSymbol: res.Append("dynamic"); break; case IArrayTypeSymbol a: ToTypeSyntaxInner(a.ElementType, res, options); res.Append('['); if (a.Rank > 1) { res.Append(new string(',', a.Rank - 1)); } res.Append(']'); break; case ITypeParameterSymbol tp: if (options.Substitutions is { } substitutions && substitutions.TryGetValue(tp, out var sub)) { res.Append(sub); } else { res.Append(tp.Name.EscapeIdentifier()); } break; case INamedTypeSymbol n: OnNamedTypeSymbol(n, res, options); break; default: throw new NotSupportedException($"Symbols of type {typeSymbol?.GetType().ToString() ?? "null"} are not supported"); } static void OnNamedTypeSymbol(INamedTypeSymbol symbol, StringBuilder res, DisplayNameOptions options) { switch (symbol.ContainingSymbol) { case INamespaceSymbol ns when options.IncludeNamespace: AddFullNamespace(ns, res, options.IncludeGlobalSpecifier); break; case INamedTypeSymbol containingType: OnNamedTypeSymbol(containingType, res, options); res.Append('.'); break; } res.Append(symbol.Name.EscapeIdentifier()); if (symbol.TypeArguments.Length > 0) { res.Append('<'); bool first = true; foreach (var typeParameter in symbol.TypeArguments) { if (!first) { res.Append(','); } ToTypeSyntaxInner(typeParameter, res, options); first = false; } res.Append('>'); } } static void AddFullNamespace(INamespaceSymbol symbol, StringBuilder res, bool includeGlobalSpecifier) { if (symbol.ContainingNamespace is { } parent) { AddFullNamespace(parent, res, includeGlobalSpecifier); } if (symbol.IsGlobalNamespace) { if (includeGlobalSpecifier) { res.Append("global::"); } } else { res.Append(symbol.Name.EscapeIdentifier()); res.Append('.'); } } } public static TypeSyntax ToTypeSyntax(this ITypeSymbol typeSymbol, params TypeSyntax[] genericParameters) { var displayString = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var nameSyntax = ParseName(displayString); switch (nameSyntax) { case AliasQualifiedNameSyntax aliased: return aliased.WithName(WithGenericParameters(aliased.Name)); case QualifiedNameSyntax qualified: return qualified.WithRight(WithGenericParameters(qualified.Right)); case GenericNameSyntax g: return WithGenericParameters(g); default: throw new InvalidOperationException( $"Attempted to add generic parameters to non-generic type {displayString} ({nameSyntax.GetType()}, adding parameters {string.Join(", ", genericParameters.Select(n => n.ToFullString()))}"); } SimpleNameSyntax WithGenericParameters(SimpleNameSyntax simpleNameSyntax) { if (simpleNameSyntax is GenericNameSyntax generic) { return generic.WithTypeArgumentList(TypeArgumentList(SeparatedList(genericParameters))); } throw new InvalidOperationException( $"Attempted to add generic parameters to non-generic type {displayString} ({nameSyntax.GetType()}, adding parameters {string.Join(", ", genericParameters.Select(n => n.ToFullString()))}"); } } public static TypeSyntax ToOpenTypeSyntax(this ITypeSymbol typeSymbol) { var displayString = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var nameSyntax = ParseName(displayString); return Visit(nameSyntax); static NameSyntax Visit(NameSyntax nameSyntax) { switch (nameSyntax) { case GenericNameSyntax generic: { var argCount = generic.TypeArgumentList.Arguments.Count; return generic.WithTypeArgumentList(TypeArgumentList(SeparatedList(Enumerable.Range(0, argCount).Select(_ => OmittedTypeArgument())))); } case AliasQualifiedNameSyntax aliased: return aliased.WithName((SimpleNameSyntax)Visit(aliased.Name)); case QualifiedNameSyntax qualified: return qualified.WithRight((SimpleNameSyntax)Visit(qualified.Right)).WithLeft(Visit(qualified.Left)); default: return nameSyntax; } } } public static NameSyntax ToNameSyntax(this ITypeSymbol typeSymbol) => ParseName(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); public static string GetValidIdentifier(this ITypeSymbol type) => type switch { INamedTypeSymbol named when !named.IsGenericType => $"{named.Name}", INamedTypeSymbol named => $"{named.Name}_{string.Join("_", named.TypeArguments.Select(GetValidIdentifier))}", IArrayTypeSymbol array => $"{GetValidIdentifier(array.ElementType)}_{array.Rank}", ITypeParameterSymbol parameter => $"{parameter.Name}", _ => throw new NotSupportedException($"Unable to format type of kind {type.GetType()} with name \"{type.Name}\""), }; public static bool HasBaseType(this ITypeSymbol typeSymbol, INamedTypeSymbol baseType) { var current = typeSymbol; for (; current != null; current = current.BaseType) { if (SymbolEqualityComparer.Default.Equals(baseType, current)) { return true; } } return false; } public static bool HasAnyAttribute(this ISymbol symbol, INamedTypeSymbol[] attributeTypes) => GetAnyAttribute(symbol, attributeTypes) != null; public static AttributeData? GetAnyAttribute(this ISymbol symbol, INamedTypeSymbol[] attributeTypes) { foreach (var attr in symbol.GetAttributes()) { foreach (var t in attributeTypes) { if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, t)) { return attr; } } } return null; } public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) => GetAttribute(symbol, attributeType) != null; public static AttributeData? GetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType) { foreach (var attr in symbol.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeType)) { return attr; } } return null; } /// /// Gets all attributes which are assignable to the specified attribute type. /// public static bool GetAttributes(this ISymbol symbol, INamedTypeSymbol attributeType, out AttributeData[]? attributes) { var result = default(List); foreach (var attr in symbol.GetAttributes()) { if (attr.AttributeClass is { } attrClass && !attrClass.HasBaseType(attributeType)) { continue; } if (result is null) { result = new List(); } result.Add(attr); } attributes = result?.ToArray(); return attributes != null && attributes.Length > 0; } /// /// Gets all attributes which are assignable to the specified attribute type. /// public static bool GetAttributes(this INamedTypeSymbol symbol, INamedTypeSymbol attributeType, out AttributeData[]? attributes, bool inherited = false) { var result = default(List); AddSymbolAttributes(symbol, attributeType, ref result); if (inherited) { foreach (var iface in symbol.AllInterfaces) { AddSymbolAttributes(iface, attributeType, ref result); } var s = symbol; while ((s = s.BaseType) != null) { AddSymbolAttributes(s, attributeType, ref result); } } attributes = result?.ToArray(); return attributes != null && attributes.Length > 0; static void AddSymbolAttributes(ISymbol symbol, INamedTypeSymbol attributeType, ref List? result) { foreach (var attr in symbol.GetAttributes()) { if (attr.AttributeClass is { } attrClass && !attrClass.HasBaseType(attributeType)) { continue; } if (result is null) { result = new List(); } result.Add(attr); } } } public static IEnumerable GetAllMembers(this ITypeSymbol type, string name) where TSymbol : ISymbol { foreach (var member in type.GetAllMembers()) { if (!string.Equals(member.Name, name, StringComparison.Ordinal)) { continue; } yield return member; } } public static IEnumerable GetAllMembers(this ITypeSymbol type, string name, Accessibility accessibility) where TSymbol : ISymbol { foreach (var member in type.GetAllMembers(name)) { if (member.DeclaredAccessibility != accessibility) { continue; } yield return member; } } public static IEnumerable GetAllMembers(this ITypeSymbol type) where TSymbol : ISymbol { var bases = new Stack(); var b = type.BaseType; while (b is { }) { bases.Push(b); b = b.BaseType; } foreach (var @base in bases) { foreach (var member in @base.GetDeclaredInstanceMembers()) { yield return member; } } foreach (var iface in type.AllInterfaces) { foreach (var member in iface.GetDeclaredInstanceMembers()) { yield return member; } } foreach (var member in type.GetDeclaredInstanceMembers()) { yield return member; } } public static IEnumerable GetDeclaredInstanceMembers(this ITypeSymbol type) where TSymbol : ISymbol { foreach (var candidate in type.GetMembers()) { if (candidate.IsStatic) { continue; } if (candidate is TSymbol symbol) { yield return symbol; } } } public static string GetNamespaceAndNesting(this ISymbol symbol) { var result = new StringBuilder(); Visit(symbol, result); return result.ToString(); static void Visit(ISymbol symbol, StringBuilder res) { switch (symbol.ContainingSymbol) { case INamespaceOrTypeSymbol parent: Visit(parent, res); if (res is { Length: > 0 }) { res.Append('.'); } res.Append(parent.Name); break; } } } public static IEnumerable GetAllTypeParameters(this INamedTypeSymbol symbol) { // Note that this will not work if multiple points in the inheritance hierarchy are containing within a single generic type. // To solve that, we could retain some context throughout the recursive calls. if (symbol.ContainingType is { } containingType && containingType.IsGenericType) { foreach (var containingTypeParameter in containingType.GetAllTypeParameters()) { yield return containingTypeParameter; } } foreach (var tp in symbol.TypeParameters) { yield return tp; } } public static IEnumerable GetAllTypeArguments(this INamedTypeSymbol symbol) { if (symbol.ContainingType is { } containingType && containingType.IsGenericType) { foreach (var containingTypeParameter in containingType.GetAllTypeArguments()) { yield return containingTypeParameter; } } foreach (var tp in symbol.TypeArguments) { yield return tp; } } public static bool IsAssignableFrom(this INamedTypeSymbol symbol, INamedTypeSymbol type) { if (symbol.TypeKind == TypeKind.Interface) { return IsInterfaceAssignableFromInternal(symbol, type); } return IsBaseAssignableFromInternal(symbol, type); } private static bool IsBaseAssignableFromInternal(this INamedTypeSymbol symbol, INamedTypeSymbol? type) { if (type is null) return false; if (SymbolEqualityComparer.Default.Equals(symbol, type)) { return true; } return IsBaseAssignableFromInternal(symbol, type.BaseType); } private static bool IsInterfaceAssignableFromInternal(this INamedTypeSymbol iface, INamedTypeSymbol type) { if (SymbolEqualityComparer.Default.Equals(iface, type)) { return true; } foreach (var typeInterface in type.AllInterfaces) { if (SymbolEqualityComparer.Default.Equals(iface, typeInterface)) { return true; } } return false; } public static IEnumerable GetDeclaredTypes(this IAssemblySymbol reference) { foreach (var module in reference.Modules) { foreach (var type in GetDeclaredTypes(module.GlobalNamespace)) { yield return type; } } IEnumerable GetDeclaredTypes(INamespaceOrTypeSymbol ns) { foreach (var member in ns.GetMembers()) { switch (member) { case INamespaceSymbol nestedNamespace: foreach (var nested in GetDeclaredTypes(nestedNamespace)) yield return nested; break; case ITypeSymbol type: if (type is INamedTypeSymbol namedType) yield return namedType; foreach (var nested in GetDeclaredTypes(type)) yield return nested; break; } } } } } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/SymbolSyntaxExtensions.cs ================================================ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Reflection; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Orleans.CodeGenerator.SyntaxGeneration { internal static class SymbolSyntaxExtensions { public static ParenthesizedExpressionSyntax GetBindingFlagsParenthesizedExpressionSyntax( SyntaxKind operationKind, params BindingFlags[] bindingFlags) { if (bindingFlags.Length < 2) { throw new ArgumentOutOfRangeException( nameof(bindingFlags), $"Can't create parenthesized binary expression with {bindingFlags.Length} arguments"); } var flags = AliasQualifiedName("global", IdentifierName("System")).Member("Reflection").Member("BindingFlags"); var bindingFlagsBinaryExpression = BinaryExpression( operationKind, flags.Member(bindingFlags[0].ToString()), flags.Member(bindingFlags[1].ToString())); for (var i = 2; i < bindingFlags.Length; i++) { bindingFlagsBinaryExpression = BinaryExpression( operationKind, bindingFlagsBinaryExpression, flags.Member(bindingFlags[i].ToString())); } return ParenthesizedExpression(bindingFlagsBinaryExpression); } /// /// Returns the System.String that represents the current TypedConstant. /// /// A System.String that represents the current TypedConstant. public static ExpressionSyntax ToExpression(this TypedConstant constant) { if (constant.IsNull) { return LiteralExpression(SyntaxKind.NullLiteralExpression); } if (constant.Kind == TypedConstantKind.Array) { throw new NotSupportedException($"Unsupported TypedConstant: {constant.ToCSharpString()}"); } if (constant.Kind == TypedConstantKind.Type) { Debug.Assert(constant.Value is not null); return TypeOfExpression(((ITypeSymbol)constant.Value).ToTypeSyntax()); } if (constant.Kind == TypedConstantKind.Enum) { return DisplayEnumConstant(constant); } return ParseExpression(constant.ToCSharpString()); } // Decode the value of enum constant private static ExpressionSyntax DisplayEnumConstant(TypedConstant constant) { //string typeName = constant.Type.ToDisplayName(); var constantToDecode = ConvertToUInt64(constant.Value); ulong curValue = 0; // Iterate through all the constant members in the enum type var members = constant.Type!.GetMembers(); var type = constant.Type.ToTypeSyntax(); ExpressionSyntax result = null; foreach (var member in members) { var field = member as IFieldSymbol; if (field is object && field.HasConstantValue) { ulong memberValue = ConvertToUInt64(field.ConstantValue); if (memberValue == constantToDecode) { return constant.Type.ToTypeSyntax().Member(field.Name); } if ((memberValue & constantToDecode) == memberValue) { // update the current value curValue = curValue | memberValue; var valueExpression = type.Member(field.Name); if (result is null) { result = valueExpression; } else { result = BinaryExpression(SyntaxKind.BitwiseOrExpression, result, valueExpression); } } } } return result; } private static ulong ConvertToUInt64(object value) { return value switch { byte b => b, sbyte sb => (ulong)sb, short s => (ulong)s, ushort us => us, int i => (ulong)i, uint ui => ui, long l => (ulong)l, ulong ul => ul, _ => throw new NotSupportedException($"Type {value?.GetType()} not supported") }; } } } ================================================ FILE: src/Orleans.CodeGenerator/SyntaxGeneration/SyntaxFactoryUtility.cs ================================================ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Linq; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Orleans.CodeGenerator.SyntaxGeneration { internal static class SyntaxFactoryUtility { /// /// Returns member access syntax. /// /// /// The instance. /// /// /// The member. /// /// /// The resulting . /// public static MemberAccessExpressionSyntax Member(this ExpressionSyntax instance, string member) => instance.Member(member.ToIdentifierName()); /// /// Returns member access syntax. /// /// /// The instance. /// /// /// The member. /// /// /// The resulting . /// public static MemberAccessExpressionSyntax Member(this ExpressionSyntax instance, IdentifierNameSyntax member) => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, instance, member); public static MemberAccessExpressionSyntax Member(this ExpressionSyntax instance, GenericNameSyntax member) => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, instance, member); public static MemberAccessExpressionSyntax Member( this ExpressionSyntax instance, string member, params TypeSyntax[] genericTypes) => instance.Member( member.ToGenericName() .AddTypeArgumentListArguments(genericTypes)); public static GenericNameSyntax ToGenericName(this string identifier) => GenericName(identifier.ToIdentifier()); public static ClassDeclarationSyntax AddGenericTypeParameters( ClassDeclarationSyntax classDeclaration, List<(string Name, ITypeParameterSymbol Parameter)> typeParameters) { var typeParametersWithConstraints = GetTypeParameterConstraints(typeParameters); foreach (var (name, constraints) in typeParametersWithConstraints) { if (constraints.Count > 0) { classDeclaration = classDeclaration.AddConstraintClauses( TypeParameterConstraintClause(name).AddConstraints(constraints.ToArray())); } } if (typeParametersWithConstraints.Count > 0) { classDeclaration = classDeclaration.WithTypeParameterList( TypeParameterList(SeparatedList(typeParametersWithConstraints.Select(tp => TypeParameter(tp.Name))))); } return classDeclaration; } public static List<(string Name, List Constraints)> GetTypeParameterConstraints(List<(string Name, ITypeParameterSymbol Parameter)> typeParameter) { var allConstraints = new List<(string, List)>(); foreach (var (name, tp) in typeParameter) { var constraints = new List(); if (tp.HasUnmanagedTypeConstraint) { constraints.Add(TypeConstraint(IdentifierName("unmanaged"))); } else if (tp.HasValueTypeConstraint) { constraints.Add(ClassOrStructConstraint(SyntaxKind.StructConstraint)); } else if (tp.HasNotNullConstraint) { constraints.Add(TypeConstraint(IdentifierName("notnull"))); } else if (tp.HasReferenceTypeConstraint) { constraints.Add(ClassOrStructConstraint(SyntaxKind.ClassConstraint)); } foreach (var c in tp.ConstraintTypes) { constraints.Add(TypeConstraint(c.ToTypeSyntax())); } if (tp.HasConstructorConstraint) { constraints.Add(ConstructorConstraint()); } allConstraints.Add((name, constraints)); } return allConstraints; } } } ================================================ FILE: src/Orleans.CodeGenerator/build/Microsoft.Orleans.CodeGenerator.props ================================================ $(DesignTimeBuild) $(OrleansGenerateFieldIds) $(OrleansConstructorAttributes) ================================================ FILE: src/Orleans.CodeGenerator/buildMultiTargeting/Microsoft.Orleans.CodeGenerator.props ================================================ ================================================ FILE: src/Orleans.CodeGenerator/buildTransitive/Microsoft.Orleans.CodeGenerator.props ================================================ ================================================ FILE: src/Orleans.Connections.Security/Hosting/HostingExtensions.IClientBuilder.cs ================================================ using System; using System.Security.Cryptography.X509Certificates; using Orleans.Configuration; using Orleans.Connections.Security; namespace Orleans.Hosting { public static partial class OrleansConnectionSecurityHostingExtensions { /// /// Configures TLS. /// /// The builder to configure. /// The certificate store to load the certificate from. /// The subject name for the certificate to load. /// Indicates if invalid certificates should be considered, such as self-signed certificates. /// The store location to load the certificate from. /// An Action to configure the . /// The builder. public static IClientBuilder UseTls( this IClientBuilder builder, StoreName storeName, string subject, bool allowInvalid, StoreLocation location, Action configureOptions) { if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } return builder.UseTls( CertificateLoader.LoadFromStoreCert(subject, storeName.ToString(), location, allowInvalid, server: false), configureOptions); } /// /// Configures TLS. /// /// The builder to configure. /// The server certificate. /// An Action to configure the . /// The builder. public static IClientBuilder UseTls( this IClientBuilder builder, X509Certificate2 certificate, Action configureOptions) { if (certificate is null) { throw new ArgumentNullException(nameof(certificate)); } if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } if (!certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, nameof(certificate)); } return builder.UseTls(options => { options.LocalCertificate = certificate; configureOptions(options); }); } /// /// Configures TLS. /// /// The builder to configure. /// The server certificate. /// The builder. public static IClientBuilder UseTls( this IClientBuilder builder, X509Certificate2 certificate) { if (certificate is null) { throw new ArgumentNullException(nameof(certificate)); } if (!certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, nameof(certificate)); } return builder.UseTls(options => { options.LocalCertificate = certificate; }); } /// /// Configures TLS. /// /// The builder to configure. /// An Action to configure the . /// The builder. public static IClientBuilder UseTls( this IClientBuilder builder, Action configureOptions) { if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } var options = new TlsOptions(); configureOptions(options); if (options.LocalCertificate is null && options.ClientCertificateMode == RemoteCertificateMode.RequireCertificate) { throw new InvalidOperationException("No certificate specified"); } if (options.LocalCertificate is X509Certificate2 certificate && !certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, $"{nameof(TlsOptions)}.{nameof(TlsOptions.LocalCertificate)}"); } return builder.Configure(connectionOptions => { connectionOptions.ConfigureConnection(connectionBuilder => { connectionBuilder.UseClientTls(options); }); }); } } } ================================================ FILE: src/Orleans.Connections.Security/Hosting/HostingExtensions.ISiloBuilder.cs ================================================ using System; using System.Security.Cryptography.X509Certificates; using Orleans.Configuration; using Orleans.Connections.Security; namespace Orleans.Hosting { public static partial class OrleansConnectionSecurityHostingExtensions { /// /// Configures TLS. /// /// The builder to configure. /// The certificate store to load the certificate from. /// The subject name for the certificate to load. /// Indicates if invalid certificates should be considered, such as self-signed certificates. /// The store location to load the certificate from. /// An Action to configure the . /// The builder. public static ISiloBuilder UseTls( this ISiloBuilder builder, StoreName storeName, string subject, bool allowInvalid, StoreLocation location, Action configureOptions) { if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } return builder.UseTls( CertificateLoader.LoadFromStoreCert(subject, storeName.ToString(), location, allowInvalid, server: true), configureOptions); } /// /// Configures TLS. /// /// The builder to configure. /// The server certificate. /// An Action to configure the . /// The builder. public static ISiloBuilder UseTls( this ISiloBuilder builder, X509Certificate2 certificate, Action configureOptions) { if (certificate is null) { throw new ArgumentNullException(nameof(certificate)); } if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } if (!certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, nameof(certificate)); } return builder.UseTls(options => { options.LocalCertificate = certificate; configureOptions(options); }); } /// /// Configures TLS. /// /// The builder to configure. /// The server certificate. /// The builder. public static ISiloBuilder UseTls( this ISiloBuilder builder, X509Certificate2 certificate) { if (certificate is null) { throw new ArgumentNullException(nameof(certificate)); } if (!certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, nameof(certificate)); } return builder.UseTls(options => { options.LocalCertificate = certificate; }); } /// /// Configures TLS. /// /// The builder to configure. /// An Action to configure the . /// The builder. public static ISiloBuilder UseTls( this ISiloBuilder builder, Action configureOptions) { if (configureOptions is null) { throw new ArgumentNullException(nameof(configureOptions)); } var options = new TlsOptions(); configureOptions(options); if (options.LocalCertificate is null && options.LocalServerCertificateSelector is null) { throw new InvalidOperationException("No certificate specified"); } if (options.LocalCertificate is X509Certificate2 certificate && !certificate.HasPrivateKey) { TlsConnectionBuilderExtensions.ThrowNoPrivateKey(certificate, $"{nameof(TlsOptions)}.{nameof(TlsOptions.LocalCertificate)}"); } return builder.Configure(connectionOptions => { connectionOptions.ConfigureSiloInboundConnection(connectionBuilder => { connectionBuilder.UseServerTls(options); }); connectionOptions.ConfigureGatewayInboundConnection(connectionBuilder => { connectionBuilder.UseServerTls(options); }); connectionOptions.ConfigureSiloOutboundConnection(connectionBuilder => { connectionBuilder.UseClientTls(options); }); }); } } } ================================================ FILE: src/Orleans.Connections.Security/Hosting/HostingExtensions.cs ================================================ using System; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Orleans.Connections.Security; namespace Orleans { public static class TlsConnectionBuilderExtensions { public static void UseServerTls( this IConnectionBuilder builder, TlsOptions options) { if (options is null) { throw new ArgumentNullException(nameof(options)); } var loggerFactory = builder.ApplicationServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory ?? NullLoggerFactory.Instance; builder.Use(next => { var middleware = new TlsServerConnectionMiddleware(next, options, loggerFactory); return middleware.OnConnectionAsync; }); } public static void UseClientTls( this IConnectionBuilder builder, TlsOptions options) { if (options is null) { throw new ArgumentNullException(nameof(options)); } var loggerFactory = builder.ApplicationServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory ?? NullLoggerFactory.Instance; builder.Use(next => { var middleware = new TlsClientConnectionMiddleware(next, options, loggerFactory); return middleware.OnConnectionAsync; }); } internal static void ThrowNoPrivateKey(X509Certificate2 certificate, string parameterName) { throw new ArgumentException($"Certificate {certificate.ToString(verbose: true)} does not contain a private key", parameterName); } } } ================================================ FILE: src/Orleans.Connections.Security/Orleans.Connections.Security.csproj ================================================ Microsoft.Orleans.Connections.Security Microsoft Orleans TLS support Support for security communication using TLS in Microsoft Orleans. $(PackageTags) TLS SSL $(DefaultTargetFrameworks) true ================================================ FILE: src/Orleans.Connections.Security/Security/CertificateLoader.cs ================================================ using System; using System.Linq; using System.Security.Cryptography.X509Certificates; namespace Orleans.Connections.Security { public static class CertificateLoader { // See http://oid-info.com/get/1.3.6.1.5.5.7.3.1 // Indicates that a certificate can be used as a TLS server certificate private const string ServerAuthenticationOid = "1.3.6.1.5.5.7.3.1"; // See http://oid-info.com/get/1.3.6.1.5.5.7.3.2 // Indicates that a certificate can be used as a TLS client certificate private const string ClientAuthenticationOid = "1.3.6.1.5.5.7.3.2"; public static X509Certificate2 LoadFromStoreCert(string subject, string storeName, StoreLocation storeLocation, bool allowInvalid, bool server) { using (var store = new X509Store(storeName, storeLocation)) { X509Certificate2Collection storeCertificates = null; X509Certificate2 foundCertificate = null; try { store.Open(OpenFlags.ReadOnly); storeCertificates = store.Certificates; var foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectName, subject, !allowInvalid); foundCertificate = foundCertificates .OfType() .Where(c => server ? IsCertificateAllowedForServerAuth(c) : IsCertificateAllowedForClientAuth(c)) .Where(DoesCertificateHaveAnAccessiblePrivateKey) .OrderByDescending(certificate => certificate.NotAfter) .FirstOrDefault(); if (foundCertificate == null) { throw new InvalidOperationException($"Certificate {subject} not found in store {storeLocation} / {storeName}. AllowInvalid: {allowInvalid}"); } return foundCertificate; } finally { DisposeCertificates(storeCertificates, except: foundCertificate); } } } internal static bool IsCertificateAllowedForServerAuth(X509Certificate2 certificate) => IsCertificateAllowedForKeyUsage(certificate, ServerAuthenticationOid); internal static bool IsCertificateAllowedForClientAuth(X509Certificate2 certificate) => IsCertificateAllowedForKeyUsage(certificate, ClientAuthenticationOid); private static bool IsCertificateAllowedForKeyUsage(X509Certificate2 certificate, string purposeOid) { /* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1) * If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages. * * See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/ * * From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage" * * If the (Extended Key Usage) extension is present, then the certificate MUST only be used * for one of the purposes indicated. If multiple purposes are * indicated the application need not recognize all purposes indicated, * as long as the intended purpose is present. Certificate using * applications MAY require that a particular purpose be indicated in * order for the certificate to be acceptable to that application. */ var hasEkuExtension = false; foreach (var extension in certificate.Extensions.OfType()) { hasEkuExtension = true; foreach (var oid in extension.EnhancedKeyUsages) { if (oid.Value.Equals(purposeOid, StringComparison.Ordinal)) { return true; } } } return !hasEkuExtension; } internal static bool DoesCertificateHaveAnAccessiblePrivateKey(X509Certificate2 certificate) => certificate.HasPrivateKey; private static void DisposeCertificates(X509Certificate2Collection certificates, X509Certificate2 except) { if (certificates != null) { foreach (var certificate in certificates) { if (!certificate.Equals(except)) { certificate.Dispose(); } } } } } } ================================================ FILE: src/Orleans.Connections.Security/Security/DuplexPipeStream.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; namespace Orleans.Connections.Security { internal class DuplexPipeStream : Stream { private readonly PipeReader _reader; private readonly PipeWriter _writer; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public DuplexPipeStream(IDuplexPipe pipe) { _reader = pipe.Input; _writer = pipe.Output; } protected override void Dispose(bool disposing) { if (disposing) { _reader.Complete(); _writer.Complete(); } base.Dispose(disposing); } public override async ValueTask DisposeAsync() { await _reader.CompleteAsync().ConfigureAwait(false); await _writer.CompleteAsync().ConfigureAwait(false); } public override void Flush() { FlushAsync().GetAwaiter().GetResult(); } public override async Task FlushAsync(CancellationToken cancellationToken) { FlushResult r = await _writer.FlushAsync(cancellationToken).ConfigureAwait(false); if (r.IsCanceled) throw new OperationCanceledException(cancellationToken); } public override int Read(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); ValueTask t = ReadAsync(buffer.AsMemory(offset, count)); return t.IsCompleted ? t.GetAwaiter().GetResult() : t.AsTask().GetAwaiter().GetResult(); } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ValidateBufferArguments(buffer, offset, count); return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); } public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { ReadResult result = await _reader.ReadAsync(cancellationToken).ConfigureAwait(false); if (result.IsCanceled) { throw new OperationCanceledException(); } ReadOnlySequence sequence = result.Buffer; long bufferLength = sequence.Length; SequencePosition consumed = sequence.Start; try { if (bufferLength != 0) { int actual = (int)Math.Min(bufferLength, buffer.Length); ReadOnlySequence slice = actual == bufferLength ? sequence : sequence.Slice(0, actual); consumed = slice.End; slice.CopyTo(buffer.Span); return actual; } if (result.IsCompleted) { return 0; } } finally { _reader.AdvanceTo(consumed); } // This is a buggy PipeReader implementation that returns 0 byte reads even though the PipeReader // isn't completed or canceled. throw new InvalidOperationException("Read zero bytes unexpectedly"); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state); } public override int EndRead(IAsyncResult asyncResult) { return TaskToApm.End(asyncResult); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ValidateBufferArguments(buffer, offset, count); return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); } public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { FlushResult r = await _writer.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); if (r.IsCanceled) throw new OperationCanceledException(cancellationToken); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { TaskToApm.End(asyncResult); } public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { return _reader.CopyToAsync(destination, cancellationToken); } /// /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. /// internal static class TaskToApm { /// /// Marshals the Task as an IAsyncResult, using the supplied callback and state /// to implement the APM pattern. /// /// The Task to be marshaled. /// The callback to be invoked upon completion. /// The state to be stored in the IAsyncResult. /// An IAsyncResult to represent the task's asynchronous operation. public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) => new TaskAsyncResult(task, state, callback); /// Processes an IAsyncResult returned by Begin. /// The IAsyncResult to unwrap. public static void End(IAsyncResult asyncResult) { if (GetTask(asyncResult) is Task t) { t.GetAwaiter().GetResult(); return; } ThrowArgumentException(asyncResult); } /// Processes an IAsyncResult returned by Begin. /// The IAsyncResult to unwrap. public static TResult End(IAsyncResult asyncResult) { if (GetTask(asyncResult) is Task task) { return task.GetAwaiter().GetResult(); } ThrowArgumentException(asyncResult); return default!; // unreachable } /// Gets the task represented by the IAsyncResult. public static Task GetTask(IAsyncResult asyncResult) => (asyncResult as TaskAsyncResult)?._task; /// Throws an argument exception for the invalid . private static void ThrowArgumentException(IAsyncResult asyncResult) => throw (asyncResult is null ? new ArgumentNullException(nameof(asyncResult)) : new ArgumentException(null, nameof(asyncResult))); /// Provides a simple IAsyncResult that wraps a Task. /// /// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state, /// but that's very rare, in particular in a situation where someone cares about allocation, and always /// using TaskAsyncResult simplifies things and enables additional optimizations. /// internal sealed class TaskAsyncResult : IAsyncResult { /// The wrapped Task. internal readonly Task _task; /// Callback to invoke when the wrapped task completes. private readonly AsyncCallback _callback; /// Initializes the IAsyncResult with the Task to wrap and the associated object state. /// The Task to wrap. /// The new AsyncState value. /// Callback to invoke when the wrapped task completes. internal TaskAsyncResult(Task task, object state, AsyncCallback callback) { Debug.Assert(task != null); _task = task; AsyncState = state; if (task.IsCompleted) { // Synchronous completion. Invoke the callback. No need to store it. CompletedSynchronously = true; callback?.Invoke(this); } else if (callback != null) { // Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in // order to avoid running synchronously if the task has already completed by the time we get here but still run // synchronously as part of the task's completion if the task completes after (the more common case). _callback = callback; _task.ConfigureAwait(continueOnCapturedContext: false) .GetAwaiter() .OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure } } /// Invokes the callback. private void InvokeCallback() { Debug.Assert(!CompletedSynchronously); Debug.Assert(_callback != null); _callback.Invoke(this); } /// Gets a user-defined object that qualifies or contains information about an asynchronous operation. public object AsyncState { get; } /// Gets a value that indicates whether the asynchronous operation completed synchronously. /// This is set lazily based on whether the has completed by the time this object is created. public bool CompletedSynchronously { get; } /// Gets a value that indicates whether the asynchronous operation has completed. public bool IsCompleted => _task.IsCompleted; /// Gets a that is used to wait for an asynchronous operation to complete. public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle; } } } } ================================================ FILE: src/Orleans.Connections.Security/Security/DuplexPipeStreamAdapter.cs ================================================ using System; using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; namespace Orleans.Connections.Security { /// /// A helper for wrapping a Stream decorator from an . /// /// internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe where TStream : Stream { private bool _disposed; #if NET9_0_OR_GREATER private readonly Lock _disposeLock = new(); #else private readonly object _disposeLock = new(); #endif public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream) { } public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func createStream) : base(duplexPipe) { var stream = createStream(this); Stream = stream; Input = PipeReader.Create(stream, readerOptions); Output = PipeWriter.Create(stream, writerOptions); } public TStream Stream { get; } public PipeReader Input { get; } public PipeWriter Output { get; } public override async ValueTask DisposeAsync() { lock (_disposeLock) { if (_disposed) { return; } _disposed = true; } await Input.CompleteAsync(); await Output.CompleteAsync(); } protected override void Dispose(bool disposing) { lock (_disposeLock) { if (_disposed) { return; } _disposed = true; } if (disposing) { Input.Complete(); Output.Complete(); } } } } ================================================ FILE: src/Orleans.Connections.Security/Security/ITlsApplicationProtocolFeature.cs ================================================ using System; namespace Orleans.Connections.Security { public interface ITlsApplicationProtocolFeature { ReadOnlyMemory ApplicationProtocol { get; } } } ================================================ FILE: src/Orleans.Connections.Security/Security/ITlsConnectionFeature.cs ================================================ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.Threading; namespace Orleans.Connections.Security { public interface ITlsConnectionFeature { /// /// Synchronously retrieves the remote endpoint's certificate, if any. /// X509Certificate2 RemoteCertificate { get; set; } /// /// Asynchronously retrieves the remote endpoint's certificate, if any. /// /// Task GetRemoteCertificateAsync(CancellationToken cancellationToken); } } ================================================ FILE: src/Orleans.Connections.Security/Security/ITlsHandshakeFeature.cs ================================================ using System; using System.Net.Security; using System.Security.Authentication; namespace Orleans.Connections.Security { public interface ITlsHandshakeFeature { SslProtocols Protocol { get; } /// /// Gets the . /// TlsCipherSuite? NegotiatedCipherSuite => null; /// /// Gets the host name from the "server_name" (SNI) extension of the client hello if present. /// string HostName => string.Empty; #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif CipherAlgorithmType CipherAlgorithm { get; } #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif int CipherStrength { get; } #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif HashAlgorithmType HashAlgorithm { get; } #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif int HashStrength { get; } #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif ExchangeAlgorithmType KeyExchangeAlgorithm { get; } #if NET10_0_OR_GREATER [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif int KeyExchangeStrength { get; } } } ================================================ FILE: src/Orleans.Connections.Security/Security/MemoryPoolExtensions.cs ================================================ using System; using System.Buffers; namespace Orleans.Connections.Security { internal static class MemoryPoolExtensions { /// /// Computes a minimum segment size /// /// /// public static int GetMinimumSegmentSize(this MemoryPool pool) { if (pool == null) { return 4096; } return Math.Min(4096, pool.MaxBufferSize); } public static int GetMinimumAllocSize(this MemoryPool pool) { // 1/2 of a segment return pool.GetMinimumSegmentSize() / 2; } } } ================================================ FILE: src/Orleans.Connections.Security/Security/OrleansApplicationProtocol.cs ================================================ using System.Net.Security; namespace Orleans.Connections.Security { internal static class OrleansApplicationProtocol { public static readonly SslApplicationProtocol Orleans1 = new SslApplicationProtocol("Orleans1"); } } ================================================ FILE: src/Orleans.Connections.Security/Security/RemoteCertificateMode.cs ================================================ namespace Orleans.Connections.Security { /// /// Describes the remote certificate requirements for a TLS connection. /// public enum RemoteCertificateMode { /// /// A remote certificate is not required and will not be requested from remote endpoints. /// NoCertificate, /// /// A remote certificate will be requested; however, authentication will not fail if a certificate is not provided by the remote endpoint. /// AllowCertificate, /// /// A remote certificate will be requested, and the remote endpoint must provide a valid certificate for authentication. /// RequireCertificate } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsClientAuthenticationOptions.cs ================================================ using System.Collections.Generic; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace Orleans.Connections.Security { public delegate X509Certificate ClientCertificateSelectionCallback(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers); public class TlsClientAuthenticationOptions { internal SslClientAuthenticationOptions Value { get; } = new SslClientAuthenticationOptions { ApplicationProtocols = new List { OrleansApplicationProtocol.Orleans1 } }; public ClientCertificateSelectionCallback LocalCertificateSelectionCallback { get => Value.LocalCertificateSelectionCallback is null ? null : new ClientCertificateSelectionCallback(Value.LocalCertificateSelectionCallback); set => Value.LocalCertificateSelectionCallback = value is null ? null : new System.Net.Security.LocalCertificateSelectionCallback(value); } public X509CertificateCollection ClientCertificates { get => this.Value.ClientCertificates; set => this.Value.ClientCertificates = value; } public SslProtocols EnabledSslProtocols { get => this.Value.EnabledSslProtocols; set => this.Value.EnabledSslProtocols = value; } public X509RevocationMode CertificateRevocationCheckMode { get => this.Value.CertificateRevocationCheckMode; set => this.Value.CertificateRevocationCheckMode = value; } public string TargetHost { get => this.Value.TargetHost; set => this.Value.TargetHost = value; } public object SslClientAuthenticationOptions => this.Value; } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsClientConnectionMiddleware.cs ================================================ using System; using System.IO.Pipelines; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.Extensions.Logging; namespace Orleans.Connections.Security { internal class TlsClientConnectionMiddleware { private readonly ConnectionDelegate _next; private readonly TlsOptions _options; private readonly ILogger _logger; private readonly X509Certificate2 _certificate; private readonly Func _certificateSelector; public TlsClientConnectionMiddleware(ConnectionDelegate next, TlsOptions options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next; // capture the certificate now so it can't be switched after validation _certificate = ValidateCertificate(options.LocalCertificate, options.ClientCertificateMode); _certificateSelector = options.LocalClientCertificateSelector; _options = options; _logger = loggerFactory?.CreateLogger(); } public Task OnConnectionAsync(ConnectionContext context) { return InnerOnConnectionAsync(context); } private async Task InnerOnConnectionAsync(ConnectionContext context) { var feature = new TlsConnectionFeature(); context.Features.Set(feature); context.Features.Set(feature); var memoryPool = context.Features.Get()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPool, bufferSize: memoryPool.GetMinimumSegmentSize(), minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( pool: memoryPool, leaveOpen: true ); TlsDuplexPipe tlsDuplexPipe = null; if (_options.RemoteCertificateMode == RemoteCertificateMode.NoCertificate) { tlsDuplexPipe = new TlsDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); } else { tlsDuplexPipe = new TlsDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream( s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return _options.RemoteCertificateMode != RemoteCertificateMode.RequireCertificate; } if (_options.RemoteCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return false; } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return false; } if (_options.RemoteCertificateValidation != null) { if (!_options.RemoteCertificateValidation(certificate2, chain, sslPolicyErrors)) { return false; } } return true; })); } var sslStream = tlsDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { ClientCertificateSelectionCallback selector = null; if (_certificateSelector != null) { selector = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => { var cert = _certificateSelector(sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers); if (cert != null) { EnsureCertificateIsAllowedForClientAuth(cert); } return cert; }; } var sslOptions = new TlsClientAuthenticationOptions { ClientCertificates = _certificate == null || _certificateSelector != null ? null : new X509CertificateCollection { _certificate }, LocalCertificateSelectionCallback = selector, EnabledSslProtocols = _options.SslProtocols, }; _options.OnAuthenticateAsClient?.Invoke(context, sslOptions); await sslStream.AuthenticateAsClientAsync(sslOptions.Value, cancellationTokeSource.Token); } catch (OperationCanceledException ex) { _logger?.LogWarning(2, ex, "Authentication timed out"); await sslStream.DisposeAsync(); return; } catch (Exception ex) { _logger?.LogWarning(1, ex, "Authentication failed"); await sslStream.DisposeAsync(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set(feature); feature.LocalCertificate = ConvertToX509Certificate2(sslStream.LocalCertificate); feature.RemoteCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.NegotiatedCipherSuite = sslStream.NegotiatedCipherSuite; #if NET10_0_OR_GREATER #pragma warning disable SYSLIB0058 #endif feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; #if NET10_0_OR_GREATER #pragma warning restore SYSLIB0058 #endif feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; try { context.Transport = tlsDuplexPipe; // Disposing the stream will dispose the tlsDuplexPipe await using (sslStream) await using (tlsDuplexPipe) { await _next(context); // Dispose the inner stream (tlsDuplexPipe) before disposing the SslStream // as the duplex pipe can hit an ODE as it still may be writing. } } finally { // Restore the original so that it gets closed appropriately context.Transport = originalTransport; } } private static X509Certificate2 ValidateCertificate(X509Certificate2 certificate, RemoteCertificateMode mode) { switch (mode) { case RemoteCertificateMode.NoCertificate: return null; case RemoteCertificateMode.AllowCertificate: //if certificate exists but can not be used for client authentication. if (certificate != null && CertificateLoader.IsCertificateAllowedForClientAuth(certificate)) return certificate; return null; case RemoteCertificateMode.RequireCertificate: EnsureCertificateIsAllowedForClientAuth(certificate); return certificate; default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } } protected static void EnsureCertificateIsAllowedForClientAuth(X509Certificate2 certificate) { if (certificate is null) { throw new InvalidOperationException("No certificate provided for client authentication."); } if (!CertificateLoader.IsCertificateAllowedForClientAuth(certificate)) { throw new InvalidOperationException($"Invalid client certificate for client authentication: {certificate.Thumbprint}"); } } private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate) { if (certificate is null) { return null; } return certificate as X509Certificate2 ?? new X509Certificate2(certificate); } } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs ================================================ using System; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; namespace Orleans.Connections.Security { internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature, ITlsHandshakeFeature { public X509Certificate2 LocalCertificate { get; set; } public X509Certificate2 RemoteCertificate { get; set; } public ReadOnlyMemory ApplicationProtocol { get; set; } public SslProtocols Protocol { get; set; } public TlsCipherSuite? NegotiatedCipherSuite { get; set; } public string HostName { get; set; } = string.Empty; #if NET10_0_OR_GREATER #pragma warning disable SYSLIB0058 #endif public CipherAlgorithmType CipherAlgorithm { get; set; } public int CipherStrength { get; set; } public HashAlgorithmType HashAlgorithm { get; set; } public int HashStrength { get; set; } public ExchangeAlgorithmType KeyExchangeAlgorithm { get; set; } public int KeyExchangeStrength { get; set; } #if NET10_0_OR_GREATER #pragma warning restore SYSLIB0058 #endif public Task GetRemoteCertificateAsync(CancellationToken cancellationToken) { return Task.FromResult(RemoteCertificate); } } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsDuplexPipe.cs ================================================ using System; using System.IO; using System.IO.Pipelines; using System.Net.Security; namespace Orleans.Connections.Security { internal class TlsDuplexPipe : DuplexPipeStreamAdapter { public TlsDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) : this(transport, readerOptions, writerOptions, s => new SslStream(s)) { } public TlsDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : base(transport, readerOptions, writerOptions, factory) { } } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsOptions.cs ================================================ using System; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using Microsoft.AspNetCore.Connections; namespace Orleans.Connections.Security { public delegate bool RemoteCertificateValidator(X509Certificate2 certificate, X509Chain chain, SslPolicyErrors policyErrors); /// /// Settings for how TLS connections are handled. /// public class TlsOptions { private TimeSpan _handshakeTimeout = TimeSpan.FromSeconds(10); /// /// /// Specifies the local certificate used to authenticate TLS connections. This is ignored on server if LocalCertificateSelector is set. /// /// /// To omit client authentication set to null on client and set to or on server. /// /// /// If the certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1) for server and Client Authentication (OID 1.3.6.1.5.5.7.3.2) for client. /// /// public X509Certificate2 LocalCertificate { get; set; } /// /// /// A callback that will be invoked to dynamically select a local server certificate. This is higher priority than LocalCertificate. /// If SNI is not available then the name parameter will be null. /// /// /// If the certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1). /// /// public Func LocalServerCertificateSelector { get; set; } /// /// /// A callback that will be invoked to dynamically select a local client certificate. This is higher priority than LocalCertificate. /// /// /// If the certificate has an Extended Key Usage extension, the usages must include Client Authentication (OID 1.3.6.1.5.5.7.3.2). /// /// public Func LocalClientCertificateSelector { get; set; } /// /// Specifies the remote endpoint certificate requirements for a TLS connection. Defaults to . /// public RemoteCertificateMode RemoteCertificateMode { get; set; } = RemoteCertificateMode.RequireCertificate; /// /// Specifies the client authentication certificate requirements for a TLS connection to Silo. Defaults to . /// public RemoteCertificateMode ClientCertificateMode { get; set; } = RemoteCertificateMode.AllowCertificate; /// /// Specifies a callback for additional remote certificate validation that will be invoked during authentication. This will be ignored /// if is called after this callback is set. /// public RemoteCertificateValidator RemoteCertificateValidation { get; set; } /// /// Specifies allowable SSL protocols. Defaults to and . /// public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls13 | SslProtocols.Tls12; /// /// Specifies whether the certificate revocation list is checked during authentication. /// public bool CheckCertificateRevocation { get; set; } /// /// Overrides the current callback and allows any client certificate. /// public void AllowAnyRemoteCertificate() { RemoteCertificateValidation = (_, __, ___) => true; } /// /// Provides direct configuration of the on a per-connection basis. /// This is called after all of the other settings have already been applied. /// public Action OnAuthenticateAsServer { get; set; } /// /// Provides direct configuration of the on a per-connection basis. /// This is called after all of the other settings have already been applied. /// Use this to set the target host name for SNI (Server Name Indication) via . /// public Action OnAuthenticateAsClient { get; set; } /// /// Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite. /// public TimeSpan HandshakeTimeout { get => _handshakeTimeout; set { if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) { throw new ArgumentOutOfRangeException(nameof(value), nameof(HandshakeTimeout) + " must be positive"); } _handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue; } } } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsServerAuthenticationOptions.cs ================================================ using System.Collections.Generic; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace Orleans.Connections.Security { public delegate X509Certificate ServerCertificateSelectionCallback(object sender, string hostName); public class TlsServerAuthenticationOptions { internal SslServerAuthenticationOptions Value { get; } = new SslServerAuthenticationOptions { ApplicationProtocols = new List { OrleansApplicationProtocol.Orleans1 } }; public X509Certificate ServerCertificate { get => Value.ServerCertificate; set => Value.ServerCertificate = value; } public ServerCertificateSelectionCallback ServerCertificateSelectionCallback { get => Value.ServerCertificateSelectionCallback is null ? null : new ServerCertificateSelectionCallback(Value.ServerCertificateSelectionCallback); set => Value.ServerCertificateSelectionCallback = value is null ? null : new System.Net.Security.ServerCertificateSelectionCallback(value); } public bool ClientCertificateRequired { get => Value.ClientCertificateRequired; set => Value.ClientCertificateRequired = value; } public SslProtocols EnabledSslProtocols { get => Value.EnabledSslProtocols; set => Value.EnabledSslProtocols = value; } public X509RevocationMode CertificateRevocationCheckMode { get => Value.CertificateRevocationCheckMode; set => Value.CertificateRevocationCheckMode = value; } public object SslServerAuthenticationOptions => this.Value; } } ================================================ FILE: src/Orleans.Connections.Security/Security/TlsServerConnectionMiddleware.cs ================================================ using System; using System.IO.Pipelines; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.Extensions.Logging; namespace Orleans.Connections.Security { internal class TlsServerConnectionMiddleware { private readonly ConnectionDelegate _next; private readonly TlsOptions _options; private readonly ILogger _logger; private readonly X509Certificate2 _certificate; private readonly Func _certificateSelector; public TlsServerConnectionMiddleware(ConnectionDelegate next, TlsOptions options, ILoggerFactory loggerFactory) { if (options == null) { throw new ArgumentNullException(nameof(options)); } _next = next; // capture the certificate now so it can't be switched after validation _certificate = options.LocalCertificate; _certificateSelector = options.LocalServerCertificateSelector; if (_certificate == null && _certificateSelector == null) { throw new ArgumentException("Server certificate is required", nameof(options)); } // If a selector is provided then ignore the cert, it may be a default cert. if (_certificateSelector != null) { // SslStream doesn't allow both. _certificate = null; } else { EnsureCertificateIsAllowedForServerAuth(_certificate); } _options = options; _logger = loggerFactory?.CreateLogger(); } public Task OnConnectionAsync(ConnectionContext context) { return InnerOnConnectionAsync(context); } private async Task InnerOnConnectionAsync(ConnectionContext context) { bool certificateRequired; var feature = new TlsConnectionFeature(); context.Features.Set(feature); context.Features.Set(feature); var memoryPool = context.Features.Get()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPool, bufferSize: memoryPool.GetMinimumSegmentSize(), minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( pool: memoryPool, leaveOpen: true ); TlsDuplexPipe tlsDuplexPipe = null; if (_options.RemoteCertificateMode == RemoteCertificateMode.NoCertificate) { tlsDuplexPipe = new TlsDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { tlsDuplexPipe = new TlsDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream( s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return _options.RemoteCertificateMode != RemoteCertificateMode.RequireCertificate; } if (_options.RemoteCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return false; } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return false; } if (_options.RemoteCertificateValidation != null) { if (!_options.RemoteCertificateValidation(certificate2, chain, sslPolicyErrors)) { return false; } } return true; })); certificateRequired = true; } var sslStream = tlsDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_certificateSelector != null) { selector = (sender, name) => { feature.HostName = name ?? string.Empty; context.Features.Set(sslStream); var cert = _certificateSelector(context, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return cert; }; } else if (_certificate != null) { // Even with a fixed certificate, we still want to capture the SNI hostname selector = (sender, name) => { feature.HostName = name ?? string.Empty; return _certificate; }; } var sslOptions = new TlsServerAuthenticationOptions { ServerCertificate = selector == null ? _certificate : null, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, }; _options.OnAuthenticateAsServer?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions.Value, cancellationTokeSource.Token); } catch (OperationCanceledException ex) { _logger?.LogWarning(2, ex, "Authentication timed out"); await sslStream.DisposeAsync(); return; } catch (Exception ex) { _logger?.LogWarning(1, ex, "Authentication failed"); await sslStream.DisposeAsync(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set(feature); feature.LocalCertificate = ConvertToX509Certificate2(sslStream.LocalCertificate); feature.RemoteCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.NegotiatedCipherSuite = sslStream.NegotiatedCipherSuite; #if NET10_0_OR_GREATER #pragma warning disable SYSLIB0058 #endif feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; #if NET10_0_OR_GREATER #pragma warning restore SYSLIB0058 #endif feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; try { context.Transport = tlsDuplexPipe; // Disposing the stream will dispose the TlsDuplexPipe await using (sslStream) await using (tlsDuplexPipe) { await _next(context); // Dispose the inner stream (TlsDuplexPipe) before disposing the SslStream // as the duplex pipe can hit an ODE as it still may be writing. } } finally { // Restore the original so that it gets closed appropriately context.Transport = originalTransport; } } protected static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate) { if (!CertificateLoader.IsCertificateAllowedForServerAuth(certificate)) { throw new InvalidOperationException($"Invalid server certificate for server authentication: {certificate.Thumbprint}"); } } private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate) { if (certificate is null) { return null; } return certificate as X509Certificate2 ?? new X509Certificate2(certificate); } } } ================================================ FILE: src/Orleans.Core/Async/AsyncExecutorWithRetries.cs ================================================ using System; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Internal { /// /// This class is a convenient utility class to execute a certain asynchronous function with retries, /// allowing to specify custom retry filters and policies. /// public static class AsyncExecutorWithRetries { /// /// Constant used to request an infinite number of retries. /// public static readonly int INFINITE_RETRIES = -1; /// /// Execute a given function a number of times, based on retry configuration parameters. /// /// /// The action to be executed. /// /// /// The maximum number of retries. /// /// /// The retry exception filter. /// /// /// The maximum execution time. /// /// /// The backoff provider. /// /// /// A representing the operation. /// public static Task ExecuteWithRetries( Func action, int maxNumErrorTries, Func retryExceptionFilter, TimeSpan maxExecutionTime, IBackoffProvider onErrorBackOff) { async Task function(int i) { await action(i); return true; } return ExecuteWithRetriesHelper( function, 0, maxNumErrorTries, maxExecutionTime, DateTime.UtcNow, null, retryExceptionFilter, null, onErrorBackOff); } /// /// Execute a given function a number of times, based on retry configuration parameters. /// /// /// The delegate to be executed. /// /// /// The maximum number of retries. /// /// /// The retry exception filter. /// /// /// The maximum execution time. /// /// /// The backoff provider. /// /// /// The value returned from the successful invocation of the provided function. /// public static Task ExecuteWithRetries( Func> function, int maxNumErrorTries, Func retryExceptionFilter, TimeSpan maxExecutionTime, IBackoffProvider onErrorBackOff, CancellationToken cancellationToken = default) { return ExecuteWithRetries( function, 0, maxNumErrorTries, null, retryExceptionFilter, maxExecutionTime, null, onErrorBackOff, cancellationToken); } /// /// Execute a given a number of times, based on retry configuration parameters. /// /// /// The underlying return type of . /// /// /// Function to execute /// /// /// Maximal number of successful execution attempts. will try to re-execute the given again if directed so by . /// Set to -1 for unlimited number of success retries, until is satisfied. Set to 0 for only one success attempt, which will cause to be /// ignored and the given executed only once until first success. /// /// /// Maximal number of execution attempts due to errors. Set to -1 for unlimited number of error retries, until is satisfied. /// /// /// Filter to indicate if successful execution should be retried. Set to to disable successful retries. /// /// /// Filter to indicate if error execution should be retried. Set to to disable error retries. /// /// /// The maximal execution time of the function. /// /// /// The backoff provider object, which determines how much to wait between success retries. /// /// /// The backoff provider object, which determines how much to wait between error retries /// /// /// The value returned from the successful invocation of . /// public static Task ExecuteWithRetries( Func> function, int maxNumSuccessTries, int maxNumErrorTries, Func retryValueFilter, Func retryExceptionFilter, TimeSpan maxExecutionTime = default, IBackoffProvider onSuccessBackOff = null, IBackoffProvider onErrorBackOff = null, CancellationToken cancellationToken = default) { return ExecuteWithRetriesHelper( function, maxNumSuccessTries, maxNumErrorTries, maxExecutionTime, DateTime.UtcNow, retryValueFilter, retryExceptionFilter, onSuccessBackOff, onErrorBackOff, cancellationToken); } /// /// Execute a given a number of times, based on retry configuration parameters. /// /// /// The underlying return type of . /// /// /// Function to execute. /// /// /// Maximal number of successful execution attempts. will try to re-execute the given again if directed so by . /// Set to -1 for unlimited number of success retries, until is satisfied. Set to 0 for only one success attempt, which will cause to be /// ignored and the given executed only once until first success. /// /// /// Maximal number of execution attempts due to errors. Set to -1 for unlimited number of error retries, until is satisfied. /// /// /// The maximal execution time of the function. /// /// /// The time at which execution was started. /// /// /// Filter to indicate if successful execution should be retried. Set to to disable successful retries. /// /// /// Filter to indicate if error execution should be retried. Set to to disable error retries. /// /// /// The backoff provider object, which determines how much to wait between success retries. /// /// /// The backoff provider object, which determines how much to wait between error retries /// /// /// The value returned from the successful invocation of . /// private static async Task ExecuteWithRetriesHelper( Func> function, int maxNumSuccessTries, int maxNumErrorTries, TimeSpan maxExecutionTime, DateTime startExecutionTime, Func retryValueFilter = null, Func retryExceptionFilter = null, IBackoffProvider onSuccessBackOff = null, IBackoffProvider onErrorBackOff = null, CancellationToken cancellationToken = default) { T result = default; ExceptionDispatchInfo lastExceptionInfo = null; bool retry; var callCounter = 0; do { retry = false; cancellationToken.ThrowIfCancellationRequested(); if (maxExecutionTime != Timeout.InfiniteTimeSpan && maxExecutionTime != default) { DateTime now = DateTime.UtcNow; if (now - startExecutionTime > maxExecutionTime) { if (lastExceptionInfo == null) { throw new TimeoutException( $"ExecuteWithRetries has exceeded its max execution time of {maxExecutionTime}. Now is {LogFormatter.PrintDate(now)}, started at {LogFormatter.PrintDate(startExecutionTime)}, passed {now - startExecutionTime}"); } lastExceptionInfo.Throw(); } } int counter = callCounter; try { callCounter++; result = await function(counter); lastExceptionInfo = null; if (callCounter < maxNumSuccessTries || maxNumSuccessTries == INFINITE_RETRIES) // -1 for infinite retries { if (retryValueFilter != null) retry = retryValueFilter(result, counter); } if (retry && !cancellationToken.IsCancellationRequested) { TimeSpan? delay = onSuccessBackOff?.Next(counter); if (delay.HasValue) { await Task.Delay(delay.Value, cancellationToken); } } } catch (Exception exc) { retry = false; if (callCounter < maxNumErrorTries || maxNumErrorTries == INFINITE_RETRIES) { if (retryExceptionFilter != null) retry = retryExceptionFilter(exc, counter); } if (!retry || cancellationToken.IsCancellationRequested) { throw; } lastExceptionInfo = ExceptionDispatchInfo.Capture(exc); TimeSpan? delay = onErrorBackOff?.Next(counter); if (delay.HasValue) { await Task.Delay(delay.Value, cancellationToken); } } } while (retry); return result; } } /// /// Functionality for determining how long to wait between successive operation attempts. /// public interface IBackoffProvider { /// /// Returns the amount of time to wait before attempting a subsequent operation. /// /// The number of operation attempts which have been made. /// The amount of time to wait before attempting a subsequent operation. TimeSpan Next(int attempt); } /// /// A fixed-duration backoff implementation, which always returns the configured delay. /// public class FixedBackoff : IBackoffProvider { private readonly TimeSpan fixedDelay; /// /// Initializes a new instance of the class. /// /// /// The fixed delay between attempts. /// public FixedBackoff(TimeSpan delay) { fixedDelay = delay; } /// public TimeSpan Next(int attempt) { return fixedDelay; } } /// /// An exponential backoff implementation, which initially returns the minimum delay it is configured /// with and exponentially increases its delay by two raised to the power of the attempt number until /// the maximum backoff delay is reached. /// internal class ExponentialBackoff : IBackoffProvider { private readonly TimeSpan minDelay; private readonly TimeSpan maxDelay; private readonly TimeSpan step; /// /// Initializes a new instance of the class. /// /// /// The minimum delay. /// /// /// The maximum delay. /// /// /// The step, which is multiplied by two raised to the power of the attempt number and added to the minimum delay to compute the delay for each iteration. /// /// /// One or more argument values are outside out of their valid range. /// public ExponentialBackoff(TimeSpan minDelay, TimeSpan maxDelay, TimeSpan step) { if (minDelay <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(minDelay), minDelay, "ExponentialBackoff min delay must be a positive number."); if (maxDelay <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(maxDelay), maxDelay, "ExponentialBackoff max delay must be a positive number."); if (step <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(step), step, "ExponentialBackoff step must be a positive number."); if (minDelay >= maxDelay) throw new ArgumentOutOfRangeException(nameof(minDelay), minDelay, "ExponentialBackoff min delay must be greater than max delay."); this.minDelay = minDelay; this.maxDelay = maxDelay; this.step = step; } /// public TimeSpan Next(int attempt) { TimeSpan currMax; try { long multiple = checked(1 << attempt); currMax = minDelay + step.Multiply(multiple); // may throw OverflowException if (currMax <= TimeSpan.Zero) { throw new OverflowException(); } } catch (OverflowException) { currMax = maxDelay; } currMax = StandardExtensions.Min(currMax, maxDelay); if (minDelay >= currMax) throw new ArgumentOutOfRangeException($"minDelay {minDelay}, currMax = {currMax}"); return RandomTimeSpan.Next(minDelay, currMax); } } } ================================================ FILE: src/Orleans.Core/Async/AsyncLock.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans { /// /// An async mutual exclusion mechanism that supports scoping via ‘using’. /// /// /// (Adapted from http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx) /// /// When programming with async, the lock keyword is problematic: /// /// lock will cause the thread to block while it waits for exclusive access to the critical section of code. /// The await keyword cannot be used within the scope of a lock construct. /// /// /// It is still useful, at times, to provide exclusive access to a critical section of code. AsyncLock provides semantics /// that correspond to that of a (non-recursive) mutex, while maintaining compatibility with the tenets of async programming. /// /// /// The following example implements some work that needs to be done under lock: /// /// class Test /// { /// private AsyncLock _initLock = new AsyncLock(); /// public async Task<int> WorkUnderLock() /// { /// using (await _initLock.LockAsync()) // analogous to lock(_initLock) /// { /// return await DoSomeWork(); /// } /// } /// } /// /// /// /// We decided to keep the implementation simple and mimic the semantics of a regular mutex as much as possible. /// 1) AsyncLock is NOT IDisposable, since we don't want to give the developer an option to erroneously manually dispose the lock /// while there may be some unreleased LockReleasers. /// 2) AsyncLock does NOT have to implement the Finalizer function. The underlying resource of SemaphoreSlim will be eventually released by the .NET, /// when SemaphoreSlim is finalized. Having finalizer for AsyncLock will not speed it up. /// 3) LockReleaser is IDisposable to implement the "using" pattern. /// 4) LockReleaser does NOT have to implement the Finalizer function. If users forget to Dispose the LockReleaser (analogous to forgetting to release a mutex) /// the AsyncLock will remain locked, which may potentially cause deadlock. This is OK, since these are the exact regular mutex semantics - if one forgets to unlock the mutex, it stays locked. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] internal class AsyncLock { private readonly SemaphoreSlim semaphore; /// /// Initializes a new instance of the class. /// public AsyncLock() { semaphore = new SemaphoreSlim(1); } /// /// Acquires the lock asynchronously. /// /// An which must be used to release the lock. public ValueTask LockAsync() { Task wait = semaphore.WaitAsync(); if (wait.IsCompletedSuccessfully) { return new(new LockReleaser(this)); } else { return LockAsyncInternal(this, wait); static async ValueTask LockAsyncInternal(AsyncLock self, Task waitTask) { await waitTask.ConfigureAwait(false); return new LockReleaser(self); } } } private class LockReleaser : IDisposable { private AsyncLock target; internal LockReleaser(AsyncLock target) { this.target = target; } public void Dispose() { if (target == null) return; // first null it, next Release, so even if Release throws, we don't hold the reference any more. AsyncLock tmp = target; target = null; try { tmp.semaphore.Release(); } catch (Exception) { } // just ignore the Exception } } } } ================================================ FILE: src/Orleans.Core/Async/AsyncSerialExecutor.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; namespace Orleans { /// /// A utility class that provides serial execution of async functions. /// In can be used inside reentrant grain code to execute some methods in a non-reentrant (serial) way. /// /// /// The underlying type returned from functions invoked by this executor. /// public class AsyncSerialExecutor { private readonly ConcurrentQueue, Func>>> actions = new ConcurrentQueue, Func>>>(); private readonly InterlockedExchangeLock locker = new InterlockedExchangeLock(); /// /// A lock which relies on /// private class InterlockedExchangeLock { private const int Locked = 1; private const int Unlocked = 0; private int lockState = Unlocked; /// /// Attempts to acquire the lock, returning if the lock was acquired and otherwise. /// /// /// if the lock was acquired and otherwise. /// public bool TryGetLock() { return Interlocked.Exchange(ref lockState, Locked) != Locked; } /// /// Releases the lock unconditionally. /// public void ReleaseLock() { Interlocked.Exchange(ref lockState, Unlocked); } } /// /// Submit the next function for execution. It will execute after all previously submitted functions have finished, without interleaving their executions. /// Returns a promise that represents the execution of this given function. /// The returned promise will be resolved when the given function is done executing. /// /// The function to schedule for invocation. /// The result of the scheduled function invocation. public Task AddNext(Func> func) { var resolver = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); actions.Enqueue(new Tuple, Func>>(resolver, func)); Task task = resolver.Task; ExecuteNext().Ignore(); return task; } /// /// Executes the next scheduled function. /// /// A representing the operation. private async Task ExecuteNext() { while (!actions.IsEmpty) { bool gotLock = false; try { if (!(gotLock = locker.TryGetLock())) { return; } while (!actions.IsEmpty) { Tuple, Func>> actionTuple; if (actions.TryDequeue(out actionTuple)) { try { TResult result = await actionTuple.Item2(); actionTuple.Item1.TrySetResult(result); } catch (Exception exc) { actionTuple.Item1.TrySetException(exc); } } } } finally { if (gotLock) locker.ReleaseLock(); } } } } /// /// A utility class that provides serial execution of async functions. /// In can be used inside reentrant grain code to execute some methods in a non-reentrant (serial) way. /// public class AsyncSerialExecutor { private readonly AsyncSerialExecutor executor = new AsyncSerialExecutor(); /// /// Submits the next function for execution. It will execute after all previously submitted functions have finished, without interleaving their executions. /// Returns a promise that represents the execution of this given function. /// The returned promise will be resolved when the given function is done executing. /// /// The function to schedule for invocation. /// The result of the scheduled function invocation. public Task AddNext(Func func) { return this.executor.AddNext(() => Wrap(func)); } private static async Task Wrap(Func func) { await func(); return true; } } } ================================================ FILE: src/Orleans.Core/Async/BatchWorker.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Orleans.Timers.Internal; #nullable enable namespace Orleans { /// /// General pattern for an asynchronous worker that performs a work task, when notified, /// to service queued work. Each work cycle handles ALL the queued work. /// If new work arrives during a work cycle, another cycle is scheduled. /// The worker never executes more than one instance of the work cycle at a time, /// and consumes no resources when idle. It uses TaskScheduler.Current /// to schedule the work cycles. /// public abstract class BatchWorker { #if NET9_0_OR_GREATER private readonly Lock lockable = new(); #else private readonly object lockable = new(); #endif private DateTime? scheduledNotify; // Task for the current work cycle, or null if idle private Task? currentWorkCycle; // Flag is set to indicate that more work has arrived during execution of the task private bool moreWork; // Used to communicate the task for the next work cycle to waiters. // This value is non-null only if there are waiters. private TaskCompletionSource? nextWorkCyclePromise; private Task? nextWorkCycle; /// Implement this member in derived classes to define what constitutes a work cycle /// > /// A /// protected abstract Task Work(); /// /// Gets or sets the cancellation used to cancel this batch worker. /// protected CancellationToken CancellationToken { get; set; } /// /// Notify the worker that there is more work. /// public void Notify() { lock (lockable) { if (currentWorkCycle != null) { // lets the current work cycle know that there is more work moreWork = true; } else { // start a work cycle Start(); } } } /// /// Instructs the batch worker to run again to check for work, if /// it has not run again already by then, at specified . /// /// public void Notify(DateTime utcTime) { var now = DateTime.UtcNow; if (now >= utcTime) { Notify(); } else { lock (lockable) { if (!scheduledNotify.HasValue || scheduledNotify.Value > utcTime) { scheduledNotify = utcTime; ScheduleNotify(utcTime, now).Ignore(); } } } } private async Task ScheduleNotify(DateTime time, DateTime now) { await TimerManager.Delay(time - now, this.CancellationToken); if (scheduledNotify == time) { Notify(); } } private Task Start() { // Clear any scheduled runs scheduledNotify = null; // Queue a task that is doing the work var task = Task.Factory.StartNew(s => ((BatchWorker)s!).Work(), this, default, default, TaskScheduler.Current).Unwrap(); currentWorkCycle = task; // chain a continuation that checks for more work, on the same scheduler task.ContinueWith((_, s) => ((BatchWorker)s!).CheckForMoreWork(), this); return task; } /// /// Executes at the end of each work cycle on the same task scheduler. /// private void CheckForMoreWork() { TaskCompletionSource? signal; Task taskToSignal; lock (lockable) { if (moreWork) { moreWork = false; // see if someone created a promise for waiting for the next work cycle // if so, take it and remove it signal = this.nextWorkCyclePromise; this.nextWorkCyclePromise = null; this.nextWorkCycle = null; // start the next work cycle taskToSignal = Start(); } else { currentWorkCycle = null; return; } } // to be safe, must do the signalling out here so it is not under the lock signal?.SetResult(taskToSignal); } /// /// Check if this worker is idle. /// public bool IsIdle() => currentWorkCycle == null; /// /// Wait for the current work cycle, and also the next work cycle if there is currently unserviced work. /// public Task WaitForCurrentWorkToBeServiced() { // Figure out exactly what we need to wait for lock (lockable) { if (!moreWork) { // Just wait for current work cycle return currentWorkCycle ?? Task.CompletedTask; } else { // we need to wait for the next work cycle // but that task does not exist yet, so we use a promise that signals when the next work cycle is launched return nextWorkCycle ?? CreateNextWorkCyclePromise(); } } } private Task CreateNextWorkCyclePromise() { // it's OK to run any continuations synchrnously because this promise only gets signaled at the very end of CheckForMoreWork nextWorkCyclePromise = new TaskCompletionSource(); return nextWorkCycle = nextWorkCyclePromise.Task.Unwrap(); } /// /// Notify the worker that there is more work, and wait for the current work cycle, and also the next work cycle if there is currently unserviced work. /// public Task NotifyAndWaitForWorkToBeServiced() { lock (lockable) { if (currentWorkCycle != null) { moreWork = true; return nextWorkCycle ?? CreateNextWorkCyclePromise(); } else { return Start(); } } } } /// /// A implementation which executes a provided delegate as its implementation. /// public class BatchWorkerFromDelegate : BatchWorker { private readonly Func work; /// /// Initializes a new instance. /// /// The delegate to invoke when is invoked. /// The cancellation token used to stop the worker. public BatchWorkerFromDelegate(Func work, CancellationToken cancellationToken = default) { this.work = work; this.CancellationToken = cancellationToken; } /// protected override Task Work() { return work(); } } } ================================================ FILE: src/Orleans.Core/Async/MultiTaskCompletionSource.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans; /// /// An alternative to which completes only once a specified number of signals have been received. /// internal sealed class MultiTaskCompletionSource { private readonly TaskCompletionSource _tcs; private int _count; /// /// Initializes a new instance of the class. /// /// /// The number of signals which must occur before this completion source completes. /// /// /// The count value is less than or equal to zero. /// public MultiTaskCompletionSource(int count) { ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(count, 0); _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _count = count; } /// /// Gets the task which is completed when a sufficient number of signals are received. /// public Task Task => _tcs.Task; /// /// Signals this instance. /// /// This method was called more times than the initially specified count argument allows. public void SetOneResult() { var current = Interlocked.Decrement(ref _count); if (current < 0) { throw new InvalidOperationException( "SetOneResult was called more times than initially specified by the count argument."); } if (current == 0) { _tcs.SetResult(); } } } ================================================ FILE: src/Orleans.Core/Async/TaskExtensions.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Internal { /// /// Extensions for working with and . /// internal static class OrleansTaskExtentions { public static ConfiguredTaskAwaitable SuppressThrowing(this ValueTask task) => task.AsTask().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); public static ConfiguredTaskAwaitable SuppressThrowing(this Task task) => task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); public static void Ignore(this ValueTask valueTask) { if (valueTask.IsCompletedSuccessfully) { valueTask.GetAwaiter().GetResult(); } else { valueTask.AsTask().Ignore(); } } public static async Task LogException(this Task task, ILogger logger, ErrorCode errorCode, string message) { try { await task; } catch (Exception exc) { // TODO: pending on https://github.com/dotnet/runtime/issues/110570 logger.LogError((int)errorCode, exc, "{Message}", message); throw; } } // Executes an async function such as Exception is never thrown but rather always returned as a broken task. public static async Task SafeExecute(Func action) { await action(); } public static async Task ExecuteAndIgnoreException(Func action) { try { await action(); } catch (Exception) { // dont re-throw, just eat it. } } internal static string ToString(this Task t) => t == null ? "null" : $"[Id={t.Id}, Status={t.Status}]"; public static void WaitWithThrow(this Task task, TimeSpan timeout) { if (!task.Wait(timeout)) { throw new TimeoutException($"Task.WaitWithThrow has timed out after {timeout}."); } } internal static T WaitForResultWithThrow(this Task task, TimeSpan timeout) { if (!task.Wait(timeout)) { throw new TimeoutException($"Task.WaitForResultWithThrow has timed out after {timeout}."); } return task.Result; } /// /// This will apply a timeout delay to the task, allowing us to exit early /// /// The task we will timeout after timeSpan /// Amount of time to wait before timing out /// Text to put into the timeout exception message /// If we time out we will get this exception /// The completed task public static async Task WithTimeout(this Task taskToComplete, TimeSpan timeout, string exceptionMessage = null) { if (taskToComplete.IsCompleted) { await taskToComplete; return; } using var timeoutCancellationTokenSource = new CancellationTokenSource(); var completedTask = await Task.WhenAny(taskToComplete, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); // We got done before the timeout, or were able to complete before this code ran, return the result if (taskToComplete == completedTask) { timeoutCancellationTokenSource.Cancel(); // Await this so as to propagate the exception correctly await taskToComplete; return; } // We did not complete before the timeout, we fire and forget to ensure we observe any exceptions that may occur taskToComplete.Ignore(); var errorMessage = exceptionMessage ?? $"WithTimeout has timed out after {timeout}"; throw new TimeoutException(errorMessage); } /// /// This will apply a timeout delay to the task, allowing us to exit early /// /// The task we will timeout after timeSpan /// Amount of time to wait before timing out /// Text to put into the timeout exception message /// If we time out we will get this exception /// If we time out we will get this exception /// The value of the completed task public static async Task WithTimeout(this Task taskToComplete, TimeSpan timeSpan, string exceptionMessage = null) { if (taskToComplete.IsCompleted) { return await taskToComplete; } using var timeoutCancellationTokenSource = new CancellationTokenSource(); var completedTask = await Task.WhenAny(taskToComplete, Task.Delay(timeSpan, timeoutCancellationTokenSource.Token)); // We got done before the timeout, or were able to complete before this code ran, return the result if (taskToComplete == completedTask) { timeoutCancellationTokenSource.Cancel(); // Await this so as to propagate the exception correctly return await taskToComplete; } // We did not complete before the timeout, we fire and forget to ensure we observe any exceptions that may occur taskToComplete.Ignore(); var errorMessage = exceptionMessage ?? $"WithTimeout has timed out after {timeSpan}"; throw new TimeoutException(errorMessage); } /// /// For making an uncancellable task cancellable, by ignoring its result. /// /// The task to wait for unless cancelled /// Message to set in the exception /// A cancellation token for cancelling the wait /// internal static async Task WithCancellation( this Task taskToComplete, string message, CancellationToken cancellationToken) { try { await taskToComplete.WithCancellation(cancellationToken); } catch (TaskCanceledException ex) { throw new TaskCanceledException(message, ex); } } /// /// For making an uncancellable task cancellable, by ignoring its result. /// /// The task to wait for unless cancelled /// A cancellation token for cancelling the wait /// internal static Task WithCancellation(this Task taskToComplete, CancellationToken cancellationToken) { if (taskToComplete.IsCompleted || !cancellationToken.CanBeCanceled) { return taskToComplete; } else if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } else { return MakeCancellable(taskToComplete, cancellationToken); } } private static async Task MakeCancellable(Task task, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken), useSynchronizationContext: false)) { var firstToComplete = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false); if (firstToComplete != task) { task.Ignore(); } await firstToComplete.ConfigureAwait(false); } } internal static Task WrapInTask(Action action) { try { action(); return Task.CompletedTask; } catch (Exception exc) { return Task.FromException(exc); } } //The rationale for GetAwaiter().GetResult() instead of .Result //is presented at https://github.com/aspnet/Security/issues/59. internal static T GetResult(this Task task) { return task.GetAwaiter().GetResult(); } internal static Task WhenCancelled(this CancellationToken token) { if (token.IsCancellationRequested) { return Task.CompletedTask; } var waitForCancellation = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); token.Register(obj => { var tcs = (TaskCompletionSource)obj; tcs.TrySetResult(null); }, waitForCancellation); return waitForCancellation.Task; } } } ================================================ FILE: src/Orleans.Core/Caching/ConcurrentLruCache.cs ================================================ #nullable enable using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Orleans.Caching.Internal; namespace Orleans.Caching; /// /// A pseudo LRU based on the TU-Q eviction policy. The LRU list is composed of 3 segments: hot, warm and cold. /// Cost of maintaining segments is amortized across requests. Items are only cycled when capacity is exceeded. /// Pure read does not cycle items if all segments are within capacity constraints. There are no global locks. /// On cache miss, a new item is added. Tail items in each segment are dequeued, examined, and are either enqueued /// or discarded. /// The TU-Q scheme of hot, warm and cold is similar to that used in MemCached (https://memcached.org/blog/modern-lru/) /// and OpenBSD (https://flak.tedunangst.com/post/2Q-buffer-cache-algorithm), but does not use a background thread /// to maintain the internal queues. /// /// /// This implementation is derived from BitFaster.Caching (https://github.com/bitfaster/BitFaster.Caching), removing /// functionality that is not needed for Orleans (async, custom policies), to reduce the number of source files. /// /// Each segment has a capacity. When segment capacity is exceeded, items are moved as follows: /// /// New items are added to hot, WasAccessed = false. /// When items are accessed, update WasAccessed = true. /// When items are moved WasAccessed is set to false. /// When hot is full, hot tail is moved to either Warm or Cold depending on WasAccessed. /// When warm is full, warm tail is moved to warm head or cold depending on WasAccessed. /// When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed. /// /// /// /// Initializes a new instance of the ConcurrentLruCore class with the specified concurrencyLevel, capacity, equality comparer, item policy and telemetry policy. /// /// The capacity. /// The equality comparer. internal class ConcurrentLruCache( int capacity, IEqualityComparer? comparer) : IEnumerable>, ICacheMetrics, ConcurrentLruCache.ITestAccessor where K : notnull { private readonly ConcurrentDictionary _dictionary = new(concurrencyLevel: -1, capacity: capacity, comparer: comparer); private readonly ConcurrentQueue _hotQueue = new(); private readonly ConcurrentQueue _warmQueue = new(); private readonly ConcurrentQueue _coldQueue = new(); private readonly CapacityPartition _capacity = new(capacity); private readonly TelemetryPolicy _telemetryPolicy = new(); // maintain count outside ConcurrentQueue, since ConcurrentQueue.Count holds a global lock private PaddedQueueCount _counter; private bool _isWarm; /// /// Initializes a new instance of the ConcurrentLruCore class with the specified capacity. /// /// The capacity. public ConcurrentLruCache(int capacity) : this(capacity, comparer: null) { } // No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/ /// public int Count => _dictionary.Where(_ => true).Count(); /// public int Capacity => _capacity.Hot + _capacity.Warm + _capacity.Cold; /// public ICacheMetrics Metrics => this; /// /// Gets the number of hot items. /// public int HotCount => Volatile.Read(ref _counter.Hot); /// /// Gets the number of warm items. /// public int WarmCount => Volatile.Read(ref _counter.Warm); /// /// Gets the number of cold items. /// public int ColdCount => Volatile.Read(ref _counter.Cold); /// /// Gets a collection containing the keys in the cache. /// public ICollection Keys => _dictionary.Keys; /// Returns an enumerator that iterates through the cache. /// An enumerator for the cache. /// /// The enumerator returned from the cache is safe to use concurrently with /// reads and writes, however it does not represent a moment-in-time snapshot. /// The contents exposed through the enumerator may contain modifications /// made after was called. /// public IEnumerator> GetEnumerator() { foreach (var kvp in _dictionary) { yield return new KeyValuePair(kvp.Key, kvp.Value.Value); } } /// public V Get(K key) { if (!TryGet(key, out var value)) { throw new KeyNotFoundException($"Key '{key}' not found in the cache."); } return value; } /// public bool TryGet(K key, [MaybeNullWhen(false)] out V value) { if (_dictionary.TryGetValue(key, out var item)) { value = item.Value; item.MarkAccessed(); _telemetryPolicy.IncrementHit(); return true; } value = default; _telemetryPolicy.IncrementMiss(); return false; } public bool TryAdd(K key, V value) { var newItem = new LruItem(key, value); if (_dictionary.TryAdd(key, newItem)) { _hotQueue.Enqueue(newItem); Cycle(Interlocked.Increment(ref _counter.Hot)); return true; } DisposeValue(newItem.Value); return false; } /// public V GetOrAdd(K key, Func valueFactory) { while (true) { if (TryGet(key, out var value)) { return value; } // The value factory may be called concurrently for the same key, but the first write to the dictionary wins. value = valueFactory(key); if (TryAdd(key, value)) { return value; } } } /// /// Adds a key/value pair to the cache if the key does not already exist. Returns the new value, or the /// existing value if the key already exists. /// /// The type of an argument to pass into valueFactory. /// The key of the element to add. /// The factory function used to generate a value for the key. /// An argument value to pass into valueFactory. /// The value for the key. This will be either the existing value for the key if the key is already /// in the cache, or the new value if the key was not in the cache. public V GetOrAdd(K key, Func valueFactory, TArg factoryArgument) { while (true) { if (TryGet(key, out var value)) { return value; } // The value factory may be called concurrently for the same key, but the first write to the dictionary wins. value = valueFactory(key, factoryArgument); if (TryAdd(key, value)) { return value; } } } /// /// Attempts to remove the specified key value pair. /// /// The predicate used to determine if the item should be removed. /// Argument passed to the predicate. /// true if the item was removed successfully; otherwise, false. public bool TryRemove(K key, Func predicate, TArg predicateArgument) { if (_dictionary.TryGetValue(key, out var existing)) { lock (existing) { if (predicate(existing.Value, predicateArgument)) { var kvp = new KeyValuePair(key, existing); if (_dictionary.TryRemove(kvp)) { OnRemove(kvp.Value, ItemRemovedReason.Removed); return true; } } } // it existed, but we couldn't remove - this means value was replaced after the TryGetValue (a race) } return false; } /// /// Attempts to remove the specified key value pair. /// /// The item to remove. /// true if the item was removed successfully; otherwise, false. public bool TryRemove(KeyValuePair item) { if (_dictionary.TryGetValue(item.Key, out var existing)) { lock (existing) { if (EqualityComparer.Default.Equals(existing.Value, item.Value)) { var kvp = new KeyValuePair(item.Key, existing); if (_dictionary.TryRemove(kvp)) { OnRemove(kvp.Value, ItemRemovedReason.Removed); return true; } } } // it existed, but we couldn't remove - this means value was replaced after the TryGetValue (a race) } return false; } /// /// Attempts to remove and return the value that has the specified key. /// /// The key of the element to remove. /// When this method returns, contains the object removed, or the default value of the value type if key does not exist. /// true if the object was removed successfully; otherwise, false. public bool TryRemove(K key, [MaybeNullWhen(false)] out V value) { if (_dictionary.TryRemove(key, out var item)) { OnRemove(item, ItemRemovedReason.Removed); value = item.Value; return true; } value = default; return false; } /// public bool TryRemove(K key) => TryRemove(key, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OnRemove(LruItem item, ItemRemovedReason reason) { // Mark as not accessed, it will later be cycled out of the queues because it can never be fetched // from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled // from the queue. item.WasAccessed = false; item.WasRemoved = true; if (reason == ItemRemovedReason.Evicted) { _telemetryPolicy.IncrementEvicted(); } // serialize dispose (common case dispose not thread safe) lock (item) { DisposeValue(item.Value); } } /// ///Note: Calling this method does not affect LRU order. public bool TryUpdate(K key, V value) { if (_dictionary.TryGetValue(key, out var existing)) { lock (existing) { if (!existing.WasRemoved) { var oldValue = existing.Value; existing.Value = value; _telemetryPolicy.IncrementUpdated(); DisposeValue(oldValue); return true; } } } return false; } /// ///Note: Updates to existing items do not affect LRU order. Added items are at the top of the LRU. public void AddOrUpdate(K key, V value) { while (true) { // first, try to update if (TryUpdate(key, value)) { return; } // then try add var newItem = new LruItem(key, value); if (_dictionary.TryAdd(key, newItem)) { _hotQueue.Enqueue(newItem); Cycle(Interlocked.Increment(ref _counter.Hot)); return; } // if both update and add failed there was a race, try again } } /// public void Clear() { // don't overlap Clear/Trim/TrimExpired lock (_dictionary) { // evaluate queue count, remove everything including items removed from the dictionary but // not the queues. This also avoids the expensive o(n) no lock count, or locking the dictionary. var queueCount = HotCount + WarmCount + ColdCount; TrimLiveItems(queueCount, ItemRemovedReason.Cleared); } } /// /// Trim the specified number of items from the cache. Removes items in LRU order. /// /// The number of items to remove. /// The number of items removed from the cache. /// is less than 0./ /// is greater than capacity./ /// /// Note: Trim affects LRU order. Calling Trim resets the internal accessed status of items. /// public void Trim(int itemCount) { var capacity = Capacity; ArgumentOutOfRangeException.ThrowIfLessThan(itemCount, 1); ArgumentOutOfRangeException.ThrowIfGreaterThan(itemCount, capacity); // clamp itemCount to number of items actually in the cache itemCount = Math.Min(itemCount, HotCount + WarmCount + ColdCount); // don't overlap Clear/Trim/TrimExpired lock (_dictionary) { TrimLiveItems(itemCount, ItemRemovedReason.Trimmed); } } private void TrimLiveItems(int itemCount, ItemRemovedReason reason) { // When items are touched, they are moved to warm by cycling. Therefore, to guarantee // that we can remove itemCount items, we must cycle (2 * capacity.Warm) + capacity.Hot times. // If clear is called during trimming, it would be possible to get stuck in an infinite // loop here. The warm + hot limit also guards against this case. var trimWarmAttempts = 0; var itemsRemoved = 0; var maxWarmHotAttempts = _capacity.Warm * 2 + _capacity.Hot; while (itemsRemoved < itemCount && trimWarmAttempts < maxWarmHotAttempts) { if (Volatile.Read(ref _counter.Cold) > 0) { if (TryRemoveCold(reason) == (ItemDestination.Remove, 0)) { itemsRemoved++; trimWarmAttempts = 0; } else { TrimWarmOrHot(reason); } } else { TrimWarmOrHot(reason); trimWarmAttempts++; } } if (Volatile.Read(ref _counter.Warm) < _capacity.Warm) { Volatile.Write(ref _isWarm, false); } } private void TrimWarmOrHot(ItemRemovedReason reason) { if (Volatile.Read(ref _counter.Warm) > 0) { CycleWarmUnchecked(reason); } else if (Volatile.Read(ref _counter.Hot) > 0) { CycleHotUnchecked(reason); } } private void Cycle(int hotCount) { if (_isWarm) { (var dest, var count) = CycleHot(hotCount); var cycles = 0; while (cycles++ < 3 && dest != ItemDestination.Remove) { if (dest == ItemDestination.Warm) { (dest, count) = CycleWarm(count); } else if (dest == ItemDestination.Cold) { (dest, count) = CycleCold(count); } } // If nothing was removed yet, constrain the size of warm and cold by discarding the coldest item. if (dest != ItemDestination.Remove) { if (dest == ItemDestination.Warm && count > _capacity.Warm) { count = LastWarmToCold(); } ConstrainCold(count, ItemRemovedReason.Evicted); } } else { // fill up the warm queue with new items until warm is full. // else during warmup the cache will only use the hot + cold queues until any item is requested twice. CycleDuringWarmup(hotCount); } } [MethodImpl(MethodImplOptions.NoInlining)] private void CycleDuringWarmup(int hotCount) { // do nothing until hot is full if (hotCount > _capacity.Hot) { Interlocked.Decrement(ref _counter.Hot); if (_hotQueue.TryDequeue(out var item)) { // special case: removed during warmup if (item.WasRemoved) { return; } var count = Move(item, ItemDestination.Warm, ItemRemovedReason.Evicted); // if warm is now full, overflow to cold and mark as warm if (count > _capacity.Warm) { Volatile.Write(ref _isWarm, true); count = LastWarmToCold(); ConstrainCold(count, ItemRemovedReason.Evicted); } } else { Interlocked.Increment(ref _counter.Hot); } } } private (ItemDestination, int) CycleHot(int hotCount) { if (hotCount > _capacity.Hot) { return CycleHotUnchecked(ItemRemovedReason.Evicted); } return (ItemDestination.Remove, 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private (ItemDestination, int) CycleHotUnchecked(ItemRemovedReason removedReason) { Interlocked.Decrement(ref _counter.Hot); if (_hotQueue.TryDequeue(out var item)) { var where = RouteHot(item); return (where, Move(item, where, removedReason)); } else { Interlocked.Increment(ref _counter.Hot); return (ItemDestination.Remove, 0); } } private (ItemDestination, int) CycleWarm(int count) { if (count > _capacity.Warm) { return CycleWarmUnchecked(ItemRemovedReason.Evicted); } return (ItemDestination.Remove, 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private (ItemDestination, int) CycleWarmUnchecked(ItemRemovedReason removedReason) { var wc = Interlocked.Decrement(ref _counter.Warm); if (_warmQueue.TryDequeue(out var item)) { if (item.WasRemoved) { return (ItemDestination.Remove, 0); } var where = RouteWarm(item); // When the warm queue is full, we allow an overflow of 1 item before redirecting warm items to cold. // This only happens when hit rate is high, in which case we can consider all items relatively equal in // terms of which was least recently used. if (where == ItemDestination.Warm && wc <= _capacity.Warm) { return (ItemDestination.Warm, Move(item, where, removedReason)); } else { return (ItemDestination.Cold, Move(item, ItemDestination.Cold, removedReason)); } } else { Interlocked.Increment(ref _counter.Warm); return (ItemDestination.Remove, 0); } } private (ItemDestination, int) CycleCold(int count) { if (count > _capacity.Cold) { return TryRemoveCold(ItemRemovedReason.Evicted); } return (ItemDestination.Remove, 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private (ItemDestination, int) TryRemoveCold(ItemRemovedReason removedReason) { Interlocked.Decrement(ref _counter.Cold); if (_coldQueue.TryDequeue(out var item)) { var where = RouteCold(item); if (where == ItemDestination.Warm && Volatile.Read(ref _counter.Warm) <= _capacity.Warm) { return (ItemDestination.Warm, Move(item, where, removedReason)); } else { Move(item, ItemDestination.Remove, removedReason); return (ItemDestination.Remove, 0); } } else { return (ItemDestination.Cold, Interlocked.Increment(ref _counter.Cold)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int LastWarmToCold() { Interlocked.Decrement(ref _counter.Warm); if (_warmQueue.TryDequeue(out var item)) { var destination = item.WasRemoved ? ItemDestination.Remove : ItemDestination.Cold; return Move(item, destination, ItemRemovedReason.Evicted); } else { Interlocked.Increment(ref _counter.Warm); return 0; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ConstrainCold(int coldCount, ItemRemovedReason removedReason) { if (coldCount > _capacity.Cold && _coldQueue.TryDequeue(out var item)) { Interlocked.Decrement(ref _counter.Cold); Move(item, ItemDestination.Remove, removedReason); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int Move(LruItem item, ItemDestination where, ItemRemovedReason removedReason) { item.WasAccessed = false; switch (where) { case ItemDestination.Warm: _warmQueue.Enqueue(item); return Interlocked.Increment(ref _counter.Warm); case ItemDestination.Cold: _coldQueue.Enqueue(item); return Interlocked.Increment(ref _counter.Cold); case ItemDestination.Remove: var kvp = new KeyValuePair(item.Key, item); if (_dictionary.TryRemove(kvp)) { OnRemove(item, removedReason); } break; } return 0; } /// Returns an enumerator that iterates through the cache. /// An enumerator for the cache. /// /// The enumerator returned from the cache is safe to use concurrently with /// reads and writes, however it does not represent a moment-in-time snapshot. /// The contents exposed through the enumerator may contain modifications /// made after was called. /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #if DEBUG /// /// Format the LRU as a string by converting all the keys to strings. /// /// The LRU formatted as a string. internal string FormatLruString() { var sb = new System.Text.StringBuilder(); sb.Append("Hot ["); sb.Append(string.Join(",", _hotQueue.Select(n => n.Key.ToString()))); sb.Append("] Warm ["); sb.Append(string.Join(",", _warmQueue.Select(n => n.Key.ToString()))); sb.Append("] Cold ["); sb.Append(string.Join(",", _coldQueue.Select(n => n.Key.ToString()))); sb.Append(']'); return sb.ToString(); } #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ItemDestination RouteHot(LruItem item) { if (item.WasRemoved) { return ItemDestination.Remove; } if (item.WasAccessed) { return ItemDestination.Warm; } return ItemDestination.Cold; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ItemDestination RouteWarm(LruItem item) { if (item.WasRemoved) { return ItemDestination.Remove; } if (item.WasAccessed) { return ItemDestination.Warm; } return ItemDestination.Cold; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ItemDestination RouteCold(LruItem item) { if (item.WasAccessed & !item.WasRemoved) { return ItemDestination.Warm; } return ItemDestination.Remove; } double ICacheMetrics.HitRatio => _telemetryPolicy.HitRatio; long ICacheMetrics.Total => _telemetryPolicy.Total; long ICacheMetrics.Hits => _telemetryPolicy.Hits; long ICacheMetrics.Misses => _telemetryPolicy.Misses; long ICacheMetrics.Evicted => _telemetryPolicy.Evicted; long ICacheMetrics.Updated => _telemetryPolicy.Updated; ConcurrentQueue ITestAccessor.HotQueue => _hotQueue; ConcurrentQueue ITestAccessor.WarmQueue => _warmQueue; ConcurrentQueue ITestAccessor.ColdQueue => _coldQueue; ConcurrentDictionary ITestAccessor.Dictionary => _dictionary; bool ITestAccessor.IsWarm => _isWarm; /// /// Represents an LRU item. /// /// /// Initializes a new instance of the LruItem class with the specified key and value. /// /// The key. /// The value. // NOTE: Internal for testing [DebuggerDisplay("[{Key}] = {Value}")] internal sealed class LruItem(K key, V value) { private V _data = value; // only used when V is a non-atomic value type to prevent torn reads private int _sequence; /// /// Gets the key. /// public readonly K Key = key; /// /// Gets or sets the value. /// public V Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (TypeProps.IsWriteAtomic) { return _data; } else { return SeqLockRead(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { if (TypeProps.IsWriteAtomic) { _data = value; } else { SeqLockWrite(value); } } } /// /// Gets or sets a value indicating whether the item was accessed. /// public bool WasAccessed { get; set; } /// /// Gets or sets a value indicating whether the item was removed. /// public bool WasRemoved { get; set; } /// /// Marks the item as accessed, if it was not already accessed. /// public void MarkAccessed() { if (!WasAccessed) { WasAccessed = true; } } internal V SeqLockRead() { var spin = new SpinWait(); while (true) { var start = Volatile.Read(ref _sequence); if ((start & 1) == 1) { // A write is in progress, spin. spin.SpinOnce(); continue; } var copy = _data; var end = Volatile.Read(ref _sequence); if (start == end) { return copy; } } } // Note: LruItem should be locked while invoking this method. Multiple writer threads are not supported. internal void SeqLockWrite(V value) { Interlocked.Increment(ref _sequence); _data = value; Interlocked.Increment(ref _sequence); } } /// /// Represents a telemetry policy with counters and events. /// [DebuggerDisplay("Hit = {Hits}, Miss = {Misses}, Upd = {Updated}, Evict = {Evicted}")] internal readonly struct TelemetryPolicy { private readonly Counter _hitCount = new(); private readonly Counter _missCount = new(); private readonly Counter _evictedCount = new(); private readonly Counter _updatedCount = new(); public TelemetryPolicy() { } /// public readonly double HitRatio => Total == 0 ? 0 : Hits / (double)Total; /// public readonly long Total => _hitCount.Count() + _missCount.Count(); /// public readonly long Hits => _hitCount.Count(); /// public readonly long Misses => _missCount.Count(); /// public readonly long Evicted => _evictedCount.Count(); /// public readonly long Updated => _updatedCount.Count(); /// public readonly void IncrementMiss() => _missCount.Increment(); /// public readonly void IncrementHit() => _hitCount.Increment(); /// public readonly void IncrementEvicted() => _evictedCount.Increment(); /// public readonly void IncrementUpdated() => _updatedCount.Increment(); } private enum ItemDestination { Warm, Cold, Remove } private enum ItemRemovedReason { Removed, Evicted, Cleared, Trimmed, } internal interface ITestAccessor { public ConcurrentQueue HotQueue { get; } public ConcurrentQueue WarmQueue { get; } public ConcurrentQueue ColdQueue { get; } public ConcurrentDictionary Dictionary { get; } public bool IsWarm { get; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void DisposeValue(V value) { if (value is IDisposable d) { d.Dispose(); } } } ================================================ FILE: src/Orleans.Core/Caching/Internal/CacheDebugView.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Orleans.Caching.Internal; // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/CacheDebugView.cs [ExcludeFromCodeCoverage] internal sealed class CacheDebugView where K : notnull { private readonly ConcurrentLruCache _cache; public CacheDebugView(ConcurrentLruCache cache) { ArgumentNullException.ThrowIfNull(cache); _cache = cache; } public KeyValuePair[] Items { get { var items = new KeyValuePair[_cache.Count]; var index = 0; foreach (var kvp in _cache) { items[index++] = kvp; } return items; } } public ICacheMetrics? Metrics => _cache.Metrics; } ================================================ FILE: src/Orleans.Core/Caching/Internal/CapacityPartition.cs ================================================ using System; using System.Diagnostics; namespace Orleans.Caching.Internal; /// /// A capacity partitioning scheme that favors frequently accessed items by allocating 80% /// capacity to the warm queue. /// // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Lru/FavorWarmPartition.cs [DebuggerDisplay("{Hot}/{Warm}/{Cold}")] internal readonly struct CapacityPartition { /// /// Default to 80% capacity allocated to warm queue, 20% split equally for hot and cold. /// This favors frequently accessed items. /// public const double DefaultWarmRatio = 0.8; /// /// Initializes a new instance of the CapacityPartition class with the specified capacity and the default warm ratio. /// /// The total capacity. public CapacityPartition(int totalCapacity) : this(totalCapacity, DefaultWarmRatio) { } /// /// Initializes a new instance of the CapacityPartition class with the specified capacity and warm ratio. /// /// The total capacity. /// The ratio of warm items to hot and cold items. public CapacityPartition(int totalCapacity, double warmRatio) { ArgumentOutOfRangeException.ThrowIfLessThan(totalCapacity, 3); ArgumentOutOfRangeException.ThrowIfLessThan(warmRatio, 0.0); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(warmRatio, 1.0); var (hot, warm, cold) = ComputeQueueCapacity(totalCapacity, warmRatio); Debug.Assert(cold >= 1); Debug.Assert(warm >= 1); Debug.Assert(hot >= 1); Hot = hot; Warm = warm; Cold = cold; } public int Cold { get; } public int Warm { get; } public int Hot { get; } private static (int hot, int warm, int cold) ComputeQueueCapacity(int capacity, double warmRatio) { var warm2 = (int)(capacity * warmRatio); var hot2 = (capacity - warm2) / 2; if (hot2 < 1) { hot2 = 1; } var cold2 = hot2; var overflow = warm2 + hot2 + cold2 - capacity; warm2 -= overflow; return (hot2, warm2, cold2); } } ================================================ FILE: src/Orleans.Core/Caching/Internal/Counter.cs ================================================ #nullable enable /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ using Orleans; namespace Orleans.Caching.Internal; /// /// A thread-safe counter suitable for high throughput counting across many concurrent threads. /// /// Based on the LongAdder class by Doug Lea. // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Counters/Counter.cs internal sealed class Counter : Striped64 { /// /// Creates a new Counter with an initial sum of zero. /// public Counter() { } /// /// Computes the current count. /// /// The current sum. public long Count() { var @as = cells; Cell a; var sum = @base.VolatileRead(); if (@as != null) { for (var i = 0; i < @as.Length; ++i) { if ((a = @as[i]) != null) sum += a.Value.VolatileRead(); } } return sum; } /// /// Increment by 1. /// public void Increment() { Add(1L); } /// /// Adds the specified value. /// /// The value to add. public void Add(long value) { Cell[]? @as; long b, v; int m; Cell a; if ((@as = cells) != null || !@base.CompareAndSwap(b = @base.VolatileRead(), b + value)) { var uncontended = true; if (@as == null || (m = @as.Length - 1) < 0 || (a = @as[GetProbe() & m]) == null || !(uncontended = a.Value.CompareAndSwap(v = a.Value.VolatileRead(), v + value))) { LongAccumulate(value, uncontended); } } } } ================================================ FILE: src/Orleans.Core/Caching/Internal/ICacheMetrics.cs ================================================ namespace Orleans.Caching.Internal; /// /// Represents cache metrics collected over the lifetime of the cache. /// If metrics are disabled. /// // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/ICacheMetrics.cs?plain=1#L8C22-L8C35 internal interface ICacheMetrics { /// /// Gets the ratio of hits to misses, where a value of 1 indicates 100% hits. /// double HitRatio { get; } /// /// Gets the total number of requests made to the cache. /// long Total { get; } /// /// Gets the total number of cache hits. /// long Hits { get; } /// /// Gets the total number of cache misses. /// long Misses { get; } /// /// Gets the total number of evicted items. /// long Evicted { get; } /// /// Gets the total number of updated items. /// long Updated { get; } } ================================================ FILE: src/Orleans.Core/Caching/Internal/PaddedLong.cs ================================================ using System.Runtime.InteropServices; using System.Threading; namespace Orleans.Caching.Internal; /// /// A long value padded by the size of a CPU cache line to mitigate false sharing. /// // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Counters/PaddedLong.cs [StructLayout(LayoutKind.Explicit, Size = 2 * Padding.CACHE_LINE_SIZE)] // padding before/between/after fields internal struct PaddedLong { /// /// The value. /// [FieldOffset(Padding.CACHE_LINE_SIZE)] public long Value; /// /// Reads the value of the field, and on systems that require it inserts a memory barrier to /// prevent reordering of memory operations. /// /// The value that was read. public long VolatileRead() => Volatile.Read(ref Value); /// /// Compares the current value with an expected value, if they are equal replaces the current value. /// /// The expected value. /// The updated value. /// True if the value is updated, otherwise false. public bool CompareAndSwap(long expected, long updated) => Interlocked.CompareExchange(ref Value, updated, expected) == expected; } ================================================ FILE: src/Orleans.Core/Caching/Internal/PaddedQueueCount.cs ================================================ using System.Diagnostics; using System.Runtime.InteropServices; namespace Orleans.Caching.Internal; // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Lru/PaddedQueueCount.cs [DebuggerDisplay("Hot = {Hot}, Warm = {Warm}, Cold = {Cold}")] [StructLayout(LayoutKind.Explicit, Size = 4 * Padding.CACHE_LINE_SIZE)] // padding before/between/after fields internal struct PaddedQueueCount { [FieldOffset(1 * Padding.CACHE_LINE_SIZE)] public int Hot; [FieldOffset(2 * Padding.CACHE_LINE_SIZE)] public int Warm; [FieldOffset(3 * Padding.CACHE_LINE_SIZE)] public int Cold; } ================================================ FILE: src/Orleans.Core/Caching/Internal/Padding.cs ================================================ namespace Orleans.Caching.Internal; // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Padding.cs internal static class Padding { #if TARGET_ARM64 || TARGET_LOONGARCH64 internal const int CACHE_LINE_SIZE = 128; #else internal const int CACHE_LINE_SIZE = 64; #endif } ================================================ FILE: src/Orleans.Core/Caching/Internal/Striped64.cs ================================================ #nullable enable using System; using System.Diagnostics.CodeAnalysis; using System.Threading; /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ namespace Orleans.Caching.Internal; /* * This class maintains a lazily-initialized table of atomically * updated variables, plus an extra "base" field. The table size * is a power of two. Indexing uses masked per-thread hash codes. * Nearly all declarations in this class are package-private, * accessed directly by subclasses. * * Table entries are of class Cell; a variant of AtomicLong padded * to reduce cache contention on most processors. Padding is * overkill for most Atomics because they are usually irregularly * scattered in memory and thus don't interfere much with each * other. But Atomic objects residing in arrays will tend to be * placed adjacent to each other, and so will most often share * cache lines (with a huge negative performance impact) without * this precaution. * * In part because Cells are relatively large, we avoid creating * them until they are needed. When there is no contention, all * updates are made to the base field. Upon first contention (a * failed CAS on base update), the table is initialized to size 2. * The table size is doubled upon further contention until * reaching the nearest power of two greater than or equal to the * number of CPUS. Table slots remain empty (null) until they are * needed. * * A single spinlock ("busy") is used for initializing and * resizing the table, as well as populating slots with new Cells. * There is no need for a blocking lock; when the lock is not * available, threads try other slots (or the base). During these * retries, there is increased contention and reduced locality, * which is still better than alternatives. * * Per-thread hash codes are initialized to random values. * Contention and/or table collisions are indicated by failed * CASes when performing an update operation (see method * retryUpdate). Upon a collision, if the table size is less than * the capacity, it is doubled in size unless some other thread * holds the lock. If a hashed slot is empty, and lock is * available, a new Cell is created. Otherwise, if the slot * exists, a CAS is tried. Retries proceed by "double hashing", * using a secondary hash (Marsaglia XorShift) to try to find a * free slot. * * The table size is capped because, when there are more threads * than CPUs, supposing that each thread were bound to a CPU, * there would exist a perfect hash function mapping threads to * slots that eliminates collisions. When we reach capacity, we * search for this mapping by randomly varying the hash codes of * colliding threads. Because search is random, and collisions * only become known via CAS failures, convergence can be slow, * and because threads are typically not bound to CPUS forever, * may not occur at all. However, despite these limitations, * observed contention rates are typically low in these cases. * * It is possible for a Cell to become unused when threads that * once hashed to it terminate, as well as in the case where * doubling the table causes no thread to hash to it under * expanded mask. We do not try to detect or remove such cells, * under the assumption that for long-running instances, observed * contention levels will recur, so the cells will eventually be * needed again; and for short-lived ones, it does not matter. */ /// /// Maintains a lazily-initialized table of atomically updated variables, plus an extra /// "base" field. The table size is a power of two. Indexing uses masked thread IDs. /// // Derived from BitFaster.Caching by Alex Peck // https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Counters/Striped64.cs [ExcludeFromCodeCoverage] internal abstract class Striped64 { // Number of CPUS, to place bound on table size private static readonly int MaxBuckets = Environment.ProcessorCount * 4; /// /// The base value used mainly when there is no contention, but also as a fallback /// during table initialization races. Updated via CAS. /// protected PaddedLong @base; /// /// When non-null, size is a power of 2. /// protected Cell[]? cells; private int _cellsBusy; /// /// A wrapper for PaddedLong. /// /// The value. protected sealed class Cell(long value) { /// /// The value of the cell. /// public PaddedLong Value = new() { Value = value }; } /** * CASes the cellsBusy field from 0 to 1 to acquire lock. */ private bool CasCellsBusy() => Interlocked.CompareExchange(ref _cellsBusy, 1, 0) == 0; private void VolatileWriteNotBusy() => Volatile.Write(ref _cellsBusy, 0); /** * Returns the probe value for the current thread. * Duplicated from ThreadLocalRandom because of packaging restrictions. */ protected static int GetProbe() => // Note: this results in higher throughput than introducing a random. Environment.CurrentManagedThreadId; /** * Pseudo-randomly advances and records the given probe value for the * given thread. * Duplicated from ThreadLocalRandom because of packaging restrictions. */ private static int AdvanceProbe(int probe) { // xorshift probe ^= probe << 13; probe ^= (int)((uint)probe >> 17); probe ^= probe << 5; return probe; } /** * Handles cases of updates involving initialization, resizing, * creating new Cells, and/or contention. See above for * explanation. This method suffers the usual non-modularity * problems of optimistic retry code, relying on rechecked sets of * reads. * * @param x the value * @param wasUncontended false if CAS failed before call */ protected void LongAccumulate(long x, bool wasUncontended) { var h = GetProbe(); // True if last slot nonempty var collide = false; while (true) { Cell[]? @as; Cell a; int n; long v; if ((@as = cells) != null && (n = @as.Length) > 0) { if ((a = @as[n - 1 & h]) == null) { if (_cellsBusy == 0) { // Try to attach new Cell // Optimistically create var r = new Cell(x); if (_cellsBusy == 0 && CasCellsBusy()) { try { // Recheck under lock Cell[]? rs; int m, j; if ((rs = cells) != null && (m = rs.Length) > 0 && rs[j = m - 1 & h] == null) { rs[j] = r; break; } } finally { VolatileWriteNotBusy(); } // Slot is now non-empty continue; } } collide = false; } else if (!wasUncontended) { // CAS already known to fail // Continue after rehash wasUncontended = true; } else if (a.Value.CompareAndSwap(v = a.Value.VolatileRead(), v + x)) { break; } else if (n >= MaxBuckets || cells != @as) { // At max size or stale collide = false; } else if (!collide) { collide = true; } else if (_cellsBusy == 0 && CasCellsBusy()) { try { if (cells == @as) { // Expand table unless stale var rs = new Cell[n << 1]; for (var i = 0; i < n; ++i) { rs[i] = @as[i]; } cells = rs; } } finally { VolatileWriteNotBusy(); } collide = false; // Retry with expanded table continue; } // Rehash h = AdvanceProbe(h); } else if (_cellsBusy == 0 && cells == @as && CasCellsBusy()) { try { // Initialize table if (cells == @as) { var rs = new Cell[2]; rs[h & 1] = new Cell(x); cells = rs; break; } } finally { VolatileWriteNotBusy(); } } // Fall back on using base else if (@base.CompareAndSwap(v = @base.VolatileRead(), v + x)) { break; } } } } ================================================ FILE: src/Orleans.Core/Caching/Internal/TypeProps.cs ================================================ using System; namespace Orleans.Caching.Internal; // https://source.dot.net/#System.Collections.Concurrent/System/Collections/Concurrent/ConcurrentDictionary.cs,2293 internal static class TypeProps { /// Whether T's type can be written atomically (i.e., with no danger of torn reads). internal static readonly bool IsWriteAtomic = IsWriteAtomicPrivate(); private static bool IsWriteAtomicPrivate() { // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without // the risk of tearing. See https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf if (!typeof(T).IsValueType || typeof(T) == typeof(nint) || typeof(T) == typeof(nuint)) { return true; } switch (Type.GetTypeCode(typeof(T))) { case TypeCode.Boolean: case TypeCode.Byte: case TypeCode.Char: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.SByte: case TypeCode.Single: case TypeCode.UInt16: case TypeCode.UInt32: return true; case TypeCode.Double: case TypeCode.Int64: case TypeCode.UInt64: return nint.Size == 8; default: return false; } } } ================================================ FILE: src/Orleans.Core/Cancellation/IGrainCallCancellationExtension.cs ================================================ #nullable enable using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Internal; namespace Orleans.Runtime; internal interface IGrainCallCancellationExtension : IGrainExtension { /// /// Indicates that a cancellation token has been canceled. /// /// /// The of the original message sender. /// /// /// The message id of the request message being canceled. /// /// /// A representing the operation. /// [AlwaysInterleave, OneWay] ValueTask CancelRequestAsync(GrainId senderGrainId, CorrelationId messageId); } /// /// Functionality for cancelling grain calls. /// internal interface IGrainCallCancellationManager { /// /// Attempts to cancel a grain call. /// void SignalCancellation(SiloAddress? targetSilo, GrainId targetGrainId, GrainId sendingGrainId, CorrelationId messageId); } internal sealed class ExternalClientGrainCallCancellationManager(IInternalGrainFactory grainFactory) : IGrainCallCancellationManager { public void SignalCancellation(SiloAddress? targetSilo, GrainId targetGrainId, GrainId sendingGrainId, CorrelationId messageId) { var targetGrain = grainFactory.GetGrain(targetGrainId); targetGrain.CancelRequestAsync(sendingGrainId, messageId).Ignore(); } } ================================================ FILE: src/Orleans.Core/ClientObservers/ClientGatewayObserver.cs ================================================ using Orleans.Messaging; using Orleans.Runtime; namespace Orleans.ClientObservers { /// /// Handles gateway notifications which are sent to connected clients. /// internal interface IClientGatewayObserver : IGrainObserver { /// /// Signals a client that it should stop sending messages to the specified gateway. /// /// The gateway void StopSendingToGateway(SiloAddress gateway); } /// /// Handles gateway notification events. /// internal sealed class ClientGatewayObserver : ClientObserver, IClientGatewayObserver { private static readonly IdSpan ScopedId = IdSpan.Create(nameof(ClientGatewayObserver)); private readonly GatewayManager gatewayManager; /// /// Initializes a new instance of the class. /// /// /// The gateway manager. /// public ClientGatewayObserver(GatewayManager gatewayManager) { this.gatewayManager = gatewayManager; } /// public void StopSendingToGateway(SiloAddress gateway) => this.gatewayManager.MarkAsUnavailableForSend(gateway); internal override ObserverGrainId GetObserverGrainId(ClientGrainId clientId) => ObserverGrainId.Create(clientId, ScopedId); internal static IClientGatewayObserver GetObserver(IInternalGrainFactory grainFactory, ClientGrainId clientId) { var observerId = ObserverGrainId.Create(clientId, ScopedId); return grainFactory.GetGrain(observerId.GrainId); } } } ================================================ FILE: src/Orleans.Core/ClientObservers/ClientObserver.cs ================================================ using Orleans.Runtime; namespace Orleans.ClientObservers { /// /// Base type for special client-wide observers. /// internal abstract class ClientObserver { /// /// Gets the observer id. /// /// The client id. /// The observer id. internal abstract ObserverGrainId GetObserverGrainId(ClientGrainId clientId); } } ================================================ FILE: src/Orleans.Core/CodeGeneration/GrainInterfaceUtils.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.CodeGeneration { /// /// Utilities for grain interface types. /// internal static class GrainInterfaceUtils { /// /// Gets all grain interface methods for a specified type, which may be a class. /// /// The grain type. /// Whether to get all methods or only declared methods. /// All grain interface methods for a specified type. public static MethodInfo[] GetMethods(Type grainType, bool bAllMethods = true) { var methodInfos = new List(); GetMethodsImpl(grainType, grainType, methodInfos); var flags = BindingFlags.Public | BindingFlags.Instance; if (!bAllMethods) flags |= BindingFlags.DeclaredOnly; MethodInfo[] infos = grainType.GetMethods(flags); foreach (var methodInfo in infos) { if (!methodInfos.Contains(methodInfo, MethodInfoComparer.Default)) { methodInfos.Add(methodInfo); } } return methodInfos.ToArray(); static void GetMethodsImpl(Type grainType, Type serviceType, List methodInfos) { foreach (var iType in GetGrainInterfaces(serviceType)) { if (grainType.IsClass) { var mapping = grainType.GetInterfaceMap(iType); foreach (var methodInfo in iType.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { foreach (var info in mapping.TargetMethods) { if (info.DeclaringType == grainType && MethodInfoComparer.Default.Equals(methodInfo, info)) { if (!methodInfos.Contains(methodInfo, MethodInfoComparer.Default)) methodInfos.Add(methodInfo); break; } } } } else if (grainType.IsInterface) { foreach (var methodInfo in iType.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { if (!methodInfos.Contains(methodInfo, MethodInfoComparer.Default)) methodInfos.Add(methodInfo); } } } } static List GetGrainInterfaces(Type type) { var res = new List(); if (IsGrainInterface(type)) { res.Add(type); } foreach (var interfaceType in type.GetInterfaces()) { res.Add(interfaceType); } return res; } static bool IsGrainInterface(Type t) { if (t.IsClass) return false; if (t == typeof(IGrainObserver) || t == typeof(IAddressable) || t == typeof(IGrainExtension)) return false; if (t == typeof(IGrain) || t == typeof(IGrainWithGuidKey) || t == typeof(IGrainWithIntegerKey) || t == typeof(IGrainWithGuidCompoundKey) || t == typeof(IGrainWithIntegerCompoundKey)) return false; if (t == typeof(ISystemTarget)) return false; return typeof(IAddressable).IsAssignableFrom(t); } } public static int GetGrainClassTypeCode(Type grainClass) => (int)StableHash.ComputeHash(RuntimeTypeNameFormatter.Format(grainClass)); private sealed class MethodInfoComparer : IEqualityComparer, IComparer { public static MethodInfoComparer Default { get; } = new(); private MethodInfoComparer() { } public bool Equals(MethodInfo x, MethodInfo y) { if (!string.Equals(x.Name, y.Name, StringComparison.Ordinal)) { return false; } var xArgs = x.GetGenericArguments(); var yArgs = y.GetGenericArguments(); if (xArgs.Length != yArgs.Length) { return false; } for (var i = 0; i < x.GetGenericArguments().Length; i++) { if (xArgs[i] != yArgs[i]) { return false; } } var xParams = x.GetParameters(); var yParams = y.GetParameters(); if (xParams.Length != yParams.Length) { return false; } for (var i = 0; i < xParams.Length; i++) { if (xParams[i].ParameterType != yParams[i].ParameterType) { return false; } } return true; } public int GetHashCode(MethodInfo obj) { int hashCode = -499943048; hashCode = hashCode * -1521134295 + StringComparer.Ordinal.GetHashCode(obj.Name); foreach (var arg in obj.GetGenericArguments()) { hashCode = hashCode * -1521134295 + arg.GetHashCode(); } foreach (var parameter in obj.GetParameters()) { hashCode = hashCode * -1521134295 + parameter.ParameterType.GetHashCode(); } return hashCode; } public int Compare(MethodInfo x, MethodInfo y) { var result = StringComparer.Ordinal.Compare(x.Name, y.Name); if (result != 0) { return result; } var xArgs = x.GetGenericArguments(); var yArgs = y.GetGenericArguments(); result = xArgs.Length.CompareTo(yArgs.Length); if (result != 0) { return result; } for (var i = 0; i < xArgs.Length; i++) { var xh = xArgs[i].GetHashCode(); var yh = yArgs[i].GetHashCode(); result = xh.CompareTo(yh); if (result != 0) { return result; } } var xParams = x.GetParameters(); var yParams = y.GetParameters(); result = xParams.Length.CompareTo(yParams.Length); if (result != 0) { return result; } for (var i = 0; i < xParams.Length; i++) { var xh = xParams[i].ParameterType.GetHashCode(); var yh = yParams[i].ParameterType.GetHashCode(); result = xh.CompareTo(yh); if (result != 0) { return result; } } return 0; } } } } ================================================ FILE: src/Orleans.Core/CodeGeneration/IGrainState.cs ================================================ using System; namespace Orleans { /// /// Defines the state of a grain /// /// /// The underlying state type. /// public interface IGrainState { /// /// Gets or sets the state. /// T State { get; set; } /// Gets or sets the ETag that allows optimistic concurrency checks at the storage provider level. string ETag { get; set; } /// /// Gets or sets a value indicating whether the record exists in storage. /// bool RecordExists { get; set; } } /// /// Default implementation of . /// /// The type of application level payload. [Serializable] [GenerateSerializer] public sealed class GrainState : IGrainState { /// [Id(0)] public T State { get; set; } /// [Id(1)] public string ETag { get; set; } /// [Id(2)] public bool RecordExists { get; set; } /// /// Initializes a new instance of the class. /// public GrainState() { } /// /// Initializes a new instance of the class. /// /// /// The initial value of the state. /// public GrainState(T state) : this(state, null) { } /// /// Initializes a new instance of the class. /// /// /// The initial value of the state. /// /// /// The initial e-tag value that allows optimistic concurrency checks at the storage provider level. /// public GrainState(T state, string eTag) { State = state; ETag = eTag; } } } ================================================ FILE: src/Orleans.Core/Configuration/CollectionAgeLimitAttribute.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans { /// /// Specifies the period of inactivity before a grain is available for collection and deactivation. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class CollectionAgeLimitAttribute : Attribute, IGrainPropertiesProviderAttribute { private TimeSpan? _value; /// /// Specifies the period of inactivity before a grain is available for collection and deactivation. /// /// /// Use the , , and properties or the to set the limit. /// public CollectionAgeLimitAttribute() { } /// /// Specifies the period of inactivity before a grain is available for collection and deactivation. /// /// The period of inactivity before a grain is available for collection and deactivation, expressed as a string using syntax. public CollectionAgeLimitAttribute(string inactivityPeriod) => _value = TimeSpan.Parse(inactivityPeriod); /// /// Gets the minimum activation age. /// public static readonly TimeSpan MinAgeLimit = TimeSpan.FromMinutes(1); /// /// Gets or sets the number of days to delay collecting an idle activation for. /// public double Days { get; set; } /// /// Gets or sets the number of hours to delay collecting an idle activation for. /// public double Hours { get; set; } /// /// Gets or sets the number of minutes to delay collecting an idle activation for. /// public double Minutes { get; set; } /// /// Gets or sets a value indicating whether this grain should never be collected by the idle activation collector. /// public bool AlwaysActive { get; set; } /// /// Gets the idle activation collection age. /// public TimeSpan AgeLimit => _value ??= CalculateValue(); /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { string idleDeactivationPeriod; if (AlwaysActive) { idleDeactivationPeriod = WellKnownGrainTypeProperties.IndefiniteIdleDeactivationPeriodValue; } else { idleDeactivationPeriod = AgeLimit.ToString("c"); } properties[WellKnownGrainTypeProperties.IdleDeactivationPeriod] = idleDeactivationPeriod; } private TimeSpan CalculateValue() { var span = AlwaysActive ? TimeSpan.FromDays(short.MaxValue) : TimeSpan.FromDays(Days) + TimeSpan.FromHours(Hours) + TimeSpan.FromMinutes(Minutes); return span < MinAgeLimit ? MinAgeLimit : span; } } /// /// When applied to a grain implementation type this attribute specifies that activations of the grain shouldn't be collected by the idle activation collector. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class KeepAliveAttribute : Attribute, IGrainPropertiesProviderAttribute { /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.IdleDeactivationPeriod] = WellKnownGrainTypeProperties.IndefiniteIdleDeactivationPeriodValue; } } } ================================================ FILE: src/Orleans.Core/Configuration/ConfigUtilities.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; namespace Orleans.Runtime.Configuration { /// /// Utilities class for working with configuration. /// public static class ConfigUtilities { // Time spans are entered as a string of decimal digits, optionally followed by a unit string: "ms", "s", "m", "hr" internal static TimeSpan ParseTimeSpan(string input, string errorMessage) { long unitSize; string numberInput; var trimmedInput = input.Trim().ToLowerInvariant(); if (trimmedInput.EndsWith("ms", StringComparison.Ordinal)) { unitSize = 10000; numberInput = trimmedInput.Remove(trimmedInput.Length - 2).Trim(); } else if (trimmedInput.EndsWith('s')) { unitSize = 1000 * 10000; numberInput = trimmedInput.Remove(trimmedInput.Length - 1).Trim(); } else if (trimmedInput.EndsWith('m')) { unitSize = 60 * 1000 * 10000; numberInput = trimmedInput.Remove(trimmedInput.Length - 1).Trim(); } else if (trimmedInput.EndsWith("hr", StringComparison.Ordinal)) { unitSize = 60 * 60 * 1000 * 10000L; numberInput = trimmedInput.Remove(trimmedInput.Length - 2).Trim(); } else { unitSize = 1000 * 10000; // Default is seconds numberInput = trimmedInput; } decimal rawTimeSpan; if (!decimal.TryParse(numberInput, NumberStyles.Any, CultureInfo.InvariantCulture, out rawTimeSpan)) { throw new FormatException(errorMessage + ". Tried to parse " + input); } return TimeSpan.FromTicks((long)(rawTimeSpan * unitSize)); } internal static IPAddress ResolveIPAddressOrDefault(byte[] subnet, AddressFamily family) { IList nodeIps = NetworkInterface.GetAllNetworkInterfaces() .Where(iface => iface.OperationalStatus == OperationalStatus.Up) .SelectMany(iface => iface.GetIPProperties().UnicastAddresses) .Select(addr => addr.Address) .Where(addr => addr.AddressFamily == family && !IPAddress.IsLoopback(addr)) .ToList(); var ipAddress = PickIPAddress(nodeIps, subnet, family); return ipAddress; } internal static IPAddress ResolveIPAddressOrDefault(string addrOrHost, byte[] subnet, AddressFamily family) { var loopback = family == AddressFamily.InterNetwork ? IPAddress.Loopback : IPAddress.IPv6Loopback; // if the address is an empty string, just enumerate all ip addresses available // on this node if (string.IsNullOrEmpty(addrOrHost)) { return ResolveIPAddressOrDefault(subnet, family); } else { // Fix StreamFilteringTests_SMS tests if (addrOrHost.Equals("loopback", StringComparison.OrdinalIgnoreCase)) { return loopback; } // check if addrOrHost is a valid IP address including loopback (127.0.0.0/8, ::1) and any (0.0.0.0/0, ::) addresses if (IPAddress.TryParse(addrOrHost, out var address)) { return address; } // Get IP address from DNS. If addrOrHost is localhost will // return loopback IPv4 address (or IPv4 and IPv6 addresses if OS is supported IPv6) var nodeIps = Dns.GetHostAddresses(addrOrHost); return PickIPAddress(nodeIps, subnet, family); } } private static IPAddress PickIPAddress(IList nodeIps, byte[] subnet, AddressFamily family) { var candidates = new List(); foreach (var nodeIp in nodeIps.Where(x => x.AddressFamily == family)) { // If the subnet does not match - we can't resolve this address. // If subnet is not specified - pick smallest address deterministically. if (subnet == null) { candidates.Add(nodeIp); } else { var ip = nodeIp; if (subnet.Select((b, i) => ip.GetAddressBytes()[i] == b).All(x => x)) { candidates.Add(nodeIp); } } } return candidates.Count > 0 ? PickIPAddress(candidates) : null; } private static IPAddress PickIPAddress(IReadOnlyList candidates) { IPAddress chosen = null; foreach (IPAddress addr in candidates) { if (chosen == null) { chosen = addr; } else { if (CompareIPAddresses(addr, chosen)) // pick smallest address deterministically chosen = addr; } } return chosen; // returns true if lhs is "less" (in some repeatable sense) than rhs static bool CompareIPAddresses(IPAddress lhs, IPAddress rhs) { byte[] lbytes = lhs.GetAddressBytes(); byte[] rbytes = rhs.GetAddressBytes(); if (lbytes.Length != rbytes.Length) return lbytes.Length < rbytes.Length; // compare starting from most significant octet. // 10.68.20.21 < 10.98.05.04 for (int i = 0; i < lbytes.Length; i++) { if (lbytes[i] != rbytes[i]) { return lbytes[i] < rbytes[i]; } } // They're equal return false; } } /// /// Gets the address of the local server. /// If there are multiple addresses in the correct family in the server's DNS record, the first will be returned. /// /// The server's IPv4 address. internal static IPAddress GetLocalIPAddress(AddressFamily family = AddressFamily.InterNetwork, string interfaceName = null) { var loopback = (family == AddressFamily.InterNetwork) ? IPAddress.Loopback : IPAddress.IPv6Loopback; // get list of all network interfaces NetworkInterface[] netInterfaces = NetworkInterface.GetAllNetworkInterfaces(); var candidates = new List(); // loop through interfaces for (int i = 0; i < netInterfaces.Length; i++) { NetworkInterface netInterface = netInterfaces[i]; if (netInterface.OperationalStatus != OperationalStatus.Up) { // Skip network interfaces that are not operational continue; } if (!string.IsNullOrWhiteSpace(interfaceName) && !netInterface.Name.StartsWith(interfaceName, StringComparison.Ordinal)) continue; bool isLoopbackInterface = (netInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback); // get list of all unicast IPs from current interface UnicastIPAddressInformationCollection ipAddresses = netInterface.GetIPProperties().UnicastAddresses; // loop through IP address collection foreach (UnicastIPAddressInformation ip in ipAddresses) { if (ip.Address.AddressFamily == family) // Picking the first address of the requested family for now. Will need to revisit later { //don't pick loopback address, unless we were asked for a loopback interface if (!(isLoopbackInterface && ip.Address.Equals(loopback))) { candidates.Add(ip.Address); // collect all candidates. } } } } return ResolveLocalIPAddress(candidates, family, interfaceName); } internal static IPAddress ResolveLocalIPAddress(IReadOnlyList candidates, AddressFamily family, string interfaceName) { if (candidates.Count > 0) { return PickIPAddress(candidates); } if (string.IsNullOrWhiteSpace(interfaceName)) { return family switch { AddressFamily.InterNetwork => IPAddress.Loopback, AddressFamily.InterNetworkV6 => IPAddress.IPv6Loopback, _ => throw new OrleansException("Failed to get a local IP address."), }; } throw new OrleansException("Failed to get a local IP address."); } /// /// Prints the DataConnectionString, /// without disclosing any credential info /// such as the Azure Storage AccountKey, SqlServer password or AWS SecretKey. /// /// The connection string to print. /// The string representation of the DataConnectionString with account credential info redacted. public static string RedactConnectionStringInfo(string connectionString) { string[] secretKeys = { "AccountKey=", // Azure Storage "SharedAccessSignature=", // Many Azure services "SharedAccessKey=", "SharedSecretValue=", // ServiceBus "Password=", // SQL "SecretKey=", "SessionToken=", // DynamoDb }; var mark = "<--SNIP-->"; if (string.IsNullOrEmpty(connectionString)) return "null"; //if connection string format doesn't contain any secretKey, then return just <--SNIP--> if (!Array.Exists(secretKeys, key => connectionString.Contains(key))) return mark; string connectionInfo = connectionString; // Remove any secret keys from connection string info written to log files foreach (var secretKey in secretKeys) { int keyPos = connectionInfo.IndexOf(secretKey, StringComparison.OrdinalIgnoreCase); if (keyPos >= 0) { connectionInfo = connectionInfo.Remove(keyPos + secretKey.Length) + mark; } } return connectionInfo; } } } ================================================ FILE: src/Orleans.Core/Configuration/GrainTypeOptions.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Serialization.Configuration; namespace Orleans.Configuration { /// /// Contains grain type descriptions. /// public class GrainTypeOptions { /// /// Gets a collection of metadata about grain classes. /// public HashSet Classes { get; } = new (); /// /// Gets a collection of metadata about grain interfaces. /// public HashSet Interfaces { get; } = new (); } /// /// The default configuration provider for . /// internal sealed class DefaultGrainTypeOptionsProvider : IConfigureOptions { private readonly TypeManifestOptions _typeManifestOptions; /// /// Initializes a new instance of the class. /// /// The type manifest options. public DefaultGrainTypeOptionsProvider(IOptions typeManifestOptions) => _typeManifestOptions = typeManifestOptions.Value; /// public void Configure(GrainTypeOptions options) { foreach (var type in _typeManifestOptions.Interfaces) { if (typeof(IAddressable).IsAssignableFrom(type)) { options.Interfaces.Add(type); } } foreach (var type in _typeManifestOptions.InterfaceImplementations) { if (IsImplementationType(type)) { options.Classes.Add(type switch { { IsGenericType: true, IsConstructedGenericType: false } => type.GetGenericTypeDefinition(), _ => type }); } } static bool IsImplementationType(Type type) { if (type.IsAbstract || type.IsInterface) { return false; } if (typeof(IGrain).IsAssignableFrom(type)) { return true; } return false; } } } /// /// Validates . /// public sealed class GrainTypeOptionsValidator : IConfigurationValidator { private readonly IOptions _options; private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// The options. /// The service provider. public GrainTypeOptionsValidator(IOptions options, IServiceProvider serviceProvider) { _options = options; _serviceProvider = serviceProvider; } /// public void ValidateConfiguration() { if (_options.Value.Interfaces is not { Count: > 0 }) { throw new OrleansConfigurationException($"No grain interfaces have been configured. Either add some grain interfaces and reference the Orleans.Sdk package, or remove {nameof(GrainTypeOptionsValidator)} from the services collection."); } var isSilo = _serviceProvider.GetService(typeof(ILocalSiloDetails)) != null; if (isSilo) { if (_options.Value.Classes is not { Count: > 0 }) { throw new OrleansConfigurationException($"No grain classes have been configured. Either add some grain classes and reference the Orleans.Sdk package, or remove {nameof(GrainTypeOptionsValidator)} from the services collection."); } } } } } ================================================ FILE: src/Orleans.Core/Configuration/NamedServiceConfigurator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.Hosting { /// /// Functionality for configuring a named service. /// public interface INamedServiceConfigurator { /// /// Gets the service name. /// string Name { get; } /// /// Gets the delegate used to configure the service. /// Action> ConfigureDelegate { get; } } /// /// Component configurator base class for names services /// This associates any configurations or subcomponents with the same name as the service being configured /// public class NamedServiceConfigurator : INamedServiceConfigurator { /// public string Name { get; } /// public Action> ConfigureDelegate { get; } /// /// Initializes a new instance of the class. /// /// /// The name. /// /// /// The configuration delegate. /// public NamedServiceConfigurator(string name, Action> configureDelegate) { this.Name = name; this.ConfigureDelegate = configureDelegate; } } /// /// Extensions for working with . /// public static class NamedServiceConfiguratorExtensions { /// /// Configures options for a named service. /// /// /// The named service configurator. /// /// /// The options configuration delegate. /// /// /// The underlying options type. /// public static void Configure(this INamedServiceConfigurator configurator, Action> configureOptions) where TOptions : class, new() { configurator.ConfigureDelegate(services => { configureOptions?.Invoke(services.AddOptions(configurator.Name)); services.ConfigureNamedOptionForLogging(configurator.Name); }); } /// /// Adds a singleton component to a named service and configures options for the named service. /// /// The options type being configured. /// The component service type being registered. /// The named configurator which the component and options will be configured for. /// The factory used to create the component for the named service. /// The delegate used to configure options for the named service. public static void ConfigureComponent(this INamedServiceConfigurator configurator, Func factory, Action> configureOptions = null) where TOptions : class, new() where TComponent : class { configurator.Configure(configureOptions); configurator.ConfigureComponent(factory); } /// /// Adds a singleton component to a named service. /// /// The component service type. /// The named configurator which the component will be configured for. /// The factory used to create the component for the named service. public static void ConfigureComponent(this INamedServiceConfigurator configurator, Func factory) where TComponent : class { configurator.ConfigureDelegate(services => { services.AddKeyedSingleton(configurator.Name, (sp, key) => factory(sp, key as string)); }); } public static void ConfigureLifecycle(this INamedServiceConfigurator configurator) where T : ILifecycleSubject { } } } ================================================ FILE: src/Orleans.Core/Configuration/OptionLogger/DefaultOptionsFormatter.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using Microsoft.Extensions.Options; namespace Orleans { /// /// Default implementation of . /// /// The options type. internal sealed class DefaultOptionsFormatter : IOptionFormatter where T : class { private readonly T _options; /// /// Initializes a new instance of the class. /// /// The options. public DefaultOptionsFormatter(IOptions options) { _options = options.Value; Name = OptionFormattingUtilities.Name(); } /// /// Initializes a new instance of the class. /// /// The options name. /// The options. internal DefaultOptionsFormatter(string name, T options) { _options = options; Name = OptionFormattingUtilities.Name(name); } /// /// Gets the options name. /// public string Name { get; } /// /// For /// /// public IEnumerable Format() { foreach (var prop in typeof(T).GetProperties()) { if (!IsFormattableProperty(prop)) { continue; } foreach (var formattedValue in FormatProperty(_options, prop)) { yield return formattedValue; } } } private static bool IsFormattableProperty(PropertyInfo prop) { if (prop is null) return false; if (!IsFormattableType(prop.PropertyType)) return false; if (prop.GetCustomAttribute() is not null) return false; if (!IsAccessibleMethod(prop.GetSetMethod())) return false; if (!IsAccessibleMethod(prop.GetGetMethod())) return false; return true; } private static bool IsAccessibleMethod(MethodInfo accessor) { if (accessor is null) return false; if (!accessor.IsPublic) return false; if (accessor.GetCustomAttribute() is not null) return false; return true; } private static bool IsFormattableType(Type type) { if (type is null) return false; if (typeof(Delegate).IsAssignableFrom(type)) return false; return true; } private static IEnumerable FormatProperty(object options, PropertyInfo property) { var name = property.Name; var value = property.GetValue(options); var redactAttribute = property.GetCustomAttribute(inherit: true); // If redact specified, let the attribute implementation do the work if (redactAttribute != null) { yield return OptionFormattingUtilities.Format(name, redactAttribute.Redact(value)); } else { if (value is IDictionary dict) { // If it is a dictionary -> one line per item var enumerator = dict.GetEnumerator(); while (enumerator.MoveNext()) { var kvp = enumerator.Entry; yield return $"{name}.{kvp.Key}: {kvp.Value}"; } } else if (value is ICollection coll) { // If it is a simple collection -> one line per item if (coll.Count > 0) { var index = 0; foreach (var item in coll) { yield return $"{name}.{index}: {item}"; index++; } } } else { // Simple case yield return OptionFormattingUtilities.Format(name, value); } } } } internal class DefaultOptionsFormatterResolver : IOptionFormatterResolver where T: class { private readonly IOptionsMonitor _optionsMonitor; public DefaultOptionsFormatterResolver(IOptionsMonitor optionsMonitor) { _optionsMonitor = optionsMonitor; } public IOptionFormatter Resolve(string name) => new DefaultOptionsFormatter(name, _optionsMonitor.Get(name)); } } ================================================ FILE: src/Orleans.Core/Configuration/OptionLogger/IOptionFormatter.cs ================================================ using System.Collections.Generic; namespace Orleans { /// /// format the option and give it a category and a name /// public interface IOptionFormatter { /// /// Gets the name of the options object. /// string Name { get; } /// /// Formats the options object into a collection of strings. /// /// A collection of formatted string-value pairs corresponding the the properties on the options object. IEnumerable Format(); } /// /// Option formatter for a certain option type /// /// The options type. public interface IOptionFormatter : IOptionFormatter { } /// /// IOptionFormatterResolver resolve specific OptionFormatter for certain named option /// /// The options type. public interface IOptionFormatterResolver { /// /// Resolves the options formatter for the specified options type with the specified options name. /// /// The options name. /// The options type. IOptionFormatter Resolve(string name); } /// /// Utility class for option formatting /// public static class OptionFormattingUtilities { /// /// The default format string. /// private const string DefaultFormatFormatting = "{0}: {1}"; /// /// The default format string for options types which are named. /// private const string DefaultNamedFormatting = "{0}-{1}"; /// /// Formats a key-value pair using default format /// /// /// The key. /// /// /// The value. /// /// /// The format string. /// /// A formatted key-value pair. public static string Format(object key, object value, string formatting = null) { var valueFormat = formatting ?? DefaultFormatFormatting; return string.Format(valueFormat, key, value); } /// /// Formats the name of an options object. /// /// The options type. /// The options name. /// The format string. /// The formatted options object name. public static string Name(string name = null, string formatting = null) { return name is null && formatting is null ? typeof(TOptions).FullName : string.Format(formatting ?? DefaultNamedFormatting, typeof(TOptions).FullName, name); } } } ================================================ FILE: src/Orleans.Core/Configuration/OptionLogger/IOptionsLogger.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Orleans { /// /// Logger for options on the client. /// internal class ClientOptionsLogger : OptionsLogger, ILifecycleParticipant { /// /// Logs options as soon as possible. /// private const int ClientOptionLoggerLifeCycleRing = int.MinValue; /// /// Initializes a new instance of the class. /// /// /// The logger. /// /// /// The services. /// public ClientOptionsLogger(ILogger logger, IServiceProvider services) : base(logger, services) { } /// public void Participate(IClusterClientLifecycle lifecycle) { lifecycle.Subscribe(ClientOptionLoggerLifeCycleRing, this.OnStart); } /// public Task OnStart(CancellationToken token) { this.LogOptions(); return Task.CompletedTask; } } /// /// Base class for client and silo default options loggers. /// public abstract partial class OptionsLogger { private readonly ILogger logger; private readonly IServiceProvider services; /// /// Initializes a new instance of the class. /// /// /// The logger. /// /// /// The services. /// protected OptionsLogger(ILogger logger, IServiceProvider services) { this.logger = logger; this.services = services; } /// /// Log all options with registered formatters /// public void LogOptions() { this.LogOptions(services.GetServices()); } /// /// Log options using a set of formatters. /// /// The collection of options formatters. public void LogOptions(IEnumerable formatters) { foreach (var optionFormatter in formatters.OrderBy(f => f.Name)) { this.LogOption(optionFormatter); } } /// /// Log an options using a formatter. /// /// The options formatter. public void LogOption(IOptionFormatter formatter) { try { var stringBuilder = new StringBuilder(); foreach (var setting in formatter.Format()) { stringBuilder.AppendLine($"{setting}"); } LogInformationOptions(logger, formatter.Name, stringBuilder.ToString()); } catch(Exception ex) { LogErrorOptions(logger, ex, formatter.Name); throw; } } [LoggerMessage( Level = LogLevel.Information, Message = "Configuration {Name}:\n{Options}" )] private static partial void LogInformationOptions(ILogger logger, string name, string options); [LoggerMessage( Level = LogLevel.Error, Message = "An error occurred while logging '{Name}' options." )] private static partial void LogErrorOptions(ILogger logger, Exception exception, string name); } } ================================================ FILE: src/Orleans.Core/Configuration/OptionLogger/OptionFormatterExtensionMethods.cs ================================================ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration.Internal; namespace Orleans.Configuration { /// /// Extension methods on , to provider better usability to IOptionFormatter. /// public static class OptionConfigureExtensionMethods { /// /// Configures an options formatter for . /// /// /// The services. /// /// /// The options type. /// /// /// The option formatter type. /// /// /// The , for chaining with other calls. /// public static IServiceCollection ConfigureFormatter(this IServiceCollection services) where TOptions : class where TOptionFormatter : class, IOptionFormatter { if (services.All(service => service.ServiceType != typeof(IOptionFormatter))) { services .AddSingleton, TOptionFormatter>() .AddFromExisting>(); } else { // override IOptionFormatter services.AddSingleton, TOptionFormatter>(); } return services; } /// /// Configures an options formatter for . /// /// /// This will use the default options formatter unless a non-default formatter is configured. /// /// /// The services. /// /// /// The options type. /// /// /// The , for chaining with other calls. /// public static IServiceCollection ConfigureFormatter(this IServiceCollection services) where TOptions : class, new() { return services.AddSingleton(sp => sp.GetService>()); } /// /// Configures an options formatter for if none are already configured. /// /// /// The services. /// /// /// The options type. /// /// /// The option formatter type. /// /// /// The , for chaining with other calls. /// public static IServiceCollection TryConfigureFormatter(this IServiceCollection services) where TOptions : class where TOptionFormatter : class, IOptionFormatter { if (services.All(service => service.ServiceType != typeof(IOptionFormatter))) { services.ConfigureFormatter(); } return services; } /// /// Configures an options formatter resolver for . /// /// /// The services. /// /// /// The options type. /// /// /// The option formatter resolver type. /// /// /// The , for chaining with other calls. /// public static IServiceCollection ConfigureFormatterResolver(this IServiceCollection services) where TOptions : class where TOptionFormatterResolver : class, IOptionFormatterResolver { return services.AddSingleton, TOptionFormatterResolver>(); } /// /// Configure option formatter resolver for named option TOptions, if none is configured /// public static IServiceCollection TryConfigureFormatterResolver(this IServiceCollection services) where TOptions : class where TOptionFormatterResolver : class, IOptionFormatterResolver { if (services.All(service => service.ServiceType != typeof(IOptionFormatterResolver))) { return services.ConfigureFormatterResolver(); } return services; } /// /// Configures a named option to be logged. /// /// /// The option object's type. /// /// /// The services. /// /// /// The option object's name. /// /// /// The , for chaining with other calls. /// public static IServiceCollection ConfigureNamedOptionForLogging(this IServiceCollection services, string name) where TOptions : class { return services.AddSingleton(sp => sp.GetService>().Resolve(name)); } } } ================================================ FILE: src/Orleans.Core/Configuration/OptionLogger/RedactMemberAttribute.cs ================================================ using Orleans.Runtime.Configuration; using System; namespace Orleans { /// /// When applied to a property on an options class, this attribute prevents the property value from being formatted by conforming instances. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class RedactAttribute : Attribute { /// /// Redacts the provided value. /// /// The value. /// The redacted value. public virtual string Redact(object value) { return "REDACTED"; } } /// /// When applied to a connection string property on an options class, this attribute prevents the property value from being formatted by conforming instances. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class RedactConnectionStringAttribute : RedactAttribute { /// /// Redacts the provided value. /// /// The value. /// The redacted value. public override string Redact(object value) { return ConfigUtilities.RedactConnectionStringInfo(value as string); } } } ================================================ FILE: src/Orleans.Core/Configuration/Options/ClientMessagingOptions.cs ================================================ using System.Net; using System.Net.Sockets; namespace Orleans.Configuration { /// /// Specifies global messaging options that are client related. /// public class ClientMessagingOptions : MessagingOptions { /// /// Gets or sets the total number of grain buckets used by the client in client-to-gateway communication /// protocol. In this protocol, grains are mapped to buckets and buckets are mapped to gateway connections, in order to enable stickiness /// of grain to gateway (messages to the same grain go to the same gateway, while evenly spreading grains across gateways). /// This number should be about 10 to 100 times larger than the expected number of gateway connections. /// If this attribute is not specified, then Math.Pow(2, 13) is used. /// public int ClientSenderBuckets { get; set; } = DEFAULT_CLIENT_SENDER_BUCKETS; /// /// The default value for . /// /// 8192 public const int DEFAULT_CLIENT_SENDER_BUCKETS = 8192; /// /// Gets or sets the preferred to be used when determining an appropriate client identity. /// public AddressFamily PreferredFamily { get; set; } = DEFAULT_PREFERRED_FAMILY; /// /// The default value for . /// /// public const AddressFamily DEFAULT_PREFERRED_FAMILY = AddressFamily.InterNetwork; /// /// Gets or sets the name of the network interface to use to work out an IP address for this machine. /// public string NetworkInterfaceName { get; set; } /// /// Gets or sets the IP address used for cluster client. /// public IPAddress LocalAddress { get; set; } } } ================================================ FILE: src/Orleans.Core/Configuration/Options/ClusterMembershipOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Settings for cluster membership. /// public class ClusterMembershipOptions { /// /// Gets or sets the number of missed "I am alive" updates in the table from a silo that causes warning to be logged. /// /// public int NumMissedTableIAmAliveLimit { get; set; } = 3; /// /// Gets or sets a value indicating whether to disable silo liveness protocol (should be used only for testing). /// If a silo is suspected to be down, but this attribute is set to , the suspicions will not propagated to the system and enforced. /// This parameter is intended for use only for testing and troubleshooting. /// In production, liveness should always be enabled. /// /// Liveness is enabled by default. public bool LivenessEnabled { get; set; } = true; /// /// Gets or sets both the period between sending a liveness probe to any given host as well as the timeout for each probe. /// /// Probes timeout and a new probe is sent every 5 seconds by default. public TimeSpan ProbeTimeout { get; set; } = TimeSpan.FromSeconds(5); /// /// Gets or sets the period between fetching updates from the membership table. /// /// The membership table is refreshed every 60 seconds by default. public TimeSpan TableRefreshTimeout { get; set; } = TimeSpan.FromSeconds(60); /// /// Gets or sets the expiration time in seconds for votes in the membership table. /// /// Votes expire after 2 minutes by default. public TimeSpan DeathVoteExpirationTimeout { get; set; } = TimeSpan.FromMinutes(2); /// /// Gets or sets the period between updating this silo's heartbeat in the membership table. /// /// /// These heartbeats are largely for diagnostic purposes, however they are also used to ignore entries /// in the membership table in the event of a total cluster reset. This value multiplied by /// is used to skip hosts in the membership table when performing an initial connectivity check upon startup. /// /// Publish an update every 5 minutes by default. public TimeSpan IAmAliveTablePublishTimeout { get; set; } = TimeSpan.FromSeconds(30); /// /// Gets or sets the maximum amount of time to attempt to join a cluster before giving up. /// /// Attempt to join for 5 minutes before giving up by default. public TimeSpan MaxJoinAttemptTime { get; set; } = TimeSpan.FromMinutes(5); /// /// Gets or sets a value indicating whether gossip membership updates between hosts. /// /// Membership updates are disseminated using gossip by default. public bool UseLivenessGossip { get; set; } = true; /// /// Gets or sets the number of silos each silo probes for liveness. /// /// /// This determines how many hosts each host will monitor by default. /// A low value, such as 3, is generally sufficient and allows for prompt removal of another silo in the event that it stops functioning. /// Monitoring is not expensive, however, and a higher value improves recovery during sudden changes in cluster size. /// When a silo becomes suspicious of another silo, additional silos may begin to probe that silo to speed up the detection of non-functioning silos. /// /// Each silo will actively monitor up to 10 other silos by default. public int NumProbedSilos { get; set; } = 10; /// /// Gets or sets the number of missed probe requests from a silo that lead to suspecting this silo as down. /// /// A silo will be suspected as being down if three probes are missed, by default. public int NumMissedProbesLimit { get; set; } = 3; /// /// Gets or sets the number of non-expired votes that are needed to declare some silo as down (should be at most ) /// /// Two votes are sufficient for a silo to be declared as down, by default. public int NumVotesForDeathDeclaration { get; set; } = 2; /// /// Gets or sets the period of time after which membership entries for defunct silos are eligible for removal. /// Valid only if is not . /// /// Defunct silos are removed from membership after one week by default. public TimeSpan DefunctSiloExpiration { get; set; } = TimeSpan.FromDays(7); /// /// Gets or sets the duration between membership table cleanup operations. When this period elapses, all defunct silo /// entries older than are removed. This value is per-silo. /// /// Membership is cleared of expired, defunct silos every hour, by default. public TimeSpan? DefunctSiloCleanupPeriod { get; set; } = TimeSpan.FromHours(1); /// /// /// Gets the period after which a silo is ignored for initial connectivity validation if it has not updated its heartbeat in the silo membership table. /// internal TimeSpan AllowedIAmAliveMissPeriod => IAmAliveTablePublishTimeout.Multiply(NumMissedTableIAmAliveLimit); /// /// Gets the amount of time to wait for the cluster membership system to terminate during shutdown. /// internal static TimeSpan ClusteringShutdownGracePeriod => TimeSpan.FromSeconds(5); /// /// Gets or sets the period between self-tests to log local health degradation status. /// /// The local host will perform a self-test every ten seconds by default. public TimeSpan LocalHealthDegradationMonitoringPeriod { get; set; } = TimeSpan.FromSeconds(10); /// /// Gets or sets a value indicating whether to extend the effective value based upon current local health degradation. /// public bool ExtendProbeTimeoutDuringDegradation { get; set; } = true; /// /// Gets or sets a value indicating whether to enable probing silos indirectly, via other silos. /// public bool EnableIndirectProbes { get; set; } = true; /// /// Gets or sets a value indicating whether to enable membership eviction of silos when in a state of `Joining` or `Created` for longer than MaxJoinAttemptTime /// public bool EvictWhenMaxJoinAttemptTimeExceeded { get; set; } = true; } } ================================================ FILE: src/Orleans.Core/Configuration/Options/ClusterOptions.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Configures the Orleans cluster. /// public class ClusterOptions { /// /// The default value of . /// public const string DefaultClusterId = "default"; /// /// The default value of . /// public const string DefaultServiceId = "default"; /// /// Default cluster id for development clusters. /// internal const string DevelopmentClusterId = "dev"; /// /// Default service id for development clusters. /// internal const string DevelopmentServiceId = "dev"; /// /// Gets or sets the cluster identity. This used to be called DeploymentId before Orleans 2.0 name. /// public string ClusterId { get; set; } = DefaultClusterId; /// /// Gets or sets a unique identifier for this service, which should survive deployment and redeployment, where as might not. /// public string ServiceId { get; set; } = DefaultServiceId; } /// /// Validator for /// public class ClusterOptionsValidator : IConfigurationValidator { private readonly ClusterOptions options; /// /// Initializes a new instance of the class. /// /// /// The options. /// public ClusterOptionsValidator(IOptions options) { this.options = options.Value; } /// public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(this.options.ClusterId)) { throw new OrleansConfigurationException( $"Configuration for {nameof(ClusterOptions)} is invalid. " + $"A non-empty value for {nameof(options.ClusterId)} is required. " + $"See {Constants.TroubleshootingHelpLink} for more information."); } if (string.IsNullOrWhiteSpace(this.options.ServiceId)) { throw new OrleansConfigurationException( $"Configuration for {nameof(ClusterOptions)} is invalid. " + $"A non-empty value for {nameof(options.ServiceId)} is required. " + $"See {Constants.TroubleshootingHelpLink} for more information."); } } } } ================================================ FILE: src/Orleans.Core/Configuration/Options/GatewayOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Options for configuring how clients interact with gateway endpoints. /// public class GatewayOptions { /// /// Gets or sets the period of time between refreshing the list of active gateways. /// /// The list of active gateways will be refreshed every minute by default. public TimeSpan GatewayListRefreshPeriod { get; set; } = TimeSpan.FromMinutes(1); /// /// Default preferred gateway index,. Value -1 means prefer no gateway /// public const int DEFAULT_PREFERED_GATEWAY_INDEX = -1; /// /// Gets or sets the index of the preferred gateway within the list of active gateways. /// /// Set this value to its default value, -1, to disable this functionality. /// No gateway is preferred by default. public int PreferredGatewayIndex { get; set; } = DEFAULT_PREFERED_GATEWAY_INDEX; } } ================================================ FILE: src/Orleans.Core/Configuration/Options/GrainVersioningOptions.cs ================================================ using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Configuration { /// /// Versioning options govern grain implementation selection in heterogeneous deployments. /// public class GrainVersioningOptions { /// /// Gets or sets the name of the default strategy used to determine grain compatibility in heterogeneous deployments. /// /// The strategy is used by default. public string DefaultCompatibilityStrategy { get; set; } = nameof(BackwardCompatible); /// /// Gets or sets the name of the default strategy for selecting grain versions in heterogeneous deployments. /// /// The strategy is used by default. public string DefaultVersionSelectorStrategy { get; set; } = nameof(AllCompatibleVersions); } } ================================================ FILE: src/Orleans.Core/Configuration/Options/LoadSheddingOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Options relating to load shedding. /// public class LoadSheddingOptions { /// /// The default value for . /// internal const int DefaultCpuThreshold = 95; /// /// The default value for . /// internal const int DefaultMemoryThreshold = 90; /// /// Gets or sets a value indicating whether load shedding in the client gateway and stream providers is enabled. /// The default value is , meaning that load shedding is disabled. /// /// Load shedding is disabled by default. public bool LoadSheddingEnabled { get; set; } /// /// Gets or sets the CPU utilization, expressed as a value between 0 and 100, at which load shedding begins. /// Note that this value is in %, so valid values range from 1 to 100, and a reasonable value is typically between 80 and 95. /// This value is ignored if load shedding is disabled, which is the default. /// /// Load shedding begins at a CPU utilization of 95% by default, if load shedding is enabled. /// This property is deprecated. Use instead. [Obsolete($"Use {nameof(CpuThreshold)} instead.", error: true)] public int LoadSheddingLimit { get => CpuThreshold; set => CpuThreshold = value; } /// /// Gets or sets the CPU utilization, expressed as a value between 0 and 100, at which load shedding begins. /// Note that this value is in %, so valid values range from 1 to 100, and a reasonable value is typically between 80 and 95. /// This value is ignored if load shedding is disabled, which is the default. /// /// Load shedding begins at a CPU utilization of 95% by default, if load shedding is enabled. public int CpuThreshold { get; set; } = DefaultCpuThreshold; /// /// Gets or sets the memory utilization, expressed as a value between 0 and 100, at which load shedding begins. /// Note that this value is in %, so valid values range from 1 to 100, and a reasonable value is typically between 80 and 95. /// This value is ignored if load shedding is disabled, which is the default. /// /// Load shedding begins at a memory utilization of 90% by default, if load shedding is enabled. public int MemoryThreshold { get; set; } = DefaultMemoryThreshold; } } ================================================ FILE: src/Orleans.Core/Configuration/Options/MessagingOptions.cs ================================================ using System; using System.Diagnostics; namespace Orleans.Configuration { /// /// Specifies global messaging options that are common to client and silo. /// public abstract class MessagingOptions { /// /// The value. /// private TimeSpan _responseTimeout = TimeSpan.FromSeconds(30); /// /// Gets or sets the default timeout before a request is assumed to have failed. /// /// /// Requests will timeout after 30 seconds by default. public TimeSpan ResponseTimeout { get { return Debugger.IsAttached ? ResponseTimeoutWithDebugger : _responseTimeout; } set { this._responseTimeout = value; } } /// /// Gets or sets the effective value to use when a debugger is attached. /// /// Requests will timeout after 30 minutes when a debugger is attached, by default. public TimeSpan ResponseTimeoutWithDebugger { get; set; } = TimeSpan.FromMinutes(30); /// /// Gets or sets a value indicating whether messages should be dropped once they expire, that is if it was not delivered /// to the destination before it has timed out on the sender. /// /// Messages are dropped once they expire, by default. public bool DropExpiredMessages { get; set; } = true; /// /// The maximum number of times a message send attempt will be retried. /// internal const short DEFAULT_MAX_MESSAGE_SEND_RETRIES = 1; /// /// The maximum size, in bytes, of the header for a message. The runtime will forcibly close the connection /// if the header size is greater than this value. /// /// The maximum message header size is 10 MB by default. public int MaxMessageHeaderSize { get; set; } = 10 * 1024 * 1024; /// /// The maximum size, in bytes, of the body for a message. The runtime will forcibly close the connection /// if the body size is greater than this value. /// /// The maximum message body size is 100 MB by default. public int MaxMessageBodySize { get; set; } = 100 * 1024 * 1024; /// /// Gets the response timeout underlying the property, without debugger checks. /// internal TimeSpan ConfiguredResponseTimeout => _responseTimeout; /// /// Whether request cancellation should be attempted when a request times out. /// /// /// Request cancellation may involve sending a cancellation message to the silo which hosts the target grain. /// Defaults to . /// public bool CancelRequestOnTimeout { get; set; } /// /// Whether local calls should be cancelled immediately when cancellation is signaled () /// rather than waiting for callees to acknowledge cancellation before a call is considered cancelled (). /// /// /// Defaults to . /// public bool WaitForCancellationAcknowledgement { get; set; } /// /// Whether request cancellation should be attempted when a status update is received for an unknown request. /// /// /// A status update for an unknown request likely indicates that the request previously timed out and was subsequently dropped. /// Request cancellation may involve sending a cancellation message to the silo which hosts the target grain. /// If the remote callee does not support cancellation, this setting has no effect. /// Defaults to . /// public bool CancelUnknownRequestOnStatusUpdate { get; set; } } } ================================================ FILE: src/Orleans.Core/Configuration/Options/StaticGatewayListProviderOptions.cs ================================================ using System; using System.Collections.Generic; using Orleans.Hosting; namespace Orleans.Configuration { /// /// Options for configuring a static list of gateways. /// /// > /// See for more information. /// public class StaticGatewayListProviderOptions { /// /// Gets or sets the list of gateway addresses. /// public List Gateways { get; set; } = new List(); } } ================================================ FILE: src/Orleans.Core/Configuration/Options/TypeManagementOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Type management settings for in place upgrade. /// public class TypeManagementOptions { /// /// The number of seconds to refresh the cluster grain interface map /// public TimeSpan TypeMapRefreshInterval { get; set; } = DEFAULT_REFRESH_CLUSTER_INTERFACEMAP_TIME; public static readonly TimeSpan DEFAULT_REFRESH_CLUSTER_INTERFACEMAP_TIME = TimeSpan.FromMinutes(1); } } ================================================ FILE: src/Orleans.Core/Configuration/OptionsOverrides.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration.Overrides { /// /// Functionality for overriding options using named options, falling back to the default (unnamed) value. /// public static class OptionsOverrides { /// /// Gets which may have been overridden on a per-provider basis. /// Note: This is intended for migration purposes as a means to handle previously inconsistent behaviors in how providers used ServiceId and ClusterId. /// public static IOptions GetProviderClusterOptions(this IServiceProvider services, string providerName) => services.GetOverridableOption(providerName); /// /// Gets option that can be overridden by named service. /// private static IOptions GetOverridableOption(this IServiceProvider services, string key) where TOptions : class, new() { TOptions option = services.GetKeyedService(key); return option != null ? Options.Create(option) : services.GetRequiredService>(); } } } ================================================ FILE: src/Orleans.Core/Configuration/ServiceCollectionExtensions.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Configuration.Internal { /// /// Extension methods for configuring dependency injection. /// public static class ServiceCollectionExtensions { /// /// Registers an existing registration of as a provider of service type . /// /// The service type being provided. /// The implementation of . /// The service collection. public static void AddFromExisting(this IServiceCollection services) where TImplementation : TService { services.AddFromExisting(typeof(TService), typeof(TImplementation)); } /// /// Registers an existing registration of as a provider of service type . /// /// The service collection. /// The service type being provided. /// The implementation of . public static void AddFromExisting(this IServiceCollection services, Type service, Type implementation) { ServiceDescriptor registration = null; foreach (var descriptor in services) { if (descriptor.ServiceType == implementation) { registration = descriptor; break; } } if (registration is null) { throw new ArgumentNullException(nameof(implementation), $"Unable to find previously registered ServiceType of '{implementation.FullName}'"); } var newRegistration = new ServiceDescriptor( service, sp => sp.GetRequiredService(implementation), registration.Lifetime); services.Add(newRegistration); } /// /// Registers an existing registration of as a provider of if there are no existing implementations. /// /// The service type being provided. /// The implementation of . /// The service collection. public static void TryAddFromExisting(this IServiceCollection services) where TImplementation : TService { if (services.All(service => service.ServiceType != typeof(TService))) { services.AddFromExisting(); } } } } ================================================ FILE: src/Orleans.Core/Configuration/Validators/ClientClusteringValidator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Messaging; using Orleans.Runtime; namespace Orleans.Configuration.Validators { /// /// Validator for client-side clustering. /// internal class ClientClusteringValidator : IConfigurationValidator { /// /// The error message displayed when clustering is misconfigured. /// internal const string ClusteringNotConfigured = "Clustering has not been configured. Configure clustering using one of the clustering packages, such as:" + "\n * Microsoft.Orleans.Clustering.AzureStorage" + "\n * Microsoft.Orleans.Clustering.AdoNet for ADO.NET systems such as SQL Server, MySQL, PostgreSQL, and Oracle" + "\n * Microsoft.Orleans.Clustering.DynamoDB" + "\n * Microsoft.Orleans.Clustering.Consul" + "\n * Microsoft.Orleans.Clustering.ZooKeeper" + "\n * Others, see: https://www.nuget.org/packages?q=Microsoft.Orleans.Clustering."; /// /// The service provider. /// private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public ClientClusteringValidator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// public void ValidateConfiguration() { var gatewayProvider = _serviceProvider.GetService(); if (gatewayProvider == null) { throw new OrleansConfigurationException(ClusteringNotConfigured); } } } } ================================================ FILE: src/Orleans.Core/Configuration/Validators/LoadSheddingValidator.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration.Validators; /// /// Validates configuration. /// /// /// Initializes a new instance of the class. /// /// /// The load shedding options. /// internal class LoadSheddingValidator(IOptions loadSheddingOptions) : IConfigurationValidator { private readonly LoadSheddingOptions _loadSheddingOptions = loadSheddingOptions.Value; /// public void ValidateConfiguration() { // When Load Shedding is disabled, don't validate configuration. if (!_loadSheddingOptions.LoadSheddingEnabled) { return; } if (_loadSheddingOptions.CpuThreshold > 100 || _loadSheddingOptions.CpuThreshold <= 0) { throw new OrleansConfigurationException($"Limit '{nameof(LoadSheddingOptions)}.{nameof(LoadSheddingOptions.CpuThreshold)}' must be greater than 0% and less than or equal to 100%."); } if (_loadSheddingOptions.MemoryThreshold > 100 || _loadSheddingOptions.MemoryThreshold <= 0) { throw new OrleansConfigurationException($"Limit '{nameof(LoadSheddingOptions)}.{nameof(LoadSheddingOptions.MemoryThreshold)}' must be greater than 0% and less than or equal to 100%."); } } } ================================================ FILE: src/Orleans.Core/Configuration/Validators/SerializerConfigurationValidator.cs ================================================ using System; using System.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; namespace Orleans { /// /// Validates serializer configuration. /// public class SerializerConfigurationValidator : IConfigurationValidator { private readonly ICodecProvider _codecProvider; private readonly TypeManifestOptions _options; private readonly bool _enabled; /// /// Initializes a new instance of the class. /// /// /// The codec provider. /// /// /// The type manifest options. /// /// /// The service provider. /// public SerializerConfigurationValidator(ICodecProvider codecProvider, IOptions options, IServiceProvider serviceProvider) { _codecProvider = codecProvider; _options = options.Value; var configEnabled = _options.EnableConfigurationAnalysis; if (configEnabled.HasValue) { _enabled = configEnabled.Value; } else { // Enable in development envioronment by default. var environment = serviceProvider.GetService(); _enabled = environment is not null && environment.IsDevelopment(); } } void IConfigurationValidator.ValidateConfiguration() { if (!_enabled) { return; } var complaints = SerializerConfigurationAnalyzer.AnalyzeSerializerAvailability(_codecProvider, _options); if (complaints.Count > 0) { var result = new StringBuilder(); result.AppendLine("Found unserializable or uncopyable types which are being referenced in grain interface signatures:"); foreach (var (type, complaint) in complaints) { result.Append($"Type: {type}"); if (!complaint.HasSerializer) { result.Append(" has no serializer"); if (!complaint.HasCopier) { result.Append(" or copier"); } } else if (!complaint.HasCopier) { result.Append(" has no copier"); } result.Append(" and was referenced by the following:"); foreach (var (declaringType, methodList) in complaint.Methods) { result.Append($"\n\t* {RuntimeTypeNameFormatter.Format(declaringType)} methods: {string.Join(", ", methodList)}"); } result.AppendLine(); } result.AppendLine("Ensure that all types which are used in grain interfaces have serializers available for them."); result.AppendLine("Applying the [GenerateSerializer] attribute to your type and adding [Id(x)] attributes to serializable properties and fields is the simplest way to accomplish this."); result.AppendLine("Alternatively, for types which are outside of your control, serializers may have to be manually crafted, potentially using surrogate types."); throw new OrleansConfigurationException(result.ToString()); } } } } ================================================ FILE: src/Orleans.Core/Core/ClientBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// Builder for configuring an Orleans client. /// public class ClientBuilder : IClientBuilder { /// /// Initializes a new instance of the class. /// /// /// The service collection. /// public ClientBuilder(IServiceCollection services, IConfiguration configuration) { Services = services; Configuration = configuration; DefaultClientServices.AddDefaultServices(this); } /// public IServiceCollection Services { get; } /// public IConfiguration Configuration { get; } } } ================================================ FILE: src/Orleans.Core/Core/ClientBuilderExtensions.cs ================================================ using System; using System.Linq; using System.Net; using System.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Runtime; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Orleans.Hosting { /// /// Extension methods for . /// public static class ClientBuilderExtensions { /// /// Configures the provided delegate as a connection retry filter, used to determine whether initial connection to the Orleans cluster should be retried after a failure. /// /// The host builder. /// The connection retry filter. /// The same instance of the for chaining. public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder, Func> connectionRetryFilter) { return builder.ConfigureServices(collection => collection.AddSingleton(new DelegateConnectionRetryFilter(connectionRetryFilter))); } /// /// Configures the provided delegate as a connection retry filter, used to determine whether initial connection to the Orleans cluster should be retried after a failure. /// /// The host builder. /// The connection retry filter. /// The same instance of the for chaining. public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder, IClientConnectionRetryFilter connectionRetryFilter) { return builder.ConfigureServices(collection => collection.AddSingleton(connectionRetryFilter)); } /// /// Configures the provided type as a connection retry filter, used to determine whether initial connection to the Orleans cluster should be retried after a failure. /// /// The host builder. /// The same instance of the for chaining. public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder) where TConnectionRetryFilter : class, IClientConnectionRetryFilter { return builder.ConfigureServices(collection => collection.AddSingleton()); } private sealed class DelegateConnectionRetryFilter : IClientConnectionRetryFilter { private readonly Func> _filter; public DelegateConnectionRetryFilter(Func> connectionRetryFilter) => _filter = connectionRetryFilter ?? throw new ArgumentNullException(nameof(connectionRetryFilter)); public Task ShouldRetryConnectionAttempt(Exception exception, CancellationToken cancellationToken) => _filter(exception, cancellationToken); } /// /// Adds services to the container. This can be called multiple times and the results will be additive. /// /// The host builder. /// /// The same instance of the for chaining. public static IClientBuilder ConfigureServices(this IClientBuilder builder, Action configureDelegate) { if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); configureDelegate(builder.Services); return builder; } /// /// Registers an action used to configure a particular type of options. /// /// The options type to be configured. /// The host builder. /// The action used to configure the options. /// The client builder. public static IClientBuilder Configure(this IClientBuilder builder, Action configureOptions) where TOptions : class { return builder.ConfigureServices(services => services.Configure(configureOptions)); } /// /// Registers a configuration instance which will bind against. /// /// The options type to be configured. /// The host builder. /// The configuration. /// The client builder. public static IClientBuilder Configure(this IClientBuilder builder, IConfiguration configuration) where TOptions : class { return builder.ConfigureServices(services => services.AddOptions().Bind(configuration)); } /// /// Registers a event handler. /// public static IClientBuilder AddGatewayCountChangedHandler(this IClientBuilder builder, GatewayCountChangedHandler handler) { builder.ConfigureServices(services => services.AddSingleton(handler)); return builder; } /// /// Registers a event handler. /// public static IClientBuilder AddGatewayCountChangedHandler(this IClientBuilder builder, Func handlerFactory) { builder.ConfigureServices(services => services.AddSingleton(handlerFactory)); return builder; } /// /// Registers a cluster connection status observer. /// public static IClientBuilder AddClusterConnectionStatusObserver(this IClientBuilder builder, TObserver observer) where TObserver : IClusterConnectionStatusObserver { builder.Services.AddSingleton(observer); return builder; } /// /// Registers a cluster connection status observer. /// public static IClientBuilder AddClusterConnectionStatusObserver(this IClientBuilder builder) where TObserver : class, IClusterConnectionStatusObserver { builder.Services.AddSingleton(); return builder; } /// /// Registers a event handler. /// /// The builder. /// The handler. /// The builder. public static IClientBuilder AddClusterConnectionLostHandler(this IClientBuilder builder, ConnectionToClusterLostHandler handler) { builder.ConfigureServices(services => services.AddSingleton(handler)); return builder; } /// /// Registers a event handler. /// /// The builder. /// The handler factory. /// The builder. public static IClientBuilder AddClusterConnectionLostHandler(this IClientBuilder builder, Func handlerFactory) { builder.ConfigureServices(services => services.AddSingleton(handlerFactory)); return builder; } /// /// Add propagation through grain calls. /// Note: according to activity will be created only when any listener for activity exists and returns . /// /// The builder. /// The builder. public static IClientBuilder AddActivityPropagation(this IClientBuilder builder) { builder.Services.TryAddSingleton(DistributedContextPropagator.Current); return builder .AddOutgoingGrainCallFilter() .AddIncomingGrainCallFilter(); } /// /// Configures the client to connect to a silo on the localhost. /// /// /// The client builder. /// /// /// The local silo's gateway port. /// /// /// The service id. /// /// /// The cluster id. /// /// /// The . /// public static IClientBuilder UseLocalhostClustering( this IClientBuilder builder, int gatewayPort = 30000, string serviceId = ClusterOptions.DevelopmentServiceId, string clusterId = ClusterOptions.DevelopmentClusterId) { return builder.UseLocalhostClustering(new [] {gatewayPort}, serviceId, clusterId); } /// /// Configures the client to connect to a silo on the localhost. /// /// /// The client builder. /// /// /// The local silo gateway ports. /// /// /// The service id. /// /// /// The cluster id. /// /// /// The . /// public static IClientBuilder UseLocalhostClustering(this IClientBuilder builder, int[] gatewayPorts, string serviceId = ClusterOptions.DevelopmentServiceId, string clusterId = ClusterOptions.DevelopmentClusterId) { return builder.UseStaticClustering(gatewayPorts.Select(p => new IPEndPoint(IPAddress.Loopback, p)).ToArray()) .ConfigureServices(services => { // If the caller did not override service id or cluster id, configure default values as a fallback. if (string.Equals(serviceId, ClusterOptions.DevelopmentServiceId) && string.Equals(clusterId, ClusterOptions.DevelopmentClusterId)) { services.PostConfigure(options => { if (string.IsNullOrWhiteSpace(options.ClusterId)) options.ClusterId = ClusterOptions.DevelopmentClusterId; if (string.IsNullOrWhiteSpace(options.ServiceId)) options.ServiceId = ClusterOptions.DevelopmentServiceId; }); } else { services.Configure(options => { options.ServiceId = serviceId; options.ClusterId = clusterId; }); } }); } /// /// Configures the client to use static clustering. /// /// /// The client builder. /// /// /// The gateway endpoints. /// /// /// The . /// public static IClientBuilder UseStaticClustering(this IClientBuilder builder, params IPEndPoint[] endpoints) { return builder.UseStaticClustering(options => options.Gateways = endpoints.Select(ep => ep.ToGatewayUri()).ToList()); } /// /// Configures the client to use static clustering. /// /// /// The client builder. /// /// /// The configure Options. /// /// /// The . /// public static IClientBuilder UseStaticClustering(this IClientBuilder builder, Action configureOptions) { return builder.ConfigureServices( collection => { if (configureOptions != null) { collection.Configure(configureOptions); } collection.AddSingleton() .ConfigureFormatter(); }); } /// /// Configures the client to use static clustering. /// /// /// The client builder. /// /// /// The configure Options. /// /// /// The . /// public static IClientBuilder UseStaticClustering(this IClientBuilder builder, Action> configureOptions) { return builder.ConfigureServices( collection => { configureOptions?.Invoke(collection.AddOptions()); collection.AddSingleton() .ConfigureFormatter(); }); } } } ================================================ FILE: src/Orleans.Core/Core/ClientBuilderGrainCallFilterExtensions.cs ================================================ namespace Orleans.Hosting; /// /// Extensions for configuring grain call filters. /// public static class ClientBuilderGrainCallFilterExtensions { /// /// Adds an to the filter pipeline. /// /// The builder. /// The filter. /// The builder. public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder, IIncomingGrainCallFilter filter) { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The builder. /// The builder. public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder) where TImplementation : class, IIncomingGrainCallFilter { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter()); } /// /// Adds an to the filter pipeline via a delegate. /// /// The builder. /// The filter. /// The builder. public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder, IncomingGrainCallFilterDelegate filter) { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The builder. /// The filter. /// The . public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder, IOutgoingGrainCallFilter filter) { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The builder. /// The . public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder) where TImplementation : class, IOutgoingGrainCallFilter { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter()); } /// /// Adds an to the filter pipeline via a delegate. /// /// The builder. /// The filter. /// The . public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder, OutgoingGrainCallFilterDelegate filter) { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter(filter)); } } ================================================ FILE: src/Orleans.Core/Core/ClusterClient.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Microsoft.Extensions.Hosting; namespace Orleans { /// /// Client for communicating with clusters of Orleans silos. /// internal partial class ClusterClient : IInternalClusterClient, IHostedService { private readonly OutsideRuntimeClient _runtimeClient; private readonly ILogger _logger; private readonly ClusterClientLifecycle _clusterClientLifecycle; /// /// Initializes a new instance of the class. /// /// The service provider. /// The runtime client. /// Logger factory used to create loggers /// Messaging parameters public ClusterClient(IServiceProvider serviceProvider, OutsideRuntimeClient runtimeClient, ILoggerFactory loggerFactory, IOptions clientMessagingOptions) { ValidateSystemConfiguration(serviceProvider); runtimeClient.ConsumeServices(); _runtimeClient = runtimeClient; _logger = loggerFactory.CreateLogger(); _clusterClientLifecycle = new ClusterClientLifecycle(_logger); // register all lifecycle participants IEnumerable> lifecycleParticipants = ServiceProvider.GetServices>(); foreach (var participant in lifecycleParticipants) { participant?.Participate(_clusterClientLifecycle); } static void ValidateSystemConfiguration(IServiceProvider serviceProvider) { var validators = serviceProvider.GetServices(); foreach (var validator in validators) { validator.ValidateConfiguration(); } } } /// public IServiceProvider ServiceProvider => _runtimeClient.ServiceProvider; /// public async Task StartAsync(CancellationToken cancellationToken) { await _runtimeClient.StartAsync(cancellationToken).ConfigureAwait(false); await _clusterClientLifecycle.OnStart(cancellationToken).ConfigureAwait(false); } /// public async Task StopAsync(CancellationToken cancellationToken) { try { LogClientShuttingDown(_logger); await _clusterClientLifecycle.OnStop(cancellationToken).ConfigureAwait(false); await _runtimeClient.StopAsync(cancellationToken).WaitAsync(cancellationToken); } finally { LogClientShutdownCompleted(_logger); } } /// public TGrainInterface GetGrain(Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey => _runtimeClient.InternalGrainFactory.GetGrain(primaryKey, grainClassNamePrefix); /// public TGrainInterface GetGrain(long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey => _runtimeClient.InternalGrainFactory.GetGrain(primaryKey, grainClassNamePrefix); /// public TGrainInterface GetGrain(string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey => _runtimeClient.InternalGrainFactory.GetGrain(primaryKey, grainClassNamePrefix); /// public TGrainInterface GetGrain(Guid primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey => _runtimeClient.InternalGrainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix); /// public TGrainInterface GetGrain(long primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey => _runtimeClient.InternalGrainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix); /// public TGrainObserverInterface CreateObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver => ((IGrainFactory)_runtimeClient.InternalGrainFactory).CreateObjectReference(obj); /// public void DeleteObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver => _runtimeClient.InternalGrainFactory.DeleteObjectReference(obj); /// public TGrainObserverInterface CreateObjectReference(IAddressable obj) where TGrainObserverInterface : IAddressable => _runtimeClient.InternalGrainFactory.CreateObjectReference(obj); /// TGrainInterface IInternalGrainFactory.GetSystemTarget(GrainType grainType, SiloAddress destination) => _runtimeClient.InternalGrainFactory.GetSystemTarget(grainType, destination); public TGrainInterface GetSystemTarget(GrainId grainId) where TGrainInterface : ISystemTarget => _runtimeClient.InternalGrainFactory.GetSystemTarget(grainId); /// TGrainInterface IInternalGrainFactory.Cast(IAddressable grain) => _runtimeClient.InternalGrainFactory.Cast(grain); /// TGrainInterface IGrainFactory.GetGrain(GrainId grainId) => _runtimeClient.InternalGrainFactory.GetGrain(grainId); /// IAddressable IGrainFactory.GetGrain(GrainId grainId) => _runtimeClient.InternalGrainFactory.GetGrain(grainId); /// public IGrain GetGrain(Type grainInterfaceType, string grainPrimaryKey) => _runtimeClient.InternalGrainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); /// public IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey) => _runtimeClient.InternalGrainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); /// public IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey) => _runtimeClient.InternalGrainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); /// public IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey, string keyExtension) => _runtimeClient.InternalGrainFactory.GetGrain(grainInterfaceType, grainPrimaryKey, keyExtension); /// public IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey, string keyExtension) => _runtimeClient.InternalGrainFactory.GetGrain(grainInterfaceType, grainPrimaryKey, keyExtension); /// public object Cast(IAddressable grain, Type outputGrainInterfaceType) => _runtimeClient.InternalGrainFactory.Cast(grain, outputGrainInterfaceType); /// public IAddressable GetGrain(GrainId grainId, GrainInterfaceType interfaceType) => _runtimeClient.InternalGrainFactory.GetGrain(grainId, interfaceType); /// public IAddressable GetGrain(Type interfaceType, IdSpan grainKey, string grainClassNamePrefix) => _runtimeClient.InternalGrainFactory.GetGrain(interfaceType, grainKey, grainClassNamePrefix); /// public IAddressable GetGrain(Type interfaceType, IdSpan grainKey) => _runtimeClient.InternalGrainFactory.GetGrain(interfaceType, grainKey); [LoggerMessage( Level = LogLevel.Information, Message = "Client shutting down." )] private static partial void LogClientShuttingDown(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Client shutdown completed." )] private static partial void LogClientShutdownCompleted(ILogger logger); } } ================================================ FILE: src/Orleans.Core/Core/DefaultClientServices.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.Configuration.Validators; using Orleans.GrainReferences; using Orleans.Hosting; using Orleans.Messaging; using Orleans.Metadata; using Orleans.Networking.Shared; using Orleans.Placement.Repartitioning; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Messaging; using Orleans.Runtime.Versions; using Orleans.Serialization; using Orleans.Serialization.Cloning; using Orleans.Serialization.Internal; using Orleans.Serialization.Serializers; using Orleans.Statistics; namespace Orleans { /// /// Configures the default services for a client. /// internal static class DefaultClientServices { private static readonly ServiceDescriptor ServiceDescriptor = new(typeof(ServicesAdded), new ServicesAdded()); /// /// Configures the default services for a client. /// /// The client builder. public static void AddDefaultServices(IClientBuilder builder) { var services = builder.Services; if (services.Contains(ServiceDescriptor)) { return; } services.Add(ServiceDescriptor); // Common services services.AddLogging(); services.AddOptions(); services.AddMetrics(); services.TryAddSingleton(TimeProvider.System); services.TryAddSingleton(); // Options logging services.TryAddSingleton(typeof(IOptionFormatter<>), typeof(DefaultOptionsFormatter<>)); services.TryAddSingleton(typeof(IOptionFormatterResolver<>), typeof(DefaultOptionsFormatterResolver<>)); services.AddSingleton(); services.AddFromExisting, ClientOptionsLogger>(); // Lifecycle services.AddSingleton>(); services.TryAddFromExisting>(); services.AddFromExisting, ServiceLifecycle>(); // Statistics services.AddSingleton(); #pragma warning disable 618 services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); #pragma warning restore 618 services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.TryAddFromExisting(); services.TryAddFromExisting(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddFromExisting(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddFromExisting(); services.AddFromExisting(); services.AddTransient>(static sp => sp.GetRequiredService>()); services.TryAddSingleton(); services.AddSingleton, DefaultGrainTypeOptionsProvider>(); // Add default option formatter if none is configured, for options which are required to be configured services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // TODO: abstract or move into some options. services.AddSingleton(); services.AddSingleton(); // Networking services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton, ConnectionManagerLifecycleAdapter>(); services.AddKeyedSingleton( ClientOutboundConnectionFactory.ServicesKey, (sp, key) => ActivatorUtilities.CreateInstance(sp)); services.AddSerializer(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton, ConfigureOrleansJsonSerializerOptions>(); services.AddSingleton(); services.TryAddTransient(sp => ActivatorUtilities.CreateInstance( sp, sp.GetRequiredService>().Value)); services.TryAddSingleton(); services.TryAddSingleton(sp => sp.GetRequiredService().MessageCenter); services.TryAddFromExisting(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Type metadata services.AddSingleton(); services.AddFromExisting(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); ApplyConfiguration(builder); } private static void ApplyConfiguration(IClientBuilder builder) { var services = builder.Services; var cfg = builder.Configuration.GetSection("Orleans"); var knownProviderTypes = GetRegisteredProviders(); services.Configure(cfg); services.Configure(cfg.GetSection("Messaging")); services.Configure(cfg.GetSection("Gateway")); if (bool.TryParse(cfg["EnableDistributedTracing"], out var enableDistributedTracing) && enableDistributedTracing) { builder.AddActivityPropagation(); } ApplySubsection(builder, cfg, knownProviderTypes, "Clustering"); ApplySubsection(builder, cfg, knownProviderTypes, "Reminders"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "BroadcastChannel"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "Streaming"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "GrainStorage"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "GrainDirectory"); static void ConfigureProvider( IClientBuilder builder, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string kind, string? name, IConfigurationSection configurationSection) { var providerType = configurationSection["ProviderType"] ?? "Default"; var provider = GetRequiredProvider(knownProviderTypes, kind, providerType); provider.Configure(builder, name, configurationSection); } static IProviderBuilder GetRequiredProvider(Dictionary<(string Kind, string Name), Type> knownProviderTypes, string kind, string name) { if (knownProviderTypes.TryGetValue((kind, name), out var type)) { var instance = Activator.CreateInstance(type); return instance as IProviderBuilder ?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder)}."); } var knownProvidersOfKind = knownProviderTypes .Where(kvp => string.Equals(kvp.Key.Kind, kind, StringComparison.OrdinalIgnoreCase)) .Select(kvp => kvp.Key.Name) .OrderBy(n => n) .ToList(); var knownProvidersMessage = knownProvidersOfKind.Count > 0 ? $" Known {kind} providers: {string.Join(", ", knownProvidersOfKind)}." : string.Empty; throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced by your application.{knownProvidersMessage}"); } static Dictionary<(string Kind, string Name), Type> GetRegisteredProviders() { var result = new Dictionary<(string, string), Type>(); foreach (var asm in ReferencedAssemblyProvider.GetRelevantAssemblies()) { foreach (var attr in asm.GetCustomAttributes()) { if (string.Equals(attr.Target, "Client")) { result[(attr.Kind, attr.Name)] = attr.Type; } } } return result; } static void ApplySubsection(IClientBuilder builder, IConfigurationSection cfg, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string sectionName) { if (cfg.GetSection(sectionName) is { } section && section.Exists()) { ConfigureProvider(builder, knownProviderTypes, sectionName, name: null, section); } } static void ApplyNamedSubsections(IClientBuilder builder, IConfigurationSection cfg, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string sectionName) { if (cfg.GetSection(sectionName) is { } section && section.Exists()) { foreach (var child in section.GetChildren()) { ConfigureProvider(builder, knownProviderTypes, sectionName, name: child.Key, child); } } } } internal partial class RootConfiguration { public IConfigurationSection? Clustering { get; set; } } /// /// A which allows any type from an assembly containing "Orleans" in its name to be allowed for the purposes of serialization and deserialization. /// private class AllowOrleansTypes : ITypeNameFilter { /// public bool? IsTypeNameAllowed(string typeName, string assemblyName) { if (assemblyName is { Length: > 0} && assemblyName.Contains("Orleans")) { return true; } return null; } } /// /// A marker type used to determine /// private class ServicesAdded { } } } ================================================ FILE: src/Orleans.Core/Core/GatewayCountChangedEventArgs.cs ================================================ using System; namespace Orleans { /// /// Handler for client disconnection from a cluster. /// /// The sender. /// The event arguments. public delegate void ConnectionToClusterLostHandler(object sender, EventArgs e); /// /// Handler for the number of gateways. /// /// The sender. /// The event arguments. public delegate void GatewayCountChangedHandler(object sender, GatewayCountChangedEventArgs e); /// /// Event arguments for gateway connectivity events. /// public class GatewayCountChangedEventArgs : EventArgs { /// /// Gets the number of gateways which this client is currently connected to. /// public int NumberOfConnectedGateways { get; } /// /// Gets the number of gateways which this client was currently connected to before this event. /// public int PreviousNumberOfConnectedGateways { get; } /// /// Helper to detect situations where cluster connectivity was regained. /// public bool ConnectionRecovered => this.NumberOfConnectedGateways > 0 && this.PreviousNumberOfConnectedGateways <= 0; /// /// Initializes a new instance of the class. /// /// /// The current number of connected gateways. /// /// /// The previous number of connected gateways. /// public GatewayCountChangedEventArgs(int currentNumberOfConnectedGateways, int previousNumberOfConnectedGateways) { this.NumberOfConnectedGateways = currentNumberOfConnectedGateways; this.PreviousNumberOfConnectedGateways = previousNumberOfConnectedGateways; } } } ================================================ FILE: src/Orleans.Core/Core/GrainCallFilterServiceCollectionExtensions.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// extensions. /// public static class GrainCallFilterServiceCollectionExtensions { /// /// Adds an to the filter pipeline. /// /// The service collection. /// The filter. /// The service collection. [Obsolete("Use ISiloBuilder." + nameof(AddIncomingGrainCallFilter), error: true)] public static IServiceCollection AddGrainCallFilter(this IServiceCollection services, IIncomingGrainCallFilter filter) { throw new NotSupportedException($"{nameof(AddGrainCallFilter)} is no longer supported. Use ISiloBuilder.AddIncomingGrainCallFilter(...) instead."); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The service collection. /// The service collection. [Obsolete("Use ISiloBuilder." + nameof(AddIncomingGrainCallFilter), error: true)] public static IServiceCollection AddGrainCallFilter(this IServiceCollection services) where TImplementation : class, IIncomingGrainCallFilter { throw new NotSupportedException($"{nameof(AddGrainCallFilter)} is no longer supported. Use ISiloBuilder.AddIncomingGrainCallFilter(...) instead."); } /// /// Adds an to the filter pipeline via a delegate. /// /// The service collection. /// The filter. /// The service collection. [Obsolete("Use ISiloBuilder." + nameof(AddIncomingGrainCallFilter), error: true)] public static IServiceCollection AddGrainCallFilter(this IServiceCollection services, GrainCallFilterDelegate filter) { throw new NotSupportedException($"{nameof(AddGrainCallFilter)} is no longer supported. Use ISiloBuilder.AddIncomingGrainCallFilter(...) instead."); } /// /// Adds an to the filter pipeline. /// /// The service collection. /// The filter. /// The service collection. internal static IServiceCollection AddIncomingGrainCallFilter(this IServiceCollection services, IIncomingGrainCallFilter filter) { return services.AddSingleton(filter); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The service collection. /// The service collection. internal static IServiceCollection AddIncomingGrainCallFilter(this IServiceCollection services) where TImplementation : class, IIncomingGrainCallFilter { return services.AddSingleton(); } /// /// Adds an to the filter pipeline via a delegate. /// /// The service collection. /// The filter. /// The service collection. internal static IServiceCollection AddIncomingGrainCallFilter(this IServiceCollection services, IncomingGrainCallFilterDelegate filter) { return services.AddSingleton(new IncomingGrainCallFilterWrapper(filter)); } /// /// Adds an to the filter pipeline. /// /// The service collection. /// The filter. /// The service collection. internal static IServiceCollection AddOutgoingGrainCallFilter(this IServiceCollection services, IOutgoingGrainCallFilter filter) { return services.AddSingleton(filter); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The service collection. /// The service collection. internal static IServiceCollection AddOutgoingGrainCallFilter(this IServiceCollection services) where TImplementation : class, IOutgoingGrainCallFilter { return services.AddSingleton(); } /// /// Adds an to the filter pipeline via a delegate. /// /// The service collection. /// The filter. /// The service collection. internal static IServiceCollection AddOutgoingGrainCallFilter(this IServiceCollection services, OutgoingGrainCallFilterDelegate filter) { return services.AddSingleton(new OutgoingGrainCallFilterWrapper(filter)); } /// /// Adapts delegates to the interface. /// private class OutgoingGrainCallFilterWrapper : IOutgoingGrainCallFilter { private readonly OutgoingGrainCallFilterDelegate interceptor; /// /// Initializes a new instance of the class. /// /// /// The interceptor. /// public OutgoingGrainCallFilterWrapper(OutgoingGrainCallFilterDelegate interceptor) { this.interceptor = interceptor; } /// public Task Invoke(IOutgoingGrainCallContext context) => this.interceptor.Invoke(context); } /// /// Implements by delegating all calls to the provided delegate. /// private class IncomingGrainCallFilterWrapper : IIncomingGrainCallFilter { private readonly IncomingGrainCallFilterDelegate interceptor; /// /// Initializes a new instance of the class. /// /// /// The interceptor. /// public IncomingGrainCallFilterWrapper(IncomingGrainCallFilterDelegate interceptor) { this.interceptor = interceptor; } /// /// Invokes this filter. /// /// The grain call context. /// A representing the work performed. public Task Invoke(IIncomingGrainCallContext context) => this.interceptor.Invoke(context); } } } ================================================ FILE: src/Orleans.Core/Core/GrainFactory.cs ================================================ using System; using System.Collections.Generic; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans { /// /// Factory for accessing grains. /// internal class GrainFactory : IInternalGrainFactory { private GrainReferenceRuntime grainReferenceRuntime; /// /// The cache of typed system target references. /// private readonly Dictionary<(GrainId, Type), ISystemTarget> typedSystemTargetReferenceCache = new Dictionary<(GrainId, Type), ISystemTarget>(); private readonly GrainReferenceActivator referenceActivator; private readonly GrainInterfaceTypeResolver interfaceTypeResolver; private readonly GrainInterfaceTypeToGrainTypeResolver interfaceTypeToGrainTypeResolver; private readonly IRuntimeClient runtimeClient; public GrainFactory( IRuntimeClient runtimeClient, GrainReferenceActivator referenceActivator, GrainInterfaceTypeResolver interfaceTypeResolver, GrainInterfaceTypeToGrainTypeResolver interfaceToTypeResolver) { this.runtimeClient = runtimeClient; this.referenceActivator = referenceActivator; this.interfaceTypeResolver = interfaceTypeResolver; this.interfaceTypeToGrainTypeResolver = interfaceToTypeResolver; } private GrainReferenceRuntime GrainReferenceRuntime => this.grainReferenceRuntime ??= (GrainReferenceRuntime)this.runtimeClient.GrainReferenceRuntime; /// public TGrainInterface GetGrain(Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey { var grainKey = GrainIdKeyExtensions.CreateGuidKey(primaryKey); return (TGrainInterface)GetGrain(typeof(TGrainInterface), grainKey, grainClassNamePrefix: grainClassNamePrefix); } /// public TGrainInterface GetGrain(long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey { var grainKey = GrainIdKeyExtensions.CreateIntegerKey(primaryKey); return (TGrainInterface)GetGrain(typeof(TGrainInterface), grainKey, grainClassNamePrefix: grainClassNamePrefix); } /// public TGrainInterface GetGrain(string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey { ArgumentNullException.ThrowIfNullOrWhiteSpace(primaryKey); var grainKey = IdSpan.Create(primaryKey); return (TGrainInterface)GetGrain(typeof(TGrainInterface), grainKey, grainClassNamePrefix: grainClassNamePrefix); } /// public TGrainInterface GetGrain(Guid primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey { ValidateGrainKeyExtension(keyExtension); var grainKey = GrainIdKeyExtensions.CreateGuidKey(primaryKey, keyExtension); return (TGrainInterface)GetGrain(typeof(TGrainInterface), grainKey, grainClassNamePrefix: grainClassNamePrefix); } /// public TGrainInterface GetGrain(long primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey { ValidateGrainKeyExtension(keyExtension); var grainKey = GrainIdKeyExtensions.CreateIntegerKey(primaryKey, keyExtension); return (TGrainInterface)GetGrain(typeof(TGrainInterface), grainKey, grainClassNamePrefix: grainClassNamePrefix); } /// public TGrainObserverInterface CreateObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver { return this.CreateObjectReference((IAddressable)obj); } /// public void DeleteObjectReference( IGrainObserver obj) where TGrainObserverInterface : IGrainObserver { this.runtimeClient.DeleteObjectReference(obj); } /// public TGrainObserverInterface CreateObjectReference(IAddressable obj) where TGrainObserverInterface : IAddressable { return (TGrainObserverInterface)this.CreateObjectReference(typeof(TGrainObserverInterface), obj); } /// public TGrainInterface Cast(IAddressable grain) { var interfaceType = typeof(TGrainInterface); return (TGrainInterface)this.Cast(grain, interfaceType); } /// public object Cast(IAddressable grain, Type interfaceType) => this.GrainReferenceRuntime.Cast(grain, interfaceType); public TGrainInterface GetSystemTarget(GrainType grainType, SiloAddress destination) where TGrainInterface : ISystemTarget { var grainId = SystemTargetGrainId.Create(grainType, destination); return this.GetSystemTarget(grainId.GrainId); } /// public TGrainInterface GetSystemTarget(GrainId grainId) where TGrainInterface : ISystemTarget { ISystemTarget reference; ValueTuple key = ValueTuple.Create(grainId, typeof(TGrainInterface)); lock (this.typedSystemTargetReferenceCache) { if (this.typedSystemTargetReferenceCache.TryGetValue(key, out reference)) { return (TGrainInterface)reference; } reference = this.GetGrain(grainId); this.typedSystemTargetReferenceCache[key] = reference; return (TGrainInterface)reference; } } /// public TGrainInterface GetGrain(GrainId grainId) where TGrainInterface : IAddressable { return (TGrainInterface)this.CreateGrainReference(typeof(TGrainInterface), grainId); } /// public IAddressable GetGrain(GrainId grainId) => this.referenceActivator.CreateReference(grainId, default); /// public IGrain GetGrain(Type grainInterfaceType, Guid key) { var grainKey = GrainIdKeyExtensions.CreateGuidKey(key); return (IGrain)GetGrain(grainInterfaceType, grainKey, grainClassNamePrefix: null); } /// public IGrain GetGrain(Type grainInterfaceType, long key) { var grainKey = GrainIdKeyExtensions.CreateIntegerKey(key); return (IGrain)GetGrain(grainInterfaceType, grainKey, grainClassNamePrefix: null); } /// public IGrain GetGrain(Type grainInterfaceType, string key) { ArgumentNullException.ThrowIfNullOrWhiteSpace(key); var grainKey = IdSpan.Create(key); return (IGrain)GetGrain(grainInterfaceType, grainKey, grainClassNamePrefix: null); } /// public IGrain GetGrain(Type grainInterfaceType, Guid key, string keyExtension) { var grainKey = GrainIdKeyExtensions.CreateGuidKey(key, keyExtension); return (IGrain)GetGrain(grainInterfaceType, grainKey, grainClassNamePrefix: null); } /// public IGrain GetGrain(Type grainInterfaceType, long key, string keyExtension) { var grainKey = GrainIdKeyExtensions.CreateIntegerKey(key, keyExtension); return (IGrain)GetGrain(grainInterfaceType, grainKey, grainClassNamePrefix: null); } /// public IAddressable GetGrain(GrainId grainId, GrainInterfaceType interfaceType) { return this.referenceActivator.CreateReference(grainId, interfaceType); } /// public IAddressable GetGrain(Type interfaceType, IdSpan grainKey) => GetGrain(interfaceType, grainKey, null); /// /// Gets a grain reference which implements the specified grain interface type and has the specified grain key, without specifying the grain type directly. /// /// /// This method infers the most appropriate value based on the argument and optional argument. /// The type is responsible for determining the most appropriate grain type. /// /// The interface type which the returned grain reference will implement. /// The portion of the grain id. /// An optional grain class name prefix. /// A grain reference which implements the provided interface. public IAddressable GetGrain(Type interfaceType, IdSpan grainKey, string grainClassNamePrefix = null) { ArgumentNullException.ThrowIfNull(interfaceType); var grainInterfaceType = this.interfaceTypeResolver.GetGrainInterfaceType(interfaceType); GrainType grainType; if (!string.IsNullOrWhiteSpace(grainClassNamePrefix)) { grainType = this.interfaceTypeToGrainTypeResolver.GetGrainType(grainInterfaceType, grainClassNamePrefix); } else { grainType = this.interfaceTypeToGrainTypeResolver.GetGrainType(grainInterfaceType); } var grainId = GrainId.Create(grainType, grainKey); var grain = this.referenceActivator.CreateReference(grainId, grainInterfaceType); return grain; } /// /// Creates a grain reference. /// /// The interface type which the reference must implement.. /// The grain id which the reference will target. /// A grain reference. private object CreateGrainReference(Type interfaceType, GrainId grainId) { var grainInterfaceType = this.interfaceTypeResolver.GetGrainInterfaceType(interfaceType); return this.referenceActivator.CreateReference(grainId, grainInterfaceType); } /// /// Creates an object reference which points to the provided object. /// /// The interface type which the reference must implement.. /// The addressable object implementation. /// An object reference. private object CreateObjectReference(Type interfaceType, IAddressable obj) { if (!interfaceType.IsInterface) { throw new ArgumentException( $"The provided type parameter must be an interface. '{interfaceType.FullName}' is not an interface."); } if (!interfaceType.IsInstanceOfType(obj)) { throw new ArgumentException($"The provided object must implement '{interfaceType.FullName}'.", nameof(obj)); } return this.Cast(this.runtimeClient.CreateObjectReference(obj), interfaceType); } /// /// Validates the provided grain key extension. /// /// The grain key extension. /// The key is . /// The key is empty or contains only whitespace. private static void ValidateGrainKeyExtension(string keyExt) { if (!string.IsNullOrWhiteSpace(keyExt)) return; if (null == keyExt) { throw new ArgumentNullException(nameof(keyExt)); } throw new ArgumentException("Key extension is empty or white space.", nameof(keyExt)); } } } ================================================ FILE: src/Orleans.Core/Core/GrainInterfaceTypeToGrainTypeResolver.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using Orleans.Metadata; using Orleans.Runtime; using Orleans.Utilities; namespace Orleans { /// /// Associates s with a compatible . /// /// /// This is primarily intended for end-users calling methods without needing to be overly explicit. /// public class GrainInterfaceTypeToGrainTypeResolver { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly ConcurrentDictionary _genericMapping = new ConcurrentDictionary(); private readonly IClusterManifestProvider _clusterManifestProvider; private Cache _cache; /// /// Creates a new instance of the class. /// /// The cluster manifest provider. public GrainInterfaceTypeToGrainTypeResolver(IClusterManifestProvider clusterManifestProvider) { _clusterManifestProvider = clusterManifestProvider; } /// /// Returns the which supports the provided and which has an implementing type name beginning with the provided prefix string. /// public GrainType GetGrainType(GrainInterfaceType interfaceType, string prefix) { if (string.IsNullOrWhiteSpace(prefix)) { return GetGrainType(interfaceType); } GrainType result = default; GrainInterfaceType lookupType; if (GenericGrainInterfaceType.TryParse(interfaceType, out var genericInterface)) { lookupType = genericInterface.GetGenericGrainType().Value; } else { lookupType = interfaceType; } var cache = GetCache(); if (cache.Map.TryGetValue(lookupType, out var entry)) { var hasCandidate = false; foreach (var impl in entry.Implementations) { if (impl.Prefix.StartsWith(prefix, StringComparison.Ordinal)) { if (impl.Prefix.Length == prefix.Length) { // Exact matches take precedence result = impl.GrainType; break; } if (hasCandidate) { var candidates = string.Join(", ", entry.Implementations.Select(i => $"{i.GrainType} ({i.Prefix})")); throw new ArgumentException($"Unable to identify a single appropriate grain type for interface {interfaceType} with implementation prefix \"{prefix}\". Candidates: {candidates}"); } result = impl.GrainType; hasCandidate = true; } } } if (result.IsDefault) { throw new ArgumentException($"Could not find an implementation matching prefix \"{prefix}\" for interface {interfaceType}"); } if (GenericGrainType.TryParse(result, out var genericGrainType) && !genericGrainType.IsConstructed) { result = genericGrainType.GetConstructed(genericInterface); } return result; } /// /// Returns a which implements the provided . /// public GrainType GetGrainType(GrainInterfaceType interfaceType) { if (!TryGetGrainType(interfaceType, out var result)) { throw new ArgumentException($"Could not find an implementation for interface {interfaceType}"); } return result; } /// /// Resolves a which implements the provided , returning if an implementation was found; otherwise . /// /// if an implementation was found; otherwise . public bool TryGetGrainType(GrainInterfaceType interfaceType, out GrainType result) { result = default; var cache = GetCache(); if (cache.Map.TryGetValue(interfaceType, out var entry)) { if (!entry.PrimaryImplementation.IsDefault) { result = entry.PrimaryImplementation; } else if (entry.Implementations.Count == 1) { result = entry.Implementations[0].GrainType; } else if (entry.Implementations.Count > 1) { var candidates = string.Join(", ", entry.Implementations.Select(i => $"{i.GrainType} ({i.Prefix})")); throw new ArgumentException($"Unable to identify a single appropriate grain type for interface {interfaceType}. Candidates: {candidates}"); } else { // No implementations } } else if (_genericMapping.TryGetValue(interfaceType, out result)) { } else if (GenericGrainInterfaceType.TryParse(interfaceType, out var genericInterface) && genericInterface.IsConstructed) { var unconstructedInterface = genericInterface.GetGenericGrainType(); if (TryGetGrainType(unconstructedInterface.Value, out var unconstructed)) { if (GenericGrainType.TryParse(unconstructed, out var genericGrainType)) { if (genericGrainType.IsConstructed) { result = genericGrainType.GrainType; } else { result = genericGrainType.GetConstructed(genericInterface); } } else { result = unconstructed; } } _genericMapping[interfaceType] = result; } return !result.IsDefault; } /// /// Returns the cache, rebuilding it if it is out of date. /// /// The cache. private Cache GetCache() { if (_cache is Cache cache && cache.Version == _clusterManifestProvider.Current.Version) { return cache; } lock (_lockObj) { var manifest = _clusterManifestProvider.Current; cache = _cache; if (cache is not null && cache.Version == manifest.Version) { return cache; } return _cache = BuildCache(manifest); } } /// /// Builds a cached resolution mapping. /// /// The current cluster manifest. /// The cache. private static Cache BuildCache(ClusterManifest clusterManifest) { var result = new Dictionary(); foreach (var manifest in clusterManifest.AllGrainManifests) { foreach (var grainType in manifest.Grains) { var id = grainType.Key; grainType.Value.Properties.TryGetValue(WellKnownGrainTypeProperties.TypeName, out var typeName); grainType.Value.Properties.TryGetValue(WellKnownGrainTypeProperties.FullTypeName, out var fullTypeName); foreach (var property in grainType.Value.Properties) { if (!property.Key.StartsWith(WellKnownGrainTypeProperties.ImplementedInterfacePrefix, StringComparison.Ordinal)) continue; var implemented = GrainInterfaceType.Create(property.Value); string interfaceTypeName; if (manifest.Interfaces.TryGetValue(implemented, out var interfaceProperties)) { interfaceProperties.Properties.TryGetValue(WellKnownGrainInterfaceProperties.TypeName, out interfaceTypeName); } else { interfaceTypeName = null; } // Try to work out the best primary implementation result.TryGetValue(implemented, out var entry); var implementations = entry.Implementations ?? new List<(string Prefix, GrainType GrainType)>(); if (!implementations.Contains((fullTypeName, id))) implementations.Add((fullTypeName, id)); GrainType primaryImplementation; if (!entry.PrimaryImplementation.IsDefault) { primaryImplementation = entry.PrimaryImplementation; } else if (interfaceProperties?.Properties is { } props && props.TryGetValue(WellKnownGrainInterfaceProperties.DefaultGrainType, out var defaultTypeString)) { // A specified default grain type trumps others. primaryImplementation = GrainType.Create(defaultTypeString); } else if (string.Equals(interfaceTypeName?[1..], typeName, StringComparison.Ordinal)) { // Otherwise, a substring match on the interface name, dropping the 'I', is used. primaryImplementation = id; } else { primaryImplementation = default; } result[implemented] = new CacheEntry(primaryImplementation, implementations); } } } return new Cache(clusterManifest.Version, result); } /// /// Contains a mapping from grain interface type to the implementations of that interface. /// private class Cache { /// /// Initializes a new instance of the class. /// /// The cluster manifest version which this instance corresponds to. /// The interface map. public Cache(MajorMinorVersion version, Dictionary map) { this.Version = version; this.Map = map; } /// /// Gets the cluster manifest version which this cache corresponds to. /// public MajorMinorVersion Version { get; } /// /// Gets the mapping from grain interface type to implementations. /// public Dictionary Map { get; } } /// /// Represents the implementation values for a grain interface type. /// private readonly struct CacheEntry { /// /// Initializes a new instance of the struct. /// /// The primary implementation type. /// The set of other implementations along with their grain type prefixes. public CacheEntry(GrainType primaryImplementation, List<(string Prefix, GrainType GrainType)> implementations) { this.PrimaryImplementation = primaryImplementation; this.Implementations = implementations; } /// /// Gets the primary implementation type. /// public GrainType PrimaryImplementation { get; } /// /// Gets the collection of implementation types with their class name prefixes. /// public List<(string Prefix, GrainType GrainType)> Implementations { get; } } } } ================================================ FILE: src/Orleans.Core/Core/GrainMethodInvoker.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Orleans.Serialization; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// Invokes a request on a grain. /// internal sealed class GrainMethodInvoker : IIncomingGrainCallContext { private readonly Message message; private readonly IInvokable request; private readonly List filters; private readonly InterfaceToImplementationMappingCache interfaceToImplementationMapping; private readonly DeepCopier responseCopier; private readonly IGrainContext grainContext; private int stage; /// /// Initializes a new instance of the class. /// /// The message. /// The grain. /// The request. /// The invocation interceptors. /// The implementation map. /// The response copier. public GrainMethodInvoker( Message message, IGrainContext grainContext, IInvokable request, List filters, InterfaceToImplementationMappingCache interfaceToImplementationMapping, DeepCopier responseCopier) { this.message = message; this.request = request; this.grainContext = grainContext; this.filters = filters; this.interfaceToImplementationMapping = interfaceToImplementationMapping; this.responseCopier = responseCopier; } public IInvokable Request => request; public object Grain => grainContext.GrainInstance; public MethodInfo InterfaceMethod => request.GetMethod(); public MethodInfo ImplementationMethod => GetMethodEntry().ImplementationMethod; public object Result { get => Response switch { { Exception: null } response => response.Result, _ => null }; set => Response = Response.FromResult(value); } public Response Response { get; set; } public GrainId? SourceId => message.SendingGrain is { IsDefault: false } source ? source : null; public IGrainContext TargetContext => grainContext; public GrainId TargetId => grainContext.GrainId; public GrainInterfaceType InterfaceType => message.InterfaceType; public string InterfaceName => request.GetInterfaceName(); public string MethodName => request.GetMethodName(); public async Task Invoke() { try { // Execute each stage in the pipeline. Each successive call to this method will invoke the next stage. // Stages which are not implemented (eg, because the user has not specified an interceptor) are skipped. var numFilters = filters.Count; if (stage < numFilters) { // Call each of the specified interceptors. var systemWideFilter = this.filters[stage]; stage++; await systemWideFilter.Invoke(this); // If Response is null some filter did not continue the call chain if (this.Response is null) { ThrowBrokenCallFilterChain(systemWideFilter.GetType().Name); } return; } if (stage == numFilters) { stage++; // Grain-level invoker, if present. if (this.Grain is IIncomingGrainCallFilter grainClassLevelFilter) { await grainClassLevelFilter.Invoke(this); // If Response is null some filter did not continue the call chain if (this.Response is null) { ThrowBrokenCallFilterChain(this.Grain.GetType().Name); } return; } } if (stage == numFilters + 1) { // Finally call the root-level invoker. stage++; this.Response = await request.Invoke(); // Propagate exceptions to other filters. if (this.Response.Exception is { } exception) { ExceptionDispatchInfo.Capture(exception).Throw(); } this.Response = this.responseCopier.Copy(this.Response); return; } } finally { stage--; } // If this method has been called more than the expected number of times, that is invalid. ThrowInvalidCall(); } private static void ThrowInvalidCall() { throw new InvalidOperationException( $"{nameof(GrainMethodInvoker)}.{nameof(Invoke)}() received an invalid call."); } private static void ThrowBrokenCallFilterChain(string filterName) { throw new InvalidOperationException($"{nameof(GrainMethodInvoker)}.{nameof(Invoke)}() invoked a broken filter: {filterName}."); } private (MethodInfo ImplementationMethod, MethodInfo InterfaceMethod) GetMethodEntry() { var interfaceType = this.request.GetInterfaceType(); var implementationType = this.request.GetTarget().GetType(); // Get or create the implementation map for this object. var implementationMap = interfaceToImplementationMapping.GetOrCreate( implementationType, interfaceType); // Get the method info for the method being invoked. var method = request.GetMethod(); if (method.IsConstructedGenericMethod) { if (implementationMap.TryGetValue(method.GetGenericMethodDefinition(), out var entry)) { return entry.GetConstructedGenericMethod(method); } } else if (implementationMap.TryGetValue(method, out var entry)) { return (entry.ImplementationMethod, entry.InterfaceMethod); } Debug.Assert(false, "Method entry not found"); return default; } } } ================================================ FILE: src/Orleans.Core/Core/IClientBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// Builder for configuring an Orleans client. /// public interface IClientBuilder { /// /// Gets the services collection. /// IServiceCollection Services { get; } /// /// Gets the configuration. /// IConfiguration Configuration { get; } } } ================================================ FILE: src/Orleans.Core/Core/IClientConnectionRetryFilter.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans { /// /// Filter used to determine if cluster connection should be retried. /// public interface IClientConnectionRetryFilter { /// /// Returns a value indicating whether connection to an Orleans cluster should be re-attempted. /// /// The exception thrown from the last connection attempt. /// The cancellation token used to notify when connection has been aborted externally. /// if connection should be re-attempted, if attempts to connect to the cluster should be aborted. Task ShouldRetryConnectionAttempt(Exception exception, CancellationToken cancellationToken); } internal sealed class LinearBackoffClientConnectionRetryFilter : IClientConnectionRetryFilter { private int _retryCount = 0; private const int MaxRetry = 5; private const int Delay = 1_500; public async Task ShouldRetryConnectionAttempt( Exception exception, CancellationToken cancellationToken) { if (_retryCount >= MaxRetry) { return false; } if (!cancellationToken.IsCancellationRequested && exception is SiloUnavailableException) { await Task.Delay(++_retryCount * Delay, cancellationToken); return true; } return false; } } } ================================================ FILE: src/Orleans.Core/Core/IClusterClient.cs ================================================ using System; namespace Orleans { /// /// Client interface for interacting with an Orleans cluster. /// public interface IClusterClient : IGrainFactory { /// /// Gets the service provider used by this client. /// IServiceProvider ServiceProvider { get; } } } ================================================ FILE: src/Orleans.Core/Core/IClusterConnectionStatusListener.cs ================================================ namespace Orleans { /// /// Interface for notifying observers that connection to the cluster has been lost. /// internal interface IClusterConnectionStatusListener { /// /// Notifies this client that the number of connected gateways has changed /// /// /// The current number of gateways. /// /// /// The previous number of gateways. /// void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways); /// /// Notifies this client that the connection to the cluster has been lost. /// void NotifyClusterConnectionLost(); } } ================================================ FILE: src/Orleans.Core/Core/IClusterConnectionStatusObserver.cs ================================================ namespace Orleans; /// /// Interface that receives notifications about the status of the cluster connection. /// public interface IClusterConnectionStatusObserver { /// /// Notifies this observer that the number of connected gateways has changed. /// /// /// The current number of gateways. /// /// /// The previous number of gateways. /// /// Indicates whether a loss of connectivity has been resolved. void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways, bool connectionRecovered); /// /// Notifies this observer that the connection to the cluster has been lost. /// void NotifyClusterConnectionLost(); } ================================================ FILE: src/Orleans.Core/Core/IInternalClusterClient.cs ================================================ namespace Orleans { /// /// The internal-facing client interface. /// internal interface IInternalClusterClient : IClusterClient, IInternalGrainFactory { } } ================================================ FILE: src/Orleans.Core/Core/IInternalGrainFactory.cs ================================================ using System; using Orleans.Runtime; namespace Orleans { /// /// The internal grain factory interface. /// internal interface IInternalGrainFactory : IGrainFactory { /// /// Creates a reference to the provided object. /// /// The interface which interface. /// The object. /// A reference to the provided object. TGrainObserverInterface CreateObjectReference(IAddressable obj) where TGrainObserverInterface : IAddressable; /// /// Gets a reference to the specified system target. /// /// The system target interface. /// The type of the target. /// The destination silo. /// A reference to the specified system target. TGrainInterface GetSystemTarget(GrainType grainType, SiloAddress destination) where TGrainInterface : ISystemTarget; /// /// Gets a reference to the specified system target. /// /// The system target interface. /// The id of the target. /// A reference to the specified system target. TGrainInterface GetSystemTarget(GrainId grainId) where TGrainInterface : ISystemTarget; /// /// Casts the provided to the specified interface /// /// The target grain interface type. /// The grain reference being cast. /// /// A reference to which implements . /// TGrainInterface Cast(IAddressable grain); /// /// Casts the provided to the provided . /// /// The grain. /// The resulting interface type. /// A reference to which implements . object Cast(IAddressable grain, Type interfaceType); } } ================================================ FILE: src/Orleans.Core/Core/InterfaceToImplementationMappingCache.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Orleans.CodeGeneration; namespace Orleans { /// /// Maintains a map between grain classes and corresponding interface-implementation mappings. /// internal class InterfaceToImplementationMappingCache { /// /// Maps a grain interface method's to an implementation's . /// public readonly struct Entry { public Entry(MethodInfo implementationMethod, MethodInfo interfaceMethod) { Debug.Assert(implementationMethod is not null); Debug.Assert(interfaceMethod is not null); ImplementationMethod = implementationMethod; InterfaceMethod = interfaceMethod; } /// /// Gets the grain implementation . /// public MethodInfo ImplementationMethod { get; } /// /// Gets the grain interface . /// public MethodInfo InterfaceMethod { get; } public (MethodInfo ImplementationMethod, MethodInfo InterfaceMethod) GetConstructedGenericMethod(MethodInfo method) { return ConstructedGenericMethods.GetOrAdd(method.GetGenericArguments(), (key, state) => { var (entry, method) = state; var genericArgs = key; var constructedImplementationMethod = entry.ImplementationMethod.MakeGenericMethod(genericArgs); var constructedInterfaceMethod = entry.InterfaceMethod.MakeGenericMethod(genericArgs); return (constructedImplementationMethod, constructedInterfaceMethod); }, (this, method)); } /// /// Gets the constructed generic instances of this method. /// public ConcurrentDictionary ConstructedGenericMethods { get; } = new(TypeArrayComparer.Instance); private sealed class TypeArrayComparer : IEqualityComparer { internal static readonly TypeArrayComparer Instance = new(); public bool Equals(Type[] x, Type[] y) => ReferenceEquals(x, y) || x is null && y is null || x.Length != y.Length || x.AsSpan().SequenceEqual(y.AsSpan()); public int GetHashCode([DisallowNull] Type[] obj) { HashCode result = new(); result.Add(obj.Length); foreach (var value in obj) { result.Add(value); } return result.ToHashCode(); } } } /// /// The map from implementation types to interface types to map of method to method infos. /// private readonly ConcurrentDictionary>> mappings = new(); /// /// Returns a mapping from method id to method info for the provided implementation and interface types. /// /// The implementation type. /// The interface type. /// /// A mapping from method id to method info. /// public Dictionary GetOrCreate(Type implementationType, Type interfaceType) { // Get or create the mapping between interfaceId and invoker for the provided type. if (!this.mappings.TryGetValue(implementationType, out var invokerMap)) { // Generate an the invoker mapping using the provided invoker. invokerMap = mappings.GetOrAdd(implementationType, CreateInterfaceToImplementationMap(implementationType)); } // Attempt to get the invoker for the provided interfaceId. if (!invokerMap.TryGetValue(interfaceType, out var interfaceToImplementationMap)) { throw new InvalidOperationException($"Type {implementationType} does not implement interface {interfaceType}"); } return interfaceToImplementationMap; } /// /// Maps the interfaces of the provided . /// /// The implementation type. /// The mapped interface. private static Dictionary> CreateInterfaceToImplementationMap(Type implementationType) { var interfaces = implementationType.GetInterfaces(); // Create an invoker for every interface on the provided type. var result = new Dictionary>(interfaces.Length); foreach (var iface in interfaces) { var methods = GrainInterfaceUtils.GetMethods(iface); // Map every method on this interface from the definition interface onto the implementation class. var methodMap = new Dictionary(methods.Length); var mapping = default(InterfaceMapping); for (var i = 0; i < methods.Length; i++) { var method = methods[i]; // If this method is not from the expected interface (eg, because it's from a parent interface), then // get the mapping for the interface which it does belong to. if (mapping.InterfaceType != method.DeclaringType) { mapping = implementationType.GetInterfaceMap(method.DeclaringType); } // Find the index of the interface method and then get the implementation method at that position. for (var k = 0; k < mapping.InterfaceMethods.Length; k++) { if (mapping.InterfaceMethods[k] != method) continue; Debug.Assert(method is not null); methodMap[method] = new Entry(mapping.TargetMethods[k], method); break; } } // Add the resulting map of methodId -> method to the interface map. result[iface] = methodMap; } return result; } } } ================================================ FILE: src/Orleans.Core/Core/InvalidSchedulingContextException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Signifies that an operation was attempted on an invalid SchedulingContext. /// [Serializable] [GenerateSerializer] internal sealed class InvalidSchedulingContextException : OrleansException { /// /// Initializes a new instance of the class. /// /// /// The message. /// public InvalidSchedulingContextException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public InvalidSchedulingContextException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// [Obsolete] private InvalidSchedulingContextException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Diagnostics/ActivityNames.cs ================================================ namespace Orleans.Runtime; public static class ActivityNames { public const string PlaceGrain = "place grain"; public const string FilterPlacementCandidates = "filter placement candidates"; public const string ActivateGrain = "activate grain"; public const string DeactivateGrain = "deactivate grain"; public const string OnActivate = "execute OnActivateAsync"; public const string OnDeactivate = "execute OnDeactivateAsync"; public const string RegisterDirectoryEntry = "register directory entry"; public const string StorageRead = "read storage"; public const string StorageWrite = "write storage"; public const string StorageClear = "clear storage"; public const string ActivationDehydrate = "dehydrate activation"; public const string ActivationRehydrate = "rehydrate activation"; public const string WaitMigration = "wait migration"; } ================================================ FILE: src/Orleans.Core/Diagnostics/ActivityPropagationGrainCallFilter.cs ================================================ using System.Diagnostics; using Orleans.Diagnostics; namespace Orleans.Runtime { /// /// A grain call filter which helps to propagate activity correlation information across a call chain. /// internal abstract class ActivityPropagationGrainCallFilter { internal const string RpcSystem = "orleans"; internal const string OrleansNamespacePrefix = "Orleans"; protected static ActivitySource GetActivitySource(IGrainCallContext context) { var interfaceType = context.Request.GetInterfaceType(); var interfaceTypeName = interfaceType.Name; switch (interfaceTypeName) { // Memory storage uses grains for its implementation case "IMemoryStorageGrain": return ActivitySources.StorageGrainSource; // This extension is for explicit migrate/deactivate calls case "IGrainManagementExtension": // This target is for accepting migration batches case "IActivationMigrationManagerSystemTarget": return ActivitySources.LifecycleGrainSource; // These extensions are for async stream subscriptions case "IAsyncEnumerableGrainExtension": return ActivitySources.ApplicationGrainSource; default: return interfaceType.Namespace?.StartsWith(OrleansNamespacePrefix) == true ? ActivitySources.RuntimeGrainSource : ActivitySources.ApplicationGrainSource; } } protected static void GetRequestContextValue(object carrier, string fieldName, out string fieldValue, out IEnumerable fieldValues) { fieldValues = default; fieldValue = RequestContext.Get(fieldName) as string; } protected static async Task Process(IGrainCallContext context, Activity activity) { if (activity is not null) { // rpc attributes from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md activity.SetTag(ActivityTagKeys.RpcSystem, RpcSystem); activity.SetTag(ActivityTagKeys.RpcService, context.InterfaceName); activity.SetTag(ActivityTagKeys.RpcMethod, context.MethodName); if (activity.IsAllDataRequested) { // Custom attributes activity.SetTag(ActivityTagKeys.RpcOrleansTargetId, context.TargetId.ToString()); if (context.SourceId is GrainId sourceId) { activity.SetTag(ActivityTagKeys.RpcOrleansSourceId, sourceId.ToString()); } } } try { await context.Invoke(); } catch (Exception e) { if (activity is not null && activity.IsAllDataRequested) { activity.SetStatus(ActivityStatusCode.Error); // exception attributes from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md activity.SetTag(ActivityTagKeys.ExceptionType, e.GetType().FullName); activity.SetTag(ActivityTagKeys.ExceptionMessage, e.Message); // Note that "exception.stacktrace" is the full exception detail, not just the StackTrace property. // See https://opentelemetry.io/docs/specs/semconv/attributes-registry/exception/ // and https://github.com/open-telemetry/opentelemetry-specification/pull/697#discussion_r453662519 activity.SetTag(ActivityTagKeys.ExceptionStacktrace, e.ToString()); activity.SetTag(ActivityTagKeys.ExceptionEscaped, true); } throw; } finally { activity?.Stop(); } } } /// /// Propagates distributed context information to outgoing requests. /// internal class ActivityPropagationOutgoingGrainCallFilter : ActivityPropagationGrainCallFilter, IOutgoingGrainCallFilter { private readonly DistributedContextPropagator _propagator; /// /// Initializes a new instance of the class. /// /// The context propagator. public ActivityPropagationOutgoingGrainCallFilter(DistributedContextPropagator propagator) { _propagator = propagator; } /// public Task Invoke(IOutgoingGrainCallContext context) { var source = GetActivitySource(context); var activity = source.StartActivity(context.Request.GetActivityName(), ActivityKind.Client); if (activity is null) { return context.Invoke(); } _propagator.Inject(activity, null, static (carrier, key, value) => RequestContext.Set(key, value)); return Process(context, activity); } } /// /// Populates distributed context information from incoming requests. /// internal class ActivityPropagationIncomingGrainCallFilter : ActivityPropagationGrainCallFilter, IIncomingGrainCallFilter { private readonly DistributedContextPropagator _propagator; /// /// Initializes a new instance of the class. /// /// The context propagator. public ActivityPropagationIncomingGrainCallFilter(DistributedContextPropagator propagator) { _propagator = propagator; } /// public Task Invoke(IIncomingGrainCallContext context) { Activity activity = default; _propagator.ExtractTraceIdAndState(null, GetRequestContextValue, out var traceParent, out var traceState); var source = GetActivitySource(context); if (!string.IsNullOrEmpty(traceParent)) { if (ActivityContext.TryParse(traceParent, traceState, isRemote: true, out ActivityContext parentContext)) { // traceParent is a W3CId activity = source.CreateActivity(context.Request.GetActivityName(), ActivityKind.Server, parentContext); } else { // Most likely, traceParent uses ActivityIdFormat.Hierarchical activity = source.CreateActivity(context.Request.GetActivityName(), ActivityKind.Server, traceParent); } if (activity is not null) { if (!string.IsNullOrEmpty(traceState)) { activity.TraceStateString = traceState; } var baggage = _propagator.ExtractBaggage(null, GetRequestContextValue); if (baggage is not null) { foreach (var baggageItem in baggage) { activity.AddBaggage(baggageItem.Key, baggageItem.Value); } } } } else { activity = source.CreateActivity(context.Request.GetActivityName(), ActivityKind.Server); } if (activity is null) { return context.Invoke(); } activity.Start(); return Process(context, activity); } } } ================================================ FILE: src/Orleans.Core/Diagnostics/EventSourceEvents.cs ================================================ using System.Diagnostics.Tracing; namespace Orleans.Runtime { /// /// Event source for . /// [EventSource(Name = "Microsoft-Orleans-CallBackData")] internal sealed class OrleansCallBackDataEvent : EventSource { public static readonly OrleansCallBackDataEvent Log = new OrleansCallBackDataEvent(); /// /// Indicates that a request timeout occurred. /// /// The message. [NonEvent] public void OnTimeout(Message message) { if (this.IsEnabled()) { this.OnTimeout(); } } /// /// Indicates that a request timeout occurred. /// [Event(1, Level = EventLevel.Warning)] private void OnTimeout() => this.WriteEvent(1); /// /// Indicates that a target silo failed. /// /// A message addressed to the target silo. [NonEvent] public void OnTargetSiloFail(Message message) { if (this.IsEnabled()) { this.OnTargetSiloFail(); } } /// /// Indicates that a target silo failed. /// [Event(2, Level = EventLevel.Warning)] private void OnTargetSiloFail() => this.WriteEvent(2); /// /// Indicates that a request completed. /// [NonEvent] public void DoCallback(Message message) { if (this.IsEnabled()) { this.DoCallback(); } } /// /// Indicates that a request completed. /// [Event(3, Level = EventLevel.Verbose)] private void DoCallback() => this.WriteEvent(3); /// /// Indicates that a request was canceled. /// [NonEvent] public void OnCanceled(Message message) { if (this.IsEnabled()) { this.OnCanceled(); } } /// /// Indicates that a request was canceled. /// [Event(4, Level = EventLevel.Verbose)] private void OnCanceled() => this.WriteEvent(4); } [EventSource(Name = "Microsoft-Orleans-OutsideRuntimeClient")] internal sealed class OrleansOutsideRuntimeClientEvent : EventSource { public static readonly OrleansOutsideRuntimeClientEvent Log = new OrleansOutsideRuntimeClientEvent(); [NonEvent] public void SendRequest(Message message) { if (this.IsEnabled()) { this.SendRequest(); } } [Event(1, Level = EventLevel.Verbose)] private void SendRequest() => this.WriteEvent(1); [NonEvent] public void ReceiveResponse(Message message) { if (this.IsEnabled()) { this.ReceiveResponse(); } } [Event(2, Level = EventLevel.Verbose)] private void ReceiveResponse() => this.WriteEvent(2); [NonEvent] public void SendResponse(Message message) { if (this.IsEnabled()) { this.SendResponse(); } } [Event(3, Level = EventLevel.Verbose)] private void SendResponse() => this.WriteEvent(3); } [EventSource(Name = "Microsoft-Orleans-Dispatcher")] internal sealed class OrleansDispatcherEvent : EventSource { public static readonly OrleansDispatcherEvent Log = new OrleansDispatcherEvent(); [NonEvent] public void ReceiveMessage(Message message) { if (this.IsEnabled()) { this.ReceiveMessage(); } } [Event(1, Level = EventLevel.Verbose)] private void ReceiveMessage() => WriteEvent(1); } [EventSource(Name = "Microsoft-Orleans-InsideRuntimeClient")] internal sealed class OrleansInsideRuntimeClientEvent : EventSource { public static readonly OrleansInsideRuntimeClientEvent Log = new OrleansInsideRuntimeClientEvent(); [NonEvent] public void SendRequest(Message message) { if (this.IsEnabled()) { this.SendRequest(); } } [Event(1, Level = EventLevel.Verbose)] private void SendRequest() => WriteEvent(1); [NonEvent] public void ReceiveResponse(Message message) { if (this.IsEnabled()) { this.ReceiveResponse(); } } [Event(2, Level = EventLevel.Verbose)] private void ReceiveResponse() => WriteEvent(2); [NonEvent] public void SendResponse(Message message) { if (this.IsEnabled()) { this.SendResponse(); } } [Event(3, Level = EventLevel.Verbose)] private void SendResponse() => WriteEvent(3); } [EventSource(Name = "Microsoft-Orleans-IncomingMessageAgent")] internal sealed class OrleansIncomingMessageAgentEvent : EventSource { public static readonly OrleansIncomingMessageAgentEvent Log = new OrleansIncomingMessageAgentEvent(); [NonEvent] public void ReceiveMessage(Message message) { if (this.IsEnabled()) { this.ReceiveMessage(); } } [Event(1, Level = EventLevel.Verbose)] private void ReceiveMessage() => WriteEvent(1); } } ================================================ FILE: src/Orleans.Core/Diagnostics/MessagingTrace.cs ================================================ using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal class MessagingTrace : DiagnosticListener, ILogger { public const string Category = "Orleans.Messaging"; public const string CreateMessageEventName = Category + ".CreateMessage"; public const string SendMessageEventName = Category + ".Outbound.Send"; public const string IncomingMessageAgentReceiveMessageEventName = Category + ".IncomingMessageAgent.Receive"; public const string DispatcherReceiveMessageEventName = Category + ".Dispatcher.Receive"; public const string DropExpiredMessageEventName = Category + ".Drop.Expired"; public const string DropSendingMessageEventName = Category + ".Drop.Sending"; public const string DropBlockedApplicationMessageEventName = Category + ".Drop.Blocked"; public const string EnqueueInboundMessageEventName = Category + ".Inbound.Enqueue"; public const string DequeueInboundMessageEventName = Category + ".Inbound.Dequeue"; public const string ScheduleMessageEventName = Category + ".Schedule"; public const string EnqueueMessageOnActivationEventName = Category + ".Activation.Enqueue"; public const string InvokeMessageEventName = Category + ".Invoke"; public const string RejectSendMessageToDeadSiloEventName = Category + ".Reject.TargetDead"; private static readonly Action LogDropExpiredMessage = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Messaging_DroppingExpiredMessage, DropExpiredMessageEventName), "Dropping expired message {Message} at phase {Phase}"); private static readonly Action LogDropBlockedApplicationMessage = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Messaging_DroppingBlockedMessage, DropBlockedApplicationMessageEventName), "Dropping message {Message} since this silo is blocking application messages"); private static readonly Action LogEnqueueInboundMessage = LoggerMessage.Define( LogLevel.Trace, new EventId((int)ErrorCode.Messaging_Inbound_Enqueue, EnqueueInboundMessageEventName), "Enqueueing inbound message {Message}"); private static readonly Action LogDequeueInboundMessage = LoggerMessage.Define( LogLevel.Trace, new EventId((int)ErrorCode.Messaging_Inbound_Dequeue, DequeueInboundMessageEventName), "Dequeueing inbound message {Message}"); private static readonly Action LogSiloDropSendingMessage = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Messaging_OutgoingMS_DroppingMessage, DropSendingMessageEventName), "Silo {SiloAddress} is dropping message {Message}. Reason: {Reason}"); private static readonly Action LogRejectSendMessageToDeadSilo = LoggerMessage.Define( LogLevel.Information, new EventId((int)ErrorCode.MessagingSendingRejection, RejectSendMessageToDeadSiloEventName), "Silo {SiloAddress} is rejecting message to known-dead silo {DeadSilo}: {Message}"); private readonly ILogger log; public MessagingTrace(ILoggerFactory loggerFactory) : base(Category) { this.log = loggerFactory.CreateLogger(Category); } public void OnSendMessage(Message message) { if (this.IsEnabled(SendMessageEventName)) { this.Write(SendMessageEventName, message); } } public void OnIncomingMessageAgentReceiveMessage(Message message) { if (this.IsEnabled(IncomingMessageAgentReceiveMessageEventName)) { this.Write(IncomingMessageAgentReceiveMessageEventName, message); } OrleansIncomingMessageAgentEvent.Log.ReceiveMessage(message); MessagingProcessingInstruments.OnImaMessageReceived(message); } public void OnDispatcherReceiveMessage(Message message) { if (this.IsEnabled(DispatcherReceiveMessageEventName)) { this.Write(DispatcherReceiveMessageEventName, message); } OrleansDispatcherEvent.Log.ReceiveMessage(message); MessagingProcessingInstruments.OnDispatcherMessageReceive(message); } internal void OnDropExpiredMessage(Message message, MessagingInstruments.Phase phase) { if (this.IsEnabled(DropExpiredMessageEventName)) { this.Write(DropExpiredMessageEventName, new { Message = message, Phase = phase }); } MessagingInstruments.OnMessageExpired(phase); LogDropExpiredMessage(this, message, phase, null); } internal void OnDropBlockedApplicationMessage(Message message) { if (this.IsEnabled(DropBlockedApplicationMessageEventName)) { this.Write(DropBlockedApplicationMessageEventName, message); } LogDropBlockedApplicationMessage(this, message, null); } internal void OnSiloDropSendingMessage(SiloAddress localSiloAddress, Message message, string reason) { MessagingInstruments.OnDroppedSentMessage(message); LogSiloDropSendingMessage(this, localSiloAddress, message, reason, null); } public void OnEnqueueInboundMessage(Message message) { if (this.IsEnabled(EnqueueInboundMessageEventName)) { this.Write(EnqueueInboundMessageEventName, message); } LogEnqueueInboundMessage(this, message, null); } public void OnDequeueInboundMessage(Message message) { if (this.IsEnabled(DequeueInboundMessageEventName)) { this.Write(DequeueInboundMessageEventName, message); } LogDequeueInboundMessage(this, message, null); } internal void OnCreateMessage(Message message) { if (this.IsEnabled(CreateMessageEventName)) { this.Write(CreateMessageEventName, message); } } public void OnScheduleMessage(Message message) { if (this.IsEnabled(ScheduleMessageEventName)) { this.Write(ScheduleMessageEventName, message); } } public void OnEnqueueMessageOnActivation(Message message, IGrainContext context) { if (this.IsEnabled(EnqueueMessageOnActivationEventName)) { this.Write(EnqueueMessageOnActivationEventName, message); } MessagingProcessingInstruments.OnImaMessageEnqueued(context); } public void OnInvokeMessage(Message message) { if (this.IsEnabled(InvokeMessageEventName)) { this.Write(InvokeMessageEventName, message); } } public void OnRejectSendMessageToDeadSilo(SiloAddress localSilo, Message message) { MessagingInstruments.OnFailedSentMessage(message); if (this.IsEnabled(RejectSendMessageToDeadSiloEventName)) { this.Write(RejectSendMessageToDeadSiloEventName, message); } LogRejectSendMessageToDeadSilo( this, localSilo, message.TargetSilo, message, null); } internal void OnSendRequest(Message message) { OrleansInsideRuntimeClientEvent.Log.SendRequest(message); } public IDisposable BeginScope(TState state) { return this.log.BeginScope(state); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsEnabled(LogLevel logLevel) { return this.log.IsEnabled(logLevel); } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { this.log.Log(logLevel, eventId, state, exception, formatter); } } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/AggregatorKey.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans.Runtime; internal readonly struct AggregatorKey : IEquatable { public AggregatorKey(string instrumentName, KeyValuePair[] tags) { InstrumentName = instrumentName; Tags = tags; } public string InstrumentName { get; } public KeyValuePair[] Tags { get; } public override int GetHashCode() => HashCode.Combine(InstrumentName, Tags); public bool Equals(AggregatorKey other) => InstrumentName == other.InstrumentName && Tags.SequenceEqual(other.Tags); public override bool Equals(object obj) => obj is AggregatorKey key && Equals(key); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/CounterAggregator.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; namespace Orleans.Runtime; internal sealed class CounterAggregator { private readonly KeyValuePair[] _tags; private long _value = 0; public CounterAggregator() { _tags = Array.Empty>(); } public CounterAggregator(in TagList tagList) { if (tagList.Name1 == null) { _tags = Array.Empty>(); } else if (tagList.Name2 == null) { _tags = new[] { new KeyValuePair(tagList.Name1, tagList.Value1) }; } else if (tagList.Name3 == null) { _tags = new[] { new KeyValuePair(tagList.Name1, tagList.Value1), new KeyValuePair(tagList.Name2, tagList.Value2) }; } else if (tagList.Name4 == null) { _tags = new[] { new KeyValuePair(tagList.Name1, tagList.Value1), new KeyValuePair(tagList.Name2, tagList.Value2), new KeyValuePair(tagList.Name3, tagList.Value3) }; } else { _tags = new[] { new KeyValuePair(tagList.Name1, tagList.Value1), new KeyValuePair(tagList.Name2, tagList.Value2), new KeyValuePair(tagList.Name3, tagList.Value3), new KeyValuePair(tagList.Name4, tagList.Value4) }; } } public long Value => _value; public void Add(long measurement) => Interlocked.Add(ref _value, measurement); public Measurement Collect() => new(_value, _tags); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/CounterAggregatorGroup.cs ================================================ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal sealed class CounterAggregatorGroup { internal ConcurrentDictionary Aggregators { get; } = new(); public CounterAggregator FindOrCreate(TagList tagList) { if (Aggregators.TryGetValue(tagList, out var stat)) { return stat; } return Aggregators.GetOrAdd(tagList, new CounterAggregator(tagList)); } public void Add(long measurement, string tagName1, object tagValue1) => FindOrCreate(new(tagName1, tagValue1)) .Add(measurement); public void Add(long measurement, string tagName1, object tagValue1, string tagName2, object tagValue2) => FindOrCreate(new(tagName1, tagValue1, tagName2, tagValue2)) .Add(measurement); public void Add(long measurement, string tagName1, object tagValue1, string tagName2, object tagValue2, string tagName3, object tagValue3) => FindOrCreate(new(tagName1, tagValue1, tagName2, tagValue2, tagName3, tagValue3)) .Add(measurement); public void Add(long measurement, string tagName1, object tagValue1, string tagName2, object tagValue2, string tagName3, object tagValue3, string tagName4, object tagValue4) => FindOrCreate(new(tagName1, tagValue1, tagName2, tagValue2, tagName3, tagValue3, tagName4, tagValue4)) .Add(measurement); public void Add(long measurement, TagList tagList) => FindOrCreate(tagList) .Add(measurement); public IEnumerable> Collect() { foreach (var (_, aggregator) in Aggregators) { if (aggregator.Value != 0) { yield return aggregator.Collect(); } } } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/HistogramAggregator.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; using System.Threading; namespace Orleans.Runtime; internal class HistogramAggregator { private readonly KeyValuePair[] _tags; private readonly HistogramBucketAggregator[] _buckets; private long _count; private long _sum; public HistogramAggregator(long[] buckets, KeyValuePair[] tags, Func> getLabel) { if (buckets[^1] != long.MaxValue) { buckets = buckets.Concat(new[] { long.MaxValue }).ToArray(); } _tags = tags; _buckets = buckets.Select(b => new HistogramBucketAggregator(tags, b, getLabel(b))).ToArray(); } public void Record(long number) { int i; for (i = 0; i < _buckets.Length; i++) { if (number <= _buckets[i].Bound) { break; } } _buckets[i].Add(1); Interlocked.Increment(ref _count); Interlocked.Add(ref _sum, number); } public IEnumerable> CollectBuckets() { foreach (var bucket in _buckets) { yield return bucket.Collect(); } } public Measurement CollectCount() => new(_count, _tags); public Measurement CollectSum() => new(_sum, _tags); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/HistogramBucketAggregator.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; using System.Threading; namespace Orleans.Runtime; internal class HistogramBucketAggregator { private long _value = 0; private readonly KeyValuePair[] _tags; public long Bound { get; } public HistogramBucketAggregator(KeyValuePair[] tags, long bound, KeyValuePair label) { _tags = tags.Concat(new[] { label }).ToArray(); Bound = bound; } public ReadOnlySpan> Tags => _tags; public long Value => _value; public void Add(long measurement) => Interlocked.Add(ref _value, measurement); public Measurement Collect() { return new Measurement(_value, _tags); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Aggregators/TagList.cs ================================================ namespace Orleans.Runtime; internal record struct TagList( string Name1, object Value1, string Name2 = default, object Value2 = default, string Name3 = default, object Value3 = default, string Name4 = default, object Value4 = default); ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/ApplicationRequestInstruments.cs ================================================ using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal class ApplicationRequestInstruments { private readonly Counter _timedOutRequestsCounter; private readonly Counter _canceledRequestsCounter; private static readonly long[] AppRequestsLatencyHistogramBuckets = [1, 2, 4, 6, 8, 10, 50, 100, 200, 400, 800, 1_000, 1_500, 2_000, 5_000, 10_000, 15_000]; private readonly HistogramAggregator _appRequestsLatencyHistogramAggregator; private readonly ObservableCounter _appRequestsLatencyHistogramBucket; private readonly ObservableCounter _appRequestsLatencyHistogramCount; private readonly ObservableCounter _appRequestsLatencyHistogramSum; internal ApplicationRequestInstruments(OrleansInstruments instruments) { _timedOutRequestsCounter = instruments.Meter.CreateCounter(InstrumentNames.APP_REQUESTS_TIMED_OUT); _canceledRequestsCounter = instruments.Meter.CreateCounter(InstrumentNames.APP_REQUESTS_CANCELED); _appRequestsLatencyHistogramAggregator = new(AppRequestsLatencyHistogramBuckets, [], value => new("duration", $"{value}ms")); _appRequestsLatencyHistogramBucket = instruments.Meter.CreateObservableCounter(InstrumentNames.APP_REQUESTS_LATENCY_HISTOGRAM + "-bucket", _appRequestsLatencyHistogramAggregator.CollectBuckets); _appRequestsLatencyHistogramCount = instruments.Meter.CreateObservableCounter(InstrumentNames.APP_REQUESTS_LATENCY_HISTOGRAM + "-count", _appRequestsLatencyHistogramAggregator.CollectCount); _appRequestsLatencyHistogramSum = instruments.Meter.CreateObservableCounter(InstrumentNames.APP_REQUESTS_LATENCY_HISTOGRAM + "-sum", _appRequestsLatencyHistogramAggregator.CollectSum); } internal void OnAppRequestsEnd(long durationMilliseconds) { if (_appRequestsLatencyHistogramSum.Enabled) _appRequestsLatencyHistogramAggregator.Record(durationMilliseconds); } internal void OnAppRequestsTimedOut() { _timedOutRequestsCounter.Add(1); } internal void OnAppRequestsCanceled() { _canceledRequestsCounter.Add(1); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/CatalogInstruments.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class CatalogInstruments { internal static Counter ActivationFailedToActivate = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_FAILED_TO_ACTIVATE); internal static Counter ActivationCollections = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_COLLECTION_NUMBER_OF_COLLECTIONS); internal static Counter ActivationShutdown = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_SHUTDOWN); internal static void ActivationShutdownViaCollection() => ActivationShutdown.Add(1, new KeyValuePair("via", "collection")); internal static void ActivationShutdownViaDeactivateOnIdle() => ActivationShutdown.Add(1, new KeyValuePair("via", "deactivateOnIdle")); internal static void ActivationShutdownViaMigration() => ActivationShutdown.Add(1, new KeyValuePair("via", "migration")); internal static void ActivationShutdownViaDeactivateStuckActivation() => ActivationShutdown.Add(1, new KeyValuePair("via", "deactivateStuckActivation")); internal static Counter NonExistentActivations = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_NON_EXISTENT_ACTIVATIONS); internal static Counter ActivationConcurrentRegistrationAttempts = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_CONCURRENT_REGISTRATION_ATTEMPTS); internal static readonly Counter ActivationsCreated = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_CREATED); internal static readonly Counter ActivationsDestroyed = Instruments.Meter.CreateCounter(InstrumentNames.CATALOG_ACTIVATION_DESTROYED); internal static ObservableGauge ActivationCount; internal static void RegisterActivationCountObserve(Func observeValue) { ActivationCount = Instruments.Meter.CreateObservableGauge(InstrumentNames.CATALOG_ACTIVATION_COUNT, observeValue); } internal static ObservableGauge ActivationWorkingSet; internal static void RegisterActivationWorkingSetObserve(Func observeValue) { ActivationWorkingSet = Instruments.Meter.CreateObservableGauge(InstrumentNames.CATALOG_ACTIVATION_WORKING_SET, observeValue); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/ClientInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; public static class ClientInstruments { internal static ObservableGauge ConnectedGatewayCount; internal static void RegisterConnectedGatewayCountObserve(Func observeValue) { ConnectedGatewayCount = Instruments.Meter.CreateObservableGauge(InstrumentNames.CLIENT_CONNECTED_GATEWAY_COUNT, observeValue); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/ConsistentRingInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class ConsistentRingInstruments { internal static ObservableGauge RingSize; internal static void RegisterRingSizeObserve(Func observeValue) { RingSize = Instruments.Meter.CreateObservableGauge(InstrumentNames.CONSISTENTRING_SIZE, observeValue); } internal static ObservableGauge MyRangeRingPercentage; internal static void RegisterMyRangeRingPercentageObserve(Func observeValue) { MyRangeRingPercentage = Instruments.Meter.CreateObservableGauge(InstrumentNames.CONSISTENTRING_LOCAL_SIZE_PERCENTAGE, observeValue); } internal static ObservableGauge AverageRingPercentage; internal static void RegisterAverageRingPercentageObserve(Func observeValue) { AverageRingPercentage = Instruments.Meter.CreateObservableGauge(InstrumentNames.CONSISTENTRING_AVERAGE_SIZE_PERCENTAGE, observeValue); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/DirectoryInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class DirectoryInstruments { internal static readonly Counter LookupsLocalIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_LOCAL_ISSUED); internal static readonly Counter LookupsLocalSuccesses = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_LOCAL_SUCCESSES); internal static readonly Counter LookupsFullIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_FULL_ISSUED); internal static readonly Counter LookupsRemoteSent = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_REMOTE_SENT); internal static readonly Counter LookupsRemoteReceived = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_REMOTE_RECEIVED); internal static readonly Counter LookupsLocalDirectoryIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_LOCALDIRECTORY_ISSUED); internal static readonly Counter LookupsLocalDirectorySuccesses = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_LOCALDIRECTORY_SUCCESSES); internal static readonly Counter LookupsCacheIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_CACHE_ISSUED); internal static readonly Counter LookupsCacheSuccesses = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_LOOKUPS_CACHE_SUCCESSES); internal static readonly Counter ValidationsCacheSent = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_VALIDATIONS_CACHE_SENT); internal static readonly Counter ValidationsCacheReceived = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_VALIDATIONS_CACHE_RECEIVED); internal static readonly Counter SnapshotTransferCount = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_RANGE_SNAPSHOT_TRANSFER_COUNT); internal static readonly Histogram SnapshotTransferDuration = Instruments.Meter.CreateHistogram(InstrumentNames.DIRECTORY_RANGE_SNAPSHOT_TRANSFER_DURATION); internal static readonly Counter RangeRecoveryCount = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_RANGE_RECOVERY_COUNT); internal static readonly Histogram RangeRecoveryDuration = Instruments.Meter.CreateHistogram(InstrumentNames.DIRECTORY_RANGE_RECOVERY_DURATION); internal static readonly Histogram RangeLockHeldDuration = Instruments.Meter.CreateHistogram(InstrumentNames.DIRECTORY_RANGE_LOCK_HELD_DURATION); internal static ObservableGauge DirectoryPartitionSize; internal static void RegisterDirectoryPartitionSizeObserve(Func observeValue) { DirectoryPartitionSize = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_PARTITION_SIZE, observeValue); } internal static ObservableGauge CacheSize; internal static void RegisterCacheSizeObserve(Func observeValue) { CacheSize = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_CACHE_SIZE, observeValue); } internal static ObservableGauge RingSize; internal static void RegisterRingSizeObserve(Func observeValue) { RingSize = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_RING_RINGSIZE, observeValue); } internal static ObservableGauge MyPortionRingDistance; internal static void RegisterMyPortionRingDistanceObserve(Func observeValue) { MyPortionRingDistance = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_RING_MYPORTION_RINGDISTANCE, observeValue); } internal static ObservableGauge MyPortionRingPercentage; internal static void RegisterMyPortionRingPercentageObserve(Func observeValue) { MyPortionRingPercentage = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_RING_MYPORTION_RINGPERCENTAGE, observeValue); } internal static ObservableGauge MyPortionAverageRingPercentage; internal static void RegisterMyPortionAverageRingPercentageObserve(Func observeValue) { MyPortionAverageRingPercentage = Instruments.Meter.CreateObservableGauge(InstrumentNames.DIRECTORY_RING_MYPORTION_AVERAGERINGPERCENTAGE, observeValue); } internal static readonly Counter RegistrationsSingleActIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_REGISTRATIONS_SINGLE_ACT_ISSUED); internal static readonly Counter RegistrationsSingleActLocal = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_REGISTRATIONS_SINGLE_ACT_LOCAL); internal static readonly Counter RegistrationsSingleActRemoteSent = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_REGISTRATIONS_SINGLE_ACT_REMOTE_SENT); internal static readonly Counter RegistrationsSingleActRemoteReceived = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_REGISTRATIONS_SINGLE_ACT_REMOTE_RECEIVED); internal static readonly Counter UnregistrationsIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_ISSUED); internal static readonly Counter UnregistrationsLocal = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_LOCAL); internal static readonly Counter UnregistrationsRemoteSent = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_REMOTE_SENT); internal static readonly Counter UnregistrationsRemoteReceived = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_REMOTE_RECEIVED); internal static readonly Counter UnregistrationsManyIssued = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_MANY_ISSUED); internal static readonly Counter UnregistrationsManyRemoteSent = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_MANY_REMOTE_SENT); internal static readonly Counter UnregistrationsManyRemoteReceived = Instruments.Meter.CreateCounter(InstrumentNames.DIRECTORY_UNREGISTRATIONS_MANY_REMOTE_RECEIVED); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/GatewayInstruments.cs ================================================ using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class GatewayInstruments { internal static readonly Counter GatewaySent = Instruments.Meter.CreateCounter(InstrumentNames.GATEWAY_SENT); internal static readonly Counter GatewayReceived = Instruments.Meter.CreateCounter(InstrumentNames.GATEWAY_RECEIVED); internal static readonly Counter GatewayLoadShedding = Instruments.Meter.CreateCounter(InstrumentNames.GATEWAY_LOAD_SHEDDING); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/GrainInstruments.cs ================================================ using System.Collections.Generic; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class GrainInstruments { static GrainInstruments() { GrainMetricsListener.Start(); } internal static UpDownCounter GrainCounts = Instruments.Meter.CreateUpDownCounter(InstrumentNames.GRAIN_COUNTS); internal static void IncrementGrainCounts(string grainTypeName) { GrainCounts.Add(1, new KeyValuePair("type", grainTypeName)); } internal static void DecrementGrainCounts(string grainTypeName) { GrainCounts.Add(-1, new KeyValuePair("type", grainTypeName)); } internal static UpDownCounter SystemTargetCounts = Instruments.Meter.CreateUpDownCounter(InstrumentNames.SYSTEM_TARGET_COUNTS); internal static void IncrementSystemTargetCounts(string systemTargetTypeName) { SystemTargetCounts.Add(1, new KeyValuePair("type", systemTargetTypeName)); } internal static void DecrementSystemTargetCounts(string systemTargetTypeName) { SystemTargetCounts.Add(-1, new KeyValuePair("type", systemTargetTypeName)); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/InstrumentNames.cs ================================================ namespace Orleans.Runtime; internal static class InstrumentNames { // Networking public const string NETWORKING_SOCKETS_CLOSED = "orleans-networking-sockets-closed"; public const string NETWORKING_SOCKETS_OPENED = "orleans-networking-sockets-opened"; // Messaging public const string MESSAGING_SENT_MESSAGES_SIZE = "orleans-messaging-sent-messages-size"; public const string MESSAGING_RECEIVED_MESSAGES_SIZE = "orleans-messaging-received-messages-size"; public const string MESSAGING_SENT_BYTES_HEADER = "orleans-messaging-sent-header-size"; public const string MESSAGING_SENT_FAILED = "orleans-messaging-sent-failed"; public const string MESSAGING_SENT_DROPPED = "orleans-messaging-sent-dropped"; public const string MESSAGING_RECEIVED_BYTES_HEADER = "orleans-messaging-received-header-size"; public const string MESSAGING_DISPATCHER_RECEIVED = "orleans-messaging-processing-dispatcher-received"; public const string MESSAGING_DISPATCHER_PROCESSED = "orleans-messaging-processing-dispatcher-processed"; public const string MESSAGING_DISPATCHER_FORWARDED = "orleans-messaging-processing-dispatcher-forwarded"; public const string MESSAGING_IMA_RECEIVED = "orleans-messaging-processing-ima-received"; public const string MESSAGING_IMA_ENQUEUED = "orleans-messaging-processing-ima-enqueued"; public const string MESSAGING_PROCESSING_ACTIVATION_DATA_ALL = "orleans-messaging-processing-activation-data"; public const string MESSAGING_PINGS_SENT = "orleans-messaging-pings-sent"; public const string MESSAGING_PINGS_RECEIVED = "orleans-messaging-pings-received"; public const string MESSAGING_PINGS_REPLYRECEIVED = "orleans-messaging-pings-reply-received"; public const string MESSAGING_PINGS_REPLYMISSED = "orleans-messaging-pings-reply-missed"; public const string MESSAGING_EXPIRED = "orleans-messaging-expired"; public const string MESSAGING_REJECTED = "orleans-messaging-rejected"; public const string MESSAGING_REROUTED = "orleans-messaging-rerouted"; public const string MESSAGING_SENT_LOCALMESSAGES = "orleans-messaging-sent-local"; // Gateway public const string GATEWAY_CONNECTED_CLIENTS = "orleans-gateway-connected-clients"; public const string GATEWAY_SENT = "orleans-gateway-sent"; public const string GATEWAY_RECEIVED = "orleans-gateway-received"; public const string GATEWAY_LOAD_SHEDDING = "orleans-gateway-load-shedding"; // Runtime public const string SCHEDULER_NUM_LONG_RUNNING_TURNS = "orleans-scheduler-long-running-turns"; // Catalog public const string CATALOG_ACTIVATION_COUNT = "orleans-catalog-activations"; public const string CATALOG_ACTIVATION_WORKING_SET = "orleans-catalog-activation-working-set"; public const string CATALOG_ACTIVATION_CREATED = "orleans-catalog-activation-created"; public const string CATALOG_ACTIVATION_DESTROYED = "orleans-catalog-activation-destroyed"; public const string CATALOG_ACTIVATION_FAILED_TO_ACTIVATE = "orleans-catalog-activation-failed-to-activate"; public const string CATALOG_ACTIVATION_COLLECTION_NUMBER_OF_COLLECTIONS = "orleans-catalog-activation-collections"; public const string CATALOG_ACTIVATION_SHUTDOWN = "orleans-catalog-activation-shutdown"; public const string CATALOG_ACTIVATION_NON_EXISTENT_ACTIVATIONS = "orleans-catalog-activation-non-existent"; public const string CATALOG_ACTIVATION_CONCURRENT_REGISTRATION_ATTEMPTS = "orleans-catalog-activation-concurrent-registration-attempts"; // Directory // not used... public const string DIRECTORY_LOOKUPS_LOCAL_ISSUED = "orleans-directory-lookups-local-issued"; // not used... public const string DIRECTORY_LOOKUPS_LOCAL_SUCCESSES = "orleans-directory-lookups-local-successes"; public const string DIRECTORY_LOOKUPS_FULL_ISSUED = "orleans-directory-lookups-full-issued"; public const string DIRECTORY_LOOKUPS_REMOTE_SENT = "orleans-directory-lookups-remote-sent"; public const string DIRECTORY_LOOKUPS_REMOTE_RECEIVED = "orleans-directory-lookups-remote-received"; public const string DIRECTORY_LOOKUPS_LOCALDIRECTORY_ISSUED = "orleans-directory-lookups-local-directory-issued"; public const string DIRECTORY_LOOKUPS_LOCALDIRECTORY_SUCCESSES = "orleans-directory-lookups-local-directory-successes"; // not used public const string DIRECTORY_LOOKUPS_CACHE_ISSUED = "orleans-directory-lookups-cache-issued"; // not used public const string DIRECTORY_LOOKUPS_CACHE_SUCCESSES = "orleans-directory-lookups-cache-successes"; public const string DIRECTORY_VALIDATIONS_CACHE_SENT = "orleans-directory-validations-cache-sent"; public const string DIRECTORY_VALIDATIONS_CACHE_RECEIVED = "orleans-directory-validations-cache-received"; public const string DIRECTORY_PARTITION_SIZE = "orleans-directory-partition-size"; public const string DIRECTORY_CACHE_SIZE = "orleans-directory-cache-size"; public const string DIRECTORY_RING_RINGSIZE = "orleans-directory-ring-size"; public const string DIRECTORY_RING_MYPORTION_RINGDISTANCE = "orleans-directory-ring-local-portion-distance"; public const string DIRECTORY_RING_MYPORTION_RINGPERCENTAGE = "orleans-directory-ring-local-portion-percentage"; public const string DIRECTORY_RING_MYPORTION_AVERAGERINGPERCENTAGE = "orleans-directory-ring-local-portion-average-percentage"; public const string DIRECTORY_REGISTRATIONS_SINGLE_ACT_ISSUED = "orleans-directory-registrations-single-act-issued"; public const string DIRECTORY_REGISTRATIONS_SINGLE_ACT_LOCAL = "orleans-directory-registrations-single-act-local"; public const string DIRECTORY_REGISTRATIONS_SINGLE_ACT_REMOTE_SENT = "orleans-directory-registrations-single-act-remote-sent"; public const string DIRECTORY_REGISTRATIONS_SINGLE_ACT_REMOTE_RECEIVED = "orleans-directory-registrations-single-act-remote-received"; public const string DIRECTORY_UNREGISTRATIONS_ISSUED = "orleans-directory-unregistrations-issued"; public const string DIRECTORY_UNREGISTRATIONS_LOCAL = "orleans-directory-unregistrations-local"; public const string DIRECTORY_UNREGISTRATIONS_REMOTE_SENT = "orleans-directory-unregistrations-remote-sent"; public const string DIRECTORY_UNREGISTRATIONS_REMOTE_RECEIVED = "orleans-directory-unregistrations-remote-received"; public const string DIRECTORY_UNREGISTRATIONS_MANY_ISSUED = "orleans-directory-unregistrations-many-issued"; public const string DIRECTORY_UNREGISTRATIONS_MANY_REMOTE_SENT = "orleans-directory-unregistrations-many-remote-sent"; public const string DIRECTORY_UNREGISTRATIONS_MANY_REMOTE_RECEIVED = "orleans-directory-unregistrations-many-remote-received"; public const string DIRECTORY_RANGE_SNAPSHOT_TRANSFER_COUNT = "orleans-directory-snapshot-transfer-count"; public const string DIRECTORY_RANGE_SNAPSHOT_TRANSFER_DURATION = "orleans-directory-snapshot-transfer-duration"; public const string DIRECTORY_RANGE_RECOVERY_COUNT = "orleans-directory-recovery-count"; public const string DIRECTORY_RANGE_RECOVERY_DURATION = "orleans-directory-recovery-duration"; public const string DIRECTORY_RANGE_LOCK_HELD_DURATION = "orleans-directory-range-lock-held-duration"; // ConsistentRing public const string CONSISTENTRING_SIZE = "orleans-consistent-ring-size"; public const string CONSISTENTRING_LOCAL_SIZE_PERCENTAGE = "orleans-consistent-ring-range-percentage-local"; public const string CONSISTENTRING_AVERAGE_SIZE_PERCENTAGE = "orleans-consistent-ring-range-percentage-average"; // Watchdog public const string WATCHDOG_NUM_HEALTH_CHECKS = "orleans-watchdog-health-checks"; public const string WATCHDOG_NUM_FAILED_HEALTH_CHECKS = "orleans-watchdog-health-checks-failed"; // Client public const string CLIENT_CONNECTED_GATEWAY_COUNT = "orleans-client-connected-gateways"; // Misc public const string GRAIN_COUNTS = "orleans-grains"; public const string SYSTEM_TARGET_COUNTS = "orleans-system-targets"; // App requests public const string APP_REQUESTS_LATENCY_HISTOGRAM = "orleans-app-requests-latency"; public const string APP_REQUESTS_TIMED_OUT = "orleans-app-requests-timedout"; public const string APP_REQUESTS_CANCELED = "orleans-app-requests-canceled"; // Reminders public const string REMINDERS_TARDINESS = "orleans-reminders-tardiness"; public const string REMINDERS_NUMBER_ACTIVE_REMINDERS = "orleans-reminders-active"; public const string REMINDERS_COUNTERS_TICKS_DELIVERED = "orleans-reminders-ticks-delivered"; // Storage public const string STORAGE_READ_ERRORS = "orleans-storage-read-errors"; public const string STORAGE_WRITE_ERRORS = "orleans-storage-write-errors"; public const string STORAGE_CLEAR_ERRORS = "orleans-storage-clear-errors"; public const string STORAGE_READ_LATENCY = "orleans-storage-read-latency"; public const string STORAGE_WRITE_LATENCY = "orleans-storage-write-latency"; public const string STORAGE_CLEAR_LATENCY = "orleans-storage-clear-latency"; // Streams public const string STREAMS_PUBSUB_PRODUCERS_ADDED = "orleans-streams-pubsub-producers-added"; public const string STREAMS_PUBSUB_PRODUCERS_REMOVED = "orleans-streams-pubsub-producers-removed"; public const string STREAMS_PUBSUB_PRODUCERS_TOTAL = "orleans-streams-pubsub-producers"; public const string STREAMS_PUBSUB_CONSUMERS_ADDED = "orleans-streams-pubsub-consumers-added"; public const string STREAMS_PUBSUB_CONSUMERS_REMOVED = "orleans-streams-pubsub-consumers-removed"; public const string STREAMS_PUBSUB_CONSUMERS_TOTAL = "orleans-streams-pubsub-consumers"; public const string STREAMS_PERSISTENT_STREAM_NUM_PULLING_AGENTS = "orleans-streams-persistent-stream-pulling-agents"; public const string STREAMS_PERSISTENT_STREAM_NUM_READ_MESSAGES = "orleans-streams-persistent-stream-messages-read"; public const string STREAMS_PERSISTENT_STREAM_NUM_SENT_MESSAGES = "orleans-streams-persistent-stream-messages-sent"; public const string STREAMS_PERSISTENT_STREAM_PUBSUB_CACHE_SIZE = "orleans-streams-persistent-stream-pubsub-cache-size"; public const string STREAMS_QUEUE_INITIALIZATION_FAILURES = "orleans-streams-queue-initialization-failures"; public const string STREAMS_QUEUE_INITIALIZATION_DURATION = "orleans-streams-queue-initialization-duration"; public const string STREAMS_QUEUE_INITIALIZATION_EXCEPTIONS = "orleans-streams-queue-initialization-exceptions"; public const string STREAMS_QUEUE_READ_FAILURES = "orleans-streams-queue-read-failures"; public const string STREAMS_QUEUE_READ_DURATION = "orleans-streams-queue-read-duration"; public const string STREAMS_QUEUE_READ_EXCEPTIONS = "orleans-streams-queue-read-exceptions"; public const string STREAMS_QUEUE_SHUTDOWN_FAILURES = "orleans-streams-queue-shutdown-failures"; public const string STREAMS_QUEUE_SHUTDOWN_DURATION = "orleans-streams-queue-shutdown-duration"; public const string STREAMS_QUEUE_SHUTDOWN_EXCEPTIONS = "orleans-streams-queue-shutdown-exceptions"; public const string STREAMS_QUEUE_MESSAGES_RECEIVED = "orleans-streams-queue-messages-received"; public const string STREAMS_QUEUE_OLDEST_MESSAGE_ENQUEUE_AGE = "orleans-streams-queue-oldest-message-enqueue-age"; public const string STREAMS_QUEUE_NEWEST_MESSAGE_ENQUEUE_AGE = "orleans-streams-queue-newest-message-enqueue-age"; public const string STREAMS_BLOCK_POOL_TOTAL_MEMORY = "orleans-streams-block-pool-total-memory"; public const string STREAMS_BLOCK_POOL_AVAILABLE_MEMORY = "orleans-streams-block-pool-available-memory"; public const string STREAMS_BLOCK_POOL_CLAIMED_MEMORY = "orleans-streams-block-pool-claimed-memory"; public const string STREAMS_BLOCK_POOL_RELEASED_MEMORY = "orleans-streams-block-pool-released-memory"; public const string STREAMS_BLOCK_POOL_ALLOCATED_MEMORY = "orleans-streams-block-pool-allocated-memory"; public const string STREAMS_QUEUE_CACHE_SIZE = "orleans-streams-queue-cache-size"; public const string STREAMS_QUEUE_CACHE_LENGTH = "orleans-streams-queue-cache-length"; public const string STREAMS_QUEUE_CACHE_MESSAGES_ADDED = "orleans-streams-queue-cache-messages-added"; public const string STREAMS_QUEUE_CACHE_MESSAGES_PURGED = "orleans-streams-queue-cache-messages-purged"; public const string STREAMS_QUEUE_CACHE_MEMORY_ALLOCATED = "orleans-streams-queue-cache-memory-allocated"; public const string STREAMS_QUEUE_CACHE_MEMORY_RELEASED = "orleans-streams-queue-cache-memory-released"; public const string STREAMS_QUEUE_CACHE_OLDEST_TO_NEWEST_DURATION = "orleans-streams-queue-cache-oldest-to-newest-duration"; public const string STREAMS_QUEUE_CACHE_OLDEST_AGE = "orleans-streams-queue-cache-oldest-age"; public const string STREAMS_QUEUE_CACHE_PRESSURE = "orleans-streams-queue-cache-pressure"; public const string STREAMS_QUEUE_CACHE_UNDER_PRESSURE = "orleans-streams-queue-cache-under-pressure"; public const string STREAMS_QUEUE_CACHE_PRESSURE_CONTRIBUTION_COUNT = "orleans-streams-queue-cache-pressure-contribution-count"; public const string RUNTIME_MEMORY_TOTAL_PHYSICAL_MEMORY_MB = "orleans-runtime-total-physical-memory"; public const string RUNTIME_MEMORY_AVAILABLE_MEMORY_MB = "orleans-runtime-available-memory"; } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/Instruments.cs ================================================ using System.Diagnostics.Metrics; namespace Orleans.Runtime; public static class Instruments { public static readonly Meter Meter = new("Microsoft.Orleans"); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/MessagingInstruments.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; using System.Threading; using Orleans.Messaging; namespace Orleans.Runtime { internal static class MessagingInstruments { internal static long _headerBytesSent; internal static long _headerBytesReceived; internal static readonly ObservableCounter HeaderBytesSentCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_SENT_BYTES_HEADER, () => _headerBytesSent, "bytes"); internal static readonly ObservableCounter HeaderBytesReceivedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_RECEIVED_BYTES_HEADER, () => _headerBytesReceived, "bytes"); internal static readonly CounterAggregator LocalMessagesSentCounterAggregator = new(); private static readonly ObservableCounter LocalMessagesSentCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_SENT_LOCALMESSAGES, LocalMessagesSentCounterAggregator.Collect); internal static readonly Counter FailedSentMessagesCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_SENT_FAILED); internal static readonly Counter DroppedSentMessagesCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_SENT_DROPPED); internal static readonly Counter RejectedMessagesCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_REJECTED); internal static readonly Counter ReroutedMessagesCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_REROUTED); internal static readonly Counter ExpiredMessagesCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_EXPIRED); internal static readonly UpDownCounter ConnectedClient = Instruments.Meter.CreateUpDownCounter(InstrumentNames.GATEWAY_CONNECTED_CLIENTS); internal static readonly Counter PingSendCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_PINGS_SENT); internal static readonly Counter PingReceivedCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_PINGS_RECEIVED); internal static readonly Counter PingReplyReceivedCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_PINGS_REPLYRECEIVED); internal static readonly Counter PingReplyMissedCounter = Instruments.Meter.CreateCounter(InstrumentNames.MESSAGING_PINGS_REPLYMISSED); // currently, bucket size need to be configured at collector side // [Add "hints" in Metric API to provide things like histogram bounds] // https://github.com/dotnet/runtime/issues/63650 // Histogram of sent message size, starting from 0 in multiples of 2 // (1=2^0, 2=2^2, ... , 256=2^8, 512=2^9, 1024==2^10, ... , up to ... 2^30=1GB) internal static readonly Histogram MessageSentSizeHistogram = Instruments.Meter.CreateHistogram(InstrumentNames.MESSAGING_SENT_MESSAGES_SIZE, "bytes"); internal static readonly Histogram MessageReceivedSizeHistogram = Instruments.Meter.CreateHistogram(InstrumentNames.MESSAGING_RECEIVED_MESSAGES_SIZE, "bytes"); internal enum Phase { Send, Receive, Dispatch, Invoke, Respond, } internal static void OnMessageExpired(Phase phase) { ExpiredMessagesCounter.Add(1, new KeyValuePair("Phase", phase)); } internal static void OnPingSend(SiloAddress destination) { PingSendCounter.Add(1, new KeyValuePair("Destination", destination.ToString())); } internal static void OnPingReceive(SiloAddress destination) { PingReceivedCounter.Add(1, new KeyValuePair("Destination", destination.ToString())); } internal static void OnPingReplyReceived(SiloAddress replier) { PingReplyReceivedCounter.Add(1, new KeyValuePair("Destination", replier.ToString())); } internal static void OnPingReplyMissed(SiloAddress replier) { PingReplyMissedCounter.Add(1, new KeyValuePair("Destination", replier.ToString())); } internal static void OnFailedSentMessage(Message msg) { if (msg == null || !msg.HasDirection) return; FailedSentMessagesCounter.Add(1, new KeyValuePair("Direction", msg.Direction.ToString())); } internal static void OnDroppedSentMessage(Message msg) { if (msg == null || !msg.HasDirection) return; DroppedSentMessagesCounter.Add(1, new KeyValuePair("Direction", msg.Direction.ToString())); } internal static void OnRejectedMessage(Message msg) { if (msg == null || !msg.HasDirection) return; RejectedMessagesCounter.Add(1, new KeyValuePair("Direction", msg.Direction.ToString())); } internal static void OnMessageReRoute(Message msg) { ReroutedMessagesCounter.Add(1, new KeyValuePair("Direction", msg.Direction.ToString())); } internal static void OnMessageReceive(Message msg, int numTotalBytes, int headerBytes, ConnectionDirection connectionDirection, SiloAddress remoteSiloAddress = null) { if (MessageReceivedSizeHistogram.Enabled) { if (remoteSiloAddress != null) { MessageReceivedSizeHistogram.Record(numTotalBytes, new KeyValuePair("ConnectionDirection", connectionDirection.ToString()), new KeyValuePair("MessageDirection", msg.Direction.ToString()), new KeyValuePair("silo", remoteSiloAddress)); } else { MessageReceivedSizeHistogram.Record(numTotalBytes, new KeyValuePair("ConnectionDirection", connectionDirection.ToString()), new KeyValuePair("MessageDirection", msg.Direction.ToString())); } } Interlocked.Add(ref _headerBytesReceived, headerBytes); } internal static void OnMessageSend(Message msg, int numTotalBytes, int headerBytes, ConnectionDirection connectionDirection, SiloAddress remoteSiloAddress = null) { Debug.Assert(numTotalBytes >= 0, $"OnMessageSend(numTotalBytes={numTotalBytes})"); if (MessageSentSizeHistogram.Enabled) { if (remoteSiloAddress != null) { MessageSentSizeHistogram.Record(numTotalBytes, new KeyValuePair("ConnectionDirection", connectionDirection.ToString()), new KeyValuePair("MessageDirection", msg.Direction.ToString()), new KeyValuePair("silo", remoteSiloAddress)); } else { MessageSentSizeHistogram.Record(numTotalBytes, new KeyValuePair("ConnectionDirection", connectionDirection.ToString()), new KeyValuePair("MessageDirection", msg.Direction.ToString())); } } Interlocked.Add(ref _headerBytesSent, headerBytes); } } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/MessagingProcessingInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class MessagingProcessingInstruments { private static readonly CounterAggregatorGroup DispatcherMessagesProcessedCounterAggregatorGroup = new(); private static readonly ObservableCounter DispatcherMessagesProcessedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_DISPATCHER_PROCESSED, DispatcherMessagesProcessedCounterAggregatorGroup.Collect); private static readonly CounterAggregatorGroup DispatcherMessagesReceivedCounterAggregatorGroup = new(); private static readonly ObservableCounter DispatcherMessagesReceivedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_DISPATCHER_RECEIVED, DispatcherMessagesReceivedCounterAggregatorGroup.Collect); private static readonly CounterAggregator DispatcherMessagesForwardedCounterAggregator = new(); private static readonly ObservableCounter DispatcherMessagesForwardedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_DISPATCHER_FORWARDED, DispatcherMessagesForwardedCounterAggregator.Collect); private static readonly CounterAggregator ImaReceivedCounterAggregator = new(); private static readonly ObservableCounter ImaReceivedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_IMA_RECEIVED, ImaReceivedCounterAggregator.Collect); private static readonly CounterAggregatorGroup ImaEnqueuedCounterAggregatorGroup = new(); private static readonly ObservableCounter ImaEnqueuedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.MESSAGING_IMA_ENQUEUED, ImaEnqueuedCounterAggregatorGroup.Collect); private static readonly CounterAggregator ImaMessageEnqueuedNullContext; private static readonly CounterAggregator ImaMessageEnqueuedSystemTarget; private static readonly CounterAggregator ImaMessageEnqueuedGrain; private static readonly CounterAggregator[] DispatcherMessagesReceivedCounters_NullContext; private static readonly CounterAggregator[] DispatcherMessagesReceivedCounters_Grain; private static readonly CounterAggregator[] DispatcherMessagesProcessedCounters_Ok; private static readonly CounterAggregator[] DispatcherMessagesProcessedCounters_Error; static MessagingProcessingInstruments() { ImaMessageEnqueuedNullContext = ImaEnqueuedCounterAggregatorGroup.FindOrCreate(new("Context", "ToNull")); ImaMessageEnqueuedSystemTarget = ImaEnqueuedCounterAggregatorGroup.FindOrCreate(new("Context", "ToSystemTarget")); ImaMessageEnqueuedGrain = ImaEnqueuedCounterAggregatorGroup.FindOrCreate(new("Context", "ToGrain")); var directionEnumValues = Enum.GetValues(); DispatcherMessagesReceivedCounters_NullContext = new CounterAggregator[directionEnumValues.Length + 1]; DispatcherMessagesReceivedCounters_Grain = new CounterAggregator[directionEnumValues.Length + 1]; DispatcherMessagesProcessedCounters_Ok = new CounterAggregator[directionEnumValues.Length + 1]; DispatcherMessagesProcessedCounters_Error = new CounterAggregator[directionEnumValues.Length + 1]; foreach (var value in directionEnumValues) { DispatcherMessagesReceivedCounters_NullContext[(int)value] = DispatcherMessagesReceivedCounterAggregatorGroup.FindOrCreate(new("Context", "None", "Direction", value.ToString())); DispatcherMessagesReceivedCounters_Grain[(int)value] = DispatcherMessagesReceivedCounterAggregatorGroup.FindOrCreate(new("Context", "Grain", "Direction", value.ToString())); DispatcherMessagesProcessedCounters_Ok[(int)value] = DispatcherMessagesProcessedCounterAggregatorGroup.FindOrCreate(new("Direction", value.ToString(), "Status", "Ok")); DispatcherMessagesProcessedCounters_Error[(int)value] = DispatcherMessagesProcessedCounterAggregatorGroup.FindOrCreate(new("Direction", value.ToString(), "Status", "Error")); } } internal static void OnDispatcherMessageReceive(Message msg) { if (!DispatcherMessagesReceivedCounter.Enabled) return; var context = RuntimeContext.Current; var counters = context switch { null => DispatcherMessagesReceivedCounters_NullContext, _ => DispatcherMessagesReceivedCounters_Grain, }; counters[(int)msg.Direction].Add(1); } internal static void OnDispatcherMessageProcessedOk(Message msg) { if (DispatcherMessagesProcessedCounter.Enabled) { DispatcherMessagesProcessedCounters_Ok[(int)msg.Direction].Add(1); } } internal static void OnDispatcherMessageProcessedError(Message msg) { if (DispatcherMessagesProcessedCounter.Enabled) { DispatcherMessagesProcessedCounters_Error[(int)msg.Direction].Add(1); } } internal static void OnDispatcherMessageForwared(Message msg) { if (DispatcherMessagesForwardedCounter.Enabled) { DispatcherMessagesForwardedCounterAggregator.Add(1); } } internal static void OnImaMessageReceived(Message msg) { if (ImaReceivedCounter.Enabled) { ImaReceivedCounterAggregator.Add(1); } } internal static void OnImaMessageEnqueued(IGrainContext context) { if (!ImaEnqueuedCounter.Enabled) { return; } switch (context) { case null: ImaMessageEnqueuedNullContext.Add(1); break; case ISystemTargetBase: ImaMessageEnqueuedSystemTarget.Add(1); break; default: ImaMessageEnqueuedGrain.Add(1); break; } } internal static ObservableGauge ActivationDataAll; internal static void RegisterActivationDataAllObserve(Func observeValue) { ActivationDataAll = Instruments.Meter.CreateObservableGauge(InstrumentNames.MESSAGING_PROCESSING_ACTIVATION_DATA_ALL, observeValue); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/NetworkingInstruments.cs ================================================ using System.Collections.Generic; using System.Diagnostics.Metrics; using Orleans.Messaging; namespace Orleans.Runtime; internal static class NetworkingInstruments { internal static Counter ClosedSocketsCounter = Instruments.Meter.CreateCounter(InstrumentNames.NETWORKING_SOCKETS_CLOSED); internal static Counter OpenedSocketsCounter = Instruments.Meter.CreateCounter(InstrumentNames.NETWORKING_SOCKETS_OPENED); internal static void OnOpenedSocket(ConnectionDirection direction) { OpenedSocketsCounter.Add(1, new KeyValuePair("Direction", direction.ToString())); } internal static void OnClosedSocket(ConnectionDirection direction) { ClosedSocketsCounter.Add(1, new KeyValuePair("Direction", direction.ToString())); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/OrleansInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; /// /// Provides the used by Orleans runtime metrics. /// /// The meter factory used to create the Orleans meter. public class OrleansInstruments(IMeterFactory meterFactory) { /// /// Gets the Orleans runtime meter. /// public Meter Meter { get; } = (meterFactory ?? throw new ArgumentNullException(nameof(meterFactory))).Create("Microsoft.Orleans"); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/ReminderInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class ReminderInstruments { public static Histogram TardinessSeconds = Instruments.Meter.CreateHistogram(InstrumentNames.REMINDERS_TARDINESS, "seconds"); public static ObservableGauge ActiveReminders; public static void RegisterActiveRemindersObserve(Func observeValue) { ActiveReminders = Instruments.Meter.CreateObservableGauge(InstrumentNames.REMINDERS_NUMBER_ACTIVE_REMINDERS, observeValue); } public static Counter TicksDelivered = Instruments.Meter.CreateCounter(InstrumentNames.REMINDERS_COUNTERS_TICKS_DELIVERED); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/SchedulerInstruments.cs ================================================ using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal class SchedulerInstruments { internal static readonly Counter LongRunningTurnsCounter = Instruments.Meter.CreateCounter(InstrumentNames.SCHEDULER_NUM_LONG_RUNNING_TURNS); } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/StorageInstruments.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class StorageInstruments { private static readonly Histogram StorageReadHistogram = Instruments.Meter.CreateHistogram(InstrumentNames.STORAGE_READ_LATENCY, "ms"); private static readonly Histogram StorageWriteHistogram = Instruments.Meter.CreateHistogram(InstrumentNames.STORAGE_WRITE_LATENCY, "ms"); private static readonly Histogram StorageClearHistogram = Instruments.Meter.CreateHistogram(InstrumentNames.STORAGE_CLEAR_LATENCY, "ms"); private static readonly Counter StorageReadErrorsCounter = Instruments.Meter.CreateCounter(InstrumentNames.STORAGE_READ_ERRORS); private static readonly Counter StorageWriteErrorsCounter = Instruments.Meter.CreateCounter(InstrumentNames.STORAGE_WRITE_ERRORS); private static readonly Counter StorageClearErrorsCounter = Instruments.Meter.CreateCounter(InstrumentNames.STORAGE_CLEAR_ERRORS); internal static void OnStorageRead(TimeSpan latency, string providerTypeName, string stateName, string stateTypeName) { if (StorageReadHistogram.Enabled) { StorageReadHistogram.Record( latency.TotalMilliseconds, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } internal static void OnStorageWrite(TimeSpan latency, string providerTypeName, string stateName, string stateTypeName) { if (StorageWriteHistogram.Enabled) { StorageWriteHistogram.Record( latency.TotalMilliseconds, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } internal static void OnStorageReadError(string providerTypeName, string stateName, string stateTypeName) { if (StorageReadErrorsCounter.Enabled) { StorageReadErrorsCounter.Add(1, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } internal static void OnStorageWriteError(string providerTypeName, string stateName, string stateTypeName) { if (StorageWriteErrorsCounter.Enabled) { StorageWriteErrorsCounter.Add(1, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } internal static void OnStorageDelete(TimeSpan latency, string providerTypeName, string stateName, string stateTypeName) { if (StorageClearHistogram.Enabled) { StorageClearHistogram.Record(latency.TotalMilliseconds, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } internal static void OnStorageDeleteError(string providerTypeName, string stateName, string stateTypeName) { if (StorageClearErrorsCounter.Enabled) { StorageClearErrorsCounter.Add(1, [ new KeyValuePair("provider_type_name", providerTypeName), new KeyValuePair("state_name", stateName), new KeyValuePair("state_type", stateTypeName) ]); } } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/StreamInstruments.cs ================================================ using System; using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class StreamInstruments { public static Counter PubSubProducersAdded = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_PRODUCERS_ADDED); public static Counter PubSubProducersRemoved = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_PRODUCERS_REMOVED); public static Counter PubSubProducersTotal = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_PRODUCERS_TOTAL); public static Counter PubSubConsumersAdded = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_CONSUMERS_ADDED); public static Counter PubSubConsumersRemoved = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_CONSUMERS_REMOVED); public static Counter PubSubConsumersTotal = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PUBSUB_CONSUMERS_TOTAL); public static ObservableGauge PersistentStreamPullingAgents; public static void RegisterPersistentStreamPullingAgentsObserve(Func> observeValue) { PersistentStreamPullingAgents = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_PERSISTENT_STREAM_NUM_PULLING_AGENTS, observeValue); } public static Counter PersistentStreamReadMessages = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PERSISTENT_STREAM_NUM_READ_MESSAGES); public static Counter PersistentStreamSentMessages = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_PERSISTENT_STREAM_NUM_SENT_MESSAGES); public static ObservableGauge PersistentStreamPubSubCacheSize; public static void RegisterPersistentStreamPubSubCacheSizeObserve(Func> observeValue) { PersistentStreamPubSubCacheSize = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_PERSISTENT_STREAM_PUBSUB_CACHE_SIZE, observeValue); } } ================================================ FILE: src/Orleans.Core/Diagnostics/Metrics/WatchdogInstruments.cs ================================================ using System.Diagnostics.Metrics; namespace Orleans.Runtime; internal static class WatchdogInstruments { internal static Counter HealthChecks = Instruments.Meter.CreateCounter(InstrumentNames.WATCHDOG_NUM_HEALTH_CHECKS); internal static Counter FailedHealthChecks = Instruments.Meter.CreateCounter(InstrumentNames.WATCHDOG_NUM_FAILED_HEALTH_CHECKS); } ================================================ FILE: src/Orleans.Core/GrainDirectory/IDhtGrainDirectory.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.GrainDirectory { /// /// Recursive distributed operations on grain directories. /// Each operation may forward the request to a remote owner, increasing the hopCount. /// /// The methods here can be called remotely (where extended by IRemoteGrainDirectory) or /// locally (where extended by ILocalGrainDirectory) /// internal interface IDhtGrainDirectory { /// /// Record a new grain activation by adding it to the directory. /// This method must be called from a scheduler thread. /// /// The address of the new activation. /// Counts recursion depth across silos /// The registered address and the version associated with this directory mapping. Task RegisterAsync(GrainAddress address, int hopCount = 0); /// /// Record a new grain activation by adding it to the directory. /// This method must be called from a scheduler thread. /// /// The address of the new activation. /// The existing registration, which may be null. /// Counts recursion depth across silos /// The registered address and the version associated with this directory mapping. Task RegisterAsync(GrainAddress address, GrainAddress? currentRegistration, int hopCount = 0); /// /// Removes the record for an existing activation from the directory service. /// This is used when an activation is being deleted. /// This method must be called from a scheduler thread. /// /// The address of the activation to remove. /// Counts recursion depth across silos. /// The reason for deregistration. /// An acknowledgement that the deregistration has completed. Task UnregisterAsync(GrainAddress address, UnregistrationCause cause, int hopCount = 0); /// /// Unregister a batch of addresses at once /// This method must be called from a scheduler thread. /// /// The addresses to deregister. /// Counts recursion depth across silos. /// The reason for deregistration. /// An acknowledgement that the unregistration has completed. Task UnregisterManyAsync(List addresses, UnregistrationCause cause, int hopCount = 0); /// /// Removes all directory information about a grain. /// This method must be called from a scheduler thread. /// /// The ID of the grain. /// Counts recursion depth across silos. /// /// An acknowledgement that the deletion has completed. /// Task DeleteGrainAsync(GrainId grainId, int hopCount = 0); /// /// Fetches complete directory information for a grain. /// If there is no local information, then this method will query the appropriate remote directory node. /// This method must be called from a scheduler thread. /// /// The ID of the grain to look up. /// Counts recursion depth across silos. /// A list of all known activations of the grain, and the e-tag. Task LookupAsync(GrainId grainId, int hopCount = 0); } /// /// Represents the address of a grain as well as a version tag. /// [Serializable, GenerateSerializer, Immutable] internal readonly struct AddressAndTag { /// /// The address. /// [Id(0)] public readonly GrainAddress? Address; /// /// The version of this entry. /// [Id(1)] public readonly int VersionTag; public AddressAndTag(GrainAddress? address, int versionTag) { Address = address; VersionTag = versionTag; } } /// /// Indicates the reason for removing activations from the directory. /// This influences the conditions that are applied when determining whether or not to remove an entry. /// public enum UnregistrationCause : byte { /// /// Remove the directory entry forcefully, without any conditions /// Force = 0, /// /// Remove the directory entry only if it is not too fresh (to avoid races on new registrations) /// NonexistentActivation = 1, } } ================================================ FILE: src/Orleans.Core/GrainDirectory/IGrainLocator.cs ================================================ #nullable enable using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.GrainDirectory { /// /// Used to locate Grain activation in the cluster /// public interface IGrainLocator { /// /// Registers the provided address in the appropriate grain directory. /// /// The address to register. /// The grain address which is registered in the directory immediately following this call. Task Register(GrainAddress address, GrainAddress? previousRegistration); /// /// Deregisters a grain address from the directory. /// /// The address to deregister. /// The cause for deregistration. /// A representing the work performed. Task Unregister(GrainAddress address, UnregistrationCause cause); /// /// Finds the corresponding address for a grain. /// /// The grain id. /// The address corresponding to the specified grain id, or if the grain is not currently registered. ValueTask Lookup(GrainId grainId); /// /// Updates the cache with a grain placement decision or known activation address. /// /// The grain identifier. /// The silo which may host the grain. void UpdateCache(GrainId grainId, SiloAddress siloAddress); /// /// Invalidates any lookup cache entry associated with the provided grain id. /// /// /// The grain id. /// void InvalidateCache(GrainId grainId); /// /// Removes the specified address from the lookup cache. /// /// /// The grain address to invalidate. /// void InvalidateCache(GrainAddress address); /// /// Attempts to find the grain address for the provided grain id in the local lookup cache. /// /// The grain id to find. /// The resulting grain address, if found, or if not found. /// A value indicating whether a valid entry was found. bool TryLookupInCache(GrainId grainId, [NotNullWhen(true)] out GrainAddress? address); } } ================================================ FILE: src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.CodeGeneration; using Orleans.Metadata; using Orleans.Runtime; using Orleans.Runtime.Versions; using Orleans.Serialization; using Orleans.Serialization.Cloning; using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; namespace Orleans.GrainReferences { /// /// The central point for creating instances. /// public sealed class GrainReferenceActivator { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly IServiceProvider _serviceProvider; private readonly IGrainReferenceActivatorProvider[] _providers; private Dictionary<(GrainType, GrainInterfaceType), IGrainReferenceActivator> _activators = new(); /// /// Initializes a new instance of the class. /// /// The service provider. /// The collection of grain reference activator providers. public GrainReferenceActivator( IServiceProvider serviceProvider, IEnumerable providers) { _serviceProvider = serviceProvider; _providers = providers.ToArray(); } /// /// Creates a grain reference pointing to the specified grain id and implementing the specified grain interface type. /// /// The grain id. /// The grain interface type. /// A new grain reference. public GrainReference CreateReference(GrainId grainId, GrainInterfaceType interfaceType) { if (!_activators.TryGetValue((grainId.Type, interfaceType), out var entry)) { entry = CreateActivator(grainId.Type, interfaceType); } var result = entry.CreateReference(grainId); return result; } /// /// Creates a grain reference activator for the provided arguments. /// /// The grain type. /// the grain interface type. /// An activator for the provided arguments. /// No suitable activator was found. private IGrainReferenceActivator CreateActivator(GrainType grainType, GrainInterfaceType interfaceType) { lock (_lockObj) { if (!_activators.TryGetValue((grainType, interfaceType), out var entry)) { IGrainReferenceActivator activator = null; foreach (var provider in _providers) { if (provider.TryGet(grainType, interfaceType, out activator)) { break; } } if (activator is null) { throw new InvalidOperationException($"Unable to find an {nameof(IGrainReferenceActivatorProvider)} for grain type {grainType}"); } entry = activator; _activators = new(_activators) { [(grainType, interfaceType)] = entry }; } return entry; } } } /// /// Creates grain references which do not have any specified grain interface, only a target grain id. /// internal class UntypedGrainReferenceActivatorProvider : IGrainReferenceActivatorProvider { private readonly CopyContextPool _copyContextPool; private readonly CodecProvider _codecProvider; private readonly GrainVersionManifest _versionManifest; private readonly IServiceProvider _serviceProvider; private IGrainReferenceRuntime _grainReferenceRuntime; /// /// Initializes a new instance of the class. /// /// The grain version manifest. /// The copy context pool. /// The serialization codec provider. /// The service provider. public UntypedGrainReferenceActivatorProvider( GrainVersionManifest manifest, CodecProvider codecProvider, CopyContextPool copyContextPool, IServiceProvider serviceProvider) { _versionManifest = manifest; _codecProvider = codecProvider; _copyContextPool = copyContextPool; _serviceProvider = serviceProvider; } /// public bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, out IGrainReferenceActivator activator) { if (!interfaceType.IsDefault) { activator = default; return false; } var interfaceVersion = _versionManifest.GetLocalVersion(interfaceType); var runtime = _grainReferenceRuntime ??= _serviceProvider.GetRequiredService(); var shared = new GrainReferenceShared( grainType, interfaceType, interfaceVersion, runtime, InvokeMethodOptions.None, _codecProvider, _copyContextPool, _serviceProvider); activator = new UntypedGrainReferenceActivator(shared); return true; } /// /// Activator for grain references which have no specified grain interface, only a target grain id. /// private class UntypedGrainReferenceActivator : IGrainReferenceActivator { private readonly GrainReferenceShared _shared; /// /// Initializes a new instance of the class. /// /// The shared functionality for all grains of a given type. public UntypedGrainReferenceActivator(GrainReferenceShared shared) { _shared = shared; } /// public GrainReference CreateReference(GrainId grainId) { return GrainReference.FromGrainId(_shared, grainId); } } } /// /// Provides functionality for mapping from a to the corresponding generated proxy type. /// internal class RpcProvider { private readonly TypeConverter _typeConverter; private readonly Dictionary _mapping; /// /// Initializes a new instance of the class. /// /// The local type manifest. /// The grain interface type to grain type resolver. /// The type converter, for generic parameter. public RpcProvider( IOptions config, GrainInterfaceTypeResolver resolver, TypeConverter typeConverter) { _typeConverter = typeConverter; var proxyTypes = config.Value.InterfaceProxies; _mapping = new Dictionary(); foreach (var proxyType in proxyTypes) { if (!typeof(IAddressable).IsAssignableFrom(proxyType)) { continue; } var type = proxyType switch { { IsGenericType: true } => proxyType.GetGenericTypeDefinition(), _ => proxyType }; var grainInterface = GetMainInterface(type); var id = resolver.GetGrainInterfaceType(grainInterface); _mapping[id] = type; } static Type GetMainInterface(Type t) { var all = t.GetInterfaces(); Type result = null; foreach (var candidate in all) { if (result is null) { result = candidate; } else { if (result.IsAssignableFrom(candidate)) { result = candidate; } } } return result switch { { IsGenericType: true } => result.GetGenericTypeDefinition(), _ => result }; } } /// /// Gets the generated proxy object type corresponding to the specified . /// /// The grain interface type. /// The proxy object type. /// A value indicating whether a suitable type was found and was able to be constructed. public bool TryGet(GrainInterfaceType interfaceType, [NotNullWhen(true)] out Type result) { GrainInterfaceType lookupId; Type[] args; if (GenericGrainInterfaceType.TryParse(interfaceType, out var genericId)) { lookupId = genericId.GetGenericGrainType().Value; args = genericId.GetArguments(_typeConverter); } else { lookupId = interfaceType; args = default; } if (!_mapping.TryGetValue(lookupId, out result)) { return false; } if (args is not null) { result = result.MakeGenericType(args); } return true; } } /// /// Creates grain references using generated proxy objects. /// internal class GrainReferenceActivatorProvider : IGrainReferenceActivatorProvider { private readonly CopyContextPool _copyContextPool; private readonly CodecProvider _codecProvider; private readonly IServiceProvider _serviceProvider; private readonly GrainPropertiesResolver _propertiesResolver; private readonly RpcProvider _rpcProvider; private readonly GrainVersionManifest _grainVersionManifest; private IGrainReferenceRuntime _grainReferenceRuntime; /// /// Initializes a new instance of the class. /// /// The service provider. /// The grain property resolver. /// The proxy object type provider. /// The copy context pool. /// The serialization codec provider. /// The grain version manifest. public GrainReferenceActivatorProvider( IServiceProvider serviceProvider, GrainPropertiesResolver propertiesResolver, RpcProvider rpcProvider, CopyContextPool copyContextPool, CodecProvider codecProvider, GrainVersionManifest grainVersionManifest) { _serviceProvider = serviceProvider; _propertiesResolver = propertiesResolver; _rpcProvider = rpcProvider; _copyContextPool = copyContextPool; _codecProvider = codecProvider; _grainVersionManifest = grainVersionManifest; } /// public bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, out IGrainReferenceActivator activator) { if (!_rpcProvider.TryGet(interfaceType, out var proxyType)) { activator = default; return false; } var unordered = false; var properties = _propertiesResolver.GetGrainProperties(grainType); if (properties.Properties.TryGetValue(WellKnownGrainTypeProperties.Unordered, out var unorderedString) && string.Equals("true", unorderedString, StringComparison.OrdinalIgnoreCase)) { unordered = true; } var interfaceVersion = _grainVersionManifest.GetLocalVersion(interfaceType); var invokeMethodOptions = unordered ? InvokeMethodOptions.Unordered : InvokeMethodOptions.None; var runtime = _grainReferenceRuntime ??= _serviceProvider.GetRequiredService(); var shared = new GrainReferenceShared( grainType, interfaceType, interfaceVersion, runtime, invokeMethodOptions, _codecProvider, _copyContextPool, _serviceProvider); activator = new GrainReferenceActivator(proxyType, shared); return true; } /// /// Creates grain references for a given grain type and grain interface type. /// private sealed class GrainReferenceActivator : IGrainReferenceActivator { private readonly GrainReferenceShared _shared; private readonly Func _create; /// /// Initializes a new instance of the class. /// /// The generated proxy object type. /// The functionality shared between all grain references for a specified grain type and grain interface type. public GrainReferenceActivator(Type referenceType, GrainReferenceShared shared) { _shared = shared; var ctor = referenceType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { typeof(GrainReferenceShared), typeof(IdSpan) }) ?? throw new SerializerException("Invalid proxy type: " + referenceType); var method = new DynamicMethod(referenceType.Name, typeof(GrainReference), new[] { typeof(object), typeof(GrainReferenceShared), typeof(IdSpan) }); var il = method.GetILGenerator(); // arg0 is unused for better delegate performance (avoids argument shuffling thunk) il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Ret); _create = method.CreateDelegate>(); } public GrainReference CreateReference(GrainId grainId) => _create(_shared, grainId.Key); } } /// /// Functionality for getting the appropriate for a given and . /// public interface IGrainReferenceActivatorProvider { /// /// Gets a grain reference activator for the provided arguments. /// /// The grain type. /// The grain interface type. /// The grain activator. /// A value indicating whether a suitable grain activator was found. bool TryGet(GrainType grainType, GrainInterfaceType interfaceType, [NotNullWhen(true)] out IGrainReferenceActivator activator); } /// /// Creates grain references. /// public interface IGrainReferenceActivator { /// /// Creates a new grain reference. /// /// The grain id. /// A new grain reference. public GrainReference CreateReference(GrainId grainId); } } ================================================ FILE: src/Orleans.Core/Hosting/OrleansClientGenericHostExtensions.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Runtime; namespace Microsoft.Extensions.Hosting { /// /// Extension methods for . /// public static class OrleansClientGenericHostExtensions { private static readonly Type MarkerType = typeof(OrleansBuilderMarker); /// /// Configures the host app builder to host an Orleans client. /// /// The host app builder. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// Note that this method shouldn't be used in conjunction with HostApplicationBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null. public static HostApplicationBuilder UseOrleansClient(this HostApplicationBuilder hostAppBuilder) => UseOrleansClient(hostAppBuilder, _ => { }); /// /// Configures the host app builder to host an Orleans client. /// /// The host app builder. /// The delegate used to configure the client. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method shouldn't be used in conjunction with HostApplicationBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static HostApplicationBuilder UseOrleansClient( this HostApplicationBuilder hostAppBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostAppBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); hostAppBuilder.Services.AddOrleansClient(hostAppBuilder.Configuration, configureDelegate); return hostAppBuilder; } /// Configures the host app builder to host an Orleans client. /// /// /// The host app builder. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// Note that this method shouldn't be used in conjunction with IHostApplicationBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null. public static IHostApplicationBuilder UseOrleansClient(this IHostApplicationBuilder hostAppBuilder) => UseOrleansClient(hostAppBuilder, _ => { }); /// /// Configures the host app builder to host an Orleans client. /// /// The host app builder. /// The delegate used to configure the client. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method shouldn't be used in conjunction with IHostApplicationBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static IHostApplicationBuilder UseOrleansClient( this IHostApplicationBuilder hostAppBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostAppBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); hostAppBuilder.Services.AddOrleansClient(hostAppBuilder.Configuration, configureDelegate); return hostAppBuilder; } /// /// Configures the host builder to host an Orleans client. /// /// The host builder. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// Note that this method should not be used in conjunction with IHostBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null. public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder) => hostBuilder.UseOrleansClient((_, _) => { }); /// /// Configures the host builder to host an Orleans client. /// /// The host builder. /// The delegate used to configure the client. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method should not be used in conjunction with IHostBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Action configureDelegate) => hostBuilder.UseOrleansClient((_, clientBuilder) => configureDelegate(clientBuilder)); /// /// Configures the host builder to host an Orleans client. /// /// The host builder. /// The delegate used to configure the client. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method should not be used in conjunction with IHostBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); if (hostBuilder.Properties.ContainsKey("HasOrleansSiloBuilder")) { throw GetOrleansSiloAddedException(); } hostBuilder.Properties["HasOrleansClientBuilder"] = "true"; return hostBuilder.ConfigureServices((ctx, services) => configureDelegate(ctx, AddOrleansClient(services, ctx.Configuration))); } /// /// Configures the service collection to host an Orleans client. /// /// The service collection. /// The delegate used to configure the client. /// The service collection. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method should not be used in conjunction with UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static IServiceCollection AddOrleansClient(this IServiceCollection services, Action configureDelegate) { ArgumentNullException.ThrowIfNull(configureDelegate); var clientBuilder = AddOrleansClient(services, configuration: null); configureDelegate(clientBuilder); return services; } /// /// Configures the service collection to host an Orleans client. /// /// The service collection. /// The configuration. /// The delegate used to configure the client. /// The service collection. /// /// Calling this method multiple times on the same instance will result in one client being configured. /// However, the effects of will be applied once for each call. /// Note that this method should not be used in conjunction with UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. public static IServiceCollection AddOrleansClient(this IServiceCollection services, IConfiguration configuration, Action configureDelegate) { ArgumentNullException.ThrowIfNull(configureDelegate); var clientBuilder = AddOrleansClient(services, configuration: configuration); configureDelegate(clientBuilder); return services; } private static IClientBuilder AddOrleansClient(IServiceCollection services, IConfiguration configuration) { configuration ??= new ConfigurationBuilder().Build(); IClientBuilder clientBuilder = default; foreach (var descriptor in services.Where(d => d.ServiceType.Equals(MarkerType))) { var instance = (OrleansBuilderMarker)descriptor.ImplementationInstance; clientBuilder = instance.BuilderInstance switch { IClientBuilder existingBuilder => existingBuilder, _ => throw GetOrleansSiloAddedException() }; } if (clientBuilder is null) { clientBuilder = new ClientBuilder(services, configuration); services.AddSingleton(new OrleansBuilderMarker(clientBuilder)); } return clientBuilder; } private static OrleansConfigurationException GetOrleansSiloAddedException() => new("Do not use UseOrleans with UseOrleansClient. If you want a client and server in the same process, only UseOrleans is necessary and the UseOrleansClient call can be removed."); } /// /// Marker type used for storing a builder in a service collection. /// internal sealed class OrleansBuilderMarker { /// /// Initializes a new instance of the class. /// /// The builder instance. public OrleansBuilderMarker(object builderInstance) => BuilderInstance = builderInstance; /// /// Gets the builder instance. /// public object BuilderInstance { get; } } } ================================================ FILE: src/Orleans.Core/IDs/GenericGrainInterfaceType.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Orleans.Serialization.TypeSystem; using Orleans.Utilities; namespace Orleans.Runtime { /// /// Represents a that is parameterized using type parameters. /// [Immutable] public readonly struct GenericGrainInterfaceType { /// /// Initializes a new instance of the struct. /// /// The underlying grain interface type. private GenericGrainInterfaceType(GrainInterfaceType value, int arity) { Value = value; Arity = arity; } /// /// The underlying /// public GrainInterfaceType Value { get; } /// /// The arity of the generic type. /// public int Arity { get; } /// /// Returns if this instance contains concrete type parameters. /// public bool IsConstructed => TypeConverterExtensions.IsConstructed(this.Value.Value); /// /// Returns the generic interface id corresponding to the provided value. /// public static bool TryParse(GrainInterfaceType grainType, out GenericGrainInterfaceType result) { if (grainType.IsDefault) { result = default; return false; } var arity = TypeConverterExtensions.GetGenericTypeArity(grainType.Value); if (arity > 0) { result = new GenericGrainInterfaceType(grainType, arity); return true; } result = default; return false; } /// /// Returns a non-constructed version of this instance. /// public GenericGrainInterfaceType GetGenericGrainType() { var generic = TypeConverterExtensions.GetDeconstructed(Value.Value); return new GenericGrainInterfaceType(new GrainInterfaceType(generic), Arity); } /// /// Returns a constructed version of this instance. /// public GenericGrainInterfaceType Construct(TypeConverter formatter, params Type[] typeArguments) { if (Arity != typeArguments.Length) { ThrowIncorrectArgumentLength(typeArguments); } var constructed = formatter.GetConstructed(this.Value.Value, typeArguments); return new GenericGrainInterfaceType(new GrainInterfaceType(constructed), Arity); } /// /// Returns the type arguments which this instance was constructed with. /// public Type[] GetArguments(TypeConverter formatter) => formatter.GetArguments(this.Value.Value); /// /// Returns a UTF8 interpretation of the current instance. /// public override string ToString() => Value.ToString(); [DoesNotReturn] private void ThrowIncorrectArgumentLength(Type[] typeArguments) => throw new ArgumentException($"Incorrect number of type arguments, {typeArguments.Length}, to construct a generic grain type with arity {Arity}.", nameof(typeArguments)); } } ================================================ FILE: src/Orleans.Core/IDs/GenericGrainType.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Orleans.Serialization.TypeSystem; using Orleans.Utilities; namespace Orleans.Runtime { /// /// Represents a that is parameterized using type parameters. /// [Immutable] public readonly struct GenericGrainType : IEquatable { /// /// Initializes a new instance of the struct. /// /// The underlying grain type. /// The generic arity of the grain type. private GenericGrainType(GrainType grainType, int arity) { GrainType = grainType; Arity = arity; } /// /// The underlying grain type. /// public GrainType GrainType { get; } /// /// The generic arity of the grain type. /// public int Arity { get; } /// /// Returns if this instance contains concrete type parameters. /// public bool IsConstructed => TypeConverterExtensions.IsConstructed(this.GrainType.Value); /// /// Returns the generic grain type corresponding to the provided value. /// public static bool TryParse(GrainType grainType, out GenericGrainType result) { var arity = TypeConverterExtensions.GetGenericTypeArity(grainType.Value); if (arity > 0) { result = new GenericGrainType(grainType, arity); return true; } result = default; return false; } /// /// Returns a non-constructed version of this instance. /// public GenericGrainType GetUnconstructedGrainType() { var generic = TypeConverterExtensions.GetDeconstructed(GrainType.Value); return new GenericGrainType(new GrainType(generic), Arity); } /// /// Returns a constructed version of this instance. /// public GenericGrainType Construct(TypeConverter formatter, params Type[] typeArguments) { if (Arity != typeArguments.Length) { ThrowIncorrectArgumentLength(typeArguments); } var constructed = formatter.GetConstructed(this.GrainType.Value, typeArguments); return new GenericGrainType(new GrainType(constructed), Arity); } /// /// Gets the type arguments using the provided type converter. /// /// The type converter /// The type arguments. public Type[] GetArguments(TypeConverter converter) => converter.GetArguments(this.GrainType.Value); /// public override string ToString() => this.GrainType.ToString(); /// public bool Equals(GenericGrainType other) => this.GrainType.Equals(other.GrainType); /// public override bool Equals(object obj) => obj is GenericGrainType other && this.Equals(other); /// public override int GetHashCode() => this.GrainType.GetHashCode(); [DoesNotReturn] private void ThrowIncorrectArgumentLength(Type[] typeArguments) => throw new ArgumentException($"Incorrect number of type arguments, {typeArguments.Length}, to construct a generic grain type with arity {Arity}.", nameof(typeArguments)); } } ================================================ FILE: src/Orleans.Core/Lifecycle/ClusterClientLifecycle.cs ================================================ using Microsoft.Extensions.Logging; namespace Orleans { /// /// Implementation of . /// internal class ClusterClientLifecycle : LifecycleSubject, IClusterClientLifecycle { /// /// Initializes a new instance of the class. /// /// The logger. public ClusterClientLifecycle(ILogger logger) : base(logger) { } } } ================================================ FILE: src/Orleans.Core/Lifecycle/ClusterClientLifecycleExtensions.cs ================================================ namespace Orleans.Runtime { /// /// Extensions for . /// public static class LifecycleParticipantExtensions { /// /// Conforms components written to participate with any to take part in specific lifecycles. /// /// The target lifecycle observer type. /// The lifecycle participant. /// An adapter wrapped around which implements . public static ILifecycleParticipant ParticipateIn(this ILifecycleParticipant participant) where TLifecycle : ILifecycleObservable { return new Bridge(participant); } /// /// Adapts one lifecycle participant to a lifecycle participant of another observer type. /// /// private class Bridge : ILifecycleParticipant where TLifecycle : ILifecycleObservable { private readonly ILifecycleParticipant participant; public Bridge(ILifecycleParticipant participant) { this.participant = participant; } public void Participate(TLifecycle lifecycle) { this.participant?.Participate(lifecycle); } } } } ================================================ FILE: src/Orleans.Core/Lifecycle/IClusterClientLifecycle.cs ================================================ namespace Orleans { /// /// A marker type for client lifecycles. /// public interface IClusterClientLifecycle : ILifecycleObservable { } } ================================================ FILE: src/Orleans.Core/Lifecycle/LifecycleSubject.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans { /// /// Provides functionality for observing a lifecycle. /// /// /// /// Single use, does not support multiple start/stop cycles. /// Once started, no other observers can be subscribed. /// OnStart starts stages in order until first failure or cancellation. /// OnStop stops states in reverse order starting from highest started stage. /// OnStop stops all stages regardless of errors even if canceled. /// /// public abstract partial class LifecycleSubject : ILifecycleSubject { private readonly List subscribers = []; protected readonly ILogger Logger; private int? _highStage = null; protected LifecycleSubject(ILogger logger) { ArgumentNullException.ThrowIfNull(logger); Logger = logger; } /// /// Gets the name of the specified numeric stage. /// /// The stage number. /// The name of the stage. protected virtual string GetStageName(int stage) => stage.ToString(); /// /// Gets the collection of all stage numbers and their corresponding names. /// /// /// The lifecycle stage class. /// The collection of all stage numbers and their corresponding names. protected static ImmutableDictionary GetStageNames(Type type) { try { var result = ImmutableDictionary.CreateBuilder(); var fields = type.GetFields( System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); foreach (var field in fields) { if (typeof(int).IsAssignableFrom(field.FieldType)) { try { var value = (int)field.GetValue(null); result[value] = $"{field.Name} ({value})"; } catch { // Ignore. } } } return result.ToImmutable(); } catch { return ImmutableDictionary.Empty; } } /// /// Logs the observed performance of an call. /// /// The stage. /// The period of time which elapsed before completed once it was initiated. protected virtual void PerfMeasureOnStart(int stage, TimeSpan elapsed) { LogLifecycleStageStarted(Logger, GetStageName(stage), elapsed); } /// public virtual async Task OnStart(CancellationToken cancellationToken = default) { if (this._highStage.HasValue) throw new InvalidOperationException("Lifecycle has already been started."); try { foreach (IGrouping observerGroup in this.subscribers .GroupBy(orderedObserver => orderedObserver.Stage) .OrderBy(group => group.Key)) { if (cancellationToken.IsCancellationRequested) { throw new OrleansLifecycleCanceledException($"Lifecycle start canceled at stage '{GetStageName(observerGroup.Key)}' by request."); } var stage = observerGroup.Key; this._highStage = stage; var stopWatch = ValueStopwatch.StartNew(); await Task.WhenAll(observerGroup.Select(orderedObserver => CallOnStart(orderedObserver, cancellationToken))); stopWatch.Stop(); this.PerfMeasureOnStart(stage, stopWatch.Elapsed); this.OnStartStageCompleted(stage); } } catch (Exception ex) when (ex is not OrleansLifecycleCanceledException) { LogErrorLifecycleStartFailure(Logger, ex, _highStage is { } highStage ? GetStageName(highStage) : "Unknown"); throw; } static Task CallOnStart(OrderedObserver observer, CancellationToken cancellationToken) { try { return observer.Observer?.OnStart(cancellationToken) ?? Task.CompletedTask; } catch (Exception ex) { return Task.FromException(ex); } } } /// /// Signifies that completed. /// /// The stage which completed. protected virtual void OnStartStageCompleted(int stage) { } /// /// Logs the observed performance of an call. /// /// The stage. /// The period of time which elapsed before completed once it was initiated. protected virtual void PerfMeasureOnStop(int stage, TimeSpan elapsed) { LogLifecycleStageStopped(Logger, GetStageName(stage), elapsed); } /// public virtual async Task OnStop(CancellationToken cancellationToken = default) { // if not started, do nothing if (!this._highStage.HasValue) return; var loggedCancellation = false; foreach (IGrouping observerGroup in this.subscribers // include up to highest started stage .Where(orderedObserver => orderedObserver.Stage <= _highStage && orderedObserver.Observer != null) .GroupBy(orderedObserver => orderedObserver.Stage) .OrderByDescending(group => group.Key)) { if (cancellationToken.IsCancellationRequested && !loggedCancellation) { LogWarningLifecycleStopCanceled(Logger, GetStageName(observerGroup.Key)); loggedCancellation = true; } var stage = observerGroup.Key; this._highStage = stage; try { var stopwatch = ValueStopwatch.StartNew(); await Task.WhenAll(observerGroup.Select(orderedObserver => orderedObserver?.Observer is not null ? CallObserverStopAsync(orderedObserver.Observer, cancellationToken) : Task.CompletedTask)); stopwatch.Stop(); this.PerfMeasureOnStop(stage, stopwatch.Elapsed); } catch (Exception ex) { LogWarningLifecycleStopFailure(Logger, ex, _highStage is { } highStage ? GetStageName(highStage) : "Unknown"); } this.OnStopStageCompleted(stage); } } protected virtual Task CallObserverStopAsync(ILifecycleObserver observer, CancellationToken cancellationToken) { try { return observer.OnStop(cancellationToken) ?? Task.CompletedTask; } catch (Exception ex) { return Task.FromException(ex); } } /// /// Signifies that completed. /// /// The stage which completed. protected virtual void OnStopStageCompleted(int stage) { } public virtual IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer) { if (observer == null) throw new ArgumentNullException(nameof(observer)); if (this._highStage.HasValue) throw new InvalidOperationException("Lifecycle has already been started."); var orderedObserver = new OrderedObserver(stage, observer); this.subscribers.Add(orderedObserver); return orderedObserver; } /// /// Represents a 's participation in a given lifecycle stage. /// private class OrderedObserver : IDisposable { /// /// Gets the observer. /// public ILifecycleObserver Observer { get; private set; } /// /// Gets the stage which the observer is participating in. /// public int Stage { get; } /// /// Initializes a new instance of the class. /// /// The stage which the observer is participating in. /// The participating observer. public OrderedObserver(int stage, ILifecycleObserver observer) { this.Stage = stage; this.Observer = observer; } /// public void Dispose() => Observer = null; } [LoggerMessage( EventId = (int)ErrorCode.LifecycleStartFailure, Level = LogLevel.Error, Message = "Lifecycle start canceled due to errors at stage '{Stage}'." )] private static partial void LogErrorLifecycleStartFailure(ILogger logger, Exception ex, string stage); [LoggerMessage( EventId = (int)ErrorCode.LifecycleStopFailure, Level = LogLevel.Warning, Message = "Stopping lifecycle encountered an error at stage '{Stage}'. Continuing to stop." )] private static partial void LogWarningLifecycleStopFailure(ILogger logger, Exception ex, string stage); [LoggerMessage( Level = LogLevel.Warning, Message = "Lifecycle stop operations canceled at stage '{Stage}' by request." )] private static partial void LogWarningLifecycleStopCanceled(ILogger logger, string stage); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Trace, Message = "Starting lifecycle stage '{Stage}' took '{Elapsed}'." )] private static partial void LogLifecycleStageStarted(ILogger logger, string stage, TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Trace, Message = "Stopping lifecycle stage '{Stage}' took '{Elapsed}'." )] private static partial void LogLifecycleStageStopped(ILogger logger, string stage, TimeSpan elapsed); } } ================================================ FILE: src/Orleans.Core/Lifecycle/MigrationContext.cs ================================================ #nullable enable using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Runtime; [GenerateSerializer, Immutable, Alias("MigrationCtx")] internal sealed class MigrationContext : IDehydrationContext, IRehydrationContext, IDisposable, IEnumerable, IBufferWriter { #if NET9_0_OR_GREATER [NonSerialized] private readonly Lock _lock = new(); #else [NonSerialized] private readonly object _lock = new(); #endif [NonSerialized] internal readonly SerializerSessionPool _sessionPool; [GeneratedActivatorConstructor] public MigrationContext(SerializerSessionPool sessionPool) { _sessionPool = sessionPool; } [Id(0), Immutable] private Dictionary _indices = new(StringComparer.Ordinal); [Id(1), Immutable] private PooledBuffer _buffer = new(); public void AddBytes(string key, ReadOnlySpan value) { lock (_lock) { _indices.Add(key, (_buffer.Length, value.Length)); _buffer.Write(value); } } public void AddBytes(string key, Action> valueWriter, T value) { lock (_lock) { var startOffset = _buffer.Length; valueWriter(value, this); var endOffset = _buffer.Length; _indices.Add(key, (startOffset, endOffset - startOffset)); } } public bool TryAddValue(string key, T? value) { if (_sessionPool.CodecProvider.TryGetCodec() is { } codec) { lock (_lock) { ref var indexValue = ref CollectionsMarshal.GetValueRefOrAddDefault(_indices, key, out var exists); if (!exists) { var startOffset = _buffer.Length; using var session = _sessionPool.GetSession(); var writer = Writer.Create(this, session); codec.WriteField(ref writer, 0, typeof(T), value!); writer.Commit(); var endOffset = _buffer.Length; indexValue = (Offset: startOffset, Length: endOffset - startOffset); return true; } } } return false; } public IEnumerable Keys => this; public void Reset() { lock (_lock) { _indices = []; _buffer.Reset(); _buffer = default; } } public void Dispose() => Reset(); public bool TryGetBytes(string key, out ReadOnlySequence value) { lock (_lock) { if (_indices.TryGetValue(key, out var record)) { value = _buffer.AsReadOnlySequence().Slice(record.Offset, record.Length); return true; } } value = default; return false; } public bool TryGetValue(string key, [NotNullWhen(true)] out T? value) { lock (_lock) { if (_indices.TryGetValue(key, out var record) && _sessionPool.CodecProvider.TryGetCodec() is { } codec) { using var session = _sessionPool.GetSession(); var source = _buffer.Slice(record.Offset, record.Length); var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); value = codec.ReadValue(ref reader, field); return value is not null; } } value = default; return false; } // Implemented on this class to prevent the need to repeatedly box & unbox _buffer. void IBufferWriter.Advance(int count) => _buffer.Advance(count); Memory IBufferWriter.GetMemory(int sizeHint) => _buffer.GetMemory(sizeHint); Span IBufferWriter.GetSpan(int sizeHint) => _buffer.GetSpan(sizeHint); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); private sealed class Enumerator(MigrationContext context) : IEnumerator, IEnumerator { private Dictionary.KeyCollection.Enumerator _value = context._indices.Keys.GetEnumerator(); public string Current => _value.Current; object IEnumerator.Current => Current; public void Dispose() => _value.Dispose(); public bool MoveNext() => _value.MoveNext(); public void Reset() { var boxed = (IEnumerator)_value; boxed.Reset(); _value = (Dictionary.KeyCollection.Enumerator)boxed; } } } ================================================ FILE: src/Orleans.Core/Lifecycle/ServiceLifecycle.cs ================================================ using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans; #nullable enable /// /// Allows consumers to observe and participate in the client/silo's lifecycle. /// public interface IServiceLifecycle { /// /// Triggered when the client/silo has fully started and is ready to accept traffic. /// IServiceLifecycleStage Started { get; } /// /// Triggered when the client/silo is beginning the shutdown process. /// IServiceLifecycleStage Stopping { get; } /// /// Triggered when the client/silo has completed its shutdown process. /// IServiceLifecycleStage Stopped { get; } } internal sealed class ServiceLifecycle(ILogger> logger) : IServiceLifecycle, ILifecycleParticipant where TLifecycleObservable : ILifecycleObservable { private readonly ServiceLifecycleNotificationStage _started = new(logger, "Started"); private readonly ServiceLifecycleNotificationStage _stopping = new(logger, "Stopping"); private readonly ServiceLifecycleNotificationStage _stopped = new(logger, "Stopped"); public IServiceLifecycleStage Started => _started; public IServiceLifecycleStage Stopping => _stopping; public IServiceLifecycleStage Stopped => _stopped; public void Participate(TLifecycleObservable lifecycle) { lifecycle.Subscribe( observerName: nameof(Started), stage: ServiceLifecycleStage.Active, onStart: _started.NotifyCompleted, onStop: _ => Task.CompletedTask); lifecycle.Subscribe( observerName: nameof(Stopping), stage: ServiceLifecycleStage.Active, onStart: _ => Task.CompletedTask, onStop: _stopping.NotifyCompleted); lifecycle.Subscribe( observerName: nameof(Stopped), stage: ServiceLifecycleStage.RuntimeInitialize - 1, onStart: _ => Task.CompletedTask, onStop: _stopped.NotifyCompleted); } } ================================================ FILE: src/Orleans.Core/Lifecycle/ServiceLifecycleNotificationStage.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans; #nullable enable /// /// Represents a specific stage in the client / silo lifecycle. /// public interface IServiceLifecycleStage { /// /// Gets a cancellation token that is triggered when this stage completes. /// /// Avoid registering callbacks in this token, prefer /// instead. CancellationToken Token { get; } /// /// Waits for this lifecycle stage to complete. /// /// /// A token used to cancel the wait. This does not cancel the lifecycle stage itself! /// Task WaitAsync(CancellationToken cancellationToken = default); /// /// Registers a callback to be executed during this lifecycle stage. /// /// /// The asynchronous operation to perform. /// Never call inside a callback, as it will result in a deadlock! /// /// /// If true, the client / silo will shut down if there is a failure; /// otherwise an error will be logged and the client / silo will continue to the next stage. /// /// An optional state to pass. /// /// Disposing the returned value removes the callback from the lifecycle stage. /// This is useful for components that have a shorter lifespan than the client / silo to prevent holding onto the reference, /// and ensure that cleanup logic is not executed for components that are no longer active. /// IDisposable Register(Func callback, object? state = null, bool terminateOnError = true); } internal sealed partial class ServiceLifecycleNotificationStage(ILogger logger, string name) : IServiceLifecycleStage { // We use this so that late registrations can still be executed, otherwise // we'd need to rely on the TCS which means we'd need to set it *before* the callbacks // have been executed, ideally we should fire the TCS only after non-late registered callbacks have completed. private bool _isNotifyingOrHasCompleted; private readonly object _lock = new(); private readonly List _participants = []; private readonly CancellationTokenSource _cts = new(); private readonly TaskCompletionSource _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); public CancellationToken Token => _cts.Token; public Task WaitAsync(CancellationToken cancellationToken) => _tcs.Task.WaitAsync(cancellationToken); public IDisposable Register(Func callback, object? state, bool terminateOnError) { ArgumentNullException.ThrowIfNull(callback); var participant = new StageParticipant(this, callback, state, terminateOnError); lock (_lock) { if (_isNotifyingOrHasCompleted) { LogStageAlreadyCompleted(logger, name); _ = Task.Run(() => ExecuteLateCallback(participant)); return participant; } _participants.Add(participant); } return participant; async Task ExecuteLateCallback(StageParticipant participant) { try { // The original token passed to NotifyCompleted (typically related to the silo startup/shutdown) must be "gone" by now. // Since the stage has already completed, there is no impending timeout for this late registration, so we pass CancellationToken.None. // For late participants we do not check for termination! await participant.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { LogLateCallbackError(logger, ex, name); } } } public async Task NotifyCompleted(CancellationToken cancellationToken) { List? snapshot; lock (_lock) { if (_isNotifyingOrHasCompleted) { snapshot = null; } else { _isNotifyingOrHasCompleted = true; snapshot = [.. _participants]; } } if (snapshot is null) { await _tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(false); return; } var tasks = new List(snapshot.Count + 1) { CancelTokenAsync() }; foreach (var participant in snapshot) { tasks.Add(ExecuteParticipantAsync(participant, cancellationToken)); } var allTasks = Task.WhenAll(tasks); try { await allTasks.ConfigureAwait(false); _tcs.SetResult(); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { _tcs.TrySetCanceled(cancellationToken); } catch (Exception ex) { // Note that awaiting WhenAll returns only the first exception, and we want to show all, if there are multiple. if (allTasks.Exception is { } aggEx) { var flattened = aggEx.Flatten(); if (flattened.InnerExceptions.Count == 1) { // For cleaner reporting in case one callback throws. _tcs.SetException(flattened.InnerExceptions[0]); } else { // Otherwise we let the user see all failures. _tcs.SetException(flattened); } } else { // Unlikely but hey! _tcs.SetException(ex); } // We throw here regardless, because it's the callback participant who controls whether to TerminateOnError or not. throw; } } private async Task CancelTokenAsync() { try { await _cts.CancelAsync().ConfigureAwait(false); } catch (Exception ex) { // Should not happen if callers respect the contract to register // callbacks with the proper method, but it can happen! LogCancellationCallbackError(logger, ex, name); } } private async Task ExecuteParticipantAsync(StageParticipant participant, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return; } try { await participant.ExecuteAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { // If the upstream token triggered this, we rethrow so WhenAll knows we stopped due to cancellation. throw; } catch (Exception ex) { LogCallbackError(logger, ex, name); if (participant.TerminateOnError) { // This will cause WhenAll to fault, eventually triggering _tcs.SetException above. // NotifyCompleted relies on us to throw in case TerminateOnError is set to true. throw; } } } private void Unregister(StageParticipant participant) { lock (_lock) { _participants.Remove(participant); } } private record StageParticipant(ServiceLifecycleNotificationStage Stage, Func Callback, object? State, bool TerminateOnError) : IDisposable { public Task ExecuteAsync(CancellationToken cancellationToken) => Callback(State, cancellationToken); void IDisposable.Dispose() => Stage.Unregister(this); } [LoggerMessage(Level = LogLevel.Information, Message = "Lifecycle stage = '{StageName}' has already completed. Executing callback immediately.")] public static partial void LogStageAlreadyCompleted(ILogger logger, string stageName); [LoggerMessage(Level = LogLevel.Error, Message = "Error executing late-registered callback for lifecycle stage = '{StageName}'")] public static partial void LogLateCallbackError(ILogger logger, Exception exception, string stageName); [LoggerMessage(Level = LogLevel.Information, Message = "Lifecycle stage = '{StageName}' has been canceled.")] public static partial void LogStageCanceled(ILogger logger, string stageName); [LoggerMessage(Level = LogLevel.Error, Message = "Error executing callback for lifecycle stage = '{StageName}'")] public static partial void LogCallbackError(ILogger logger, Exception exception, string stageName); [LoggerMessage(Level = LogLevel.Error, Message = "An exception occurred inside a CancellationToken callback for lifecycle stage = '{StageName}'")] public static partial void LogCancellationCallbackError(ILogger logger, Exception exception, string stageName); } ================================================ FILE: src/Orleans.Core/Lifecycle/ServiceLifecycleStage.cs ================================================ namespace Orleans { /// /// Lifecycle stages of an Orleans client or silo. /// public static class ServiceLifecycleStage { /// /// First valid stage in service's lifecycle /// public const int First = int.MinValue; /// /// Initialize runtime /// public const int RuntimeInitialize = 2000; /// /// Start runtime services /// public const int RuntimeServices = 4000; /// /// Initialize runtime storage /// public const int RuntimeStorageServices = 6000; /// /// Start runtime services /// public const int RuntimeGrainServices = 8000; /// /// After runtime services have started. /// public const int AfterRuntimeGrainServices = 8100; /// /// Start application layer services /// public const int ApplicationServices = 10000; /// /// Service will be active after this step. /// It should only be used by the membership oracle /// and the gateway, no other component should run /// at this stage /// public const int BecomeActive = Active-1; /// /// Service is active. /// public const int Active = 20000; /// /// Last valid stage in service's lifecycle /// public const int Last = int.MaxValue; } } ================================================ FILE: src/Orleans.Core/Manifest/ClientClusterManifestProvider.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Messaging; using Orleans.Metadata; using Orleans.Runtime.Utilities; namespace Orleans.Runtime { /// /// implementation for external clients. /// internal partial class ClientClusterManifestProvider : IClusterManifestProvider, IAsyncDisposable, IDisposable { private readonly TaskCompletionSource _initialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly ILogger _logger; private readonly TypeManagementOptions _typeManagementOptions; private readonly IServiceProvider _services; private readonly LocalClientDetails _localClientDetails; private readonly GatewayManager _gatewayManager; private readonly AsyncEnumerable _updates; private readonly CancellationTokenSource _shutdownCts = new CancellationTokenSource(); private ClusterManifest _current; private Task? _runTask; public ClientClusterManifestProvider( IServiceProvider services, LocalClientDetails localClientDetails, GatewayManager gatewayManager, ILogger logger, ClientManifestProvider clientManifestProvider, IOptions typeManagementOptions) { _logger = logger; _typeManagementOptions = typeManagementOptions.Value; _services = services; _localClientDetails = localClientDetails; _gatewayManager = gatewayManager; LocalGrainManifest = clientManifestProvider.ClientManifest; // Create a fake manifest for the very first generation, which only includes the local client's manifest. var builder = ImmutableDictionary.CreateBuilder(); builder.Add(_localClientDetails.ClientAddress, LocalGrainManifest); _current = new ClusterManifest(MajorMinorVersion.MinValue, builder.ToImmutable()); _updates = new AsyncEnumerable( initialValue: _current, updateValidator: (previous, proposed) => proposed.Version > previous.Version, onPublished: update => Interlocked.Exchange(ref _current, update)); } /// public ClusterManifest Current => _current; /// public IAsyncEnumerable Updates => _updates; /// public GrainManifest LocalGrainManifest { get; } /// /// Starts this service. /// /// A which completes once the service has started. public Task StartAsync() { _runTask = Task.Run(RunAsync); return _initialized.Task; } public async Task StopAsync(CancellationToken cancellationToken) { try { _shutdownCts.Cancel(); if (_runTask is { } task) { await task.WaitAsync(cancellationToken); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { LogGracefulShutdownCanceled(_logger); } catch (Exception exception) { LogStoppingClusterManifestProvider(_logger, exception); } } private async Task RunAsync() { try { var grainFactory = _services.GetRequiredService(); SiloAddress? gateway = null; IClusterManifestSystemTarget? provider = null; var minorVersion = 0; var gatewayVersion = MajorMinorVersion.MinValue; while (!_shutdownCts.IsCancellationRequested) { // Select a new gateway if the current one is not available. // This could be caused by a temporary issue or a permanent gateway failure. if (gateway is null || !_gatewayManager.IsGatewayAvailable(gateway)) { gateway = _gatewayManager.GetLiveGateway(); if (gateway is null) { await Task.Delay(StandardExtensions.Min(_typeManagementOptions.TypeMapRefreshInterval, TimeSpan.FromMilliseconds(500)), _shutdownCts.Token); continue; } provider = grainFactory.GetGrain(SystemTargetGrainId.Create(Constants.ManifestProviderType, gateway).GrainId); // Accept any cluster manifest version from the new gateway. // Since the minor version of the manifest is specific to each gateway, we reset it to the lowest possible value. // This means that it is possible to receive the an older or equivalent cluster manifest when the gateway changes. // That hiccup is addressed by resetting the expected manifest version and merging incomplete manifests until a complete // manifest is received. gatewayVersion = MajorMinorVersion.MinValue; } Debug.Assert(provider is not null); try { var updateResult = await GetClusterManifestUpdate(provider, gatewayVersion).WaitAsync(_shutdownCts.Token); if (updateResult is null) { // There was no newer cluster manifest, so wait for the next refresh interval and try again. await Task.Delay(_typeManagementOptions.TypeMapRefreshInterval, _shutdownCts.Token); continue; } gatewayVersion = updateResult.Version; // If the manifest does not contain all active servers, merge with the existing manifest until it does. // This prevents reversed progress at the expense of including potentially defunct silos. ImmutableDictionary siloManifests; if (!updateResult.IncludesAllActiveServers) { // Merge manifests until the manifest contains all active servers. var mergedSilos = _current.Silos.ToBuilder(); mergedSilos.Add(_localClientDetails.ClientAddress, LocalGrainManifest); foreach (var kvp in updateResult.SiloManifests) { mergedSilos[kvp.Key] = kvp.Value; } siloManifests = mergedSilos.ToImmutable(); } else { siloManifests = updateResult.SiloManifests.Add(_localClientDetails.ClientAddress, LocalGrainManifest); } var updatedManifest = new ClusterManifest(new MajorMinorVersion(gatewayVersion.Major, ++minorVersion), siloManifests); if (!_updates.TryPublish(updatedManifest)) { await Task.Delay(StandardExtensions.Min(_typeManagementOptions.TypeMapRefreshInterval, TimeSpan.FromMilliseconds(500)), _shutdownCts.Token); continue; } _initialized.TrySetResult(true); LogRefreshedClusterManifest(_logger); await Task.Delay(_typeManagementOptions.TypeMapRefreshInterval, _shutdownCts.Token); } catch (OperationCanceledException) when (_shutdownCts.IsCancellationRequested) { // Ignore during shutdown. } catch (Exception exception) { LogErrorTryingToGetClusterManifest(_logger, exception, gateway); await Task.Delay(StandardExtensions.Min(_typeManagementOptions.TypeMapRefreshInterval, TimeSpan.FromSeconds(5)), _shutdownCts.Token).SuppressThrowing(); // Reset the gateway so that another will be selected on the next iteration. gateway = null; } } } finally { _initialized.TrySetResult(false); LogStoppedRefreshingClusterManifest(_logger); } } private async Task GetClusterManifestUpdate(IClusterManifestSystemTarget provider, MajorMinorVersion previousVersion) { try { // First, attempt to call the new API, which provides more information. // This returns null if there is no newer cluster manifest. return await provider.GetClusterManifestUpdate(previousVersion); } catch (Exception exception) { LogFailedToFetchClusterManifestUpdate(_logger, exception, provider); // If the provider does not support the new API, fall back to the old one. var manifest = await provider.GetClusterManifest(); var result = new ClusterManifestUpdate(manifest.Version, manifest.Silos, includesAllActiveServers: true); return result; } } /// public async ValueTask DisposeAsync() { if (_shutdownCts.IsCancellationRequested) { return; } _shutdownCts.Cancel(); if (_runTask is Task task) { await task.SuppressThrowing(); } } /// public void Dispose() { _shutdownCts.Cancel(); } [LoggerMessage( Level = LogLevel.Information, Message = "Graceful shutdown of cluster manifest provider was canceled." )] private static partial void LogGracefulShutdownCanceled(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error stopping cluster manifest provider." )] private static partial void LogStoppingClusterManifestProvider(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Refreshed cluster manifest." )] private static partial void LogRefreshedClusterManifest(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, Message = "Error trying to get cluster manifest from gateway '{Gateway}'." )] private static partial void LogErrorTryingToGetClusterManifest(ILogger logger, Exception exception, SiloAddress gateway); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopped refreshing cluster manifest." )] private static partial void LogStoppedRefreshingClusterManifest(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to fetch cluster manifest update from '{Provider}'." )] private static partial void LogFailedToFetchClusterManifestUpdate(ILogger logger, Exception exception, IClusterManifestSystemTarget provider); } } ================================================ FILE: src/Orleans.Core/Manifest/ClientManifestProvider.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Metadata; namespace Orleans.Runtime { /// /// Creates a manifest of the locally available grain interface types. /// internal class ClientManifestProvider { public ClientManifestProvider( IEnumerable grainInterfacePropertiesProviders, IOptions grainTypeOptions, GrainInterfaceTypeResolver interfaceTypeResolver) { var interfaces = CreateInterfaceManifest(grainInterfacePropertiesProviders, grainTypeOptions, interfaceTypeResolver); this.ClientManifest = new GrainManifest(ImmutableDictionary.Empty, interfaces); } /// /// Gets the client manifest. /// public GrainManifest ClientManifest { get; } private static ImmutableDictionary CreateInterfaceManifest( IEnumerable propertyProviders, IOptions grainTypeOptions, GrainInterfaceTypeResolver interfaceTypeResolver) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var grainInterface in grainTypeOptions.Value.Interfaces) { var interfaceId = interfaceTypeResolver.GetGrainInterfaceType(grainInterface); var properties = new Dictionary(); foreach (var provider in propertyProviders) { provider.Populate(grainInterface, interfaceId, properties); } var result = new GrainInterfaceProperties(properties.ToImmutableDictionary()); if (builder.TryGetValue(interfaceId, out var grainInterfaceProperty)) { throw new InvalidOperationException($"An entry with the key {interfaceId} is already present." + $"\nExisting: {grainInterfaceProperty.ToDetailedString()}\nTrying to add: {result.ToDetailedString()}" + "\nConsider using the [GrainInterfaceType(\"name\")] attribute to give these interfaces unique names."); } builder.Add(interfaceId, result); } return builder.ToImmutable(); } } } ================================================ FILE: src/Orleans.Core/Manifest/GrainBindings.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Describes the bindings for a given grain type. /// /// /// Bindings are a way to declaratively connect grains with other resources. /// public class GrainBindings { /// /// Initializes a new instance of the class. /// /// The grain type. /// The bindings for the specified grain type. public GrainBindings(GrainType grainType, ImmutableArray> bindings) { this.GrainType = grainType; this.Bindings = bindings; } /// /// Gets the grain type. /// public GrainType GrainType { get; } /// /// Gets the bindings for the specified grain type. /// public ImmutableArray> Bindings { get; } } /// /// Resolves bindings for grain types. /// public class GrainBindingsResolver { private const string BindingPrefix = WellKnownGrainTypeProperties.BindingPrefix + "."; private const char BindingIndexEnd = '.'; #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly ConcurrentDictionary _genericMapping = new ConcurrentDictionary(); private readonly IClusterManifestProvider _clusterManifestProvider; private Cache _cache; /// /// Initializes a new instance of the class. /// /// /// The cluster manifest provider. /// public GrainBindingsResolver(IClusterManifestProvider clusterManifestProvider) { _clusterManifestProvider = clusterManifestProvider; _cache = BuildCache(_clusterManifestProvider.Current); } /// /// Gets bindings for the provided grain type. /// /// /// The grain type. /// /// The grain bindings. public GrainBindings GetBindings(GrainType grainType) { GrainType lookupType; if (GenericGrainType.TryParse(grainType, out var generic)) { if (!_genericMapping.TryGetValue(generic, out lookupType)) { lookupType = _genericMapping[generic] = generic.GetUnconstructedGrainType().GrainType; } } else { lookupType = grainType; } var cache = GetCache(); if (cache.Map.TryGetValue(lookupType, out var result)) { return result; } return new GrainBindings(grainType, ImmutableArray>.Empty); } /// /// Gets all bindings. /// /// The collection of all grain bindings. public (MajorMinorVersion Version, ImmutableDictionary Bindings) GetAllBindings() { var cache = GetCache(); return (cache.Version, cache.Map); } private Cache GetCache() { var cache = _cache; var manifest = _clusterManifestProvider.Current; if (manifest.Version == cache.Version) { return cache; } lock (_lockObj) { cache = _cache; manifest = _clusterManifestProvider.Current; if (manifest.Version == cache.Version) { return cache; } return _cache = BuildCache(manifest); } } private static Cache BuildCache(ClusterManifest clusterManifest) { var result = new Dictionary(); var bindings = new Dictionary>(); foreach (var manifest in clusterManifest.AllGrainManifests) { foreach (var grainType in manifest.Grains) { var id = grainType.Key; if (result.ContainsKey(id)) continue; bindings.Clear(); foreach (var pair in grainType.Value.Properties) { if (TryExtractBindingProperty(pair, out var binding)) { if (!bindings.TryGetValue(binding.Index, out var properties)) { bindings[binding.Index] = properties = new Dictionary(); } properties.Add(binding.Key, binding.Value); } } var builder = ImmutableArray.CreateBuilder>(); foreach (var binding in bindings.Values) { builder.Add(ImmutableDictionary.CreateRange(binding)); } result.Add(id, new GrainBindings(id, builder.ToImmutable())); } } return new Cache(clusterManifest.Version, result.ToImmutableDictionary()); bool TryExtractBindingProperty(KeyValuePair property, out (string Index, string Key, string Value) result) { if (!property.Key.StartsWith(BindingPrefix, StringComparison.Ordinal) || property.Key.IndexOf(BindingIndexEnd, BindingPrefix.Length) is int indexEndIndex && indexEndIndex < 0) { result = default; return false; } var bindingIndex = property.Key[BindingPrefix.Length..indexEndIndex]; var bindingKey = property.Key[(indexEndIndex + 1)..]; if (string.IsNullOrWhiteSpace(bindingIndex) || string.IsNullOrWhiteSpace(bindingKey)) { result = default; return false; } result = (bindingIndex, bindingKey, property.Value); return true; } } private class Cache { public Cache(MajorMinorVersion version, ImmutableDictionary map) { this.Version = version; this.Map = map; } public MajorMinorVersion Version { get; } public ImmutableDictionary Map { get; } } } } ================================================ FILE: src/Orleans.Core/Manifest/GrainInterfaceTypeResolver.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Metadata { /// /// Associates a with a . /// public class GrainInterfaceTypeResolver { private readonly IGrainInterfaceTypeProvider[] _providers; private readonly TypeConverter _typeConverter; /// /// Initializes a new instance of the class. /// /// /// The collection of grain interface type providers. /// /// /// The type converter, used for generic parameter names. /// public GrainInterfaceTypeResolver( IEnumerable providers, TypeConverter typeConverter) { _providers = providers.ToArray(); _typeConverter = typeConverter; } /// /// Returns the for the provided interface. /// /// The grain interface. /// The for the provided interface. public GrainInterfaceType GetGrainInterfaceType(Type type) { if (!type.IsInterface) { throw new ArgumentException($"Argument {nameof(type)} must be an interface. Provided value, \"{type}\", is not an interface.", nameof(type)); } // Configured providers take precedence foreach (var provider in _providers) { if (provider.TryGetGrainInterfaceType(type, out var interfaceType)) { interfaceType = AddGenericParameters(interfaceType, type); return interfaceType; } } // Conventions are used as a fallback. return GetGrainInterfaceTypeByConvention(type); } /// /// Gets a grain interface type based upon the default conventions. /// /// The grain interface type. /// The grain interface type name. public GrainInterfaceType GetGrainInterfaceTypeByConvention(Type type) { var result = GrainInterfaceType.Create(_typeConverter.Format(type, input => input switch { AssemblyQualifiedTypeSpec asm => asm.Type, // drop outer assembly qualification _ => input })); result = AddGenericParameters(result, type); return result; } private GrainInterfaceType AddGenericParameters(GrainInterfaceType result, Type type) { if (GenericGrainInterfaceType.TryParse(result, out var genericGrainType) && type.IsConstructedGenericType && !type.ContainsGenericParameters && !genericGrainType.IsConstructed) { result = genericGrainType.Construct(_typeConverter, type.GetGenericArguments()).Value; } return result; } } } ================================================ FILE: src/Orleans.Core/Manifest/GrainPropertiesResolver.cs ================================================ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Responsible for resolving for values. /// public class GrainPropertiesResolver { private readonly IClusterManifestProvider _clusterManifestProvider; /// /// Initializes a new instance of the class. /// /// /// The cluster manifest provider. /// public GrainPropertiesResolver(IClusterManifestProvider clusterManifestProvider) { _clusterManifestProvider = clusterManifestProvider; } /// /// Gets the grain properties for the provided type. /// /// /// The grain type. /// /// /// The grain properties. /// public GrainProperties GetGrainProperties(GrainType grainType) { if (!TryGetGrainProperties(grainType, out var result)) { //ThrowNotFoundException(grainType); result = new GrainProperties(ImmutableDictionary.Empty); } return result; } /// /// Gets the grain properties for the provided type. /// /// /// The grain type. /// /// /// The grain properties. /// /// /// A value indicating whether grain properties could be found for the provided grain type. /// public bool TryGetGrainProperties(GrainType grainType, [NotNullWhen(true)] out GrainProperties properties) { var clusterManifest = _clusterManifestProvider.Current; if (clusterManifest is null) { properties = default; return false; } GrainType lookupKey; if (GenericGrainType.TryParse(grainType, out var generic)) { lookupKey = generic.GetUnconstructedGrainType().GrainType; } else { lookupKey = grainType; } foreach (var manifest in clusterManifest.AllGrainManifests) { if (manifest.Grains.TryGetValue(lookupKey, out properties)) { return true; } } properties = default; return false; } } } ================================================ FILE: src/Orleans.Core/Manifest/GrainTypeResolver.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Metadata { /// /// Associates a with a grain class. /// public class GrainTypeResolver { private const string GrainSuffix = "grain"; private readonly IGrainTypeProvider[] _providers; private readonly TypeConverter _typeConverter; /// /// Initializes a new instance of the class. /// /// /// The grain type name providers. /// /// /// The type converter, used to format generic parameters. /// public GrainTypeResolver( IEnumerable resolvers, TypeConverter argumentFormatter) { _providers = resolvers.ToArray(); _typeConverter = argumentFormatter; } /// /// Returns the grain type for the provided class. /// /// The grain class. /// The grain type for the provided class. public GrainType GetGrainType(Type type) { if (!type.IsClass || type.IsAbstract) { throw new ArgumentException($"Argument {nameof(type)} must be a non-abstract class. Provided value, \"{type}\", is not a class.", nameof(type)); } // Configured providers take precedence foreach (var provider in _providers) { if (provider.TryGetGrainType(type, out var grainType)) { grainType = AddGenericParameters(grainType, type); return grainType; } } // Conventions are used as a fallback return GetGrainTypeByConvention(type); } private GrainType GetGrainTypeByConvention(Type type) { var name = type.Name.ToLowerInvariant(); // Trim generic arity var index = name.IndexOf('`'); if (index > 0) { name = name[..index]; } // Trim "Grain" suffix index = name.LastIndexOf(GrainSuffix); if (index > 0 && name.Length - index == GrainSuffix.Length) { name = name[..index]; } // Append the generic arity, eg typeof(MyListGrain) would eventually become mylist`1 if (type.IsGenericType) { name = name + '`' + type.GetGenericArguments().Length; } var grainType = GrainType.Create(name); grainType = AddGenericParameters(grainType, type); return grainType; } private GrainType AddGenericParameters(GrainType grainType, Type type) { if (GenericGrainType.TryParse(grainType, out var genericGrainType) && type.IsConstructedGenericType && !type.ContainsGenericParameters && !genericGrainType.IsConstructed) { var typeArguments = type.GetGenericArguments(); grainType = genericGrainType.Construct(_typeConverter, typeArguments).GrainType; } return grainType; } } } ================================================ FILE: src/Orleans.Core/Manifest/GrainVersionManifest.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using Orleans.Metadata; namespace Orleans.Runtime.Versions { /// /// Functionality for querying the declared version of grain interfaces. /// internal class GrainVersionManifest { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly ConcurrentDictionary _genericInterfaceMapping = new ConcurrentDictionary(); private readonly ConcurrentDictionary _genericGrainTypeMapping = new ConcurrentDictionary(); private readonly IClusterManifestProvider _clusterManifestProvider; private readonly Dictionary _localVersions; private Cache _cache; /// /// Initializes a new instance of the class. /// /// The cluster manifest provider. public GrainVersionManifest(IClusterManifestProvider clusterManifestProvider) { _clusterManifestProvider = clusterManifestProvider; _cache = BuildCache(clusterManifestProvider.Current); _localVersions = BuildLocalVersionMap(clusterManifestProvider.LocalGrainManifest); } /// /// Gets the current cluster manifest version. /// public MajorMinorVersion LatestVersion => _clusterManifestProvider.Current.Version; /// /// Gets the local version for a specified grain interface type. /// /// The grain interface type name. /// The version of the specified grain interface. public ushort GetLocalVersion(GrainInterfaceType interfaceType) { if (_localVersions.TryGetValue(interfaceType, out var result)) { return result; } if (_genericInterfaceMapping.TryGetValue(interfaceType, out var genericInterfaceId)) { return GetLocalVersion(genericInterfaceId); } if (GenericGrainInterfaceType.TryParse(interfaceType, out var generic) && generic.IsConstructed) { var genericId = _genericInterfaceMapping[interfaceType] = generic.GetGenericGrainType().Value; return GetLocalVersion(genericId); } return 0; } /// /// Gets a collection of all known versions for a grain interface. /// /// The grain interface type name. /// All known versions for the specified grain interface. public (MajorMinorVersion Version, ushort[] Result) GetAvailableVersions(GrainInterfaceType interfaceType) { var cache = GetCache(); if (cache.AvailableVersions.TryGetValue(interfaceType, out var result)) { return (cache.Version, result); } if (_genericInterfaceMapping.TryGetValue(interfaceType, out var genericInterfaceId)) { return GetAvailableVersions(genericInterfaceId); } if (GenericGrainInterfaceType.TryParse(interfaceType, out var generic) && generic.IsConstructed) { var genericId = _genericInterfaceMapping[interfaceType] = generic.GetGenericGrainType().Value; return GetAvailableVersions(genericId); } // No versions available. return (cache.Version, Array.Empty()); } /// /// Gets the set of supported silos for a specified grain interface and version. /// /// The grain interface type name. /// The grain interface version. /// The set of silos which support the specified grain interface type and version. public (MajorMinorVersion Version, SiloAddress[] Result) GetSupportedSilos(GrainInterfaceType interfaceType, ushort version) { var cache = GetCache(); if (cache.SupportedSilosByInterface.TryGetValue((interfaceType, version), out var result)) { return (cache.Version, result); } if (_genericInterfaceMapping.TryGetValue(interfaceType, out var genericInterfaceId)) { return GetSupportedSilos(genericInterfaceId, version); } if (GenericGrainInterfaceType.TryParse(interfaceType, out var generic) && generic.IsConstructed) { var genericId = _genericInterfaceMapping[interfaceType] = generic.GetGenericGrainType().Value; return GetSupportedSilos(genericId, version); } // No supported silos for this version. return (cache.Version, Array.Empty()); } /// /// Gets the set of supported silos for the specified grain type. /// /// The grain type. /// The silos which support the specified grain type. public (MajorMinorVersion Version, SiloAddress[] Result) GetSupportedSilos(GrainType grainType) { var cache = GetCache(); if (cache.SupportedSilosByGrainType.TryGetValue(grainType, out var result)) { return (cache.Version, result); } if (_genericGrainTypeMapping.TryGetValue(grainType, out var genericGrainType)) { return GetSupportedSilos(genericGrainType); } if (GenericGrainType.TryParse(grainType, out var generic) && generic.IsConstructed) { var genericId = _genericGrainTypeMapping[grainType] = generic.GetUnconstructedGrainType().GrainType; return GetSupportedSilos(genericId); } // No supported silos for this type. return (cache.Version, Array.Empty()); } /// /// Gets the set of supported silos for the specified combination of grain type, interface type, and version. /// /// The grain type. /// The grain interface type name. /// The grain interface version. /// The set of silos which support the specified grain. public (MajorMinorVersion Version, Dictionary Result) GetSupportedSilos(GrainType grainType, GrainInterfaceType interfaceType, ushort[] versions) { var result = new Dictionary(); // Track the minimum version in case of inconsistent reads, since the caller can use that information to // ensure they refresh on the next call. MajorMinorVersion? minCacheVersion = null; foreach (var version in versions) { (var cacheVersion, var silosWithGrain) = this.GetSupportedSilos(grainType); if (!minCacheVersion.HasValue || cacheVersion > minCacheVersion.Value) { minCacheVersion = cacheVersion; } // We need to sort this so the list of silos returned will // be the same across all silos in the cluster SiloAddress[] silosWithCorrectVersion; (cacheVersion, silosWithCorrectVersion) = this.GetSupportedSilos(interfaceType, version); if (!minCacheVersion.HasValue || cacheVersion > minCacheVersion.Value) { minCacheVersion = cacheVersion; } result[version] = silosWithCorrectVersion .Intersect(silosWithGrain) .OrderBy(addr => addr) .ToArray(); } if (!minCacheVersion.HasValue) minCacheVersion = MajorMinorVersion.Zero; return (minCacheVersion.Value, result); } private Cache GetCache() { var cache = _cache; var manifest = _clusterManifestProvider.Current; if (manifest.Version == cache.Version) { return cache; } lock (_lockObj) { cache = _cache; manifest = _clusterManifestProvider.Current; if (manifest.Version == cache.Version) { return cache; } return _cache = BuildCache(manifest); } } private static Dictionary BuildLocalVersionMap(GrainManifest manifest) { var result = new Dictionary(); foreach (var grainInterface in manifest.Interfaces) { var id = grainInterface.Key; if (!grainInterface.Value.Properties.TryGetValue(WellKnownGrainInterfaceProperties.Version, out var versionString) || !ushort.TryParse(versionString, out var version)) { version = 0; } result[id] = version; } return result; } private static Cache BuildCache(ClusterManifest clusterManifest) { var available = new Dictionary>(); var supportedInterfaces = new Dictionary<(GrainInterfaceType, ushort), List>(); var supportedGrains = new Dictionary>(); foreach (var entry in clusterManifest.Silos) { var silo = entry.Key; // Since clients are not eligible for placement, we exclude them here. if (silo.IsClient) { continue; } var manifest = entry.Value; foreach (var grainInterface in manifest.Interfaces) { var id = grainInterface.Key; if (!grainInterface.Value.Properties.TryGetValue(WellKnownGrainInterfaceProperties.Version, out var versionString) || !ushort.TryParse(versionString, out var version)) { version = 0; } if (!available.TryGetValue(id, out var versions)) { available[id] = new List { version }; } else if (!versions.Contains(version)) { versions.Add(version); } if (!supportedInterfaces.TryGetValue((id, version), out var supportedSilos)) { supportedInterfaces[(id, version)] = new List { silo }; } else if (!supportedSilos.Contains(silo)) { supportedSilos.Add(silo); } } foreach (var grainType in manifest.Grains) { var id = grainType.Key; if (!supportedGrains.TryGetValue(id, out var supportedSilos)) { supportedGrains[id] = new List { silo }; } else if (!supportedSilos.Contains(silo)) { supportedSilos.Add(silo); } } } var resultAvailable = new Dictionary(); foreach (var entry in available) { entry.Value.Sort(); resultAvailable[entry.Key] = entry.Value.ToArray(); } var resultSupportedByInterface = new Dictionary<(GrainInterfaceType, ushort), SiloAddress[]>(); foreach (var entry in supportedInterfaces) { entry.Value.Sort(); resultSupportedByInterface[entry.Key] = entry.Value.ToArray(); } var resultSupportedSilosByGrainType = new Dictionary(); foreach (var entry in supportedGrains) { entry.Value.Sort(); resultSupportedSilosByGrainType[entry.Key] = entry.Value.ToArray(); } return new Cache(clusterManifest.Version, resultAvailable, resultSupportedByInterface, resultSupportedSilosByGrainType); } private class Cache { public Cache( MajorMinorVersion version, Dictionary availableVersions, Dictionary<(GrainInterfaceType, ushort), SiloAddress[]> supportedSilosByInterface, Dictionary supportedSilosByGrainType) { this.Version = version; this.AvailableVersions = availableVersions; this.SupportedSilosByGrainType = supportedSilosByGrainType; this.SupportedSilosByInterface = supportedSilosByInterface; } public MajorMinorVersion Version { get; } public Dictionary AvailableVersions { get; } public Dictionary<(GrainInterfaceType, ushort), SiloAddress[]> SupportedSilosByInterface { get; } = new Dictionary<(GrainInterfaceType, ushort), SiloAddress[]>(); public Dictionary SupportedSilosByGrainType { get; } = new Dictionary(); } } } ================================================ FILE: src/Orleans.Core/Manifest/IClusterManifestProvider.cs ================================================ using System.Collections.Generic; using Orleans.Metadata; namespace Orleans.Runtime { /// /// Provides access to the cluster manifest. /// /// public interface IClusterManifestProvider { /// /// Gets the current cluster manifest. /// ClusterManifest Current { get; } /// /// Gets the stream of cluster manifest updates. /// IAsyncEnumerable Updates { get; } /// /// Gets the local grain manifest. /// GrainManifest LocalGrainManifest { get; } } } ================================================ FILE: src/Orleans.Core/Manifest/IClusterManifestSystemTarget.cs ================================================ #nullable enable using System.Collections.Immutable; using System.Threading.Tasks; using Orleans.Metadata; namespace Orleans.Runtime { /// /// Internal interface for exposing the cluster manifest. /// internal interface IClusterManifestSystemTarget : ISystemTarget { /// /// Gets the current cluster manifest. /// /// The current cluster manifest. ValueTask GetClusterManifest(); /// /// Gets an updated cluster manifest if newer than the provided . /// /// The current cluster manifest, or if it is not newer than the provided version. ValueTask GetClusterManifestUpdate(MajorMinorVersion previousVersion); } /// /// Represents an update to the cluster manifest. /// [GenerateSerializer, Immutable] public class ClusterManifestUpdate { public ClusterManifestUpdate( MajorMinorVersion manifestVersion, ImmutableDictionary siloManifests, bool includesAllActiveServers) { Version = manifestVersion; SiloManifests = siloManifests; IncludesAllActiveServers = includesAllActiveServers; } /// /// Gets the version of this instance. /// [Id(0)] public MajorMinorVersion Version { get; } /// /// Gets the manifests for each silo in the cluster. /// [Id(1)] public ImmutableDictionary SiloManifests { get; } /// /// Gets a value indicating whether this update includes all active servers. /// [Id(2)] public bool IncludesAllActiveServers { get; } } } ================================================ FILE: src/Orleans.Core/Manifest/ImplementedInterfaceProvider.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Populates grain interface properties with the grain interfaces implemented by a grain class. /// internal sealed class ImplementedInterfaceProvider : IGrainPropertiesProvider { private readonly GrainInterfaceTypeResolver interfaceTypeResolver; private readonly string[] _cachedKeys = new string[16]; /// /// Initializes a new instance of the class. /// /// The interface type resolver. public ImplementedInterfaceProvider(GrainInterfaceTypeResolver interfaceTypeResolver) { this.interfaceTypeResolver = interfaceTypeResolver; } /// public void Populate(Type grainClass, GrainType grainType, Dictionary properties) { var counter = 0; foreach (var @interface in grainClass.GetInterfaces()) { if (!IsGrainInterface(@interface)) continue; var type = @interface switch { { IsGenericType: true } when grainClass is { IsGenericType: true } => @interface.GetGenericTypeDefinition(), _ => @interface }; var interfaceId = this.interfaceTypeResolver.GetGrainInterfaceType(type); var key = (uint)counter < (uint)_cachedKeys.Length ? (_cachedKeys[counter] ??= GetKey(counter)) : GetKey(counter); properties[key] = interfaceId.ToString(); ++counter; } } private static string GetKey(int counter) => $"{WellKnownGrainTypeProperties.ImplementedInterfacePrefix}{counter}"; /// /// Gets a value indicating whether the specified type is a grain interface type. /// /// The type to inspect. /// A value indicating whether the specified type is a grain interface type. public static bool IsGrainInterface(Type type) { if (type.IsClass) return false; if (type == typeof(IGrainObserver) || type == typeof(IAddressable) || type == typeof(IGrainExtension)) return false; if (type == typeof(IGrain) || type == typeof(IGrainWithGuidKey) || type == typeof(IGrainWithIntegerKey) || type == typeof(IGrainWithGuidCompoundKey) || type == typeof(IGrainWithIntegerCompoundKey)) return false; if (type == typeof(ISystemTarget)) return false; return typeof(IAddressable).IsAssignableFrom(type); } } } ================================================ FILE: src/Orleans.Core/Manifest/TypeNameGrainPropertiesProvider.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Metadata { /// /// Populates type names on grain properties and grain interface properties. /// internal sealed class TypeNameGrainPropertiesProvider : IGrainPropertiesProvider, IGrainInterfacePropertiesProvider { /// public void Populate(Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.TypeName] = grainClass.Name; properties[WellKnownGrainTypeProperties.FullTypeName] = grainClass.FullName; properties["diag.type"] = RuntimeTypeNameFormatter.Format(grainClass); properties["diag.asm"] = CachedTypeResolver.GetName(grainClass.Assembly); } /// public void Populate(Type interfaceType, GrainInterfaceType interfaceId, Dictionary properties) { properties[WellKnownGrainInterfaceProperties.TypeName] = interfaceType.Name; properties["diag.type"] = RuntimeTypeNameFormatter.Format(interfaceType); properties["diag.asm"] = CachedTypeResolver.GetName(interfaceType.Assembly); } } } ================================================ FILE: src/Orleans.Core/Messaging/CachingIdSpanCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Caching; namespace Orleans.Runtime.Messaging { /// /// A serializer for which caches values and avoids re-encoding and unnecessary allocations. /// internal sealed class CachingIdSpanCodec { private static readonly ConcurrentLruCache SharedCache = new(capacity: 128_000); // Purge entries which have not been accessed in over 2 minutes. private const long PurgeAfterMilliseconds = 2 * 60 * 1000; // Scan for entries which are expired every minute private const long GarbageCollectionIntervalMilliseconds = 60 * 1000; private readonly Dictionary _cache = new(); private long _lastGarbageCollectionTimestamp; public CachingIdSpanCodec() { _lastGarbageCollectionTimestamp = Environment.TickCount64; } public IdSpan ReadRaw(ref Reader reader) { var currentTimestamp = Environment.TickCount64; var length = reader.ReadVarUInt32(); if (length == 0) return default; var hashCode = reader.ReadInt32(); IdSpan result = default; byte[] payloadArray = default; if (!reader.TryReadBytes((int)length, out var payloadSpan)) { payloadSpan = payloadArray = reader.ReadBytes(length); } ref var cacheEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, hashCode, out var exists); if (exists && payloadSpan.SequenceEqual(cacheEntry.Value)) { result = IdSpan.UnsafeCreate(cacheEntry.Value, hashCode); } else { result = IdSpan.UnsafeCreate(payloadArray ?? payloadSpan.ToArray(), hashCode); // Before adding this value to the private cache and returning it, intern it via the shared cache to hopefully reduce duplicates. result = SharedCache.GetOrAdd(result, static (key, _) => key, (object)null); // Update the cache. If there is a hash collision, the last entry wins. cacheEntry.Value = IdSpan.UnsafeGetArray(result); } cacheEntry.LastSeen = currentTimestamp; // Perform periodic maintenance to prevent unbounded memory leaks. if (currentTimestamp - _lastGarbageCollectionTimestamp > GarbageCollectionIntervalMilliseconds) { PurgeStaleEntries(); _lastGarbageCollectionTimestamp = currentTimestamp; } return result; } [MethodImpl(MethodImplOptions.NoInlining)] private void PurgeStaleEntries() { var currentTimestamp = Environment.TickCount64; foreach (var entry in _cache) { if (currentTimestamp - entry.Value.LastSeen > PurgeAfterMilliseconds) { _cache.Remove(entry.Key); } } } public void WriteRaw(ref Writer writer, IdSpan value) where TBufferWriter : IBufferWriter { IdSpanCodec.WriteRaw(ref writer, value); SharedCache.GetOrAdd(value, static (key, _) => key, (object)null); } } } ================================================ FILE: src/Orleans.Core/Messaging/CachingSiloAddressCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Caching; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; namespace Orleans.Runtime.Messaging { /// /// A serializer for which caches values and avoids re-encoding and unnecessary allocations. /// internal sealed class CachingSiloAddressCodec { internal static ConcurrentLruCache SharedCache { get; } = new(capacity: 1024); // Purge entries which have not been accessed in over 2 minutes. private const long PurgeAfterMilliseconds = 2 * 60 * 1000; // Scan for entries which are expired every minute private const long GarbageCollectionIntervalMilliseconds = 60 * 1000; private readonly Dictionary _cache = new(); private long _lastGarbageCollectionTimestamp; public CachingSiloAddressCodec() { _lastGarbageCollectionTimestamp = Environment.TickCount64; } public SiloAddress ReadRaw(ref Reader reader) { var currentTimestamp = Environment.TickCount64; SiloAddress result = null; byte[] payloadArray = default; var length = (int)reader.ReadVarUInt32(); if (length == 0) { return null; } if (!reader.TryReadBytes(length, out var payloadSpan)) { payloadSpan = payloadArray = reader.ReadBytes((uint)length); } var innerReader = Reader.Create(payloadSpan, null); var hashCode = innerReader.ReadInt32(); ref var cacheEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, hashCode, out var exists); if (exists && payloadSpan.SequenceEqual(cacheEntry.Encoded)) { result = cacheEntry.Value; cacheEntry.LastSeen = currentTimestamp; } else { result = ReadSiloAddressInner(ref innerReader); result.InternalSetConsistentHashCode(hashCode); // Before adding this value to the private cache and returning it, intern it via the shared cache to hopefully reduce duplicates. payloadArray ??= payloadSpan.ToArray(); (result, payloadArray) = SharedCache.GetOrAdd(result, static (key, encoded) => (key, encoded), payloadArray); // If there is a hash collision, then the last seen entry will always win. cacheEntry.Encoded = payloadArray; cacheEntry.Value = result; cacheEntry.LastSeen = currentTimestamp; } // Perform periodic maintenance to prevent unbounded memory leaks. if (currentTimestamp - _lastGarbageCollectionTimestamp > GarbageCollectionIntervalMilliseconds) { PurgeStaleEntries(); _lastGarbageCollectionTimestamp = currentTimestamp; } return result; } [MethodImpl(MethodImplOptions.NoInlining)] private void PurgeStaleEntries() { var currentTimestamp = Environment.TickCount64; foreach (var entry in _cache) { if (currentTimestamp - entry.Value.LastSeen > PurgeAfterMilliseconds) { _cache.Remove(entry.Key); } } } private static SiloAddress ReadSiloAddressInner(ref Reader reader) { var ip = IPAddressCodec.ReadRaw(ref reader); var port = (int)reader.ReadVarUInt32(); var generation = reader.ReadInt32(); return SiloAddress.New(ip, port, generation); } public void WriteRaw(ref Writer writer, SiloAddress value) where TBufferWriter : IBufferWriter { var currentTimestamp = Environment.TickCount64; if (value is null) { writer.WriteByte(1); // writer.WriteVarUInt32(0); return; } var hashCode = value.GetConsistentHashCode(); ref var cacheEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, hashCode, out var exists); if (exists && value.Equals(cacheEntry.Value)) { writer.WriteVarUInt32((uint)cacheEntry.Encoded.Length); writer.Write(cacheEntry.Encoded); cacheEntry.LastSeen = currentTimestamp; // Perform periodic maintenance to prevent unbounded memory leaks. if (currentTimestamp - _lastGarbageCollectionTimestamp > GarbageCollectionIntervalMilliseconds) { PurgeStaleEntries(); _lastGarbageCollectionTimestamp = currentTimestamp; } return; } var innerWriter = Writer.Create(new PooledBuffer(), null); innerWriter.WriteInt32(value.GetConsistentHashCode()); WriteSiloAddressInner(ref innerWriter, value); innerWriter.Commit(); var payloadArray = innerWriter.Output.ToArray(); innerWriter.Dispose(); writer.WriteVarUInt32((uint)payloadArray.Length); writer.Write(payloadArray); // Before adding this value to the private cache, intern it via the shared cache to hopefully reduce duplicates. (_, payloadArray) = SharedCache.GetOrAdd(value, static (key, encoded) => (key, encoded), payloadArray); // If there is a hash collision, then the last seen entry will always win. cacheEntry.Encoded = payloadArray; cacheEntry.Value = value; cacheEntry.LastSeen = currentTimestamp; } private static void WriteSiloAddressInner(ref Writer writer, SiloAddress value) where TBufferWriter : IBufferWriter { var ep = value.Endpoint; // IP IPAddressCodec.WriteRaw(ref writer, ep.Address); // Port writer.WriteVarUInt16((ushort)ep.Port); // Generation writer.WriteInt32(value.Generation); } } } ================================================ FILE: src/Orleans.Core/Messaging/ClientMessageCenter.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime; using Orleans.Runtime.Messaging; namespace Orleans.Messaging { // // This class is used on the client only. // It provides the client counterpart to the Gateway and GatewayAcceptor classes on the silo side. // // There is one ClientMessageCenter instance per OutsideRuntimeClient. There can be multiple ClientMessageCenter instances // in a single process, but because RuntimeClient keeps a static pointer to a single OutsideRuntimeClient instance, this is not // generally done in practice. // // Each ClientMessageCenter keeps a collection of GatewayConnection instances. Each of these represents a bidirectional connection // to a single gateway endpoint. Requests are assigned to a specific connection based on the target grain ID, so that requests to // the same grain will go to the same gateway, in sending order. To do this efficiently and scalably, we bucket grains together // based on their hash code mod a reasonably large number (currently 8192). // // When the first message is sent to a bucket, we assign a gateway to that bucket, selecting in round-robin fashion from the known // gateways. If this is the first message to be sent to the gateway, we will create a new connection for it and assign the bucket to // the new connection. Either way, all messages to grains in that bucket will be sent to the assigned connection as long as the // connection is live. // // Connections stay live as long as possible. If a socket error or other communications error occurs, then the client will try to // reconnect twice before giving up on the gateway. If the connection cannot be re-established, then the gateway is deemed (temporarily) // dead, and any buckets assigned to the connection are unassigned (so that the next message sent will cause a new gateway to be selected). // There is no assumption that this death is permanent; the system will try to reuse the gateway every 5 minutes. // // The list of known gateways is managed by the GatewayManager class. See comments there for details. // internal partial class ClientMessageCenter : IMessageCenter, IDisposable { #if NET9_0_OR_GREATER private readonly Lock grainBucketUpdateLock = new(); #else private readonly object grainBucketUpdateLock = new(); #endif internal static readonly TimeSpan MINIMUM_INTERCONNECT_DELAY = TimeSpan.FromMilliseconds(100); // wait one tenth of a second between connect attempts internal const int CONNECT_RETRY_COUNT = 2; // Retry twice before giving up on a gateway server internal ClientGrainId ClientId => _localClientDetails.ClientId; public IRuntimeClient RuntimeClient { get; } internal bool Running { get; private set; } private readonly GatewayManager gatewayManager; private Action messageHandler; private int numMessages; // The grainBuckets array is used to select the connection to use when sending an ordered message to a grain. // Requests are bucketed by GrainID, so that all requests to a grain get routed through the same bucket. // Each bucket holds a (possibly null) weak reference to a GatewayConnection object. That connection instance is used // if the WeakReference is non-null, is alive, and points to a live gateway connection. If any of these conditions is // false, then a new gateway is selected using the gateway manager, and a new connection established if necessary. private readonly WeakReference[] grainBuckets; private readonly ILogger logger; public SiloAddress MyAddress => _localClientDetails.ClientAddress; private int numberOfConnectedGateways = 0; private readonly MessageFactory messageFactory; private readonly IClusterConnectionStatusListener connectionStatusListener; private readonly ConnectionManager connectionManager; private readonly LocalClientDetails _localClientDetails; public ClientMessageCenter( IOptions clientMessagingOptions, LocalClientDetails localClientDetails, IRuntimeClient runtimeClient, MessageFactory messageFactory, IClusterConnectionStatusListener connectionStatusListener, ILoggerFactory loggerFactory, ConnectionManager connectionManager, GatewayManager gatewayManager) { this.connectionManager = connectionManager; _localClientDetails = localClientDetails; this.RuntimeClient = runtimeClient; this.messageFactory = messageFactory; this.connectionStatusListener = connectionStatusListener; Running = false; this.gatewayManager = gatewayManager; numMessages = 0; this.grainBuckets = new WeakReference[clientMessagingOptions.Value.ClientSenderBuckets]; logger = loggerFactory.CreateLogger(); ClientInstruments.RegisterConnectedGatewayCountObserve(() => connectionManager.ConnectionCount); } public async Task StartAsync(CancellationToken cancellationToken) { await EstablishInitialConnection(cancellationToken); Running = true; LogClientMessageCenterStarted(); } private async Task EstablishInitialConnection(CancellationToken cancellationToken) { var liveGateways = gatewayManager.GetLiveGateways(); if (liveGateways.Count == 0) { throw new ConnectionFailedException("There are no available gateways."); } var pendingTasks = new List(liveGateways.Count); foreach (var gateway in liveGateways) { pendingTasks.Add(connectionManager.GetConnection(gateway).AsTask()); } try { while (pendingTasks.Count > 0) { var completedTask = await Task.WhenAny(pendingTasks).WaitAsync(cancellationToken); pendingTasks.Remove(completedTask); // If at least one gateway connection has been established, break out of the loop and continue startup. if (completedTask.IsCompletedSuccessfully) { break; } // If there are no more gateways, observe the most recent exception and bail out. if (pendingTasks.Count == 0) { await completedTask; } else { completedTask.Ignore(); } } } catch (Exception exception) { throw new ConnectionFailedException( $"Unable to connect to any of the {liveGateways.Count} available gateways.", exception); } } public async Task StopAsync(CancellationToken cancellationToken) { Running = false; await gatewayManager.StopAsync(cancellationToken); } public void DispatchLocalMessage(Message message) { var handler = this.messageHandler; if (handler is null) { ThrowNullMessageHandler(); } else { handler(message); } static void ThrowNullMessageHandler() => throw new InvalidOperationException("MessageCenter does not have a message handler set"); } public void SendMessage(Message msg) { if (!Running) { LogNotRunning(msg); return; } var connectionTask = this.GetGatewayConnection(msg); if (connectionTask.IsCompletedSuccessfully) { var connection = connectionTask.Result; if (connection is null) return; connection.Send(msg); LogSendingMessage(msg, connection.RemoteEndPoint); } else { _ = SendAsync(connectionTask, msg); async Task SendAsync(ValueTask task, Message message) { try { var connection = await task; // If the connection returned is null then the message was already rejected due to a failure. if (connection is null) return; connection.Send(message); LogSendingMessage(message, connection.RemoteEndPoint); } catch (Exception exception) { if (message.RetryCount < MessagingOptions.DEFAULT_MAX_MESSAGE_SEND_RETRIES) { ++message.RetryCount; _ = Task.Factory.StartNew( state => this.SendMessage((Message)state), message, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } else { this.RejectMessage(message, $"Unable to send message due to exception {exception}", exception); } } } } } private ValueTask GetGatewayConnection(Message msg) { // If there's a specific gateway specified, use it if (msg.TargetSilo != null && gatewayManager.IsGatewayAvailable(msg.TargetSilo)) { var siloAddress = SiloAddress.New(msg.TargetSilo.Endpoint, 0); var connectionTask = this.connectionManager.GetConnection(siloAddress); if (connectionTask.IsCompletedSuccessfully) return connectionTask; return ConnectAsync(msg.TargetSilo, connectionTask, msg, directGatewayMessage: true); } // For untargeted messages to system targets, and for unordered messages, pick a next connection in round robin fashion. if (msg.TargetGrain.IsSystemTarget() || msg.IsUnordered) { // Get the cached list of live gateways. // Pick a next gateway name in a round robin fashion. // See if we have a live connection to it. // If Yes, use it. // If not, create a new GatewayConnection and start it. // If start fails, we will mark this connection as dead and remove it from the GetCachedLiveGatewayNames. int msgNumber = Interlocked.Increment(ref numMessages); var gatewayAddresses = gatewayManager.GetLiveGateways(); int numGateways = gatewayAddresses.Count; if (numGateways == 0) { RejectMessage(msg, "No gateways available"); LogSendFailed(msg, gatewayManager); return new ValueTask(default(Connection)); } var gatewayAddress = gatewayAddresses[msgNumber % numGateways]; var connectionTask = this.connectionManager.GetConnection(gatewayAddress); if (connectionTask.IsCompletedSuccessfully) return connectionTask; return ConnectAsync(gatewayAddress, connectionTask, msg, directGatewayMessage: false); } // Otherwise, use the buckets to ensure ordering. var index = GetHashCodeModulo(msg.TargetGrain.GetHashCode(), (uint)grainBuckets.Length); // Repeated from above, at the declaration of the grainBuckets array: // Requests are bucketed by GrainID, so that all requests to a grain get routed through the same bucket. // Each bucket holds a (possibly null) weak reference to a GatewayConnection object. That connection instance is used // if the WeakReference is non-null, is alive, and points to a live gateway connection. If any of these conditions is // false, then a new gateway is selected using the gateway manager, and a new connection established if necessary. WeakReference weakRef = grainBuckets[index]; if (weakRef != null && weakRef.TryGetTarget(out var existingConnection) && existingConnection.IsValid && gatewayManager.IsGatewayAvailable(existingConnection.RemoteSiloAddress)) { return new ValueTask(existingConnection); } var addr = gatewayManager.GetLiveGateway(); if (addr == null) { RejectMessage(msg, "No gateways available"); LogNoGatewayAvailableForMessage(msg, gatewayManager); return new ValueTask(default(Connection)); } var gatewayConnection = this.connectionManager.GetConnection(addr); if (gatewayConnection.IsCompletedSuccessfully) { this.UpdateBucket(index, (ClientOutboundConnection)gatewayConnection.Result); return gatewayConnection; } return AddToBucketAsync(index, gatewayConnection, addr); async ValueTask AddToBucketAsync( uint bucketIndex, ValueTask connectionTask, SiloAddress gatewayAddress) { try { var connection = (ClientOutboundConnection)await connectionTask.ConfigureAwait(false); this.UpdateBucket(bucketIndex, connection); return connection; } catch { this.gatewayManager.MarkAsDead(gatewayAddress); this.UpdateBucket(bucketIndex, null); throw; } } async ValueTask ConnectAsync( SiloAddress gateway, ValueTask connectionTask, Message message, bool directGatewayMessage) { Connection result = default; try { return result = await connectionTask; } catch (Exception exception) when (directGatewayMessage) { RejectMessage(message, $"Target silo {message.TargetSilo} is unavailable", exception); return null; } finally { if (result is null) this.gatewayManager.MarkAsDead(gateway); } } static uint GetHashCodeModulo(int key, uint umod) { int mod = (int)umod; key = ((key % mod) + mod) % mod; // key should be positive now. So assert with checked. return checked((uint)key); } } private void UpdateBucket(uint index, ClientOutboundConnection connection) { lock (this.grainBucketUpdateLock) { var value = this.grainBuckets[index] ?? new WeakReference(connection); value.SetTarget(connection); this.grainBuckets[index] = value; } } public void RegisterLocalMessageHandler(Action handler) { this.messageHandler = handler; } public void RejectMessage(Message msg, string reason, Exception exc = null) { if (!Running) return; if (msg.Direction != Message.Directions.Request) { LogDroppingMessage(msg, reason); } else { LogRejectingMessage(msg, reason); MessagingInstruments.OnRejectedMessage(msg); var error = this.messageFactory.CreateRejectionResponse(msg, Message.RejectionTypes.Unrecoverable, reason, exc); DispatchLocalMessage(error); } } internal void OnGatewayConnectionOpen() { int newCount = Interlocked.Increment(ref numberOfConnectedGateways); this.connectionStatusListener.NotifyGatewayCountChanged(newCount, newCount - 1); } internal void OnGatewayConnectionClosed() { var gatewayCount = Interlocked.Decrement(ref numberOfConnectedGateways); if (gatewayCount == 0) { this.connectionStatusListener.NotifyClusterConnectionLost(); } this.connectionStatusListener.NotifyGatewayCountChanged(gatewayCount, gatewayCount + 1); } public void Dispose() { gatewayManager.Dispose(); } [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_MsgCtrNotRunning, Level = LogLevel.Error, Message = "Ignoring {Message} because the client message center is not running." )] private partial void LogNotRunning(Message message); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_QueueRequest, Level = LogLevel.Trace, Message = "Sending message {Message} via gateway '{Gateway}'." )] private partial void LogSendingMessage(Message message, EndPoint gateway); [LoggerMessage( Level = LogLevel.Trace, Message = "Client message center started." )] private partial void LogClientMessageCenterStarted(); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_CannotSend, Level = LogLevel.Warning, Message = "Unable to send message {Message}; Gateway manager state is {GatewayManager}." )] private partial void LogSendFailed(Message message, GatewayManager gatewayManager); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_CannotSend_NoGateway, Level = LogLevel.Warning, Message = "No gateway available to receive message {Message}; Gateway manager state is {GatewayManager}." )] private partial void LogNoGatewayAvailableForMessage(Message message, GatewayManager gatewayManager); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_DroppingMsg, Level = LogLevel.Debug, Message = "Dropping message: {Message}. Reason = {Reason}" )] private partial void LogDroppingMessage(Message message, string reason); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_RejectingMsg, Level = LogLevel.Debug, Message = "Rejecting message: {Message}. Reason = {Reason}" )] private partial void LogRejectingMessage(Message message, string reason); } } ================================================ FILE: src/Orleans.Core/Messaging/CorrelationId.cs ================================================ using System; using System.Runtime.CompilerServices; #nullable enable namespace Orleans.Runtime { [Serializable, GenerateSerializer, Immutable] internal readonly struct CorrelationId : IEquatable, IComparable, ISpanFormattable { [Id(0)] private readonly long id; private static long lastUsed; public CorrelationId(long value) => id = value; public CorrelationId(CorrelationId other) => id = other.id; public static CorrelationId GetNext() => new(System.Threading.Interlocked.Increment(ref lastUsed)); public override int GetHashCode() => id.GetHashCode(); public override bool Equals(object? obj) => obj is CorrelationId correlationId && Equals(correlationId); public bool Equals(CorrelationId other) => id == other.id; public static bool operator ==(CorrelationId lhs, CorrelationId rhs) => rhs.id == lhs.id; public static bool operator !=(CorrelationId lhs, CorrelationId rhs) => rhs.id != lhs.id; public int CompareTo(CorrelationId other) => id.CompareTo(other.id); public override string ToString() => id.ToString("X16"); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => id.ToString(format ?? "X16", formatProvider); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { if (format.IsEmpty) { format = "X16"; } return id.TryFormat(destination, out charsWritten, format, provider); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal long ToInt64() => id; } } ================================================ FILE: src/Orleans.Core/Messaging/GatewayManager.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Runtime.Messaging; using static Orleans.Internal.StandardExtensions; namespace Orleans.Messaging { /// /// The GatewayManager class holds the list of known gateways, as well as maintaining the list of "dead" gateways. /// /// The known list can come from one of two places: the full list may appear in the client configuration object, or /// the config object may contain an IGatewayListProvider delegate. If both appear, then the delegate takes priority. /// internal partial class GatewayManager : IDisposable { #if NET9_0_OR_GREATER private readonly Lock lockable = new(); #else private readonly object lockable = new(); #endif private readonly Dictionary knownDead = new Dictionary(); private readonly Dictionary knownMasked = new Dictionary(); private readonly IGatewayListProvider gatewayListProvider; private readonly ILogger logger; private readonly ConnectionManager connectionManager; private readonly GatewayOptions gatewayOptions; private readonly PeriodicTimer gatewayRefreshTimer; private List cachedLiveGateways = []; private HashSet cachedLiveGatewaysSet = []; private List knownGateways = []; private DateTime lastRefreshTime; private int roundRobinCounter; private bool gatewayRefreshCallInitiated; private bool gatewayListProviderInitialized; private Task? gatewayRefreshTimerTask; public GatewayManager( IOptions gatewayOptions, IGatewayListProvider gatewayListProvider, ILoggerFactory loggerFactory, ConnectionManager connectionManager, TimeProvider timeProvider) { this.gatewayOptions = gatewayOptions.Value; this.logger = loggerFactory.CreateLogger(); this.connectionManager = connectionManager; this.gatewayListProvider = gatewayListProvider; var refreshPeriod = Max(this.gatewayOptions.GatewayListRefreshPeriod, TimeSpan.FromMilliseconds(1)); this.gatewayRefreshTimer = new PeriodicTimer(this.gatewayOptions.GatewayListRefreshPeriod, timeProvider); } public async Task StartAsync(CancellationToken cancellationToken) { if (!gatewayListProviderInitialized) { await this.gatewayListProvider.InitializeGatewayListProvider(); gatewayListProviderInitialized = true; } var knownGateways = await this.gatewayListProvider.GetGateways(); if (knownGateways == null || knownGateways.Count == 0) { // this situation can occur if the client starts faster than the silos. var providerName = this.gatewayListProvider.GetType().FullName; LogNoGatewayDuringInitialization(this.logger, providerName); var message = $"Could not find any gateway in '{providerName}'. Orleans client cannot initialize until at least one gateway becomes available."; throw new SiloUnavailableException(message); } LogFoundGateways(this.logger, knownGateways.Count, new(knownGateways)); this.roundRobinCounter = this.gatewayOptions.PreferredGatewayIndex >= 0 ? this.gatewayOptions.PreferredGatewayIndex : Random.Shared.Next(knownGateways.Count); var newGateways = new List(); foreach (var gatewayUri in knownGateways) { if (gatewayUri?.ToGatewayAddress() is { } gatewayAddress) { newGateways.Add(gatewayAddress); } } this.knownGateways = this.cachedLiveGateways = newGateways; this.cachedLiveGatewaysSet = new HashSet(cachedLiveGateways); this.lastRefreshTime = DateTime.UtcNow; this.gatewayRefreshTimerTask ??= PeriodicallyRefreshGatewaySnapshot(); } public async Task StopAsync(CancellationToken cancellationToken) { gatewayRefreshTimer.Dispose(); if (gatewayRefreshTimerTask is { } task) { await task.WaitAsync(cancellationToken); } } public void MarkAsDead(SiloAddress gateway) { lock (lockable) { knownDead[gateway] = DateTime.UtcNow; var copy = new List(cachedLiveGateways); copy.Remove(gateway); // swap the reference, don't mutate cachedLiveGateways, so we can access cachedLiveGateways without the lock. cachedLiveGateways = copy; cachedLiveGatewaysSet = new HashSet(cachedLiveGateways); } } public void MarkAsUnavailableForSend(SiloAddress gateway) { lock (lockable) { knownMasked[gateway] = DateTime.UtcNow; var copy = new List(cachedLiveGateways); copy.Remove(gateway); // swap the reference, don't mutate cachedLiveGateways, so we can access cachedLiveGateways without the lock. cachedLiveGateways = copy; cachedLiveGatewaysSet = new HashSet(cachedLiveGateways); } } public override string ToString() { var sb = new StringBuilder(); sb.Append("GatewayManager: "); lock (lockable) { if (cachedLiveGateways != null) { sb.Append(cachedLiveGateways.Count); sb.Append(" cachedLiveGateways, "); } if (knownDead != null) { sb.Append(knownDead.Count); sb.Append(" known dead gateways."); } } return sb.ToString(); } /// /// Selects a gateway to use for a new bucket. /// /// Note that if a list provider delegate was given, the delegate is invoked every time this method is called. /// This method performs caching to avoid hammering the ultimate data source. /// /// This implementation does a simple round robin selection. It assumes that the gateway list from the provider /// is in the same order every time. /// /// #nullable enable public SiloAddress? GetLiveGateway() #nullable disable { List live = GetLiveGateways(); int count = live.Count; if (count > 0) { lock (lockable) { // Round-robin through the known gateways and take the next live one, starting from where we last left off roundRobinCounter = (roundRobinCounter + 1) % count; return live[roundRobinCounter]; } } // If we drop through, then all of the known gateways are presumed dead return null; } public List GetLiveGateways() { // Never takes a lock and returns the cachedLiveGateways list quickly without any operation. // Asynchronously starts gateway refresh only when it is empty. if (cachedLiveGateways.Count == 0) { ExpediteUpdateLiveGatewaysSnapshot(); if (knownGateways.Count > 0) { lock (this.lockable) { if (cachedLiveGateways.Count == 0 && knownGateways.Count > 0) { LogAllGatewaysMarkedDead(this.logger); cachedLiveGateways = knownGateways; cachedLiveGatewaysSet = new HashSet(knownGateways); } } } } return cachedLiveGateways; } public bool IsGatewayAvailable(SiloAddress siloAddress) { return cachedLiveGatewaysSet.Contains(siloAddress); } internal void ExpediteUpdateLiveGatewaysSnapshot() { // If there is already an expedited refresh call in place, don't call again, until the previous one is finished. // We don't want to issue too many Gateway refresh calls. if (gatewayListProvider == null || gatewayRefreshCallInitiated) return; // Initiate gateway list refresh asynchronously. The Refresh timer will keep ticking regardless. // We don't want to block the client with synchronously Refresh call. // Client's call will fail with "No Gateways found" but we will try to refresh the list quickly. gatewayRefreshCallInitiated = true; _ = Task.Run(async () => { try { await RefreshGatewaySnapshot(); } finally { gatewayRefreshCallInitiated = false; } }); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] internal async Task PeriodicallyRefreshGatewaySnapshot() { await Task.Yield(); if (gatewayListProvider is null) { return; } while (await gatewayRefreshTimer.WaitForNextTickAsync()) { await RefreshGatewaySnapshot(); } } private async Task RefreshGatewaySnapshot() { try { if (gatewayListProvider is null) { return; } // the listProvider.GetGateways() is not under lock. var allGateways = await gatewayListProvider.GetGateways(); var refreshedGateways = allGateways.Select(gw => gw.ToGatewayAddress()).ToList(); await UpdateLiveGatewaysSnapshot(refreshedGateways, gatewayListProvider.MaxStaleness); } catch (Exception exc) { LogErrorRefreshingGateways(this.logger, exc); } } // This function is called asynchronously from gateway refresh timer. private async Task UpdateLiveGatewaysSnapshot(IEnumerable refreshedGateways, TimeSpan maxStaleness) { List connectionsToKeepAlive; // This is a short lock, protecting the access to knownDead, knownMasked and cachedLiveGateways. lock (lockable) { // now take whatever listProvider gave us and exclude those we think are dead. var live = new List(); var now = DateTime.UtcNow; this.knownGateways = refreshedGateways as List ?? refreshedGateways.ToList(); foreach (SiloAddress trial in knownGateways) { var address = trial.Generation == 0 ? trial : SiloAddress.New(trial.Endpoint, 0); // We consider a node to be dead if we recorded it is dead due to socket error // and it was recorded (diedAt) not too long ago (less than maxStaleness ago). // The latter is to cover the case when the Gateway provider returns an outdated list that does not yet reflect the actually recently died Gateway. // If it has passed more than maxStaleness - we assume maxStaleness is the upper bound on Gateway provider freshness. var isDead = false; if (knownDead.TryGetValue(address, out var diedAt)) { if (now.Subtract(diedAt) < maxStaleness) { isDead = true; } else { // Remove stale entries. knownDead.Remove(address); } } if (knownMasked.TryGetValue(address, out var maskedAt)) { if (now.Subtract(maskedAt) < maxStaleness) { isDead = true; } else { // Remove stale entries. knownMasked.Remove(address); } } if (!isDead) { live.Add(address); } } if (live.Count == 0) { LogAllGatewaysDead(logger); live.AddRange(knownGateways); knownDead.Clear(); } // swap cachedLiveGateways pointer in one atomic operation cachedLiveGateways = live; cachedLiveGatewaysSet = new HashSet(live); DateTime prevRefresh = lastRefreshTime; lastRefreshTime = now; LogRefreshedLiveGatewayList(logger, knownGateways.Count, new(knownGateways), cachedLiveGateways.Count, new(cachedLiveGateways), prevRefresh); // Close connections to known dead connections, but keep the "masked" ones. // Client will not send any new request to the "masked" connections, but might still // receive responses connectionsToKeepAlive = new List(live); connectionsToKeepAlive.AddRange(knownMasked.Select(e => e.Key)); } await this.CloseEvictedGatewayConnections(connectionsToKeepAlive); } private async Task CloseEvictedGatewayConnections(List liveGateways) { if (this.connectionManager == null) return; var connectedGateways = this.connectionManager.GetConnectedAddresses(); foreach (var address in connectedGateways) { var isLiveGateway = false; foreach (var live in liveGateways) { if (live.Matches(address)) { isLiveGateway = true; break; } } if (!isLiveGateway) { LogClosingConnectionToDeadGateway(this.logger, address); await this.connectionManager.CloseAsync(address); } } } public void Dispose() { this.gatewayRefreshTimer.Dispose(); } [LoggerMessage( EventId = (int)ErrorCode.GatewayManager_NoGateways, Level = LogLevel.Warning, Message = "Could not find any gateway in '{GatewayListProviderName}'. Orleans client cannot initialize until at least one gateway becomes available." )] private static partial void LogNoGatewayDuringInitialization(ILogger logger, string gatewayListProviderName); private readonly struct UrisLogValue(IList uris) { public override readonly string ToString() => Utils.EnumerableToString(uris); } [LoggerMessage( EventId = (int)ErrorCode.GatewayManager_FoundKnownGateways, Level = LogLevel.Information, Message = "Found '{GatewayCount}' gateways: '{Gateways}'." )] private static partial void LogFoundGateways(ILogger logger, int gatewayCount, UrisLogValue gateways); [LoggerMessage( Level = LogLevel.Warning, Message = "All known gateways have been marked dead locally. Expediting gateway refresh and resetting all gateways to live status." )] private static partial void LogAllGatewaysMarkedDead(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_GetGateways, Level = LogLevel.Error, Message = "Error refreshing gateways." )] private static partial void LogErrorRefreshingGateways(ILogger logger, Exception exc); [LoggerMessage( Level = LogLevel.Information, Message = "Closing connection to '{EndPoint}' because it has been marked as dead." )] private static partial void LogClosingConnectionToDeadGateway(ILogger logger, SiloAddress endPoint); [LoggerMessage( EventId = (int)ErrorCode.GatewayManager_AllGatewaysDead, Level = LogLevel.Warning, Message = "All gateways have previously been marked as dead. Clearing the list of dead gateways to expedite reconnection." )] private static partial void LogAllGatewaysDead(ILogger logger); private readonly struct SiloAddressesLogValue(List addresses) { public override string ToString() => Utils.EnumerableToString(addresses); } [LoggerMessage( EventId = (int)ErrorCode.GatewayManager_FoundKnownGateways, Level = LogLevel.Debug, Message = "Refreshed the live gateway list. Found '{KnownGatewayCount}' gateways from gateway list provider: '{KnownGateways}'. Picked only known live out of them. Now has '{LiveGatewayCount}' live gateways: '{LiveGateways}'. Previous refresh time was = '{PreviousRefreshTime}'" )] private static partial void LogRefreshedLiveGatewayList(ILogger logger, int knownGatewayCount, SiloAddressesLogValue knownGateways, int liveGatewayCount, SiloAddressesLogValue liveGateways, DateTime previousRefreshTime); } } ================================================ FILE: src/Orleans.Core/Messaging/IGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Messaging { /// /// Interface that provides Orleans gateways information. /// public interface IGatewayListProvider { /// /// Initializes the provider, will be called before all other methods. /// /// A representing the work performed. Task InitializeGatewayListProvider(); /// /// Returns the list of gateways (silos) that can be used by a client to connect to Orleans cluster. /// The Uri is in the form of: "gwy.tcp://IP:port/Generation". See Utils.ToGatewayUri and Utils.ToSiloAddress for more details about Uri format. /// /// The list of gateway endpoints. Task> GetGateways(); /// /// Gets the period of time between refreshes. /// TimeSpan MaxStaleness { get; } /// /// Gets a value indicating whether this IGatewayListProvider ever refreshes its returned information, or always returns the same gateway list. /// [Obsolete("This attribute is no longer used and all providers are considered updatable")] bool IsUpdatable { get; } } } ================================================ FILE: src/Orleans.Core/Messaging/IMessageCenter.cs ================================================ namespace Orleans.Runtime { internal interface IMessageCenter { void SendMessage(Message msg); void DispatchLocalMessage(Message message); } } ================================================ FILE: src/Orleans.Core/Messaging/InvalidMessageFrameException.cs ================================================ #nullable enable using System; using System.Runtime.Serialization; namespace Orleans.Runtime.Messaging; /// /// Indicates that a message frame is invalid, either when sending a message or receiving a message. /// [GenerateSerializer] public sealed class InvalidMessageFrameException : OrleansException { /// /// Initializes a new instance of the class. /// public InvalidMessageFrameException() { } /// /// Initializes a new instance of the class. /// /// The message that describes the error. public InvalidMessageFrameException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message that describes the error. /// The exception that is the cause of the current exception. public InvalidMessageFrameException(string message, Exception innerException) : base(message, innerException) { } [Obsolete] protected InvalidMessageFrameException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Orleans.Core/Messaging/Message.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Orleans.Runtime { [Id(101)] internal sealed class Message : ISpanFormattable { public const int LENGTH_HEADER_SIZE = 8; public const int LENGTH_META_HEADER = 4; [NonSerialized] private short _retryCount; public CoarseStopwatch _timeToExpiry; public object? BodyObject { get; set; } public PackedHeaders _headers; public CorrelationId _id; public Dictionary? _requestContextData; public SiloAddress? _targetSilo; public GrainId _targetGrain; public SiloAddress? _sendingSilo; public GrainId _sendingGrain; public ushort _interfaceVersion; public GrainInterfaceType _interfaceType; public List? _cacheInvalidationHeader; public PackedHeaders Headers { get => _headers; set => _headers = value; } [GenerateSerializer] public enum Directions : byte { None, Request, Response, OneWay } [GenerateSerializer] public enum ResponseTypes : byte { None, Success, Error, Rejection, Status } [GenerateSerializer] public enum RejectionTypes : byte { None, Transient, Overloaded, Unrecoverable, GatewayTooBusy, CacheInvalidation } public Directions Direction { get => _headers.Direction; set => _headers.Direction = value; } public bool HasDirection => _headers.Direction != Directions.None; public bool IsSenderFullyAddressed => SendingSilo is not null && !SendingGrain.IsDefault; public bool IsTargetFullyAddressed => TargetSilo is not null && !TargetGrain.IsDefault; public bool IsExpired => _timeToExpiry is { IsDefault: false, ElapsedMilliseconds: > 0 }; public short RetryCount { get => _retryCount; set => _retryCount = value; } public bool HasCacheInvalidationHeader => CacheInvalidationHeader is { Count: > 0 }; public bool IsSystemMessage { get => _headers.HasFlag(MessageFlags.SystemMessage); set => _headers.SetFlag(MessageFlags.SystemMessage, value); } /// /// Indicates whether the message does not mutate application state and therefore whether it can be interleaved with other read-only messages. /// /// /// Defaults to . /// public bool IsReadOnly { get => _headers.HasFlag(MessageFlags.ReadOnly); set => _headers.SetFlag(MessageFlags.ReadOnly, value); } public bool IsAlwaysInterleave { get => _headers.HasFlag(MessageFlags.AlwaysInterleave); set => _headers.SetFlag(MessageFlags.AlwaysInterleave, value); } public bool IsUnordered { get => _headers.HasFlag(MessageFlags.Unordered); set => _headers.SetFlag(MessageFlags.Unordered, value); } /// /// Whether the message is allowed to be sent to another activation of the target grain. /// /// /// Defaults to . /// public bool IsLocalOnly { get => _headers.HasFlag(MessageFlags.IsLocalOnly); set => _headers.SetFlag(MessageFlags.IsLocalOnly, value); } /// /// Whether the message is allowed to activate a grain and/or extend its lifetime. /// /// /// Defaults to . /// public bool IsKeepAlive { get => !_headers.HasFlag(MessageFlags.SuppressKeepAlive); set => _headers.SetFlag(MessageFlags.SuppressKeepAlive, !value); } public CorrelationId Id { get => _id; set => _id = value; } public int ForwardCount { get => _headers.ForwardCount; set => _headers.ForwardCount = value; } public SiloAddress? TargetSilo { get => _targetSilo; set { _targetSilo = value; } } public GrainId TargetGrain { get => _targetGrain; set { _targetGrain = value; } } public SiloAddress? SendingSilo { get => _sendingSilo; set { _sendingSilo = value; } } public GrainId SendingGrain { get => _sendingGrain; set { _sendingGrain = value; } } public ushort InterfaceVersion { get => _interfaceVersion; set { _interfaceVersion = value; _headers.SetFlag(MessageFlags.HasInterfaceVersion, value is not 0); } } public ResponseTypes Result { get => _headers.ResponseType; set => _headers.ResponseType = value; } public TimeSpan? TimeToLive { get => _timeToExpiry.IsDefault ? null : -_timeToExpiry.Elapsed; set { if (value.HasValue) { SetTimeToLiveMilliseconds((long)value.Value.TotalMilliseconds); } else { SetInfiniteTimeToLive(); } } } internal long GetTimeToLiveMilliseconds() => -_timeToExpiry.ElapsedMilliseconds; internal void SetTimeToLiveMilliseconds(long milliseconds) { _headers.SetFlag(MessageFlags.HasTimeToLive, true); _timeToExpiry = CoarseStopwatch.StartNew(-milliseconds); } internal void SetInfiniteTimeToLive() { _headers.SetFlag(MessageFlags.HasTimeToLive, false); _timeToExpiry = default; } public List? CacheInvalidationHeader { get => _cacheInvalidationHeader; set { _cacheInvalidationHeader = value; _headers.SetFlag(MessageFlags.HasCacheInvalidationHeader, value is not null); } } public Dictionary? RequestContextData { get => _requestContextData; set { _requestContextData = value; _headers.SetFlag(MessageFlags.HasRequestContextData, value is not null); } } public GrainInterfaceType InterfaceType { get => _interfaceType; set { _interfaceType = value; _headers.SetFlag(MessageFlags.HasInterfaceType, !value.IsDefault); } } public bool IsExpirableMessage() { GrainId id = TargetGrain; if (id.IsDefault) return false; // don't set expiration for one way, system target and system grain messages. return Direction != Directions.OneWay && !id.IsSystemTarget(); } internal void AddToCacheInvalidationHeader(GrainAddress invalidAddress, GrainAddress? validAddress) { var grainAddressCacheUpdate = new GrainAddressCacheUpdate(invalidAddress, validAddress); if (_cacheInvalidationHeader is null) { var newList = new List { grainAddressCacheUpdate }; if (Interlocked.CompareExchange(ref _cacheInvalidationHeader, newList, null) is not null) { // Another thread initialized it, add to the existing list lock (_cacheInvalidationHeader) { _cacheInvalidationHeader.Add(grainAddressCacheUpdate); } } else { _headers.SetFlag(MessageFlags.HasCacheInvalidationHeader, true); } } else { lock (_cacheInvalidationHeader) { _cacheInvalidationHeader.Add(grainAddressCacheUpdate); } } } public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span dst, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { ref var origin = ref MemoryMarshal.GetReference(dst); int len; if (IsReadOnly && !Append(ref dst, "ReadOnly ")) goto grow; if (IsAlwaysInterleave && !Append(ref dst, "IsAlwaysInterleave ")) goto grow; if (Direction == Directions.Response) { switch (Result) { case ResponseTypes.Rejection when BodyObject is RejectionResponse rejection: if (!dst.TryWrite($"{rejection.RejectionType} Rejection (info: {rejection.RejectionInfo}) ", out len)) goto grow; dst = dst[len..]; break; case ResponseTypes.Error: if (!Append(ref dst, "Error ")) goto grow; break; case ResponseTypes.Status: if (!Append(ref dst, "Status ")) goto grow; break; } } if (!dst.TryWrite($"{Direction} [{SendingSilo} {SendingGrain}]->[{TargetSilo} {TargetGrain}]", out len)) goto grow; dst = dst[len..]; if (BodyObject is { } request) { if (!dst.TryWrite($" {request}", out len)) goto grow; dst = dst[len..]; } if (!dst.TryWrite($" #{Id}", out len)) goto grow; dst = dst[len..]; if (ForwardCount > 0) { if (!dst.TryWrite($"[ForwardCount={ForwardCount}]", out len)) goto grow; dst = dst[len..]; } charsWritten = (int)Unsafe.ByteOffset(ref origin, ref MemoryMarshal.GetReference(dst)) / sizeof(char); return true; grow: charsWritten = 0; return false; static bool Append(ref Span dst, ReadOnlySpan value) { if (!value.TryCopyTo(dst)) return false; dst = dst[value.Length..]; return true; } } internal bool IsPing() => _requestContextData?.TryGetValue(RequestContext.PING_APPLICATION_HEADER, out var value) == true && value is bool isPing && isPing; [Flags] internal enum MessageFlags : ushort { SystemMessage = 1 << 0, ReadOnly = 1 << 1, AlwaysInterleave = 1 << 2, Unordered = 1 << 3, HasRequestContextData = 1 << 4, HasInterfaceVersion = 1 << 5, HasInterfaceType = 1 << 6, HasCacheInvalidationHeader = 1 << 7, HasTimeToLive = 1 << 8, // Message cannot be forwarded to another activation. IsLocalOnly = 1 << 9, // Message must not trigger grain activation or extend an activation's lifetime. SuppressKeepAlive = 1 << 10, // The most significant bit is reserved, possibly for use to indicate more data follows. Reserved = 1 << 15, } internal struct PackedHeaders { private const uint DirectionMask = 0x000F_0000; private const int DirectionShift = 16; private const uint ResponseTypeMask = 0x00F0_0000; private const int ResponseTypeShift = 20; private const uint ForwardCountMask = 0xFF00_0000; private const int ForwardCountShift = 24; public static implicit operator PackedHeaders(uint fields) => new() { _fields = fields }; public static implicit operator uint(PackedHeaders value) => value._fields; // 32 bits: HHHH_HHHH RRRR_DDDD FFFF_FFFF FFFF_FFFF // F: 16 bits for MessageFlags // D: 4 bits for Direction // R: 4 bits for ResponseType // H: 8 bits for ForwardCount (hop count) private uint _fields; public int ForwardCount { readonly get => (int)(_fields >> ForwardCountShift); set => _fields = (_fields & ~ForwardCountMask) | (uint)value << ForwardCountShift; } public Directions Direction { readonly get => (Directions)((_fields & DirectionMask) >> DirectionShift); set => _fields = (_fields & ~DirectionMask) | (uint)value << DirectionShift; } public ResponseTypes ResponseType { readonly get => (ResponseTypes)((_fields & ResponseTypeMask) >> ResponseTypeShift); set => _fields = (_fields & ~ResponseTypeMask) | (uint)value << ResponseTypeShift; } public readonly bool HasFlag(MessageFlags flag) => (_fields & (uint)flag) != 0; public void SetFlag(MessageFlags flag, bool value) => _fields = value switch { true => _fields | (uint)flag, false => _fields & ~(uint)flag, }; } } } ================================================ FILE: src/Orleans.Core/Messaging/MessageFactory.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Microsoft.Extensions.Logging; using Orleans.CodeGeneration; using Orleans.Serialization; namespace Orleans.Runtime { internal partial class MessageFactory { private static ulong _nextId; // The nonce reduces the chance of an id collision for a given grain to effectively zero. Id collisions are only relevant in scenarios // where where the infinitesimally small chance of a collision is acceptable, such as call cancellation. private readonly ulong _seed; private readonly DeepCopier _deepCopier; private readonly ILogger _logger; private readonly MessagingTrace _messagingTrace; public MessageFactory(DeepCopier deepCopier, ILogger logger, MessagingTrace messagingTrace) { _deepCopier = deepCopier; _logger = logger; _messagingTrace = messagingTrace; // Generate a 64-bit nonce for the host, to be combined with per-message correlation ids to get a unique, per-host value. // This avoids id collisions across different hosts for a given grain. _seed = unchecked((ulong)Random.Shared.NextInt64()); } public Message CreateMessage(object body, InvokeMethodOptions options) { var message = new Message { Direction = (options & InvokeMethodOptions.OneWay) != 0 ? Message.Directions.OneWay : Message.Directions.Request, Id = GetNextCorrelationId(), IsReadOnly = (options & InvokeMethodOptions.ReadOnly) != 0, IsUnordered = (options & InvokeMethodOptions.Unordered) != 0, IsAlwaysInterleave = (options & InvokeMethodOptions.AlwaysInterleave) != 0, BodyObject = body, RequestContextData = RequestContextExtensions.Export(_deepCopier), }; _messagingTrace.OnCreateMessage(message); return message; } private CorrelationId GetNextCorrelationId() { var id = _seed ^ Interlocked.Increment(ref _nextId); return new CorrelationId(unchecked((long)id)); } public Message CreateResponseMessage(Message request) { var response = new Message { IsSystemMessage = request.IsSystemMessage, Direction = Message.Directions.Response, Id = request.Id, IsReadOnly = request.IsReadOnly, IsAlwaysInterleave = request.IsAlwaysInterleave, TargetSilo = request.SendingSilo, TargetGrain = request.SendingGrain, SendingSilo = request.TargetSilo, SendingGrain = request.TargetGrain, CacheInvalidationHeader = request.CacheInvalidationHeader, TimeToLive = request.TimeToLive, RequestContextData = RequestContextExtensions.Export(_deepCopier), }; _messagingTrace.OnCreateMessage(response); return response; } public Message CreateRejectionResponse(Message request, Message.RejectionTypes type, string info, Exception ex = null) { var response = CreateResponseMessage(request); response.Result = Message.ResponseTypes.Rejection; response.BodyObject = new RejectionResponse { RejectionType = type, RejectionInfo = info, Exception = ex, }; LogCreatingRejectionResponse(_logger, ex, type, info); return response; } internal Message CreateDiagnosticResponseMessage(Message request, bool isExecuting, bool isWaiting, List diagnostics) { var response = CreateResponseMessage(request); response.Result = Message.ResponseTypes.Status; response.BodyObject = new StatusResponse(isExecuting, isWaiting, diagnostics); LogCreatingStatusUpdate(_logger, request, new(diagnostics)); return response; } [LoggerMessage( Level = LogLevel.Debug, Message = "Creating '{RejectionType}' rejection with info '{Information}'." )] private static partial void LogCreatingRejectionResponse(ILogger logger, Exception exception, Message.RejectionTypes rejectionType, string information); private readonly struct DiagnosticsLogValue(List diagnostics) { public override string ToString() => string.Join(", ", diagnostics); } [LoggerMessage( Level = LogLevel.Debug, Message = "Creating '{RequestMessage}' status update with diagnostics '{Diagnostics}'" )] private static partial void LogCreatingStatusUpdate(ILogger logger, Message requestMessage, DiagnosticsLogValue diagnostics); } } ================================================ FILE: src/Orleans.Core/Messaging/MessageSerializer.cs ================================================ #nullable enable using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Orleans.Configuration; using Orleans.Networking.Shared; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Orleans.Serialization.Session; using static Orleans.Runtime.Message; namespace Orleans.Runtime.Messaging { internal sealed class MessageSerializer { private const int FramingLength = Message.LENGTH_HEADER_SIZE; private const int MessageSizeHint = 4096; private readonly Dictionary _rawResponseCodecs = []; private readonly CodecProvider _codecProvider; private readonly IFieldCodec _grainAddressCacheUpdateCodec; private readonly CachingSiloAddressCodec _readerSiloAddressCodec = new(); private readonly CachingSiloAddressCodec _writerSiloAddressCodec = new(); private readonly CachingIdSpanCodec _idSpanCodec = new(); private readonly SerializerSession _serializationSession; private readonly SerializerSession _deserializationSession; private readonly int _maxHeaderLength; private readonly int _maxBodyLength; private readonly DictionaryCodec _requestContextCodec; private readonly PrefixingBufferWriter _bufferWriter; public MessageSerializer( SerializerSessionPool sessionPool, SharedMemoryPool memoryPool, MessagingOptions options) { _serializationSession = sessionPool.GetSession(); _deserializationSession = sessionPool.GetSession(); _maxHeaderLength = options.MaxMessageHeaderSize; _maxBodyLength = options.MaxMessageBodySize; _codecProvider = sessionPool.CodecProvider; _requestContextCodec = OrleansGeneratedCodeHelper.GetService>(this, sessionPool.CodecProvider); _grainAddressCacheUpdateCodec = OrleansGeneratedCodeHelper.GetService>(this, sessionPool.CodecProvider); _bufferWriter = new(FramingLength, MessageSizeHint, memoryPool.Pool); } public (int RequiredBytes, int HeaderLength, int BodyLength) TryRead(ref ReadOnlySequence input, out Message? message) { if (input.Length < FramingLength) { message = default; return (FramingLength, 0, 0); } Span lengthBytes = stackalloc byte[FramingLength]; input.Slice(input.Start, FramingLength).CopyTo(lengthBytes); var headerLength = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes); var bodyLength = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes[4..]); // Check lengths ThrowIfLengthsInvalid(headerLength, bodyLength); var requiredBytes = FramingLength + headerLength + bodyLength; if (input.Length < requiredBytes) { message = default; return (requiredBytes, 0, 0); } try { // Decode header var header = input.Slice(FramingLength, headerLength); // Decode body int bodyOffset = FramingLength + headerLength; var body = input.Slice(bodyOffset, bodyLength); // Build message message = new(); if (header.IsSingleSegment) { var headersReader = Reader.Create(header.First.Span, _deserializationSession); Deserialize(ref headersReader, message); } else { var headersReader = Reader.Create(header, _deserializationSession); Deserialize(ref headersReader, message); } if (bodyLength != 0) { _deserializationSession.PartialReset(); // Body deserialization is more likely to fail than header deserialization. // Separating the two allows for these kinds of errors to be propagated back to the caller. if (body.IsSingleSegment) { var reader = Reader.Create(body.First.Span, _deserializationSession); ReadBodyObject(message, ref reader); } else { var reader = Reader.Create(body, _deserializationSession); ReadBodyObject(message, ref reader); } } return (0, headerLength, bodyLength); } finally { input = input.Slice(requiredBytes); _deserializationSession.Reset(); } } private void ReadBodyObject(Message message, ref Reader reader) { var field = reader.ReadFieldHeader(); if (message.Result == ResponseTypes.Success) { message.Result = ResponseTypes.None; // reset raw response indicator if (!_rawResponseCodecs.TryGetValue(field.FieldType, out var rawCodec)) rawCodec = GetRawCodec(field.FieldType); message.BodyObject = rawCodec.ReadRaw(ref reader, ref field); } else { var bodyCodec = _codecProvider.GetCodec(field.FieldType); message.BodyObject = bodyCodec.ReadValue(ref reader, field); } } private ResponseCodec GetRawCodec(Type fieldType) { var rawCodec = (ResponseCodec)_codecProvider.GetCodec(typeof(Response<>).MakeGenericType(fieldType)); _rawResponseCodecs.Add(fieldType, rawCodec); return rawCodec; } public (int HeaderLength, int BodyLength) Write(PipeWriter writer, Message message) { var headers = message.Headers; IFieldCodec? bodyCodec = null; ResponseCodec? rawCodec = null; if (message.BodyObject is not null) { bodyCodec = _codecProvider.GetCodec(message.BodyObject.GetType()); if (headers.ResponseType is ResponseTypes.None && bodyCodec is ResponseCodec responseCodec) { rawCodec = responseCodec; headers.ResponseType = ResponseTypes.Success; // indicates a raw simple response (not wrapped in Response) // The raw encoding changes the type encoded in the field header from Response to T // and does not encode a null reference value, but otherwise it's identical to normal encoding. } } try { var bufferWriter = _bufferWriter; bufferWriter.Init(writer); var innerWriter = Writer.Create(new MessageBufferWriter(bufferWriter), _serializationSession); Serialize(ref innerWriter, message, headers); innerWriter.Commit(); var headerLength = bufferWriter.CommittedBytes; _serializationSession.PartialReset(); if (bodyCodec is not null) { innerWriter = Writer.Create(new MessageBufferWriter(bufferWriter), _serializationSession); if (rawCodec != null) rawCodec.WriteRaw(ref innerWriter, message.BodyObject!); else bodyCodec.WriteField(ref innerWriter, 0, null, message.BodyObject); innerWriter.Commit(); } var bodyLength = bufferWriter.CommittedBytes - headerLength; // Before completing, check lengths ThrowIfLengthsInvalid(headerLength, bodyLength); // Write length prefixes, first header length then body length. var lengthFields = (headerLength, bodyLength); if (!BitConverter.IsLittleEndian) { lengthFields.headerLength = BinaryPrimitives.ReverseEndianness(headerLength); lengthFields.bodyLength = BinaryPrimitives.ReverseEndianness(bodyLength); } bufferWriter.Complete(MemoryMarshal.AsBytes(new Span<(int, int)>(ref lengthFields))); return (headerLength, bodyLength); } finally { _bufferWriter.Reset(); _serializationSession.Reset(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfLengthsInvalid(int headerLength, int bodyLength) { if (headerLength <= 0 || headerLength > _maxHeaderLength) ThrowInvalidHeaderLength(headerLength); if ((uint)bodyLength > (uint)_maxBodyLength) ThrowInvalidBodyLength(bodyLength); } private void ThrowInvalidHeaderLength(int headerLength) => throw new InvalidMessageFrameException($"Invalid header size: {headerLength} (max configured value is {_maxHeaderLength}, see {nameof(MessagingOptions.MaxMessageHeaderSize)})"); private void ThrowInvalidBodyLength(int bodyLength) => throw new InvalidMessageFrameException($"Invalid body size: {bodyLength} (max configured value is {_maxBodyLength}, see {nameof(MessagingOptions.MaxMessageBodySize)})"); private void Serialize(ref Writer writer, Message value, PackedHeaders headers) where TBufferWriter : IBufferWriter { writer.WriteUInt32((uint)headers); writer.WriteInt64(value.Id.ToInt64()); WriteGrainId(ref writer, value.SendingGrain); WriteGrainId(ref writer, value.TargetGrain); _writerSiloAddressCodec.WriteRaw(ref writer, value.SendingSilo); _writerSiloAddressCodec.WriteRaw(ref writer, value.TargetSilo); if (headers.HasFlag(MessageFlags.HasTimeToLive)) { writer.WriteInt32((int)value.GetTimeToLiveMilliseconds()); } if (headers.HasFlag(MessageFlags.HasInterfaceType)) { _idSpanCodec.WriteRaw(ref writer, value.InterfaceType.Value); } if (headers.HasFlag(MessageFlags.HasInterfaceVersion)) { writer.WriteVarUInt32(value.InterfaceVersion); } if (headers.HasFlag(MessageFlags.HasCacheInvalidationHeader)) { WriteCacheInvalidationHeaders(ref writer, value); } // Always write RequestContext last if (headers.HasFlag(MessageFlags.HasRequestContextData)) { WriteRequestContext(ref writer, value.RequestContextData!); } } private void Deserialize(ref Reader reader, Message result) { var headers = (PackedHeaders)reader.ReadUInt32(); result.Headers = headers; result.Id = new CorrelationId(reader.ReadInt64()); result.SendingGrain = ReadGrainId(ref reader); result.TargetGrain = ReadGrainId(ref reader); result.SendingSilo = _readerSiloAddressCodec.ReadRaw(ref reader); result.TargetSilo = _readerSiloAddressCodec.ReadRaw(ref reader); if (headers.HasFlag(MessageFlags.HasTimeToLive)) { result.SetTimeToLiveMilliseconds(reader.ReadInt32()); } else { result.SetInfiniteTimeToLive(); } if (headers.HasFlag(MessageFlags.HasInterfaceType)) { var interfaceTypeSpan = _idSpanCodec.ReadRaw(ref reader); result.InterfaceType = new GrainInterfaceType(interfaceTypeSpan); } if (headers.HasFlag(MessageFlags.HasInterfaceVersion)) { result.InterfaceVersion = (ushort)reader.ReadVarUInt32(); } if (headers.HasFlag(MessageFlags.HasCacheInvalidationHeader)) { result.CacheInvalidationHeader = ReadCacheInvalidationHeaders(ref reader); } if (headers.HasFlag(MessageFlags.HasRequestContextData)) { result.RequestContextData = ReadRequestContext(ref reader); } } internal List ReadCacheInvalidationHeaders(ref Reader reader) { var n = (int)reader.ReadVarUInt32(); if (n > 0) { var list = new List(n); for (int i = 0; i < n; i++) { list.Add(_grainAddressCacheUpdateCodec.ReadValue(ref reader, reader.ReadFieldHeader())); } return list; } return []; } internal void WriteCacheInvalidationHeaders(ref Writer writer, Message message) where TBufferWriter : IBufferWriter { // Lock during enumeration to avoid concurrent modifications. // The list can be modified by other threads after the message is queued for sending. var cacheUpdates = message.CacheInvalidationHeader; if (cacheUpdates is null) { writer.WriteVarUInt32(0u); } else { lock (cacheUpdates) { writer.WriteVarUInt32((uint)cacheUpdates.Count); foreach (var entry in cacheUpdates) { _grainAddressCacheUpdateCodec.WriteField(ref writer, 0, typeof(GrainAddressCacheUpdate), entry); } } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string? ReadString(ref Reader reader) { var length = (int)reader.ReadVarUInt32() - 1; if (length <= 0) { if (length < 0) { return null; } return string.Empty; } return StringCodec.ReadRaw(ref reader, (uint)length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteString(ref Writer writer, string value) where TBufferWriter : IBufferWriter { if (value is null) { writer.WriteByte(1); // Equivalent to `writer.WriteVarUInt32(0);` return; } var numBytes = Encoding.UTF8.GetByteCount(value); writer.WriteVarUInt32((uint)numBytes + 1); StringCodec.WriteRaw(ref writer, value, numBytes); } private static void WriteRequestContext(ref Writer writer, Dictionary value) where TBufferWriter : IBufferWriter { writer.WriteVarUInt32((uint)value.Count); foreach (var entry in value) { WriteString(ref writer, entry.Key); ObjectCodec.WriteField(ref writer, 0, entry.Value); } } private static Dictionary ReadRequestContext(ref Reader reader) { var size = (int)reader.ReadVarUInt32(); var result = new Dictionary(size); for (var i = 0; i < size; i++) { var key = ReadString(ref reader); var value = ObjectCodec.ReadValue(ref reader, reader.ReadFieldHeader()); Debug.Assert(key is not null); result.Add(key, value); } return result; } private GrainId ReadGrainId(ref Reader reader) { var grainType = _idSpanCodec.ReadRaw(ref reader); var grainKey = IdSpanCodec.ReadRaw(ref reader); return new GrainId(new GrainType(grainType), grainKey); } private void WriteGrainId(ref Writer writer, GrainId value) where TBufferWriter : IBufferWriter { _idSpanCodec.WriteRaw(ref writer, value.Type.Value); IdSpanCodec.WriteRaw(ref writer, value.Key); } } internal readonly struct MessageBufferWriter : IBufferWriter { private readonly PrefixingBufferWriter _buffer; public MessageBufferWriter(PrefixingBufferWriter buffer) => _buffer = buffer; public void Advance(int count) => _buffer.Advance(count); public Memory GetMemory(int sizeHint = 0) => _buffer.GetMemory(sizeHint); public Span GetSpan(int sizeHint = 0) => _buffer.GetSpan(sizeHint); } } ================================================ FILE: src/Orleans.Core/Messaging/OverloadDetectionLogic.cs ================================================ using Orleans.Configuration; using Orleans.Statistics; namespace Orleans.Core.Messaging; internal static class OverloadDetectionLogic { /// /// Determines whether or not the process is overloaded. /// /// is ignored here. public static bool IsOverloaded(ref readonly EnvironmentStatistics statistics, LoadSheddingOptions options) { bool isMemoryOverloaded = statistics.MemoryUsagePercentage > options.MemoryThreshold; bool isCpuOverloaded = statistics.FilteredCpuUsagePercentage > options.CpuThreshold; return isMemoryOverloaded || isCpuOverloaded; } } ================================================ FILE: src/Orleans.Core/Messaging/PrefixingBufferWriter.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.CompilerServices; namespace Orleans.Runtime.Messaging { /// /// An that reserves some fixed size for a header. /// /// /// This type is used for inserting the length of list in the header when the length is not known beforehand. /// It is optimized to minimize or avoid copying. /// internal sealed class PrefixingBufferWriter : IBufferWriter, IDisposable { private readonly MemoryPool memoryPool; /// /// The length of the header. /// private readonly int expectedPrefixSize; /// /// A hint from our owner at the size of the payload that follows the header. /// private readonly int payloadSizeHint; /// /// The underlying buffer writer. /// private PipeWriter innerWriter; /// /// The memory reserved for the header from the . /// This memory is not reserved until the first call from this writer to acquire memory. /// private Memory prefixMemory; /// /// The memory acquired from . /// This memory is not reserved until the first call from this writer to acquire memory. /// private Memory realMemory; /// /// The number of elements written to a buffer belonging to . /// private int advanced; /// /// The fallback writer to use when the caller writes more than we allowed for given the /// in anything but the initial call to . /// private Sequence privateWriter; private int _committedBytes; /// /// Initializes a new instance of the class. /// /// The length of the header to reserve space for. Must be a positive number. /// A hint at the expected max size of the payload. The real size may be more or less than this, but additional copying is avoided if it does not exceed this amount. If 0, a reasonable guess is made. /// public PrefixingBufferWriter(int prefixSize, int payloadSizeHint, MemoryPool memoryPool) { if (prefixSize <= 0) { ThrowPrefixSize(); } this.expectedPrefixSize = prefixSize; this.payloadSizeHint = payloadSizeHint; this.memoryPool = memoryPool; static void ThrowPrefixSize() => throw new ArgumentOutOfRangeException(nameof(prefixSize)); } public int CommittedBytes => _committedBytes; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int count) { if (privateWriter == null) { advanced += count; _committedBytes += count; } else { AdvancePrivateWriter(count); } } [MethodImpl(MethodImplOptions.NoInlining)] private void AdvancePrivateWriter(int count) { privateWriter.Advance(count); _committedBytes += count; } /// public Memory GetMemory(int sizeHint = 0) { if (privateWriter == null) { if (prefixMemory.IsEmpty) Initialize(sizeHint); var res = realMemory[advanced..]; if (!res.IsEmpty && (uint)sizeHint <= (uint)res.Length) return res; privateWriter = new(memoryPool); } return privateWriter.GetMemory(sizeHint); } /// public Span GetSpan(int sizeHint = 0) { if (privateWriter == null) { var res = realMemory.Span[advanced..]; if (!res.IsEmpty && (uint)sizeHint <= (uint)res.Length) return res; } return GetMemory(sizeHint).Span; } /// /// Inserts the prefix and commits the payload to the underlying . /// /// The prefix to write in. The length must match the one given in the constructor. public void Complete(ReadOnlySpan prefix) { if (prefix.Length != this.expectedPrefixSize) { ThrowPrefixLength(); static void ThrowPrefixLength() => throw new ArgumentOutOfRangeException(nameof(prefix), "Prefix was not expected length."); } if (this.prefixMemory.Length == 0) { // No payload was actually written, and we never requested memory, so just write it out. this.innerWriter.Write(prefix); } else { // Payload has been written, so write in the prefix then commit the payload. prefix.CopyTo(this.prefixMemory.Span); this.innerWriter.Advance(prefix.Length + this.advanced); if (this.privateWriter != null) CompletePrivateWriter(); } } private void CompletePrivateWriter() { var sequence = privateWriter.AsReadOnlySequence; var sequenceLength = checked((int)sequence.Length); sequence.CopyTo(innerWriter.GetSpan(sequenceLength)); innerWriter.Advance(sequenceLength); } /// /// Sets this instance to a usable state. /// /// The underlying writer that should ultimately receive the prefix and payload. public void Init(PipeWriter writer) => innerWriter = writer; /// /// Resets this instance to a reusable state. /// public void Reset() { privateWriter?.Dispose(); privateWriter = null; prefixMemory = default; realMemory = default; innerWriter = null; advanced = 0; _committedBytes = 0; } public void Dispose() { this.privateWriter?.Dispose(); } /// /// Makes the initial call to acquire memory from the underlying writer. /// /// The size requested by the caller to either or . private void Initialize(int sizeHint) { int sizeToRequest = this.expectedPrefixSize + Math.Max(sizeHint, this.payloadSizeHint); var memory = this.innerWriter.GetMemory(sizeToRequest); this.prefixMemory = memory[..this.expectedPrefixSize]; this.realMemory = memory[this.expectedPrefixSize..]; } /// /// Manages a sequence of elements, readily castable as a . /// /// /// Instance members are not thread-safe. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] private sealed class Sequence { private const int DefaultBufferSize = 4 * 1024; private readonly Stack segmentPool = new Stack(); private readonly MemoryPool memoryPool; private SequenceSegment first; private SequenceSegment last; /// /// Initializes a new instance of the class. /// /// The pool to use for recycling backing arrays. public Sequence(MemoryPool memoryPool) { if (memoryPool is null) ThrowNull(); this.memoryPool = memoryPool; static void ThrowNull() => throw new ArgumentNullException(nameof(memoryPool)); } /// /// Gets this sequence expressed as a . /// /// A read only sequence representing the data in this object. public ReadOnlySequence AsReadOnlySequence => first != null ? new(first, first.Start, last, last.End) : default; /// /// Gets the value to display in a debugger datatip. /// private string DebuggerDisplay => $"Length: {AsReadOnlySequence.Length}"; /// /// Advances the sequence to include the specified number of elements initialized into memory /// returned by a prior call to . /// /// The number of elements written into memory. public void Advance(int count) => last.Advance(count); /// /// Gets writable memory that can be initialized and added to the sequence via a subsequent call to . /// /// The size of the memory required, or 0 to just get a convenient (non-empty) buffer. /// The requested memory. public Memory GetMemory(int sizeHint) => last?.TrailingSlack is { Length: > 0 } slack && (uint)slack.Length >= (uint)sizeHint ? slack : Append(sizeHint); /// /// Clears the entire sequence, recycles associated memory into pools, /// and resets this instance for reuse. /// This invalidates any previously produced by this instance. /// public void Dispose() { var current = this.first; while (current != null) { current = this.RecycleAndGetNext(current); } this.first = this.last = null; } private Memory Append(int sizeHint) { var array = memoryPool.Rent(Math.Min(sizeHint > 0 ? sizeHint : DefaultBufferSize, memoryPool.MaxBufferSize)); var segment = this.segmentPool.Count > 0 ? this.segmentPool.Pop() : new SequenceSegment(); segment.SetMemory(array); if (this.last == null) { this.first = this.last = segment; } else { if (this.last.Length > 0) { // Add a new block. this.last.SetNext(segment); } else { // The last block is completely unused. Replace it instead of appending to it. var current = this.first; if (this.first != this.last) { while (current.Next != this.last) { current = current.Next; } } else { this.first = segment; } current.SetNext(segment); this.RecycleAndGetNext(this.last); } this.last = segment; } return segment.AvailableMemory; } private SequenceSegment RecycleAndGetNext(SequenceSegment segment) { var recycledSegment = segment; segment = segment.Next; recycledSegment.ResetMemory(); this.segmentPool.Push(recycledSegment); return segment; } private sealed class SequenceSegment : ReadOnlySequenceSegment { /// /// Gets the index of the first element in to consider part of the sequence. /// /// /// The represents the offset into where the range of "active" bytes begins. At the point when the block is leased /// the is guaranteed to be equal to 0. The value of may be assigned anywhere between 0 and /// .Length, and must be equal to or less than . /// internal int Start { get; private set; } /// /// Gets or sets the index of the element just beyond the end in to consider part of the sequence. /// /// /// The represents the offset into where the range of "active" bytes ends. At the point when the block is leased /// the is guaranteed to be equal to . The value of may be assigned anywhere between 0 and /// .Length, and must be equal to or less than . /// internal int End { get; private set; } internal Memory TrailingSlack => this.AvailableMemory[this.End..]; private IMemoryOwner MemoryOwner; internal Memory AvailableMemory; internal int Length => this.End - this.Start; internal new SequenceSegment Next { get => (SequenceSegment)base.Next; set => base.Next = value; } internal void SetMemory(IMemoryOwner memoryOwner) { this.MemoryOwner = memoryOwner; this.AvailableMemory = memoryOwner.Memory; } internal void ResetMemory() { this.MemoryOwner.Dispose(); this.MemoryOwner = null; this.AvailableMemory = default; this.Memory = default; this.Next = null; this.RunningIndex = 0; this.Start = 0; this.End = 0; } internal void SetNext(SequenceSegment segment) { segment.RunningIndex = this.RunningIndex + this.End; this.Next = segment; } public void Advance(int count) { if (count < 0) ThrowNegative(); var value = End + count; // If we ever support creating these instances on existing arrays, such that // this.Start isn't 0 at the beginning, we'll have to "pin" this.Start and remove // Advance, forcing Sequence itself to track it, the way Pipe does it internally. this.Memory = AvailableMemory[..value]; this.End = value; static void ThrowNegative() => throw new ArgumentOutOfRangeException( nameof(count), "Value must be greater than or equal to 0"); } } } } } ================================================ FILE: src/Orleans.Core/Messaging/RejectionResponse.cs ================================================ using System; namespace Orleans.Runtime { [Id(102), GenerateSerializer, Immutable] internal sealed class RejectionResponse { [Id(0)] public string RejectionInfo { get; init; } [Id(1)] public Message.RejectionTypes RejectionType { get; init; } [Id(2)] public Exception Exception { get; init; } } } ================================================ FILE: src/Orleans.Core/Messaging/StaticGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Messaging { /// /// implementation which returns a static list, configured via . /// public class StaticGatewayListProvider : IGatewayListProvider { private readonly StaticGatewayListProviderOptions options; private readonly TimeSpan maxStaleness; /// /// Initializes a new instance of the class. /// /// The specific options. /// The general gateway options. public StaticGatewayListProvider(IOptions options, IOptions gatewayOptions) { this.options = options.Value; this.maxStaleness = gatewayOptions.Value.GatewayListRefreshPeriod; } /// public Task InitializeGatewayListProvider() => Task.CompletedTask; /// public Task> GetGateways() => Task.FromResult>(this.options.Gateways); /// public TimeSpan MaxStaleness { get => this.maxStaleness; } /// public bool IsUpdatable { get => true; } } } ================================================ FILE: src/Orleans.Core/Messaging/StaticGatewayListProviderBuilder.cs ================================================ using System.Collections.Generic; using System.Net; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("Development", "Clustering", "Client", typeof(StaticGatewayListProviderBuilder))] [assembly: RegisterProvider("Static", "Clustering", "Client", typeof(StaticGatewayListProviderBuilder))] namespace Orleans.Providers; internal sealed class StaticGatewayListProviderBuilder : IProviderBuilder { public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { var endpoints = new List(); var gatewaysSection = configurationSection.GetSection("Gateways"); foreach (var child in gatewaysSection.GetChildren()) { if (IPEndPoint.TryParse(child.Value, out var ep)) { endpoints.Add(ep); } } builder.UseStaticClustering([.. endpoints]); } } ================================================ FILE: src/Orleans.Core/Messaging/StatusResponse.cs ================================================ using System; using System.Collections.Generic; namespace Orleans.Runtime { [Id(103), Serializable, GenerateSerializer, Immutable] internal sealed class StatusResponse { [Id(0)] private readonly uint _statusFlags; public StatusResponse(bool isExecuting, bool isWaiting, List diagnostics) { if (isExecuting) _statusFlags |= 0x1; if (isWaiting) _statusFlags |= 0x2; Diagnostics = diagnostics; } [Id(1)] public List Diagnostics { get; } public bool IsExecuting => (_statusFlags & 0x1) != 0; public bool IsWaiting => (_statusFlags & 0x2) != 0; public override string ToString() => $"IsExecuting: {IsExecuting}, IsWaiting: {IsWaiting}, Diagnostics: [{string.Join(", ", this.Diagnostics)}]"; } } ================================================ FILE: src/Orleans.Core/Networking/ClientConnectionOptions.cs ================================================ using System; using Microsoft.AspNetCore.Connections; namespace Orleans.Configuration { /// /// Options for clients connections. /// public class ClientConnectionOptions { private readonly ConnectionBuilderDelegates delegates = new ConnectionBuilderDelegates(); /// /// Adds a connection configuration delegate. /// /// The configuration delegate. public void ConfigureConnection(Action configure) => this.delegates.Add(configure); /// /// Configures the provided connection builder using these options. /// /// The connection builder. internal void ConfigureConnectionBuilder(IConnectionBuilder builder) => this.delegates.Invoke(builder); } } ================================================ FILE: src/Orleans.Core/Networking/ClientOutboundConnection.cs ================================================ using System; using System.Diagnostics; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Messaging; namespace Orleans.Runtime.Messaging { internal sealed partial class ClientOutboundConnection : Connection { private readonly ClientMessageCenter messageCenter; private readonly ConnectionManager connectionManager; private readonly ConnectionOptions connectionOptions; private readonly ClusterOptions clusterOptions; private readonly ConnectionPreambleHelper connectionPreambleHelper; public ClientOutboundConnection( SiloAddress remoteSiloAddress, ConnectionContext connection, ConnectionDelegate middleware, ClientMessageCenter messageCenter, ConnectionManager connectionManager, ConnectionOptions connectionOptions, ConnectionCommon connectionShared, ConnectionPreambleHelper connectionPreambleHelper, ClusterOptions clusterOptions) : base(connection, middleware, connectionShared) { this.messageCenter = messageCenter; this.connectionManager = connectionManager; this.connectionOptions = connectionOptions; this.connectionPreambleHelper = connectionPreambleHelper; this.clusterOptions = clusterOptions; this.RemoteSiloAddress = remoteSiloAddress ?? throw new ArgumentNullException(nameof(remoteSiloAddress)); } public SiloAddress RemoteSiloAddress { get; } protected override ConnectionDirection ConnectionDirection => ConnectionDirection.ClientToGateway; protected override IMessageCenter MessageCenter => this.messageCenter; protected override void RecordMessageReceive(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageReceive(msg, numTotalBytes, headerBytes, ConnectionDirection, RemoteSiloAddress); } protected override void RecordMessageSend(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageSend(msg, numTotalBytes, headerBytes, ConnectionDirection, RemoteSiloAddress); } protected override void OnReceivedMessage(Message message) { this.messageCenter.DispatchLocalMessage(message); } protected override async Task RunInternal() { Exception error = default; try { this.messageCenter.OnGatewayConnectionOpen(); var myClusterId = clusterOptions.ClusterId; await connectionPreambleHelper.Write( this.Context, new ConnectionPreamble { NetworkProtocolVersion = this.connectionOptions.ProtocolVersion, NodeIdentity = this.messageCenter.ClientId.GrainId, SiloAddress = null, ClusterId = myClusterId }); var preamble = await connectionPreambleHelper.Read(this.Context); LogInformationEstablishedConnection(this.Log, preamble.SiloAddress, preamble.NetworkProtocolVersion.ToString()); if (preamble.ClusterId != myClusterId) { throw new InvalidOperationException($@"Unexpected cluster id ""{preamble.ClusterId}"", expected ""{myClusterId}"""); } await base.RunInternal(); } catch (Exception exception) when ((error = exception) is null) { Debug.Fail("Execution should not be able to reach this point."); } finally { this.connectionManager.OnConnectionTerminated(this.RemoteSiloAddress, this, error); this.messageCenter.OnGatewayConnectionClosed(); } } protected override bool PrepareMessageForSend(Message msg) { // Check to make sure we're not stopped if (!this.IsValid) { // Recycle the message we've dequeued. Note that this will recycle messages that were queued up to be sent when the gateway connection is declared dead msg.TargetSilo = null; this.messageCenter.SendMessage(msg); return false; } if (msg.TargetSilo != null) return true; msg.TargetSilo = this.RemoteSiloAddress; return true; } protected override void RetryMessage(Message msg, Exception ex = null) { if (msg == null) return; if (msg.RetryCount < MessagingOptions.DEFAULT_MAX_MESSAGE_SEND_RETRIES) { ++msg.RetryCount; this.messageCenter.SendMessage(msg); } else { var reason = new StringBuilder("Retry count exceeded. "); if (ex != null) { reason.Append("Original exception is: ").Append(ex.ToString()); } reason.Append("Msg is: ").Append(msg); FailMessage(msg, reason.ToString()); } } internal void SendRejection(Message msg, Message.RejectionTypes rejectionType, string reason) { MessagingInstruments.OnRejectedMessage(msg); if (string.IsNullOrEmpty(reason)) reason = "Rejection from silo - Unknown reason."; var error = this.MessageFactory.CreateRejectionResponse(msg, rejectionType, reason); // rejection msgs are always originated locally, they are never remote. this.OnReceivedMessage(error); } public void FailMessage(Message msg, string reason) { MessagingInstruments.OnFailedSentMessage(msg); if (msg.Direction == Message.Directions.Request) { LogDebugClientIsRejectingMessage(this.Log, msg, reason); // Done retrying, send back an error instead this.SendRejection(msg, Message.RejectionTypes.Transient, $"Client is rejecting message: {msg}. Reason = {reason}"); } else { LogInformationClientIsDroppingMessage(this.Log, msg, reason); MessagingInstruments.OnDroppedSentMessage(msg); } } protected override void OnSendMessageFailure(Message message, string error) { message.TargetSilo = null; this.messageCenter.SendMessage(message); } [LoggerMessage( Level = LogLevel.Information, Message = "Established connection to {Silo} with protocol version {ProtocolVersion}" )] private static partial void LogInformationEstablishedConnection(ILogger logger, SiloAddress silo, string protocolVersion); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.MessagingSendingRejection, Message = "Client is rejecting message: {Message}. Reason = {Reason}" )] private static partial void LogDebugClientIsRejectingMessage(ILogger logger, Message message, string reason); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.Messaging_OutgoingMS_DroppingMessage, Message = "Client is dropping message: {Message}. Reason = {Reason}" )] private static partial void LogInformationClientIsDroppingMessage(ILogger logger, Message message, string reason); } } ================================================ FILE: src/Orleans.Core/Networking/ClientOutboundConnectionFactory.cs ================================================ using System.Threading; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Messaging; namespace Orleans.Runtime.Messaging { internal sealed class ClientOutboundConnectionFactory : ConnectionFactory { internal static readonly object ServicesKey = new object(); private readonly ConnectionCommon connectionShared; private readonly ClientConnectionOptions clientConnectionOptions; private readonly ClusterOptions clusterOptions; private readonly ConnectionPreambleHelper connectionPreambleHelper; #if NET9_0_OR_GREATER private readonly Lock initializationLock = new(); #else private readonly object initializationLock = new(); #endif private volatile bool isInitialized; private ClientMessageCenter messageCenter; private ConnectionManager connectionManager; public ClientOutboundConnectionFactory( IOptions connectionOptions, IOptions clientConnectionOptions, IOptions clusterOptions, ConnectionCommon connectionShared, ConnectionPreambleHelper connectionPreambleHelper) : base(connectionShared.ServiceProvider.GetRequiredKeyedService(ServicesKey), connectionShared.ServiceProvider, connectionOptions) { this.connectionShared = connectionShared; this.clientConnectionOptions = clientConnectionOptions.Value; this.clusterOptions = clusterOptions.Value; this.connectionPreambleHelper = connectionPreambleHelper; } protected override Connection CreateConnection(SiloAddress address, ConnectionContext context) { EnsureInitialized(); return new ClientOutboundConnection( address, context, this.ConnectionDelegate, this.messageCenter, this.connectionManager, this.ConnectionOptions, this.connectionShared, this.connectionPreambleHelper, this.clusterOptions); } protected override void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { this.clientConnectionOptions.ConfigureConnectionBuilder(connectionBuilder); base.ConfigureConnectionBuilder(connectionBuilder); } private void EnsureInitialized() { if (!isInitialized) { lock (this.initializationLock) { if (!isInitialized) { this.messageCenter = this.connectionShared.ServiceProvider.GetRequiredService(); this.connectionManager = this.connectionShared.ServiceProvider.GetRequiredService(); this.isInitialized = true; } } } } } } ================================================ FILE: src/Orleans.Core/Networking/Connection.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Serialization.Invocation; namespace Orleans.Runtime.Messaging { internal abstract partial class Connection { private static readonly Func OnConnectedDelegate = context => OnConnectedAsync(context); private static readonly Action OnConnectionClosedDelegate = state => ((Connection)state).OnTransportConnectionClosed(); private static readonly UnboundedChannelOptions OutgoingMessageChannelOptions = new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }; private static readonly ObjectPool MessageHandlerPool = ObjectPool.Create(new MessageHandlerPoolPolicy()); private readonly ConnectionCommon shared; private readonly ConnectionDelegate middleware; private readonly Channel outgoingMessages; private readonly ChannelWriter outgoingMessageWriter; private readonly List inflight = new List(4); private readonly TaskCompletionSource _transportConnectionClosed = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly TaskCompletionSource _initializationTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); private IDuplexPipe _transport; private Task _processIncomingTask; private Task _processOutgoingTask; private Task _closeTask; protected Connection( ConnectionContext connection, ConnectionDelegate middleware, ConnectionCommon shared) { this.Context = connection ?? throw new ArgumentNullException(nameof(connection)); this.middleware = middleware ?? throw new ArgumentNullException(nameof(middleware)); this.shared = shared; this.outgoingMessages = Channel.CreateUnbounded(OutgoingMessageChannelOptions); this.outgoingMessageWriter = this.outgoingMessages.Writer; // Set the connection on the connection context so that it can be retrieved by the middleware. this.Context.Features.Set(this); this.RemoteEndPoint = NormalizeEndpoint(this.Context.RemoteEndPoint); this.LocalEndPoint = NormalizeEndpoint(this.Context.LocalEndPoint); } public ConnectionCommon Shared => shared; public string ConnectionId => this.Context?.ConnectionId; public virtual EndPoint RemoteEndPoint { get; } public virtual EndPoint LocalEndPoint { get; } protected ConnectionContext Context { get; } protected NetworkingTrace Log => this.shared.NetworkingTrace; protected MessagingTrace MessagingTrace => this.shared.MessagingTrace; protected abstract ConnectionDirection ConnectionDirection { get; } protected MessageFactory MessageFactory => this.shared.MessageFactory; protected abstract IMessageCenter MessageCenter { get; } public bool IsValid => _closeTask is null; public Task Initialized => _initializationTcs.Task; public static void ConfigureBuilder(ConnectionBuilder builder) => builder.Run(OnConnectedDelegate); /// /// Start processing this connection. /// /// A which completes when the connection terminates and has completed processing. public async Task Run() { Exception error = default; try { // Eventually calls through to OnConnectedAsync (unless the connection delegate has been misconfigured) await this.middleware(this.Context); } catch (Exception exception) { error = exception; } finally { await this.CloseAsync(error); } } private static Task OnConnectedAsync(ConnectionContext context) { var connection = context.Features.Get(); context.ConnectionClosed.Register(OnConnectionClosedDelegate, connection); NetworkingInstruments.OnOpenedSocket(connection.ConnectionDirection); return connection.RunInternal(); } protected virtual async Task RunInternal() { _transport = this.Context.Transport; _processIncomingTask = this.ProcessIncoming(); _processOutgoingTask = this.ProcessOutgoing(); _initializationTcs.TrySetResult(0); await Task.WhenAll(_processIncomingTask, _processOutgoingTask); } /// /// Called immediately prior to transporting a message. /// /// /// Whether or not to continue transporting the message. protected abstract bool PrepareMessageForSend(Message msg); protected abstract void RetryMessage(Message msg, Exception ex = null); public Task CloseAsync(Exception exception) { StartClosing(exception); return _closeTask; } private void OnTransportConnectionClosed() { StartClosing(new ConnectionAbortedException("Underlying connection closed")); _transportConnectionClosed.SetResult(0); } private void StartClosing(Exception exception) { if (_closeTask is not null) { return; } var task = new Task(CloseAsync); if (Interlocked.CompareExchange(ref _closeTask, task.Unwrap(), null) is not null) { return; } _initializationTcs.TrySetException(exception ?? new ConnectionAbortedException("Connection initialization failed")); _initializationTcs.Task.Ignore(); LogInformationClosingConnection(this.Log, exception, this); task.Start(TaskScheduler.Default); } /// /// Close the connection. This method should only be called by . /// private async Task CloseAsync() { NetworkingInstruments.OnClosedSocket(this.ConnectionDirection); // Signal the outgoing message processor to exit gracefully. this.outgoingMessageWriter.TryComplete(); var transportFeature = Context.Features.Get(); var transport = transportFeature?.Transport ?? _transport; transport.Input.CancelPendingRead(); transport.Output.CancelPendingFlush(); // Try to gracefully stop the reader/writer loops, if they are running. if (_processIncomingTask is { IsCompleted: false } incoming) { try { await incoming; } catch (Exception processIncomingException) { // Swallow any exceptions here. LogWarningExceptionProcessingIncomingMessages(this.Log, processIncomingException, this); } } if (_processOutgoingTask is { IsCompleted: false } outgoing) { try { await outgoing; } catch (Exception processOutgoingException) { // Swallow any exceptions here. LogWarningExceptionProcessingOutgoingMessages(this.Log, processOutgoingException, this); } } // Only wait for the transport to close if the connection actually started being processed. if (_processIncomingTask is not null && _processOutgoingTask is not null) { // Abort the connection and wait for the transport to signal that it's closed before disposing it. try { this.Context.Abort(); } catch (Exception exception) { LogWarningExceptionAbortingConnection(this.Log, exception, this); } await _transportConnectionClosed.Task; } try { await this.Context.DisposeAsync(); } catch (Exception abortException) { // Swallow any exceptions here. LogWarningExceptionTerminatingConnection(this.Log, abortException, this); } // Reject in-flight messages. foreach (var message in this.inflight) { this.OnSendMessageFailure(message, "Connection terminated"); } this.inflight.Clear(); // Reroute enqueued messages. var i = 0; while (this.outgoingMessages.Reader.TryRead(out var message)) { if (i == 0) { LogInformationReroutingMessages(this.Log, new EndPointLogValue(this.RemoteEndPoint)); } ++i; this.RetryMessage(message); } if (i > 0) { LogInformationReroutedMessages(this.Log, i, new EndPointLogValue(this.RemoteEndPoint)); } } public virtual void Send(Message message) { Debug.Assert(!message.IsLocalOnly); if (!this.outgoingMessageWriter.TryWrite(message)) { this.RerouteMessage(message); } } public override string ToString() => $"[Local: {this.LocalEndPoint}, Remote: {this.RemoteEndPoint}, ConnectionId: {this.Context.ConnectionId}]"; protected abstract void RecordMessageReceive(Message msg, int numTotalBytes, int headerBytes); protected abstract void RecordMessageSend(Message msg, int numTotalBytes, int headerBytes); protected abstract void OnReceivedMessage(Message message); protected abstract void OnSendMessageFailure(Message message, string error); private async Task ProcessIncoming() { await Task.Yield(); Exception error = default; var serializer = this.shared.ServiceProvider.GetRequiredService(); try { var input = this._transport.Input; var requiredBytes = 0; while (true) { var readResult = await input.ReadAsync(); var buffer = readResult.Buffer; if (buffer.Length >= requiredBytes) { do { Message message = default; try { int headerLength, bodyLength; (requiredBytes, headerLength, bodyLength) = serializer.TryRead(ref buffer, out message); if (requiredBytes == 0) { Debug.Assert(message is not null); RecordMessageReceive(message, bodyLength + headerLength, headerLength); var handler = MessageHandlerPool.Get(); handler.Set(message, this); ThreadPool.UnsafeQueueUserWorkItem(handler, preferLocal: true); } } catch (Exception exception) { if (!HandleReceiveMessageFailure(message, exception)) { throw; } } } while (requiredBytes == 0); } if (readResult.IsCanceled || readResult.IsCompleted) { break; } input.AdvanceTo(buffer.Start, buffer.End); } } catch (Exception exception) { if (IsValid) { LogWarningExceptionProcessingMessagesFromRemote(this.Log, exception, this.RemoteEndPoint); } error = exception; } finally { _transport.Input.Complete(); this.StartClosing(error); } } private async Task ProcessOutgoing() { await Task.Yield(); Exception error = default; var serializer = this.shared.ServiceProvider.GetRequiredService(); var messageObserver = this.shared.MessageStatisticsSink.GetMessageObserver(); try { var output = this._transport.Output; var reader = this.outgoingMessages.Reader; while (true) { var more = await reader.WaitToReadAsync(); if (!more) { break; } Message message = default; try { while (inflight.Count < inflight.Capacity && reader.TryRead(out message) && this.PrepareMessageForSend(message)) { inflight.Add(message); var (headerLength, bodyLength) = serializer.Write(output, message); RecordMessageSend(message, headerLength + bodyLength, headerLength); messageObserver?.Invoke(message); message = null; } } catch (Exception exception) { if (!HandleSendMessageFailure(message, exception)) { throw; } } var flushResult = await output.FlushAsync(); if (flushResult.IsCompleted || flushResult.IsCanceled) { break; } inflight.Clear(); } } catch (Exception exception) { if (IsValid) { LogWarningExceptionProcessingMessagesToRemote(this.Log, exception, this.RemoteEndPoint); } error = exception; } finally { _transport.Output.Complete(); this.StartClosing(error); } } private void RerouteMessage(Message message) { LogInformationReroutingMessage(this.Log, message, new EndPointLogValue(this.RemoteEndPoint)); ThreadPool.UnsafeQueueUserWorkItem(state => { var (t, msg) = ((Connection, Message))state; t.RetryMessage(msg); }, (this, message)); } private static EndPoint NormalizeEndpoint(EndPoint endpoint) { if (!(endpoint is IPEndPoint ep)) return endpoint; // Normalize endpoints if (ep.Address.IsIPv4MappedToIPv6) { return new IPEndPoint(ep.Address.MapToIPv4(), ep.Port); } return ep; } /// /// Handles a message receive failure. /// /// if the exception should not be caught and if it should be caught. private bool HandleReceiveMessageFailure(Message message, Exception exception) { LogErrorExceptionReadingMessage(this.Log, exception, message, this.RemoteEndPoint, this.LocalEndPoint); // If deserialization completely failed, rethrow the exception so that it can be handled at another level. if (message is null || exception is InvalidMessageFrameException) { // Returning false here informs the caller that the exception should not be caught. return false; } // The message body was not successfully decoded, but the headers were. MessagingInstruments.OnRejectedMessage(message); if (message.HasDirection) { if (message.Direction == Message.Directions.Request) { // Send a fast fail to the caller. var response = this.MessageFactory.CreateResponseMessage(message); response.Result = Message.ResponseTypes.Error; response.BodyObject = Response.FromException(exception); // Send the error response and continue processing the next message. this.Send(response); } else if (message.Direction == Message.Directions.Response) { // If the message was a response, propagate the exception to the intended recipient. message.Result = Message.ResponseTypes.Error; message.BodyObject = Response.FromException(exception); this.OnReceivedMessage(message); } } // The exception has been handled by propagating it onwards. return true; } private bool HandleSendMessageFailure(Message message, Exception exception) { // We get here if we failed to serialize the msg (or any other catastrophic failure). // Request msg fails to serialize on the sender, so we just enqueue a rejection msg. // Response msg fails to serialize on the responding silo, so we try to send an error response back. LogErrorExceptionSendingMessage(this.Log, exception, message, this.RemoteEndPoint, this.LocalEndPoint); if (message is null || exception is InvalidMessageFrameException) { // Returning false here informs the caller that the exception should not be caught. return false; } MessagingInstruments.OnFailedSentMessage(message); if (message.Direction == Message.Directions.Request) { var response = this.MessageFactory.CreateResponseMessage(message); response.Result = Message.ResponseTypes.Error; response.BodyObject = Response.FromException(exception); this.MessageCenter.DispatchLocalMessage(response); } else if (message.Direction == Message.Directions.Response && message.RetryCount < MessagingOptions.DEFAULT_MAX_MESSAGE_SEND_RETRIES) { // If we failed sending an original response, turn the response body into an error and reply with it. // unless we have already tried sending the response multiple times. message.Result = Message.ResponseTypes.Error; message.BodyObject = Response.FromException(exception); ++message.RetryCount; this.Send(message); } else { LogWarningDroppingMessage( this.Log, exception, message); MessagingInstruments.OnDroppedSentMessage(message); } return true; } private sealed class MessageHandlerPoolPolicy : PooledObjectPolicy { public override MessageHandler Create() => new MessageHandler(); public override bool Return(MessageHandler obj) { obj.Reset(); return true; } } private sealed class MessageHandler : IThreadPoolWorkItem { private Message message; private Connection connection; public void Set(Message m, Connection c) { this.message = m; this.connection = c; } public void Execute() { this.connection.OnReceivedMessage(this.message); MessageHandlerPool.Return(this); } public void Reset() { this.message = null; this.connection = null; } } private readonly struct EndPointLogValue(EndPoint endPoint) { public override string ToString() => endPoint?.ToString() ?? "(never connected)"; } [LoggerMessage( Level = LogLevel.Information, Message = "Closing connection {Connection}" )] private static partial void LogInformationClosingConnection(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception processing incoming messages on connection {Connection}" )] private static partial void LogWarningExceptionProcessingIncomingMessages(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception processing outgoing messages on connection {Connection}" )] private static partial void LogWarningExceptionProcessingOutgoingMessages(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception aborting connection {Connection}" )] private static partial void LogWarningExceptionAbortingConnection(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception terminating connection {Connection}" )] private static partial void LogWarningExceptionTerminatingConnection(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Information, Message = "Rerouting messages for remote endpoint {EndPoint}" )] private static partial void LogInformationReroutingMessages(ILogger logger, EndPointLogValue endPoint); [LoggerMessage( Level = LogLevel.Information, Message = "Rerouted {Count} messages for remote endpoint {EndPoint}" )] private static partial void LogInformationReroutedMessages(ILogger logger, int count, EndPointLogValue endPoint); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception while processing messages from remote endpoint {EndPoint}" )] private static partial void LogWarningExceptionProcessingMessagesFromRemote(ILogger logger, Exception exception, EndPoint endPoint); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception while processing messages to remote endpoint {EndPoint}" )] private static partial void LogWarningExceptionProcessingMessagesToRemote(ILogger logger, Exception exception, EndPoint endPoint); [LoggerMessage( Level = LogLevel.Information, Message = "Rerouting message {Message} from remote endpoint {EndPoint}" )] private static partial void LogInformationReroutingMessage(ILogger logger, Message message, EndPointLogValue endPoint); [LoggerMessage( Level = LogLevel.Error, Message = "Exception reading message {Message} from remote endpoint {Remote} to local endpoint {Local}" )] private static partial void LogErrorExceptionReadingMessage(ILogger logger, Exception exception, Message message, EndPoint remote, EndPoint local); [LoggerMessage( Level = LogLevel.Error, Message = "Exception sending message {Message} to remote endpoint {Remote} from local endpoint {Local}" )] private static partial void LogErrorExceptionSendingMessage(ILogger logger, Exception exception, Message message, EndPoint remote, EndPoint local); [LoggerMessage( EventId = (int)ErrorCode.Messaging_OutgoingMS_DroppingMessage, Level = LogLevel.Warning, Message = "Dropping message which failed during serialization: {Message}" )] private static partial void LogWarningDroppingMessage(ILogger logger, Exception exception, Message message); } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionBuilderDelegates.cs ================================================ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Connections; namespace Orleans.Configuration { internal class ConnectionBuilderDelegates { private readonly List> configurationDelegates = new List>(); public void Add(Action configure) => this.configurationDelegates.Add(configure ?? throw new ArgumentNullException(nameof(configure))); public void Invoke(IConnectionBuilder builder) { foreach (var configureDelegate in this.configurationDelegates) { configureDelegate(builder); } } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionFactory.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Messaging { internal abstract class ConnectionFactory { private readonly IConnectionFactory connectionFactory; private readonly IServiceProvider serviceProvider; private ConnectionDelegate connectionDelegate; protected ConnectionFactory( IConnectionFactory connectionFactory, IServiceProvider serviceProvider, IOptions connectionOptions) { this.connectionFactory = connectionFactory; this.serviceProvider = serviceProvider; this.ConnectionOptions = connectionOptions.Value; } protected ConnectionOptions ConnectionOptions { get; } protected ConnectionDelegate ConnectionDelegate { get { if (this.connectionDelegate != null) return this.connectionDelegate; lock (this) { if (this.connectionDelegate != null) return this.connectionDelegate; // Configure the connection builder using the user-defined options. var connectionBuilder = new ConnectionBuilder(this.serviceProvider); connectionBuilder.Use(next => { return context => { context.Features.Set(new UnderlyingConnectionTransportFeature { Transport = context.Transport }); return next(context); }; }); this.ConfigureConnectionBuilder(connectionBuilder); Connection.ConfigureBuilder(connectionBuilder); return this.connectionDelegate = connectionBuilder.Build(); } } } protected virtual void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { } protected abstract Connection CreateConnection(SiloAddress address, ConnectionContext context); public virtual async ValueTask ConnectAsync(SiloAddress address, CancellationToken cancellationToken) { var connectionContext = await this.connectionFactory.ConnectAsync(address.Endpoint, cancellationToken); var connection = this.CreateConnection(address, connectionContext); return connection; } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionFailedException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime.Messaging { /// /// Indicates that a connection failed. /// public class ConnectionFailedException : OrleansException { /// /// Initializes a new instance of the class. /// public ConnectionFailedException() { } /// /// Initializes a new instance of the class. /// /// The message. public ConnectionFailedException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public ConnectionFailedException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] protected ConnectionFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionLogScope.cs ================================================ using System; using System.Collections; using System.Collections.Generic; namespace Orleans.Runtime.Messaging { internal class ConnectionLogScope : IReadOnlyList> { private readonly Connection _connection; private string _cachedToString; public ConnectionLogScope(Connection connection) { _connection = connection; } public KeyValuePair this[int index] { get { if (index == 0) { return new KeyValuePair(nameof(Connection.ConnectionId), _connection.ConnectionId); } if (index == 1) { return new KeyValuePair(nameof(Connection.LocalEndPoint), _connection.LocalEndPoint); } if (index == 2) { return new KeyValuePair(nameof(Connection.RemoteEndPoint), _connection.RemoteEndPoint); } throw new ArgumentOutOfRangeException(nameof(index)); } } public int Count => 3; public IEnumerator> GetEnumerator() { for (int i = 0; i < Count; ++i) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public override string ToString() { if (_cachedToString == null) { _cachedToString = _connection.ToString(); } return _cachedToString; } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionManager.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; namespace Orleans.Runtime.Messaging { internal sealed partial class ConnectionManager { [ThreadStatic] private static uint nextConnection; private readonly ConcurrentDictionary connections = new(); private readonly ConnectionOptions connectionOptions; private readonly ConnectionFactory connectionFactory; private readonly NetworkingTrace trace; private readonly CancellationTokenSource shutdownCancellation = new(); #if NET9_0_OR_GREATER private readonly Lock lockObj = new(); #else private readonly object lockObj = new(); #endif private readonly TaskCompletionSource closedTaskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); public ConnectionManager( IOptions connectionOptions, ConnectionFactory connectionFactory, NetworkingTrace trace) { if (trace == null) throw new ArgumentNullException(nameof(trace)); this.connectionOptions = connectionOptions.Value; this.connectionFactory = connectionFactory; this.trace = trace; } public int ConnectionCount => connections.Sum(e => e.Value.Connections.Length); public Task Closed => this.closedTaskCompletionSource.Task; public List GetConnectedAddresses() => connections.Select(i => i.Key).ToList(); public ValueTask GetConnection(SiloAddress endpoint) { if (this.connections.TryGetValue(endpoint, out var entry) && entry.NextConnection() is { } connection) { if (!entry.HasSufficientConnections(connectionOptions) && entry.PendingConnection is null) { this.GetConnectionAsync(endpoint).Ignore(); } // Return the existing connection. return new(connection); } // Start a new connection attempt since there are no suitable connections. return new(this.GetConnectionAsync(endpoint)); } public bool TryGetConnection(SiloAddress endpoint, out Connection connection) { if (this.connections.TryGetValue(endpoint, out var entry) && entry.NextConnection() is { } c) { connection = c; return true; } connection = null; return false; } private async Task GetConnectionAsync(SiloAddress endpoint) { await Task.Yield(); while (true) { if (this.shutdownCancellation.IsCancellationRequested) { throw new OperationCanceledException("Shutting down"); } Task pendingAttempt; lock (this.lockObj) { var entry = this.GetOrCreateEntry(endpoint); entry.RemoveDefunct(); // If there are sufficient connections available then return an existing connection. if (entry.HasSufficientConnections(connectionOptions) && entry.NextConnection() is { } connection) { return connection; } var remainingDelay = entry.GetRemainingRetryDelay(connectionOptions); if (remainingDelay.Ticks > 0) { throw new ConnectionFailedException($"Unable to connect to {endpoint}, will retry after {remainingDelay.TotalMilliseconds}ms"); } // If there is no pending attempt then start one, otherwise the pending attempt will be awaited before reevaluating. pendingAttempt = entry.PendingConnection ??= ConnectAsync(endpoint, entry); } await pendingAttempt; } } private void OnConnectionFailed(ConnectionEntry entry) { var lastFailure = DateTime.UtcNow; lock (this.lockObj) { if (entry.LastFailure < lastFailure) entry.LastFailure = lastFailure; entry.PendingConnection = null; entry.RemoveDefunct(); } } public void OnConnected(SiloAddress address, Connection connection) => OnConnected(address, connection, null); private void OnConnected(SiloAddress address, Connection connection, ConnectionEntry entry) { lock (this.lockObj) { entry ??= GetOrCreateEntry(address); entry.Connections = entry.Connections.Contains(connection) ? entry.Connections : entry.Connections.Add(connection); entry.LastFailure = default; entry.PendingConnection = null; } LogInformationConnectionEstablished(this.trace, connection, address); } public void OnConnectionTerminated(SiloAddress address, Connection connection, Exception exception) { if (connection is null) return; lock (this.lockObj) { if (this.connections.TryGetValue(address, out var entry)) { entry.Connections = entry.Connections.Remove(connection); if (entry.Connections.Length == 0 && entry.PendingConnection is null) { // Remove the entire entry. this.connections.TryRemove(address, out _); } else { entry.RemoveDefunct(); } } } if (exception != null && !this.shutdownCancellation.IsCancellationRequested) { LogWarningConnectionTerminated(this.trace, exception, connection); } else { LogDebugConnectionClosed(this.trace, connection); } } private ConnectionEntry GetOrCreateEntry(SiloAddress address) => connections.GetOrAdd(address, _ => new()); private async Task ConnectAsync(SiloAddress address, ConnectionEntry entry) { await Task.Yield(); CancellationTokenSource openConnectionCancellation = default; try { LogInformationEstablishingConnection(this.trace, address); // Cancel pending connection attempts either when the host terminates or after the configured time limit. openConnectionCancellation = CancellationTokenSource.CreateLinkedTokenSource(this.shutdownCancellation.Token, default); openConnectionCancellation.CancelAfter(this.connectionOptions.OpenConnectionTimeout); var connection = await this.connectionFactory.ConnectAsync(address, openConnectionCancellation.Token); LogInformationConnectedToEndpoint(this.trace, address); this.StartConnection(address, connection); await connection.Initialized.WaitAsync(openConnectionCancellation.Token); this.OnConnected(address, connection, entry); return connection; } catch (Exception exception) { this.OnConnectionFailed(entry); LogWarningConnectionAttemptFailed(this.trace, exception, address); if (exception is OperationCanceledException && openConnectionCancellation?.IsCancellationRequested == true && !shutdownCancellation.IsCancellationRequested) throw new ConnectionFailedException($"Connection attempt to endpoint {address} timed out after {connectionOptions.OpenConnectionTimeout}"); throw new ConnectionFailedException( $"Unable to connect to endpoint {address}. See {nameof(exception.InnerException)}", exception); } finally { openConnectionCancellation?.Dispose(); } } public async Task CloseAsync(SiloAddress endpoint) { ImmutableArray connections; lock (this.lockObj) { if (!this.connections.TryGetValue(endpoint, out var entry)) { return; } connections = entry.Connections; if (entry.PendingConnection is null) { this.connections.TryRemove(endpoint, out _); } } if (connections.Length == 1) { await connections[0].CloseAsync(exception: null); } else if (!connections.IsEmpty) { var closeTasks = new List(); foreach (var connection in connections) { try { closeTasks.Add(connection.CloseAsync(exception: null)); } catch { } } await Task.WhenAll(closeTasks); } } public async Task Close(CancellationToken ct) { try { LogDebugShuttingDownConnections(this.trace); this.shutdownCancellation.Cancel(throwOnFirstException: false); var cycles = 0; for (var closeTasks = new List(); ; closeTasks.Clear()) { var pendingConnections = false; foreach (var kv in connections) { pendingConnections |= kv.Value.PendingConnection != null; foreach (var connection in kv.Value.Connections) { try { closeTasks.Add(connection.CloseAsync(exception: null)); } catch { } } } if (closeTasks.Count > 0) { await Task.WhenAll(closeTasks).WaitAsync(ct).SuppressThrowing(); if (ct.IsCancellationRequested) break; } else if (!pendingConnections) break; await Task.Delay(10); if (++cycles > 100 && cycles % 500 == 0 && this.ConnectionCount is var remaining and > 0) { LogWarningWaitingForConnectionsToTerminate(this.trace, remaining); } } } catch (Exception exception) { LogWarningExceptionDuringShutdown(this.trace, exception); } finally { this.closedTaskCompletionSource.TrySetResult(0); } } private void StartConnection(SiloAddress address, Connection connection) { ThreadPool.UnsafeQueueUserWorkItem(state => { var (t, address, connection) = ((ConnectionManager, SiloAddress, Connection))state; t.RunConnectionAsync(address, connection).Ignore(); }, (this, address, connection)); } private async Task RunConnectionAsync(SiloAddress address, Connection connection) { Exception error = default; try { using (this.BeginConnectionScope(connection)) { await connection.Run(); } } catch (Exception exception) { error = exception; } finally { this.OnConnectionTerminated(address, connection, error); } } private IDisposable BeginConnectionScope(Connection connection) { if (this.trace.IsEnabled(LogLevel.Critical)) { return this.trace.BeginScope(new ConnectionLogScope(connection)); } return null; } private sealed class ConnectionEntry { public Task PendingConnection { get; set; } public DateTime LastFailure { get; set; } public ImmutableArray Connections { get; set; } = ImmutableArray.Empty; public TimeSpan GetRemainingRetryDelay(ConnectionOptions options) { var lastFailure = this.LastFailure; if (lastFailure.Ticks > 0) { var retryAfter = lastFailure + options.ConnectionRetryDelay; var remainingDelay = retryAfter - DateTime.UtcNow; if (remainingDelay.Ticks > 0) { return remainingDelay; } } return default; } public bool HasSufficientConnections(ConnectionOptions options) => Connections.Length >= options.ConnectionsPerEndpoint; public Connection NextConnection() { var connections = this.Connections; if (connections.IsEmpty) { return null; } var result = connections.Length == 1 ? connections[0] : connections[(int)(++nextConnection % (uint)connections.Length)]; return result.IsValid ? result : null; } public void RemoveDefunct() => Connections = Connections.RemoveAll(c => !c.IsValid); } [LoggerMessage( Level = LogLevel.Information, Message = "Connection {Connection} established with {Silo}" )] private static partial void LogInformationConnectionEstablished(ILogger logger, Connection connection, SiloAddress silo); [LoggerMessage( Level = LogLevel.Warning, Message = "Connection {Connection} terminated" )] private static partial void LogWarningConnectionTerminated(ILogger logger, Exception exception, Connection connection); [LoggerMessage( Level = LogLevel.Debug, Message = "Connection {Connection} closed" )] private static partial void LogDebugConnectionClosed(ILogger logger, Connection connection); [LoggerMessage( Level = LogLevel.Information, Message = "Establishing connection to endpoint {EndPoint}" )] private static partial void LogInformationEstablishingConnection(ILogger logger, SiloAddress endPoint); [LoggerMessage( Level = LogLevel.Information, Message = "Connected to endpoint {EndPoint}" )] private static partial void LogInformationConnectedToEndpoint(ILogger logger, SiloAddress endPoint); [LoggerMessage( Level = LogLevel.Warning, Message = "Connection attempt to endpoint {EndPoint} failed" )] private static partial void LogWarningConnectionAttemptFailed(ILogger logger, Exception exception, SiloAddress endPoint); [LoggerMessage( Level = LogLevel.Debug, Message = "Shutting down connections" )] private static partial void LogDebugShuttingDownConnections(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, Message = "Waiting for {NumRemaining} connections to terminate" )] private static partial void LogWarningWaitingForConnectionsToTerminate(ILogger logger, int numRemaining); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception during shutdown" )] private static partial void LogWarningExceptionDuringShutdown(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionManagerLifecycleAdapter.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime.Messaging { internal class ConnectionManagerLifecycleAdapter : ILifecycleParticipant, ILifecycleObserver where TLifecycle : ILifecycleObservable { private readonly ConnectionManager connectionManager; public ConnectionManagerLifecycleAdapter(ConnectionManager connectionManager) { this.connectionManager = connectionManager; } public Task OnStart(CancellationToken ct) => Task.CompletedTask; public async Task OnStop(CancellationToken ct) { await Task.Run(() => this.connectionManager.Close(ct)); } public void Participate(TLifecycle lifecycle) { lifecycle.Subscribe( nameof(ConnectionManager), ServiceLifecycleStage.RuntimeInitialize-1, // Components from RuntimeInitialize need network this); } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionOptions.cs ================================================ using System; using Orleans.Runtime.Messaging; namespace Orleans.Configuration { /// /// Connection options. /// public class ConnectionOptions { /// /// Gets or sets the network protocol version to negotiate with. /// public NetworkProtocolVersion ProtocolVersion { get; set; } = NetworkProtocolVersion.Version1; /// /// Gets or sets the number of connections to maintain for each endpoint. /// public int ConnectionsPerEndpoint { get; set; } = 1; /// /// Gets or sets the amount of time to wait after a failed connection attempt before retrying the connection. /// public TimeSpan ConnectionRetryDelay { get; set; } = TimeSpan.FromSeconds(1); /// /// Gets or sets the timeout before a connection open is assumed to have failed. /// public TimeSpan OpenConnectionTimeout { get; set; } = DEFAULT_OPENCONNECTION_TIMEOUT; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_OPENCONNECTION_TIMEOUT = TimeSpan.FromSeconds(5); } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionPreamble.cs ================================================ using System; using System.Buffers; using System.Buffers.Binary; using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Orleans.Serialization; namespace Orleans.Runtime.Messaging { [GenerateSerializer, Immutable] internal sealed class ConnectionPreamble { [Id(0)] public NetworkProtocolVersion NetworkProtocolVersion { get; init; } [Id(1)] public GrainId NodeIdentity { get; init; } [Id(2)] public SiloAddress SiloAddress { get; init; } [Id(3)] public string ClusterId { get; init; } } internal sealed class ConnectionPreambleHelper { private const int MaxPreambleLength = 1024; private readonly Serializer _preambleSerializer; public ConnectionPreambleHelper(Serializer preambleSerializer) { _preambleSerializer = preambleSerializer; } internal async ValueTask Write(ConnectionContext connection, ConnectionPreamble preamble) { var output = connection.Transport.Output; using var outputWriter = new PrefixingBufferWriter(sizeof(int), 1024, MemoryPool.Shared); outputWriter.Init(output); _preambleSerializer.Serialize( preamble, outputWriter); var length = outputWriter.CommittedBytes; if (length > MaxPreambleLength) { throw new InvalidOperationException($"Created preamble of length {length}, which is greater than maximum allowed size of {MaxPreambleLength}."); } WriteLength(outputWriter, length); var flushResult = await output.FlushAsync(); if (flushResult.IsCanceled) { throw new OperationCanceledException("Flush canceled"); } return; } private static void WriteLength(PrefixingBufferWriter outputWriter, int length) { Span lengthSpan = stackalloc byte[4]; BinaryPrimitives.WriteInt32LittleEndian(lengthSpan, length); outputWriter.Complete(lengthSpan); } internal async ValueTask Read(ConnectionContext connection) { var input = connection.Transport.Input; var readResult = await input.ReadAsync(); var buffer = readResult.Buffer; CheckForCompletion(ref readResult); while (buffer.Length < 4) { input.AdvanceTo(buffer.Start, buffer.End); readResult = await input.ReadAsync(); buffer = readResult.Buffer; CheckForCompletion(ref readResult); } int ReadLength(ref ReadOnlySequence b) { Span lengthBytes = stackalloc byte[4]; b.Slice(0, 4).CopyTo(lengthBytes); b = b.Slice(4); return BinaryPrimitives.ReadInt32LittleEndian(lengthBytes); } var length = ReadLength(ref buffer); if (length > MaxPreambleLength) { throw new InvalidOperationException($"Remote connection sent preamble length of {length}, which is greater than maximum allowed size of {MaxPreambleLength}."); } while (buffer.Length < length) { input.AdvanceTo(buffer.Start, buffer.End); readResult = await input.ReadAsync(); buffer = readResult.Buffer; CheckForCompletion(ref readResult); } var payloadBuffer = buffer.Slice(0, length); try { var preamble = _preambleSerializer.Deserialize(payloadBuffer); return preamble; } finally { input.AdvanceTo(payloadBuffer.End); } void CheckForCompletion(ref ReadResult r) { if (r.IsCanceled || r.IsCompleted) throw new InvalidOperationException("Connection terminated prematurely"); } } } } ================================================ FILE: src/Orleans.Core/Networking/ConnectionShared.cs ================================================ using System; using Orleans.Placement.Repartitioning; namespace Orleans.Runtime.Messaging { internal sealed class ConnectionCommon( IServiceProvider serviceProvider, MessageFactory messageFactory, MessagingTrace messagingTrace, NetworkingTrace networkingTrace, IMessageStatisticsSink messageStatisticsSink) { public MessageFactory MessageFactory { get; } = messageFactory; public IServiceProvider ServiceProvider { get; } = serviceProvider; public NetworkingTrace NetworkingTrace { get; } = networkingTrace; public IMessageStatisticsSink MessageStatisticsSink { get; } = messageStatisticsSink; public MessagingTrace MessagingTrace { get; } = messagingTrace; } } ================================================ FILE: src/Orleans.Core/Networking/IUnderlyingTransportFeature.cs ================================================ using System.IO.Pipelines; namespace Orleans.Runtime.Messaging { /// /// Holds the underlying transport used by a connection. /// internal interface IUnderlyingTransportFeature { /// /// Gets the underlying transport. /// IDuplexPipe Transport { get; } } /// /// Holds the underlying transport used by a connection. /// internal class UnderlyingConnectionTransportFeature : IUnderlyingTransportFeature { /// public IDuplexPipe Transport { get; set; } } } ================================================ FILE: src/Orleans.Core/Networking/NetworkProtocolVersion.cs ================================================ namespace Orleans.Runtime.Messaging { /// /// Identifies a network protocol version. /// public enum NetworkProtocolVersion : byte { Version1 = 1, } } ================================================ FILE: src/Orleans.Core/Networking/NetworkingTrace.cs ================================================ using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.Messaging { internal sealed class NetworkingTrace : DiagnosticListener, ILogger { private readonly ILogger log; public NetworkingTrace(ILoggerFactory loggerFactory) : base(typeof(NetworkingTrace).FullName) { this.log = loggerFactory.CreateLogger(typeof(NetworkingTrace).FullName); } public IDisposable BeginScope(TState state) { return this.log.BeginScope(state); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsEnabled(LogLevel logLevel) { return this.log.IsEnabled(logLevel); } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { this.log.Log(logLevel, eventId, state, exception, formatter); } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/BufferExtensions.cs ================================================ using System; using System.Runtime.InteropServices; namespace Orleans.Networking.Shared { internal static class BufferExtensions { public static ArraySegment GetArray(this Memory memory) => ((ReadOnlyMemory)memory).GetArray(); public static ArraySegment GetArray(this ReadOnlyMemory memory) { if (!MemoryMarshal.TryGetArray(memory, out var result)) { ThrowInvalid(); } return result; void ThrowInvalid() => throw new InvalidOperationException("Buffer backed by array was expected"); } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/CorrelationIdGenerator.cs ================================================ using System; using System.Threading; namespace Orleans.Networking.Shared { internal static class CorrelationIdGenerator { // Base32 encoding - in ascii sort order for easy text based sorting private static readonly char[] s_encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV".ToCharArray(); // Seed the _lastConnectionId for this application instance with // the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001 // for a roughly increasing _lastId over restarts private static long _lastId = DateTime.UtcNow.Ticks; public static string GetNextId() => GenerateId(Interlocked.Increment(ref _lastId)); private static string GenerateId(long id) { return string.Create(13, id, (buffer, value) => { char[] encode32Chars = s_encode32Chars; buffer[12] = encode32Chars[value & 31]; buffer[11] = encode32Chars[(value >> 5) & 31]; buffer[10] = encode32Chars[(value >> 10) & 31]; buffer[9] = encode32Chars[(value >> 15) & 31]; buffer[8] = encode32Chars[(value >> 20) & 31]; buffer[7] = encode32Chars[(value >> 25) & 31]; buffer[6] = encode32Chars[(value >> 30) & 31]; buffer[5] = encode32Chars[(value >> 35) & 31]; buffer[4] = encode32Chars[(value >> 40) & 31]; buffer[3] = encode32Chars[(value >> 45) & 31]; buffer[2] = encode32Chars[(value >> 50) & 31]; buffer[1] = encode32Chars[(value >> 55) & 31]; buffer[0] = encode32Chars[(value >> 60) & 31]; }); } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/DuplexPipe.cs ================================================ using System.IO.Pipelines; namespace Orleans.Networking.Shared { internal class DuplexPipe : IDuplexPipe { public DuplexPipe(PipeReader reader, PipeWriter writer) { Input = reader; Output = writer; } public PipeReader Input { get; } public PipeWriter Output { get; } public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) { var input = new Pipe(inputOptions); var output = new Pipe(outputOptions); var transportToApplication = new DuplexPipe(output.Reader, input.Writer); var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); return new DuplexPipePair(applicationToTransport, transportToApplication); } // This class exists to work around issues with value tuple on .NET Framework public readonly struct DuplexPipePair { public IDuplexPipe Transport { get; } public IDuplexPipe Application { get; } public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) { Transport = transport; Application = application; } } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/IOQueue.cs ================================================ using System; using System.Collections.Concurrent; using System.IO.Pipelines; using System.Threading; namespace Orleans.Networking.Shared { internal sealed class IOQueue : PipeScheduler, IThreadPoolWorkItem { private readonly ConcurrentQueue<(Action Callback, object State)> _workItems = new(); private int _doingWork; public override void Schedule(Action action, object state) { _workItems.Enqueue((action, state)); // Set working if it wasn't (via atomic Interlocked). if (Interlocked.CompareExchange(ref _doingWork, 1, 0) == 0) { // Wasn't working, schedule. _ = System.Threading.ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } public void Execute() { while (true) { while (_workItems.TryDequeue(out var item)) { item.Callback(item.State); } // All work done. // Set _doingWork (0 == false) prior to checking IsEmpty to catch any missed work in interim. // This doesn't need to be volatile due to the following barrier (i.e. it is volatile). _doingWork = 0; // Ensure _doingWork is written before IsEmpty is read. // As they are two different memory locations, we insert a barrier to guarantee ordering. Thread.MemoryBarrier(); // Check if there is work to do if (_workItems.IsEmpty) { // Nothing to do, exit. break; } // Is work, can we set it as active again (via atomic Interlocked), prior to scheduling? if (Interlocked.Exchange(ref _doingWork, 1) == 1) { // Execute has been rescheduled already, exit. break; } // Is work, wasn't already scheduled so continue loop. } } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/ISocketsTrace.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.Networking.Shared { internal interface ISocketsTrace : ILogger { void ConnectionReadFin(string connectionId); void ConnectionWriteFin(string connectionId, string reason); void ConnectionError(string connectionId, Exception ex); void ConnectionReset(string connectionId); void ConnectionPause(string connectionId); void ConnectionResume(string connectionId); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/KestrelMemoryPool.cs ================================================ using System.Buffers; namespace Orleans.Networking.Shared { internal static class KestrelMemoryPool { public static MemoryPool Create() { return CreateSlabMemoryPool(); } public static MemoryPool CreateSlabMemoryPool() { return new SlabMemoryPool(); } public static readonly int MinimumSegmentSize = 4096; } } ================================================ FILE: src/Orleans.Core/Networking/Shared/MemoryPoolBlock.cs ================================================ using System; using System.Buffers; using System.Runtime.InteropServices; namespace Orleans.Networking.Shared { /// /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The /// individual blocks are then treated as independent array segments. /// internal sealed class MemoryPoolBlock : IMemoryOwner { private readonly int _offset; private readonly int _length; /// /// This object cannot be instantiated outside of the static Create method /// internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length) { _offset = offset; _length = length; Pool = pool; Slab = slab; Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length); } /// /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool. /// public SlabMemoryPool Pool { get; } /// /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory. /// public MemoryPoolSlab Slab { get; } public Memory Memory { get; } ~MemoryPoolBlock() { Pool.RefreshBlock(Slab, _offset, _length); } public void Dispose() { Pool.Return(this); } public void Lease() { } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/MemoryPoolSlab.cs ================================================ using System; using System.Runtime.InteropServices; namespace Orleans.Networking.Shared { /// /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The /// individual blocks are then treated as independent array segments. /// internal class MemoryPoolSlab : IDisposable { /// /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls. /// private GCHandle _gcHandle; private bool _isDisposed; public MemoryPoolSlab(byte[] data) { Array = data; _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); NativePointer = _gcHandle.AddrOfPinnedObject(); } /// /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will /// be unpinned by the slab's Dispose. /// public bool IsActive => !_isDisposed; public IntPtr NativePointer { get; private set; } public byte[] Array { get; private set; } public static MemoryPoolSlab Create(int length) { // allocate and pin requested memory length var array = new byte[length]; // allocate and return slab tracking object return new MemoryPoolSlab(array); } protected void Dispose(bool disposing) { if (_isDisposed) { return; } _isDisposed = true; Array = null; NativePointer = IntPtr.Zero; if (_gcHandle.IsAllocated) { _gcHandle.Free(); } } ~MemoryPoolSlab() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SharedMemoryPool.cs ================================================ using System.Buffers; namespace Orleans.Networking.Shared { internal sealed class SharedMemoryPool { public MemoryPool Pool { get; } = KestrelMemoryPool.Create(); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SlabMemoryPool.cs ================================================ using System; using System.Buffers; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; namespace Orleans.Networking.Shared { /// /// Used to allocate and distribute re-usable blocks of memory. /// internal sealed class SlabMemoryPool : MemoryPool { /// /// The size of a block. 4096 is chosen because most operating systems use 4k pages. /// private const int _blockSize = 4096; /// /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively /// affect memory management's compactification. /// private const int _blockCount = 32; /// /// Max allocation block size for pooled blocks, /// larger values can be leased but they will be disposed after use rather than returned to the pool. /// public override int MaxBufferSize { get; } = _blockSize; /// /// The size of a block. 4096 is chosen because most operating systems use 4k pages. /// public static int BlockSize => _blockSize; /// /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab /// private static readonly int _slabLength = _blockSize * _blockCount; /// /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added. /// private readonly ConcurrentQueue _blocks = new ConcurrentQueue(); /// /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive, /// the blocks will be added to _blocks when returned. /// private readonly ConcurrentStack _slabs = new ConcurrentStack(); /// /// This is part of implementing the IDisposable pattern. /// private bool _isDisposed; // To detect redundant calls private int _totalAllocatedBlocks; private readonly object _disposeSync = new object(); /// /// This default value passed in to Rent to use the default value for the pool. /// private const int AnySize = -1; public override IMemoryOwner Rent(int size = AnySize) { if (size > _blockSize) { ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize); } var block = Lease(); return block; } /// /// Called to take a block from the pool. /// /// The block that is reserved for the called. It must be passed to Return when it is no longer being used. private MemoryPoolBlock Lease() { if (_isDisposed) { ThrowObjectDisposedException(); } if (_blocks.TryDequeue(out MemoryPoolBlock block)) { // block successfully taken from the stack - return it block.Lease(); return block; } // no blocks available - grow the pool block = AllocateSlab(); block.Lease(); return block; } /// /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the /// block tracking objects, and adds them all to the pool. /// private MemoryPoolBlock AllocateSlab() { var slab = MemoryPoolSlab.Create(_slabLength); _slabs.Push(slab); var basePtr = slab.NativePointer; // Page align the blocks var offset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr); // Ensure page aligned Debug.Assert(((ulong)basePtr + (uint)offset) % _blockSize == 0); var blockCount = (_slabLength - offset) / _blockSize; Interlocked.Add(ref _totalAllocatedBlocks, blockCount); MemoryPoolBlock block = null; for (int i = 0; i < blockCount; i++) { block = new MemoryPoolBlock(this, slab, offset, _blockSize); if (i != blockCount - 1) // last block { #if BLOCK_LEASE_TRACKING block.IsLeased = true; #endif Return(block); } offset += _blockSize; } return block; } /// /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid /// leaving "dead zones" in the slab due to lost block tracking objects. /// /// The block to return. It must have been acquired by calling Lease on the same memory pool instance. internal void Return(MemoryPoolBlock block) { #if BLOCK_LEASE_TRACKING Debug.Assert(block.Pool == this, "Returned block was not leased from this pool"); Debug.Assert(block.IsLeased, $"Block being returned to pool twice: {block.Leaser}{Environment.NewLine}"); block.IsLeased = false; #endif if (!_isDisposed) { _blocks.Enqueue(block); } else { GC.SuppressFinalize(block); } } // This method can ONLY be called from the finalizer of MemoryPoolBlock internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length) { lock (_disposeSync) { if (!_isDisposed && slab != null && slab.IsActive) { // Need to make a new object because this one is being finalized // Note, this must be called within the _disposeSync lock because the block // could be disposed at the same time as the finalizer. Return(new MemoryPoolBlock(this, slab, offset, length)); } } } protected override void Dispose(bool disposing) { if (_isDisposed) { return; } lock (_disposeSync) { _isDisposed = true; if (disposing) { while (_slabs.TryPop(out MemoryPoolSlab slab)) { // dispose managed state (managed objects). slab.Dispose(); } } // Discard blocks in pool while (_blocks.TryDequeue(out MemoryPoolBlock block)) { GC.SuppressFinalize(block); } } } private static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) { throw new ArgumentOutOfRangeException("size", $"Cannot allocate more than {maxSize} bytes in a single buffer"); } private static void ThrowObjectDisposedException() => throw new ObjectDisposedException("MemoryPool"); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketAwaitableEventArgs.cs ================================================ using System; using System.Diagnostics; using System.IO.Pipelines; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Orleans.Networking.Shared { internal class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion { private static readonly Action _callbackCompleted = () => { }; private readonly PipeScheduler _ioScheduler; private Action _callback; public SocketAwaitableEventArgs(PipeScheduler ioScheduler) { _ioScheduler = ioScheduler; } public SocketAwaitableEventArgs GetAwaiter() => this; public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); public int GetResult() { Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); _callback = null; if (SocketError != SocketError.Success) { ThrowSocketException(SocketError); } return BytesTransferred; void ThrowSocketException(SocketError e) { throw new SocketException((int)e); } } public void OnCompleted(Action continuation) { if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) { Task.Run(continuation); } } public void UnsafeOnCompleted(Action continuation) { OnCompleted(continuation); } public void Complete() { OnCompleted(this); } protected override void OnCompleted(SocketAsyncEventArgs _) { var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); if (continuation != null) { _ioScheduler.Schedule(state => ((Action)state)(), continuation); } } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketConnection.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.IO.Pipelines; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; namespace Orleans.Networking.Shared { internal sealed partial class SocketConnection : TransportConnection { private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2; private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); private readonly Socket _socket; private readonly ISocketsTrace _trace; private readonly SocketReceiver _receiver; private readonly SocketSender _sender; private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource(); #if NET9_0_OR_GREATER private readonly Lock _shutdownLock = new(); #else private readonly object _shutdownLock = new(); #endif private volatile bool _socketDisposed; private volatile Exception _shutdownReason; private Task _processingTask; private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private bool _connectionClosed; internal SocketConnection(Socket socket, MemoryPool memoryPool, PipeScheduler scheduler, ISocketsTrace trace, long? maxReadBufferSize = null, long? maxWriteBufferSize = null) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); Debug.Assert(trace != null); _socket = socket; MemoryPool = memoryPool; _trace = trace; LocalEndPoint = _socket.LocalEndPoint; RemoteEndPoint = _socket.RemoteEndPoint; ConnectionClosed = _connectionClosedTokenSource.Token; // On *nix platforms, Sockets already dispatches to the ThreadPool. // Yes, the IOQueues are still used for the PipeSchedulers. This is intentional. // https://github.com/aspnet/KestrelHttpServer/issues/2573 var awaiterScheduler = IsWindows ? scheduler : PipeScheduler.Inline; _receiver = new SocketReceiver(_socket, awaiterScheduler); _sender = new SocketSender(_socket, awaiterScheduler); maxReadBufferSize = maxReadBufferSize ?? 0; maxWriteBufferSize = maxWriteBufferSize ?? 0; var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, scheduler, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false); var outputOptions = new PipeOptions(MemoryPool, scheduler, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false); var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); // Set the transport and connection id Transport = pair.Transport; Application = pair.Application; } public PipeWriter Input => Application.Output; public PipeReader Output => Application.Input; public override MemoryPool MemoryPool { get; } public void Start() { _processingTask = StartAsync(); } private async Task StartAsync() { try { // Spawn send and receive logic var receiveTask = DoReceive(); var sendTask = DoSend(); // Now wait for both to complete await receiveTask; await sendTask; _receiver.Dispose(); _sender.Dispose(); } catch (Exception ex) { LogErrorUnexpectedExceptionInStartAsync(_trace, ex); } } public override void Abort(ConnectionAbortedException abortReason) { // Try to gracefully close the socket to match libuv behavior. Shutdown(abortReason); // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. Output.CancelPendingRead(); } // Only called after connection middleware is complete which means the ConnectionClosed token has fired. public override async ValueTask DisposeAsync() { Transport.Input.Complete(); Transport.Output.Complete(); if (_processingTask != null) { await _processingTask; } _connectionClosedTokenSource.Dispose(); } private async Task DoReceive() { Exception error = null; try { await ProcessReceives(); } catch (SocketException ex) when (IsConnectionResetError(ex.SocketErrorCode)) { // This could be ignored if _shutdownReason is already set. error = new ConnectionResetException(ex.Message, ex); // There's still a small chance that both DoReceive() and DoSend() can log the same connection reset. // Both logs will have the same ConnectionId. I don't think it's worthwhile to lock just to avoid this. if (!_socketDisposed) { _trace.ConnectionReset(ConnectionId); } } catch (Exception ex) when ((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This exception should always be ignored because _shutdownReason should be set. error = ex; if (!_socketDisposed) { // This is unexpected if the socket hasn't been disposed yet. _trace.ConnectionError(ConnectionId, error); } } catch (Exception ex) { // This is unexpected. error = ex; _trace.ConnectionError(ConnectionId, error); } finally { // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. Input.Complete(_shutdownReason ?? error); FireConnectionClosed(); await _waitForConnectionClosedTcs.Task; } } private async Task ProcessReceives() { // Resolve `input` PipeWriter via the IDuplexPipe interface prior to loop start for performance. var input = Input; while (true) { // Wait for data before allocating a buffer. await _receiver.WaitForDataAsync(); // Ensure we have some reasonable amount of buffer space var buffer = input.GetMemory(MinAllocBufferSize); var bytesReceived = await _receiver.ReceiveAsync(buffer); if (bytesReceived == 0) { // FIN _trace.ConnectionReadFin(ConnectionId); break; } input.Advance(bytesReceived); var flushTask = input.FlushAsync(); var paused = !flushTask.IsCompleted; if (paused) { _trace.ConnectionPause(ConnectionId); } var result = await flushTask; if (paused) { _trace.ConnectionResume(ConnectionId); } if (result.IsCompleted || result.IsCanceled) { // Pipe consumer is shut down, do we stop writing break; } } } private async Task DoSend() { Exception shutdownReason = null; Exception unexpectedError = null; try { await ProcessSends(); } catch (SocketException ex) when (IsConnectionResetError(ex.SocketErrorCode)) { shutdownReason = new ConnectionResetException(ex.Message, ex); _trace.ConnectionReset(ConnectionId); } catch (Exception ex) when ((ex is SocketException socketEx && IsConnectionAbortError(socketEx.SocketErrorCode)) || ex is ObjectDisposedException) { // This should always be ignored since Shutdown() must have already been called by Abort(). shutdownReason = ex; } catch (Exception ex) { shutdownReason = ex; unexpectedError = ex; _trace.ConnectionError(ConnectionId, unexpectedError); } finally { Shutdown(shutdownReason); // Complete the output after disposing the socket Output.Complete(unexpectedError); // Cancel any pending flushes so that the input loop is un-paused Input.CancelPendingFlush(); } } private async Task ProcessSends() { // Resolve `output` PipeReader via the IDuplexPipe interface prior to loop start for performance. var output = Output; while (true) { var result = await output.ReadAsync(); if (result.IsCanceled) { break; } var buffer = result.Buffer; var end = buffer.End; var isCompleted = result.IsCompleted; if (!buffer.IsEmpty) { await _sender.SendAsync(buffer); } output.AdvanceTo(end); if (isCompleted) { break; } } } private void FireConnectionClosed() { // Guard against scheduling this multiple times if (_connectionClosed) { return; } _connectionClosed = true; ThreadPool.UnsafeQueueUserWorkItem(state => { ((SocketConnection)state).CancelConnectionClosedToken(); ((SocketConnection)state)._waitForConnectionClosedTcs.TrySetResult(null); }, this); } private void Shutdown(Exception shutdownReason) { lock (_shutdownLock) { if (_socketDisposed) { return; } // Make sure to close the connection only after the _aborted flag is set. // Without this, the RequestsCanBeAbortedMidRead test will sometimes fail when // a BadHttpRequestException is thrown instead of a TaskCanceledException. _socketDisposed = true; // shutdownReason should only be null if the output was completed gracefully, so no one should ever // ever observe the nondescript ConnectionAbortedException except for connection middleware attempting // to half close the connection which is currently unsupported. _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully."); _trace.ConnectionWriteFin(ConnectionId, _shutdownReason.Message); try { // Try to gracefully close the socket even for aborts to match libuv behavior. _socket.Shutdown(SocketShutdown.Both); } catch { // Ignore any errors from Socket.Shutdown() since we're tearing down the connection anyway. } _socket.Dispose(); } } private void CancelConnectionClosedToken() { try { _connectionClosedTokenSource.Cancel(); } catch (Exception ex) { LogErrorUnexpectedExceptionInCancelConnectionClosedToken(_trace, ex); } } private static bool IsConnectionResetError(SocketError errorCode) { // A connection reset can be reported as SocketError.ConnectionAborted on Windows. // ProtocolType can be removed once https://github.com/dotnet/corefx/issues/31927 is fixed. return errorCode == SocketError.ConnectionReset || errorCode == SocketError.Shutdown || (errorCode == SocketError.ConnectionAborted && IsWindows) || (errorCode == SocketError.ProtocolType && IsMacOS); } private static bool IsConnectionAbortError(SocketError errorCode) { // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix. return errorCode == SocketError.OperationAborted || errorCode == SocketError.Interrupted || (errorCode == SocketError.InvalidArgument && !IsWindows); } [LoggerMessage( Level = LogLevel.Error, Message = "Unexpected exception in SocketConnection.StartAsync." )] private static partial void LogErrorUnexpectedExceptionInStartAsync(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Unexpected exception in SocketConnection.CancelConnectionClosedToken." )] private static partial void LogErrorUnexpectedExceptionInCancelConnectionClosedToken(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketConnectionFactory.cs ================================================ using System; using System.Buffers; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Networking.Shared { internal class SocketConnectionFactory : IConnectionFactory { private readonly SocketsTrace trace; private readonly SocketSchedulers schedulers; private readonly MemoryPool memoryPool; private readonly SocketConnectionOptions _options; public SocketConnectionFactory(ILoggerFactory loggerFactory, SocketSchedulers schedulers, SharedMemoryPool memoryPool, IOptions options) { var logger = loggerFactory.CreateLogger("Orleans.Sockets"); this.trace = new SocketsTrace(logger); this.schedulers = schedulers; this.memoryPool = memoryPool.Pool; _options = options.Value; } public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken) { var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { LingerState = new LingerOption(true, 0), NoDelay = _options.NoDelay, }; if (_options.KeepAlive) { socket.EnableKeepAlive( timeSeconds: _options.KeepAliveTimeSeconds, intervalSeconds: _options.KeepAliveIntervalSeconds, retryCount: _options.KeepAliveRetryCount); } socket.EnableFastPath(); using var completion = new SingleUseSocketAsyncEventArgs { RemoteEndPoint = endpoint }; if (socket.ConnectAsync(completion)) { using (cancellationToken.Register(s => Socket.CancelConnectAsync((SingleUseSocketAsyncEventArgs)s), completion)) { await completion.Task; } } if (completion.SocketError != SocketError.Success) { if (completion.SocketError == SocketError.OperationAborted) cancellationToken.ThrowIfCancellationRequested(); throw new SocketConnectionException($"Unable to connect to {endpoint}. Error: {completion.SocketError}"); } var scheduler = this.schedulers.GetScheduler(); var connection = new SocketConnection(socket, this.memoryPool, scheduler, this.trace); connection.Start(); return connection; } private sealed class SingleUseSocketAsyncEventArgs : SocketAsyncEventArgs { private readonly TaskCompletionSource completion = new(); public Task Task => completion.Task; protected override void OnCompleted(SocketAsyncEventArgs _) => this.completion.TrySetResult(null); } } [Serializable] [GenerateSerializer] public sealed class SocketConnectionException : OrleansException { public SocketConnectionException(string message) : base(message) { } public SocketConnectionException(string message, Exception innerException) : base(message, innerException) { } [Obsolete] public SocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketConnectionListener.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; namespace Orleans.Networking.Shared { internal sealed class SocketConnectionListener : IConnectionListener { private readonly MemoryPool _memoryPool; private readonly SocketSchedulers _schedulers; private readonly ISocketsTrace _trace; private Socket _listenSocket; private readonly SocketConnectionOptions _options; public EndPoint EndPoint { get; private set; } internal SocketConnectionListener( EndPoint endpoint, SocketConnectionOptions options, ISocketsTrace trace, SocketSchedulers schedulers) { Debug.Assert(endpoint != null); Debug.Assert(endpoint is IPEndPoint); Debug.Assert(trace != null); EndPoint = endpoint; _trace = trace; _schedulers = schedulers; _options = options; _memoryPool = options.MemoryPoolFactory(); } internal void Bind() { if (_listenSocket != null) { throw new InvalidOperationException("Transport already bound"); } var listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { LingerState = new LingerOption(true, 0), NoDelay = true }; listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); if (_options.KeepAlive) { listenSocket.EnableKeepAlive( timeSeconds: _options.KeepAliveTimeSeconds, intervalSeconds: _options.KeepAliveIntervalSeconds, retryCount: _options.KeepAliveRetryCount); } listenSocket.EnableFastPath(); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 if (EndPoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) { listenSocket.DualMode = true; } try { listenSocket.Bind(EndPoint); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { throw new AddressInUseException(e.Message, e); } EndPoint = listenSocket.LocalEndPoint; listenSocket.Listen(512); _listenSocket = listenSocket; } public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { while (true) { try { var acceptSocket = await _listenSocket.AcceptAsync(); acceptSocket.NoDelay = _options.NoDelay; if (_options.KeepAlive) { acceptSocket.EnableKeepAlive( timeSeconds: _options.KeepAliveTimeSeconds, intervalSeconds: _options.KeepAliveIntervalSeconds, retryCount: _options.KeepAliveRetryCount); } var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers.GetScheduler(), _trace); connection.Start(); return connection; } catch (ObjectDisposedException) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException) { // The connection got reset while it was in the backlog, so we try again. _trace.ConnectionReset(connectionId: "(null)"); } } } public ValueTask UnbindAsync(CancellationToken cancellationToken) { _listenSocket?.Dispose(); return default; } public ValueTask DisposeAsync() { _listenSocket?.Dispose(); // Dispose the memory pool _memoryPool.Dispose(); return default; } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketConnectionListenerFactory.cs ================================================ using System; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Orleans.Networking.Shared { internal sealed class SocketConnectionListenerFactory : IConnectionListenerFactory { private readonly SocketConnectionOptions socketConnectionOptions; private readonly SocketsTrace trace; private readonly SocketSchedulers schedulers; public SocketConnectionListenerFactory( ILoggerFactory loggerFactory, IOptions socketConnectionOptions, SocketSchedulers schedulers) { if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } this.socketConnectionOptions = socketConnectionOptions.Value; var logger = loggerFactory.CreateLogger("Orleans.Sockets"); this.trace = new SocketsTrace(logger); this.schedulers = schedulers; } public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { if (!(endpoint is IPEndPoint ipEndpoint)) { throw new ArgumentNullException(nameof(endpoint)); } var listener = new SocketConnectionListener(ipEndpoint, this.socketConnectionOptions, this.trace, this.schedulers); listener.Bind(); return new ValueTask(listener); } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketConnectionOptions.cs ================================================ using System; using System.Buffers; namespace Orleans.Networking.Shared { /// /// Options for configuring socket connections. /// public class SocketConnectionOptions { /// /// The number of I/O queues used to process requests. Set to 0 to directly schedule I/O to the ThreadPool. /// /// /// Defaults to rounded down and clamped between 1 and 16. /// public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16); /// /// Whether the Nagle algorithm should be enabled or disabled. /// public bool NoDelay { get; set; } = true; /// /// Whether TCP KeepAlive is enabled or disabled. /// public bool KeepAlive { get; set; } = true; /// /// The number of seconds before the first keep-alive packet is sent on an idle connection. /// /// public int KeepAliveTimeSeconds { get; set; } = 90; /// /// The number of seconds between keep-alive packets when the remote endpoint is not responding. /// /// public int KeepAliveIntervalSeconds { get; set; } = 30; /// /// The number of retry attempts for keep-alive packets before the connection is considered dead. /// /// public int KeepAliveRetryCount { get; set; } = 10; /// /// Gets or sets the memory pool factory. /// internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketExtensions.cs ================================================ using System; using System.Net.Sockets; using System.Runtime.InteropServices; namespace Orleans.Networking.Shared { internal static class SocketExtensions { private const int SIO_LOOPBACK_FAST_PATH = -1744830448; private static readonly byte[] Enabled = BitConverter.GetBytes(1); /// /// Enables TCP Loopback Fast Path on a socket. /// See https://blogs.technet.microsoft.com/wincat/2012/12/05/fast-tcp-loopback-performance-and-low-latency-with-windows-server-2012-tcp-loopback-fast-path/ /// for more information. /// /// The socket for which FastPath should be enabled. internal static void EnableFastPath(this Socket socket) { try { socket.NoDelay = true; } catch { } if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } try { // Win8/Server2012+ only var osVersion = Environment.OSVersion.Version; if (osVersion.Major > 6 || osVersion.Major == 6 && osVersion.Minor >= 2) { socket.IOControl(SIO_LOOPBACK_FAST_PATH, Enabled, null); } } catch { // If the operating system version on this machine did // not support SIO_LOOPBACK_FAST_PATH (i.e. version // prior to Windows 8 / Windows Server 2012), handle the exception } } /// /// Enables TCP KeepAlive on a socket. /// /// The socket. internal static void EnableKeepAlive(this Socket socket, int timeSeconds = 90, int intervalSeconds = 30, int retryCount = 2) { try { socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, timeSeconds); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, intervalSeconds); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, retryCount); } catch { } } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketReceiver.cs ================================================ using System; using System.IO.Pipelines; using System.Net.Sockets; namespace Orleans.Networking.Shared { internal sealed class SocketReceiver : SocketSenderReceiverBase { public SocketReceiver(Socket socket, PipeScheduler scheduler) : base(socket, scheduler) { } public SocketAwaitableEventArgs WaitForDataAsync() { _awaitableEventArgs.SetBuffer(Memory.Empty); if (!_socket.ReceiveAsync(_awaitableEventArgs)) { _awaitableEventArgs.Complete(); } return _awaitableEventArgs; } public SocketAwaitableEventArgs ReceiveAsync(Memory buffer) { _awaitableEventArgs.SetBuffer(buffer); if (!_socket.ReceiveAsync(_awaitableEventArgs)) { _awaitableEventArgs.Complete(); } return _awaitableEventArgs; } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketSchedulers.cs ================================================ using System.IO.Pipelines; using Microsoft.Extensions.Options; namespace Orleans.Networking.Shared { internal class SocketSchedulers { private static readonly PipeScheduler[] ThreadPoolSchedulerArray = new PipeScheduler[] { PipeScheduler.ThreadPool }; private readonly int _numSchedulers; private readonly PipeScheduler[] _schedulers; private int nextScheduler; public SocketSchedulers(IOptions options) { var o = options.Value; if (o.IOQueueCount > 0) { _numSchedulers = o.IOQueueCount; _schedulers = new IOQueue[_numSchedulers]; for (var i = 0; i < _numSchedulers; i++) { _schedulers[i] = new IOQueue(); } } else { _numSchedulers = ThreadPoolSchedulerArray.Length; _schedulers = ThreadPoolSchedulerArray; } } public PipeScheduler GetScheduler() => _schedulers[++nextScheduler % _numSchedulers]; } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketSender.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Net.Sockets; using System.Runtime.InteropServices; namespace Orleans.Networking.Shared { internal sealed class SocketSender : SocketSenderReceiverBase { private List> _bufferList; public SocketSender(Socket socket, PipeScheduler scheduler) : base(socket, scheduler) { } public SocketAwaitableEventArgs SendAsync(in ReadOnlySequence buffers) { if (buffers.IsSingleSegment) { return SendAsync(buffers.First); } if (!_awaitableEventArgs.Equals(Memory.Empty)) { _awaitableEventArgs.SetBuffer(null, 0, 0); } _awaitableEventArgs.BufferList = GetBufferList(buffers); if (!_socket.SendAsync(_awaitableEventArgs)) { _awaitableEventArgs.Complete(); } return _awaitableEventArgs; } private SocketAwaitableEventArgs SendAsync(ReadOnlyMemory memory) { // The BufferList getter is much less expensive then the setter. if (_awaitableEventArgs.BufferList != null) { _awaitableEventArgs.BufferList = null; } _awaitableEventArgs.SetBuffer(MemoryMarshal.AsMemory(memory)); if (!_socket.SendAsync(_awaitableEventArgs)) { _awaitableEventArgs.Complete(); } return _awaitableEventArgs; } private List> GetBufferList(in ReadOnlySequence buffer) { Debug.Assert(!buffer.IsEmpty); Debug.Assert(!buffer.IsSingleSegment); if (_bufferList == null) { _bufferList = new List>(); } else { // Buffers are pooled, so it's OK to root them until the next multi-buffer write. _bufferList.Clear(); } foreach (var b in buffer) { _bufferList.Add(b.GetArray()); } return _bufferList; } } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketSenderReceiverBase.cs ================================================ using System; using System.IO.Pipelines; using System.Net.Sockets; namespace Orleans.Networking.Shared { internal abstract class SocketSenderReceiverBase : IDisposable { protected readonly Socket _socket; protected readonly SocketAwaitableEventArgs _awaitableEventArgs; protected SocketSenderReceiverBase(Socket socket, PipeScheduler scheduler) { _socket = socket; _awaitableEventArgs = new SocketAwaitableEventArgs(scheduler); } public void Dispose() => _awaitableEventArgs.Dispose(); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/SocketsTrace.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.Networking.Shared { internal partial class SocketsTrace : ISocketsTrace { // ConnectionRead: Reserved: 3 private readonly ILogger _logger; public SocketsTrace(ILogger logger) { _logger = logger; } public void ConnectionRead(string connectionId, int count) { // Don't log for now since this could be *too* verbose. // Reserved: Event ID 3 } [LoggerMessage( EventId = 6, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" received FIN." )] public partial void ConnectionReadFin(string connectionId); [LoggerMessage( EventId = 7, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""" )] public partial void ConnectionWriteFin(string connectionId, string reason); public void ConnectionWrite(string connectionId, int count) { // Don't log for now since this could be *too* verbose. // Reserved: Event ID 11 } public void ConnectionWriteCallback(string connectionId, int status) { // Don't log for now since this could be *too* verbose. // Reserved: Event ID 12 } [LoggerMessage( EventId = 13, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" sending FIN." )] public partial void ConnectionError(string connectionId, Exception ex); [LoggerMessage( EventId = 19, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" reset." )] public partial void ConnectionReset(string connectionId); [LoggerMessage( EventId = 4, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" paused." )] public partial void ConnectionPause(string connectionId); [LoggerMessage( EventId = 5, Level = LogLevel.Debug, Message = @"Connection id ""{ConnectionId}"" resumed." )] public partial void ConnectionResume(string connectionId); public IDisposable BeginScope(TState state) => _logger.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel); public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) => _logger.Log(logLevel, eventId, state, exception, formatter); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/TransportConnection.Features.cs ================================================ using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; namespace Orleans.Networking.Shared { internal interface IConnectionIdFeature { string ConnectionId { get; set; } } internal interface IConnectionTransportFeature { IDuplexPipe Transport { get; set; } } internal interface IConnectionItemsFeature { IDictionary Items { get; set; } } internal partial class TransportConnection : IConnectionIdFeature, IConnectionTransportFeature, IConnectionItemsFeature, IMemoryPoolFeature, IConnectionLifetimeFeature { // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation, // then the list of `features` in the generated code project MUST also be updated. // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool; IDuplexPipe IConnectionTransportFeature.Transport { get => Transport; set => Transport = value; } IDictionary IConnectionItemsFeature.Items { get => Items; set => Items = value; } CancellationToken IConnectionLifetimeFeature.ConnectionClosed { get => ConnectionClosed; set => ConnectionClosed = value; } void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort().")); } internal partial class TransportConnection : IFeatureCollection { private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature); private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature); private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature); private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature); private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature); private object _currentIConnectionIdFeature; private object _currentIConnectionTransportFeature; private object _currentIConnectionItemsFeature; private object _currentIMemoryPoolFeature; private object _currentIConnectionLifetimeFeature; private int _featureRevision; private List> MaybeExtra; private void FastReset() { _currentIConnectionIdFeature = this; _currentIConnectionTransportFeature = this; _currentIConnectionItemsFeature = this; _currentIMemoryPoolFeature = this; _currentIConnectionLifetimeFeature = this; } // Internal for testing internal void ResetFeatureCollection() { FastReset(); MaybeExtra?.Clear(); _featureRevision++; } private object ExtraFeatureGet(Type key) { if (MaybeExtra == null) { return null; } for (var i = 0; i < MaybeExtra.Count; i++) { var kv = MaybeExtra[i]; if (kv.Key == key) { return kv.Value; } } return null; } private void ExtraFeatureSet(Type key, object value) { if (MaybeExtra == null) { MaybeExtra = new List>(2); } for (var i = 0; i < MaybeExtra.Count; i++) { if (MaybeExtra[i].Key == key) { MaybeExtra[i] = new KeyValuePair(key, value); return; } } MaybeExtra.Add(new KeyValuePair(key, value)); } bool IFeatureCollection.IsReadOnly => false; int IFeatureCollection.Revision => _featureRevision; object IFeatureCollection.this[Type key] { get { object feature = null; if (key == IConnectionIdFeatureType) { feature = _currentIConnectionIdFeature; } else if (key == IConnectionTransportFeatureType) { feature = _currentIConnectionTransportFeature; } else if (key == IConnectionItemsFeatureType) { feature = _currentIConnectionItemsFeature; } else if (key == IMemoryPoolFeatureType) { feature = _currentIMemoryPoolFeature; } else if (key == IConnectionLifetimeFeatureType) { feature = _currentIConnectionLifetimeFeature; } else if (MaybeExtra != null) { feature = ExtraFeatureGet(key); } return feature; } set { _featureRevision++; if (key == IConnectionIdFeatureType) { _currentIConnectionIdFeature = value; } else if (key == IConnectionTransportFeatureType) { _currentIConnectionTransportFeature = value; } else if (key == IConnectionItemsFeatureType) { _currentIConnectionItemsFeature = value; } else if (key == IMemoryPoolFeatureType) { _currentIMemoryPoolFeature = value; } else if (key == IConnectionLifetimeFeatureType) { _currentIConnectionLifetimeFeature = value; } else { ExtraFeatureSet(key, value); } } } TFeature IFeatureCollection.Get() { TFeature feature = default; if (typeof(TFeature) == typeof(IConnectionIdFeature)) { feature = (TFeature)_currentIConnectionIdFeature; } else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { feature = (TFeature)_currentIConnectionTransportFeature; } else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) { feature = (TFeature)_currentIConnectionItemsFeature; } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { feature = (TFeature)_currentIMemoryPoolFeature; } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { feature = (TFeature)_currentIConnectionLifetimeFeature; } else if (MaybeExtra != null) { feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); } return feature; } void IFeatureCollection.Set(TFeature feature) { _featureRevision++; if (typeof(TFeature) == typeof(IConnectionIdFeature)) { _currentIConnectionIdFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionTransportFeature)) { _currentIConnectionTransportFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionItemsFeature)) { _currentIConnectionItemsFeature = feature; } else if (typeof(TFeature) == typeof(IMemoryPoolFeature)) { _currentIMemoryPoolFeature = feature; } else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature)) { _currentIConnectionLifetimeFeature = feature; } else { ExtraFeatureSet(typeof(TFeature), feature); } } private IEnumerable> FastEnumerable() { if (_currentIConnectionIdFeature != null) { yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature); } if (_currentIConnectionTransportFeature != null) { yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature); } if (_currentIConnectionItemsFeature != null) { yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature); } if (_currentIMemoryPoolFeature != null) { yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature); } if (_currentIConnectionLifetimeFeature != null) { yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature); } if (MaybeExtra != null) { foreach (var item in MaybeExtra) { yield return item; } } } IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator(); } } ================================================ FILE: src/Orleans.Core/Networking/Shared/TransportConnection.cs ================================================ using System.Buffers; using System.Collections.Generic; using System.IO.Pipelines; using System.Net; using System.Threading; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; namespace Orleans.Networking.Shared { internal abstract partial class TransportConnection : ConnectionContext { private IDictionary _items; private string _connectionId; public TransportConnection() { FastReset(); } public override EndPoint LocalEndPoint { get; set; } public override EndPoint RemoteEndPoint { get; set; } public override string ConnectionId { get { if (_connectionId == null) { _connectionId = CorrelationIdGenerator.GetNextId(); } return _connectionId; } set { _connectionId = value; } } public override IFeatureCollection Features => this; public virtual MemoryPool MemoryPool { get; } public override IDuplexPipe Transport { get; set; } public IDuplexPipe Application { get; set; } public override IDictionary Items { get { // Lazily allocate connection metadata return _items ?? (_items = new ConnectionItems()); } set { _items = value; } } public override CancellationToken ConnectionClosed { get; set; } // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause // any TransportConnection that does not override Abort or calls base.Abort // to stack overflow when IConnectionLifetimeFeature.Abort() is called. // That said, all derived types should override this method should override // this implementation of Abort because canceling pending output reads is not // sufficient to abort the connection if there is backpressure. public override void Abort(ConnectionAbortedException abortReason) { Application.Input.CancelPendingRead(); } } } ================================================ FILE: src/Orleans.Core/Networking/SocketDirection.cs ================================================ namespace Orleans.Messaging { internal enum ConnectionDirection : byte { SiloToSilo, ClientToGateway, GatewayToClient } } ================================================ FILE: src/Orleans.Core/Orleans.Core.csproj ================================================ Microsoft.Orleans.Core Microsoft Orleans Core Library Core library of Microsoft Orleans used both on the client and server. $(DefaultTargetFrameworks) Orleans true README.md %(Identity) true true %(Identity) true true %(Identity) true true Designer PreserveNewest false ================================================ FILE: src/Orleans.Core/Placement/IPlacementContext.cs ================================================ using System.Collections.Generic; namespace Orleans.Runtime.Placement { /// /// Provides context for a grain placement operation. /// public interface IPlacementContext { /// /// Gets the collection of silos which are compatible with the provided placement target. /// /// /// A description of the grain being placed as well as contextual information about the request which is triggering placement. /// /// The collection of silos which are compatible with the provided placement target. SiloAddress[] GetCompatibleSilos(PlacementTarget target); /// /// Gets the collection of silos which are compatible with the provided placement target, along with the versions of the grain interface which each server supports. /// /// /// A description of the grain being placed as well as contextual information about the request which is triggering placement. /// /// The collection of silos which are compatible with the provided placement target, along with the versions of the grain interface which each server supports. IReadOnlyDictionary GetCompatibleSilosWithVersions(PlacementTarget target); /// /// Gets the local silo's identity. /// SiloAddress LocalSilo { get; } /// /// Gets the local silo's status. /// SiloStatus LocalSiloStatus { get; } } } ================================================ FILE: src/Orleans.Core/Placement/IPlacementDirector.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Runtime.Placement { /// /// Interface for placement directors. /// public interface IPlacementDirector { /// /// Gets the key used to store the placement hint, if present. /// public const string PlacementHintKey = nameof(PlacementHintKey); /// /// Picks an appropriate silo to place the specified target on. /// /// The target's placement strategy. /// The grain being placed as well as information about the request which triggered the placement. /// The placement context. /// An appropriate silo to place the specified target on. Task OnAddActivation( PlacementStrategy strategy, PlacementTarget target, IPlacementContext context); /// /// Gets the placement hint from the provided request context data, if present and valid. /// /// The request context data. /// The compatible silos. /// The placement hint, if present and valid, or otherwise. public static SiloAddress GetPlacementHint(Dictionary requestContextData, SiloAddress[] compatibleSilos) { if (requestContextData is { Count: > 0 } data && data.TryGetValue(PlacementHintKey, out var value) && value is SiloAddress placementHint && compatibleSilos.Contains(placementHint)) { return placementHint; } return null; } } } ================================================ FILE: src/Orleans.Core/Placement/IPlacementFilterDirector.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; using Orleans.Runtime.Placement; #nullable enable namespace Orleans.Placement; public interface IPlacementFilterDirector { IEnumerable Filter(PlacementFilterStrategy filterStrategy, PlacementTarget target, IEnumerable silos); } ================================================ FILE: src/Orleans.Core/Placement/PlacementFilterExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; #nullable enable namespace Orleans.Placement; public static class PlacementFilterExtensions { /// /// Configures a for filtering candidate grain placements. /// /// The placement filter. /// The placement filter director. /// The service collection. /// The lifetime of the placement strategy. /// The service collection. public static IServiceCollection AddPlacementFilter(this IServiceCollection services, ServiceLifetime strategyLifetime) where TFilter : PlacementFilterStrategy, new() where TDirector : class, IPlacementFilterDirector { services.Add(ServiceDescriptor.DescribeKeyed(typeof(PlacementFilterStrategy), typeof(TFilter).Name, typeof(TFilter), strategyLifetime)); services.AddKeyedSingleton(typeof(TFilter)); return services; } } ================================================ FILE: src/Orleans.Core/Placement/PlacementTarget.cs ================================================ using System.Collections.Generic; namespace Orleans.Runtime.Placement { /// /// Describes a placement target, which is a grain as well as context regarding the request which is triggering grain placement. /// public readonly struct PlacementTarget { /// /// Initializes a new instance of the struct. /// /// The grain being targeted. /// The dictionary for the request which triggered placement. /// The interface being requested. /// The interface version being requested. public PlacementTarget(GrainId grainIdentity, Dictionary requestContextData, GrainInterfaceType interfaceType, ushort interfaceVersion) { this.GrainIdentity = grainIdentity; this.InterfaceType = interfaceType; this.InterfaceVersion = interfaceVersion; this.RequestContextData = requestContextData; } /// /// Gets the grain being targeted. /// public GrainId GrainIdentity { get; } /// /// Gets the interface type of the interface which is being called on the grain which triggered this placement request. /// public GrainInterfaceType InterfaceType { get; } /// /// Gets the interface version being requested. /// public ushort InterfaceVersion { get; } /// /// Gets the dictionary for the request which triggered placement. /// public Dictionary RequestContextData { get; } } } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/IActivationRebalancer.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Placement.Rebalancing; /// /// A gateway to interface with the activation rebalancer. /// /// This is available only on the silo. public interface IActivationRebalancer { /// /// Returns the rebalancing report. /// The report can lag behind if you choose a session cycle period less than . /// /// If set to returns the most current report. /// Using incurs an asynchronous operation. ValueTask GetRebalancingReport(bool force = false); /// Task ResumeRebalancing(); /// Task SuspendRebalancing(TimeSpan? duration = null); /// /// Subscribe to activation rebalancer reports. /// /// The component that will be notified. void SubscribeToReports(IActivationRebalancerReportListener listener); /// /// Unsubscribe from activation rebalancer reports. /// /// The already subscribed component. void UnsubscribeFromReports(IActivationRebalancerReportListener listener); } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/IActivationRebalancerMonitor.cs ================================================ using System; using System.Threading.Tasks; #nullable enable namespace Orleans.Placement.Rebalancing; [Alias("IActivationRebalancerMonitor")] internal interface IActivationRebalancerMonitor : ISystemTarget, IActivationRebalancer { /// /// The period on which the must report back to the monitor. /// public static readonly TimeSpan WorkerReportPeriod = TimeSpan.FromSeconds(30); /// /// Invoked periodically by the . /// [Alias("Report")] Task Report(RebalancingReport report); } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/IActivationRebalancerReportListener.cs ================================================ namespace Orleans.Placement.Rebalancing; /// /// Interface for types which listen to rebalancer status changes. /// public interface IActivationRebalancerReportListener { /// /// Triggered when rebalancer has provided a new . /// /// Latest report from the rebalancer. void OnReport(RebalancingReport report); } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/IActivationRebalancerWorker.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Concurrency; namespace Orleans.Placement.Rebalancing; [Alias("IActivationRebalancerWorker")] internal interface IActivationRebalancerWorker : IGrainWithIntegerKey { /// /// Returns the most recent rebalancing report. /// /// Acts also as a way to wake up the rebalancer, if its deactivated. [AlwaysInterleave, Alias("GetReport")] ValueTask GetReport(); /// /// Resumes rebalancing if its suspended, otherwise its a no-op. /// [Alias("ResumeRebalancing")] Task ResumeRebalancing(); /// /// Suspends rebalancing if its running, otherwise its a no-op. /// /// /// The amount of time to suspend the rebalancer. /// means suspend indefinitely. /// [Alias("SuspendRebalancing")] Task SuspendRebalancing(TimeSpan? duration); } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/IFailedSessionBackoffProvider.cs ================================================ using Orleans.Internal; namespace Orleans.Placement.Rebalancing; /// /// Determines how long to wait between successive rebalancing sessions, if an aprior session has failed. /// /// /// A session is considered "failed" if n-consecutive number of cycles yielded no significant improvement /// to the cluster's entropy. /// public interface IFailedSessionBackoffProvider : IBackoffProvider { } ================================================ FILE: src/Orleans.Core/Placement/Rebalancing/RebalancingReport.cs ================================================ using System; using System.Collections.Immutable; using Orleans.Runtime; namespace Orleans.Placement.Rebalancing; /// /// The status of the . /// [GenerateSerializer] public enum RebalancerStatus : byte { /// /// It is executing. /// Executing = 0, /// /// It is suspended. /// Suspended = 1 } /// /// A report of the current state of the activation rebalancer. /// [GenerateSerializer, Immutable, Alias("RebalancingReport")] public readonly struct RebalancingReport { /// /// The silo where the rebalancer is currently located. /// [Id(0)] public required SiloAddress Host { get; init; } /// /// The current status of the rebalancer. /// [Id(1)] public required RebalancerStatus Status { get; init; } /// /// The amount of time the rebalancer is suspended (if at all). /// /// This will always be if is . [Id(2)] public TimeSpan? SuspensionDuration { get; init; } /// /// The current view of the cluster's imbalance. /// /// Range: [0-1] [Id(3)] public required double ClusterImbalance { get; init; } /// /// Latest rebalancing statistics. /// [Id(4)] public required ImmutableArray Statistics { get; init; } } /// /// Rebalancing statistics for the given . /// /// /// Used for diagnostics / metrics purposes. Note that statistics are an approximation. [GenerateSerializer, Immutable, Alias("RebalancingStatistics")] public readonly struct RebalancingStatistics { /// /// The time these statistics were assembled. /// [Id(0)] public required DateTime TimeStamp { get; init; } /// /// The silo to which these statistics belong to. /// [Id(1)] public required SiloAddress SiloAddress { get; init; } /// /// The approximate number of activations that have been dispersed from this silo thus far. /// [Id(2)] public required ulong DispersedActivations { get; init; } /// /// The approximate number of activations that have been acquired by this silo thus far. /// [Id(3)] public required ulong AcquiredActivations { get; init; } } ================================================ FILE: src/Orleans.Core/Placement/Repartitioning/IActivationRepartitionerSystemTarget.cs ================================================ using System.Threading.Tasks; using System.Collections.Immutable; using Orleans.Runtime; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System; namespace Orleans.Placement.Repartitioning; [Alias("IActivationRepartitionerSystemTarget")] internal interface IActivationRepartitionerSystemTarget : ISystemTarget { static IActivationRepartitionerSystemTarget GetReference(IGrainFactory grainFactory, SiloAddress targetSilo) => grainFactory.GetGrain(SystemTargetGrainId.Create(Constants.ActivationRepartitionerType, targetSilo).GrainId); [ResponseTimeout("00:10:00")] ValueTask TriggerExchangeRequest(); [ResponseTimeout("00:10:00")] ValueTask AcceptExchangeRequest(AcceptExchangeRequest request); /// /// For use in testing only! /// ValueTask ResetCounters(); /// /// For use in testing only! /// ValueTask GetActivationCount(); /// /// For use in testing only! /// ValueTask SetActivationCountOffset(int activationCountOffset); /// /// For diagnostics only. /// ValueTask> GetGrainCallFrequencies(); /// /// For use in testing only! Flushes buffered messages. /// ValueTask FlushBuffers(); } // We use a readonly struct so that we can fully decouple the message-passing and potentially modifications to the Silo fields. /// /// Data structure representing a 'communication edge' between a source and target. /// [GenerateSerializer, Immutable, DebuggerDisplay("Source: [{Source.Id} - {Source.Silo}] | Target: [{Target.Id} - {Target.Silo}]")] internal readonly struct Edge(EdgeVertex source, EdgeVertex target) : IEquatable { [Id(0)] public EdgeVertex Source { get; } = source; [Id(1)] public EdgeVertex Target { get; } = target; public static bool operator ==(Edge left, Edge right) => left.Equals(right); public static bool operator !=(Edge left, Edge right) => !left.Equals(right); public override bool Equals([NotNullWhen(true)] object obj) => obj is Edge other && Equals(other); public bool Equals(Edge other) => Source == other.Source && Target == other.Target; public override int GetHashCode() => HashCode.Combine(Source, Target); /// /// Returns a copy of this but with flipped sources and targets. /// public Edge Flip() => new(source: Target, target: Source); public override string ToString() => $"[{Source} -> {Target}]"; } /// /// Data structure representing one side of a . /// [GenerateSerializer, Immutable] public readonly struct EdgeVertex( GrainId id, SiloAddress silo, bool isMigratable) : IEquatable { [Id(0)] public readonly GrainId Id = id; [Id(1)] public readonly SiloAddress Silo = silo; [Id(2)] public readonly bool IsMigratable = isMigratable; public static bool operator ==(EdgeVertex left, EdgeVertex right) => left.Equals(right); public static bool operator !=(EdgeVertex left, EdgeVertex right) => !left.Equals(right); public override bool Equals([NotNullWhen(true)] object obj) => obj is EdgeVertex other && Equals(other); public bool Equals(EdgeVertex other) => Id == other.Id && Silo == other.Silo && IsMigratable == other.IsMigratable; public override int GetHashCode() => HashCode.Combine(Id, Silo, IsMigratable); public override string ToString() => $"[{Id}@{Silo}{(IsMigratable ? "" : "/NotMigratable")}]"; } /// /// A candidate vertex to be transferred to another silo. /// [GenerateSerializer, DebuggerDisplay("Id = {Id} | Accumulated = {AccumulatedTransferScore}")] internal sealed class CandidateVertex { /// /// The id of the candidate grain. /// [Id(0), Immutable] public GrainId Id { get; init; } /// /// The cost reduction expected from migrating the vertex with to another silo. /// [Id(1)] public long AccumulatedTransferScore { get; set; } /// /// These are all the vertices connected to the vertex with . /// /// These will be important when this vertex is removed from the max-sorted heap on the receiver silo. [Id(2), Immutable] public ImmutableArray ConnectedVertices { get; init; } = []; public override string ToString() => $"[{Id} * {AccumulatedTransferScore} -> [{string.Join(", ", ConnectedVertices)}]]"; } [GenerateSerializer, Immutable] public readonly struct CandidateConnectedVertex(GrainId id, long transferScore) { [Id(0)] public GrainId Id { get; } = id; [Id(1)] public long TransferScore { get; } = transferScore; public static bool operator ==(CandidateConnectedVertex left, CandidateConnectedVertex right) => left.Equals(right); public static bool operator !=(CandidateConnectedVertex left, CandidateConnectedVertex right) => !left.Equals(right); public override bool Equals([NotNullWhen(true)] object obj) => obj is CandidateConnectedVertex other && Equals(other); public bool Equals(CandidateConnectedVertex other) => Id == other.Id && TransferScore == other.TransferScore; public override int GetHashCode() => HashCode.Combine(Id, TransferScore); public override string ToString() => $"[{Id} * {TransferScore}]"; } [GenerateSerializer, Immutable] internal sealed class AcceptExchangeRequest(SiloAddress sendingSilo, ImmutableArray exchangeSet, int activationCountSnapshot) { /// /// The silo which is offering to transfer grains to us. /// [Id(0)] public SiloAddress SendingSilo { get; } = sendingSilo; /// /// The set of grains which the sending silo is offering to transfer to us. /// [Id(1)] public ImmutableArray ExchangeSet { get; } = exchangeSet; /// /// The activation count of the sending silo at the time of the exchange request. /// [Id(2)] public int ActivationCountSnapshot { get; } = activationCountSnapshot; } [GenerateSerializer, Immutable] internal sealed class AcceptExchangeResponse(AcceptExchangeResponse.ResponseType type, ImmutableArray acceptedGrains, ImmutableArray givenGrains) { public static readonly AcceptExchangeResponse CachedExchangedRecently = new(ResponseType.ExchangedRecently, [], []); public static readonly AcceptExchangeResponse CachedMutualExchangeAttempt = new(ResponseType.MutualExchangeAttempt, [], []); [Id(0)] public ResponseType Type { get; } = type; /// /// The grains which the sender is asking the receiver to transfer. /// [Id(1)] public ImmutableArray AcceptedGrainIds { get; } = acceptedGrains; /// /// The grains which the receiver is transferring to the sender. /// [Id(2)] public ImmutableArray GivenGrainIds { get; } = givenGrains; [GenerateSerializer] public enum ResponseType { /// /// The exchange was accepted and an exchange set is returned. /// Success, /// /// The other silo has been recently involved in another exchange. /// ExchangedRecently, /// /// An attempt to do an exchange between this and the other silo was about to happen at the same time. /// MutualExchangeAttempt } } ================================================ FILE: src/Orleans.Core/Placement/Repartitioning/IImbalanceToleranceRule.cs ================================================ namespace Orleans.Placement.Repartitioning; /// /// Represents a rule that controls the degree of imbalance between the number of grain activations (that is considered tolerable), when any pair of silos are exchanging activations. /// public interface IImbalanceToleranceRule { /// /// Checks if this rule is satisfied by . /// /// The imbalance between the exchanging silo pair that will be, if this method were to return bool IsSatisfiedBy(uint imbalance); } ================================================ FILE: src/Orleans.Core/Placement/Repartitioning/IMessageStatisticsSink.cs ================================================ #nullable enable using System; using Orleans.Runtime; namespace Orleans.Placement.Repartitioning; internal interface IMessageStatisticsSink { Action? GetMessageObserver(); } internal sealed class NoOpMessageStatisticsSink : IMessageStatisticsSink { public Action? GetMessageObserver() => null; } ================================================ FILE: src/Orleans.Core/Providers/ClientProviderRuntime.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Providers { /// /// for clients. /// /// internal class ClientProviderRuntime : IProviderRuntime { private readonly IInternalGrainFactory grainFactory; private readonly ClientGrainContext clientContext; /// /// Initializes a new instance of the class. /// /// The grain factory. /// The service provider. /// The client context. public ClientProviderRuntime( IInternalGrainFactory grainFactory, IServiceProvider serviceProvider, ClientGrainContext clientContext) { this.grainFactory = grainFactory; this.ServiceProvider = serviceProvider; this.clientContext = clientContext; } /// public IGrainFactory GrainFactory => this.grainFactory; /// public IServiceProvider ServiceProvider { get; } /// public (TExtension Extension, TExtensionInterface ExtensionReference) BindExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { return this.clientContext.GetOrSetExtension(newExtensionFunc); } } } ================================================ FILE: src/Orleans.Core/Providers/GrainStorageHelpers.cs ================================================ #nullable enable using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Orleans.Providers; namespace Orleans.Storage; /// /// Utility functions for grain storage. /// public static class GrainStorageHelpers { /// /// Gets the associated with the specified grain type, which must derive from . /// /// The grain type, which must derive from . /// The service provider. /// /// The associated with the specified grain type, which must derive from . /// public static IGrainStorage GetGrainStorage(Type grainType, IServiceProvider services) { if (grainType is null) throw new ArgumentNullException(nameof(grainType)); var attrs = grainType.GetCustomAttributes(typeof(StorageProviderAttribute), true); var attr = attrs.Length > 0 ? (StorageProviderAttribute)attrs[0] : null; var storageProvider = attr != null ? services.GetKeyedService(attr.ProviderName) : services.GetService(); if (storageProvider == null) { ThrowMissingProviderException(grainType, attr?.ProviderName); } return storageProvider; } [DoesNotReturn] private static void ThrowMissingProviderException(Type grainType, string? name) { var grainTypeName = grainType.FullName; var errMsg = string.IsNullOrEmpty(name) ? $"No default storage provider found loading grain type {grainTypeName}." : $"No storage provider named \"{name}\" found loading grain type {grainTypeName}."; throw new BadProviderConfigException(errMsg); } } ================================================ FILE: src/Orleans.Core/Providers/IControllable.cs ================================================ using System.Threading.Tasks; namespace Orleans.Providers { /// /// A general interface for controllable components inside Orleans runtime. /// public interface IControllable { /// /// A function to execute a control command. /// /// A serial number of the command. /// An opaque command argument. /// The value returned from the command handler. Task ExecuteCommand(int command, object arg); } } ================================================ FILE: src/Orleans.Core/Providers/IGrainStorage.cs ================================================ using System; using System.Runtime.Serialization; using System.Threading.Tasks; using Orleans.Runtime; using System.Net; namespace Orleans.Storage { /// /// Interface to be implemented for a storage able to read and write Orleans grain state data. /// public interface IGrainStorage { /// Read data function for this storage instance. /// Name of the state for this grain /// Grain ID /// State data object to be populated for this grain. /// The grain state type. /// Completion promise for the Read operation on the specified grain. Task ReadStateAsync(string stateName, GrainId grainId, IGrainState grainState); /// Write data function for this storage instance. /// Name of the state for this grain /// Grain ID /// State data object to be written for this grain. /// The grain state type. /// Completion promise for the Write operation on the specified grain. Task WriteStateAsync(string stateName, GrainId grainId, IGrainState grainState); /// Delete / Clear data function for this storage instance. /// Name of the state for this grain /// Grain ID /// Copy of last-known state data object for this grain. /// The grain state type. /// Completion promise for the Delete operation on the specified grain. Task ClearStateAsync(string stateName, GrainId grainId, IGrainState grainState); } /// /// Interface to be optionally implemented by storage to return richer exception details. /// TODO: Remove this interface. Move to decorator pattern for monitoring purposes. - jbragg /// public interface IRestExceptionDecoder { /// /// Decode details of the exception. /// /// Exception to decode. /// HTTP status code for the error. /// REST status for the error. /// Whether or not to extract REST error code. /// A value indicating whether the exception was decoded. bool DecodeException(Exception exception, out HttpStatusCode httpStatusCode, out string restStatus, bool getExtendedErrors = false); } /// /// Exception thrown when a storage detects an Etag inconsistency when attempting to perform a WriteStateAsync operation. /// [Serializable] [GenerateSerializer] public class InconsistentStateException : OrleansException { /// /// Gets or sets a value indicating whether this exception occurred on the current activation. /// [Id(0)] internal bool IsSourceActivation { get; set; } = true; /// Gets the Etag value currently held in persistent storage. [Id(1)] public string StoredEtag { get; private set; } /// Gets the Etag value currently help in memory, and attempting to be updated. [Id(2)] public string CurrentEtag { get; private set; } /// /// Initializes a new instance of the class. /// public InconsistentStateException() { } /// /// Initializes a new instance of the class. /// /// The message. public InconsistentStateException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public InconsistentStateException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] protected InconsistentStateException(SerializationInfo info, StreamingContext context) : base(info, context) { this.StoredEtag = info.GetString(nameof(StoredEtag)); this.CurrentEtag = info.GetString(nameof(CurrentEtag)); this.IsSourceActivation = info.GetBoolean(nameof(this.IsSourceActivation)); } /// /// Initializes a new instance of the class. /// /// The error message. /// The stored ETag. /// The current ETag. /// The inner exception. public InconsistentStateException( string errorMsg, string storedEtag, string currentEtag, Exception storageException) : base(errorMsg, storageException) { StoredEtag = storedEtag; CurrentEtag = currentEtag; } /// /// Initializes a new instance of the class. /// /// The error message. /// The stored ETag. /// The current ETag. public InconsistentStateException( string errorMsg, string storedEtag, string currentEtag) : this(errorMsg, storedEtag, currentEtag, null) { } /// /// Initializes a new instance of the class. /// /// The stored ETag. /// The current ETag. /// The storage exception. public InconsistentStateException(string storedEtag, string currentEtag, Exception storageException) : this(storageException.Message, storedEtag, currentEtag, storageException) { } /// public override string ToString() { return string.Format("InconsistentStateException: {0} Expected Etag={1} Received Etag={2} {3}", Message, StoredEtag, CurrentEtag, InnerException); } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException(nameof(info)); info.AddValue(nameof(StoredEtag), this.StoredEtag); info.AddValue(nameof(CurrentEtag), this.CurrentEtag); info.AddValue(nameof(this.IsSourceActivation), this.IsSourceActivation); base.GetObjectData(info, context); } } } ================================================ FILE: src/Orleans.Core/Providers/IGrainStorageSerializer.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Storage { /// /// Common interface for grain state serializers. /// public interface IGrainStorageSerializer { /// /// Serializes the object input. /// /// The object to serialize. /// The input type. /// The serialized input. BinaryData Serialize(T input); /// /// Deserializes the provided data. /// /// The data to deserialize. /// The output type. /// The deserialized object. T Deserialize(BinaryData input); } /// /// Extensions for . /// public static class GrainStorageSerializerExtensions { /// /// Deserializes the provided data. /// /// The grain state serializer. /// The data to deserialize. /// The output type. /// The deserialized object. public static T Deserialize(this IGrainStorageSerializer serializer, ReadOnlyMemory input) => serializer.Deserialize(new BinaryData(input)); } /// /// Interface to be implemented by the storage provider options. /// public interface IStorageProviderSerializerOptions { /// /// Gets or sets the serializer to use for this storage provider. /// IGrainStorageSerializer GrainStorageSerializer { get; set; } } /// /// Provides default configuration for . /// /// The options type. public class DefaultStorageProviderSerializerOptionsConfigurator : IPostConfigureOptions where TOptions : class, IStorageProviderSerializerOptions { private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// The service provider. public DefaultStorageProviderSerializerOptionsConfigurator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// public void PostConfigure(string name, TOptions options) { if (options.GrainStorageSerializer == default) { // First, try to get a IGrainStorageSerializer that was registered with // the same name as the storage provider // If none is found, fallback to system wide default options.GrainStorageSerializer = _serviceProvider.GetKeyedService(name) ?? _serviceProvider.GetRequiredService(); } } } } ================================================ FILE: src/Orleans.Core/Providers/ILeaseProvider.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.LeaseProviders { /// /// Acquired lease /// [GenerateSerializer, Immutable] public sealed class AcquiredLease { /// /// The resource key which the lease is attached to /// [Id(0)] public string ResourceKey { get; } /// /// Duration of the acquired lease /// [Id(1)] public TimeSpan Duration { get; } /// /// Lease token, which will be null if acquiring or renewing the lease failed /// [Id(2)] public string Token { get; } /// /// Caller side start time for this lease, which is when the lease is acquired or renewed /// [Id(3)] public DateTime StartTimeUtc { get; } /// /// Constructor /// /// /// /// /// public AcquiredLease(string resourceKey, TimeSpan duration, string token, DateTime startTimeUtc) { this.ResourceKey = resourceKey; this.Duration = duration; this.Token = token; this.StartTimeUtc = startTimeUtc; } /// /// Constructor /// /// public AcquiredLease(string resourceKey) { this.ResourceKey = resourceKey; } } /// /// AcquireLeaseResult class, which demonstrates result of acquiring or renewing lease operation /// [GenerateSerializer, Immutable] public sealed class AcquireLeaseResult { /// /// Acquired lease, which will be null if acquire or renew operation failed. /// [Id(0)] public AcquiredLease AcquiredLease { get; } /// /// Response status /// [Id(1)] public ResponseCode StatusCode { get; } /// /// If acquiring or renewing the lease failed, this is the exception which caused it. This field would be null if operation succeed. /// [Id(2)] public Exception FailureException { get; } public AcquireLeaseResult(AcquiredLease acquiredLease, ResponseCode statusCode, Exception failureException) { this.AcquiredLease = acquiredLease; this.StatusCode = statusCode; this.FailureException = failureException; } } [GenerateSerializer] public enum ResponseCode { /// /// Operation succeed /// OK, /// /// Lease is owned by other entity /// LeaseNotAvailable, /// /// The token in the AcquiredLease is invalid, which means the lease expired /// InvalidToken, /// /// TransientFailure, which should be retriable. /// TransientFailure } /// /// Lease request where you can specify ResourceKey and duration of your lease. /// [GenerateSerializer, Immutable] public sealed class LeaseRequest { /// /// The key of the resource where you want to apply the lease on /// [Id(0)] public string ResourceKey { get; } /// /// Duration of the lease /// [Id(1)] public TimeSpan Duration { get; } /// /// Constructor /// /// /// public LeaseRequest(string resourceKey, TimeSpan duration) { this.ResourceKey = resourceKey; this.Duration = duration; } } /// /// Lease provider interface /// public interface ILeaseProvider { /// /// Batch acquire leases operation /// /// resource category /// /// Lease acquiring results array, whose order is the same with leaseRequstes Task Acquire(string category, LeaseRequest[] leaseRequests); /// /// Batch renew lease operation /// /// resource category /// /// Lease renew results array, whose order is the same with acquiredLeases Task Renew(string category, AcquiredLease[] aquiredLeases); /// /// Batch release lease operation /// /// resource category /// /// Task Release(string category, AcquiredLease[] aquiredLeases); } } ================================================ FILE: src/Orleans.Core/Providers/IMemoryStorageGrain.cs ================================================ using System.Threading.Tasks; namespace Orleans.Storage { /// /// Grain interface for internal memory storage grain used by Orleans in-memory storage provider. /// public interface IMemoryStorageGrain : IGrainWithIntegerKey { /// Async method to cause retrieval of the specified grain state data from memory store. /// Store key for this grain. /// Value promise for the currently stored grain state for the specified grain. Task> ReadStateAsync(string grainStoreKey); /// Async method to cause update of the specified grain state data into memory store. /// Grain ID. /// New state data to be stored for this grain. /// Completion promise with new eTag for the update operation for stored grain state for the specified grain. Task WriteStateAsync(string grainStoreKey, IGrainState grainState); /// Store key for this grain. /// The previous etag that was read. /// Completion promise for the update operation for stored grain state for the specified grain. Task DeleteStateAsync(string grainStoreKey, string eTag); } } ================================================ FILE: src/Orleans.Core/Providers/IProviderRuntime.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Providers { /// /// Interface to allow callbacks from providers into their assigned provider-manager. /// This allows access to runtime functionality, such as logging. /// public interface IProviderRuntime { /// /// Gets factory for getting references to grains. /// IGrainFactory GrainFactory { get; } /// /// Gets service provider for dependency injection. /// IServiceProvider ServiceProvider { get; } /// /// Binds an extension to an addressable object, if not already done. /// /// The type of the extension (e.g. StreamConsumerExtension). /// The public interface type of the implementation. /// A factory function that constructs a new extension object. /// A tuple, containing first the extension and second an addressable reference to the extension's interface. (TExtension Extension, TExtensionInterface ExtensionReference) BindExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension; } } ================================================ FILE: src/Orleans.Core/Providers/IStorageProvider.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Storage { /// /// Exception thrown whenever a grain call is attempted with a bad / missing storage provider configuration settings for that grain. /// [Serializable, GenerateSerializer] public sealed class BadProviderConfigException : OrleansException { public BadProviderConfigException() { } public BadProviderConfigException(string msg) : base(msg) { } public BadProviderConfigException(string msg, Exception exc) : base(msg, exc) { } [Obsolete] private BadProviderConfigException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Providers/ProviderInitializationException.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Providers { /// /// Exception thrown whenever a provider has failed to be initialized. /// [Serializable, GenerateSerializer] public sealed class ProviderInitializationException : OrleansException { /// /// Initializes a new instance of the class. /// public ProviderInitializationException() { } /// /// Initializes a new instance of the class. /// /// The message. public ProviderInitializationException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public ProviderInitializationException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private ProviderInitializationException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Providers/ProviderStateManager.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Providers { internal enum ProviderState { None, Initialized, Started, Closed } internal class ProviderStateManager { public ProviderState State { get; private set; } private ProviderState presetState; public ProviderStateManager() { State = ProviderState.None; } public bool PresetState(ProviderState state) { presetState = state; switch (state) { case ProviderState.None: throw new ProviderStateException("Provider state can not be set to none."); case ProviderState.Initialized: switch(State) { case ProviderState.None: return true; } break; case ProviderState.Started: switch(State) { case ProviderState.None: throw new ProviderStateException("Trying to start a provider that hasn't been initialized."); case ProviderState.Initialized: return true; case ProviderState.Closed: throw new ProviderStateException("Trying to start a provider that has been closed."); } break; case ProviderState.Closed: switch (State) { case ProviderState.None: throw new ProviderStateException("Trying to close a provider that hasn't been initialized."); case ProviderState.Initialized: case ProviderState.Started: return true; } return true; } return false; } public void CommitState() { State = presetState; } } [Serializable, GenerateSerializer] public sealed class ProviderStateException : OrleansException { public ProviderStateException() : base("Unexpected provider state") { } public ProviderStateException(string message) : base(message) { } public ProviderStateException(string message, Exception innerException) : base(message, innerException) { } [Obsolete] private ProviderStateException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core/Providers/StorageSerializer/GrainStorageSerializer.cs ================================================ using System; namespace Orleans.Storage { /// /// Provides functionality for serializing and deserializing grain state, delegating to a prefered and fallback implementation of . /// public class GrainStorageSerializer : IGrainStorageSerializer { private readonly IGrainStorageSerializer _serializer; private readonly IGrainStorageSerializer _fallbackDeserializer; /// /// Initializes a new instance of the class. /// /// The grain storage serializer. /// The fallback grain storage serializer. public GrainStorageSerializer(IGrainStorageSerializer serializer, IGrainStorageSerializer fallbackDeserializer) { _serializer = serializer; _fallbackDeserializer = fallbackDeserializer; } /// public BinaryData Serialize(T input) => _serializer.Serialize(input); /// public T Deserialize(BinaryData input) { try { return _serializer.Deserialize(input); } catch (Exception ex1) { try { return _fallbackDeserializer.Deserialize(input); } catch (Exception ex2) { throw new AggregateException("Failed to deserialize input", ex1, ex2); } } } } } ================================================ FILE: src/Orleans.Core/Providers/StorageSerializer/JsonGrainStorageSerializer.cs ================================================ using System; using Orleans.Serialization; namespace Orleans.Storage { /// /// Grain storage serializer that uses Newtonsoft.Json /// public class JsonGrainStorageSerializer : IGrainStorageSerializer { private readonly OrleansJsonSerializer _orleansJsonSerializer; /// /// Initializes a new instance of the class. /// public JsonGrainStorageSerializer(OrleansJsonSerializer orleansJsonSerializer) { _orleansJsonSerializer = orleansJsonSerializer; } /// public BinaryData Serialize(T value) { var data = _orleansJsonSerializer.Serialize(value, typeof(T)); return new BinaryData(data); } /// public T Deserialize(BinaryData input) { return (T)_orleansJsonSerializer.Deserialize(typeof(T), input.ToString()); } } } ================================================ FILE: src/Orleans.Core/Providers/StorageSerializer/OrleansGrainStateSerializer.cs ================================================ using System; using System.Buffers; using Orleans.Serialization; namespace Orleans.Storage { /// /// Grain storage serializer that uses the Orleans . /// public class OrleansGrainStorageSerializer : IGrainStorageSerializer { private readonly Serializer serializer; /// /// Initializes a new instance of the class. /// /// The serializer. public OrleansGrainStorageSerializer(Serializer serializer) { this.serializer = serializer; } /// public BinaryData Serialize(T value) { var buffer = new ArrayBufferWriter(); this.serializer.Serialize(value, buffer); return new BinaryData(buffer.WrittenMemory); } /// public T Deserialize(BinaryData input) { return this.serializer.Deserialize(input.ToMemory()); } } } ================================================ FILE: src/Orleans.Core/README.md ================================================ # Microsoft Orleans Core Library ## Introduction Microsoft Orleans Core is the primary library used by both client and server applications. It provides the runtime components necessary for Orleans applications, including serialization, communication, and the core hosting infrastructure. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Core ``` This package is automatically included when you reference the Orleans SDK or the Orleans client/server metapackages. ## Example - Configuring a Client ```csharp using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Configuration; using System; using System.Threading.Tasks; // Define a grain interface namespace MyGrainNamespace; public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } // Implement the grain interface public class HelloGrain : Grain, IHelloGrain { public Task SayHello(string greeting) { return Task.FromResult($"Hello! I got: {greeting}"); } } // Create a client var builder = Host.CreateApplicationBuilder(args) .UseOrleansClient(client => { client.UseLocalhostClustering(); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var grain = host.Services.GetRequiredService().GetGrain("grain-id"); var response = await grain.SayHello("Hello from client!"); // Print the result Console.WriteLine($"Response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Client Configuration](https://learn.microsoft.com/en-us/dotnet/orleans/host/client) - [Dependency Injection](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/dependency-injection) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Core/Runtime/AsyncEnumerableGrainExtension.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Timers; namespace Orleans.Runtime; /// /// Grain-side support for returning from grain methods. /// internal sealed partial class AsyncEnumerableGrainExtension : IAsyncEnumerableGrainExtension, IAsyncDisposable, IDisposable { private static readonly DiagnosticListener DiagnosticListener = new("Orleans.Runtime.AsyncEnumerableGrainExtension"); private readonly Dictionary _enumerators = []; private readonly ILogger _logger; private readonly MessagingOptions _messagingOptions; // Internal for testing internal IGrainTimer Timer { get; } internal IGrainContext GrainContext { get; } /// /// Initializes a new instance. /// /// The grain which this extension is attached to. public AsyncEnumerableGrainExtension( IGrainContext grainContext, IOptions messagingOptions, ILogger logger) { _logger = logger; GrainContext = grainContext; _messagingOptions = messagingOptions.Value; var registry = GrainContext.GetComponent(); var cleanupPeriod = messagingOptions.Value.ResponseTimeout; Timer = registry.RegisterGrainTimer( GrainContext, static async (state, cancellationToken) => await state.RemoveExpiredAsync(cancellationToken), this, new() { DueTime = cleanupPeriod, Period = cleanupPeriod, Interleave = true, KeepAlive = false }); OnAsyncEnumeratorGrainExtensionCreated(this); } /// public ValueTask DisposeAsync(Guid requestId) => RemoveEnumeratorAsync(requestId); private async ValueTask RemoveExpiredAsync(CancellationToken cancellationToken) { List toRemove = default; foreach (var (requestId, state) in _enumerators) { if (MarkAndCheck(requestId)) { toRemove ??= []; toRemove.Add(requestId); } bool MarkAndCheck(Guid requestId) { ref var state = ref CollectionsMarshal.GetValueRefOrNullRef(_enumerators, requestId); if (Unsafe.IsNullRef(ref state)) { return false; } // Returns true if no flags were set. return state.ClearSeen(); } } List tasks = default; if (toRemove is not null) { foreach (var requestId in toRemove) { var removeTask = RemoveEnumeratorAsync(requestId); if (!removeTask.IsCompletedSuccessfully) { tasks ??= []; tasks.Add(removeTask.AsTask()); } } } if (tasks is { Count: > 0 }) { await Task.WhenAll(tasks).WaitAsync(cancellationToken); } OnEnumeratorCleanupCompleted(this); } /// public ValueTask<(EnumerationResult Status, object Value)> StartEnumeration(Guid requestId, [Immutable] IAsyncEnumerableRequest request, CancellationToken cancellationToken) { ref var entry = ref CollectionsMarshal.GetValueRefOrAddDefault(_enumerators, requestId, out bool exists); if (exists) { return ThrowAlreadyExists(); } request.SetTarget(GrainContext); var cts = CancellationTokenSource.CreateLinkedTokenSource(request.GetCancellationToken()); var enumerable = request.InvokeImplementation(); var enumerator = enumerable.GetAsyncEnumerator(cts.Token); entry.Enumerator = enumerator; entry.MaxBatchSize = request.MaxBatchSize; entry.CancellationTokenSource = cts; Debug.Assert(entry.MaxBatchSize > 0, "Max batch size must be positive."); return MoveNextCore(ref entry, requestId, enumerator, cancellationToken); static ValueTask<(EnumerationResult Status, object Value)> ThrowAlreadyExists() => ValueTask.FromException<(EnumerationResult Status, object Value)>(new InvalidOperationException("An enumerator with the same id already exists.")); } /// public ValueTask<(EnumerationResult Status, object Value)> MoveNext(Guid requestId, CancellationToken cancellationToken) { ref var entry = ref CollectionsMarshal.GetValueRefOrNullRef(_enumerators, requestId); if (Unsafe.IsNullRef(ref entry)) { return new((EnumerationResult.MissingEnumeratorError, default)); } if (entry.Enumerator is not IAsyncEnumerator typedEnumerator) { throw new InvalidCastException("Attempted to access an enumerator of the wrong type."); } return MoveNextCore(ref entry, requestId, typedEnumerator, cancellationToken); } private ValueTask<(EnumerationResult Status, object Value)> MoveNextCore( ref EnumeratorState entry, Guid requestId, IAsyncEnumerator typedEnumerator, CancellationToken cancellationToken) { Debug.Assert(entry.MaxBatchSize > 0, "Max batch size must be positive."); entry.SetSeen(); try { var currentBatchSize = 0; if (entry.MoveNextTask is null) { ValueTask moveNextValueTask; object result = null; do { // Check if the enumerator has a result ready synchronously. moveNextValueTask = typedEnumerator.MoveNextAsync(); if (moveNextValueTask.IsCompletedSuccessfully) { var hasValue = moveNextValueTask.Result; if (hasValue) { // Account for the just-emitted element. ++currentBatchSize; var value = typedEnumerator.Current; if (currentBatchSize == 1) { result = value; } else if (currentBatchSize == 2) { // Grow from a single element to a list. result = new List { (T)result, value }; } else { ((List)result).Add(value); } } else { // Completed successfully, possibly with some final elements. if (currentBatchSize == 0) { return OnTerminateAsync(requestId, EnumerationResult.Completed, default); } else if (currentBatchSize == 1) { return OnTerminateAsync(requestId, EnumerationResult.CompletedWithElement, result); } return OnTerminateAsync(requestId, EnumerationResult.CompletedWithBatch, result); } } else { // The enumerator did not complete synchronously, so we need to await the result for subsequent elements. entry.MoveNextTask = moveNextValueTask.AsTask(); break; } } while (currentBatchSize < entry.MaxBatchSize); // If there are elements, return them now instead of waiting for the pending operation to complete. if (currentBatchSize == 1) { return new((EnumerationResult.Element, result)); } else if (currentBatchSize > 1) { return new((EnumerationResult.Batch, result)); } // There are no elements, so wait for the pending operation to complete. } // Prevent the enumerator from being collected while we are enumerating it. entry.SetBusy(); return AwaitMoveNextAsync(requestId, typedEnumerator, entry.MoveNextTask, cancellationToken); } catch (Exception exception) { return OnTerminateAsync(requestId, EnumerationResult.Error, exception); } } private async ValueTask<(EnumerationResult Status, object Value)> AwaitMoveNextAsync( Guid requestId, IAsyncEnumerator typedEnumerator, Task moveNextTask, CancellationToken cancellationToken) { try { // Wait for either the MoveNextAsync task to complete or the polling timeout to elapse. using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var longPollingTimeout = _messagingOptions.ConfiguredResponseTimeout / 2; cts.CancelAfter(longPollingTimeout); await moveNextTask.WaitAsync(cts.Token).SuppressThrowing(); // Update the enumerator state to indicate that we are not currently waiting for MoveNextAsync to complete. // If the MoveNextAsync task completed then clear that now, too. UpdateEnumeratorState(requestId, clearMoveNextTask: moveNextTask.IsCompleted); void UpdateEnumeratorState(Guid requestId, bool clearMoveNextTask) { ref var state = ref CollectionsMarshal.GetValueRefOrNullRef(_enumerators, requestId); if (Unsafe.IsNullRef(ref state)) { return; } state.ClearBusy(); if (clearMoveNextTask) { state.MoveNextTask = null; } } if (moveNextTask.IsCompletedSuccessfully) { var hasValue = moveNextTask.GetAwaiter().GetResult(); if (hasValue) { return (EnumerationResult.Element, typedEnumerator.Current); } else { await RemoveEnumeratorAsync(requestId); return (EnumerationResult.Completed, default); } } else if (moveNextTask.IsCanceled || cancellationToken.IsCancellationRequested) { await RemoveEnumeratorAsync(requestId); return (EnumerationResult.Canceled, default); } else if (moveNextTask.Exception is { } moveNextException) { // Completed, but not successfully. var exception = moveNextException.InnerExceptions.Count == 1 ? moveNextException.InnerException : moveNextException; await RemoveEnumeratorAsync(requestId); return (EnumerationResult.Error, exception); } return (EnumerationResult.Heartbeat, default); } catch (Exception exception) { await RemoveEnumeratorAsync(requestId); return (EnumerationResult.Error, exception); } } private async ValueTask RemoveEnumeratorAsync(Guid requestId) { if (_enumerators.Remove(requestId, out var state)) { await DisposeEnumeratorAsync(state); } } private async ValueTask<(EnumerationResult Status, object Value)> OnTerminateAsync(Guid requestId, EnumerationResult status, object value) { await RemoveEnumeratorAsync(requestId); return (status, value); } /// public async ValueTask DisposeAsync() { if (_enumerators.Count > 0) { var enumerators = new List(_enumerators.Values); _enumerators.Clear(); foreach (var enumerator in enumerators) { await DisposeEnumeratorAsync(enumerator); } } Timer.Dispose(); } private async ValueTask DisposeEnumeratorAsync(EnumeratorState enumerator) { try { enumerator.CancellationTokenSource.Cancel(); } catch (Exception exception) { LogWarningErrorCancellingEnumerator(exception); } try { using var cts = new CancellationTokenSource(_messagingOptions.ResponseTimeout); if (enumerator.MoveNextTask is { } task) { await task.WaitAsync(cts.Token).SuppressThrowing(); } if (enumerator.MoveNextTask is null or { IsCompleted: true } && enumerator.Enumerator is { } value) { await value.DisposeAsync().AsTask().WaitAsync(cts.Token).SuppressThrowing(); } } catch (Exception exception) { LogWarningErrorDisposingEnumerator(exception); } } /// public void Dispose() { Timer.Dispose(); } private static void OnAsyncEnumeratorGrainExtensionCreated(AsyncEnumerableGrainExtension extension) { if (DiagnosticListener.IsEnabled()) { DiagnosticListener.Write(nameof(OnAsyncEnumeratorGrainExtensionCreated), extension); } } private static void OnEnumeratorCleanupCompleted(AsyncEnumerableGrainExtension extension) { if (DiagnosticListener.IsEnabled()) { DiagnosticListener.Write(nameof(OnEnumeratorCleanupCompleted), extension); } } private struct EnumeratorState { private const int SeenFlag = 0x01; private const int BusyFlag = 0x10; private int _flags; public IAsyncDisposable Enumerator; public Task MoveNextTask; public int MaxBatchSize; internal CancellationTokenSource CancellationTokenSource; public void SetSeen() => _flags |= SeenFlag; public void SetBusy() => _flags |= BusyFlag | SeenFlag; public void ClearBusy() => _flags = SeenFlag; // Clear the 'Busy' flag, but set the 'Seen' flag. public bool ClearSeen() { // Clear the 'Seen' flag and check if any flags were set previously. var isExpired = _flags == 0; _flags &= ~SeenFlag; return isExpired; } } [LoggerMessage( Level = LogLevel.Warning, Message = "Error cancelling enumerator." )] private partial void LogWarningErrorCancellingEnumerator(Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Error disposing enumerator." )] private partial void LogWarningErrorDisposingEnumerator(Exception exception); } ================================================ FILE: src/Orleans.Core/Runtime/CallbackData.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Threading; using Microsoft.Extensions.Logging; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { internal sealed partial class CallbackData { private readonly SharedCallbackData shared; private readonly IResponseCompletionSource context; private readonly ApplicationRequestInstruments _applicationRequestInstruments; private int completed; private StatusResponse? lastKnownStatus; private ValueStopwatch stopwatch; private CancellationTokenRegistration _cancellationTokenRegistration; public CallbackData( SharedCallbackData shared, IResponseCompletionSource ctx, Message msg, ApplicationRequestInstruments applicationRequestInstruments) { this.shared = shared; this.context = ctx; this.Message = msg; _applicationRequestInstruments = applicationRequestInstruments; this.stopwatch = ValueStopwatch.StartNew(); } public Message Message { get; } // might hold metadata used by response pipeline public bool IsCompleted => this.completed == 1; public void SubscribeForCancellation(CancellationToken cancellationToken) { if (!cancellationToken.CanBeCanceled) { return; } cancellationToken.ThrowIfCancellationRequested(); _cancellationTokenRegistration = cancellationToken.UnsafeRegister(static arg => { var callbackData = (CallbackData)arg!; callbackData.OnCancellation(); }, this); } private void SignalCancellation() { // Only cancel requests which honor cancellation token. // Not all targets support IGrainCallCancellationExtension, so sending a cancellation in those cases could result in an error. // There are opportunities to cancel requests at the infrastructure layer which this will not exploit if the target method does not support cancellation. if (Message.BodyObject is IInvokable invokable && invokable.IsCancellable) { shared.CancellationManager?.SignalCancellation(Message.TargetSilo, Message.TargetGrain, Message.SendingGrain, Message.Id); } } public void OnStatusUpdate(StatusResponse status) { this.lastKnownStatus = status; } public bool IsExpired(long currentTimestamp) { var duration = currentTimestamp - this.stopwatch.GetRawTimestamp(); return duration > GetResponseTimeoutStopwatchTicks(); } private long GetResponseTimeoutStopwatchTicks() { var defaultResponseTimeout = (Message.BodyObject as IInvokable)?.GetDefaultResponseTimeout(); if (defaultResponseTimeout.HasValue) { return (long)(defaultResponseTimeout.Value.TotalSeconds * Stopwatch.Frequency); } return shared.ResponseTimeoutStopwatchTicks; } private TimeSpan GetResponseTimeout() => (Message.BodyObject as IInvokable)?.GetDefaultResponseTimeout() ?? shared.ResponseTimeout; private void OnCancellation() { // If waiting for acknowledgement is enabled, simply signal to the remote grain that cancellation // is requested and return. if (shared.WaitForCancellationAcknowledgement) { SignalCancellation(); return; } // Otherwise, cancel the request immediately, without waiting for the callee to acknowledge the // cancellation request. The callee will still be signaled. if (Interlocked.CompareExchange(ref completed, 1, 0) != 0) { return; } stopwatch.Stop(); SignalCancellation(); shared.Unregister(Message); _applicationRequestInstruments.OnAppRequestsEnd((long)stopwatch.Elapsed.TotalMilliseconds); _applicationRequestInstruments.OnAppRequestsTimedOut(); OrleansCallBackDataEvent.Log.OnCanceled(Message); context.Complete(Response.FromException(new OperationCanceledException(_cancellationTokenRegistration.Token))); _cancellationTokenRegistration.Dispose(); } public void OnTimeout() { if (Interlocked.CompareExchange(ref completed, 1, 0) != 0) { return; } this.stopwatch.Stop(); if (shared.CancelRequestOnTimeout) { SignalCancellation(); } this.shared.Unregister(this.Message); _cancellationTokenRegistration.Dispose(); _applicationRequestInstruments.OnAppRequestsEnd((long)this.stopwatch.Elapsed.TotalMilliseconds); _applicationRequestInstruments.OnAppRequestsTimedOut(); OrleansCallBackDataEvent.Log.OnTimeout(this.Message); var msg = this.Message; // Local working copy var statusMessage = lastKnownStatus is StatusResponse status ? $"Last known status is {status}. " : string.Empty; var timeout = GetResponseTimeout(); LogTimeout(this.shared.Logger, timeout, msg, statusMessage); var exception = new TimeoutException($"Response did not arrive on time in {timeout} for message: {msg}. {statusMessage}"); context.Complete(Response.FromException(exception)); } public void OnTargetSiloFail() { if (Interlocked.CompareExchange(ref this.completed, 1, 0) != 0) { return; } this.stopwatch.Stop(); this.shared.Unregister(this.Message); _cancellationTokenRegistration.Dispose(); _applicationRequestInstruments.OnAppRequestsEnd((long)this.stopwatch.Elapsed.TotalMilliseconds); OrleansCallBackDataEvent.Log.OnTargetSiloFail(this.Message); var msg = this.Message; var statusMessage = lastKnownStatus is StatusResponse status ? $"Last known status is {status}. " : string.Empty; LogTargetSiloFail(this.shared.Logger, msg, statusMessage, Constants.TroubleshootingHelpLink); var exception = new SiloUnavailableException($"The target silo became unavailable for message: {msg}. {statusMessage}See {Constants.TroubleshootingHelpLink} for troubleshooting help."); this.context.Complete(Response.FromException(exception)); } public void DoCallback(Message response) { if (Interlocked.CompareExchange(ref this.completed, 1, 0) != 0) { return; } OrleansCallBackDataEvent.Log.DoCallback(this.Message); this.stopwatch.Stop(); _cancellationTokenRegistration.Dispose(); _applicationRequestInstruments.OnAppRequestsEnd((long)this.stopwatch.Elapsed.TotalMilliseconds); // do callback outside the CallbackData lock. Just not a good practice to hold a lock for this unrelated operation. ResponseCallback(response, this.context); } private static void ResponseCallback(Message message, IResponseCompletionSource context) { try { var body = message.BodyObject; if (body is Response response) { context.Complete(response); } else { HandleRejectionResponse(context, body as RejectionResponse); } } catch (Exception exc) { // catch the exception and break the promise with it. context.Complete(Response.FromException(exc)); } static void HandleRejectionResponse(IResponseCompletionSource context, RejectionResponse? rejection) { Exception exception; if (rejection?.RejectionType is Message.RejectionTypes.GatewayTooBusy) { exception = new GatewayTooBusyException(); } else { exception = rejection?.Exception ?? new OrleansMessageRejectionException(rejection?.RejectionInfo ?? "Unable to send request - no rejection info available"); } context.Complete(Response.FromException(exception)); } } [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100157, Level = LogLevel.Warning, Message = "Response did not arrive on time in '{Timeout}' for message: '{Message}'. {StatusMessage}About to break its promise." )] private static partial void LogTimeout(ILogger logger, TimeSpan timeout, Message message, string statusMessage); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100157, Level = LogLevel.Warning, Message = "The target silo became unavailable for message: '{Message}'. {StatusMessage}See {TroubleshootingHelpLink} for troubleshooting help. About to break its promise." )] private static partial void LogTargetSiloFail(ILogger logger, Message message, string statusMessage, string troubleshootingHelpLink); } } ================================================ FILE: src/Orleans.Core/Runtime/ClientGrainContext.cs ================================================ #nullable enable using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.Extensions.DependencyInjection; namespace Orleans { internal sealed class ClientGrainContext : IGrainContext, IGrainExtensionBinder, IGrainContextAccessor { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly ConcurrentDictionary _extensions = new(); private readonly ConcurrentDictionary _components = new(); private readonly OutsideRuntimeClient _runtimeClient; private GrainReference? _grainReference; public ClientGrainContext(OutsideRuntimeClient runtimeClient) { _runtimeClient = runtimeClient; } public GrainReference GrainReference => _grainReference ??= (GrainReference)_runtimeClient.InternalGrainFactory.GetGrain(this.GrainId); public GrainId GrainId => _runtimeClient.CurrentActivationAddress.GrainId; public object? GrainInstance => null; public ActivationId ActivationId => _runtimeClient.CurrentActivationAddress.ActivationId; public GrainAddress Address => _runtimeClient.CurrentActivationAddress; public IServiceProvider ActivationServices => _runtimeClient.ServiceProvider; public IGrainLifecycle ObservableLifecycle => throw new NotSupportedException(); IGrainContext IGrainContextAccessor.GrainContext => this; public IWorkItemScheduler Scheduler => throw new NotSupportedException(); public bool Equals(IGrainContext? other) => ReferenceEquals(this, other); public object? GetComponent(Type componentType) { if (componentType.IsAssignableFrom(GetType())) return this; if (_components.TryGetValue(componentType, out var result)) { return result; } else if (componentType == typeof(PlacementStrategy)) { return ClientObserversPlacement.Instance; } lock (_lockObj) { if (ActivationServices.GetService(componentType) is { } activatedComponent) { return _components.GetOrAdd(componentType, activatedComponent); } } return default; } public object? GetTarget() => this; public void SetComponent(TComponent? instance) where TComponent : class { if (this is TComponent) { throw new ArgumentException("Cannot override a component which is implemented by the client context"); } lock (_lockObj) { if (instance == null) { _components.Remove(typeof(TComponent), out _); return; } _components[typeof(TComponent)] = instance; } } public (TExtension, TExtensionInterface) GetOrSetExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { (TExtension, TExtensionInterface) result; if (this.TryGetExtension(out result)) { return result; } lock (_lockObj) { if (this.TryGetExtension(out result)) { return result; } var implementation = newExtensionFunc(); var reference = _runtimeClient.InternalGrainFactory.CreateObjectReference(implementation); _extensions[typeof(TExtensionInterface)] = (implementation, reference); result = (implementation, reference); return result; } } private bool TryGetExtension(out (TExtension, TExtensionInterface) result) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { if (_extensions.TryGetValue(typeof(TExtensionInterface), out var existing)) { if (existing.Implementation is TExtension typedResult) { result = (typedResult, existing.Reference.AsReference()); return true; } throw new InvalidCastException($"Cannot cast existing extension of type {existing.Implementation} to target type {typeof(TExtension)}"); } result = default; return false; } private bool TryGetExtension([NotNullWhen(true)] out TExtensionInterface? result) where TExtensionInterface : IGrainExtension { if (_extensions.TryGetValue(typeof(TExtensionInterface), out var existing)) { result = (TExtensionInterface)existing.Implementation; return true; } result = default; return false; } public TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension { if (this.TryGetExtension(out var result)) { return result; } lock (_lockObj) { if (this.TryGetExtension(out result)) { return result; } var implementation = this.ActivationServices.GetKeyedService(typeof(TExtensionInterface)); if (implementation is null) { throw new GrainExtensionNotInstalledException($"No extension of type {typeof(TExtensionInterface)} is installed on this instance and no implementations are registered for automated install"); } var reference = this.GrainReference.Cast(); _extensions[typeof(TExtensionInterface)] = (implementation, reference); result = (TExtensionInterface)implementation; return result; } } public void ReceiveMessage(object message) => throw new NotSupportedException(); public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { } public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken) { } public void Rehydrate(IRehydrationContext context) { // Migration is not supported, but we need to dispose of the context if it's provided (context as IDisposable)?.Dispose(); } public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken) { // Migration is not supported. Do nothing: the contract is that this method attempts migration, but does not guarantee it will occur. } public Task Deactivated => Task.CompletedTask; } } ================================================ FILE: src/Orleans.Core/Runtime/ClientLocalActivationStatusChecker.cs ================================================ #nullable enable namespace Orleans.Runtime; internal sealed class ClientLocalActivationStatusChecker : ILocalActivationStatusChecker { public bool IsLocallyActivated(GrainId grainId) => false; } ================================================ FILE: src/Orleans.Core/Runtime/ClusterConnectionStatusObserverAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.Extensions.Logging; namespace Orleans.Runtime; internal sealed partial class ClusterConnectionStatusObserverAdaptor( IEnumerable gatewayCountChangedHandlers, IEnumerable connectionLostHandlers, ILogger logger) : IClusterConnectionStatusObserver { private readonly ImmutableArray _gatewayCountChangedHandlers = gatewayCountChangedHandlers.ToImmutableArray(); private readonly ImmutableArray _connectionLostHandler = connectionLostHandlers.ToImmutableArray(); public void NotifyClusterConnectionLost() { foreach (var handler in _connectionLostHandler) { try { handler(null, EventArgs.Empty); } catch (Exception ex) { LogErrorSendingClusterConnectionLostNotification(logger, ex); } } } public void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways, bool connectionRecovered) { var args = new GatewayCountChangedEventArgs(currentNumberOfGateways, previousNumberOfGateways); foreach (var handler in _gatewayCountChangedHandlers) { try { handler(null, args); } catch (Exception ex) { LogErrorSendingGatewayCountChangedNotification(logger, ex); } } } [LoggerMessage( Level = LogLevel.Error, Message = "Error sending cluster connection lost notification." )] private static partial void LogErrorSendingClusterConnectionLostNotification(ILogger logger, Exception ex); [LoggerMessage( Level = LogLevel.Error, Message = "Error sending gateway count changed notification." )] private static partial void LogErrorSendingGatewayCountChangedNotification(ILogger logger, Exception ex); } ================================================ FILE: src/Orleans.Core/Runtime/Constants.cs ================================================ using System; using System.Collections.Frozen; using System.Collections.Generic; namespace Orleans.Runtime { internal static class Constants { public const string TroubleshootingHelpLink = "https://aka.ms/orleans-troubleshooting"; public static readonly GrainType DirectoryServiceType = SystemTargetGrainId.CreateGrainType("dir.mem"); public static readonly GrainType DirectoryCacheValidatorType = SystemTargetGrainId.CreateGrainType("dir.cache-validator"); public static readonly GrainType ClientDirectoryType = SystemTargetGrainId.CreateGrainType("dir.client"); public static readonly GrainType SiloControlType = SystemTargetGrainId.CreateGrainType("silo-control"); public static readonly GrainType SiloMetadataType = SystemTargetGrainId.CreateGrainType("silo-metadata"); public static readonly GrainType CatalogType = SystemTargetGrainId.CreateGrainType("catalog"); public static readonly GrainType MembershipServiceType = SystemTargetGrainId.CreateGrainType("clustering"); public static readonly GrainType SystemMembershipTableType = SystemTargetGrainId.CreateGrainType("clustering.dev"); public static readonly GrainType DeploymentLoadPublisherSystemTargetType = SystemTargetGrainId.CreateGrainType("load-publisher"); public static readonly GrainType TestHooksSystemTargetType = SystemTargetGrainId.CreateGrainType("test.hooks"); public static readonly GrainType TransactionAgentSystemTargetType = SystemTargetGrainId.CreateGrainType("txn.agent"); public static readonly GrainType StreamProviderManagerAgentSystemTargetType = SystemTargetGrainId.CreateGrainType("stream.provider-manager"); public static readonly GrainType StreamPullingAgentManagerType = SystemTargetGrainId.CreateGrainType("stream.agent-mgr"); public static readonly GrainType StreamPullingAgentType = SystemTargetGrainId.CreateGrainType("stream.agent"); public static readonly GrainType ManifestProviderType = SystemTargetGrainId.CreateGrainType("manifest"); public static readonly GrainType ActivationMigratorType = SystemTargetGrainId.CreateGrainType("migrator"); public static readonly GrainType CancellationManagerType = SystemTargetGrainId.CreateGrainType("canceler"); public static readonly GrainType ActivationRepartitionerType = SystemTargetGrainId.CreateGrainType("repartitioner"); public static readonly GrainType ActivationRebalancerMonitorType = SystemTargetGrainId.CreateGrainType("rebalancer-monitor"); public static readonly GrainType GrainDirectoryPartitionType = SystemTargetGrainId.CreateGrainType("dir.grain.part"); public static readonly GrainType GrainDirectoryType = SystemTargetGrainId.CreateGrainType("dir.grain"); public static readonly GrainId SiloDirectConnectionId = GrainId.Create( GrainType.Create(GrainTypePrefix.SystemPrefix + "silo"), IdSpan.Create("01111111-1111-1111-1111-111111111111")); public static readonly TimeSpan DEFAULT_CLIENT_DROP_TIMEOUT = TimeSpan.FromMinutes(1); private static readonly FrozenDictionary SingletonSystemTargetNames = new Dictionary { {DirectoryServiceType, "DirectoryService"}, {DirectoryCacheValidatorType, "DirectoryCacheValidator"}, {SiloControlType, "SiloControl"}, {SiloMetadataType, "SiloMetadata"}, {ClientDirectoryType, "ClientDirectory"}, {CatalogType,"Catalog"}, {MembershipServiceType,"MembershipService"}, {DeploymentLoadPublisherSystemTargetType, "DeploymentLoadPublisherSystemTarget"}, {StreamProviderManagerAgentSystemTargetType,"StreamProviderManagerAgent"}, {TestHooksSystemTargetType,"TestHooksSystemTargetType"}, {TransactionAgentSystemTargetType,"TransactionAgentSystemTarget"}, {SystemMembershipTableType,"SystemMembershipTable"}, {StreamPullingAgentManagerType, "PullingAgentsManagerSystemTarget"}, {StreamPullingAgentType, "PullingAgentSystemTarget"}, {ManifestProviderType, "ManifestProvider"}, {ActivationMigratorType, "ActivationMigrator"}, {ActivationRepartitionerType, "ActivationRepartitioner"}, {ActivationRebalancerMonitorType, "ActivationRebalancerMonitor"}, {GrainDirectoryType, "GrainDirectory"}, }.ToFrozenDictionary(); public static string SystemTargetName(GrainType id) => SingletonSystemTargetNames.TryGetValue(id, out var name) ? name : id.ToString(); public static bool IsSingletonSystemTarget(GrainType id) => SingletonSystemTargetNames.ContainsKey(id); } } ================================================ FILE: src/Orleans.Core/Runtime/GrainCancellationTokenRuntime.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Internal; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; namespace Orleans.Runtime { internal class GrainCancellationTokenRuntime : IGrainCancellationTokenRuntime { private const int MaxNumCancelErrorTries = 30; private readonly TimeSpan _cancelCallMaxWaitTime = TimeSpan.FromSeconds(300); private readonly IBackoffProvider _cancelCallBackoffProvider = new FixedBackoff(TimeSpan.FromSeconds(1)); private readonly Func _cancelCallRetryExceptionFilter = (exception, i) => exception is GrainExtensionNotInstalledException; public Task Cancel(Guid id, CancellationTokenSource tokenSource, ConcurrentDictionary grainReferences) { if (tokenSource.IsCancellationRequested) { // This token has already been canceled. return Task.CompletedTask; } // propagate the exception from the _cancellationTokenSource.Cancel back to the caller // but also cancel _targetGrainReferences. Task localTask = null; try { // Cancel the token now, preventing recursion. tokenSource.Cancel(); } catch (Exception exception) { localTask = Task.FromException(exception); } List tasks = null; foreach (var reference in grainReferences) { if (tasks is null) { tasks = new(); if (localTask != null) tasks.Add(localTask); } tasks.Add(CancelTokenWithRetries(id, grainReferences, reference.Key, reference.Value.AsReference())); } return tasks is null ? localTask ?? Task.CompletedTask : Task.WhenAll(tasks); } private async Task CancelTokenWithRetries( Guid id, ConcurrentDictionary grainReferences, GrainId key, ICancellationSourcesExtension tokenExtension) { await AsyncExecutorWithRetries.ExecuteWithRetries( i => tokenExtension.CancelRemoteToken(id), MaxNumCancelErrorTries, _cancelCallRetryExceptionFilter, _cancelCallMaxWaitTime, _cancelCallBackoffProvider); grainReferences.TryRemove(key, out _); } } [RegisterSerializer] internal class GrainCancellationTokenCodec : GeneralizedReferenceTypeSurrogateCodec { private readonly IGrainCancellationTokenRuntime _runtime; public GrainCancellationTokenCodec(IGrainCancellationTokenRuntime runtime, IValueSerializer surrogateSerializer) : base(surrogateSerializer) { _runtime = runtime; } public override GrainCancellationToken ConvertFromSurrogate(ref GrainCancellationTokenSurrogate surrogate) { return new GrainCancellationToken(surrogate.TokenId, surrogate.IsCancellationRequested, _runtime); } public override void ConvertToSurrogate(GrainCancellationToken value, ref GrainCancellationTokenSurrogate surrogate) { surrogate.IsCancellationRequested = value.IsCancellationRequested; surrogate.TokenId = value.Id; } } [GenerateSerializer] internal struct GrainCancellationTokenSurrogate { [Id(0)] public bool IsCancellationRequested; [Id(1)] public Guid TokenId; } } ================================================ FILE: src/Orleans.Core/Runtime/GrainReferenceRuntime.cs ================================================ using Orleans.CodeGeneration; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Serialization.Invocation; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; namespace Orleans.Runtime { internal class GrainReferenceRuntime : IGrainReferenceRuntime { private readonly GrainReferenceActivator referenceActivator; private readonly GrainInterfaceTypeResolver interfaceTypeResolver; private readonly IGrainCancellationTokenRuntime cancellationTokenRuntime; private readonly IOutgoingGrainCallFilter[] filters; private readonly Action sendRequest; public GrainReferenceRuntime( IRuntimeClient runtimeClient, IGrainCancellationTokenRuntime cancellationTokenRuntime, IEnumerable outgoingCallFilters, GrainReferenceActivator referenceActivator, GrainInterfaceTypeResolver interfaceTypeResolver) { this.RuntimeClient = runtimeClient; this.cancellationTokenRuntime = cancellationTokenRuntime; this.referenceActivator = referenceActivator; this.interfaceTypeResolver = interfaceTypeResolver; this.filters = outgoingCallFilters.ToArray(); this.sendRequest = (GrainReference reference, IResponseCompletionSource callback, IInvokable body, InvokeMethodOptions options) => RuntimeClient.SendRequest(reference, body, callback, options); } public IRuntimeClient RuntimeClient { get; private set; } public ValueTask InvokeMethodAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options) { // TODO: Remove expensive interface type check if (this.filters.Length == 0 && request is not IOutgoingGrainCallFilter) { SetGrainCancellationTokensTarget(reference, request); var responseCompletionSource = ResponseCompletionSourcePool.Get(); this.RuntimeClient.SendRequest(reference, request, responseCompletionSource, options); return responseCompletionSource.AsValueTask(); } else { return InvokeMethodWithFiltersAsync(reference, request, options); } } public ValueTask InvokeMethodAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options) { // TODO: Remove expensive interface type check if (filters.Length == 0 && request is not IOutgoingGrainCallFilter) { SetGrainCancellationTokensTarget(reference, request); var responseCompletionSource = ResponseCompletionSourcePool.Get(); this.RuntimeClient.SendRequest(reference, request, responseCompletionSource, options); return responseCompletionSource.AsVoidValueTask(); } else { return InvokeMethodWithFiltersAsync(reference, request, options); } } public void InvokeMethod(GrainReference reference, IInvokable request, InvokeMethodOptions options) { Debug.Assert((options & InvokeMethodOptions.OneWay) != 0); // TODO: Remove expensive interface type check if (filters.Length == 0 && request is not IOutgoingGrainCallFilter) { SetGrainCancellationTokensTarget(reference, request); this.RuntimeClient.SendRequest(reference, request, context: null, options); } else { InvokeMethodWithFiltersAsync(reference, request, options).AsTask().Ignore(); } } private async ValueTask InvokeMethodWithFiltersAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options) { SetGrainCancellationTokensTarget(reference, request); var invoker = new OutgoingCallInvoker(reference, request, options, this.sendRequest, this.filters); await invoker.Invoke(); return invoker.TypedResult; } private async ValueTask InvokeMethodWithFiltersAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options) { SetGrainCancellationTokensTarget(reference, request); var invoker = new OutgoingCallInvoker(reference, request, options, this.sendRequest, this.filters); await invoker.Invoke(); } public object Cast(IAddressable grain, Type grainInterface) { var grainId = grain.GetGrainId(); if (grain is GrainReference && grainInterface.IsAssignableFrom(grain.GetType())) { return grain; } var interfaceType = this.interfaceTypeResolver.GetGrainInterfaceType(grainInterface); return this.referenceActivator.CreateReference(grainId, interfaceType); } /// /// Sets target grain to the found instances of type GrainCancellationToken /// private void SetGrainCancellationTokensTarget(GrainReference target, IInvokable request) { var argumentCount = request.GetArgumentCount(); for (var i = 0; i < argumentCount; i++) { var arg = request.GetArgument(i); if (arg is not GrainCancellationToken grainToken) { continue; } grainToken.AddGrainReference(this.cancellationTokenRuntime, target); } } } } ================================================ FILE: src/Orleans.Core/Runtime/IHealthCheckable.cs ================================================ using System; namespace Orleans.Runtime { /// /// Interface for services which can be probed for health status. /// public interface IHealthCheckable { /// /// Returns a value indicating the health of this instance. /// /// The last time which this instance health was checked. /// If this method returns , this parameter will describe the reason for that verdict. /// if the instance is healthy, otherwise. bool CheckHealth(DateTime lastCheckTime, out string reason); } } ================================================ FILE: src/Orleans.Core/Runtime/ILocalSiloDetails.cs ================================================ namespace Orleans.Runtime { /// /// Details of the local silo. /// public interface ILocalSiloDetails { /// /// Gets the name of this silo. /// string Name { get; } /// /// Gets the cluster identity. This used to be called DeploymentId before Orleans 2.0 name. /// string ClusterId { get; } /// /// Gets the host name of this silo. /// /// /// This is equal to . /// string DnsHostName { get; } /// /// Gets the address of this silo's inter-silo endpoint. /// SiloAddress SiloAddress { get; } /// /// Gets the address of this silo's gateway proxy endpoint. /// SiloAddress GatewayAddress { get; } } } ================================================ FILE: src/Orleans.Core/Runtime/IRuntimeClient.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; using Orleans.Serialization; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// The IRuntimeClient interface defines a subset of the runtime API that is exposed to both silo and client. /// internal interface IRuntimeClient { /// /// Gets the time provider used by the system. /// TimeProvider TimeProvider { get; } /// /// Grain Factory to get and cast grain references. /// IInternalGrainFactory InternalGrainFactory { get; } /// /// A unique identifier for the current client. /// There is no semantic content to this string, but it may be useful for logging. /// string CurrentActivationIdentity { get; } /// /// Gets the service provider. /// IServiceProvider ServiceProvider { get; } /// /// Get the current response timeout setting for this client. /// /// Response timeout value TimeSpan GetResponseTimeout(); /// /// Sets the current response timeout setting for this client. /// /// New response timeout value void SetResponseTimeout(TimeSpan timeout); void SendRequest(GrainReference target, IInvokable request, IResponseCompletionSource context, InvokeMethodOptions options); void SendResponse(Message request, Response response); void ReceiveResponse(Message message); IAddressable CreateObjectReference(IAddressable obj); void DeleteObjectReference(IAddressable obj); IGrainReferenceRuntime GrainReferenceRuntime { get; } void BreakOutstandingMessagesToSilo(SiloAddress deadSilo); // For testing purposes only. int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType); } /// /// Helper class used to invoke on objects which implement , immediately after deserialization. /// public class OnDeserializedCallbacks : DeserializationContext { /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public OnDeserializedCallbacks(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; RuntimeClient = serviceProvider.GetRequiredService(); } /// /// Gets the service provider. /// public override IServiceProvider ServiceProvider { get; } /// /// Gets the runtime client. /// public override object RuntimeClient { get; } /// /// The hook method invoked by the serialization infrastructure. /// /// The value which was deserialized. public void OnDeserialized(IOnDeserialized value) => value.OnDeserialized(this); } } ================================================ FILE: src/Orleans.Core/Runtime/InvokableObjectManager.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Invocation; namespace Orleans { internal sealed partial class InvokableObjectManager : IDisposable { private readonly CancellationTokenSource disposed = new CancellationTokenSource(); private readonly ConcurrentDictionary localObjects = new ConcurrentDictionary(); private readonly InterfaceToImplementationMappingCache _interfaceToImplementationMapping; private readonly IGrainContext rootGrainContext; private readonly IRuntimeClient runtimeClient; private readonly ILogger logger; private readonly DeepCopier deepCopier; private readonly DeepCopier _responseCopier; private readonly MessagingTrace messagingTrace; private List? _grainCallFilters; private List GrainCallFilters => _grainCallFilters ??= [.. runtimeClient.ServiceProvider.GetServices()]; public InvokableObjectManager( IGrainContext rootGrainContext, IRuntimeClient runtimeClient, DeepCopier deepCopier, MessagingTrace messagingTrace, DeepCopier responseCopier, InterfaceToImplementationMappingCache interfaceToImplementationMapping, ILogger logger) { this.rootGrainContext = rootGrainContext; this.runtimeClient = runtimeClient; this.deepCopier = deepCopier; this.messagingTrace = messagingTrace; _responseCopier = responseCopier; _interfaceToImplementationMapping = interfaceToImplementationMapping; this.logger = logger; } public bool TryRegister(IAddressable obj, ObserverGrainId objectId) { return this.localObjects.TryAdd(objectId, new LocalObjectData(obj, objectId, this)); } public bool TryDeregister(ObserverGrainId objectId) { return this.localObjects.TryRemove(objectId, out _); } public void Dispatch(Message message) { if (!ObserverGrainId.TryParse(message.TargetGrain, out var observerId)) { LogNotAddressedToAnObserver(logger, message); return; } if (this.localObjects.TryGetValue(observerId, out var objectData)) { objectData.ReceiveMessage(message); } else { LogUnexpectedTargetInRequest(logger, message.TargetGrain, message); } } public void Dispose() { var tokenSource = this.disposed; Utils.SafeExecute(() => tokenSource?.Cancel(false)); Utils.SafeExecute(() => tokenSource?.Dispose()); } public sealed partial class LocalObjectData : IGrainContext, IGrainCallCancellationExtension { private static readonly Func HandleFunc = self => ((LocalObjectData)self!).LocalObjectMessagePumpAsync(); private readonly InvokableObjectManager _manager; private readonly Dictionary _runningRequests = []; private Task? _messagePumpTask; internal LocalObjectData(IAddressable obj, ObserverGrainId observerId, InvokableObjectManager manager) { this.LocalObject = new WeakReference(obj); this.ObserverId = observerId; this.Messages = new Queue(); this.Running = false; _manager = manager; } internal WeakReference LocalObject { get; } internal ObserverGrainId ObserverId { get; } internal Queue Messages { get; } internal bool Running { get; set; } GrainId IGrainContext.GrainId => this.ObserverId.GrainId; GrainReference IGrainContext.GrainReference => _manager.runtimeClient.InternalGrainFactory.GetGrain(ObserverId.GrainId).AsReference(); object? IGrainContext.GrainInstance => this.LocalObject.Target; ActivationId IGrainContext.ActivationId => throw new NotImplementedException(); GrainAddress IGrainContext.Address => throw new NotImplementedException(); IServiceProvider IGrainContext.ActivationServices => throw new NotSupportedException(); IGrainLifecycle IGrainContext.ObservableLifecycle => throw new NotImplementedException(); public IWorkItemScheduler Scheduler => throw new NotImplementedException(); void IGrainContext.SetComponent(TComponent? value) where TComponent : class { if (this.LocalObject.Target is TComponent) { throw new ArgumentException("Cannot override a component which is implemented by this grain"); } _manager.rootGrainContext.SetComponent(value); } public object? GetComponent(Type componentType) { if (componentType.IsAssignableFrom(this.LocalObject.Target?.GetType())) { return LocalObject.Target; } else if (componentType.IsAssignableFrom(GetType())) { return this; } return _manager.rootGrainContext.GetComponent(componentType); } public object? GetTarget() => this.LocalObject.Target; bool IEquatable.Equals(IGrainContext? other) => ReferenceEquals(this, other); public void ReceiveMessage(object msg) { var message = (Message)msg; var obj = this.LocalObject.Target; if (obj is null) { // Remove from the dictionary record for the garbage collected object? But now we won't be able to detect invalid dispatch IDs anymore. LogObserverGarbageCollected(_manager.logger, this.ObserverId, message); // Try to remove. If it's not there, we don't care. _manager.TryDeregister(this.ObserverId); return; } // Handle AlwaysInterleave messages (like cancellation requests) immediately without queueing. // These messages need to be processed right away, even if another request is currently running. if (message.IsAlwaysInterleave) { // Track the running request so it can be cancelled. lock (Messages) { var task = Task.Factory.StartNew( static state => { var (self, msg) = ((LocalObjectData, Message))state!; return self.ProcessMessageAsync(msg); }, (this, message), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap(); _runningRequests.Add(message, task); } return; } bool start; lock (this.Messages) { this.Messages.Enqueue(message); start = !this.Running; this.Running = true; } LogInvokeLocalObjectAsync(_manager.logger, message, start); if (start) { // we want to ensure that the message pump operates asynchronously // with respect to the current thread. see // http://channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B317#fbid=aIWUq0ssW74 // at position 54:45. // // according to the information posted at: // http://stackoverflow.com/questions/12245935/is-task-factory-startnew-guaranteed-to-use-another-thread-than-the-calling-thr // this idiom is dependent upon the a TaskScheduler not implementing the // override QueueTask as task inlining (as opposed to queueing). this seems // implausible to the author, since none of the .NET schedulers do this and // it is considered bad form (the OrleansTaskScheduler does not do this). // // if, for some reason this doesn't hold true, we can guarantee what we // want by passing a placeholder continuation token into Task.StartNew() // instead. i.e.: // // return Task.StartNew(() => ..., new CancellationToken()); // We pass these options to Task.Factory.StartNew as they make the call identical // to Task.Run. See: https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/ _messagePumpTask = Task.Factory.StartNew( HandleFunc, this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } } private async Task LocalObjectMessagePumpAsync() { while (TryDequeueMessage(out var message)) { await ProcessMessageAsync(message); } bool TryDequeueMessage([NotNullWhen(true)] out Message? message) { lock (Messages) { var result = Messages.TryDequeue(out message); if (!result) { Running = false; } else { _runningRequests.Add(message!, _messagePumpTask); } return result; } } } private async Task ProcessMessageAsync(Message message) { try { if (message.IsExpired) { _manager.messagingTrace.OnDropExpiredMessage(message, MessagingInstruments.Phase.Invoke); return; } if (message.RequestContextData is { Count: > 0 }) { RequestContextExtensions.Import(message.RequestContextData); } IInvokable? request; try { if (message.BodyObject is not IInvokable invokableBody) { _manager.runtimeClient.SendResponse( message, Response.FromException(new InvalidOperationException("Message body is not an invokable request"))); return; } request = invokableBody; } catch (Exception deserializationException) { LogErrorDeserializingMessageBody(_manager.logger, deserializationException, message); _manager.runtimeClient.SendResponse(message, Response.FromException(deserializationException)); return; } try { request.SetTarget(this); var filters = _manager.GrainCallFilters; Response response; if (filters is { Count: > 0 } || LocalObject is IIncomingGrainCallFilter) { var invoker = new GrainMethodInvoker(message, this, request, filters, _manager._interfaceToImplementationMapping, _manager._responseCopier); await invoker.Invoke(); response = invoker.Response; } else { response = await request.Invoke(); response = _manager._responseCopier.Copy(response); } if (message.Direction != Message.Directions.OneWay) { this.SendResponseAsync(message, response); } } catch (Exception exc) { this.ReportException(message, exc); } finally { // Clear the running request when done. lock (Messages) { _runningRequests.Remove(message); } } } catch (Exception outerException) { // Ignore and keep looping. LogErrorProcessingMessage(_manager.logger, outerException, message); } } private void SendResponseAsync(Message message, Response resultObject) { if (message.IsExpired) { _manager.messagingTrace.OnDropExpiredMessage(message, MessagingInstruments.Phase.Respond); return; } Response deepCopy; try { // we're expected to notify the caller if the deep copy failed. deepCopy = _manager.deepCopier.Copy(resultObject); } catch (Exception exc2) { _manager.runtimeClient.SendResponse(message, Response.FromException(exc2)); LogErrorSendingResponse(_manager.logger, exc2); return; } // the deep-copy succeeded. _manager.runtimeClient.SendResponse(message, deepCopy); return; } private void ReportException(Message message, Exception exception) { switch (message.Direction) { case Message.Directions.OneWay: LogErrorInvokingOneWayRequest(_manager.logger, exception, message.BodyObject?.ToString(), message.InterfaceType); break; case Message.Directions.Request: Exception deepCopy; try { // we're expected to notify the caller if the deep copy failed. deepCopy = _manager.deepCopier.Copy(exception); } catch (Exception ex2) { _manager.runtimeClient.SendResponse(message, Response.FromException(ex2)); LogErrorSendingExceptionResponse(_manager.logger, ex2); return; } // the deep-copy succeeded. var response = Response.FromException(deepCopy); _manager.runtimeClient.SendResponse(message, response); break; default: throw new InvalidOperationException($"Unrecognized direction for message {message}, which resulted in exception: {exception}"); } } public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { } public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken) { } public void Rehydrate(IRehydrationContext context) { // Migration is not supported, but we need to dispose of the context if it's provided (context as IDisposable)?.Dispose(); } public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken) { // Migration is not supported. Do nothing: the contract is that this method attempts migration, but does not guarantee it will occur. } ValueTask IGrainCallCancellationExtension.CancelRequestAsync(GrainId senderGrainId, CorrelationId messageId) { if (!TryCancelRequest()) { // The message being canceled may not have arrived yet, so retry a few times. return RetryCancellationAfterDelay(); } return ValueTask.CompletedTask; async ValueTask RetryCancellationAfterDelay() { var attemptsRemaining = 3; do { await Task.Delay(1_000); if (TryCancelRequest()) { return; } } while (--attemptsRemaining > 0); } bool TryCancelRequest() { Message? message = null; var wasWaiting = false; lock (Messages) { // Check the running requests. foreach (var runningRequest in _runningRequests.Keys) { if (runningRequest.Id == messageId && runningRequest.SendingGrain == senderGrainId) { message = runningRequest; break; } } if (message is null) { // Check the waiting requests. foreach (var waitingRequest in Messages) { var waiting = waitingRequest; if (waiting.Id == messageId && waiting.SendingGrain == senderGrainId) { message = waiting; wasWaiting = true; // Remove the message, since it will be rejected immediately (outside the lock) without being executed. var initialCount = Messages.Count; for (var i = 0; i < initialCount; i++) { var current = Messages.Dequeue(); if (!ReferenceEquals(current, message)) { Messages.Enqueue(current); } } break; } } } } var didCancel = false; if (message is not null) { // The message never began executing, so send a canceled response immediately. // If the message did begin executing, wait for it to observe the cancellation token and respond itself. if (wasWaiting) { _manager.runtimeClient.SendResponse(message, Response.FromException(new OperationCanceledException())); didCancel = true; } else if (message.BodyObject is IInvokable invokableRequest) { didCancel = TryCancelInvokable(invokableRequest) || !invokableRequest.IsCancellable; } else { // Assume the request is not cancellable. didCancel = true; } } return didCancel; } bool TryCancelInvokable(IInvokable request) { try { return request.TryCancel(); } catch (Exception exception) { LogErrorCancellationCallbackFailed(_manager.logger, exception); return true; } } } [LoggerMessage( Level = LogLevel.Warning, Message = "One or more cancellation callbacks failed." )] private static partial void LogErrorCancellationCallbackFailed(ILogger logger, Exception exception); public Task Deactivated => Task.CompletedTask; } private readonly struct ObserverGrainIdLogValue(ObserverGrainId observerId) { public override string ToString() => observerId.ToString(); } [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_OGC_TargetNotFound_2, Level = LogLevel.Error, Message = "Message is not addressed to an observer. Message: '{Message}'." )] private static partial void LogNotAddressedToAnObserver(ILogger logger, Message message); [LoggerMessage( EventId = (int)ErrorCode.ProxyClient_OGC_TargetNotFound, Level = LogLevel.Error, Message = "Unexpected target grain in request: {TargetGrain}. Message: '{Message}'." )] private static partial void LogUnexpectedTargetInRequest(ILogger logger, GrainId targetGrain, Message message); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.Runtime_Error_100162, Message = "Object associated with id '{ObserverId}' has been garbage collected. Deleting object reference and unregistering it. Message: '{Message}'." )] private static partial void LogObserverGarbageCollected(ILogger logger, ObserverGrainId observerId, Message message); [LoggerMessage( Level = LogLevel.Trace, Message = "InvokeLocalObjectAsync '{Message}' start '{Start}'." )] private static partial void LogInvokeLocalObjectAsync(ILogger logger, Message message, bool start); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.ProxyClient_OGC_SendResponseFailed, Message = "Error sending a response." )] private static partial void LogErrorSendingResponse(ILogger logger, Exception exc); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.ProxyClient_OGC_UnhandledExceptionInOneWayInvoke, Message = "Exception during invocation of notification '{Request}', interface '{Interface}'. Ignoring exception because this is a one way request." )] private static partial void LogErrorInvokingOneWayRequest(ILogger logger, Exception exception, string? request, GrainInterfaceType @interface); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.ProxyClient_OGC_SendExceptionResponseFailed, Message = "Error sending an exception response." )] private static partial void LogErrorSendingExceptionResponse(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception during message body deserialization for message: '{Message}'." )] private static partial void LogErrorDeserializingMessageBody(ILogger logger, Exception exception, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception processing message '{Message}'." )] private static partial void LogErrorProcessingMessage(ILogger logger, Exception exception, Message message); } } ================================================ FILE: src/Orleans.Core/Runtime/LocalClientDetails.cs ================================================ using System.Net; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Runtime.Configuration; namespace Orleans { internal class LocalClientDetails { public LocalClientDetails(IOptions clientMessagingOptions) { var options = clientMessagingOptions.Value; var ipAddress = options.LocalAddress ?? ConfigUtilities.GetLocalIPAddress(options.PreferredFamily, options.NetworkInterfaceName); // Client generations are negative var generation = -SiloAddress.AllocateNewGeneration(); ClientAddress = SiloAddress.New(ipAddress, 0, generation); ClientId = ClientGrainId.Create(); } public ClientGrainId ClientId { get; } public IPAddress IPAddress => ClientAddress.Endpoint.Address; public SiloAddress ClientAddress { get; } } } ================================================ FILE: src/Orleans.Core/Runtime/MembershipTableSnapshot.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; namespace Orleans.Runtime { [GenerateSerializer, Immutable] internal sealed class MembershipTableSnapshot { private static readonly MembershipTableSnapshot InitialValue = new(MembershipVersion.MinValue, ImmutableDictionary.Empty); public MembershipTableSnapshot( MembershipVersion version, ImmutableDictionary entries) { this.Version = version; this.Entries = entries; } public static MembershipTableSnapshot Create(MembershipTableData table) => Update(InitialValue, table); public static MembershipTableSnapshot Update(MembershipTableSnapshot previousSnapshot, MembershipTableData table) { ArgumentNullException.ThrowIfNull(previousSnapshot); ArgumentNullException.ThrowIfNull(table); var version = (table.Version.Version == 0 && table.Version.VersionEtag == "0") ? MembershipVersion.MinValue : new MembershipVersion(table.Version.Version); return Update(previousSnapshot, version, table.Members.Select(t => t.Item1)); } public static MembershipTableSnapshot Update(MembershipTableSnapshot previousSnapshot, MembershipTableSnapshot updated) { ArgumentNullException.ThrowIfNull(previousSnapshot); ArgumentNullException.ThrowIfNull(updated); return Update(previousSnapshot, updated.Version, updated.Entries.Values); } private static MembershipTableSnapshot Update(MembershipTableSnapshot previousSnapshot, MembershipVersion version, IEnumerable updatedEntries) { ArgumentNullException.ThrowIfNull(previousSnapshot); ArgumentNullException.ThrowIfNull(updatedEntries); var entries = ImmutableDictionary.CreateBuilder(); foreach (var item in updatedEntries) { var entry = item; entry = PreserveIAmAliveTime(previousSnapshot, entry); entries.Add(entry.SiloAddress, entry); } return new MembershipTableSnapshot(version, entries.ToImmutable()); } private static MembershipEntry PreserveIAmAliveTime(MembershipTableSnapshot previousSnapshot, MembershipEntry entry) { // Retain the maximum IAmAliveTime, since IAmAliveTime updates do not increase membership version // and therefore can be clobbered by torn reads. if (previousSnapshot.Entries.TryGetValue(entry.SiloAddress, out var previousEntry) && previousEntry.IAmAliveTime > entry.IAmAliveTime) { entry = entry.WithIAmAliveTime(previousEntry.IAmAliveTime); } return entry; } [Id(0)] public MembershipVersion Version { get; } [Id(1)] public ImmutableDictionary Entries { get; } public int ActiveNodeCount { get { var count = 0; foreach (var entry in this.Entries) { if (entry.Value.Status == SiloStatus.Active) { ++count; } } return count; } } public SiloStatus GetSiloStatus(SiloAddress silo) { var status = this.Entries.TryGetValue(silo, out var entry) ? entry.Status : SiloStatus.None; if (status == SiloStatus.None) { foreach (var member in this.Entries) { if (member.Key.IsSuccessorOf(silo)) { status = SiloStatus.Dead; break; } } } return status; } public bool IsSuccessorTo(MembershipTableSnapshot other) { if (Version > other.Version) { return true; } if (Version < other.Version) { return false; } if (Entries.Count > other.Entries.Count) { // Something is amiss. return false; } foreach (var entry in Entries) { if (!other.Entries.TryGetValue(entry.Key, out var otherEntry)) { // Something is amiss. return false; } } // This is a successor if any silo has a later EffectiveIAmAliveTime. foreach (var entry in Entries) { if (entry.Value.EffectiveIAmAliveTime > other.Entries[entry.Key].EffectiveIAmAliveTime) { return true; } } return false; } public override string ToString() { var sb = new StringBuilder(); sb.Append($"[Version: {this.Version}, {this.Entries.Count} silos"); foreach (var entry in this.Entries) sb.Append($", {entry.Value}"); sb.Append(']'); return sb.ToString(); } } } ================================================ FILE: src/Orleans.Core/Runtime/OutgoingCallInvoker.cs ================================================ using System; using System.Reflection; using System.Threading.Tasks; using Orleans.CodeGeneration; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// Invokes a request on a grain reference. /// internal sealed class OutgoingCallInvoker : IOutgoingGrainCallContext { private readonly IInvokable request; private readonly InvokeMethodOptions options; private readonly Action sendRequest; private readonly IOutgoingGrainCallFilter[] filters; private readonly int stages; private readonly GrainReference grainReference; private readonly IOutgoingGrainCallFilter requestFilter; private int stage; /// /// Initializes a new instance of the class. /// /// The grain reference. /// The request. /// /// /// The invocation interceptors. public OutgoingCallInvoker( GrainReference grain, IInvokable request, InvokeMethodOptions options, Action sendRequest, IOutgoingGrainCallFilter[] filters) { this.request = request; this.options = options; this.sendRequest = sendRequest; this.grainReference = grain; this.filters = filters; this.stages = filters.Length; SourceContext = RuntimeContext.Current; if (request is IOutgoingGrainCallFilter requestFilter) { this.requestFilter = requestFilter; ++this.stages; } } public IInvokable Request => this.request; public object Grain => this.grainReference; public MethodInfo InterfaceMethod => request.GetMethod(); public object Result { get => TypedResult; set => TypedResult = (TResult)value; } public Response Response { get; set; } public TResult TypedResult { get => Response.GetResult(); set => Response = Response.FromResult(value); } public IGrainContext SourceContext { get; } public GrainId? SourceId => SourceContext?.GrainId; public GrainId TargetId => grainReference.GrainId; public GrainInterfaceType InterfaceType => grainReference.InterfaceType; public string InterfaceName => request.GetInterfaceName(); public string MethodName => request.GetMethodName(); public async Task Invoke() { try { // Execute each stage in the pipeline. Each successive call to this method will invoke the next stage. // Stages which are not implemented (eg, because the user has not specified an interceptor) are skipped. if (stage < this.filters.Length) { // Call each of the specified interceptors. var systemWideFilter = this.filters[stage]; stage++; await systemWideFilter.Invoke(this); // If Response is null some filter did not continue the call chain if (this.Response is null) { ThrowBrokenCallFilterChain(systemWideFilter.GetType().Name); } return; } else if (stage < this.stages) { stage++; await this.requestFilter.Invoke(this); // If Response is null some filter did not continue the call chain if (this.Response is null) { ThrowBrokenCallFilterChain(this.requestFilter.GetType().Name); } return; } else if (stage == this.stages) { // Finally call the root-level invoker. stage++; var responseCompletionSource = ResponseCompletionSourcePool.Get(); this.sendRequest(this.grainReference, responseCompletionSource, this.request, this.options); this.Response = await responseCompletionSource.AsValueTask(); return; } } finally { stage--; } // If this method has been called more than the expected number of times, that is invalid. ThrowInvalidCall(); } private void ThrowInvalidCall() { throw new InvalidOperationException($"{typeof(OutgoingCallInvoker)}.{nameof(Invoke)}() received an invalid call."); } private void ThrowBrokenCallFilterChain(string filterName) { throw new InvalidOperationException($"{typeof(OutgoingCallInvoker)}.{nameof(Invoke)}() invoked a broken filter: {filterName}."); } } } ================================================ FILE: src/Orleans.Core/Runtime/OutsideRuntimeClient.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.ClientObservers; using Orleans.CodeGeneration; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Invocation; using static Orleans.Internal.StandardExtensions; namespace Orleans { internal partial class OutsideRuntimeClient : IRuntimeClient, IDisposable, IClusterConnectionStatusListener { internal static bool TestOnlyThrowExceptionDuringInit { get; set; } private readonly ILogger logger; private readonly ClientMessagingOptions clientMessagingOptions; private readonly ConcurrentDictionary callbacks; private InvokableObjectManager localObjects; private bool disposing; private bool disposed; private readonly MessagingTrace messagingTrace; private readonly InterfaceToImplementationMappingCache _interfaceToImplementationMapping; private readonly ApplicationRequestInstruments _applicationRequestInstruments; private IGrainCallCancellationManager _cancellationManager; private IClusterConnectionStatusObserver[] _statusObservers; public IInternalGrainFactory InternalGrainFactory { get; private set; } private ClientClusterManifestProvider _manifestProvider; private MessageFactory messageFactory; private readonly LocalClientDetails _localClientDetails; private readonly ILoggerFactory loggerFactory; private readonly SharedCallbackData sharedCallbackData; private readonly PeriodicTimer callbackTimer; private Task callbackTimerTask; public GrainAddress CurrentActivationAddress { get; private set; } public ClientGatewayObserver gatewayObserver { get; private set; } public string CurrentActivationIdentity { get { return CurrentActivationAddress.ToString(); } } public IGrainReferenceRuntime GrainReferenceRuntime { get; private set; } internal ClientMessageCenter MessageCenter { get; private set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "MessageCenter is IDisposable but cannot call Dispose yet as it lives past the end of this method call.")] public OutsideRuntimeClient( LocalClientDetails localClientDetails, ILoggerFactory loggerFactory, IOptions clientMessagingOptions, MessagingTrace messagingTrace, IServiceProvider serviceProvider, TimeProvider timeProvider, InterfaceToImplementationMappingCache interfaceToImplementationMapping, OrleansInstruments orleansInstruments) { TimeProvider = timeProvider; _interfaceToImplementationMapping = interfaceToImplementationMapping; _applicationRequestInstruments = new(orleansInstruments); this.ServiceProvider = serviceProvider; _localClientDetails = localClientDetails; this.loggerFactory = loggerFactory; this.messagingTrace = messagingTrace; this.logger = loggerFactory.CreateLogger(); callbacks = new ConcurrentDictionary(); this.clientMessagingOptions = clientMessagingOptions.Value; var period = Max( TimeSpan.FromMilliseconds(1), Min( this.clientMessagingOptions.ResponseTimeout, TimeSpan.FromSeconds(1))); this.callbackTimer = new PeriodicTimer(period, timeProvider); this.sharedCallbackData = new SharedCallbackData( msg => this.UnregisterCallback(msg.Id), this.loggerFactory.CreateLogger(), this.clientMessagingOptions.ResponseTimeout, this.clientMessagingOptions.CancelRequestOnTimeout, this.clientMessagingOptions.WaitForCancellationAcknowledgement, null); } internal void ConsumeServices() { try { _statusObservers = this.ServiceProvider.GetServices().ToArray(); _manifestProvider = ServiceProvider.GetRequiredService(); this.InternalGrainFactory = this.ServiceProvider.GetRequiredService(); _cancellationManager = sharedCallbackData.CancellationManager = ServiceProvider.GetRequiredService(); this.messageFactory = this.ServiceProvider.GetService(); this.localObjects = new InvokableObjectManager( ServiceProvider.GetRequiredService(), this, ServiceProvider.GetRequiredService(), messagingTrace, ServiceProvider.GetRequiredService>(), _interfaceToImplementationMapping, loggerFactory.CreateLogger()); this.callbackTimerTask = Task.Run(MonitorCallbackExpiry); this.GrainReferenceRuntime = this.ServiceProvider.GetRequiredService(); // Client init / sign-on message LogStartingClient(logger, RuntimeVersion.Current, _localClientDetails.ClientAddress, _localClientDetails.ClientId); if (TestOnlyThrowExceptionDuringInit) { throw new InvalidOperationException("TestOnlyThrowExceptionDuringInit"); } } catch (Exception exc) { LogConstructorException(logger, exc); ConstructorReset(); throw; } } public IServiceProvider ServiceProvider { get; private set; } public TimeProvider TimeProvider { get; } public async Task StartAsync(CancellationToken cancellationToken) { // Deliberately avoid capturing the current synchronization context during startup and execute on the default scheduler. // This helps to avoid any issues (such as deadlocks) caused by executing with the client's synchronization context/scheduler. await Task.Run(() => this.StartInternal(cancellationToken)).ConfigureAwait(false); LogStartedClient(logger, CurrentActivationAddress, _localClientDetails.ClientId); } public async Task StopAsync(CancellationToken cancellationToken) { this.callbackTimer.Dispose(); if (this.callbackTimerTask is { } task) { await task.WaitAsync(cancellationToken); } if (MessageCenter is { } messageCenter) { await messageCenter.StopAsync(cancellationToken); } if (_manifestProvider is { } provider) { await provider.StopAsync(cancellationToken); } ConstructorReset(); } // used for testing to (carefully!) allow two clients in the same process private async Task StartInternal(CancellationToken cancellationToken) { var retryFilter = ServiceProvider.GetService(); var gatewayManager = this.ServiceProvider.GetRequiredService(); await ExecuteWithRetries( async () => await gatewayManager.StartAsync(cancellationToken), retryFilter, cancellationToken); MessageCenter = ActivatorUtilities.CreateInstance(this.ServiceProvider); MessageCenter.RegisterLocalMessageHandler(this.HandleMessage); await ExecuteWithRetries( async () => await MessageCenter.StartAsync(cancellationToken), retryFilter, cancellationToken); CurrentActivationAddress = GrainAddress.NewActivationAddress(MessageCenter.MyAddress, _localClientDetails.ClientId.GrainId); this.gatewayObserver = new ClientGatewayObserver(gatewayManager); this.InternalGrainFactory.CreateObjectReference(this.gatewayObserver); await ExecuteWithRetries( _manifestProvider.StartAsync, retryFilter, cancellationToken); static async Task ExecuteWithRetries(Func task, IClientConnectionRetryFilter retryFilter, CancellationToken cancellationToken) { do { try { await task(); return; } catch (Exception exception) when (retryFilter is not null && !cancellationToken.IsCancellationRequested) { var shouldRetry = await retryFilter.ShouldRetryConnectionAttempt(exception, cancellationToken); if (cancellationToken.IsCancellationRequested || !shouldRetry) { throw; } } } while (!cancellationToken.IsCancellationRequested); } } private void HandleMessage(Message message) { switch (message.Direction) { case Message.Directions.Response: { ReceiveResponse(message); break; } case Message.Directions.OneWay: case Message.Directions.Request: { this.localObjects.Dispatch(message); break; } default: LogMessageNotSupported(logger, message); break; } } public void SendResponse(Message request, Response response) { ThrowIfDisposed(); var message = this.messageFactory.CreateResponseMessage(request); OrleansOutsideRuntimeClientEvent.Log.SendResponse(message); message.BodyObject = response; MessageCenter.SendMessage(message); } public void SendRequest(GrainReference target, IInvokable request, IResponseCompletionSource context, InvokeMethodOptions options) { ThrowIfDisposed(); var cancellationToken = request.GetCancellationToken(); cancellationToken.ThrowIfCancellationRequested(); var message = this.messageFactory.CreateMessage(request, options); OrleansOutsideRuntimeClientEvent.Log.SendRequest(message); message.InterfaceType = target.InterfaceType; message.InterfaceVersion = target.InterfaceVersion; var targetGrainId = target.GrainId; var oneWay = (options & InvokeMethodOptions.OneWay) != 0; message.SendingGrain = CurrentActivationAddress.GrainId; message.TargetGrain = targetGrainId; if (SystemTargetGrainId.TryParse(targetGrainId, out var systemTargetGrainId)) { // If the silo isn't be supplied, it will be filled in by the sender to be the gateway silo message.TargetSilo = systemTargetGrainId.GetSiloAddress(); } if (this.clientMessagingOptions.DropExpiredMessages && message.IsExpirableMessage()) { // don't set expiration for system target messages. var ttl = request.GetDefaultResponseTimeout() ?? this.clientMessagingOptions.ResponseTimeout; message.TimeToLive = ttl; } if (!oneWay) { var callbackData = new CallbackData(this.sharedCallbackData, context, message, _applicationRequestInstruments); callbackData.SubscribeForCancellation(cancellationToken); callbacks.TryAdd(message.Id, callbackData); } else { context?.Complete(); } LogSendingMessage(logger, message); MessageCenter.SendMessage(message); } public void ReceiveResponse(Message response) { OrleansOutsideRuntimeClientEvent.Log.ReceiveResponse(response); LogReceivedMessage(logger, response); if (response.Result is Message.ResponseTypes.Status) { var status = (StatusResponse)response.BodyObject; callbacks.TryGetValue(response.Id, out var callback); var request = callback?.Message; if (request is not null) { callback.OnStatusUpdate(status); if (status.Diagnostics != null && status.Diagnostics.Count > 0) { LogReceivedStatusUpdateForPendingRequest(logger, request, new(status.Diagnostics)); } } else { if (clientMessagingOptions.CancelUnknownRequestOnStatusUpdate) { // Cancel the call since the caller has abandoned it. // Note that the target and sender arguments are swapped because this is a response to the original request. _cancellationManager?.SignalCancellation( response.SendingSilo, targetGrainId: response.SendingGrain, sendingGrainId: response.TargetGrain, messageId: response.Id); } if (status.Diagnostics != null && status.Diagnostics.Count > 0) { LogReceivedStatusUpdateForUnknownRequest(logger, response, new(status.Diagnostics)); } } return; } CallbackData callbackData; var found = callbacks.TryRemove(response.Id, out callbackData); if (found) { // We need to import the RequestContext here as well. // Unfortunately, it is not enough, since CallContext.LogicalGetData will not flow "up" from task completion source into the resolved task. // RequestContextExtensions.Import(response.RequestContextData); callbackData.DoCallback(response); } else { LogDebugNoCallbackForResponseMessage(logger, response); } } private void UnregisterCallback(CorrelationId id) { callbacks.TryRemove(id, out _); } private void ConstructorReset() { Utils.SafeExecute(() => this.Dispose()); } /// public TimeSpan GetResponseTimeout() => this.sharedCallbackData.ResponseTimeout; /// public void SetResponseTimeout(TimeSpan timeout) => this.sharedCallbackData.ResponseTimeout = timeout; public IAddressable CreateObjectReference(IAddressable obj) { if (obj is GrainReference) throw new ArgumentException("Argument obj is already a grain reference.", nameof(obj)); if (obj is IGrainBase) throw new ArgumentException("Argument must not be a grain class.", nameof(obj)); var observerId = obj is ClientObserver clientObserver ? clientObserver.GetObserverGrainId(_localClientDetails.ClientId) : ObserverGrainId.Create(_localClientDetails.ClientId); var reference = this.InternalGrainFactory.GetGrain(observerId.GrainId); if (!localObjects.TryRegister(obj, observerId)) { throw new ArgumentException($"Failed to add new observer {reference} to localObjects collection.", "reference"); } return reference; } public void DeleteObjectReference(IAddressable obj) { if (!(obj is GrainReference reference)) { throw new ArgumentException("Argument reference is not a grain reference."); } if (!ObserverGrainId.TryParse(reference.GrainId, out var observerId)) { throw new ArgumentException($"Reference {reference.GrainId} is not an observer reference"); } if (!localObjects.TryDeregister(observerId)) { throw new ArgumentException("Reference is not associated with a local object.", "reference"); } } public void Dispose() { if (this.disposing) return; this.disposing = true; Utils.SafeExecute(() => this.callbackTimer.Dispose()); Utils.SafeExecute(() => MessageCenter?.Dispose()); GC.SuppressFinalize(this); disposed = true; } public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo) { foreach (var callback in callbacks) { if (deadSilo.Equals(callback.Value.Message.TargetSilo)) { callback.Value.OnTargetSiloFail(); } } } public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType) => this.callbacks.Count(c => c.Value.Message.InterfaceType == grainInterfaceType); /// public void NotifyClusterConnectionLost() { foreach (var observer in _statusObservers) { try { observer.NotifyClusterConnectionLost(); } catch (Exception ex) { LogErrorSendingClusterDisconnectionNotification(logger, ex); } } } /// public void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways) { foreach (var observer in _statusObservers) { try { observer.NotifyGatewayCountChanged( currentNumberOfGateways, previousNumberOfGateways, currentNumberOfGateways > 0 && previousNumberOfGateways <= 0); } catch (Exception ex) { LogErrorSendingGatewayCountChangedNotification(logger, ex); } } } private async Task MonitorCallbackExpiry() { while (await callbackTimer.WaitForNextTickAsync()) { try { var currentStopwatchTicks = ValueStopwatch.GetTimestamp(); foreach (var (_, callback) in callbacks) { if (callback.IsCompleted) { continue; } if (callback.IsExpired(currentStopwatchTicks)) { callback.OnTimeout(); } } } catch (Exception ex) { LogErrorWhileProcessingCallbackExpiry(logger, ex); } } } private void ThrowIfDisposed() { if (disposed) { ThrowObjectDisposedException(); } void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(OutsideRuntimeClient)); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.ClientStarting, Message = "Starting Orleans client with runtime version '{RuntimeVersion}', local address '{LocalAddress}' and client id '{ClientId}'." )] private static partial void LogStartingClient(ILogger logger, string runtimeVersion, SiloAddress localAddress, ClientGrainId clientId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Runtime_Error_100319, Message = "OutsideRuntimeClient constructor failed." )] private static partial void LogConstructorException(ILogger logger, Exception exc); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.ProxyClient_StartDone, Message = "Started client with address '{ActivationAddress}' and id '{ClientId}'." )] private static partial void LogStartedClient(ILogger logger, GrainAddress activationAddress, ClientGrainId clientId); [LoggerMessage( Level = LogLevel.Trace, Message = "Send '{Message}'." )] private static partial void LogSendingMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Trace, Message = "Received '{Message}'." )] private static partial void LogReceivedMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Error, Message = "Message not supported: '{Message}'." )] private static partial void LogMessageNotSupported(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Error sending cluster disconnection notification." )] private static partial void LogErrorSendingClusterDisconnectionNotification(ILogger logger, Exception ex); [LoggerMessage( Level = LogLevel.Warning, Message = "Error sending gateway count changed notification." )] private static partial void LogErrorSendingGatewayCountChangedNotification(ILogger logger, Exception ex); [LoggerMessage( Level = LogLevel.Warning, Message = "Error while processing callback expiry." )] private static partial void LogErrorWhileProcessingCallbackExpiry(ILogger logger, Exception ex); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.Runtime_Error_100011, Message = "No callback for response message '{ResponseMessage}'" )] private static partial void LogDebugNoCallbackForResponseMessage(ILogger logger, Message responseMessage); private readonly struct DiagnosticsLogData(List diagnostics) { public override string ToString() => string.Join("\n", diagnostics); } [LoggerMessage( Level = LogLevel.Information, Message = "Received status update for pending request, Request: '{RequestMessage}'. Status: '{Diagnostics}'." )] private static partial void LogReceivedStatusUpdateForPendingRequest(ILogger logger, Message requestMessage, DiagnosticsLogData diagnostics); [LoggerMessage( Level = LogLevel.Information, Message = "Received status update for unknown request. Message: '{StatusMessage}'. Status: '{Diagnostics}'." )] private static partial void LogReceivedStatusUpdateForUnknownRequest(ILogger logger, Message statusMessage, DiagnosticsLogData diagnostics); } } ================================================ FILE: src/Orleans.Core/Runtime/RequestContextExtensions.cs ================================================ #nullable enable using System.Diagnostics; using Orleans.Diagnostics; using Orleans.Serialization; namespace Orleans.Runtime { /// /// Extensions for working with . /// public static class RequestContextExtensions { /// /// Imports the specified context data into the current , clearing all existing values. /// /// The context data. public static void Import(Dictionary? contextData) { var values = contextData switch { { Count: > 0 } => contextData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), _ => null, }; RequestContext.CallContextData.Value = new RequestContext.ContextProperties { Values = values, }; } /// /// Exports a copy of the current . /// /// The copier. /// A copy of the current request context. public static Dictionary? Export(DeepCopier copier) { var properties = RequestContext.CallContextData.Value; var values = properties.Values; var resultValues = (values != null && values.Count > 0) ? copier.Copy(values) : null; return resultValues; } internal static Guid GetReentrancyId(this Message message) => GetReentrancyId(message?.RequestContextData); internal static Guid GetReentrancyId(Dictionary? contextData) { if (contextData is not { Count: > 0 }) return Guid.Empty; _ = contextData.TryGetValue(RequestContext.CALL_CHAIN_REENTRANCY_HEADER, out var reentrancyId); return reentrancyId is Guid guid ? guid : Guid.Empty; } /// /// Extracts an ActivityContext from request context data if present. /// internal static ActivityContext? TryGetActivityContext(this Dictionary? requestContextData) { if (requestContextData is not { Count: > 0 }) { return null; } string? traceParent = null; string? traceState = null; if (requestContextData.TryGetValue(OpenTelemetryHeaders.TraceParent, out var traceParentObj) && traceParentObj is string tp) { traceParent = tp; } if (requestContextData.TryGetValue(OpenTelemetryHeaders.TraceState, out var traceStateObj) && traceStateObj is string ts) { traceState = ts; } if (!string.IsNullOrEmpty(traceParent) && ActivityContext.TryParse(traceParent, traceState, isRemote: true, out var parentContext)) { return parentContext; } return null; } } } ================================================ FILE: src/Orleans.Core/Runtime/RingRange.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; #nullable enable namespace Orleans.Runtime { /// /// Represents a range or set of ranges around a virtual ring where points along the ring are identified using values. /// public interface IRingRange { /// /// Returns a value indicating whether is within this ring range. /// /// /// The value to check. /// /// if the reminder is in our responsibility range, otherwise. bool InRange(uint value); /// /// Returns a value indicating whether is within this ring range. /// /// The value to check. /// if the reminder is in our responsibility range, otherwise. public sealed bool InRange(GrainId grainId) => InRange(grainId.GetUniformHashCode()); } // This is the internal interface to be used only by the different range implementations. internal interface IRingRangeInternal : IRingRange { double RangePercentage(); } /// /// Represents a single, contiguous range round a virtual ring where points along the ring are identified using values. /// /// public interface ISingleRange : IRingRange { /// /// Gets the exclusive lower bound of the range. /// uint Begin { get; } /// /// Gets the inclusive upper bound of the range. /// uint End { get; } } [Serializable, GenerateSerializer, Immutable] internal sealed class SingleRange : IRingRangeInternal, IEquatable, ISingleRange, ISpanFormattable { [Id(0)] private readonly uint begin; [Id(1)] private readonly uint end; /// /// Exclusive /// public uint Begin { get { return begin; } } /// /// Inclusive /// public uint End { get { return end; } } public SingleRange(uint begin, uint end) { this.begin = begin; this.end = end; } /// /// checks if n is element of (Begin, End], while remembering that the ranges are on a ring /// /// /// true if n is in (Begin, End], false otherwise public bool InRange(uint n) { uint num = n; if (begin < end) { return num > begin && num <= end; } // Begin > End return num > begin || num <= end; } public long RangeSize() { if (begin < end) { return end - begin; } return RangeFactory.RING_SIZE - (begin - end); } public double RangePercentage() => RangeSize() * (100.0 / RangeFactory.RING_SIZE); public bool Equals(SingleRange? other) => other != null && begin == other.begin && end == other.end; public override bool Equals(object? obj) => Equals(obj as SingleRange); public override int GetHashCode() => HashCode.Combine(GetType(), begin, end); public override string ToString() => begin == 0 && end == 0 ? "<(0 0], Size=x100000000, %Ring=100%>" : $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { return begin == 0 && end == 0 ? destination.TryWrite($"<(0 0], Size=x100000000, %Ring=100%>", out charsWritten) : destination.TryWrite($"<(x{begin:X8} x{end:X8}], Size=x{RangeSize():X8}, %Ring={RangePercentage():0.000}%>", out charsWritten); } internal bool Overlaps(SingleRange other) => Equals(other) || InRange(other.begin) || other.InRange(begin); internal SingleRange Merge(SingleRange other) { if ((begin | end) == 0 || (other.begin | other.end) == 0) { return RangeFactory.FullRange; } if (Equals(other)) { return this; } if (InRange(other.begin)) { return MergeEnds(other); } if (other.InRange(begin)) { return other.MergeEnds(this); } throw new InvalidOperationException("Ranges don't overlap"); } // other range begins inside this range, merge it based on where it ends private SingleRange MergeEnds(SingleRange other) { if (begin == other.end) { return RangeFactory.FullRange; } if (!InRange(other.end)) { return new SingleRange(begin, other.end); } if (other.InRange(begin)) { return RangeFactory.FullRange; } return this; } } /// /// Utility class for creating values. /// public static class RangeFactory { /// /// The ring size. /// public const long RING_SIZE = (long)uint.MaxValue + 1; /// /// Represents an empty range. /// private static readonly GeneralMultiRange EmptyRange = new(new()); /// /// Represents a full range. /// internal static readonly SingleRange FullRange = new(0, 0); /// /// Creates the full range. /// /// IRingRange. public static IRingRange CreateFullRange() => FullRange; /// /// Creates a new representing the values between the exclusive lower bound, , and the inclusive upper bound, . /// /// The exclusive lower bound. /// The inclusive upper bound. /// A new representing the values between the exclusive lower bound, , and the inclusive upper bound, . public static IRingRange CreateRange(uint begin, uint end) => new SingleRange(begin, end); /// /// Creates a new representing the union of all provided ranges. /// /// The ranges. /// A new representing the union of all provided ranges. public static IRingRange CreateRange(List inRanges) => inRanges.Count switch { 0 => EmptyRange, 1 => inRanges[0], _ => GeneralMultiRange.Create(inRanges) }; /// /// Creates equally divided sub-ranges from the provided range and returns one sub-range from that range. /// /// The range. /// The number of sub-ranges. /// The index of the sub-range to return. /// The identified sub-range. internal static IRingRange GetEquallyDividedSubRange(IRingRange range, int numSubRanges, int mySubRangeIndex) => EquallyDividedMultiRange.GetEquallyDividedSubRange(range, numSubRanges, mySubRangeIndex); /// /// Gets the contiguous sub-ranges represented by the provided range. /// /// The range. /// The contiguous sub-ranges represented by the provided range. public static IEnumerable GetSubRanges(IRingRange range) => range switch { ISingleRange single => new[] { single }, GeneralMultiRange m => m.Ranges, _ => throw new NotSupportedException(), }; } [Serializable, GenerateSerializer, Immutable] internal sealed class GeneralMultiRange : IRingRangeInternal, ISpanFormattable { [Id(0)] private readonly List ranges; [Id(1)] private readonly long rangeSize; internal List Ranges => ranges; internal GeneralMultiRange(List ranges) { Debug.Assert(ranges.Count != 1); this.ranges = ranges; foreach (var r in ranges) rangeSize += r.RangeSize(); } internal static IRingRange Create(List inRanges) { var ranges = inRanges.ConvertAll(r => (SingleRange)r); return HasOverlaps() ? Compact() : new GeneralMultiRange(ranges); bool HasOverlaps() { var last = ranges[0]; for (var i = 1; i < ranges.Count; i++) { if (last.Overlaps(last = ranges[i])) return true; } return false; } IRingRange Compact() { var lastIdx = 0; var last = ranges[0]; for (var i = 1; i < ranges.Count; i++) { var r = ranges[i]; if (last.Overlaps(r)) ranges[lastIdx] = last = last.Merge(r); else ranges[++lastIdx] = last = r; } if (lastIdx == 0) return last; ranges.RemoveRange(++lastIdx, ranges.Count - lastIdx); return new GeneralMultiRange(ranges); } } public bool InRange(uint n) { foreach (var s in ranges) { if (s.InRange(n)) return true; } return false; } public double RangePercentage() => rangeSize * (100.0 / RangeFactory.RING_SIZE); public override string ToString() => ranges.Count == 0 ? "Empty MultiRange" : $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { return ranges.Count == 0 ? destination.TryWrite($"Empty MultiRange", out charsWritten) : destination.TryWrite($"", out charsWritten); } } internal static class EquallyDividedMultiRange { private static SingleRange GetEquallyDividedSubRange(SingleRange singleRange, int numSubRanges, int mySubRangeIndex) { var rangeSize = singleRange.RangeSize(); uint portion = (uint)(rangeSize / numSubRanges); uint remainder = (uint)(rangeSize - portion * numSubRanges); uint start = singleRange.Begin; for (int i = 0; i < numSubRanges; i++) { // (Begin, End] uint end = unchecked(start + portion); // I want it to overflow on purpose. It will do the right thing. if (remainder > 0) { end++; remainder--; } if (i == mySubRangeIndex) return new SingleRange(start, end); start = end; // nextStart } throw new ArgumentException(nameof(mySubRangeIndex)); } // Takes a range and divides it into numSubRanges equal ranges and returns the subrange at mySubRangeIndex. public static IRingRange GetEquallyDividedSubRange(IRingRange range, int numSubRanges, int mySubRangeIndex) { if (numSubRanges <= 0) throw new ArgumentOutOfRangeException(nameof(numSubRanges)); if ((uint)mySubRangeIndex >= (uint)numSubRanges) throw new ArgumentOutOfRangeException(nameof(mySubRangeIndex)); if (numSubRanges == 1) return range; switch (range) { case SingleRange singleRange: return GetEquallyDividedSubRange(singleRange, numSubRanges, mySubRangeIndex); case GeneralMultiRange multiRange: switch (multiRange.Ranges.Count) { case 0: return multiRange; default: // Take each of the single ranges in the multi range and divide each into equal sub ranges. var singlesForThisIndex = new List(multiRange.Ranges.Count); foreach (var singleRange in multiRange.Ranges) singlesForThisIndex.Add(GetEquallyDividedSubRange(singleRange, numSubRanges, mySubRangeIndex)); return new GeneralMultiRange(singlesForThisIndex); } default: throw new ArgumentOutOfRangeException(nameof(range)); } } } } ================================================ FILE: src/Orleans.Core/Runtime/RuntimeVersion.cs ================================================ using System.Diagnostics; using System.Reflection; namespace Orleans.Runtime { internal static class RuntimeVersion { /// /// The full version string of the Orleans runtime, eg: '2012.5.9.51607 Build:12345 Timestamp: 20120509-185359' /// public static string Current { get { Assembly thisProg = typeof(RuntimeVersion).Assembly; var ApiVersion = thisProg.GetName().Version.ToString(); if (string.IsNullOrWhiteSpace(thisProg.Location)) { return ApiVersion; } FileVersionInfo progVersionInfo = FileVersionInfo.GetVersionInfo(thisProg.Location); bool isDebug = IsAssemblyDebugBuild(thisProg); string productVersion = progVersionInfo.ProductVersion + (isDebug ? " (Debug)." : " (Release)."); // progVersionInfo.IsDebug; does not work return string.IsNullOrEmpty(productVersion) ? ApiVersion : productVersion; } } /// /// Returns a value indicating whether the provided was built in debug mode. /// /// /// The assembly to check. /// /// /// A value indicating whether the provided assembly was built in debug mode. /// internal static bool IsAssemblyDebugBuild(Assembly assembly) { foreach (var debuggableAttribute in assembly.GetCustomAttributes()) { return debuggableAttribute.IsJITTrackingEnabled; } return false; } } } ================================================ FILE: src/Orleans.Core/Runtime/SharedCallbackData.cs ================================================ using System; using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Orleans.Runtime; internal sealed class SharedCallbackData { public readonly Action Unregister; public readonly ILogger Logger; private TimeSpan _responseTimeout; public long ResponseTimeoutStopwatchTicks; public SharedCallbackData( Action unregister, ILogger logger, TimeSpan responseTimeout, bool cancelOnTimeout, bool waitForCancellationAcknowledgement, IGrainCallCancellationManager cancellationManager) { Unregister = unregister; Logger = logger; ResponseTimeout = responseTimeout; CancelRequestOnTimeout = cancelOnTimeout; WaitForCancellationAcknowledgement = waitForCancellationAcknowledgement; CancellationManager = cancellationManager; } public TimeSpan ResponseTimeout { get => _responseTimeout; set { _responseTimeout = value; ResponseTimeoutStopwatchTicks = (long)(value.TotalSeconds * Stopwatch.Frequency); } } public IGrainCallCancellationManager CancellationManager { get; internal set; } public bool CancelRequestOnTimeout { get; } public bool WaitForCancellationAcknowledgement { get; } } ================================================ FILE: src/Orleans.Core/Runtime/SiloStatus.cs ================================================ namespace Orleans.Runtime { /// /// Possible statuses of a silo. /// [GenerateSerializer] public enum SiloStatus { /// /// No known status. /// None = 0, /// /// This silo was just created, but not started yet. /// Created = 1, /// /// This silo has just started, but not ready yet. It is attempting to join the cluster. /// Joining = 2, /// /// This silo is alive and functional. /// Active = 3, /// /// This silo is shutting itself down. /// ShuttingDown = 4, /// /// This silo is stopping itself down. /// Stopping = 5, /// /// This silo is deactivated/considered to be dead. /// Dead = 6 } /// /// Extensions for . /// public static class SiloStatusExtensions { /// /// Return true if this silo is currently terminating: ShuttingDown, Stopping or Dead. /// /// The silo status. /// true if the specified silo status is terminating; otherwise, false. public static bool IsTerminating(this SiloStatus siloStatus) { return siloStatus == SiloStatus.ShuttingDown || siloStatus == SiloStatus.Stopping || siloStatus == SiloStatus.Dead; } } } ================================================ FILE: src/Orleans.Core/Serialization/OrleansJsonSerializationBinder.cs ================================================ using System; using Newtonsoft.Json.Serialization; using Orleans.Serialization.TypeSystem; namespace Orleans.Serialization { /// /// Implementation of which resolves types using a . /// public class OrleansJsonSerializationBinder : DefaultSerializationBinder { private readonly TypeResolver typeResolver; /// /// Initializes a new instance of the class. /// /// The type resolver. public OrleansJsonSerializationBinder(TypeResolver typeResolver) { this.typeResolver = typeResolver; } /// public override Type BindToType(string assemblyName, string typeName) { var fullName = !string.IsNullOrWhiteSpace(assemblyName) ? typeName + ',' + assemblyName : typeName; if (typeResolver.TryResolveType(fullName, out var type)) return type; return base.BindToType(assemblyName, typeName); } } } ================================================ FILE: src/Orleans.Core/Serialization/OrleansJsonSerializer.cs ================================================ using System; using System.Net; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Runtime; using Orleans.GrainReferences; using Microsoft.Extensions.Options; namespace Orleans.Serialization { /// /// Utility class for configuring to support Orleans types. /// public class OrleansJsonSerializer { public const string UseFullAssemblyNamesProperty = "UseFullAssemblyNames"; public const string IndentJsonProperty = "IndentJSON"; public const string TypeNameHandlingProperty = "TypeNameHandling"; private readonly JsonSerializerSettings settings; /// /// Initializes a new instance of the class. /// public OrleansJsonSerializer(IOptions options) { this.settings = options.Value.JsonSerializerSettings; } /// /// Deserializes an object of the specified expected type from the provided input. /// /// The expected type. /// The input. /// The deserialized object. public object Deserialize(Type expectedType, string input) { if (string.IsNullOrWhiteSpace(input)) { return null; } return JsonConvert.DeserializeObject(input, expectedType, this.settings); } /// /// Serializes an object to a JSON string. /// /// The object to serialize. /// The type the deserializer should expect. public string Serialize(object item, Type expectedType) => JsonConvert.SerializeObject(item, expectedType, this.settings); } /// /// implementation for . /// /// public class IPAddressConverter : JsonConverter { /// public override bool CanConvert(Type objectType) { return (objectType == typeof(IPAddress)); } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IPAddress ip = (IPAddress)value; writer.WriteValue(ip.ToString()); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JToken token = JToken.Load(reader); return IPAddress.Parse(token.Value()); } } /// /// implementation for . /// /// public class GrainIdConverter : JsonConverter { /// public override bool CanConvert(Type objectType) => objectType == typeof(GrainId); /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { GrainId id = (GrainId)value; writer.WriteStartObject(); writer.WritePropertyName("Type"); writer.WriteValue(id.Type.ToString()); writer.WritePropertyName("Key"); writer.WriteValue(id.Key.ToString()); writer.WriteEndObject(); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); GrainId grainId = GrainId.Create(jo["Type"].ToObject(), jo["Key"].ToObject()); return grainId; } } /// /// implementation for . /// /// public class ActivationIdConverter : JsonConverter { /// public override bool CanConvert(Type objectType) => objectType == typeof(ActivationId); /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { ActivationId id = (ActivationId)value; writer.WriteValue(id.ToParsableString()); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return reader.Value switch { string { Length: > 0 } str => ActivationId.FromParsableString(str), _ => default }; } } /// /// implementation for . /// /// public class SiloAddressJsonConverter : JsonConverter { /// public override bool CanConvert(Type objectType) { return (objectType == typeof(SiloAddress)); } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { SiloAddress addr = (SiloAddress)value; writer.WriteValue(addr.ToParsableString()); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { switch (reader.TokenType) { case JsonToken.StartObject: var jo = JObject.Load(reader); return SiloAddress.FromParsableString(jo["SiloAddress"].ToObject()); case JsonToken.String: return SiloAddress.FromParsableString(reader.Value as string); } return null; } } /// /// implementation for . /// /// public class MembershipVersionJsonConverter : JsonConverter { /// public override bool CanConvert(Type objectType) => objectType == typeof(MembershipVersion); /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { MembershipVersion typedValue = (MembershipVersion)value; writer.WriteValue(typedValue.Value); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return reader.Value switch { long l => new MembershipVersion(l), _ => default }; } } /// /// implementation for . /// /// public class UniqueKeyConverter : JsonConverter { /// public override bool CanConvert(Type objectType) { return (objectType == typeof(UniqueKey)); } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { UniqueKey key = (UniqueKey)value; writer.WriteStartObject(); writer.WritePropertyName("UniqueKey"); writer.WriteValue(key.ToHexString()); writer.WriteEndObject(); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); UniqueKey addr = UniqueKey.Parse(jo["UniqueKey"].ToObject().AsSpan()); return addr; } } /// /// implementation for . /// /// public class IPEndPointConverter : JsonConverter { /// public override bool CanConvert(Type objectType) { return (objectType == typeof(IPEndPoint)); } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IPEndPoint ep = (IPEndPoint)value; writer.WriteStartObject(); writer.WritePropertyName("Address"); serializer.Serialize(writer, ep.Address); writer.WritePropertyName("Port"); writer.WriteValue(ep.Port); writer.WriteEndObject(); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); IPAddress address = jo["Address"].ToObject(serializer); int port = jo["Port"].Value(); return new IPEndPoint(address, port); } } /// /// implementation for . /// /// public class GrainReferenceJsonConverter : JsonConverter { private static readonly Type AddressableType = typeof(IAddressable); private readonly GrainReferenceActivator referenceActivator; /// /// Initializes a new instance of the class. /// /// The grain reference activator. public GrainReferenceJsonConverter(GrainReferenceActivator referenceActivator) { this.referenceActivator = referenceActivator; } /// public override bool CanConvert(Type objectType) { return AddressableType.IsAssignableFrom(objectType); } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var val = ((IAddressable)value).AsReference(); writer.WriteStartObject(); writer.WritePropertyName("Id"); writer.WriteStartObject(); writer.WritePropertyName("Type"); writer.WriteValue(val.GrainId.Type.ToString()); writer.WritePropertyName("Key"); writer.WriteValue(val.GrainId.Key.ToString()); writer.WriteEndObject(); writer.WritePropertyName("Interface"); writer.WriteValue(val.InterfaceType.ToString()); writer.WriteEndObject(); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); var id = jo["Id"]; GrainId grainId = GrainId.Create(id["Type"].ToObject(), id["Key"].ToObject()); var encodedInterface = jo["Interface"].ToString(); var iface = string.IsNullOrWhiteSpace(encodedInterface) ? default : GrainInterfaceType.Create(encodedInterface); return this.referenceActivator.CreateReference(grainId, iface); } } } ================================================ FILE: src/Orleans.Core/Serialization/OrleansJsonSerializerOptions.cs ================================================ using System; using Microsoft.Extensions.Options; using Newtonsoft.Json; namespace Orleans.Serialization { public class OrleansJsonSerializerOptions { public JsonSerializerSettings JsonSerializerSettings { get; set; } public OrleansJsonSerializerOptions() { JsonSerializerSettings = OrleansJsonSerializerSettings.GetDefaultSerializerSettings(); } } public class ConfigureOrleansJsonSerializerOptions : IPostConfigureOptions { private readonly IServiceProvider _serviceProvider; public ConfigureOrleansJsonSerializerOptions(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void PostConfigure(string name, OrleansJsonSerializerOptions options) { OrleansJsonSerializerSettings.Configure(_serviceProvider, options.JsonSerializerSettings); } } } ================================================ FILE: src/Orleans.Core/Serialization/OrleansJsonSerializerSettings.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Orleans.GrainReferences; using Orleans.Serialization.TypeSystem; namespace Orleans.Serialization { public static class OrleansJsonSerializerSettings { internal static JsonSerializerSettings GetDefaultSerializerSettings() { return new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, PreserveReferencesHandling = PreserveReferencesHandling.Objects, DateFormatHandling = DateFormatHandling.IsoDateFormat, DefaultValueHandling = DefaultValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, Formatting = Formatting.None, SerializationBinder = null, }; } /// /// Returns the default serializer settings. /// /// /// The service provider. /// /// The default serializer settings. public static JsonSerializerSettings GetDefaultSerializerSettings(IServiceProvider services) { var settings = GetDefaultSerializerSettings(); Configure(services, settings); return settings; } internal static void Configure(IServiceProvider services, JsonSerializerSettings jsonSerializerSettings) { if (jsonSerializerSettings.SerializationBinder == null) { var typeResolver = services.GetRequiredService(); jsonSerializerSettings.SerializationBinder = new OrleansJsonSerializationBinder(typeResolver); } jsonSerializerSettings.Converters.Add(new IPAddressConverter()); jsonSerializerSettings.Converters.Add(new IPEndPointConverter()); jsonSerializerSettings.Converters.Add(new GrainIdConverter()); jsonSerializerSettings.Converters.Add(new ActivationIdConverter()); jsonSerializerSettings.Converters.Add(new SiloAddressJsonConverter()); jsonSerializerSettings.Converters.Add(new MembershipVersionJsonConverter()); jsonSerializerSettings.Converters.Add(new UniqueKeyConverter()); jsonSerializerSettings.Converters.Add(new GrainReferenceJsonConverter(services.GetRequiredService())); } /// /// Updates the provided serializer settings with the specified options. /// /// The settings. /// if set to true, use full assembly-qualified names when formatting type names. /// if set to true, indent the formatted JSON. /// The type name handling options. /// The provided serializer settings. public static JsonSerializerSettings UpdateSerializerSettings(JsonSerializerSettings settings, bool useFullAssemblyNames, bool indentJson, TypeNameHandling? typeNameHandling) { if (useFullAssemblyNames) { settings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full; } if (indentJson) { settings.Formatting = Formatting.Indented; } if (typeNameHandling.HasValue) { settings.TypeNameHandling = typeNameHandling.Value; } return settings; } } } ================================================ FILE: src/Orleans.Core/Statistics/EnvironmentStatisticsProvider.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; using Orleans.Runtime; namespace Orleans.Statistics; #nullable enable internal sealed class EnvironmentStatisticsProvider : IEnvironmentStatisticsProvider, IDisposable { private const float OneKiloByte = 1024f; private long _availableMemoryBytes; private long _maximumAvailableMemoryBytes; private readonly EventCounterListener _eventCounterListener = new(); [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for memory-dump debugging.")] private readonly ObservableCounter _availableMemoryCounter; [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for memory-dump debugging.")] private readonly ObservableCounter _maximumAvailableMemoryCounter; private readonly DualModeKalmanFilter _cpuUsageFilter = new(); private readonly DualModeKalmanFilter _memoryUsageFilter = new(); public EnvironmentStatisticsProvider() { GC.Collect(0, GCCollectionMode.Forced, true); // we make sure the GC structure wont be empty, also performing a blocking GC guarantees immediate collection. _availableMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_AVAILABLE_MEMORY_MB, () => (long)(_availableMemoryBytes / OneKiloByte / OneKiloByte), unit: "MB"); _maximumAvailableMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.RUNTIME_MEMORY_TOTAL_PHYSICAL_MEMORY_MB, () => (long)(_maximumAvailableMemoryBytes / OneKiloByte / OneKiloByte), unit: "MB"); } /// public EnvironmentStatistics GetEnvironmentStatistics() { var cpuUsage = _eventCounterListener.CpuUsage; var memoryInfo = GC.GetGCMemoryInfo(); var maximumAvailableMemoryBytes = memoryInfo.TotalAvailableMemoryBytes; var memoryUsage = Math.Clamp(memoryInfo.TotalCommittedBytes - memoryInfo.FragmentedBytes, 0, maximumAvailableMemoryBytes); var availableMemory = maximumAvailableMemoryBytes - memoryUsage; var filteredCpuUsage = _cpuUsageFilter.Filter(cpuUsage); var filteredMemoryUsage = (long)_memoryUsageFilter.Filter(memoryUsage); var filteredAvailableMemory = maximumAvailableMemoryBytes - filteredMemoryUsage; var result = new EnvironmentStatistics( cpuUsagePercentage: filteredCpuUsage, rawCpuUsagePercentage: cpuUsage, memoryUsageBytes: filteredMemoryUsage, rawMemoryUsageBytes: memoryUsage, availableMemoryBytes: filteredAvailableMemory, rawAvailableMemoryBytes: availableMemory, maximumAvailableMemoryBytes: maximumAvailableMemoryBytes); _maximumAvailableMemoryBytes = result.MaximumAvailableMemoryBytes; _availableMemoryBytes = result.RawAvailableMemoryBytes; return result; } public void Dispose() => _eventCounterListener.Dispose(); private sealed class EventCounterListener : EventListener { public float CpuUsage { get; private set; } protected override void OnEventSourceCreated(EventSource source) { if (source.Name.Equals("System.Runtime")) { Dictionary? refreshInterval = new() { ["EventCounterIntervalSec"] = "1" }; EnableEvents(source, EventLevel.Informational, (EventKeywords)(-1), refreshInterval); } } protected override void OnEventWritten(EventWrittenEventArgs eventData) { if ("EventCounters".Equals(eventData.EventName) && eventData.Payload is { } payload) { for (var i = 0; i < payload.Count; i++) { if (payload[i] is IDictionary eventPayload && eventPayload.TryGetValue("Name", out var name) && "cpu-usage".Equals(name) && eventPayload.TryGetValue("Mean", out var mean) && mean is double value) { CpuUsage = Math.Clamp((float)value, 0f, 100f); break; } } } } } // See: https://www.ledjonbehluli.com/posts/orleans_resource_placement_kalman/ // The rationale behind using a cooperative dual-mode KF, is that we want the input signal to follow a trajectory that // decays with a slower rate than the original one, but also tracks the signal in case of signal increases // (which represent potential of overloading). Both are important, but they are inversely correlated to each other. private sealed class DualModeKalmanFilter { private const float SlowProcessNoiseCovariance = 0f; private const float FastProcessNoiseCovariance = 0.01f; private KalmanFilter _slowFilter = new(); private KalmanFilter _fastFilter = new(); private FilterRegime _regime = FilterRegime.Slow; private enum FilterRegime { Slow, Fast } public float Filter(float measurement) { float slowEstimate = _slowFilter.Filter(measurement, SlowProcessNoiseCovariance); float fastEstimate = _fastFilter.Filter(measurement, FastProcessNoiseCovariance); if (measurement > slowEstimate) { if (_regime == FilterRegime.Slow) { _regime = FilterRegime.Fast; _fastFilter.SetState(measurement, 0f); fastEstimate = _fastFilter.Filter(measurement, FastProcessNoiseCovariance); } return fastEstimate; } else { if (_regime == FilterRegime.Fast) { _regime = FilterRegime.Slow; _slowFilter.SetState(_fastFilter.PriorEstimate, _fastFilter.PriorErrorCovariance); slowEstimate = _slowFilter.Filter(measurement, SlowProcessNoiseCovariance); } return slowEstimate; } } private struct KalmanFilter() { public float PriorEstimate { get; private set; } = 0f; public float PriorErrorCovariance { get; private set; } = 1f; public void SetState(float estimate, float errorCovariance) { PriorEstimate = estimate; PriorErrorCovariance = errorCovariance; } public float Filter(float measurement, float processNoiseCovariance) { float estimate = PriorEstimate; float errorCovariance = PriorErrorCovariance + processNoiseCovariance; float gain = errorCovariance / (errorCovariance + 1f); float newEstimate = estimate + gain * (measurement - estimate); float newErrorCovariance = (1f - gain) * errorCovariance; PriorEstimate = newEstimate; PriorErrorCovariance = newErrorCovariance; return newEstimate; } } } } ================================================ FILE: src/Orleans.Core/Statistics/GrainCountStatistics.cs ================================================ using System.Collections.Generic; using System.Linq; namespace Orleans.Runtime; /// /// Centralized statistics on per-grain-type activation counts. /// internal class GrainCountStatistics { public IEnumerable> GetSimpleGrainStatistics() { return GrainMetricsListener .GrainCounts .Select(s => new KeyValuePair(s.Key, s.Value)) .Where(p => p.Value > 0); } } ================================================ FILE: src/Orleans.Core/Statistics/GrainMetricsListener.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; namespace Orleans.Runtime; internal static class GrainMetricsListener { internal static readonly ConcurrentDictionary GrainCounts = new(); private static readonly MeterListener MeterListener = new(); static GrainMetricsListener() { MeterListener.InstrumentPublished = (instrument, listener) => { if (instrument.Name == InstrumentNames.GRAIN_COUNTS) { listener.EnableMeasurementEvents(instrument); } }; MeterListener.SetMeasurementEventCallback(OnMeasurementRecorded); } internal static void Start() { MeterListener.Start(); } // Alternatives: // 1. Use existing *Statistics counters // 2. Copy source code from System.Diagnostics.Metrics.AggregationManager private static void OnMeasurementRecorded(Instrument instrument, int measurement, ReadOnlySpan> tags, object state) { var typeTag = tags[0]; var grainType = (string)typeTag.Value; if (measurement == 1) { GrainCounts.AddOrUpdate(grainType, 1, (k, v) => Interlocked.Increment(ref v)); } else if (measurement == -1) { GrainCounts.AddOrUpdate(grainType, -1, (k, v) => Interlocked.Decrement(ref v)); } } } ================================================ FILE: src/Orleans.Core/Statistics/OldEnvironmentStatistics.cs ================================================ using System; namespace Orleans.Statistics; [Obsolete("Used only until the interfaces that it implements are removed from the codebase")] internal sealed class OldEnvironmentStatistics(IEnvironmentStatisticsProvider statistics) : IAppEnvironmentStatistics, IHostEnvironmentStatistics { public float? CpuUsage => statistics.GetEnvironmentStatistics().FilteredCpuUsagePercentage; public long? MemoryUsage => statistics.GetEnvironmentStatistics().FilteredMemoryUsageBytes; public long? AvailableMemory => statistics.GetEnvironmentStatistics().FilteredAvailableMemoryBytes; public long? TotalPhysicalMemory => statistics.GetEnvironmentStatistics().MaximumAvailableMemoryBytes; } ================================================ FILE: src/Orleans.Core/Statistics/SiloRuntimeMetricsListener.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; using System.Threading; namespace Orleans.Runtime; // Can not use MetricsEventSource because it only supports single listener. public static class SiloRuntimeMetricsListener { private static readonly MeterListener MeterListener = new(); private static long _connectedClientCount; public static long ConnectedClientCount => _connectedClientCount; private static long _messageReceivedTotal; public static long MessageReceivedTotal => _messageReceivedTotal; private static long _messageSentTotal; public static long MessageSentTotal => _messageSentTotal; private static readonly string[] MetricNames = { // orleans InstrumentNames.GATEWAY_CONNECTED_CLIENTS, InstrumentNames.MESSAGING_RECEIVED_MESSAGES_SIZE, }; static SiloRuntimeMetricsListener() { MeterListener.InstrumentPublished = (instrument, listener) => { if (instrument.Meter != Instruments.Meter) { return; } if (MetricNames.Contains(instrument.Name)) { listener.EnableMeasurementEvents(instrument); } }; MeterListener.SetMeasurementEventCallback(OnMeasurementRecorded); } internal static void Start() { MeterListener.Start(); } private static void OnMeasurementRecorded(Instrument instrument, int measurement, ReadOnlySpan> tags, object state) { if (instrument == MessagingInstruments.ConnectedClient) { Interlocked.Add(ref _connectedClientCount, measurement); } if (instrument == MessagingInstruments.MessageReceivedSizeHistogram) { Interlocked.Add(ref _messageReceivedTotal, measurement); } if (instrument == MessagingInstruments.MessageSentSizeHistogram) { Interlocked.Add(ref _messageSentTotal, measurement); } } } ================================================ FILE: src/Orleans.Core/Statistics/SiloRuntimeStatistics.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Core.Messaging; using Orleans.Statistics; namespace Orleans.Runtime { /// /// Snapshot of current runtime statistics for a silo /// [Serializable, GenerateSerializer, Immutable] public sealed class SiloRuntimeStatistics { /// /// Total number of activations in a silo. /// [Id(0)] public int ActivationCount { get; } /// /// Number of activations in a silo that have been recently used. /// [Id(1)] public int RecentlyUsedActivationCount { get; } /// /// The CPU utilization. /// [Id(2), Obsolete($"The will be removed, use {nameof(EnvironmentStatistics)}.{nameof(EnvironmentStatistics.FilteredCpuUsagePercentage)} instead.", error: false)] public float? CpuUsage { get; } /// /// The amount of memory available in the silo [bytes]. /// [Id(3), Obsolete($"The will be removed, use {nameof(EnvironmentStatistics)}.{nameof(EnvironmentStatistics.FilteredAvailableMemoryBytes)} instead.", error: false)] public float? AvailableMemory { get; } /// /// The used memory size. /// [Id(4), Obsolete($"The will be removed, use {nameof(EnvironmentStatistics)}.{nameof(EnvironmentStatistics.FilteredMemoryUsageBytes)} instead.", error: false)] public long? MemoryUsage { get; } /// /// The total physical memory available [bytes]. /// [Id(5), Obsolete($"The will be removed, use {nameof(EnvironmentStatistics)}.{nameof(EnvironmentStatistics.MaximumAvailableMemoryBytes)} instead.", error: false)] public long? TotalPhysicalMemory { get; } /// /// Is this silo overloaded. /// [Id(6)] public bool IsOverloaded { get; } /// /// The number of clients currently connected to that silo. /// [Id(7)] public long ClientCount { get; } /// /// The number of messages received by that silo. /// [Id(8)] public long ReceivedMessages { get; } /// /// The number of messages sent by that silo. /// [Id(9)] public long SentMessages { get; } /// /// The DateTime when this statistics was created. /// [Id(10)] public DateTime DateTime { get; } [Id(11)] public EnvironmentStatistics EnvironmentStatistics { get; } internal SiloRuntimeStatistics( int activationCount, int recentlyUsedActivationCount, IEnvironmentStatisticsProvider environmentStatisticsProvider, IOptions loadSheddingOptions, DateTime dateTime) { ActivationCount = activationCount; RecentlyUsedActivationCount = recentlyUsedActivationCount; ClientCount = SiloRuntimeMetricsListener.ConnectedClientCount; ReceivedMessages = SiloRuntimeMetricsListener.MessageReceivedTotal; SentMessages = SiloRuntimeMetricsListener.MessageSentTotal; DateTime = dateTime; var statistics = environmentStatisticsProvider.GetEnvironmentStatistics(); EnvironmentStatistics = statistics; IsOverloaded = loadSheddingOptions.Value.LoadSheddingEnabled && OverloadDetectionLogic.IsOverloaded(ref statistics, loadSheddingOptions.Value); #pragma warning disable 618 CpuUsage = statistics.FilteredCpuUsagePercentage; MemoryUsage = statistics.FilteredMemoryUsageBytes; AvailableMemory = statistics.FilteredAvailableMemoryBytes; TotalPhysicalMemory = statistics.MaximumAvailableMemoryBytes; #pragma warning restore 618 } public override string ToString() => @$"SiloRuntimeStatistics: ActivationCount={ActivationCount} RecentlyUsedActivationCount={RecentlyUsedActivationCount } CpuUsagePercentage={EnvironmentStatistics.FilteredCpuUsagePercentage} MemoryUsageBytes={EnvironmentStatistics.FilteredMemoryUsageBytes } AvailableMemory={EnvironmentStatistics.FilteredAvailableMemoryBytes} MaximumAvailableMemoryBytes={EnvironmentStatistics.MaximumAvailableMemoryBytes } IsOverloaded={IsOverloaded} ClientCount={ClientCount} ReceivedMessages={ReceivedMessages} SentMessages={SentMessages} DateTime={DateTime}"; } /// /// Simple snapshot of current statistics for a given grain type on a given silo. /// [Serializable, GenerateSerializer, Immutable] public sealed class SimpleGrainStatistic { /// /// The type of the grain for this SimpleGrainStatistic. /// [Id(0)] public string GrainType { get; init; } /// /// The silo address for this SimpleGrainStatistic. /// [Id(1)] public SiloAddress SiloAddress { get; init; } /// /// The number of activations of this grain type on this given silo. /// [Id(2)] public int ActivationCount { get; init; } /// /// Returns the string representation of this SimpleGrainStatistic. /// public override string ToString() => $"SimpleGrainStatistic: GrainType={GrainType} Silo={SiloAddress} NumActivations={ActivationCount} "; } [Serializable, GenerateSerializer, Immutable] public sealed class DetailedGrainStatistic { /// /// The type of the grain for this DetailedGrainStatistic. /// [Id(0)] public string GrainType { get; init; } /// /// The silo address for this DetailedGrainStatistic. /// [Id(1)] public SiloAddress SiloAddress { get; init; } /// /// Unique Id for the grain. /// [Id(2)] public GrainId GrainId { get; init; } } [Serializable, GenerateSerializer, Immutable] internal sealed class DetailedGrainReport { [Id(0)] public GrainId Grain { get; init; } /// silo on which these statistics come from [Id(1)] public SiloAddress SiloAddress { get; init; } /// silo on which these statistics come from [Id(2)] public string SiloName { get; init; } /// activation addresses in the local directory cache [Id(3)] public GrainAddress LocalCacheActivationAddress { get; init; } /// activation addresses in the local directory. [Id(4)] public GrainAddress LocalDirectoryActivationAddress { get; init; } /// primary silo for this grain [Id(5)] public SiloAddress PrimaryForGrain { get; init; } /// the name of the class that implements this grain. [Id(6)] public string GrainClassTypeName { get; init; } /// activation on this silo [Id(7)] public string LocalActivation { get; init; } public override string ToString() => @$"{Environment.NewLine }**DetailedGrainReport for grain {Grain} from silo {SiloName} SiloAddress={SiloAddress}{Environment.NewLine } LocalCacheActivationAddresses={LocalCacheActivationAddress}{Environment.NewLine } LocalDirectoryActivationAddresses={LocalDirectoryActivationAddress}{Environment.NewLine } PrimaryForGrain={PrimaryForGrain}{Environment.NewLine } GrainClassTypeName={GrainClassTypeName}{Environment.NewLine } LocalActivation:{Environment.NewLine }{LocalActivation}.{Environment.NewLine}"; } } ================================================ FILE: src/Orleans.Core/SystemTargetInterfaces/IDeploymentLoadPublisher.cs ================================================ using System.Threading.Tasks; using Orleans.Concurrency; namespace Orleans.Runtime { internal interface IDeploymentLoadPublisher : ISystemTarget { [OneWay] Task UpdateRuntimeStatistics(SiloAddress siloAddress, SiloRuntimeStatistics siloStats); } } ================================================ FILE: src/Orleans.Core/SystemTargetInterfaces/IManagementGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Providers; namespace Orleans.Runtime { /// /// Interface for system management functions of silos, /// exposed as a grain for receiving remote requests / commands. /// public interface IManagementGrain : IGrainWithIntegerKey, IVersionManager { /// /// Get the list of silo hosts and statuses currently known about in this cluster. /// /// Whether data on just current active silos should be returned, /// or by default data for all current and previous silo instances [including those in Joining or Dead status]. /// The hosts and their corresponding statuses. Task> GetHosts(bool onlyActive = false); /// /// Get the list of silo hosts and membership information currently known about in this cluster. /// /// Whether data on just current active silos should be returned, /// or by default data for all current and previous silo instances [including those in Joining or Dead status]. /// The host entries. Task GetDetailedHosts(bool onlyActive = false); /// /// Perform a run of the .NET garbage collector in the specified silos. /// /// List of silos this command is to be sent to. /// A representing the work performed. Task ForceGarbageCollection(SiloAddress[] hostsIds); /// Perform a run of the Orleans activation collector in the specified silos. /// List of silos this command is to be sent to. /// Maximum idle time of activations to be collected. /// A representing the work performed. Task ForceActivationCollection(SiloAddress[] hostsIds, TimeSpan ageLimit); /// /// Forces activation collection. /// /// The age limit. Grains which have been idle for longer than this period of time will be eligible for collection. /// A representing the work performed. Task ForceActivationCollection(TimeSpan ageLimit); /// Perform a run of the silo statistics collector in the specified silos. /// List of silos this command is to be sent to. /// A representing the work performed. Task ForceRuntimeStatisticsCollection(SiloAddress[] siloAddresses); /// /// Return the most recent silo runtime statistics information for the specified silos. /// /// List of silos this command is to be sent to. /// Runtime statistics from the specified hosts. Task GetRuntimeStatistics(SiloAddress[] hostsIds); /// /// Return the most recent grain statistics information, amalgamated across silos. /// /// List of silos this command is to be sent to. /// Simple grain statistics for the specified hosts. Task GetSimpleGrainStatistics(SiloAddress[] hostsIds); /// /// Return the most recent grain statistics information, amalgamated across all silos. /// /// Simple grain statistics. Task GetSimpleGrainStatistics(); /// /// Returns the most recent detailed grain statistics information, amalgamated across silos for the specified types. /// /// List of silos this command is to be sent to. /// Array of grain types to filter the results with /// Detailed grain statistics. Task GetDetailedGrainStatistics(string[] types = null, SiloAddress[] hostsIds = null); /// /// Gets the grain activation count for a specific grain type. /// /// The grain reference. /// Gets the number of activations of grains with the same type as the provided grain reference. Task GetGrainActivationCount(GrainReference grainReference); /// /// Return the total count of all current grain activations across all silos. /// /// The total number of grain activations across all silos. Task GetTotalActivationCount(); /// /// Execute a control command on the specified providers on all silos in the cluster. /// Commands are sent to all known providers on each silo which match both the providerTypeFullName AND providerName parameters. /// /// /// Providers must implement the Orleans.Providers.IControllable /// interface in order to receive these control channel commands. /// /// Provider name to send this command to. /// An id / serial number of this command. /// This is an opaque value to the Orleans runtime - the control protocol semantics are decided between the sender and provider. /// An opaque command argument. /// This is an opaque value to the Orleans runtime - the control protocol semantics are decided between the sender and provider. /// Completion promise for this operation. public Task SendControlCommandToProvider(string providerName, int command, object arg = null) where T : IControllable; /// /// Return the where a given Grain is activated (if any). /// /// /// Please note that this method does not represent a strong consistent view of the Grain Catalog. /// The return of this method is taken based on a last known state of the grain which may or may not be up-to-date by the time the caller receive the request. /// /// The to look up. /// The where the Grain is activated or null if not activated taken from a snapshot of the last known state of the Grain Catalog. ValueTask GetActivationAddress(IAddressable reference); /// /// Returns all activations of the specified grain type. /// /// The type. /// A list of all active grains of the specified type. ValueTask> GetActiveGrains(GrainType type); /// /// Gets estimated grain call frequency statistics from the specified hosts. /// /// The hosts to request grain call frequency counts from. /// A list of estimated grain call frequencies. /// /// Note that this resulting collection does not necessarily contain all grain calls. It contains an estimation of the calls with the highest frequency. /// Task> GetGrainCallFrequencies(SiloAddress[] hostsIds = null); /// /// For testing only. Resets grain call frequency counts on the specified hosts. /// /// The hosts to invoke the operation on. /// A task representing the work performed. ValueTask ResetGrainCallFrequencies(SiloAddress[] hostsIds = null); } /// /// Represents an estimation of the frequency calls made from a source grain to a target grain. /// [GenerateSerializer] [Alias("Orleans.Runtime.GrainCallFrequency")] [Immutable] public struct GrainCallFrequency { /// /// The source grain. /// [Id(0)] public GrainId SourceGrain { get; set; } /// /// The target grain. /// [Id(1)] public GrainId TargetGrain { get; set; } /// /// The source host. /// [Id(2)] public SiloAddress SourceHost { get; set; } /// /// The target host. /// [Id(3)] public SiloAddress TargetHost { get; set; } /// /// The estimated number of calls made. /// [Id(4)] public ulong CallCount { get; set; } } } ================================================ FILE: src/Orleans.Core/SystemTargetInterfaces/IMembershipService.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Runtime { internal interface IMembershipService : ISystemTarget { /// /// Receive notifications about a change in the membership table /// /// Snapshot of the membership table /// Task MembershipChangeNotification(MembershipTableSnapshot snapshot); /// /// Ping request from another silo that probes the liveness of the recipient silo. /// /// A unique sequence number for ping message, to facilitate testing and debugging. Task Ping(int pingNumber); Task ProbeIndirectly(SiloAddress target, TimeSpan probeTimeout, int probeNumber); } /// /// Represents the result of probing a node via an intermediary node. /// [Serializable, GenerateSerializer, Immutable] public readonly struct IndirectProbeResponse { /// /// The health score of the intermediary node. /// [Id(0)] public int IntermediaryHealthScore { get; init; } /// /// if the probe succeeded; otherwise, . /// [Id(1)] public bool Succeeded { get; init; } /// /// The duration of the probe attempt. /// [Id(2)] public TimeSpan ProbeResponseTime { get; init; } /// /// The failure message if the probe did not succeed. /// [Id(3)] public string FailureMessage { get; init; } /// public override string ToString() => $"IndirectProbeResponse {{ Succeeded: {Succeeded}, IntermediaryHealthScore: {IntermediaryHealthScore}, ProbeResponseTime: {ProbeResponseTime}, FailureMessage: {FailureMessage} }}"; } } ================================================ FILE: src/Orleans.Core/SystemTargetInterfaces/IMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Net; using System.Text.Json.Serialization; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans { /// /// Interface for Membership Table. /// public interface IMembershipTable { /// /// Initializes the membership table, will be called before all other methods /// /// whether an attempt will be made to init the underlying table Task InitializeMembershipTable(bool tryInitTableVersion); /// /// Deletes all table entries of the given clusterId /// Task DeleteMembershipTableEntries(string clusterId); /// /// Delete all dead silo entries older than /// Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate); /// /// Atomically reads the Membership Table information about a given silo. /// The returned MembershipTableData includes one MembershipEntry entry for a given silo and the /// TableVersion for this table. The MembershipEntry and the TableVersion have to be read atomically. /// /// The address of the silo whose membership information needs to be read. /// The membership information for a given silo: MembershipTableData consisting one MembershipEntry entry and /// TableVersion, read atomically. Task ReadRow(SiloAddress key); /// /// Atomically reads the full content of the Membership Table. /// The returned MembershipTableData includes all MembershipEntry entry for all silos in the table and the /// TableVersion for this table. The MembershipEntries and the TableVersion have to be read atomically. /// /// The membership information for a given table: MembershipTableData consisting multiple MembershipEntry entries and /// TableVersion, all read atomically. Task ReadAll(); /// /// Atomically tries to insert (add) a new MembershipEntry for one silo and also update the TableVersion. /// If operation succeeds, the following changes would be made to the table: /// 1) New MembershipEntry will be added to the table. /// 2) The newly added MembershipEntry will also be added with the new unique automatically generated eTag. /// 3) TableVersion.Version in the table will be updated to the new TableVersion.Version. /// 4) TableVersion etag in the table will be updated to the new unique automatically generated eTag. /// All those changes to the table, insert of a new row and update of the table version and the associated etags, should happen atomically, or fail atomically with no side effects. /// The operation should fail in each of the following conditions: /// 1) A MembershipEntry for a given silo already exist in the table /// 2) Update of the TableVersion failed since the given TableVersion etag (as specified by the TableVersion.VersionEtag property) did not match the TableVersion etag in the table. /// /// MembershipEntry to be inserted. /// The new TableVersion for this table, along with its etag. /// True if the insert operation succeeded and false otherwise. Task InsertRow(MembershipEntry entry, TableVersion tableVersion); /// /// Atomically tries to update the MembershipEntry for one silo and also update the TableVersion. /// If operation succeeds, the following changes would be made to the table: /// 1) The MembershipEntry for this silo will be updated to the new MembershipEntry (the old entry will be fully substituted by the new entry) /// 2) The eTag for the updated MembershipEntry will also be eTag with the new unique automatically generated eTag. /// 3) TableVersion.Version in the table will be updated to the new TableVersion.Version. /// 4) TableVersion etag in the table will be updated to the new unique automatically generated eTag. /// All those changes to the table, update of a new row and update of the table version and the associated etags, should happen atomically, or fail atomically with no side effects. /// The operation should fail in each of the following conditions: /// 1) A MembershipEntry for a given silo does not exist in the table /// 2) A MembershipEntry for a given silo exist in the table but its etag in the table does not match the provided etag. /// 3) Update of the TableVersion failed since the given TableVersion etag (as specified by the TableVersion.VersionEtag property) did not match the TableVersion etag in the table. /// /// MembershipEntry to be updated. /// The etag for the given MembershipEntry. /// The new TableVersion for this table, along with its etag. /// True if the update operation succeeded and false otherwise. Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion); /// /// Updates the IAmAlive part (column) of the MembershipEntry for this silo. /// This operation should only update the IAmAlive column and not change other columns. /// This operation is a "dirty write" or "in place update" and is performed without etag validation. /// With regards to eTags update: /// This operation may automatically update the eTag associated with the given silo row, but it does not have to. It can also leave the etag not changed ("dirty write"). /// With regards to TableVersion: /// this operation should not change the TableVersion of the table. It should leave it untouched. /// There is no scenario where this operation could fail due to table semantical reasons. It can only fail due to network problems or table unavailability. /// /// /// Task representing the successful execution of this operation. Task UpdateIAmAlive(MembershipEntry entry); } /// /// Membership table interface for system target based implementation. /// public interface IMembershipTableSystemTarget : IMembershipTable, ISystemTarget { } [Serializable, GenerateSerializer, Immutable] public sealed class TableVersion : ISpanFormattable, IEquatable { /// /// The version part of this TableVersion. Monotonically increasing number. /// [Id(0)] public int Version { get; } /// /// The etag of this TableVersion, used for validation of table update operations. /// [Id(1)] public string VersionEtag { get; } public TableVersion(int version, string eTag) { Version = version; VersionEtag = eTag; } public TableVersion Next() => new (Version + 1, VersionEtag); public override string ToString() => $"<{Version}, {VersionEtag}>"; string IFormattable.ToString(string format, IFormatProvider formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) => destination.TryWrite($"<{Version}, {VersionEtag}>", out charsWritten); public override bool Equals(object obj) => Equals(obj as TableVersion); public override int GetHashCode() => HashCode.Combine(Version, VersionEtag); public bool Equals(TableVersion other) => other is not null && Version == other.Version && VersionEtag == other.VersionEtag; public static bool operator ==(TableVersion left, TableVersion right) => EqualityComparer.Default.Equals(left, right); public static bool operator !=(TableVersion left, TableVersion right) => !(left == right); } [Serializable] [GenerateSerializer] public sealed class MembershipTableData { [Id(0)] public IReadOnlyList> Members { get; private set; } [Id(1)] public TableVersion Version { get; private set; } public MembershipTableData(List> list, TableVersion version) { // put deads at the end, just for logging. list.Sort( (x, y) => { if (x.Item1.Status == SiloStatus.Dead) return 1; // put Deads at the end if (y.Item1.Status == SiloStatus.Dead) return -1; // put Deads at the end return string.CompareOrdinal(x.Item1.SiloName, y.Item1.SiloName); }); Members = list; Version = version; } public MembershipTableData(Tuple tuple, TableVersion version) { Members = new[] { tuple }; Version = version; } public MembershipTableData(TableVersion version) { Members = Array.Empty>(); Version = version; } public Tuple TryGet(SiloAddress silo) { foreach (var item in Members) if (item.Item1.SiloAddress.Equals(silo)) return item; return null; } public override string ToString() { int active = Members.Count(e => e.Item1.Status == SiloStatus.Active); int dead = Members.Count(e => e.Item1.Status == SiloStatus.Dead); int created = Members.Count(e => e.Item1.Status == SiloStatus.Created); int joining = Members.Count(e => e.Item1.Status == SiloStatus.Joining); int shuttingDown = Members.Count(e => e.Item1.Status == SiloStatus.ShuttingDown); int stopping = Members.Count(e => e.Item1.Status == SiloStatus.Stopping); return @$"{Members.Count} silos, {active} are Active, {dead} are Dead{ (created > 0 ? $", {created} are Created" : null)}{ (joining > 0 ? $", {joining} are Joining" : null)}{ (shuttingDown > 0 ? $", {shuttingDown} are ShuttingDown" : null)}{ (stopping > 0 ? $", {stopping} are Stopping" : null) }, Version={Version}. All silos: {Utils.EnumerableToString(Members.Select(t => t.Item1))}"; } // return a copy of the table removing all dead appereances of dead nodes, except for the last one. public MembershipTableData WithoutDuplicateDeads() { var dead = new Dictionary>(); // pick only latest Dead for each instance foreach (var next in Members.Where(item => item.Item1.Status == SiloStatus.Dead)) { var ipEndPoint = next.Item1.SiloAddress.Endpoint; Tuple prev; if (!dead.TryGetValue(ipEndPoint, out prev)) { dead[ipEndPoint] = next; } else { // later dead. if (next.Item1.SiloAddress.Generation.CompareTo(prev.Item1.SiloAddress.Generation) > 0) dead[ipEndPoint] = next; } } //now add back non-dead List> all = dead.Values.ToList(); all.AddRange(Members.Where(item => item.Item1.Status != SiloStatus.Dead)); return new MembershipTableData(all, Version); } internal Dictionary GetSiloStatuses(Func filter, bool includeMyself, SiloAddress myAddress) { var result = new Dictionary(); foreach (var memberEntry in this.Members) { var entry = memberEntry.Item1; if (!includeMyself && entry.SiloAddress.Equals(myAddress)) continue; if (filter(entry.Status)) result[entry.SiloAddress] = entry.Status; } return result; } } [GenerateSerializer] [Serializable] public sealed class MembershipEntry { /// /// The silo unique identity (ip:port:epoch). Used mainly by the Membership Protocol. /// [Id(0)] public SiloAddress SiloAddress { get; set; } /// /// The silo status. Managed by the Membership Protocol. /// [Id(1)] public SiloStatus Status { get; set; } /// /// The list of silos that suspect this silo. Managed by the Membership Protocol. /// [Id(2)] public List> SuspectTimes { get; set; } /// /// Silo to clients TCP port. Set on silo startup. /// [Id(3)] public int ProxyPort { get; set; } /// /// The DNS host name of the silo. Equals to Dns.GetHostName(). Set on silo startup. /// [Id(4)] public string HostName { get; set; } /// /// the name of the specific silo instance within a cluster. /// If running in Azure - the name of this role instance. Set on silo startup. /// [Id(5)] public string SiloName { get; set; } [Id(6)] public string RoleName { get; set; } // Optional - only for Azure role [Id(7)] public int UpdateZone { get; set; } // Optional - only for Azure role [Id(8)] public int FaultZone { get; set; } // Optional - only for Azure role /// /// Time this silo was started. For diagnostics and troubleshooting only. /// [Id(9)] public DateTime StartTime { get; set; } /// /// the last time this silo reported that it is alive. For diagnostics and troubleshooting only. /// [Id(10)] public DateTime IAmAliveTime { get; set; } internal DateTime EffectiveIAmAliveTime { get { var startTimeUtc = DateTime.SpecifyKind(StartTime, DateTimeKind.Utc); var iAmAliveTimeUtc = DateTime.SpecifyKind(IAmAliveTime, DateTimeKind.Utc); return startTimeUtc > iAmAliveTimeUtc ? startTimeUtc : iAmAliveTimeUtc; } } public void AddOrUpdateSuspector(SiloAddress localSilo, DateTime voteTime, int maxVotes) { var allVotes = SuspectTimes ??= new List>(); // Find voting place: // update my vote, if I voted previously // OR if the list is not full - just add a new vote // OR overwrite the oldest entry. int indexToWrite = allVotes.FindIndex(voter => localSilo.Equals(voter.Item1)); if (indexToWrite == -1) { // My vote is not recorded. Find the most outdated vote if the list is full, and overwrite it if (allVotes.Count >= maxVotes) // if the list is full { // The list is full, so pick the most outdated value to overwrite. DateTime minVoteTime = allVotes.Min(voter => voter.Item2); // Only overwrite an existing vote if the local time is greater than the current minimum vote time. if (voteTime >= minVoteTime) { indexToWrite = allVotes.FindIndex(voter => voter.Item2.Equals(minVoteTime)); } } } if (indexToWrite == -1) { AddSuspector(localSilo, voteTime); } else { var newEntry = new Tuple(localSilo, voteTime); SuspectTimes[indexToWrite] = newEntry; } } public void AddSuspector(SiloAddress suspectingSilo, DateTime suspectingTime) => (SuspectTimes ??= new()).Add(Tuple.Create(suspectingSilo, suspectingTime)); internal MembershipEntry Copy() { var copy = new MembershipEntry { SiloAddress = SiloAddress, Status = Status, SuspectTimes = SuspectTimes is null ? null : new(SuspectTimes), ProxyPort = ProxyPort, HostName = HostName, SiloName = SiloName, RoleName = RoleName, UpdateZone = UpdateZone, FaultZone = FaultZone, StartTime = StartTime, IAmAliveTime = IAmAliveTime }; return copy; } internal MembershipEntry WithStatus(SiloStatus status) { var updated = this.Copy(); updated.Status = status; return updated; } internal MembershipEntry WithIAmAliveTime(DateTime iAmAliveTime) { var updated = this.Copy(); updated.IAmAliveTime = iAmAliveTime; return updated; } internal ImmutableList> GetFreshVotes(DateTime now, TimeSpan expiration) { if (this.SuspectTimes == null) return ImmutableList>.Empty; var result = ImmutableList.CreateBuilder>(); // Find the latest time from the set of suspect times and the local time. // This prevents local clock skew from resulting in a different tally of fresh votes. var recencyWindowEndTime = Max(now, SuspectTimes); foreach (var voter in this.SuspectTimes) { // If now is smaller than otherVoterTime, than assume the otherVoterTime is fresh. // This could happen if clocks are not synchronized and the other voter clock is ahead of mine. var suspectTime = voter.Item2; if (recencyWindowEndTime.Subtract(suspectTime) < expiration) { result.Add(voter); } } return result.ToImmutable(); static DateTime Max(DateTime localTime, List> suspectTimes) { var maxValue = localTime; foreach (var entry in suspectTimes) { var suspectTime = entry.Item2; if (suspectTime > maxValue) maxValue = suspectTime; } return maxValue; } } public override string ToString() => $"SiloAddress={SiloAddress} SiloName={SiloName} Status={Status}"; public string ToFullString() { var suspecters = SuspectTimes == null ? null : Utils.EnumerableToString(SuspectTimes.Select(tuple => tuple.Item1)); var suspectTimes = SuspectTimes == null ? null : Utils.EnumerableToString(SuspectTimes.Select(tuple => LogFormatter.PrintDate(tuple.Item2))); return @$"[SiloAddress={SiloAddress} SiloName={SiloName} Status={Status} HostName={HostName} ProxyPort={ProxyPort} RoleName={RoleName } UpdateZone={UpdateZone} FaultZone={FaultZone} StartTime={LogFormatter.PrintDate(StartTime)} IAmAliveTime={LogFormatter.PrintDate(IAmAliveTime) }{(suspecters == null ? null : " Suspecters=")}{suspecters}{(suspectTimes == null ? null : " SuspectTimes=")}{suspectTimes}]"; } } } ================================================ FILE: src/Orleans.Core/SystemTargetInterfaces/ISiloControl.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Providers; using Orleans.Runtime; namespace Orleans { internal interface ISiloControl : ISystemTarget, IVersionManager { Task Ping(string message); Task ForceGarbageCollection(); Task ForceActivationCollection(TimeSpan ageLimit); Task ForceRuntimeStatisticsCollection(); Task GetRuntimeStatistics(); Task>> GetGrainStatistics(); Task> GetDetailedGrainStatistics(string[] types = null); Task GetSimpleGrainStatistics(); Task GetDetailedGrainReport(GrainId grainId); Task GetActivationCount(); Task MigrateRandomActivations(SiloAddress target, int count); Task SendControlCommandToProvider(string providerName, int command, object arg) where T : IControllable; Task> GetActiveGrains(GrainType grainType); } } ================================================ FILE: src/Orleans.Core/Threading/RecursiveInterlockedExchangeLock.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Threading; namespace Orleans.Threading { /// /// Lightweight recursive lock. /// internal sealed class RecursiveInterlockedExchangeLock { private const int UNLOCKED = -1; [ThreadStatic] private static int localThreadId; private int lockState = UNLOCKED; private readonly Func spinCondition; public RecursiveInterlockedExchangeLock() { this.spinCondition = this.TryGet; } private static int ThreadId => localThreadId != 0 ? localThreadId : localThreadId = Environment.CurrentManagedThreadId; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGet() { var previousValue = Interlocked.CompareExchange(ref this.lockState, ThreadId, UNLOCKED); return previousValue == UNLOCKED || previousValue == ThreadId; } /// /// Acquire the lock, blocking the thread if necessary. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Get() { if (this.TryGet()) { return; } SpinWait.SpinUntil(this.spinCondition); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryRelease() { var threadId = ThreadId; var previousValue = Interlocked.CompareExchange(ref this.lockState, UNLOCKED, threadId); return previousValue == UNLOCKED || previousValue == threadId; } public override string ToString() { var state = Volatile.Read(ref this.lockState); return state == UNLOCKED ? "Unlocked" : $"Locked by Thread {state}"; } } } ================================================ FILE: src/Orleans.Core/Timers/CoarseStopwatch.cs ================================================ using System; namespace Orleans.Runtime { /// /// Cheap, non-allocating stopwatch for timing durations with an accuracy within tens of milliseconds. /// internal struct CoarseStopwatch { private long _value; /// /// Starts a new instance. /// /// A new, running stopwatch. public static CoarseStopwatch StartNew() => new(GetTimestamp()); /// /// Starts a new instance with the specified duration already elapsed. /// /// A new, running stopwatch. public static CoarseStopwatch StartNew(long elapsedMs) => new(GetTimestamp() - elapsedMs); /// /// Creates a new instance with the specified timestamp. /// /// A new stopwatch. public static CoarseStopwatch FromTimestamp(long timestamp) => new(timestamp); private CoarseStopwatch(long timestamp) { _value = timestamp; } /// /// The number of ticks per second for this stopwatch. /// public const long Frequency = 1000; /// /// Returns true if this instance is running or false otherwise. /// public readonly bool IsRunning => _value > 0; /// /// Returns the elapsed time. /// public TimeSpan Elapsed => TimeSpan.FromMilliseconds(ElapsedMilliseconds); /// /// Returns a value indicating whether this instance has the default value. /// public readonly bool IsDefault => _value == 0; /// /// Returns the elapsed ticks. /// public readonly long ElapsedMilliseconds { get { // A positive timestamp value indicates the start time of a running stopwatch, // a negative value indicates the negative total duration of a stopped stopwatch. var timestamp = _value; long delta; if (IsRunning) { // The stopwatch is still running. var start = timestamp; var end = GetTimestamp(); delta = end - start; } else { // The stopwatch has been stopped. delta = -timestamp; } return delta; } } /// /// Gets the number of ticks in the timer mechanism. /// /// The number of ticks in the timer mechanism public static long GetTimestamp() => Environment.TickCount64; /// /// Returns a new, stopped with the provided start and end timestamps. /// /// The start timestamp. /// The end timestamp. /// A new, stopped with the provided start and end timestamps. public static CoarseStopwatch FromTimestamp(long start, long end) => new(-(end - start)); /// /// Gets the raw counter value for this instance. /// /// /// A positive timestamp value indicates the start time of a running stopwatch, /// a negative value indicates the negative total duration of a stopped stopwatch. /// /// The raw counter value. public readonly long GetRawTimestamp() => _value; /// /// Starts the stopwatch. /// public void Start() { var timestamp = _value; // If already started, do nothing. if (IsRunning) return; // Stopwatch is stopped, therefore value is zero or negative. // Add the negative value to the current timestamp to start the stopwatch again. var newValue = GetTimestamp() + timestamp; if (newValue == 0) newValue = 1; _value = newValue; } /// /// Restarts this stopwatch, beginning from zero time elapsed. /// public void Restart() => _value = GetTimestamp(); /// /// Resets this stopwatch into a stopped state with no elapsed duration. /// public void Reset() => _value = 0; /// /// Stops this stopwatch. /// public void Stop() { var timestamp = _value; // If already stopped, do nothing. if (!IsRunning) return; var end = GetTimestamp(); var delta = end - timestamp; _value = -delta; } public override readonly bool Equals(object obj) => obj is CoarseStopwatch stopwatch && _value == stopwatch._value; public readonly bool Equals(CoarseStopwatch other) => _value == other._value; public override readonly int GetHashCode() => HashCode.Combine(_value); public static bool operator ==(CoarseStopwatch left, CoarseStopwatch right) => left.Equals(right); public static bool operator !=(CoarseStopwatch left, CoarseStopwatch right) => !left.Equals(right); } } ================================================ FILE: src/Orleans.Core/Timers/NonCapturingTimer.cs ================================================ using System; using System.Threading; using Orleans.Runtime.Internal; namespace Orleans.Runtime { /// /// A convenience API for interacting with System.Threading.Timer in a way /// that doesn't capture the ExecutionContext. We should be using this (or equivalent) /// everywhere we use timers to avoid rooting any values stored in AsyncLocals. /// /// internal static class NonCapturingTimer { public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); } // Don't capture the current ExecutionContext and its AsyncLocals onto the timer using var suppressExecutionContext = new ExecutionContextSuppressor(); return new Timer(callback, state, dueTime, period); } } } ================================================ FILE: src/Orleans.Core/Timers/TimerManager.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Threading; namespace Orleans.Timers.Internal { /// /// Provides functionality for managing single-shot timers. /// public interface ITimerManager { /// /// Returns a task which will complete when the specified timespan elapses or the provided cancellation token is canceled. /// /// The time span. /// The cancellation token. /// if the timer ran to completion; otherwise . Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken = default); } internal class TimerManagerImpl : ITimerManager { public Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken = default) => TimerManager.Delay(timeSpan, cancellationToken); } internal static class TimerManager { public static Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken = default) => DelayUntil(DateTime.UtcNow + timeSpan, cancellationToken); public static Task DelayUntil(DateTime dueTime, CancellationToken cancellationToken = default) { var result = new DelayTimer(dueTime, cancellationToken); TimerManager.Register(result); return result.Completion; } private sealed class DelayTimer : ITimerCallback, ILinkedListElement { private readonly TaskCompletionSource completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public DelayTimer(DateTime dueTime, CancellationToken cancellationToken) { this.DueTime = dueTime; this.CancellationToken = cancellationToken; } public Task Completion => this.completion.Task; public DateTime DueTime { get; } public CancellationToken CancellationToken { get; } public void OnTimeout() => this.completion.TrySetResult(true); public void OnCanceled() => this.completion.TrySetResult(false); DelayTimer ILinkedListElement.Next { get; set; } } } /// /// Manages timers of a specified type, firing them after they expire. /// /// The timer type. internal static class TimerManager where T : class, ITimerCallback, ILinkedListElement { /// /// The maximum number of times a queue can be denied servicing before servicing is mandatory. /// private const int MAX_STARVATION = 2; /// /// The number of milliseconds between timer servicing ticks. /// private const int TIMER_TICK_MILLISECONDS = 50; /// /// Lock protecting . /// // ReSharper disable once StaticMemberInGenericType private static readonly object AllQueuesLock = new object(); #pragma warning disable IDE0052 // Remove unread private members private static readonly Timer QueueChecker; #pragma warning restore IDE0052 // Remove unread private members /// /// Collection of all thread-local timer queues. /// private static ThreadLocalQueue[] allQueues = new ThreadLocalQueue[16]; /// /// The queue for the current thread. /// [ThreadStatic] private static ThreadLocalQueue threadLocalQueue; static TimerManager() { var timerPeriod = TimeSpan.FromMilliseconds(TIMER_TICK_MILLISECONDS); QueueChecker = NonCapturingTimer.Create(_ => CheckQueues(), null, timerPeriod, timerPeriod); } /// /// Registers a timer. /// public static void Register(T timer) { ExpiredTimers expired = null; var queue = EnsureCurrentThreadHasQueue(); try { queue.Lock.Get(); queue.AddTail(timer); if (queue.StarvationCount >= MAX_STARVATION) { // If the queue is too starved, service it now. expired = new ExpiredTimers(); CheckQueueInLock(queue, DateTime.UtcNow, expired); Interlocked.Exchange(ref queue.StarvationCount, 0); } } finally { queue.Lock.TryRelease(); // Fire expired timers outside of lock. expired?.FireTimers(); } } private static void CheckQueues() { var expired = new ExpiredTimers(); var now = DateTime.UtcNow; try { foreach (var queue in allQueues) { if (queue == null) { continue; } if (!queue.Lock.TryGet()) { // Check for starvation. if (Interlocked.Increment(ref queue.StarvationCount) > MAX_STARVATION) { // If the queue starved, block until the lock can be acquired. queue.Lock.Get(); Interlocked.Exchange(ref queue.StarvationCount, 0); } else { // Move on to the next queue. continue; } } try { CheckQueueInLock(queue, now, expired); } finally { queue.Lock.TryRelease(); } } } finally { // Expire timers outside of the loop and outside of any lock. expired.FireTimers(); } } private static void CheckQueueInLock(ThreadLocalQueue queue, DateTime now, ExpiredTimers expired) { var previous = default(T); for (var current = queue.Head; current != null; current = current.Next) { if (current.CancellationToken.IsCancellationRequested || current.DueTime < now) { // Dequeue and add to expired list for later execution. queue.Remove(previous, current); expired.AddTail(current); } else { // If the current item wasn't removed, update previous. previous = current; } } } /// /// Returns the queue for the current thread, creating and registering one if it does not yet exist. /// /// The current thread's . [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ThreadLocalQueue EnsureCurrentThreadHasQueue() { return threadLocalQueue ?? (threadLocalQueue = InitializeThreadLocalQueue()); ThreadLocalQueue InitializeThreadLocalQueue() { var threadLocal = new ThreadLocalQueue(); while (true) { lock (AllQueuesLock) { var queues = Volatile.Read(ref allQueues); // Find a spot in the existing array to register this thread. for (var i = 0; i < queues.Length; i++) { if (Volatile.Read(ref queues[i]) == null) { Volatile.Write(ref queues[i], threadLocal); return threadLocal; } } // The existing array is full, so copy all values to a new, larger array and register this thread. var newQueues = new ThreadLocalQueue[queues.Length * 2]; Array.Copy(queues, newQueues, queues.Length); newQueues[queues.Length] = threadLocal; Volatile.Write(ref allQueues, newQueues); return threadLocal; } } } } /// /// Holds per-thread timer data. /// private sealed class ThreadLocalQueue : ILinkedList { public readonly RecursiveInterlockedExchangeLock Lock = new RecursiveInterlockedExchangeLock(); /// /// The number of times that this queue has been starved since it was last serviced. /// public int StarvationCount; public T Head { get; set; } public T Tail { get; set; } } /// /// Holds timers that have expired and should be fired. /// private sealed class ExpiredTimers : ILinkedList { public T Head { get; set; } public T Tail { get; set; } public void FireTimers() { var current = this.Head; while (current != null) { try { if (current.CancellationToken.IsCancellationRequested) { current.OnCanceled(); } else { current.OnTimeout(); } } catch { // Ignore any exceptions during firing. } current = current.Next; } } } } internal interface ITimerCallback { /// /// The UTC time when this timer is due. /// DateTime DueTime { get; } CancellationToken CancellationToken { get; } void OnTimeout(); void OnCanceled(); } /// /// Represents a linked list. /// /// The element type. internal interface ILinkedList where T : ILinkedListElement { /// /// Gets or sets the first element in the list. /// This value must never be accessed or modified by user code. /// T Head { get; set; } /// /// Gets or sets the last element in the list. /// This value must never be accessed or modified by user code. /// T Tail { get; set; } } /// /// Represents an element in a linked list. /// /// Self-type. The type implementing this interface. internal interface ILinkedListElement where TSelf : ILinkedListElement { /// /// The next element in the list. /// This value must never be accessed or modified by user code. /// TSelf Next { get; set; } } internal static class LinkedList { /// /// Appends an item to the tail of a linked list. /// /// The linked list. /// The element to append. public static void AddTail(this TList list, TElement element) where TList : class, ILinkedList where TElement : class, ILinkedListElement { // If this is the first element, update the head. if (list.Head is null) list.Head = element; // If this is not the first element, update the current tail. var prevTail = list.Tail; if (!(prevTail is null)) prevTail.Next = element; // Update the tail. list.Tail = element; } /// /// Removes an item from a linked list. /// /// The linked list. /// The element before . /// The element to remove. public static void Remove(this TList list, TElement previous, TElement current) where TList : class, ILinkedList where TElement : class, ILinkedListElement { var next = current.Next; // If not removing the first element, point the previous element at the next element. if (!(previous is null)) previous.Next = next; // If removing the first element, point the tail at the next element. if (ReferenceEquals(list.Head, current)) { list.Head = next ?? previous; } // If removing the last element, point the tail at the previous element. if (ReferenceEquals(list.Tail, current)) { list.Tail = previous; } } } } ================================================ FILE: src/Orleans.Core/Timers/ValueStopwatch.cs ================================================ using System; using System.Diagnostics; namespace Orleans.Runtime { /// /// Non-allocating stopwatch for timing durations. /// internal struct ValueStopwatch { private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; private long _value; /// /// Starts a new instance. /// /// A new, running stopwatch. public static ValueStopwatch StartNew() => new(GetTimestamp()); /// /// Starts a new instance with an initial elapsed duration. /// /// /// The initial elapsed duration. /// /// A new, running stopwatch. public static ValueStopwatch StartNew(TimeSpan elapsed) => new(GetTimestamp() - (long)(elapsed.TotalSeconds * Stopwatch.Frequency)); private ValueStopwatch(long timestamp) { this._value = timestamp; } /// /// Returns true if this instance is running or false otherwise. /// public readonly bool IsRunning => this._value > 0; /// /// Returns the elapsed time. /// public TimeSpan Elapsed => TimeSpan.FromTicks(this.ElapsedTicks); /// /// Returns the elapsed ticks. /// public readonly long ElapsedTicks { get { // A positive timestamp value indicates the start time of a running stopwatch, // a negative value indicates the negative total duration of a stopped stopwatch. var timestamp = this._value; long delta; if (this.IsRunning) { // The stopwatch is still running. var start = timestamp; var end = Stopwatch.GetTimestamp(); delta = end - start; } else { // The stopwatch has been stopped. delta = -timestamp; } return (long)(delta * TimestampToTicks); } } /// /// Gets the number of ticks in the timer mechanism. /// /// The number of ticks in the timer mechanism public static long GetTimestamp() => Stopwatch.GetTimestamp(); /// /// Returns a new, stopped with the provided start and end timestamps. /// /// The start timestamp. /// The end timestamp. /// A new, stopped with the provided start and end timestamps. public static ValueStopwatch FromTimestamp(long start, long end) => new ValueStopwatch(-(end - start)); /// /// Gets the raw counter value for this instance. /// /// /// A positive timestamp value indicates the start time of a running stopwatch, /// a negative value indicates the negative total duration of a stopped stopwatch. /// /// The raw counter value. public readonly long GetRawTimestamp() => this._value; /// /// Starts the stopwatch. /// public void Start() { var timestamp = this._value; // If already started, do nothing. if (this.IsRunning) return; // Stopwatch is stopped, therefore value is zero or negative. // Add the negative value to the current timestamp to start the stopwatch again. var newValue = GetTimestamp() + timestamp; if (newValue == 0) newValue = 1; this._value = newValue; } /// /// Restarts this stopwatch, beginning from zero time elapsed. /// public void Restart() => this._value = GetTimestamp(); /// /// Resets this stopwatch into a stopped state with no elapsed duration. /// public void Reset() => this._value = 0; /// /// Stops this stopwatch. /// public void Stop() { var timestamp = this._value; // If already stopped, do nothing. if (!this.IsRunning) return; var end = GetTimestamp(); var delta = end - timestamp; this._value = -delta; } } } ================================================ FILE: src/Orleans.Core/Utils/AsyncEnumerable.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Orleans.Internal; namespace Orleans.Runtime.Utilities { internal static class AsyncEnumerable { internal static readonly object InitialValue = new(); internal static readonly object DisposedValue = new(); } internal sealed class AsyncEnumerable : IAsyncEnumerable { #if NET9_0_OR_GREATER private readonly Lock _updateLock = new(); #else private readonly object _updateLock = new(); #endif private readonly Func _updateValidator; private readonly Action _onPublished; private Element _current; public AsyncEnumerable(T initialValue, Func updateValidator, Action onPublished) { _updateValidator = updateValidator; _current = new Element(initialValue); _onPublished = onPublished; onPublished(initialValue); } public bool TryPublish(T value) => TryPublish(new Element(value)) == PublishResult.Success; public void Publish(T value) { switch (TryPublish(new Element(value))) { case PublishResult.Success: return; case PublishResult.InvalidUpdate: ThrowInvalidUpdate(); break; case PublishResult.Disposed: ThrowDisposed(); break; } } public bool TryPublish(Func updateFunc, TState state) => TryPublishCore(updateFunc, state) == PublishResult.Success; public void Publish(Func updateFunc, TState state) { switch (TryPublishCore(updateFunc, state)) { case PublishResult.Success: return; case PublishResult.InvalidUpdate: ThrowInvalidUpdate(); break; case PublishResult.Disposed: ThrowDisposed(); break; } } private PublishResult TryPublish(Element newItem) { if (_current.IsDisposed) return PublishResult.Disposed; lock (_updateLock) { if (_current.IsDisposed) return PublishResult.Disposed; if (_current.IsValid && newItem.IsValid && !_updateValidator(_current.Value, newItem.Value)) { return PublishResult.InvalidUpdate; } var curr = _current; Interlocked.Exchange(ref _current, newItem); if (newItem.IsValid) _onPublished(newItem.Value); curr.SetNext(newItem); return PublishResult.Success; } } private PublishResult TryPublishCore(Func updateFunc, TState state) { if (_current.IsDisposed) return PublishResult.Disposed; lock (_updateLock) { if (_current.IsDisposed) return PublishResult.Disposed; var curr = _current; var newItem = new Element(updateFunc(curr.Value, state)); if (curr.IsValid && newItem.IsValid && !_updateValidator(curr.Value, newItem.Value)) { return PublishResult.InvalidUpdate; } Interlocked.Exchange(ref _current, newItem); if (newItem.IsValid) _onPublished(newItem.Value); curr.SetNext(newItem); return PublishResult.Success; } } public void Dispose() { if (_current.IsDisposed) return; lock (_updateLock) { if (_current.IsDisposed) return; TryPublish(Element.CreateDisposed()); } } [DoesNotReturn] private static void ThrowInvalidUpdate() => throw new ArgumentException("The value was not valid."); [DoesNotReturn] private static void ThrowDisposed() => throw new ObjectDisposedException("This instance has been disposed."); public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => new AsyncEnumerator(_current, cancellationToken); private enum PublishResult { Success, InvalidUpdate, Disposed } private sealed class AsyncEnumerator : IAsyncEnumerator { private readonly CancellationTokenSource _cts; private Element _current; public AsyncEnumerator(Element initial, CancellationToken cancellationToken) { _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); if (!initial.IsValid) { _current = initial; } else { var result = Element.CreateInitial(); result.SetNext(initial); _current = result; } } T IAsyncEnumerator.Current => _current.Value; async ValueTask IAsyncEnumerator.MoveNextAsync() { if (_current.IsDisposed) { return false; } _current = await _current.NextAsync().WaitAsync(_cts.Token); return _current.IsValid; } async ValueTask IAsyncDisposable.DisposeAsync() { await _cts.CancelAsync().SuppressThrowing(); _cts.Dispose(); } } private sealed class Element { private readonly TaskCompletionSource _next; private readonly object _value; public Element(T value) : this(value, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)) { } private Element(object value, TaskCompletionSource next) { _value = value; _next = next; } public static Element CreateInitial() => new( AsyncEnumerable.InitialValue, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)); public static Element CreateDisposed() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); tcs.SetException(new ObjectDisposedException("This instance has been disposed")); return new Element(AsyncEnumerable.DisposedValue, tcs); } public bool IsValid => !IsInitial && !IsDisposed; public T Value { get { if (IsInitial) ThrowInvalidInstance(); ObjectDisposedException.ThrowIf(IsDisposed, this); if (_value is T typedValue) return typedValue; return default; } } public bool IsInitial => ReferenceEquals(_value, AsyncEnumerable.InitialValue); public bool IsDisposed => ReferenceEquals(_value, AsyncEnumerable.DisposedValue); public Task NextAsync() => _next.Task; public void SetNext(Element next) => _next.SetResult(next); private static void ThrowInvalidInstance() => throw new InvalidOperationException("This instance does not have a value set."); } } } ================================================ FILE: src/Orleans.Core/Utils/ExecutionContextSuppressor.cs ================================================ using System.Threading; namespace Orleans.Runtime.Internal; /// /// Suppresses the flow of until it is disposed. /// /// /// Note that this is a ref-struct to avoid it being used in an async method. /// public ref struct ExecutionContextSuppressor { private readonly bool _restoreFlow; /// /// Initializes a new instance. /// public ExecutionContextSuppressor() { if (!ExecutionContext.IsFlowSuppressed()) { ExecutionContext.SuppressFlow(); _restoreFlow = true; } else { _restoreFlow = false; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public readonly void Dispose() { if (_restoreFlow) { ExecutionContext.RestoreFlow(); } } } ================================================ FILE: src/Orleans.Core/Utils/Factory.cs ================================================ namespace Orleans { /// /// Creates an instance of . /// /// /// The instance. public delegate TInstance Factory(); /// /// Creates an instance of . /// /// The instance type. /// The parameter type. /// The instance. public delegate TInstance Factory(TParam1 param1); /// /// Creates an instance of . /// /// The instance type. /// The first parameter type. /// The second parameter type. /// The instance. public delegate TInstance Factory(TParam1 param1, TParam2 param2); /// /// Creates an instance of . /// /// The instance type. /// The first parameter type. /// The second parameter type. /// The third parameter type. /// The instance. public delegate TInstance Factory(TParam1 param1, TParam2 param2, TParam3 param3); } ================================================ FILE: src/Orleans.Core/Utils/NamedOptionExtension.cs ================================================ using Microsoft.Extensions.Options; using System; using Microsoft.Extensions.DependencyInjection; namespace Orleans { /// /// Extensions for working with named option classes. /// public static class NamedOptionExtensions { /// /// Gets a named options instance. /// /// The type of the t option. /// The services. /// The name. /// TOption. public static TOption GetOptionsByName(this IServiceProvider services, string name) where TOption : class, new() { return services.GetRequiredService>().Get(name); } } } ================================================ FILE: src/Orleans.Core/Utils/ObserverManager.cs ================================================ #nullable enable using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using System.Collections; using System.Collections.Generic; using Orleans.Runtime; using System.Linq; namespace Orleans.Utilities; /// /// Maintains a collection of observers. /// /// /// The observer type. /// public class ObserverManager : ObserverManager { /// /// Initializes a new instance of the class. /// /// /// The expiration. /// /// The log. public ObserverManager(TimeSpan expiration, ILogger log) : base(expiration, log) { } } /// /// Maintains a collection of observers. /// /// /// The address type, used to identify observers. /// /// /// The observer type. /// public partial class ObserverManager : IEnumerable where TIdentity : notnull { /// /// The observers. /// private Dictionary _observers = []; /// /// The log. /// private readonly ILogger _log; // The number of concurrent readers. private int _numReaders; // The most recently captured read snapshot. // Each time a reader is added, we capture the current _observers reference here, signaling to writers // that _observers must be set to a copy before modifying. private Dictionary? _readSnapshot; /// /// Initializes a new instance of the class. /// /// /// The expiration. /// /// The log. public ObserverManager(TimeSpan expiration, ILogger log) { ArgumentNullException.ThrowIfNull(log); ExpirationDuration = expiration; _log = log; GetDateTime = () => DateTime.UtcNow; } /// /// Gets or sets the delegate used to get the date and time, for expiry. /// public Func GetDateTime { get; set; } /// /// Gets or sets the expiration time span, after which observers are lazily removed. /// public TimeSpan ExpirationDuration { get; set; } /// /// Gets the number of observers. /// public int Count => _observers.Count; /// /// Gets a copy of the observers. /// public IReadOnlyDictionary Observers => _observers.ToDictionary(_ => _.Key, _ => _.Value.Observer); /// /// Removes all observers. /// public void Clear() { var observers = GetWritableObservers(); observers.Clear(); } /// /// Ensures that the provided is subscribed, renewing its subscription. /// /// /// The observer's identity. /// /// /// The observer. /// /// A delegate callback throws an exception. public void Subscribe(TIdentity id, TObserver observer) { var observers = GetWritableObservers(); // Add or update the subscription. var now = GetDateTime(); if (observers.TryGetValue(id, out var entry)) { entry.LastSeen = now; entry.Observer = observer; LogDebugUpdatingEntry(id, observer, _observers.Count); } else { _observers[id] = new ObserverEntry { LastSeen = now, Observer = observer }; LogDebugAddingEntry(id, observer, _observers.Count); } } /// /// Ensures that the provided is unsubscribed. /// /// /// The observer. /// public void Unsubscribe(TIdentity id) { var observers = GetWritableObservers(); observers.Remove(id, out _); LogDebugRemovedEntry(id, observers.Count); } /// /// Notifies all observers. /// /// /// The notification delegate to call on each observer. /// /// /// The predicate used to select observers to notify. /// /// /// A representing the work performed. /// public async Task Notify(Func notification, Func? predicate = null) { var now = GetDateTime(); var defunct = default(List); using (var snapshot = CreateReadSnapshot()) { foreach (var observerEntryPair in snapshot.Observers) { if (observerEntryPair.Value.LastSeen + ExpirationDuration < now) { // Expired observers will be removed. defunct ??= []; defunct.Add(observerEntryPair.Key); continue; } // Skip observers which don't match the provided predicate. if (predicate is not null && !predicate(observerEntryPair.Value.Observer)) { continue; } try { await notification(observerEntryPair.Value.Observer); } catch (Exception) { // Failing observers are considered defunct and will be removed. defunct ??= []; defunct.Add(observerEntryPair.Key); } } } RemoveDefunct(defunct); } /// /// Notifies all observers which match the provided . /// /// /// The notification delegate to call on each observer. /// /// /// The predicate used to select observers to notify. /// public void Notify(Action notification, Func? predicate = null) { var now = GetDateTime(); var defunct = default(List); using (var snapshot = CreateReadSnapshot()) { foreach (var observerEntryPair in snapshot.Observers) { if (observerEntryPair.Value.LastSeen + ExpirationDuration < now) { // Expired observers will be removed. defunct ??= []; defunct.Add(observerEntryPair.Key); continue; } // Skip observers which don't match the provided predicate. if (predicate is not null && !predicate(observerEntryPair.Value.Observer)) { continue; } try { notification(observerEntryPair.Value.Observer); } catch (Exception) { // Failing observers are considered defunct and will be removed. defunct ??= []; defunct.Add(observerEntryPair.Key); } } } RemoveDefunct(defunct); } /// /// Removed all expired observers. /// public void ClearExpired() { var defunct = default(List); using (var snapshot = CreateReadSnapshot()) { var now = GetDateTime(); foreach (var observerEntryPair in snapshot.Observers) { if (observerEntryPair.Value.LastSeen + ExpirationDuration < now) { // Expired observers will be removed. defunct ??= []; defunct.Add(observerEntryPair.Key); } } } RemoveDefunct(defunct); } private void RemoveDefunct(List? defunct) { // Remove defunct observers. if (defunct is { Count: > 0 }) { var observers = GetWritableObservers(); LogDebugRemovingDefunctObservers(defunct.Count); foreach (var observerIdToRemove in defunct) { observers.Remove(observerIdToRemove, out _); } } } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() => new ObserverEnumerator(CreateReadSnapshot()); /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private ReadSnapshot CreateReadSnapshot() { ++_numReaders; var observers = _readSnapshot = _observers; return new(this, observers); } private Dictionary GetWritableObservers() { if (_numReaders > 0 && ReferenceEquals(_observers, _readSnapshot)) { _observers = new Dictionary(_observers); } return _observers; } /// /// An observer entry. /// private sealed class ObserverEntry { /// /// Gets or sets the observer. /// public required TObserver Observer { get; set; } /// /// Gets or sets the UTC last seen time. /// public DateTime LastSeen { get; set; } } private readonly struct ReadSnapshot( ObserverManager manager, IReadOnlyDictionary snapshot) : IDisposable { public IReadOnlyDictionary Observers { get; } = snapshot; public void Dispose() { if (--manager._numReaders == 0) { manager._readSnapshot = null; } } } private sealed class ObserverEnumerator(ReadSnapshot snapshot) : IEnumerator { private bool _isDisposed; private readonly IEnumerator> _enumerator = snapshot.Observers.GetEnumerator(); public TObserver Current => _enumerator.Current.Value.Observer; object? IEnumerator.Current => Current; public void Dispose() { if (!_isDisposed) { _isDisposed = true; _enumerator.Dispose(); snapshot.Dispose(); } } public bool MoveNext() => _enumerator.MoveNext(); public void Reset() => _enumerator.Reset(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Updating entry for {Id}/{Observer}. {Count} total observers." )] private partial void LogDebugUpdatingEntry(TIdentity id, TObserver observer, int count); [LoggerMessage( Level = LogLevel.Debug, Message = "Adding entry for {Id}/{Observer}. {Count} total observers after add." )] private partial void LogDebugAddingEntry(TIdentity id, TObserver observer, int count); [LoggerMessage( Level = LogLevel.Debug, Message = "Removed entry for {Id}. {Count} total observers after remove." )] private partial void LogDebugRemovedEntry(TIdentity id, int count); [LoggerMessage( Level = LogLevel.Debug, Message = "Removing {Count} defunct observers entries." )] private partial void LogDebugRemovingDefunctObservers(int count); } ================================================ FILE: src/Orleans.Core/Utils/RandomTimeSpan.cs ================================================ using System; namespace Orleans.Internal { /// /// Random TimeSpan generator /// internal static class RandomTimeSpan { public static TimeSpan Next(TimeSpan timeSpan) { if (timeSpan.Ticks <= 0) throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan, "TimeSpan must be positive."); return TimeSpan.FromTicks(Random.Shared.NextInt64(timeSpan.Ticks)); } public static TimeSpan Next(TimeSpan minValue, TimeSpan maxValue) { if (minValue.Ticks <= 0) throw new ArgumentOutOfRangeException(nameof(minValue), minValue, "MinValue must be positive."); if (minValue >= maxValue) throw new ArgumentOutOfRangeException(nameof(minValue), minValue, "MinValue must be less than maxValue."); return minValue + Next(maxValue - minValue); } } } ================================================ FILE: src/Orleans.Core/Utils/ReferenceEqualsComparer.cs ================================================ using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Orleans { internal class ReferenceEqualsComparer : IEqualityComparer { /// /// Gets an instance of this class. /// public static ReferenceEqualsComparer Default { get; } = new ReferenceEqualsComparer(); /// /// Defines object equality by reference equality (eq, in LISP). /// /// /// true if the specified objects are equal; otherwise, false. /// /// The first object to compare.The second object to compare. public new bool Equals(object x, object y) => object.ReferenceEquals(x, y); public int GetHashCode(object obj) => obj == null ? 0 : RuntimeHelpers.GetHashCode(obj); } internal class ReferenceEqualsComparer : IEqualityComparer where T : class { /// /// Gets an instance of this class. /// public static ReferenceEqualsComparer Default { get; } = new ReferenceEqualsComparer(); /// /// Defines object equality by reference equality (eq, in LISP). /// /// /// true if the specified objects are equal; otherwise, false. /// /// The first object to compare.The second object to compare. public bool Equals(T x, T y) => object.ReferenceEquals(x, y); public int GetHashCode(T obj) => obj == null ? 0 : RuntimeHelpers.GetHashCode(obj); } } ================================================ FILE: src/Orleans.Core/Utils/SetExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans { internal static class SetExtensions { /// /// Shortcut to create HashSet from IEnumerable that supports type inference /// (which the standard constructor does not) /// /// The element type public static HashSet ToSet(this IEnumerable values) { if (values == null) return null; return new HashSet(values); } /// /// ToString every element of an enumeration /// /// /// /// Can supply null to use Object.ToString() /// Before each element, or space if unspecified /// public static string ToStrings(this IEnumerable list, Func toString = null, string separator = " ") { if (list == null) return ""; toString = toString ?? (x => x); //Func toStringPrinter = (x => // { // object obj = toString(x); // if(obj != null) // return obj.ToString(); // else // return "null"; // }); //return Utils.IEnumerableToString(list, toStringPrinter, separator); //Do NOT use Aggregate for string concatenation. It is very inefficient, will reallocate and copy lots of intermediate strings. //toString = toString ?? (x => x); return list.Aggregate("", (s, x) => s + separator + toString(x)); } public static T GetValueOrAddNew(this Dictionary dictionary, TU key) where T : new() { T result; if (dictionary.TryGetValue(key, out result)) return result; result = new T(); dictionary[key] = result; return result; } } } ================================================ FILE: src/Orleans.Core/Utils/StandardExtensions.cs ================================================ using System; namespace Orleans.Internal { /// /// The Utils class contains a variety of utility methods for use in application and grain code. /// internal static class StandardExtensions { public static TimeSpan Max(TimeSpan first, TimeSpan second) => first >= second ? first : second; public static TimeSpan Min(TimeSpan first, TimeSpan second) => first < second ? first : second; } } ================================================ FILE: src/Orleans.Core/Utils/TypeConverterExtensions.cs ================================================ using System; using System.Buffers.Text; using System.Diagnostics.CodeAnalysis; using System.Text; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Utilities { /// /// Extensions for working with . /// internal static class TypeConverterExtensions { private const char GenericTypeIndicator = '`'; private const char StartArgument = '['; /// /// Returns true if the provided type string is a generic type. /// public static bool IsGenericType(IdSpan type) => type.AsSpan().IndexOf((byte)GenericTypeIndicator) >= 0; /// /// Returns the generic arity of the specified grain type. /// public static int GetGenericTypeArity(IdSpan type) { var typeSpan = type.AsSpan(); var startIndex = typeSpan.IndexOf((byte)GenericTypeIndicator) + 1; if (startIndex <= 0 || startIndex >= typeSpan.Length) { return 0; } int endIndex; for (endIndex = startIndex; endIndex < typeSpan.Length; endIndex++) { var c = typeSpan[endIndex]; if (c is < ((byte)'0') or > ((byte)'9')) { break; } } if (endIndex > startIndex && Utf8Parser.TryParse(typeSpan[startIndex..endIndex], out int arity, out _)) { return arity; } throw new InvalidOperationException($"Unable to parse arity from type \"{type}\""); } /// /// Returns true if the provided type string is a constructed generic type. /// public static bool IsConstructed(IdSpan type) => type.AsSpan().IndexOf((byte)StartArgument) > 0; /// /// Returns the deconstructed form of the provided generic type. /// public static IdSpan GetDeconstructed(IdSpan type) { var span = type.AsSpan(); var index = span.IndexOf((byte)StartArgument); return index <= 0 ? type : new IdSpan(span[..index].ToArray()); } /// /// Returns the constructed form of the provided generic type. /// public static IdSpan GetConstructed(this TypeConverter formatter, IdSpan unconstructed, params Type[] typeArguments) { var typeString = unconstructed.AsSpan(); var indicatorIndex = typeString.IndexOf((byte)GenericTypeIndicator); var arityString = typeString[(indicatorIndex + 1)..]; if (indicatorIndex < 0 || arityString.IndexOf((byte)StartArgument) >= 0) { throw new InvalidOperationException("Cannot construct an already-constructed type"); } if (!Utf8Parser.TryParse(arityString, out int arity, out var len) || len < arityString.Length || typeArguments.Length != arity) { throw new InvalidOperationException($"Insufficient number of type arguments, {typeArguments.Length}, provided while constructing type \"{unconstructed}\""); } var typeSpecs = new TypeSpec[typeArguments.Length]; for (var i = 0; i < typeArguments.Length; i++) { typeSpecs[i] = RuntimeTypeNameParser.Parse(formatter.Format(typeArguments[i])); } var constructed = new ConstructedGenericTypeSpec(new NamedTypeSpec(null, unconstructed.ToString(), typeArguments.Length), typeArguments.Length, typeSpecs).Format(); return IdSpan.Create(constructed); } /// /// Returns the constructed form of the provided generic grain type using the type arguments from the provided constructed interface type. /// public static GrainType GetConstructed(this GenericGrainType genericGrainType, GenericGrainInterfaceType genericGrainInterfaceType) { if (genericGrainType.Arity != genericGrainInterfaceType.Arity) { ThrowGenericArityMismatch(genericGrainType, genericGrainInterfaceType); } var grainType = genericGrainType.GrainType; var typeArguments = genericGrainInterfaceType.Value; var args = typeArguments.Value.AsSpan(); var index = args.IndexOf((byte)StartArgument); if (index <= 0) return grainType; // if no type arguments are provided, then the current logic expects the unconstructed form (but the grain call is going to fail later anyway...) args = args[index..]; var type = grainType.Value.AsSpan(); var buf = new byte[type.Length + args.Length]; type.CopyTo(buf); args.CopyTo(buf.AsSpan(type.Length)); return new GrainType(buf); } /// /// Returns the type arguments for the provided constructed generic type string. /// public static Type[] GetArguments(this TypeConverter formatter, IdSpan constructed) { var str = constructed.AsSpan(); var index = str.IndexOf((byte)StartArgument); if (index <= 0) { return Array.Empty(); } var safeString = "safer" + Encoding.UTF8.GetString(str[str.IndexOf((byte)GenericTypeIndicator)..]); var parsed = RuntimeTypeNameParser.Parse(safeString); if (!(parsed is ConstructedGenericTypeSpec spec)) { throw new InvalidOperationException($"Unable to correctly parse grain type {constructed}"); } var result = new Type[spec.Arguments.Length]; for (var i = 0; i < result.Length; i++) { var arg = spec.Arguments[i]; var formattedArg = arg.Format(); result[i] = formatter.Parse(formattedArg); if (result[i] is null) { throw new InvalidOperationException($"Unable to parse argument \"{formattedArg}\" as a type for grain type \"{constructed}\""); } } return result; } [DoesNotReturn] private static void ThrowGenericArityMismatch(GenericGrainType genericGrainType, GenericGrainInterfaceType genericInterfaceType) => throw new ArgumentException($"Cannot construct generic grain \"{genericGrainType.GrainType}\" using arguments from generic interface \"{genericInterfaceType}\" because the generic arities are not equal: {genericGrainType.Arity} is not equal to {genericInterfaceType.Arity}."); } } ================================================ FILE: src/Orleans.Core/build/Microsoft.Orleans.Core.targets ================================================ ================================================ FILE: src/Orleans.Core/buildMultiTargeting/Microsoft.Orleans.Core.targets ================================================ ================================================ FILE: src/Orleans.Core/buildTransitive/Microsoft.Orleans.Core.targets ================================================ ================================================ FILE: src/Orleans.Core.Abstractions/Cancellation/GrainCancellationToken.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans { /// /// An analogue to which can be sent between grains. /// [Immutable] public sealed class GrainCancellationToken : IDisposable { /// /// The underlying cancellation token source. /// [NonSerialized] private readonly CancellationTokenSource _cancellationTokenSource; /// /// References to remote grains to which this token was passed. /// [NonSerialized] private readonly ConcurrentDictionary _targetGrainReferences; /// /// The runtime used to manage grain cancellation tokens. /// [NonSerialized] private IGrainCancellationTokenRuntime? _cancellationTokenRuntime; /// /// Initializes the . /// /// /// The token id. /// internal GrainCancellationToken(Guid id) { _cancellationTokenSource = new CancellationTokenSource(); Id = id; _targetGrainReferences = new ConcurrentDictionary(); } /// /// Initializes the . /// /// /// The token id. /// /// /// Whether or not the instance is already canceled. /// /// /// The runtime. /// internal GrainCancellationToken(Guid id, bool canceled, IGrainCancellationTokenRuntime? runtime = null) : this(id) { _cancellationTokenRuntime = runtime; if (canceled) { // we Cancel _cancellationTokenSource just "to store" the cancelled state. _cancellationTokenSource.Cancel(); } } /// /// Gets the unique id of the token /// internal Guid Id { get; private set; } /// /// Gets the underlying cancellation token. /// public CancellationToken CancellationToken => _cancellationTokenSource.Token; /// /// Gets a value indicating if cancellation is requested. /// internal bool IsCancellationRequested => _cancellationTokenSource.IsCancellationRequested; /// /// Cancels the cancellation token. /// /// /// A representing the operation. /// internal Task Cancel() { if (_cancellationTokenRuntime == null) { // Wrap in task try { _cancellationTokenSource.Cancel(); return Task.CompletedTask; } catch (Exception exception) { var completion = new TaskCompletionSource(); completion.TrySetException(exception); return completion.Task; } } return _cancellationTokenRuntime.Cancel(Id, _cancellationTokenSource, _targetGrainReferences); } /// /// Subscribes the provided grain reference to cancellation notifications. /// /// The grain cancellation runtime. /// The grain reference to add. internal void AddGrainReference(IGrainCancellationTokenRuntime runtime, GrainReference grainReference) { if (_cancellationTokenRuntime == null) _cancellationTokenRuntime = runtime; _targetGrainReferences.TryAdd(grainReference.GrainId, grainReference); } /// public void Dispose() { _cancellationTokenSource.Dispose(); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Cancellation/GrainCancellationTokenSource.cs ================================================ using System; using System.Threading.Tasks; using System.Threading; namespace Orleans { /// /// An analogue to which can be sent between grains. /// public sealed class GrainCancellationTokenSource : IDisposable { /// /// The underlying grain cancellation token. /// private readonly GrainCancellationToken _grainCancellationToken; /// /// Initializes the . /// public GrainCancellationTokenSource() { _grainCancellationToken = new GrainCancellationToken(Guid.NewGuid()); } /// /// Gets the CancellationToken /// associated with this . /// /// The CancellationToken /// associated with this . public GrainCancellationToken Token { get { return _grainCancellationToken; } } /// /// Gets a value indicating whether cancellation has been requested. /// /// /// /// This property indicates whether cancellation has been requested for this token source, such as due to a call to its method. /// /// /// If this property returns true, it only guarantees that cancellation has been requested. It does not guarantee that every handler registered with the corresponding token has finished executing, nor that /// cancellation requests have finished propagating to all registered handlers and remote targets. Additional synchronization may be required, particularly in situations where related objects are being canceled /// concurrently. /// /// public bool IsCancellationRequested { get { return _grainCancellationToken.IsCancellationRequested; } } /// /// Communicates a request for cancellation. /// /// /// /// The associated will be notified of the cancellation and will transition to a state where /// returns true. Any callbacks or cancelable operations registered with the /// will be executed. /// /// /// Cancelable operations and callbacks registered with the token should not exceptions. However, this overload of will aggregate any exceptions thrown into a /// , such that one callback throwing an exception will not prevent other registered callbacks from being executed. /// /// The that was captured when each callback was registered will be reestablished when the callback is invoked. /// /// An aggregate exception containing all the exceptions thrown by the registered callbacks on the associated . /// This has been disposed. public Task Cancel() { return _grainCancellationToken.Cancel(); } /// /// Releases the resources used by this . /// /// /// This method is not thread-safe for any other concurrent calls. /// public void Dispose() { _grainCancellationToken.Dispose(); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Cancellation/ICancellationSourcesExtension.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Concurrency; namespace Orleans.Runtime { /// /// Extension used by the grain cancellation runtime to propagate cancellation notifications to grains. /// internal interface ICancellationSourcesExtension : IGrainExtension { /// /// Indicates that a cancellation token has been canceled. /// /// /// The token id. /// /// /// A representing the operation. /// [AlwaysInterleave] Task CancelRemoteToken(Guid tokenId); } } ================================================ FILE: src/Orleans.Core.Abstractions/CodeGeneration/IOnDeserialized.cs ================================================ using System; namespace Orleans.Serialization { /// /// Indicates that a class is to be notified when it has been deserialized. /// public interface IOnDeserialized { /// /// Notifies this instance that it has been fully deserialized. /// /// The serializer context. void OnDeserialized(DeserializationContext context); } public abstract class DeserializationContext { public abstract IServiceProvider ServiceProvider { get; } public abstract object RuntimeClient { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/CodeGeneration/InvokeMethodOptions.cs ================================================ using System; namespace Orleans.CodeGeneration { /// /// Invoke options for an InvokeMethodRequest /// /// /// These flag values are used in Orleans generated invoker code, and should not be altered. [Flags] [GenerateSerializer] public enum InvokeMethodOptions { /// No options defined. None = 0, /// Invocation is one-way with no feedback on whether the call succeeds or fails. OneWay = 1 << 0, /// Invocation is read-only and can interleave with other read-only invocations. ReadOnly = 1 << 1, /// The invocation can interleave with any other request type, including write requests. AlwaysInterleave = 1 << 2, /// Invocation does not care about ordering and can consequently be optimized. Unordered = 1 << 3, } } ================================================ FILE: src/Orleans.Core.Abstractions/CodeGeneration/VersionAttribute.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using Orleans.Metadata; namespace Orleans.CodeGeneration { /// /// The VersionAttribute allows to specify the version number of the interface /// [AttributeUsage(AttributeTargets.Interface)] public sealed class VersionAttribute : Attribute, IGrainInterfacePropertiesProviderAttribute { /// /// Initializes a new instance of the class. /// /// /// The version. /// public VersionAttribute(ushort version) { Version = version; } /// /// Gets the version. /// public ushort Version { get; private set; } /// void IGrainInterfacePropertiesProviderAttribute.Populate(IServiceProvider services, Type type, Dictionary properties) { properties[WellKnownGrainInterfaceProperties.Version] = this.Version.ToString(CultureInfo.InvariantCulture); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Concurrency/GrainAttributeConcurrency.cs ================================================ using Orleans.CodeGeneration; using Orleans.Metadata; using Orleans.Placement; using Orleans.Runtime; using System; using System.Collections.Generic; using System.Globalization; namespace Orleans.Concurrency { /// /// The ReadOnly attribute is used to mark methods that do not modify the state of a grain. /// /// Marking methods as ReadOnly allows the run-time system to perform a number of optimizations /// that may significantly improve the performance of your application. /// /// [InvokableCustomInitializer(nameof(IRequest.AddInvokeMethodOptions), InvokeMethodOptions.ReadOnly)] [AttributeUsage(AttributeTargets.Method)] public sealed class ReadOnlyAttribute : Attribute { } /// /// The Reentrant attribute is used to mark grain implementation classes that allow request interleaving within a task. /// /// This is an advanced feature and should not be used unless the implications are fully understood. /// That said, allowing request interleaving allows the run-time system to perform a number of optimizations /// that may significantly improve the performance of your application. /// /// [AttributeUsage(AttributeTargets.Class)] public sealed class ReentrantAttribute : Attribute, IGrainPropertiesProviderAttribute { /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.Reentrant] = "true"; } } /// /// The Unordered attribute is used to mark grain interface in which the delivery order of /// messages is not significant. /// /// /// This attribute has no effect and it may be removed in a future release. /// [AttributeUsage(AttributeTargets.Interface)] [Obsolete("Message ordering is not guaranteed regardless of whether this attribute is used. This attribute has no effect.")] public sealed class UnorderedAttribute : Attribute { } /// /// The StatelessWorker attribute is used to mark grain class in which there is no expectation /// of preservation of grain state between requests and where multiple activations of the same grain are allowed to be created by the runtime. /// [AttributeUsage(AttributeTargets.Class)] public sealed class StatelessWorkerAttribute : PlacementAttribute, IGrainPropertiesProviderAttribute { /// /// Initializes a new instance of the class. /// /// The maximum local workers. public StatelessWorkerAttribute(int maxLocalWorkers) : base(new StatelessWorkerPlacement(maxLocalWorkers)) { } /// /// Initializes a new instance of the class. /// /// The maximum local workers. public StatelessWorkerAttribute(int maxLocalWorkers, bool removeIdleWorkers) : base(new StatelessWorkerPlacement(maxLocalWorkers, removeIdleWorkers)) { } /// /// Initializes a new instance of the class. /// public StatelessWorkerAttribute() : base(new StatelessWorkerPlacement()) { } /// public override void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { base.Populate(services, grainClass, grainType, properties); properties[WellKnownGrainTypeProperties.Unordered] = "true"; } } /// /// The AlwaysInterleaveAttribute attribute is used to mark methods that can interleave with any method, including write (non ReadOnly) requests. /// /// /// Note that this attribute is applied to method declaration in the grain interface, /// and not to the method in the implementation class itself. /// [InvokableCustomInitializer(nameof(IRequest.AddInvokeMethodOptions), InvokeMethodOptions.AlwaysInterleave)] [AttributeUsage(AttributeTargets.Method)] public sealed class AlwaysInterleaveAttribute : Attribute { } /// /// The MayInterleaveAttribute attribute is used to mark classes /// that want to control request interleaving via supplied method callback. /// /// /// The callback method name should point to a public static function declared on the same class /// and having the following signature: public static bool MayInterleave(IInvokable req) /// [AttributeUsage(AttributeTargets.Class)] public sealed class MayInterleaveAttribute : Attribute, IGrainPropertiesProviderAttribute { /// /// Initializes a new instance of the class. /// /// /// The callback method name. This should resolve to a method with the /// following signature: public static bool NameOfMethod(IInvokable req) /// public MayInterleaveAttribute(string callbackMethodName) { this.CallbackMethodName = callbackMethodName; } /// /// Gets the name of the callback method /// internal string CallbackMethodName { get; private set; } /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.MayInterleavePredicate] = this.CallbackMethodName; } } /// /// Indicates that a method on a grain interface is one-way and that no response message will be sent to the caller. /// [InvokableCustomInitializer(nameof(IRequest.AddInvokeMethodOptions), InvokeMethodOptions.OneWay)] [AttributeUsage(AttributeTargets.Method)] public sealed class OneWayAttribute : Attribute { } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/DeactivationReason.cs ================================================ using System; namespace Orleans { /// /// Represents a reason for initiating grain deactivation. /// public readonly struct DeactivationReason { /// /// Initializes a new instance of the struct. /// /// /// The code identifying the deactivation reason. /// /// /// A descriptive reason for the deactivation. /// public DeactivationReason(DeactivationReasonCode code, string text) { ReasonCode = code; Description = text; Exception = null; } /// /// Initializes a new instance of the struct. /// /// /// The code identifying the deactivation reason. /// /// /// The exception which resulted in deactivation. /// /// /// A descriptive reason for the deactivation. /// public DeactivationReason(DeactivationReasonCode code, Exception? exception, string text) { ReasonCode = code; Description = text; Exception = exception; } /// /// Gets the descriptive reason for the deactivation. /// public string Description { get; } /// /// Gets the reason for deactivation. /// public DeactivationReasonCode ReasonCode { get; } /// /// Gets the exception which resulted in deactivation. /// public Exception? Exception { get; } /// public override string ToString() { if (Exception is not null) { return $"{ReasonCode}: {Description}. Exception: {Exception}"; } return $"{ReasonCode}: {Description}"; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/Grain.cs ================================================ #nullable enable using System; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Core; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans; /// /// The abstract base class for all grain classes. /// public abstract partial class Grain : IGrainBase, IAddressable { // Do not use this directly because we currently don't provide a way to inject it; // any interaction with it will result in non unit-testable code. Any behavior that can be accessed // from within client code (including subclasses of this class), should be exposed through IGrainRuntime. // The better solution is to refactor this interface and make it injectable through the constructor. [AllowNull] public IGrainContext GrainContext { get; private set; } public GrainReference GrainReference { get { return GrainContext.GrainReference; } } internal IGrainRuntime Runtime { get; } /// /// Gets an object which can be used to access other grains. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. /// protected IGrainFactory GrainFactory => Runtime.GrainFactory; /// /// Gets the IServiceProvider managed by the runtime. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. /// // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? Runtime?.ServiceProvider!; internal GrainId GrainId => GrainContext.GrainId; /// /// This constructor should never be invoked. We expose it so that client code (subclasses of Grain) do not have to add a constructor. /// Client code should use the GrainFactory property to get a reference to a Grain. /// // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. protected Grain() : this(RuntimeContext.Current!, grainRuntime: null) {} /// /// Grain implementers do NOT have to expose this constructor but can choose to do so. /// This constructor is particularly useful for unit testing where test code can create a Grain and replace /// the IGrainIdentity and IGrainRuntime with test doubles (mocks/stubs). /// protected Grain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) { GrainContext = grainContext; // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. Runtime = grainRuntime ?? grainContext?.ActivationServices.GetService()!; } /// /// String representation of grain's SiloIdentity including type and primary key. /// public string IdentityString => GrainId.ToString(); /// /// A unique identifier for the current silo. /// There is no semantic content to this string, but it may be useful for logging. /// public string RuntimeIdentity => Runtime?.SiloIdentity ?? string.Empty; /// /// Registers a timer to send periodic callbacks to this grain. /// /// /// /// This timer will not prevent the current grain from being deactivated. /// If the grain is deactivated, then the timer will be discarded. /// /// /// Until the Task returned from the callback is resolved, /// the next timer tick will not be scheduled. /// That is to say, timer callbacks never interleave their turns. /// /// /// The timer may be stopped at any time by calling the Dispose method /// on the timer handle returned from this call. /// /// /// Any exceptions thrown by or faulted Task's returned from the callback /// will be logged, but will not prevent the next timer tick from being queued. /// /// /// Callback function to be invoked when timer ticks. /// State object that will be passed as argument when calling the . /// Due time for first timer tick. /// Period of subsequent timer ticks. /// Handle for this timer. [Obsolete("Use 'this.RegisterGrainTimer(callback, state, new() { DueTime = dueTime, Period = period, Interleave = true })' instead.")] protected IDisposable RegisterTimer(Func callback, object? state, TimeSpan dueTime, TimeSpan period) { ArgumentNullException.ThrowIfNull(callback); EnsureRuntime(); // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. return Runtime.TimerRegistry.RegisterTimer(GrainContext ?? RuntimeContext.Current!, callback, state, dueTime, period); } /// /// Deactivate this activation of the grain after the current grain method call is completed. /// This call will mark this activation of the current grain to be deactivated and removed at the end of the current method. /// The next call to this grain will result in a different activation to be used, which typical means a new activation will be created automatically by the runtime. /// protected void DeactivateOnIdle() { EnsureRuntime(); // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. Runtime.DeactivateOnIdle(GrainContext ?? RuntimeContext.Current!); } /// /// Starts an attempt to migrating this instance to another location. /// Migration captures the current , making it available to the activation's placement director so that it can consider it when selecting a new location. /// Migration will occur asynchronously, when no requests are executing, and will not occur if the activation's placement director does not select an alternative location. /// protected void MigrateOnIdle() { EnsureRuntime(); ((IGrainBase)this).MigrateOnIdle(); } /// /// Delay Deactivation of this activation at least for the specified time duration. /// A positive timeSpan value means “prevent GC of this activation for that time span”. /// A negative timeSpan value means “cancel the previous setting of the DelayDeactivation call and make this activation behave based on the regular Activation Garbage Collection settings”. /// DeactivateOnIdle method would undo / override any current “keep alive” setting, /// making this grain immediately available for deactivation. /// protected void DelayDeactivation(TimeSpan timeSpan) { EnsureRuntime(); // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. Runtime.DelayDeactivation(GrainContext ?? RuntimeContext.Current!, timeSpan); } /// /// This method is called at the end of the process of activating a grain. /// It is called before any messages have been dispatched to the grain. /// For grains with declared persistent state, this method is called after the State property has been populated. /// /// A cancellation token which signals when activation is being canceled. public virtual Task OnActivateAsync(CancellationToken cancellationToken) => Task.CompletedTask; /// /// This method is called at the beginning of the process of deactivating a grain. /// /// The reason for deactivation. Informational only. /// A cancellation token which signals when deactivation should complete promptly. public virtual Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) => Task.CompletedTask; internal void EnsureRuntime() { if (Runtime == null) { throw new InvalidOperationException("Grain was created outside of the Orleans creation process and no runtime was specified."); } } } /// /// Base class for a Grain with declared persistent state. /// /// The class of the persistent state object public class Grain : Grain { /// /// The underlying state storage. /// [AllowNull] // This is set by the runtime during activation, so it can be null in the constructor. private IStorage _storage; /// /// Initializes a new instance of the class. /// /// /// This constructor should never be invoked. We expose it so that client code (subclasses of this class) do not have to add a constructor. /// Client code should use the GrainFactory to get a reference to a Grain. /// protected Grain() { var observer = new LifecycleObserver(this); // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. var lifecycle = RuntimeContext.Current!.ObservableLifecycle; lifecycle.AddMigrationParticipant(observer); lifecycle.Subscribe(RuntimeTypeNameFormatter.Format(GetType()), GrainLifecycleStage.SetupState, observer); } /// /// Initializes a new instance of the class. /// /// /// The storage implementation. /// /// /// Grain implementers do NOT have to expose this constructor but can choose to do so. /// This constructor is particularly useful for unit testing where test code can create a Grain and replace /// the IGrainIdentity, IGrainRuntime and State with test doubles (mocks/stubs). /// protected Grain(IStorage storage) { _storage = storage; } /// /// Gets or sets the grain state. /// protected TGrainState State { get => _storage.State; set => _storage.State = value; } /// /// Clears the current grain state data from backing store. /// /// /// A representing the operation. /// protected virtual Task ClearStateAsync() => _storage.ClearStateAsync(); /// /// Write the current grain state data into the backing store. /// /// /// A representing the operation. /// protected virtual Task WriteStateAsync() => _storage.WriteStateAsync(); /// /// Reads grain state from backing store, updating . /// /// /// Any previous contents of the grain state data will be overwritten. /// /// /// A representing the operation. /// protected virtual Task ReadStateAsync() => _storage.ReadStateAsync(); private class LifecycleObserver : ILifecycleObserver, IGrainMigrationParticipant { private const string StorageMigratedKey = "grain-state-migrated"; private readonly Grain _grain; private bool _isInitialized; public LifecycleObserver(Grain grain) => _grain = grain; private void SetupStorage() => _grain._storage ??= _grain.Runtime.GetStorage(_grain.GrainContext); public void OnDehydrate(IDehydrationContext dehydrationContext) { var storage = _grain._storage; if (storage is IGrainMigrationParticipant migrationParticipant) { dehydrationContext.TryAddValue(StorageMigratedKey, true); migrationParticipant.OnDehydrate(dehydrationContext); } } public void OnRehydrate(IRehydrationContext rehydrationContext) { SetupStorage(); if (_grain._storage is IGrainMigrationParticipant migrationParticipant) { _isInitialized = rehydrationContext.TryGetValue(StorageMigratedKey, out bool isMigrated) && isMigrated; migrationParticipant.OnRehydrate(rehydrationContext); } } public Task OnStart(CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; } // Avoid reading the state if it is already present because of rehydration if (_isInitialized || _grain._storage?.Etag is not null) { return Task.CompletedTask; } SetupStorage(); return _grain.ReadStateAsync(); } public Task OnStop(CancellationToken cancellationToken = default) => Task.CompletedTask; } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/GrainExtensions.cs ================================================ using System; using Orleans.Runtime; using System.Diagnostics.CodeAnalysis; namespace Orleans { /// /// Extension methods for grains. /// public static class GrainExtensions { private const string WRONG_GRAIN_ERROR_MSG = "Passing a half baked grain as an argument. It is possible that you instantiated a grain class explicitly, as a regular object and not via Orleans runtime or via proper test mocking"; /// /// Returns a reference to the provided grain. /// /// The grain to create a reference for. /// A reference to the provided grain. internal static GrainReference AsReference(this IAddressable grain) { if (grain is null) { ThrowGrainNull(); } // When called against an instance of a grain reference class, do nothing var reference = grain as GrainReference; if (reference != null) return reference; var context = grain switch { Grain grainBase => grainBase.GrainContext, IGrainBase activation => activation.GrainContext, ISystemTargetBase systemTarget => systemTarget, _ => throw new ArgumentException(GetWrongGrainTypeErrorMessage(grain), nameof(grain)) }; if (context?.GrainReference is not { } grainRef) { throw new ArgumentException(WRONG_GRAIN_ERROR_MSG, nameof(grain)); } return grainRef; } /// /// Returns a typed reference to the provided grain. /// /// The type of the grain interface. /// The grain to convert. /// /// If the provided value is a grain instance, this will create a reference which implements the provided interface. /// If the provided value is already grain reference, this will create a new reference which implements the provided interface. /// /// A strongly typed reference to the provided grain which implements . public static TGrainInterface AsReference(this IAddressable grain) { if (grain is null) { ThrowGrainNull(); } var grainReference = grain.AsReference(); return (TGrainInterface)grainReference.Runtime.Cast(grain, typeof(TGrainInterface)); } /// /// Returns a typed reference to the provided grain. /// /// The type of the grain interface. /// The grain to convert. /// /// This method is equivalent to . /// If the provided value is a grain instance, this will create a reference which implements the provided interface. /// If the provided value is already grain reference, this will create a new reference which implements the provided interface. /// /// A strongly typed reference to the provided grain which implements . public static TGrainInterface Cast(this IAddressable grain) => grain.AsReference(); /// /// Returns a typed reference to the provided grain. /// /// The grain to convert. /// The type of the grain interface. /// /// If the provided value is a grain instance, this will create a reference which implements the provided interface. /// If the provided value is already grain reference, this will create a new reference which implements the provided interface. /// /// A strongly typed reference to the provided grain which implements . public static object AsReference(this IAddressable grain, Type interfaceType) => grain.AsReference().Runtime.Cast(grain, interfaceType); /// /// Returns a typed reference to the provided grain. /// /// The grain to convert. /// The type of the grain interface. /// /// This method is equivalent to . /// If the provided value is a grain instance, this will create a reference which implements the provided interface. /// If the provided value is already grain reference, this will create a new reference which implements the provided interface. /// /// A strongly typed reference to the provided grain which implements . public static object Cast(this IAddressable grain, Type interfaceType) => grain.AsReference().Runtime.Cast(grain, interfaceType); /// /// Returns the grain id corresponding to the provided grain. /// /// The grain /// The grain id corresponding to the provided grain. /// The provided value has the wrong type or has no id. public static GrainId GetGrainId(this IAddressable grain) { var grainId = grain switch { Grain grainBase => grainBase.GrainId, GrainReference grainReference => grainReference.GrainId, IGrainBase grainActivation => grainActivation.GrainContext.GrainId, ISystemTargetBase systemTarget => systemTarget.GrainId, _ => throw new ArgumentException(GetWrongGrainTypeErrorMessage(grain), nameof(grain)) }; if (grainId.IsDefault) { throw new ArgumentException(WRONG_GRAIN_ERROR_MSG, nameof(grain)); } return grainId; } /// /// Gets the exception message which is thrown when a grain argument has a non-supported implementation type. /// /// The argument. /// The exception message which is thrown when a grain argument has a non-supported implementation type. private static string GetWrongGrainTypeErrorMessage(IAddressable grain) => $"{nameof(GetGrainId)} has been called on an unexpected type: {grain.GetType().FullName}." + $" If the parameter is a grain implementation, you can derive from {nameof(Grain)} or implement" + $" {nameof(IGrainBase)} in order to support this method. Alternatively, inject {nameof(IGrainContext)} and" + $" access the {nameof(IGrainContext.GrainId)} or {nameof(IGrainContext.GrainReference)} property."; /// /// Returns whether part of the primary key is of type . /// /// The target grain. /// The provided grain does not have a -based key. public static bool IsPrimaryKeyBasedOnLong(this IAddressable grain) { var grainId = GetGrainId(grain); if (grainId.TryGetIntegerKey(out _)) { return true; } if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) { return legacyId.IsLongKey; } return false; } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// The output parameter to return the extended key part of the grain primary key, if extended primary key was provided for that grain. /// A representing the primary key for this grain. /// The provided grain does not have a -based key. public static long GetPrimaryKeyLong(this IAddressable grain, out string? keyExt) { var grainId = GetGrainId(grain); if (grainId.TryGetIntegerKey(out var primaryKey, out keyExt)) { return primaryKey; } if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) { return legacyId.GetPrimaryKeyLong(out keyExt); } throw new InvalidOperationException($"Unable to extract integer key from grain id {grainId}"); } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// A representing the primary key for this grain. /// The provided grain does not have a -based key. public static long GetPrimaryKeyLong(this IAddressable grain) { var grainId = GetGrainId(grain); if (grainId.TryGetIntegerKey(out var primaryKey)) { return primaryKey; } if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) { return legacyId.GetPrimaryKeyLong(); } throw new InvalidOperationException($"Unable to extract integer key from grain id {grainId}"); } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// The output parameter to return the extended key part of the grain primary key, if extended primary key was provided for that grain. /// A representing the primary key for this grain. /// The provided grain does not have a -based key. public static Guid GetPrimaryKey(this IAddressable grain, out string? keyExt) { var grainId = GetGrainId(grain); if (grainId.TryGetGuidKey(out var guid, out keyExt)) { return guid; } if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) { return legacyId.GetPrimaryKey(out keyExt); } if (grainId.TryGetIntegerKey(out var integerKey, out keyExt)) { var N1 = integerKey; return new Guid(0, 0, 0, (byte)N1, (byte)(N1 >> 8), (byte)(N1 >> 16), (byte)(N1 >> 24), (byte)(N1 >> 32), (byte)(N1 >> 40), (byte)(N1 >> 48), (byte)(N1 >> 56)); } throw new InvalidOperationException($"Unable to extract GUID key from grain id {grainId}"); } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// A representing the primary key for this grain. /// The provided grain does not have a -based key. public static Guid GetPrimaryKey(this IAddressable grain) { var grainId = GetGrainId(grain); if (grainId.TryGetGuidKey(out var guid)) return guid; if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) return legacyId.GetPrimaryKey(); if (grainId.TryGetIntegerKey(out var integerKey)) { var N1 = integerKey; return new Guid(0, 0, 0, (byte)N1, (byte)(N1 >> 8), (byte)(N1 >> 16), (byte)(N1 >> 24), (byte)(N1 >> 32), (byte)(N1 >> 40), (byte)(N1 >> 48), (byte)(N1 >> 56)); } throw new InvalidOperationException($"Unable to extract GUID key from grain id {grainId}"); } /// /// Returns the primary key of the grain. /// /// The grain to find the primary key for. /// A representing the primary key for this grain. public static string GetPrimaryKeyString(this IAddressable grain) { var grainId = GetGrainId(grain); if (LegacyGrainId.TryConvertFromGrainId(grainId, out var legacyId)) { return legacyId.GetPrimaryKeyString(); } return grainId.Key.ToString(); } /// /// Throw an indicating that the grain argument is null. /// /// The grain argument is null. [DoesNotReturn] private static void ThrowGrainNull() => throw new ArgumentNullException("grain"); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IConfigurationValidator.cs ================================================ namespace Orleans { /// /// Describes a configuration validator which is called during client and silo initialization. /// public interface IConfigurationValidator { /// /// Validates system configuration and throws an exception if configuration is not valid. /// void ValidateConfiguration(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrain.cs ================================================ using System; namespace Orleans { using Orleans.Runtime; /// /// Marker interface for grains /// public interface IGrain : IAddressable { } /// /// Marker interface for grains with keys. /// public interface IGrainWithGuidKey : IGrain { } /// /// Marker interface for grains with keys. /// public interface IGrainWithIntegerKey : IGrain { } /// /// Marker interface for grains with keys. /// public interface IGrainWithStringKey : IGrain { } /// /// Marker interface for grains with compound keys. /// public interface IGrainWithGuidCompoundKey : IGrain { } /// /// Marker interface for grains with compound keys. /// public interface IGrainWithIntegerCompoundKey : IGrain { } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainBase.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Timers; namespace Orleans { /// /// Interface for grain implementations /// public interface IGrainBase { /// /// Gets the grain context. /// IGrainContext GrainContext { get; } /// /// Method overridden by grain implementations to handle activation. /// /// The cancellation token used to signify that activation should abort promptly. /// A which represents the operation. Task OnActivateAsync(CancellationToken token) => Task.CompletedTask; /// /// Method overridden by grain implementations to handle deactivation. /// /// The reason for deactivation. /// The cancellation token used to signify that deactivation should complete promptly. /// A which represents the operation. Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token) => Task.CompletedTask; } /// /// Helper methods for implementations. /// public static class GrainBaseExtensions { /// /// Deactivate this grain activation after the current grain method call is completed. /// This call will mark this activation of the current grain to be deactivated and removed at the end of the current method. /// The next call to this grain will result in a different activation to be used, which typical means a new activation will be created automatically by the runtime. /// public static void DeactivateOnIdle(this IGrainBase grain) => grain.GrainContext.Deactivate(new(DeactivationReasonCode.ApplicationRequested, $"{nameof(DeactivateOnIdle)} was called.")); /// /// Starts an attempt to migrating this instance to another location. /// Migration captures the current , making it available to the activation's placement director so that it can consider it when selecting a new location. /// Migration will occur asynchronously, when no requests are executing, and will not occur if the activation's placement director does not select an alternative location. /// public static void MigrateOnIdle(this IGrainBase grain) => grain.GrainContext.Migrate(RequestContext.CallContextData?.Value.Values); /// /// Creates a grain timer. /// /// The timer callback, which will be invoked whenever the timer becomes due. /// The state passed to the callback. /// /// The options for creating the timer. /// /// The type of the parameter. /// /// The instance which represents the timer. /// /// /// /// Grain timers do not keep grains active by default. Setting to /// causes each timer tick to extend the grain activation's lifetime. /// If the timer ticks are infrequent, the grain can still be deactivated due to idleness. /// When a grain is deactivated, all active timers are discarded. /// /// /// Until the returned from the callback is resolved, the next timer tick will not be scheduled. /// That is to say, a timer callback will never be concurrently executed with itself. /// If is set to , the timer callback will be allowed /// to interleave with with other grain method calls and other timers. /// If is set to , the timer callback will respect the /// reentrancy setting of the grain, just like a typical grain method call. /// /// /// The timer may be stopped at any time by calling the 's method. /// Disposing a timer prevents any further timer ticks from being scheduled. /// /// /// The timer due time and period can be updated by calling its method. /// Each time the timer is updated, the next timer tick will be scheduled based on the updated due time. /// Subsequent ticks will be scheduled after the updated period elapses. /// Note that this behavior is the same as the method. /// /// /// Exceptions thrown from the callback will be logged, but will not prevent the next timer tick from being queued. /// /// public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TState state, GrainTimerCreationOptions options) { ArgumentNullException.ThrowIfNull(callback); if (grain is Grain grainClass) { ArgumentNullException.ThrowIfNull(callback); grainClass.EnsureRuntime(); return grainClass.Runtime.TimerRegistry.RegisterGrainTimer(grainClass.GrainContext, callback, state, options); } return grain.GrainContext.ActivationServices.GetRequiredService().RegisterGrainTimer(grain.GrainContext, callback, state, options); } /// /// Creates a grain timer. /// /// The grain instance. /// The timer callback, which will be invoked whenever the timer becomes due. /// /// The options for creating the timer. /// /// /// The instance which represents the timer. /// /// /// /// Grain timers do not keep grains active by default. Setting to /// causes each timer tick to extend the grain activation's lifetime. /// If the timer ticks are infrequent, the grain can still be deactivated due to idleness. /// When a grain is deactivated, all active timers are discarded. /// /// /// Until the returned from the callback is resolved, the next timer tick will not be scheduled. /// That is to say, a timer callback will never be concurrently executed with itself. /// If is set to , the timer callback will be allowed /// to interleave with with other grain method calls and other timers. /// If is set to , the timer callback will respect the /// reentrancy setting of the grain, just like a typical grain method call. /// /// /// The timer may be stopped at any time by calling the 's method. /// Disposing a timer prevents any further timer ticks from being scheduled. /// /// /// The timer due time and period can be updated by calling its method. /// Each time the timer is updated, the next timer tick will be scheduled based on the updated due time. /// Subsequent ticks will be scheduled after the updated period elapses. /// Note that this behavior is the same as the method. /// /// /// Exceptions thrown from the callback will be logged, but will not prevent the next timer tick from being queued. /// /// public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, GrainTimerCreationOptions options) { ArgumentNullException.ThrowIfNull(callback); return RegisterGrainTimer(grain, static (callback, cancellationToken) => callback(cancellationToken), callback, options); } public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, GrainTimerCreationOptions options) { ArgumentNullException.ThrowIfNull(callback); return RegisterGrainTimer(grain, static (callback, cancellationToken) => callback(), callback, options); } /// /// The state passed to the callback. /// The type of the parameter. public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TState state, GrainTimerCreationOptions options) { ArgumentNullException.ThrowIfNull(callback); return RegisterGrainTimer(grain, static (state, _) => state.Callback(state.State), (Callback: callback, State: state), options); } /// /// Creates a grain timer. /// /// The grain instance. /// The timer callback, which will be invoked whenever the timer becomes due. /// /// A representing the amount of time to delay before invoking the callback method specified when the was constructed. /// Specify to prevent the timer from starting. /// Specify to start the timer immediately. /// /// /// The time interval between invocations of the callback method specified when the was constructed. /// Specify to disable periodic signaling. /// /// /// The instance which represents the timer. /// /// /// /// Grain timers do not keep grains active by default. Setting to /// causes each timer tick to extend the grain activation's lifetime. /// If the timer ticks are infrequent, the grain can still be deactivated due to idleness. /// When a grain is deactivated, all active timers are discarded. /// /// /// Until the returned from the callback is resolved, the next timer tick will not be scheduled. /// That is to say, a timer callback will never be concurrently executed with itself. /// If is set to , the timer callback will be allowed /// to interleave with with other grain method calls and other timers. /// If is set to , the timer callback will respect the /// reentrancy setting of the grain, just like a typical grain method call. /// /// /// The timer may be stopped at any time by calling the 's method. /// Disposing a timer prevents any further timer ticks from being scheduled. /// /// /// The timer due time and period can be updated by calling its method. /// Each time the timer is updated, the next timer tick will be scheduled based on the updated due time. /// Subsequent ticks will be scheduled after the updated period elapses. /// Note that this behavior is the same as the method. /// /// /// Exceptions thrown from the callback will be logged, but will not prevent the next timer tick from being queued. /// /// public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TimeSpan dueTime, TimeSpan period) => RegisterGrainTimer(grain, callback, new() { DueTime = dueTime, Period = period }); /// public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TimeSpan dueTime, TimeSpan period) => RegisterGrainTimer(grain, callback, new() { DueTime = dueTime, Period = period }); /// /// The state passed to the callback. /// The type of the parameter. public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TState state, TimeSpan dueTime, TimeSpan period) => RegisterGrainTimer(grain, callback, state, new() { DueTime = dueTime, Period = period }); /// /// The state passed to the callback. /// The type of the parameter. public static IGrainTimer RegisterGrainTimer(this IGrainBase grain, Func callback, TState state, TimeSpan dueTime, TimeSpan period) => RegisterGrainTimer(grain, callback, state, new() { DueTime = dueTime, Period = period }); } /// /// An informational reason code for deactivation. /// [GenerateSerializer] public enum DeactivationReasonCode : byte { /// /// No reason provided. /// None, /// /// The process is currently shutting down. /// ShuttingDown, /// /// Activation of the grain failed. /// ActivationFailed, /// /// This activation is affected by an internal failure in the distributed grain directory. /// /// /// This could be caused by the failure of a process hosting this activation's grain directory partition, for example. /// DirectoryFailure, /// /// This activation is idle. /// ActivationIdle, /// /// This activation is unresponsive to commands or requests. /// ActivationUnresponsive, /// /// Another instance of this grain has been activated. /// DuplicateActivation, /// /// This activation received a request which cannot be handled by the locally running process. /// IncompatibleRequest, /// /// An application error occurred. /// ApplicationError, /// /// The application requested to deactivate this activation. /// ApplicationRequested, /// /// This activation is migrating to a new location. /// Migrating, /// /// The runtime requested to deactivate this activation. /// RuntimeRequested, /// /// Runtime detected that app is running on low memory, and forcefully decided to deactivate. /// HighMemoryPressure, } internal static class DeactivationReasonCodeExtensions { public static bool IsTransientError(this DeactivationReasonCode reasonCode) { return reasonCode is DeactivationReasonCode.DirectoryFailure; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainCallContext.cs ================================================ using System.Reflection; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Serialization.Invocation; namespace Orleans { /// /// A delegate used to intercept invocation of a request. /// /// The invocation context. /// A which must be awaited before processing continues. public delegate Task GrainCallFilterDelegate(IGrainCallContext context); /// /// A delegate used to intercept an incoming request. /// /// The invocation context. /// A which must be awaited before processing continues. public delegate Task OutgoingGrainCallFilterDelegate(IOutgoingGrainCallContext context); /// /// A delegate used to intercept an outgoing request. /// /// The invocation context. /// A which must be awaited before processing continues. public delegate Task IncomingGrainCallFilterDelegate(IIncomingGrainCallContext context); /// /// Represents a method invocation as well as the result of invocation. /// public interface IGrainCallContext { /// /// Gets the request. /// IInvokable Request { get; } /// /// Gets the grain being invoked. /// object Grain { get; } /// /// Gets the identity of the source, if available. /// GrainId? SourceId { get; } /// /// Gets the identity of the target. /// GrainId TargetId { get; } /// /// Gets the type of the interface being invoked. /// GrainInterfaceType InterfaceType { get; } /// /// Gets the name of the interface being invoked. /// string InterfaceName { get; } /// /// Gets the name of the method being invoked. /// string MethodName { get; } /// /// Gets the for the interface method being invoked. /// MethodInfo InterfaceMethod { get; } /// /// Gets or sets the result. /// object? Result { get; set; } /// /// Gets or sets the response. /// Response? Response { get; set; } /// /// Invokes the request. /// /// /// A representing the invocation. /// Task Invoke(); } /// /// Represents an incoming method invocation as well as the result of invocation. /// public interface IIncomingGrainCallContext : IGrainCallContext { /// /// Gets the grain context of the target. /// public IGrainContext TargetContext { get; } /// /// Gets the for the implementation method being invoked. /// MethodInfo ImplementationMethod { get; } } /// /// Represents an outgoing method invocation as well as the result of invocation. /// public interface IOutgoingGrainCallContext : IGrainCallContext { /// /// Gets the grain context of the sender. /// public IGrainContext? SourceContext { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainCallFilter.cs ================================================ using System.Threading.Tasks; namespace Orleans { /// /// Interface for incoming grain call filters. /// public interface IIncomingGrainCallFilter { /// /// Invokes this filter. /// /// The grain call context. /// A representing the work performed. Task Invoke(IIncomingGrainCallContext context); } /// /// Interface for outgoing grain call filters. /// public interface IOutgoingGrainCallFilter { /// /// Invokes this filter. /// /// The grain call context. /// A representing the work performed. Task Invoke(IOutgoingGrainCallContext context); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainContext.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// Represents a grain from the perspective of the runtime. /// public interface IGrainContext : ITargetHolder, IEquatable { /// /// Gets a reference to this grain. /// GrainReference GrainReference { get; } /// /// Gets the grain identity. /// GrainId GrainId { get; } /// /// Gets the grain instance, or if the grain instance has not been set yet. /// object? GrainInstance { get; } /// /// Gets the activation id. /// ActivationId ActivationId { get; } /// /// Gets the activation address. /// GrainAddress Address { get; } /// /// Gets the that provides access to the grain activation's service container. /// IServiceProvider ActivationServices { get; } /// /// Gets the observable lifecycle, which can be used to add lifecycle hooks. /// IGrainLifecycle ObservableLifecycle { get; } /// /// Gets the scheduler. /// IWorkItemScheduler Scheduler { get; } /// /// Gets the which completes when the grain has deactivated. /// Task Deactivated { get; } /// /// Sets the provided value as the component for type . /// /// The type used to lookup this component. /// The component instance. void SetComponent(TComponent? value) where TComponent : class; /// /// Submits an incoming message to this instance. /// /// The message. void ReceiveMessage(object message); /// /// Start activating this instance. /// /// The request context of the request which is causing this instance to be activated, if any. /// A cancellation token which, when canceled, indicates that the process should complete promptly. void Activate(Dictionary? requestContext, CancellationToken cancellationToken = default); /// /// Start deactivating this instance. /// /// The reason for deactivation, for informational purposes. /// A cancellation token which, when canceled, indicates that the process should complete promptly. void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken = default); /// /// Start rehydrating this instance from the provided rehydration context. /// void Rehydrate(IRehydrationContext context); /// /// Starts an attempt to migrating this instance to another location. /// Migration captures the current , making it available to the activation's placement director so that it can consider it when selecting a new location. /// Migration will occur asynchronously, when no requests are executing, and will not occur if the activation's placement director does not select an alternative location. /// /// The request context, which is provided to the placement director so that it can be examined when selecting a new location. /// A cancellation token which, when canceled, indicates that the process should complete promptly. void Migrate(Dictionary? requestContext, CancellationToken cancellationToken = default); } /// /// Extensions for . /// public static class GrainContextExtensions { /// /// Deactivates the provided grain. /// /// /// The grain context. /// /// /// The deactivation reason. /// /// A cancellation token which when canceled, indicates that the process should complete promptly. /// /// A which will complete once the grain has deactivated. /// [Obsolete("This method is error-prone: waiting deactivation to complete from within the grain being deactivated will usually result in a deadlock.")] public static Task DeactivateAsync(this IGrainContext grainContext, DeactivationReason deactivationReason, CancellationToken cancellationToken = default) { grainContext.Deactivate(deactivationReason, cancellationToken); return grainContext.Deactivated; } } /// /// Defines functionality required for grains which are subject to activation collection. /// internal interface ICollectibleGrainContext : IGrainContext { /// /// Gets a value indicating whether the instance is available to process messages. /// bool IsValid { get; } /// /// Gets a value indicating whether this instance is exempt from collection. /// bool IsExemptFromCollection { get; } /// /// Gets a value indicating whether this instance is not currently processing a request. /// bool IsInactive { get; } /// /// Gets the collection age limit, which defines how long an instance must be inactive before it is eligible for collection. /// TimeSpan CollectionAgeLimit { get; } /// /// Gets the keep alive override value, which is the earliest time after which this instance will be available for collection. /// DateTime KeepAliveUntil { get; } /// /// Gets or sets the collection ticket, which is a special value used for tracking this activation's lifetime. /// DateTime CollectionTicket { get; set; } /// /// Gets a value indicating whether this activation has been idle longer than its . /// /// if the activation is stale, otherwise . bool IsStale(); /// /// Gets the duration which this activation has been idle for. /// /// /// The duration which this activation has been idle for. /// TimeSpan GetIdleness(); /// /// Delays activation collection until at least until the specified duration has elapsed. /// /// The period of time to delay activation collection for. void DelayDeactivation(TimeSpan timeSpan); } /// /// Functionality to schedule tasks on a grain. /// public interface IWorkItemScheduler { /// /// Schedules an action for execution by this instance. /// /// /// The action. /// void QueueAction(Action action); /// /// Schedules a task to be started by this instance. /// /// The task. void QueueTask(Task task); /// /// Schedules a work item for execution by this instance. /// /// The work item. /// The state passed when invoking the item. void QueueAction(Action action, object state); } /// /// Provides access to the currently executing grain context. /// public interface IGrainContextAccessor { /// /// Gets the currently executing grain context. /// IGrainContext GrainContext { get; } } /// /// Functionality for accessing or installing an extension on a grain. /// public interface IGrainExtensionBinder { /// /// Returns the grain extension registered for the provided . /// /// /// The grain extension interface. /// /// /// The implementation of the extension which is bound to this grain. /// TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension; /// /// Binds an extension to an addressable object, if not already done. /// /// The type of the extension (e.g. StreamConsumerExtension). /// The public interface type of the implementation. /// A factory function that constructs a new extension object. /// A tuple, containing first the extension and second an addressable reference to the extension's interface. (TExtension, TExtensionInterface) GetOrSetExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension; } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainFactory.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans { public static class GrainFactoryExtensions { /// /// Returns a reference for the provided grain id which implements the specified interface type. /// /// The grain factory. /// The primary key of the grain /// An optional class name prefix used to find the runtime type of the grain. /// The grain interface type which the returned grain reference must implement. /// /// A reference for the provided grain id which implements the specified interface type. /// public static TGrainInterface GetGrain(this IGrainFactory grainFactory, IdSpan grainPrimaryKey, string grainClassNamePrefix) where TGrainInterface : IGrain { ArgumentNullException.ThrowIfNull(grainFactory); return grainFactory.GetGrain(typeof(TGrainInterface), grainPrimaryKey, grainClassNamePrefix).AsReference(); } /// /// Returns a reference for the provided grain id which implements the specified interface type. /// /// The grain factory. /// The primary key of the grain /// The grain interface type which the returned grain reference must implement. /// /// A reference for the provided grain id which implements the specified interface type. /// public static TGrainInterface GetGrain(this IGrainFactory grainFactory, IdSpan grainPrimaryKey) where TGrainInterface : IGrain { ArgumentNullException.ThrowIfNull(grainFactory); return grainFactory.GetGrain(typeof(TGrainInterface), grainPrimaryKey).AsReference(); } } /// /// Functionality for creating references to grains. /// public interface IGrainFactory { /// /// Gets a reference to a grain. /// /// The interface type. /// The primary key of the grain. /// An optional class name prefix used to find the runtime type of the grain. /// A reference to the specified grain. TGrainInterface GetGrain(Guid primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey; /// /// Gets a reference to a grain. /// /// The interface type. /// The primary key of the grain. /// An optional class name prefix used to find the runtime type of the grain. /// A reference to the specified grain. TGrainInterface GetGrain(long primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey; /// /// Gets a reference to a grain. /// /// The interface type. /// The primary key of the grain. /// An optional class name prefix used to find the runtime type of the grain. /// A reference to the specified grain. TGrainInterface GetGrain(string primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey; /// /// Gets a reference to a grain. /// /// The interface type. /// The primary key of the grain. /// The key extension of the grain. /// An optional class name prefix used to find the runtime type of the grain. /// A reference to the specified grain. TGrainInterface GetGrain(Guid primaryKey, string keyExtension, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey; /// /// Gets a reference to a grain. /// /// The interface type. /// The primary key of the grain. /// The key extension of the grain. /// An optional class name prefix used to find the runtime type of the grain. /// A reference to the specified grain. TGrainInterface GetGrain(long primaryKey, string keyExtension, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey; /// /// Creates a reference to the provided . /// /// /// The specific type of . /// /// The object to create a reference to. /// The reference to . TGrainObserverInterface CreateObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver; /// /// Deletes the provided object reference. /// /// /// The specific type of . /// /// The reference being deleted. /// A representing the work performed. void DeleteObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver; /// /// Returns a reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// The primary key of the grain /// /// /// A reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey); /// /// Returns a reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// The primary key of the grain /// /// /// A reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey); /// /// Returns a reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// The primary key of the grain /// /// /// A reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// IGrain GetGrain(Type grainInterfaceType, string grainPrimaryKey); /// /// Returns a reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// The primary key of the grain /// /// /// The grain key extension component. /// /// /// A reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey, string keyExtension); /// /// Returns a reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// The primary key of the grain /// /// /// The grain key extension component. /// /// /// A reference to the grain which is the primary implementation of the provided interface type and has the provided primary key. /// IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey, string keyExtension); /// /// Returns a reference to the specified grain which implements the specified interface. /// /// /// The grain id. /// /// /// The grain interface type which the returned grain reference must implement. /// /// /// A reference to the specified grain which implements the specified interface. /// TGrainInterface GetGrain(GrainId grainId) where TGrainInterface : IAddressable; /// /// Returns an untyped reference for the provided grain id. /// /// /// The grain id. /// /// /// An untyped reference for the provided grain id. /// IAddressable GetGrain(GrainId grainId); /// /// Returns a reference for the provided grain id which implements the specified interface type. /// /// /// The grain id. /// /// /// The interface type which the returned grain reference must implement. /// /// /// A reference for the provided grain id which implements the specified interface type. /// IAddressable GetGrain(GrainId grainId, GrainInterfaceType interfaceType); /// /// Returns a reference for the provided grain id which implements the specified interface type. /// /// The grain interface type which the returned grain reference must implement. /// The primary key of the grain /// A class name prefix used to find the runtime type of the grain. /// /// A reference for the provided grain id which implements the specified interface type. /// IAddressable GetGrain(Type interfaceType, IdSpan grainKey, string grainClassNamePrefix); /// /// Returns a reference for the provided grain id which implements the specified interface type. /// /// The grain interface type which the returned grain reference must implement. /// The primary key of the grain /// /// A reference for the provided grain id which implements the specified interface type. /// IAddressable GetGrain(Type interfaceType, IdSpan grainKey); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IGrainObserver.cs ================================================ using Orleans.Runtime; namespace Orleans { /// /// A marker interface for grain observers. /// Observers are used to receive notifications from grains; that is, they represent the subscriber side of a /// publisher/subscriber interface. /// public interface IGrainObserver : IAddressable { } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/ILocalActivationStatusChecker.cs ================================================ using Orleans.Runtime; namespace Orleans; /// /// Provides a way to check whether a grain is locally activated. /// public interface ILocalActivationStatusChecker { /// /// Returns if the provided grain is locally activated; otherwise, . /// /// The identifier of the grain to check. /// if the provided grain is locally activated; otherwise, . bool IsLocallyActivated(GrainId grainId); } ================================================ FILE: src/Orleans.Core.Abstractions/Core/IStorage.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace Orleans.Core { /// /// Provides method for operating on grain storage. /// public interface IStorage { /// /// Gets the ETag. /// /// /// An ETag, or entity tag, is a value used to prevent concurrent writes where one or more of those writes has not first observed the most recent operation. /// string? Etag { get; } /// /// Gets a value indicating whether the record already exists. /// bool RecordExists { get; } /// /// Clears the grain state. /// /// /// This will usually mean the state record is deleted from backing store, but the specific behavior is defined by the storage provider instance configured for this grain. /// If the Etag does not match what is present in the backing store, then this operation will fail; Set to to indicate "always delete". /// /// /// A representing the operation. /// Task ClearStateAsync(); /// /// Writes grain state to storage. /// /// /// If the Etag does not match what is present in the backing store, then this operation will fail; Set to to indicate "always delete". /// /// /// A representing the operation. /// Task WriteStateAsync(); /// /// Reads grain state from storage. /// /// /// Any previous contents of the grain state data will be overwritten. /// /// /// A representing the operation. /// Task ReadStateAsync(); /// /// Clears the grain state. /// /// /// /// This will usually mean the state record is deleted from backing store, but the specific behavior is defined by the storage provider instance configured for this grain. /// If the Etag does not match what is present in the backing store, then this operation will fail; Set to to indicate "always delete". /// /// /// A representing the operation. /// Task ClearStateAsync(CancellationToken cancellationToken) => ClearStateAsync(); /// /// Writes grain state to storage. /// /// /// /// If the Etag does not match what is present in the backing store, then this operation will fail; Set to to indicate "always delete". /// /// /// A representing the operation. /// Task WriteStateAsync(CancellationToken cancellationToken) => WriteStateAsync(); /// /// Reads grain state from storage. /// /// /// /// Any previous contents of the grain state data will be overwritten. /// /// /// A representing the operation. /// Task ReadStateAsync(CancellationToken cancellationToken) => ReadStateAsync(); } /// /// Provides method for operating on grain state. /// public interface IStorage : IStorage { /// /// Gets or sets the state. /// TState State { get; set; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/Immutable.cs ================================================ namespace Orleans.Concurrency { /// /// Wrapper class for carrying immutable data. /// /// /// Objects that are known to be immutable are given special fast-path handling by the Orleans serializer /// -- which in a nutshell allows the DeepCopy step to be skipped during message sends where the sender and receiver grain are in the same silo. /// /// One very common usage pattern for Immutable is when passing byte[] parameters to a grain. /// If a program knows it will not alter the contents of the byte[] (for example, if it contains bytes from a static image file read from disk) /// then considerable savings in memory usage and message throughput can be obtained by marking that byte[] argument as Immutable. /// /// Type of data to be wrapped by this Immutable [GenerateSerializer, Immutable] public readonly struct Immutable { /// Return reference to the original value stored in this Immutable wrapper. [Id(0)] public readonly T Value; /// /// Constructor to wrap the specified data object in new Immutable wrapper. /// /// Value to be wrapped and marked as immutable. public Immutable(T value) => Value = value; } /// /// Utility class to add the .AsImmutable method to all objects. /// public static class ImmutableExtensions { /// /// Extension method to return this value wrapped in Immutable. /// /// /// Value to be wrapped. /// Immutable wrapper around the original object. /// "/> public static Immutable AsImmutable(this T value) => new(value); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/Internal/ICallChainReentrantGrainContext.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Core.Internal { /// /// Provides functionality for entering and exiting sections of code within a grain during which requests bearing the same are allowed to re-enter the grain. /// public interface ICallChainReentrantGrainContext { /// /// Marks the beginning of a section of code within a grain during which requests bearing the same are allowed to re-enter the grain. /// void OnEnterReentrantSection(Guid reentrancyId); /// /// Marks the end of a section of code within a grain during which requests bearing the same are allowed to re-enter the grain. /// void OnExitReentrantSection(Guid reentrancyId); } } ================================================ FILE: src/Orleans.Core.Abstractions/Core/Internal/IGrainManagementExtension.cs ================================================ using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Core.Internal { /// /// Provides functionality for performing management operations on a grain activation. /// public interface IGrainManagementExtension : IGrainExtension { /// /// Deactivates the current instance once it becomes idle. /// /// A which represents the method call. ValueTask DeactivateOnIdle(); /// /// Attempts to migrate the current instance to a new location once it becomes idle. /// /// A which represents the method call. ValueTask MigrateOnIdle(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Diagnostics/ActivitySources.cs ================================================ using System.Diagnostics; namespace Orleans.Diagnostics; public static class ActivitySources { /// /// Spans triggered from application level code /// public const string ApplicationGrainActivitySourceName = "Microsoft.Orleans.Application"; /// /// Spans triggered from Orleans runtime code /// public const string RuntimeActivitySourceName = "Microsoft.Orleans.Runtime"; /// /// Spans tied to lifecycle operations such as activation, migration, and deactivation. /// public const string LifecycleActivitySourceName = "Microsoft.Orleans.Lifecycle"; /// /// Spans tied to persistent storage operations. /// public const string StorageActivitySourceName = "Microsoft.Orleans.Storage"; /// /// A wildcard name to match all Orleans activity sources. /// public const string AllActivitySourceName = "Microsoft.Orleans.*"; internal static readonly ActivitySource ApplicationGrainSource = new(ApplicationGrainActivitySourceName, "1.1.0"); internal static readonly ActivitySource RuntimeGrainSource = new(RuntimeActivitySourceName, "2.0.0"); internal static readonly ActivitySource LifecycleGrainSource = new(LifecycleActivitySourceName, "1.0.0"); internal static readonly ActivitySource StorageGrainSource = new(StorageActivitySourceName, "1.0.0"); } ================================================ FILE: src/Orleans.Core.Abstractions/Diagnostics/ActivityTagKeys.cs ================================================ namespace Orleans.Diagnostics; /// /// Contains constants for Activity tag keys used throughout Orleans. /// internal static class ActivityTagKeys { /// /// The request ID for an async enumerable operation. /// public const string AsyncEnumerableRequestId = "orleans.async_enumerable.request_id"; /// /// The activation ID tag key. /// public const string ActivationId = "orleans.activation.id"; /// /// The activation cause tag key (e.g., "new" or "rehydrate"). /// public const string ActivationCause = "orleans.activation.cause"; /// /// The deactivation reason tag key. /// public const string DeactivationReason = "orleans.deactivation.reason"; /// /// The grain ID tag key. /// public const string GrainId = "orleans.grain.id"; /// /// The grain type tag key. /// public const string GrainType = "orleans.grain.type"; /// /// The grain type tag key. /// public const string GrainState = "orleans.grain.state"; /// /// The silo ID tag key. /// public const string SiloId = "orleans.silo.id"; /// /// The directory previous registration present tag key. /// public const string DirectoryPreviousRegistrationPresent = "orleans.directory.previousRegistration.present"; /// /// The directory registered address tag key. /// public const string DirectoryRegisteredAddress = "orleans.directory.registered.address"; /// /// The directory forwarding address tag key. /// public const string DirectoryForwardingAddress = "orleans.directory.forwarding.address"; /// /// The exception type tag key. /// public const string ExceptionType = "exception.type"; /// /// The exception message tag key. /// public const string ExceptionMessage = "exception.message"; /// /// The placement filter type tag key. /// public const string PlacementFilterType = "orleans.placement.filter.type"; /// /// The storage provider tag key. /// public const string StorageProvider = "orleans.storage.provider"; /// /// The storage state name tag key. /// public const string StorageStateName = "orleans.storage.state.name"; /// /// The storage state type tag key. /// public const string StorageStateType = "orleans.storage.state.type"; /// /// The RPC system tag key. /// public const string RpcSystem = "rpc.system"; /// /// The RPC service tag key. /// public const string RpcService = "rpc.service"; /// /// The RPC method tag key. /// public const string RpcMethod = "rpc.method"; /// /// The RPC Orleans target ID tag key. /// public const string RpcOrleansTargetId = "rpc.orleans.target_id"; /// /// The RPC Orleans source ID tag key. /// public const string RpcOrleansSourceId = "rpc.orleans.source_id"; /// /// The exception stacktrace tag key. /// public const string ExceptionStacktrace = "exception.stacktrace"; /// /// The exception escaped tag key. /// public const string ExceptionEscaped = "exception.escaped"; /// /// Indicates whether a rehydration attempt was ignored. /// public const string RehydrateIgnored = "orleans.rehydrate.ignored"; /// /// The reason why a rehydration attempt was ignored. /// public const string RehydrateIgnoredReason = "orleans.rehydrate.ignored.reason"; /// /// The previous registration address during rehydration. /// public const string RehydratePreviousRegistration = "orleans.rehydrate.previousRegistration"; /// /// The target silo address for migration. /// public const string MigrationTargetSilo = "orleans.migration.target.silo"; } ================================================ FILE: src/Orleans.Core.Abstractions/Diagnostics/OpenTelemetryHeaders.cs ================================================ namespace Orleans.Diagnostics; internal static class OpenTelemetryHeaders { internal const string TraceParent = "traceparent"; internal const string TraceState = "tracestate"; } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/ClientNotAvailableException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Indicates that a client is not longer reachable. /// [Serializable] [GenerateSerializer] #pragma warning disable RCS1194 // Implement exception constructors. public sealed class ClientNotAvailableException : OrleansException #pragma warning restore RCS1194 // Implement exception constructors. { /// /// Initializes a new instance of the class. /// /// /// The client id. /// internal ClientNotAvailableException(GrainId clientId) : base($"No activation for client {clientId}") { } /// /// Initializes a new instance of the class. /// /// /// The exception message. /// internal ClientNotAvailableException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// internal ClientNotAvailableException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The info. /// /// /// The context. /// [Obsolete] private ClientNotAvailableException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/GatewayTooBusyException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Signifies that a gateway silo is currently in overloaded / load shedding state /// and is unable to currently accept this message being sent. /// /// /// This situation is usually a transient condition. /// The message is likely to be accepted by this or another gateway if it is retransmitted at a later time. /// [Serializable] [GenerateSerializer] public sealed class GatewayTooBusyException : OrleansException { /// /// Initializes a new instance of the class. /// public GatewayTooBusyException() : base("Gateway too busy") { } /// /// Initializes a new instance of the class. /// /// /// The message. /// public GatewayTooBusyException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public GatewayTooBusyException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// [Obsolete] private GatewayTooBusyException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/GrainExtensionNotInstalledException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Signifies that an attempt was made to invoke a grain extension method on a grain where that extension was not installed. /// [Serializable] [GenerateSerializer] public sealed class GrainExtensionNotInstalledException : OrleansException { /// /// Initializes a new instance of the class. /// public GrainExtensionNotInstalledException() : base("GrainExtensionNotInstalledException") { } /// /// Initializes a new instance of the class. /// /// /// The message. /// public GrainExtensionNotInstalledException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public GrainExtensionNotInstalledException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// [Obsolete] private GrainExtensionNotInstalledException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/LimitExceededException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Signifies that a grain is in an overloaded state where some runtime limit setting is currently being exceeded, /// and so that grain is unable to currently accept the message being sent. /// /// /// This situation is often a transient condition. /// The message is likely to be accepted by this grain if it is retransmitted at a later time. /// [Serializable] [GenerateSerializer] public sealed class LimitExceededException : OrleansException { /// /// Initializes a new instance of the class. /// public LimitExceededException() : base("Limit exceeded") { } /// /// Initializes a new instance of the class. /// /// /// The message. /// public LimitExceededException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public LimitExceededException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The limit name. /// /// /// The current value. /// /// /// The threshold value. /// /// /// Extra, descriptive information. /// public LimitExceededException(string limitName, int current, int threshold, object extraInfo) : base($"Limit exceeded {limitName} Current={current} Threshold={threshold} {extraInfo}") { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// [Obsolete] private LimitExceededException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/OrleansConfigurationException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Represents a configuration exception. /// [Serializable] [GenerateSerializer] public sealed class OrleansConfigurationException : Exception { /// public OrleansConfigurationException(string message) : base(message) { } /// public OrleansConfigurationException(string message, Exception innerException) : base(message, innerException) { } /// /// The class name is or is zero (0). /// is . [Obsolete] private OrleansConfigurationException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/OrleansException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// An exception class used by the Orleans runtime for reporting errors. /// /// /// This is also the base class for any more specific exceptions /// raised by the Orleans runtime. /// [Serializable] [GenerateSerializer] public class OrleansException : Exception { /// /// Initializes a new instance of the class. /// public OrleansException() : base("Unexpected error.") { } /// /// Initializes a new instance of the class. /// /// /// The message. /// public OrleansException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public OrleansException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// /// The class name is or is zero (0). /// is . [Obsolete] protected OrleansException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/OrleansLifecycleCanceledException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Indicates a lifecycle was canceled, either by request or due to observer error. /// [Serializable] [GenerateSerializer] public sealed class OrleansLifecycleCanceledException : OrleansException { /// /// Initializes a new instance of the class. /// /// /// The message. /// internal OrleansLifecycleCanceledException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// internal OrleansLifecycleCanceledException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// /// The class name is or is zero (0). /// is . [Obsolete] private OrleansLifecycleCanceledException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/OrleansMessageRejectionException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Indicates that an Orleans message was rejected. /// [Serializable] [GenerateSerializer] public class OrleansMessageRejectionException : OrleansException { /// /// Initializes a new instance of the class. /// /// /// The message. /// internal OrleansMessageRejectionException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// internal OrleansMessageRejectionException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// /// /// The class name is or is zero (0). /// /// /// is . /// [Obsolete] protected OrleansMessageRejectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/SiloUnavailableException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Signifies that an request was canceled due to target silo unavailability. /// [Serializable] [GenerateSerializer] public sealed class SiloUnavailableException : OrleansMessageRejectionException { /// /// Initializes a new instance of the class. /// public SiloUnavailableException() : base("Silo unavailable") { } /// /// Initializes a new instance of the class. /// /// /// The msg. /// public SiloUnavailableException(string msg) : base(msg) { } /// /// Initializes a new instance of the class. /// /// /// The message. /// /// /// The inner exception. /// public SiloUnavailableException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// /// The info. /// /// /// The context. /// /// The class name is or is zero (0). /// is . [Obsolete] private SiloUnavailableException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Core.Abstractions/Exceptions/WrappedException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using Orleans.Serialization.TypeSystem; namespace Orleans.Runtime { /// /// An exception class used by the Orleans runtime for reporting errors. /// /// /// This is also the base class for any more specific exceptions /// raised by the Orleans runtime. /// [Serializable] [GenerateSerializer] public class WrappedException : OrleansException { /// /// Initializes a new instance of the class. /// /// /// The message. /// public WrappedException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// /// The serialization info. /// /// /// The context. /// /// The class name is or is zero (0). /// is . [Obsolete] protected WrappedException(SerializationInfo info, StreamingContext context) : base(info, context) { OriginalExceptionType = info.GetString(nameof(OriginalExceptionType)); } /// /// Gets or sets the type of the original exception. /// [Id(0)] public string? OriginalExceptionType { get; set; } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(OriginalExceptionType), OriginalExceptionType); } /// /// Creates a new instance of the class and rethrows it using the provided exception's stack trace. /// /// The exception. [DoesNotReturn] public static void CreateAndRethrow(Exception exception) { var error = exception switch { WrappedException => exception, { } => CreateFromException(exception), null => throw new ArgumentNullException(nameof(exception)) }; ExceptionDispatchInfo.Throw(error); } private static WrappedException CreateFromException(Exception exception) { var originalExceptionType = RuntimeTypeNameFormatter.Format(exception.GetType()); var detailedMessage = LogFormatter.PrintException(exception); var result = new WrappedException(detailedMessage) { OriginalExceptionType = originalExceptionType, }; if (exception.StackTrace is { } stackTrace) { ExceptionDispatchInfo.SetRemoteStackTrace(result, exception.StackTrace); } return result; } /// public override string ToString() { return $"{nameof(WrappedException)} OriginalType: {OriginalExceptionType}, Message: {Message}"; } } } ================================================ FILE: src/Orleans.Core.Abstractions/GrainDirectory/GrainDirectoryAttribute.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.GrainDirectory { /// /// Specifies the name of the grain directory provider to use for the grain class which this attribute is applied to. /// [AttributeUsage(AttributeTargets.Class)] public sealed class GrainDirectoryAttribute : Attribute, IGrainPropertiesProviderAttribute { /// /// The default grain directory. /// public const string DEFAULT_GRAIN_DIRECTORY = "default"; /// /// Initializes a new instance of the class. /// public GrainDirectoryAttribute() : this(DEFAULT_GRAIN_DIRECTORY) { } /// /// Initializes a new instance of the class. /// /// /// The grain directory provider name. /// public GrainDirectoryAttribute(string grainDirectoryName) { this.GrainDirectoryName = grainDirectoryName; } /// /// Gets or sets the grain directory provider name. /// public string GrainDirectoryName { get; set; } /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.GrainDirectory] = this.GrainDirectoryName ?? DEFAULT_GRAIN_DIRECTORY; } } } ================================================ FILE: src/Orleans.Core.Abstractions/GrainDirectory/IGrainDirectory.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.GrainDirectory { /// /// Interface for grain directory implementations /// public interface IGrainDirectory { /// /// Register a entry in the directory. /// Only one per can be registered. If there is already an /// existing entry, the directory will not override it. /// /// The to register /// The that is effectively registered in the directory. Task Register(GrainAddress address); /// /// Register a entry in the directory. /// Only one per can be registered. If there is already an /// existing entry, the directory will not override it. /// /// The to register /// The that is effectively registered in the directory. Task Register(GrainAddress address, GrainAddress? previousAddress) => GrainDirectoryExtension.Register(this, address, previousAddress); /// /// Unregisters the specified entry from the directory. /// /// /// The to unregister. /// /// /// A representing the operation. /// Task Unregister(GrainAddress address); /// /// Lookup for a for a given Grain ID. /// /// The Grain ID to lookup /// The entry found in the directory, if any Task Lookup(GrainId grainId); /// /// Unregisters all grain directory entries which point to any of the specified silos. /// /// /// Can be a No-Op depending on the implementation. /// /// The silos to be removed from the directory /// /// A representing the operation. /// Task UnregisterSilos(List siloAddresses); } internal static class GrainDirectoryExtension { internal static async Task Register(IGrainDirectory directory, GrainAddress address, GrainAddress? previousAddress) { if (previousAddress is not null) { await directory.Unregister(previousAddress); } return await directory.Register(address); } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/ActivationId.cs ================================================ using System; using System.Buffers.Binary; using System.Buffers.Text; using System.Diagnostics; using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; namespace Orleans.Runtime { /// /// Uniquely identifies a grain activation. /// [Serializable, GenerateSerializer, Immutable] [JsonConverter(typeof(ActivationIdConverter))] public readonly struct ActivationId : IEquatable, ISpanFormattable { [DataMember(Order = 0)] [Id(0)] internal readonly Guid Key; /// /// Initializes a new instance of the struct. /// /// The activation id. public ActivationId(Guid key) => Key = key; /// /// Gets a value indicating whether the instance is the default instance. /// public bool IsDefault => Key == default; /// /// Returns a new, random activation id. /// /// A new, random activation id. public static ActivationId NewId() => new(Guid.NewGuid()); /// /// Returns an activation id which has been computed deterministically and reproducibly from the provided grain id. /// /// The grain id. /// An activation id which has been computed deterministically and reproducibly from the provided grain id. public static ActivationId GetDeterministic(GrainId grain) { Span temp = stackalloc byte[16]; BinaryPrimitives.WriteUInt64LittleEndian(temp, grain.Type.GetUniformHashCode()); BinaryPrimitives.WriteUInt64LittleEndian(temp[8..], grain.Key.GetUniformHashCode()); var key = new Guid(temp); return new ActivationId(key); } /// public override bool Equals(object? obj) => obj is ActivationId other && Key.Equals(other.Key); /// public bool Equals(ActivationId other) => Key.Equals(other.Key); /// public override int GetHashCode() => Key.GetHashCode(); /// public override string ToString() => $"@{Key:N}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"@{Key:N}", out charsWritten); /// /// Returns a string representation of this activation id which can be parsed by . /// /// A string representation of this activation id which can be parsed by . public string ToParsableString() => ToString(); /// /// Parses a string representation of an activation id which was created using . /// /// The string representation of the activation id. /// The activation id. public static ActivationId FromParsableString(string activationId) { var span = activationId.AsSpan(); return span.Length == 33 && span[0] == '@' ? new(Guid.ParseExact(span[1..], "N")) : throw new FormatException($"Invalid activation id: {activationId}"); } /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(ActivationId left, ActivationId right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(ActivationId left, ActivationId right) => !(left == right); } /// /// Functionality for converting instances to and from their JSON representation. /// public sealed class ActivationIdConverter : JsonConverter { /// public override ActivationId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetString() is { } str ? ActivationId.FromParsableString(str) : default; /// public override void Write(Utf8JsonWriter writer, ActivationId value, JsonSerializerOptions options) { Span buf = stackalloc byte[33]; buf[0] = (byte)'@'; Utf8Formatter.TryFormat(value.Key, buf[1..], out var len, 'N'); Debug.Assert(len == 32); writer.WriteStringValue(buf); } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/ClientGrainId.cs ================================================ using System; namespace Orleans.Runtime { /// /// Identifies a client. /// internal readonly struct ClientGrainId : IEquatable, IComparable, ISpanFormattable { /// /// Creates a new instance. /// private ClientGrainId(GrainId grainId) => this.GrainId = grainId; /// /// Gets the underlying . /// public readonly GrainId GrainId; /// /// Creates a new instance. /// public static ClientGrainId Create() => Create(GrainIdKeyExtensions.CreateGuidKey(Guid.NewGuid())); /// /// Creates a new instance. /// public static ClientGrainId Create(string id) { ArgumentNullException.ThrowIfNullOrWhiteSpace(id); return Create(IdSpan.Create(id)); } /// /// Creates a new instance. /// public static ClientGrainId Create(IdSpan id) => new ClientGrainId(new GrainId(GrainTypePrefix.ClientGrainType, id)); /// /// Converts the provided to a . A return value indicates whether the operation succeeded. /// public static bool TryParse(GrainId grainId, out ClientGrainId clientId) { if (!grainId.Type.IsClient()) { clientId = default; return false; } // Strip the observer id, if present. var key = grainId.Key.AsSpan(); if (key.IndexOf((byte)ObserverGrainId.SegmentSeparator) is int index && index >= 0) { key = key[..index]; grainId = new GrainId(grainId.Type, new IdSpan(key.ToArray())); } clientId = new ClientGrainId(grainId); return true; } /// public override bool Equals(object? obj) => obj is ClientGrainId clientId && GrainId.Equals(clientId.GrainId); /// public override int GetHashCode() => this.GrainId.GetHashCode(); /// public override string ToString() => this.GrainId.ToString(); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => ((ISpanFormattable)GrainId).TryFormat(destination, out charsWritten, format, provider); /// public bool Equals(ClientGrainId other) => this.GrainId.Equals(other.GrainId); /// public int CompareTo(ClientGrainId other) => this.GrainId.CompareTo(other.GrainId); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(ClientGrainId left, ClientGrainId right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(ClientGrainId left, ClientGrainId right) => !(left == right); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainAddress.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using Orleans.GrainDirectory; namespace Orleans.Runtime { /// /// Represents an entry in a /// [GenerateSerializer, Immutable] public sealed class GrainAddress : IEquatable, ISpanFormattable { [Id(0)] private readonly GrainId _grainId; [Id(1)] private readonly ActivationId _activationId; /// /// Identifier of the Grain /// public GrainId GrainId { get => _grainId; init => _grainId = value; } /// /// Id of the specific Grain activation /// public ActivationId ActivationId { get => _activationId; init => _activationId = value; } /// /// Address of the silo where the grain activation lives /// [Id(2)] public SiloAddress? SiloAddress { get; init; } /// /// MembershipVersion at the time of registration /// [Id(3)] public MembershipVersion MembershipVersion { get; init; } = MembershipVersion.MinValue; [JsonIgnore] public bool IsComplete => !_grainId.IsDefault && !_activationId.IsDefault && SiloAddress != null; public override bool Equals(object? obj) => Equals(obj as GrainAddress); public bool Equals(GrainAddress? other) { if (ReferenceEquals(this, other)) return true; return MatchesGrainIdAndSilo(this, other) && _activationId.Equals(other._activationId); } /// /// Two grain addresses match if they have equal and values /// and either one has a default value or both have equal values. /// /// The other to compare this one with. /// Returns true if the two are considered to match. public bool Matches([NotNullWhen(true)] GrainAddress? other) { if (ReferenceEquals(this, other)) return true; return MatchesGrainIdAndSilo(this, other) && (_activationId.IsDefault || other._activationId.IsDefault || _activationId.Equals(other._activationId)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool MatchesGrainIdAndSilo([NotNullWhen(true)] GrainAddress? address, [NotNullWhen(true)] GrainAddress? other) { return other is not null && address is not null && address.GrainId.Equals(other.GrainId) && !(address.SiloAddress is null ^ other.SiloAddress is null) && (address.SiloAddress is null || address.SiloAddress.Equals(other.SiloAddress)); } public override int GetHashCode() => HashCode.Combine(SiloAddress, _grainId, _activationId); public override string ToString() => $"[{nameof(GrainAddress)} GrainId {_grainId}, ActivationId: {_activationId}, SiloAddress: {SiloAddress}]"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"[{nameof(GrainAddress)} GrainId {_grainId}, ActivationId: {_activationId}, SiloAddress: {SiloAddress}]", out charsWritten); public string ToFullString() => $"[{nameof(GrainAddress)} GrainId {_grainId}, ActivationId: {_activationId}, SiloAddress: {SiloAddress}, MembershipVersion: {MembershipVersion}]"; internal static GrainAddress NewActivationAddress(SiloAddress silo, GrainId grain) => GetAddress(silo, grain, ActivationId.NewId()); internal static GrainAddress GetAddress(SiloAddress? silo, GrainId grain, ActivationId activation) { // Silo part is not mandatory if (grain.IsDefault) throw new ArgumentNullException(nameof(grain)); return new GrainAddress { GrainId = grain, ActivationId = activation, SiloAddress = silo, }; } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainAddressCacheUpdate.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Orleans.Runtime; /// /// Represents a directive to update an invalid, cached to a valid . /// [GenerateSerializer, Immutable] public sealed class GrainAddressCacheUpdate : ISpanFormattable { [Id(0)] private readonly GrainId _grainId; [Id(1)] private readonly ActivationId _invalidActivationId; [Id(2)] private readonly SiloAddress? _invalidSiloAddress; [Id(3)] private readonly MembershipVersion _invalidMembershipVersion = MembershipVersion.MinValue; [Id(4)] private readonly ActivationId _validActivationId; [Id(5)] private readonly SiloAddress? _validSiloAddress; [Id(6)] private readonly MembershipVersion _validMembershipVersion = MembershipVersion.MinValue; public GrainAddressCacheUpdate(GrainAddress invalidAddress, GrainAddress? validAddress) { ArgumentNullException.ThrowIfNull(invalidAddress); _grainId = invalidAddress.GrainId; _invalidActivationId = invalidAddress.ActivationId; _invalidSiloAddress = invalidAddress.SiloAddress; _invalidMembershipVersion = invalidAddress.MembershipVersion; if (validAddress is not null) { if (invalidAddress.GrainId != validAddress.GrainId) { ThrowGrainIdDoesNotMatch(invalidAddress, validAddress); return; } _validActivationId = validAddress.ActivationId; _validSiloAddress = validAddress.SiloAddress; _validMembershipVersion = validAddress.MembershipVersion; } } /// /// Identifier of the Grain. /// public GrainId GrainId => _grainId; /// /// Identifier of the invalid grain activation. /// public ActivationId InvalidActivationId => _invalidActivationId; /// /// Address of the silo indicated by the invalid grain activation cache entry. /// public SiloAddress? InvalidSiloAddress => _invalidSiloAddress; /// /// Gets the valid grain activation address. /// public GrainAddress? ValidGrainAddress => _validSiloAddress switch { null => null, _ => new() { GrainId = _grainId, ActivationId = _validActivationId, SiloAddress = _validSiloAddress, MembershipVersion = _validMembershipVersion, } }; /// /// Gets the invalid grain activation address. /// public GrainAddress InvalidGrainAddress => new() { GrainId = _grainId, ActivationId = _invalidActivationId, SiloAddress = _invalidSiloAddress, MembershipVersion = _invalidMembershipVersion, }; public override string ToString() => $"[{nameof(GrainAddressCacheUpdate)} GrainId {_grainId}, InvalidActivationId: {_invalidActivationId}, InvalidSiloAddress: {_invalidSiloAddress}, ValidGrainAddress: {ValidGrainAddress}]"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"[{nameof(GrainAddressCacheUpdate)} GrainId {_grainId}, InvalidActivationId: {_invalidActivationId}, InvalidSiloAddress: {_invalidSiloAddress}, ValidGrainAddress: {ValidGrainAddress}]", out charsWritten); public string ToFullString() => $"[{nameof(GrainAddressCacheUpdate)} GrainId {_grainId}, InvalidActivationId: {_invalidActivationId}, InvalidSiloAddress: {_invalidSiloAddress}, ValidGrainAddress: {ValidGrainAddress}, MembershipVersion: {_invalidMembershipVersion}]"; [DoesNotReturn] private static void ThrowGrainIdDoesNotMatch(GrainAddress invalidAddress, GrainAddress validAddress) => throw new ArgumentException($"Invalid grain address grain id {invalidAddress.GrainId} does not match valid grain address grain id {validAddress.GrainId}.", nameof(validAddress)); } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainId.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace Orleans.Runtime { /// /// Identifies a grain. /// [Serializable, GenerateSerializer, Immutable] [JsonConverter(typeof(GrainIdJsonConverter))] public readonly struct GrainId : IEquatable, IComparable, ISerializable, ISpanFormattable, ISpanParsable { [Id(0)] private readonly GrainType _type; [Id(1)] private readonly IdSpan _key; /// /// Creates a new instance. /// public GrainId(GrainType type, IdSpan key) { _type = type; _key = key; } /// /// Creates a new instance. /// private GrainId(SerializationInfo info, StreamingContext context) { _type = new GrainType(IdSpan.UnsafeCreate((byte[]?)info.GetValue("tv", typeof(byte[])), info.GetInt32("th"))); _key = IdSpan.UnsafeCreate((byte[]?)info.GetValue("kv", typeof(byte[])), info.GetInt32("kh")); } /// /// Gets the grain type. /// public GrainType Type => _type; /// /// Gets the grain key. /// public IdSpan Key => _key; /// /// Creates a new instance. /// public static GrainId Create(string type, string key) => Create(GrainType.Create(type), key); /// /// Creates a new instance. /// public static GrainId Create(GrainType type, string key) { ArgumentNullException.ThrowIfNullOrWhiteSpace(key); return new GrainId(type, IdSpan.Create(key)); } /// /// Creates a new instance. /// public static GrainId Create(GrainType type, IdSpan key) => new GrainId(type, key); /// /// Parses a from the span. /// public static GrainId Parse(ReadOnlySpan value, IFormatProvider? provider = null) { if (!TryParse(value, provider, out var result)) { ThrowInvalidGrainId(value); static void ThrowInvalidGrainId(ReadOnlySpan value) => throw new ArgumentException($"Unable to parse \"{value}\" as a grain id"); } return result; } /// /// Tries to parse a from the span. /// /// if a valid was parsed. otherwise public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out GrainId result) { int i; if ((i = value.IndexOf('/')) < 0) { result = default; return false; } var typeSpan = value[0..i]; var type = new byte[Encoding.UTF8.GetByteCount(typeSpan)]; Encoding.UTF8.GetBytes(typeSpan, type); var idSpan = value[(i + 1)..]; var id = new byte[Encoding.UTF8.GetByteCount(idSpan)]; Encoding.UTF8.GetBytes(idSpan, id); result = new(new GrainType(type), new IdSpan(id)); return true; } /// /// Parses a from the string. /// public static GrainId Parse(string value) => Parse(value.AsSpan(), null); /// /// Parses a from the string. /// public static GrainId Parse(string value, IFormatProvider? provider = null) => Parse(value.AsSpan(), provider); /// /// Tries to parse a from the string. /// /// if a valid was parsed. otherwise public static bool TryParse(string? value, out GrainId result) => TryParse(value.AsSpan(), null, out result); /// /// Tries to parse a from the string. /// /// if a valid was parsed. otherwise public static bool TryParse(string? value, IFormatProvider? provider, out GrainId result) => TryParse(value.AsSpan(), provider, out result); /// /// if this instance is the default value, if it is not. /// public bool IsDefault => _type.IsDefault && _key.IsDefault; /// public override bool Equals(object? obj) => obj is GrainId id && Equals(id); /// public bool Equals(GrainId other) => _key.Equals(other._key) && _type.Equals(other._type); /// public override int GetHashCode() => HashCode.Combine(_type, _key); /// /// Generates a uniform, stable hash code for a grain id. /// public uint GetUniformHashCode() { // This value must be stable for a given id and equal for all nodes in a cluster. // HashCode.Combine does not currently offer stability with respect to its inputs. return _type.GetUniformHashCode() * 31 + _key.GetUniformHashCode(); } /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("tv", GrainType.UnsafeGetArray(_type)); info.AddValue("th", _type.GetHashCode()); info.AddValue("kv", IdSpan.UnsafeGetArray(_key)); info.AddValue("kh", _key.GetHashCode()); } /// public int CompareTo(GrainId other) { var typeComparison = _type.CompareTo(other._type); if (typeComparison != 0) return typeComparison; return _key.CompareTo(other._key); } /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(GrainId left, GrainId right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(GrainId left, GrainId right) => !left.Equals(right); /// public override string ToString() => $"{_type}/{_key}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"{_type}/{_key}", out charsWritten); } /// /// Functionality for converting a to and from a JSON string. /// public sealed class GrainIdJsonConverter : JsonConverter { /// public override GrainId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var valueLength = reader.HasValueSequence ? checked((int)reader.ValueSequence.Length) : reader.ValueSpan.Length; Span buf = valueLength <= 128 ? (stackalloc char[128])[..valueLength] : new char[valueLength]; var written = reader.CopyString(buf); buf = buf[..written]; return GrainId.Parse(buf); } /// public override GrainId ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Read(ref reader, typeToConvert, options); /// public override void Write(Utf8JsonWriter writer, GrainId value, JsonSerializerOptions options) => WriteGrainId(writer, value, isPropertyName: false); /// public override void WriteAsPropertyName(Utf8JsonWriter writer, [DisallowNull] GrainId value, JsonSerializerOptions options) => WriteGrainId(writer, value, isPropertyName: true); private static void WriteGrainId(Utf8JsonWriter writer, GrainId value, bool isPropertyName) { var type = value.Type.AsSpan(); var key = value.Key.AsSpan(); Span buf = stackalloc byte[type.Length + key.Length + 1]; type.CopyTo(buf); buf[type.Length] = (byte)'/'; key.CopyTo(buf[(type.Length + 1)..]); if (isPropertyName) { writer.WritePropertyName(buf); } else { writer.WriteStringValue(buf); } } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainIdKeyExtensions.cs ================================================ using System; using System.Buffers.Text; using System.Diagnostics; using System.Text; namespace Orleans.Runtime { /// /// Extensions for keys. /// public static class GrainIdKeyExtensions { /// /// Creates an representing a key. /// /// /// The key. /// /// /// An representing the provided key. /// public static IdSpan CreateIntegerKey(long key) { Span buf = stackalloc byte[sizeof(long) * 2]; Utf8Formatter.TryFormat(key, buf, out var len, 'X'); Debug.Assert(len > 0, "Unable to format the provided value as a UTF8 string"); return new IdSpan(buf[..len].ToArray()); } /// /// Creates an representing a key and key extension string. /// /// /// The key. /// /// /// The UTF-8 encoded key extension. /// /// /// An representing the provided key and key extension. /// public static IdSpan CreateIntegerKey(long key, ReadOnlySpan keyExtension) { if (keyExtension.IsEmpty) return CreateIntegerKey(key); Span tmp = stackalloc byte[sizeof(long) * 2]; Utf8Formatter.TryFormat(key, tmp, out var len, 'X'); Debug.Assert(len > 0, "Unable to format the provided value as a UTF8 string"); var buf = new byte[len + 1 + keyExtension.Length]; tmp[..len].CopyTo(buf); buf[len] = (byte)'+'; keyExtension.CopyTo(buf.AsSpan(len + 1)); return new(buf); } /// /// Creates an representing a key and key extension string. /// /// /// The key. /// /// /// The key extension. /// /// /// An representing the provided key and key extension. /// public static IdSpan CreateIntegerKey(long key, string? keyExtension) { if (string.IsNullOrWhiteSpace(keyExtension)) { return CreateIntegerKey(key); } Span tmp = stackalloc byte[sizeof(long) * 2]; Utf8Formatter.TryFormat(key, tmp, out var len, 'X'); Debug.Assert(len > 0, "Unable to format the provided value as a UTF8 string"); var extLen = Encoding.UTF8.GetByteCount(keyExtension); var buf = new byte[len + 1 + extLen]; tmp[..len].CopyTo(buf); buf[len] = (byte)'+'; Encoding.UTF8.GetBytes(keyExtension, 0, keyExtension.Length, buf, len + 1); return new IdSpan(buf); } /// /// Creates an representing a key. /// /// /// The key. /// /// /// An representing the provided key. /// public static IdSpan CreateGuidKey(Guid key) { var buf = new byte[32]; Utf8Formatter.TryFormat(key, buf, out var len, 'N'); Debug.Assert(len == 32, "Unable to format the provided value as a UTF8 string"); return new IdSpan(buf); } /// /// Creates an representing a key and key extension string. /// /// /// The key. /// /// /// The UTF-8 encoded key extension. /// /// /// An representing the provided key and key extension. /// public static IdSpan CreateGuidKey(Guid key, ReadOnlySpan keyExtension) { if (keyExtension.IsEmpty) return CreateGuidKey(key); var buf = new byte[32 + 1 + keyExtension.Length]; Utf8Formatter.TryFormat(key, buf, out var len, 'N'); Debug.Assert(len == 32, "Unable to format the provided value as a UTF8 string"); buf[32] = (byte)'+'; keyExtension.CopyTo(buf.AsSpan(len + 1)); return new(buf); } /// /// Creates an representing a key and key extension string. /// /// /// The key. /// /// /// The key extension. /// /// /// An representing the provided key and key extension. /// public static IdSpan CreateGuidKey(Guid key, string? keyExtension) { if (string.IsNullOrWhiteSpace(keyExtension)) { return CreateGuidKey(key); } var extLen = Encoding.UTF8.GetByteCount(keyExtension); var buf = new byte[32 + 1 + extLen]; Utf8Formatter.TryFormat(key, buf, out var len, 'N'); Debug.Assert(len == 32, "Unable to format the provided value as a UTF8 string"); buf[32] = (byte)'+'; Encoding.UTF8.GetBytes(keyExtension, 0, keyExtension.Length, buf, 33); return new IdSpan(buf); } /// /// Tries to parse the portion of the provided grain id to extract a key and key extension. /// /// /// The grain id. /// /// /// The key. /// /// /// The key extension. /// /// /// when the grain id was successfully parsed, otherwise. /// public static bool TryGetIntegerKey(this GrainId grainId, out long key, out string? keyExt) { keyExt = null; var keyString = grainId.Key.AsSpan(); if (keyString.IndexOf((byte)'+') is int index && index >= 0) { keyExt = Encoding.UTF8.GetString(keyString[(index + 1)..]); keyString = keyString[..index]; } return Utf8Parser.TryParse(keyString, out key, out var len, 'X') && len == keyString.Length; } /// /// Tries to parse the portion of the provided grain id to extract a key. /// /// /// The grain id. /// /// /// The key. /// /// /// when the grain id was successfully parsed, otherwise. /// internal static bool TryGetIntegerKey(this GrainId grainId, out long key) { var keyString = grainId.Key.AsSpan(); if (keyString.IndexOf((byte)'+') is int index && index >= 0) keyString = keyString[..index]; return Utf8Parser.TryParse(keyString, out key, out var len, 'X') && len == keyString.Length; } /// /// Returns the representation of a grain key. /// /// The grain id. /// The output parameter to return the extended key part of the grain primary key, if extended primary key was provided for that grain. /// A long representing the key for this grain. public static long GetIntegerKey(this GrainId grainId, out string? keyExt) { if (!grainId.TryGetIntegerKey(out var result, out keyExt)) { ThrowInvalidIntegerKeyFormat(grainId); } return result; } /// /// Returns the representation of a grain key. /// /// The grain to find the key for. /// A representing the key for this grain. public static long GetIntegerKey(this GrainId grainId) { if (!grainId.TryGetIntegerKey(out var result)) { ThrowInvalidIntegerKeyFormat(grainId); } return result; } /// /// Tries to parse the portion of the provided grain id to extract a key and key extension. /// /// /// The grain id. /// /// /// The key. /// /// /// The key extension. /// /// /// when the grain id was successfully parsed, otherwise. /// public static bool TryGetGuidKey(this GrainId grainId, out Guid key, out string? keyExt) { keyExt = null; var keyString = grainId.Key.AsSpan(); if (keyString.Length > 32 && keyString[32] == (byte)'+') { keyExt = Encoding.UTF8.GetString(keyString[33..]); keyString = keyString[..32]; } else if (keyString.Length != 32) { key = default; return false; } return Utf8Parser.TryParse(keyString, out key, out var len, 'N') && len == 32; } /// /// Tries to parse the portion of the provided grain id to extract a key. /// /// /// The grain id. /// /// /// The key. /// /// /// when the grain id was successfully parsed, otherwise. /// internal static bool TryGetGuidKey(this GrainId grainId, out Guid key) { var keyString = grainId.Key.AsSpan(); if (keyString.Length > 32 && keyString[32] == (byte)'+') { keyString = keyString[..32]; } else if (keyString.Length != 32) { key = default; return false; } return Utf8Parser.TryParse(keyString, out key, out var len, 'N') && len == 32; } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// The output parameter to return the extended key part of the grain primary key, if extended primary key was provided for that grain. /// A representing the primary key for this grain. public static Guid GetGuidKey(this GrainId grainId, out string? keyExt) { if (!grainId.TryGetGuidKey(out var result, out keyExt)) { ThrowInvalidGuidKeyFormat(grainId); } return result; } /// /// Returns the representation of a grain primary key. /// /// The grain to find the primary key for. /// A representing the primary key for this grain. public static Guid GetGuidKey(this GrainId grainId) { if (!grainId.TryGetGuidKey(out var result)) { ThrowInvalidGuidKeyFormat(grainId); } return result; } /// /// Throws an exception indicating that a -based grain id was incorrectly formatted. /// /// /// The grain id. /// private static void ThrowInvalidGuidKeyFormat(GrainId grainId) => throw new ArgumentException($"Value \"{grainId}\" is not in the correct format for a Guid key.", nameof(grainId)); /// /// Throws an exception indicating that a -based grain id was incorrectly formatted. /// /// /// The grain id. /// private static void ThrowInvalidIntegerKeyFormat(GrainId grainId) => throw new ArgumentException($"Value \"{grainId}\" is not in the correct format for an Integer key.", nameof(grainId)); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainInterfaceType.cs ================================================ using System; namespace Orleans.Runtime { /// /// Uniquely identifies a grain interface. /// [Serializable, GenerateSerializer, Immutable] public readonly struct GrainInterfaceType : IEquatable, ISpanFormattable { /// /// The underlying value. /// [Id(0)] private readonly IdSpan _value; /// /// Creates a instance. /// public GrainInterfaceType(string value) { ArgumentNullException.ThrowIfNullOrWhiteSpace(value); _value = IdSpan.Create(value); } /// /// Creates a instance. /// public GrainInterfaceType(IdSpan value) => _value = value; /// /// Returns the value underlying this instance. /// public IdSpan Value => _value; /// /// Returns true if this value is equal to the instance. /// public bool IsDefault => _value.IsDefault; /// /// Creates a instance. /// public static GrainInterfaceType Create(string value) => new GrainInterfaceType(value); /// public override bool Equals(object? obj) => obj is GrainInterfaceType id && Equals(id); /// public bool Equals(GrainInterfaceType other) => _value.Equals(other._value); /// public override int GetHashCode() => _value.GetHashCode(); /// /// Returns a UTF8 interpretation of the current instance. /// /// public override string ToString() => _value.ToString(); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString() ?? ""; bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => _value.TryFormat(destination, out charsWritten); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(GrainInterfaceType left, GrainInterfaceType right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(GrainInterfaceType left, GrainInterfaceType right) => !left.Equals(right); } /// /// Gets a for an interface. /// public interface IGrainInterfaceTypeProvider { /// /// Gets the corresponding to the specified . /// /// The grain interface type instance. /// The resulting grain interface type identifier. /// /// if a corresponding to the provided type was found, otherwise . /// bool TryGetGrainInterfaceType(Type type, out GrainInterfaceType grainInterfaceType); } /// /// Gets a from attributes implementing . /// public class AttributeGrainInterfaceTypeProvider : IGrainInterfaceTypeProvider { /// /// The service provider. /// private readonly IServiceProvider _serviceProvider; /// /// Creates a instance. /// public AttributeGrainInterfaceTypeProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// public bool TryGetGrainInterfaceType(Type type, out GrainInterfaceType grainInterfaceType) { foreach (var attr in type.GetCustomAttributes(inherit: false)) { if (attr is IGrainInterfaceTypeProviderAttribute provider) { grainInterfaceType = provider.GetGrainInterfaceType(this._serviceProvider, type); return true; } } grainInterfaceType = default; return false; } } /// /// An which implements this specifies the of the /// type which it is attached to. /// public interface IGrainInterfaceTypeProviderAttribute { /// /// Gets the grain interface identifier. /// /// The service provider. /// The grain interface type. /// /// The corresponding to the provided type. /// GrainInterfaceType GetGrainInterfaceType(IServiceProvider services, Type type); } /// /// When applied to a grain interface, specifies the . /// [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)] public sealed class GrainInterfaceTypeAttribute : Attribute, IGrainInterfaceTypeProviderAttribute { /// /// The grain interface type. /// private readonly GrainInterfaceType _value; /// /// Creates a instance. /// public GrainInterfaceTypeAttribute(string value) { _value = GrainInterfaceType.Create(value); } /// public GrainInterfaceType GetGrainInterfaceType(IServiceProvider services, Type type) => _value; } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainType.cs ================================================ using System; using System.Runtime.Serialization; using System.Text; namespace Orleans.Runtime { /// /// Represents the type of a grain. /// [Serializable, GenerateSerializer, Immutable] public readonly struct GrainType : IEquatable, IComparable, ISerializable, ISpanFormattable { [Id(0)] private readonly IdSpan _value; /// /// Initializes a new instance of the struct. /// /// /// The id. /// public GrainType(IdSpan id) => _value = id; /// /// Initializes a new instance of the struct. /// /// /// The raw id value. /// public GrainType(byte[] value) => _value = new IdSpan(value); /// /// Initializes a new instance of the struct. /// /// /// The serialization info. /// /// /// The context. /// private GrainType(SerializationInfo info, StreamingContext context) { _value = IdSpan.UnsafeCreate((byte[]?)info.GetValue("v", typeof(byte[])), info.GetInt32("h")); } /// /// Gets the underlying value. /// public IdSpan Value => _value; /// /// Returns a span representation of this instance. /// /// /// A representation of the value. /// public ReadOnlySpan AsSpan() => _value.AsSpan(); /// /// Creates a new instance. /// /// /// The value. /// /// /// The newly created instance. /// public static GrainType Create(string value) { ArgumentNullException.ThrowIfNullOrWhiteSpace(value); return new GrainType(Encoding.UTF8.GetBytes(value)); } /// /// Converts a to a . /// /// The grain type to convert. /// The corresponding . public static explicit operator IdSpan(GrainType kind) => kind._value; /// /// Converts a to a . /// /// The id span to convert. /// The corresponding . public static explicit operator GrainType(IdSpan id) => new GrainType(id); /// /// Gets a value indicating whether this instance is the default value. /// public bool IsDefault => _value.IsDefault; /// public override bool Equals(object? obj) => obj is GrainType kind && Equals(kind); /// public bool Equals(GrainType obj) => _value.Equals(obj._value); /// public override int GetHashCode() => _value.GetHashCode(); /// /// Generates a uniform, stable hash code for this grain type. /// /// /// A uniform, stable hash of this instance. /// public uint GetUniformHashCode() => _value.GetUniformHashCode(); /// /// Returns the array underlying a grain type instance. /// /// The grain type. /// The array underlying a grain type instance. /// /// The returned array must not be modified. /// public static byte[]? UnsafeGetArray(GrainType id) => IdSpan.UnsafeGetArray(id._value); /// public int CompareTo(GrainType other) => _value.CompareTo(other._value); /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("v", IdSpan.UnsafeGetArray(_value)); info.AddValue("h", _value.GetHashCode()); } /// /// Returns a string representation of this instance, decoding the value as UTF8. /// /// /// A representation of this instance. /// public override string? ToString() => _value.ToString(); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString() ?? ""; bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => _value.TryFormat(destination, out charsWritten); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(GrainType left, GrainType right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(GrainType left, GrainType right) => !(left == right); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GrainTypePrefix.cs ================================================ using System; using System.Text; namespace Orleans.Runtime { /// /// Prefixes and corresponding helper methods for . /// public static class GrainTypePrefix { /// /// The prefix for system types. /// public const string SystemPrefix = "sys."; /// /// The prefix for system targets. /// public const string SystemTargetPrefix = SystemPrefix + "svc."; /// /// A span representation of . /// public static readonly ReadOnlyMemory SystemTargetPrefixBytes = Encoding.UTF8.GetBytes(SystemTargetPrefix); /// /// The prefix for grain service types. /// public const string GrainServicePrefix = SystemTargetPrefix + "user."; /// /// A span representation of . /// public static readonly ReadOnlyMemory GrainServicePrefixBytes = Encoding.UTF8.GetBytes(GrainServicePrefix); /// /// The prefix for clients. /// public const string ClientPrefix = SystemPrefix + "client"; /// /// A span representation of . /// public static readonly ReadOnlyMemory ClientPrefixBytes = Encoding.UTF8.GetBytes(ClientPrefix); /// /// The prefix used to represent a grain client. /// public static readonly GrainType ClientGrainType = GrainType.Create(ClientPrefix); /// /// The prefix for legacy grains. /// public const string LegacyGrainPrefix = SystemPrefix + "grain.v1."; /// /// A span representation of . /// public static readonly ReadOnlyMemory LegacyGrainPrefixBytes = Encoding.UTF8.GetBytes(LegacyGrainPrefix); /// /// Returns if the type is a client, if not. /// /// The grain type. /// if the type is a client, if not. public static bool IsClient(this in GrainType type) => type.AsSpan().StartsWith(ClientPrefixBytes.Span); /// /// Returns if the type is a system target, if not. /// /// The grain type. /// if the type is a system target, if not. public static bool IsSystemTarget(this in GrainType type) => type.AsSpan().StartsWith(SystemTargetPrefixBytes.Span); /// /// Returns if the type is a legacy grain, if not. /// /// The grain type. /// if the type is a legacy grain, if not. public static bool IsLegacyGrain(this in GrainType type) => type.AsSpan().StartsWith(LegacyGrainPrefixBytes.Span); /// /// Returns if the type is a grain service, if not. /// /// The grain type. /// if the type is a grain service, if not. public static bool IsGrainService(this in GrainType type) => type.AsSpan().StartsWith(GrainServicePrefixBytes.Span); /// /// Returns if the id represents a client, if not. /// /// The grain id. /// if the type is a client, if not. public static bool IsClient(this in GrainId id) => id.Type.IsClient(); /// /// Returns if the id represents a system target, if not. /// /// The grain id. /// if the type is a system target, if not. public static bool IsSystemTarget(this in GrainId id) => id.Type.IsSystemTarget(); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/GuidId.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// A unique identifier based on a . /// [Serializable] [Immutable] [GenerateSerializer] public sealed class GuidId : IEquatable, IComparable, ISerializable { private static readonly Interner guidIdInternCache = new Interner(InternerConstants.SIZE_LARGE); /// /// The underlying . /// [Id(0)] public readonly Guid Guid; /// /// Initializes a new instance of the class. /// /// /// The underlying . /// private GuidId(Guid guid) { this.Guid = guid; } /// /// Returns a new, randomly generated . /// /// A new, randomly generated . public static GuidId GetNewGuidId() { return FindOrCreateGuidId(Guid.NewGuid()); } /// /// Returns a instance corresponding to the provided . /// /// The guid. /// A instance corresponding to the provided . public static GuidId GetGuidId(Guid guid) { return FindOrCreateGuidId(guid); } /// /// Returns a instance corresponding to the provided . /// /// The . /// A instance corresponding to the provided . private static GuidId FindOrCreateGuidId(Guid guid) { return guidIdInternCache.FindOrCreate(guid, g => new GuidId(g)); } /// public int CompareTo(GuidId? other) => other is null ? 1 : Guid.CompareTo(other.Guid); /// public bool Equals(GuidId? other) => other is not null && Guid.Equals(other.Guid); /// public override bool Equals(object? obj) => Equals(obj as GuidId); /// public override int GetHashCode() => Guid.GetHashCode(); /// public override string ToString() => Guid.ToString(); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(GuidId? left, GuidId? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(GuidId? left, GuidId? right) => !(left == right); /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Guid", Guid, typeof(Guid)); } private GuidId(SerializationInfo info, StreamingContext context) { // ! This is an older pattern which is not compatible with nullable reference types. Guid = (Guid)info.GetValue("Guid", typeof(Guid))!; } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/IdSpan.cs ================================================ using System; using System.Runtime.Serialization; using System.Text; namespace Orleans.Runtime { /// /// Primitive type for identities, representing a sequence of bytes. /// [Serializable, GenerateSerializer, Immutable] public readonly struct IdSpan : IEquatable, IComparable, ISerializable, ISpanFormattable { /// /// The stable hash of the underlying value. /// [Id(0)] private readonly int _hashCode; /// /// The underlying value. /// [Id(1)] private readonly byte[]? _value; /// /// Initializes a new instance of the struct. /// /// /// The value. /// public IdSpan(byte[] value) { _value = value; _hashCode = (int)StableHash.ComputeHash(value); } /// /// Initializes a new instance of the struct. /// /// /// The value. /// /// /// The hash code of the value. /// private IdSpan(byte[]? value, int hashCode) { _value = value; _hashCode = hashCode; } /// /// Initializes a new instance of the struct. /// /// /// The serialization info. /// /// /// The context. /// private IdSpan(SerializationInfo info, StreamingContext context) { _value = (byte[]?)info.GetValue("v", typeof(byte[])); _hashCode = info.GetInt32("h"); } /// /// Gets the underlying value. /// public ReadOnlyMemory Value => _value; /// /// Gets a value indicating whether this instance is the default value. /// public bool IsDefault => _value is null || _value.Length == 0; /// /// Creates a new instance from the provided value. /// /// /// A new corresponding to the provided id. /// public static IdSpan Create(string id) => new(Encoding.UTF8.GetBytes(id)); /// /// Returns a span representation of this instance. /// /// /// A span representation of this instance. /// public ReadOnlySpan AsSpan() => _value; /// public override bool Equals(object? obj) => obj is IdSpan kind && Equals(kind); /// public bool Equals(IdSpan obj) { if (_value == obj._value) { return true; } if (_value is null || obj._value is null) { return false; } return _value.AsSpan().SequenceEqual(obj._value); } /// public override int GetHashCode() => _hashCode; /// /// Returns a uniform, stable hash code for an . /// /// /// The hash code of this instance. /// public uint GetUniformHashCode() => unchecked((uint)_hashCode); /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("v", _value); info.AddValue("h", _hashCode); } /// /// Creates an instance, specifying both the hash code and the value. /// /// /// This method is intended for use by serializers and other low-level libraries. /// /// /// The underlying value. /// /// /// The hash of the underlying value. /// /// /// An instance. /// public static IdSpan UnsafeCreate(byte[]? value, int hashCode) => new(value, hashCode); /// /// Gets the underlying array from this instance. /// /// The id span. /// The underlying array from this instance. public static byte[]? UnsafeGetArray(IdSpan id) => id._value; /// public int CompareTo(IdSpan other) { if (_value is null) { return other._value is null ? 0 : -1; } if (other._value is null) { return 1; } return _value.AsSpan().SequenceCompareTo(other._value.AsSpan()); } /// /// Returns a string representation of this instance, decoding the value as UTF8. /// /// /// A string representation fo this instance. /// public override string ToString() => _value is null ? "" : Encoding.UTF8.GetString(_value); public bool TryFormat(Span destination, out int charsWritten) { if (_value is null) { charsWritten = 0; return true; } var len = Encoding.UTF8.GetCharCount(_value); if (destination.Length < len) { charsWritten = 0; return false; } charsWritten = Encoding.UTF8.GetChars(_value, destination); return true; } string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => TryFormat(destination, out charsWritten); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(IdSpan left, IdSpan right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(IdSpan left, IdSpan right) => !left.Equals(right); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/IdSpanCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; namespace Orleans.Runtime; /// /// Functionality for serializing and deserializing instances. /// [RegisterSerializer] public sealed class IdSpanCodec : IFieldCodec { private readonly Type _codecType = typeof(IdSpan); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, IdSpan value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, _codecType, WireType.LengthPrefixed); var bytes = value.AsSpan(); if (bytes.IsEmpty) writer.WriteByte(1); // Equivalent to `writer.WriteVarUInt32(0);` else { writer.WriteVarUInt32((uint)(sizeof(int) + bytes.Length)); writer.WriteInt32(value.GetHashCode()); writer.Write(bytes); } } /// /// Writes an value to the provided writer without field framing. /// /// The writer. /// The value to write. /// The underlying buffer writer type. public static void WriteRaw( ref Writer writer, IdSpan value) where TBufferWriter : IBufferWriter { var bytes = value.AsSpan(); writer.WriteVarUInt32((uint)bytes.Length); if (!bytes.IsEmpty) { writer.WriteInt32(value.GetHashCode()); writer.Write(bytes); } } /// /// Reads an value from a reader without any field framing. /// /// The underlying reader input type. /// The reader. /// An . public static unsafe IdSpan ReadRaw(ref Reader reader) { var length = reader.ReadVarUInt32(); if (length == 0) return default; var hashCode = reader.ReadInt32(); var payloadArray = reader.ReadBytes(length); return IdSpan.UnsafeCreate(payloadArray, hashCode); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe IdSpan ReadValue(ref Reader reader, Field field) { field.EnsureWireType(WireType.LengthPrefixed); ReferenceCodec.MarkValueField(reader.Session); var length = reader.ReadVarUInt32(); if (length == 0) return default; var hashCode = reader.ReadInt32(); var payloadArray = reader.ReadBytes(length - sizeof(int)); return IdSpan.UnsafeCreate(payloadArray, hashCode); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/Legacy/LegacyGrainId.cs ================================================ using System; using System.Buffers; using System.Buffers.Text; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Text; namespace Orleans.Runtime { [Serializable, GenerateSerializer, Immutable] public sealed class LegacyGrainId : IEquatable, IComparable { private static readonly Interner grainIdInternCache = new Interner(InternerConstants.SIZE_LARGE); private static readonly Interner grainTypeInternCache = new Interner(); private static readonly Interner grainKeyInternCache = new Interner(); private static readonly ReadOnlyMemory ClientPrefixBytes = Encoding.UTF8.GetBytes(GrainTypePrefix.ClientPrefix + "."); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] [DataMember] [Id(0)] internal readonly UniqueKey Key; public UniqueKey.Category Category => Key.IdCategory; public bool IsSystemTarget => Key.IsSystemTargetKey; public bool IsGrain => Category == UniqueKey.Category.Grain || Category == UniqueKey.Category.KeyExtGrain; public bool IsClient => Category == UniqueKey.Category.Client; internal LegacyGrainId(UniqueKey key) { this.Key = key; } public static implicit operator GrainId(LegacyGrainId legacy) => legacy.ToGrainId(); public static LegacyGrainId NewId() { return FindOrCreateGrainId(UniqueKey.NewKey(Guid.NewGuid(), UniqueKey.Category.Grain)); } public static LegacyGrainId NewClientId() { return NewClientId(Guid.NewGuid()); } internal static LegacyGrainId NewClientId(Guid id) { return FindOrCreateGrainId(UniqueKey.NewKey(id, UniqueKey.Category.Client, 0)); } internal static LegacyGrainId GetGrainId(UniqueKey key) { return FindOrCreateGrainId(key); } // For testing only. internal static LegacyGrainId GetGrainIdForTesting(Guid guid) { return FindOrCreateGrainId(UniqueKey.NewKey(guid)); } internal static LegacyGrainId GetSystemTargetGrainId(long typeData) { return FindOrCreateGrainId(UniqueKey.NewEmptySystemTargetKey(typeData)); } internal static GrainType GetGrainType(long typeCode, bool isKeyExt) { return GetGrainType(isKeyExt ? UniqueKey.NewKey(0, UniqueKey.Category.KeyExtGrain, typeCode, "keyext") : UniqueKey.NewKey(0, UniqueKey.Category.Grain, typeCode)); } internal static LegacyGrainId GetGrainId(long typeCode, long primaryKey, string? keyExt = null) { return FindOrCreateGrainId(UniqueKey.NewKey(primaryKey, keyExt == null ? UniqueKey.Category.Grain : UniqueKey.Category.KeyExtGrain, typeCode, keyExt)); } internal static LegacyGrainId GetGrainId(long typeCode, Guid primaryKey, string? keyExt = null) { return FindOrCreateGrainId(UniqueKey.NewKey(primaryKey, keyExt == null ? UniqueKey.Category.Grain : UniqueKey.Category.KeyExtGrain, typeCode, keyExt)); } internal static LegacyGrainId GetGrainId(long typeCode, string primaryKey) { return FindOrCreateGrainId(UniqueKey.NewKey(0L, UniqueKey.Category.KeyExtGrain, typeCode, primaryKey)); } internal static LegacyGrainId GetGrainServiceGrainId(short id, int typeData) { return FindOrCreateGrainId(UniqueKey.NewGrainServiceKey(id, typeData)); } internal static LegacyGrainId GetGrainServiceGrainId(int typeData, string systemGrainId) { return FindOrCreateGrainId(UniqueKey.NewGrainServiceKey(systemGrainId, typeData)); } public Guid PrimaryKey { get { return GetPrimaryKey(); } } public long PrimaryKeyLong { get { return GetPrimaryKeyLong(); } } public string PrimaryKeyString { get { return GetPrimaryKeyString(); } } public string IdentityString { get { return ToDetailedString(); } } public bool IsLongKey { get { return Key.IsLongKey; } } public long GetPrimaryKeyLong(out string? keyExt) { return Key.PrimaryKeyToLong(out keyExt); } internal long GetPrimaryKeyLong() { return Key.PrimaryKeyToLong(); } public Guid GetPrimaryKey(out string? keyExt) { return Key.PrimaryKeyToGuid(out keyExt); } internal Guid GetPrimaryKey() { return Key.PrimaryKeyToGuid(); } internal string GetPrimaryKeyString() { _ = GetPrimaryKey(out string? key); return key ?? string.Empty; } public int TypeCode => Key.BaseTypeCode; private static GrainType GetGrainType(UniqueKey key) { return new GrainType(grainTypeInternCache.FindOrCreate(key, key => { var prefixBytes = key.IsSystemTargetKey ? GrainTypePrefix.SystemTargetPrefixBytes : key.IdCategory == UniqueKey.Category.Client ? ClientPrefixBytes : GrainTypePrefix.LegacyGrainPrefixBytes; return CreateGrainType(prefixBytes, key.TypeCodeData); })); } private static byte[] CreateGrainType(ReadOnlyMemory prefixBytes, ulong typeCode) { var prefix = prefixBytes.Span; var buf = new byte[prefix.Length + 16]; prefix.CopyTo(buf); Utf8Formatter.TryFormat(typeCode, buf.AsSpan(prefix.Length), out var len, new StandardFormat('X', 16)); Debug.Assert(len == 16); return buf; } public static GrainType CreateGrainTypeForGrain(int typeCode) { return new GrainType(CreateGrainType(GrainTypePrefix.LegacyGrainPrefixBytes, (ulong)typeCode)); } public static GrainType CreateGrainTypeForSystemTarget(int typeCode) { return new GrainType(CreateGrainType(GrainTypePrefix.SystemTargetPrefixBytes, (ulong)typeCode)); } private IdSpan GetGrainKey() { return new IdSpan(grainKeyInternCache.FindOrCreate(Key, k => Encoding.UTF8.GetBytes($"{k.N0:X16}{k.N1:X16}{(k.HasKeyExt ? "+" : null)}{k.KeyExt}"))); } public GrainId ToGrainId() { return new GrainId(GetGrainType(Key), this.GetGrainKey()); } public static bool TryConvertFromGrainId(GrainId id, [NotNullWhen(true)] out LegacyGrainId? legacyId) { legacyId = FromGrainIdInternal(id); return legacyId is not null; } public static LegacyGrainId FromGrainId(GrainId id) { return FromGrainIdInternal(id) ?? ThrowNotLegacyGrainId(id); } private static LegacyGrainId? FromGrainIdInternal(GrainId id) { var typeSpan = id.Type.AsSpan(); if (typeSpan.Length != GrainTypePrefix.LegacyGrainPrefix.Length + 16) return null; if (!typeSpan.StartsWith(GrainTypePrefix.LegacyGrainPrefixBytes.Span)) return null; typeSpan = typeSpan.Slice(GrainTypePrefix.LegacyGrainPrefix.Length, 16); if (!Utf8Parser.TryParse(typeSpan, out ulong typeCodeData, out var len, 'X') || len < 16) return null; string? keyExt = null; var keySpan = id.Key.Value.Span; if (keySpan.Length < 32) return null; if (!Utf8Parser.TryParse(keySpan[..16], out ulong n0, out len, 'X') || len < 16) return null; if (!Utf8Parser.TryParse(keySpan.Slice(16, 16), out ulong n1, out len, 'X') || len < 16) return null; if (keySpan.Length > 32) { if (keySpan[32] != '+') return null; keyExt = Encoding.UTF8.GetString(keySpan[33..]); } return FindOrCreateGrainId(UniqueKey.NewKey(n0, n1, typeCodeData, keyExt)); } private static LegacyGrainId ThrowNotLegacyGrainId(GrainId id) { throw new InvalidOperationException($"Cannot convert non-legacy id {id} into legacy id"); } private static LegacyGrainId FindOrCreateGrainId(UniqueKey key) { return grainIdInternCache.FindOrCreate(key, k => new LegacyGrainId(k)); } public bool Equals(LegacyGrainId? other) { return other != null && Key.Equals(other.Key); } public override bool Equals(object? obj) { var o = obj as LegacyGrainId; return o != null && Key.Equals(o.Key); } // Keep compiler happy -- it does not like classes to have Equals(...) without GetHashCode() methods public override int GetHashCode() { return Key.GetHashCode(); } /// /// Get a uniformly distributed hash code value for this grain, based on Jenkins Hash function. /// NOTE: Hash code value may be positive or NEGATIVE. /// /// Hash code for this LegacyGrainId public uint GetUniformHashCode() { return Key.GetUniformHashCode(); } public override string ToString() { return ToStringImpl(false); } // same as ToString, just full primary key and type code internal string ToDetailedString() { return ToStringImpl(true); } // same as ToString, just full primary key and type code private string ToStringImpl(bool detailed) { // TODO Get name of system/target grain + name of the grain type var keyString = Key.ToString(); // this should grab the least-significant half of n1, suffixing it with the key extension. string idString = keyString; if (!detailed) { if (keyString.Length >= 48) idString = keyString.Substring(24, 8) + keyString[48..]; else idString = keyString.Substring(24, 8); } string fullString; switch (Category) { case UniqueKey.Category.Grain: case UniqueKey.Category.KeyExtGrain: var typeString = TypeCode.ToString("X"); if (!detailed) typeString = typeString[Math.Max(0, typeString.Length - 8)..]; fullString = $"*grn/{typeString}/{idString}"; break; case UniqueKey.Category.Client: fullString = $"*cli/{idString}"; break; case UniqueKey.Category.SystemTarget: case UniqueKey.Category.KeyExtSystemTarget: fullString = $"*stg/{Key.N1}/{idString}"; break; case UniqueKey.Category.SystemGrain: fullString = $"*sgn/{Key.PrimaryKeyToGuid()}/{idString}"; break; default: fullString = "???/" + idString; break; } return detailed ? string.Format("{0}-0x{1, 8:X8}", fullString, GetUniformHashCode()) : fullString; } public static bool IsLegacyGrainType(Type type) { return typeof(IGrainWithGuidKey).IsAssignableFrom(type) || typeof(IGrainWithIntegerKey).IsAssignableFrom(type) || typeof(IGrainWithStringKey).IsAssignableFrom(type) || typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(type) || typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(type); } public static bool IsLegacyKeyExtGrainType(Type type) { return typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(type) || typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(type); } /// /// Return this LegacyGrainId in a standard string form, suitable for later use with the FromParsableString method. /// /// LegacyGrainId in a standard string format. internal string ToParsableString() { // NOTE: This function must be the "inverse" of FromParsableString, and data must round-trip reliably. return Key.ToHexString(); } /// /// Create a new LegacyGrainId object by parsing string in a standard form returned from ToParsableString method. /// /// String containing the LegacyGrainId info to be parsed. /// New LegacyGrainId object created from the input data. internal static LegacyGrainId FromParsableString(string grainId) { return FromParsableString(grainId.AsSpan()); } /// /// Create a new LegacyGrainId object by parsing string in a standard form returned from ToParsableString method. /// /// String containing the LegacyGrainId info to be parsed. /// New LegacyGrainId object created from the input data. internal static LegacyGrainId FromParsableString(ReadOnlySpan grainId) { // NOTE: This function must be the "inverse" of ToParsableString, and data must round-trip reliably. var key = UniqueKey.Parse(grainId); return FindOrCreateGrainId(key); } public uint GetHashCode_Modulo(uint umod) { int key = Key.GetHashCode(); int mod = (int)umod; key = ((key % mod) + mod) % mod; // key should be positive now. So assert with checked. return checked((uint)key); } public int CompareTo(LegacyGrainId? other) { if (other is null) return 1; // null is less than any non-null instance return Key.CompareTo(other.Key); } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/Legacy/UniqueKey.cs ================================================ using System; using System.Buffers.Binary; using System.Globalization; using System.IO; using System.Text; namespace Orleans.Runtime { [Serializable, GenerateSerializer, Immutable] [SuppressReferenceTracking] public sealed class UniqueKey : IComparable, IEquatable { /// /// Type id values encoded into UniqueKeys /// public enum Category : byte { None = 0, SystemTarget = 1, SystemGrain = 2, Grain = 3, Client = 4, KeyExtGrain = 6, // 7 was GeoClient KeyExtSystemTarget = 8, } [Id(0)] public ulong N0 { get; private set; } [Id(1)] public ulong N1 { get; private set; } [Id(2)] public ulong TypeCodeData { get; private set; } [Id(3)] public string? KeyExt { get; private set; } [NonSerialized] private uint uniformHashCache; public int BaseTypeCode => (int)TypeCodeData; public Category IdCategory => GetCategory(TypeCodeData); public bool IsLongKey => N0 == 0; public bool IsSystemTargetKey => IsSystemTarget(IdCategory); private static bool IsSystemTarget(Category category) => category == Category.SystemTarget || category == Category.KeyExtSystemTarget; public bool HasKeyExt => IsKeyExt(IdCategory); private static bool IsKeyExt(Category category) => category == Category.KeyExtGrain || category == Category.KeyExtSystemTarget; internal static readonly UniqueKey Empty = new UniqueKey(); internal static UniqueKey Parse(ReadOnlySpan input) { const int minimumValidKeyLength = 48; input = input.Trim(); if (input.Length >= minimumValidKeyLength) { var n0 = ulong.Parse(input[..16].ToString(), NumberStyles.AllowHexSpecifier); var n1 = ulong.Parse(input.Slice(16, 16).ToString(), NumberStyles.AllowHexSpecifier); var typeCodeData = ulong.Parse(input.Slice(32, 16).ToString(), NumberStyles.AllowHexSpecifier); string? keyExt = null; if (input.Length > minimumValidKeyLength) { if (input[48] != '+') throw new InvalidDataException("UniqueKey hex string missing + separator."); keyExt = input[49..].ToString(); } return NewKey(n0, n1, typeCodeData, keyExt); } // last, for convenience we attempt to parse the string using GUID syntax. this is needed by unit // tests but i don't know if it's needed for production. return NewKey(Guid.Parse(input.ToString())); } internal static UniqueKey NewKey(ulong n0, ulong n1, Category category, long typeData, string? keyExt) => NewKey(n0, n1, GetTypeCodeData(category, typeData), keyExt); internal static UniqueKey NewKey(long longKey, Category category = Category.None, long typeData = 0, string? keyExt = null) { ThrowIfIsSystemTargetKey(category); var key = NewKey(GetTypeCodeData(category, typeData), keyExt); key.N1 = (ulong)longKey; return key; } public static UniqueKey NewKey() => new UniqueKey { Guid = Guid.NewGuid() }; internal static UniqueKey NewKey(Guid guid) => new UniqueKey { Guid = guid }; internal static UniqueKey NewKey(Guid guid, Category category = Category.None, long typeData = 0, string? keyExt = null) { ThrowIfIsSystemTargetKey(category); var key = NewKey(GetTypeCodeData(category, typeData), keyExt); key.Guid = guid; return key; } internal static UniqueKey NewEmptySystemTargetKey(long typeData) => new UniqueKey { TypeCodeData = GetTypeCodeData(Category.SystemTarget, typeData) }; public static UniqueKey NewSystemTargetKey(Guid guid, long typeData) => new UniqueKey { Guid = guid, TypeCodeData = GetTypeCodeData(Category.SystemTarget, typeData) }; public static UniqueKey NewSystemTargetKey(short systemId) => new UniqueKey { N1 = (ulong)systemId, TypeCodeData = GetTypeCodeData(Category.SystemTarget) }; public static UniqueKey NewGrainServiceKey(short key, long typeData) => new UniqueKey { N1 = (ulong)key, TypeCodeData = GetTypeCodeData(Category.SystemTarget, typeData) }; public static UniqueKey NewGrainServiceKey(string key, long typeData) => NewKey(GetTypeCodeData(Category.KeyExtSystemTarget, typeData), key); internal static UniqueKey NewKey(ulong n0, ulong n1, ulong typeCodeData, string? keyExt) { var key = NewKey(typeCodeData, keyExt); key.N0 = n0; key.N1 = n1; return key; } private static UniqueKey NewKey(ulong typeCodeData, string? keyExt) { if (IsKeyExt(GetCategory(typeCodeData))) { if (string.IsNullOrWhiteSpace(keyExt)) throw keyExt is null ? new ArgumentNullException(nameof(keyExt)) : throw new ArgumentException("Extended key is empty or white space.", nameof(keyExt)); } else if (keyExt != null) throw new ArgumentException("Only key extended grains can specify a non-null key extension."); return new UniqueKey { TypeCodeData = typeCodeData, KeyExt = keyExt }; } private void ThrowIfIsNotLong() { if (!IsLongKey) throw new InvalidOperationException("this key cannot be interpreted as a long value"); } private static void ThrowIfIsSystemTargetKey(Category category) { if (IsSystemTarget(category)) throw new ArgumentException( "This overload of NewKey cannot be used to construct an instance of UniqueKey containing a SystemTarget id."); } private void ThrowIfHasKeyExt(string methodName) { if (KeyExt != null) throw new InvalidOperationException( string.Format( "This overload of {0} cannot be used if the grain uses the primary key extension feature.", methodName)); } public long PrimaryKeyToLong(out string? extendedKey) { ThrowIfIsNotLong(); extendedKey = this.KeyExt; return unchecked((long)N1); } public long PrimaryKeyToLong() { ThrowIfIsNotLong(); ThrowIfHasKeyExt("UniqueKey.PrimaryKeyToLong"); return (long)N1; } public Guid PrimaryKeyToGuid(out string? extendedKey) { extendedKey = this.KeyExt; return Guid; } public Guid PrimaryKeyToGuid() { ThrowIfHasKeyExt("UniqueKey.PrimaryKeyToGuid"); return Guid; } public override bool Equals(object? o) => o is UniqueKey key && Equals(key); // We really want Equals to be as fast as possible, as a minimum cost, as close to native as possible. // No function calls, no boxing, inline. public bool Equals(UniqueKey? other) { return other is not null && N0 == other.N0 && N1 == other.N1 && TypeCodeData == other.TypeCodeData && (KeyExt is null || KeyExt == other.KeyExt); } // We really want CompareTo to be as fast as possible, as a minimum cost, as close to native as possible. // No function calls, no boxing, inline. public int CompareTo(UniqueKey? other) { if (other is null) return 1; return TypeCodeData < other.TypeCodeData ? -1 : TypeCodeData > other.TypeCodeData ? 1 : N0 < other.N0 ? -1 : N0 > other.N0 ? 1 : N1 < other.N1 ? -1 : N1 > other.N1 ? 1 : KeyExt == null ? 0 : string.CompareOrdinal(KeyExt, other.KeyExt); } public override int GetHashCode() { return unchecked((int)GetUniformHashCode()); } internal uint GetUniformHashCode() { // Disabling this ReSharper warning; hashCache is a logically read-only variable, so accessing them in GetHashCode is safe. // ReSharper disable NonReadonlyFieldInGetHashCode if (uniformHashCache == 0) { if (KeyExt != null) { uniformHashCache = StableHash.ComputeHash(this.ToByteArray()); } else { Span data = stackalloc byte[24]; BinaryPrimitives.WriteUInt64LittleEndian(data, TypeCodeData); BinaryPrimitives.WriteUInt64LittleEndian(data[8..], N0); BinaryPrimitives.WriteUInt64LittleEndian(data[16..], N1); uniformHashCache = StableHash.ComputeHash(data); } } return uniformHashCache; // ReSharper restore NonReadonlyFieldInGetHashCode } /// /// If KeyExt not exists, returns following structure /// |8 bytes|8 bytes|8 bytes|4 bytes| - total 28 bytes. /// If KeyExt exists, adds additional KeyExt bytes length /// /// internal ReadOnlySpan ToByteArray() { var extBytes = this.KeyExt != null ? Encoding.UTF8.GetBytes(KeyExt) : null; var extBytesLength = extBytes?.Length ?? 0; var sizeWithoutExtBytes = sizeof(ulong) * 3 + sizeof(int); var spanBytes = new byte[sizeWithoutExtBytes + extBytesLength].AsSpan(); BinaryPrimitives.WriteUInt64LittleEndian(spanBytes, N0); BinaryPrimitives.WriteUInt64LittleEndian(spanBytes.Slice(8, 8), N1); BinaryPrimitives.WriteUInt64LittleEndian(spanBytes.Slice(16, 8), TypeCodeData); const int offset = sizeof(ulong) * 3; // Copy KeyExt if (extBytes != null) { BinaryPrimitives.WriteInt32LittleEndian(spanBytes.Slice(offset, sizeof(int)), extBytesLength); extBytes.CopyTo(spanBytes[(offset + sizeof(int))..]); } else { BinaryPrimitives.WriteInt32LittleEndian(spanBytes.Slice(offset, sizeof(int)), -1); } return spanBytes; } private unsafe Guid Guid { get { if (BitConverter.IsLittleEndian && sizeof(Guid) == 2 * sizeof(ulong)) { Guid value; ((ulong*)&value)[0] = N0; ((ulong*)&value)[1] = N1; return value; } return new Guid((uint)N0, (ushort)(N0 >> 32), (ushort)(N0 >> 48), (byte)N1, (byte)(N1 >> 8), (byte)(N1 >> 16), (byte)(N1 >> 24), (byte)(N1 >> 32), (byte)(N1 >> 40), (byte)(N1 >> 48), (byte)(N1 >> 56)); } set { if (BitConverter.IsLittleEndian && sizeof(Guid) == 2 * sizeof(ulong)) { N0 = ((ulong*)&value)[0]; N1 = ((ulong*)&value)[1]; } else { var guid = value.ToByteArray().AsSpan(); N0 = BinaryPrimitives.ReadUInt64LittleEndian(guid); N1 = BinaryPrimitives.ReadUInt64LittleEndian(guid[8..]); } } } public override string ToString() { return ToHexString(); } internal string ToHexString() { const string format = "{0:x16}{1:x16}{2:x16}"; return KeyExt is null ? string.Format(format, N0, N1, TypeCodeData) : string.Format(format + "+{3}", N0, N1, TypeCodeData, KeyExt); } internal string ToGrainKeyString() { string keyString; if (HasKeyExt) { string? extension; keyString = IsLongKey ? PrimaryKeyToLong(out extension).ToString() : PrimaryKeyToGuid(out extension).ToString(); keyString = $"{keyString}+{extension ?? string.Empty}"; } else { keyString = this.IsLongKey ? PrimaryKeyToLong().ToString() : this.PrimaryKeyToGuid().ToString(); } return keyString; } internal static Category GetCategory(ulong typeCodeData) { return (Category)((typeCodeData >> 56) & 0xFF); } private static ulong GetTypeCodeData(Category category, long typeData = 0) => ((ulong)category << 56) + ((ulong)typeData & 0x00FFFFFFFFFFFFFF); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/ObserverGrainId.cs ================================================ using System; namespace Orleans.Runtime { /// /// Identifies a client-side observer object. /// internal readonly struct ObserverGrainId : IEquatable, IComparable, ISpanFormattable { /// /// The separator between the client id portion of the observer id and the client-scoped observer id portion. /// internal const char SegmentSeparator = '+'; /// /// Initializes a new instance of the struct. /// private ObserverGrainId(GrainId grainId) { this.GrainId = grainId; } /// /// Gets the underlying . /// public readonly GrainId GrainId; /// /// Returns a new, random instance for the provided client id. /// /// /// The client id. /// /// /// A new, random instance for the provided client id. /// public static ObserverGrainId Create(ClientGrainId clientId) => Create(clientId, GrainIdKeyExtensions.CreateGuidKey(Guid.NewGuid())); /// /// Returns a new instance for the provided client id. /// /// /// The client id. /// /// /// The client-scoped observer id. /// /// /// A new instance for the provided client id. /// public static ObserverGrainId Create(ClientGrainId clientId, IdSpan scopedId) => new ObserverGrainId(ConstructGrainId(clientId, scopedId)); /// /// Returns if the provided instance represents an observer, if otherwise. /// /// /// The grain id. /// /// /// if the provided grain id is an observer id, otherwise . /// public static bool IsObserverGrainId(GrainId grainId) => grainId.IsClient() && grainId.Key.AsSpan().IndexOf((byte)SegmentSeparator) >= 0; /// /// Converts the provided to a . A return value indicates whether the operation succeeded. /// /// /// The grain id. /// /// /// The corresponding observer id. /// /// /// if the provided grain id is an observer id, otherwise . /// public static bool TryParse(GrainId grainId, out ObserverGrainId observerId) { if (!IsObserverGrainId(grainId)) { observerId = default; return false; } observerId = new ObserverGrainId(grainId); return true; } private static GrainId ConstructGrainId(ClientGrainId clientId, IdSpan scopedId) { var grain = clientId.GrainId.Key.AsSpan(); var scope = scopedId.AsSpan(); var buf = new byte[grain.Length + 1 + scope.Length]; grain.CopyTo(buf); buf[grain.Length] = (byte)SegmentSeparator; scope.CopyTo(buf.AsSpan(grain.Length + 1)); return GrainId.Create(clientId.GrainId.Type, new IdSpan(buf)); } /// public bool Equals(ObserverGrainId other) => this.GrainId.Equals(other.GrainId); /// public override bool Equals(object? obj) => obj is ObserverGrainId observer && this.Equals(observer); /// public override int GetHashCode() => this.GrainId.GetHashCode(); /// public override string ToString() => this.GrainId.ToString(); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => ((ISpanFormattable)GrainId).TryFormat(destination, out charsWritten, format, provider); /// public int CompareTo(ObserverGrainId other) => this.GrainId.CompareTo(other.GrainId); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/SiloAddress.cs ================================================ using System; using System.Buffers.Binary; using System.Buffers.Text; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace Orleans.Runtime { /// /// Data class encapsulating the details of silo addresses. /// [Serializable, Immutable] [JsonConverter(typeof(SiloAddressConverter))] [DebuggerDisplay("SiloAddress {ToString()}")] [SuppressReferenceTracking] public sealed class SiloAddress : IEquatable, IComparable, ISpanFormattable { [NonSerialized] private int hashCode; [NonSerialized] private bool hashCodeSet; [NonSerialized] private uint[]? uniformHashCache; /// /// Gets the endpoint. /// [Id(0)] public IPEndPoint Endpoint { get; } /// /// Gets the generation. /// [Id(1)] public int Generation { get; } [NonSerialized] private byte[]? utf8; private const char SEPARATOR = '@'; private static readonly object LastGenerationLock = new(); private static long LastGeneration = 0; private static readonly long epoch = new DateTime(2022, 1, 1).Ticks; private static readonly Interner<(IPAddress Address, int Port, int Generation), SiloAddress> siloAddressInterningCache = new(InternerConstants.SIZE_MEDIUM); /// Gets the special constant value which indicate an empty . public static SiloAddress Zero { get; } = New(new IPAddress(0), 0, 0); /// /// Factory for creating new SiloAddresses with specified IP endpoint address and silo generation number. /// /// IP endpoint address of the silo. /// Generation number of the silo. /// SiloAddress object initialized with specified address and silo generation. public static SiloAddress New(IPEndPoint ep, int gen) { return siloAddressInterningCache.FindOrCreate((ep.Address, ep.Port, gen), // Normalize endpoints (k, ep) => k.Address.IsIPv4MappedToIPv6 ? New(k.Address.MapToIPv4(), k.Port, k.Generation) : new(ep, k.Generation), ep); } /// /// Factory for creating new SiloAddresses with specified IP endpoint address and silo generation number. /// /// IP address of the silo. /// Port number /// Generation number of the silo. /// SiloAddress object initialized with specified address and silo generation. public static SiloAddress New(IPAddress address, int port, int generation) { return siloAddressInterningCache.FindOrCreate((address, port, generation), // Normalize endpoints k => k.Address.IsIPv4MappedToIPv6 ? New(k.Address.MapToIPv4(), k.Port, k.Generation) : new(new(k.Address, k.Port), k.Generation)); } /// /// Initializes a new instance of the class. /// /// The endpoint. /// The generation. private SiloAddress(IPEndPoint endpoint, int generation) { Endpoint = endpoint; Generation = generation; } /// /// Gets a value indicating whether this instance represents a client (versus a server). /// public bool IsClient { get { return Generation < 0; } } /// Allocate a new silo generation number. /// A new silo generation number. public static int AllocateNewGeneration() { long elapsed = (DateTime.UtcNow.Ticks - epoch) / TimeSpan.TicksPerSecond; // For tests which restart silos within 1 second, we need to ensure that the generation number is always increasing, // since generation for a restarting silo must be unique. lock (LastGenerationLock) { LastGeneration = elapsed = Math.Max(elapsed, LastGeneration + 1); } return unchecked((int)elapsed); // Unchecked to truncate any bits beyond the lower 32 } /// /// Return this SiloAddress in a standard string form, suitable for later use with the FromParsableString method. /// /// SiloAddress in a standard string format. public string ToParsableString() { // This must be the "inverse" of FromParsableString, and must be the same across all silos in a deployment. // Basically, this should never change unless the data content of SiloAddress changes if (utf8 != null) return Encoding.UTF8.GetString(utf8); return $"{new SpanFormattableIPAddress(Endpoint.Address)}:{Endpoint.Port}@{Generation}"; } /// /// Returns a UTF8-encoded representation of this instance as a byte array. /// /// A UTF8-encoded representation of this instance as a byte array. internal byte[] ToUtf8String() { if (utf8 is null) { Span chars = stackalloc char[45]; var addr = Endpoint.Address.TryFormat(chars, out var len) ? chars[..len] : Endpoint.Address.ToString().AsSpan(); var size = Encoding.UTF8.GetByteCount(addr); // Allocate sufficient room for: address + ':' + port + '@' + generation Span buf = stackalloc byte[size + 1 + 11 + 1 + 11]; size = Encoding.UTF8.GetBytes(addr, buf); buf[size++] = (byte)':'; var success = Utf8Formatter.TryFormat(Endpoint.Port, buf[size..], out len); Debug.Assert(success); Debug.Assert(len > 0); Debug.Assert(len <= 11); size += len; buf[size++] = (byte)SEPARATOR; success = Utf8Formatter.TryFormat(Generation, buf[size..], out len); Debug.Assert(success); Debug.Assert(len > 0); Debug.Assert(len <= 11); size += len; utf8 = buf[..size].ToArray(); } return utf8; } /// /// Create a new SiloAddress object by parsing string in a standard form returned from ToParsableString method. /// /// String containing the SiloAddress info to be parsed. /// New SiloAddress object created from the input data. public static SiloAddress FromParsableString(string addr) { // This must be the "inverse" of ToParsableString, and must be the same across all silos in a deployment. // Basically, this should never change unless the data content of SiloAddress changes // First is the IPEndpoint; then '@'; then the generation int atSign = addr.LastIndexOf(SEPARATOR); // IPEndpoint is the host, then ':', then the port int lastColon = addr.LastIndexOf(':', atSign - 1); if (atSign < 0 || lastColon < 0) throw new FormatException("Invalid string SiloAddress: " + addr); var host = IPAddress.Parse(addr.AsSpan(0, lastColon)); int port = int.Parse(addr.AsSpan(lastColon + 1, atSign - lastColon - 1), NumberStyles.None); var gen = int.Parse(addr.AsSpan(atSign + 1), NumberStyles.None); return New(host, port, gen); } /// /// Create a new SiloAddress object by parsing string in a standard form returned from ToParsableString method. /// /// String containing the SiloAddress info to be parsed. /// New SiloAddress object created from the input data. public static SiloAddress FromUtf8String(ReadOnlySpan addr) { // This must be the "inverse" of ToParsableString, and must be the same across all silos in a deployment. // Basically, this should never change unless the data content of SiloAddress changes // First is the IPEndpoint; then '@'; then the generation var atSign = addr.LastIndexOf((byte)SEPARATOR); if (atSign < 0) ThrowInvalidUtf8SiloAddress(addr); // IPEndpoint is the host, then ':', then the port var endpointSlice = addr[..atSign]; int lastColon = endpointSlice.LastIndexOf((byte)':'); if (lastColon < 0) ThrowInvalidUtf8SiloAddress(addr); var ipSlice = endpointSlice[..lastColon]; Span buf = stackalloc char[45]; var hostString = Encoding.UTF8.GetCharCount(ipSlice) is int len && len <= buf.Length ? buf[..Encoding.UTF8.GetChars(ipSlice, buf)] : Encoding.UTF8.GetString(ipSlice).AsSpan(); if (!IPAddress.TryParse(hostString, out var host)) ThrowInvalidUtf8SiloAddress(addr); var portSlice = endpointSlice[(lastColon + 1)..]; if (!Utf8Parser.TryParse(portSlice, out int port, out len) || len < portSlice.Length) ThrowInvalidUtf8SiloAddress(addr); var genSlice = addr[(atSign + 1)..]; if (!Utf8Parser.TryParse(genSlice, out int generation, out len) || len < genSlice.Length) ThrowInvalidUtf8SiloAddress(addr); return New(host, port, generation); } [DoesNotReturn] private static void ThrowInvalidUtf8SiloAddress(ReadOnlySpan addr) => throw new FormatException("Invalid string SiloAddress: " + Encoding.UTF8.GetString(addr)); /// /// Return a long string representation of this SiloAddress. /// /// /// Note: This string value is not comparable with the method -- use the method for that purpose. /// /// String representation of this SiloAddress. public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { if (!destination.TryWrite($"{(IsClient ? 'C' : 'S')}{new SpanFormattableIPEndPoint(Endpoint)}:{Generation}", out charsWritten)) return false; if (format.Length == 1 && format[0] == 'H') { if (!destination[charsWritten..].TryWrite($"/x{GetConsistentHashCode():X8}", out var len)) return false; charsWritten += len; } return true; } /// /// Return a long string representation of this SiloAddress, including it's consistent hash value. /// /// /// Note: This string value is not comparable with the FromParsableString method -- use the ToParsableString method for that purpose. /// /// String representation of this SiloAddress. public string ToStringWithHashCode() => $"{this:H}"; /// public override bool Equals(object? obj) => Equals(obj as SiloAddress); /// public override int GetHashCode() => Endpoint.GetHashCode() ^ Generation; /// Returns a consistent hash value for this silo address. /// Consistent hash value for this silo address. public int GetConsistentHashCode() => hashCodeSet ? hashCode : CalculateConsistentHashCode(); /// Returns a consistent hash value for this silo address. /// Consistent hash value for this silo address. internal int GetConsistentHashCode(int seed) { var tmp = (0, 0L, 0L, 0L); // avoid stackalloc overhead by using a fixed size buffer var buf = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref tmp, 1))[..28]; Endpoint.Address.TryWriteBytes(buf, out var len); Debug.Assert(len is 4 or 16); BinaryPrimitives.WriteInt32LittleEndian(buf[16..], Endpoint.Port); BinaryPrimitives.WriteInt32LittleEndian(buf[20..], Generation); BinaryPrimitives.WriteInt32LittleEndian(buf[24..], seed); return (int)StableHash.ComputeHash(buf); } private int CalculateConsistentHashCode() { var tmp = (0L, 0L, 0L); // avoid stackalloc overhead by using a fixed size buffer var buf = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref tmp, 1))[..24]; Endpoint.Address.TryWriteBytes(buf, out var len); Debug.Assert(len is 4 or 16); BinaryPrimitives.WriteInt32LittleEndian(buf[16..], Endpoint.Port); BinaryPrimitives.WriteInt32LittleEndian(buf[20..], Generation); hashCode = (int)StableHash.ComputeHash(buf); hashCodeSet = true; return hashCode; } internal void InternalSetConsistentHashCode(int hashCode) { this.hashCode = hashCode; this.hashCodeSet = true; } /// /// Returns a collection of uniform hash codes variants for this instance. /// /// The number of hash codes to return. /// A collection of uniform hash codes variants for this instance. public uint[] GetUniformHashCodes(int numHashes) { var cache = uniformHashCache; if (cache is not null && cache.Length == numHashes) return cache; return uniformHashCache = GetUniformHashCodesImpl(numHashes); } private uint[] GetUniformHashCodesImpl(int numHashes) { Span bytes = stackalloc byte[16 + sizeof(int) + sizeof(int) + sizeof(int)]; // ip + port + generation + extraBit // Endpoint IP Address var address = Endpoint.Address; if (address.AddressFamily == AddressFamily.InterNetwork) // IPv4 { #pragma warning disable CS0618 // Type or member is obsolete BinaryPrimitives.WriteInt32LittleEndian(bytes[12..], (int)address.Address); #pragma warning restore CS0618 bytes[..12].Clear(); } else // IPv6 { address.TryWriteBytes(bytes, out var len); Debug.Assert(len == 16); } var offset = 16; // Port BinaryPrimitives.WriteInt32LittleEndian(bytes[offset..], Endpoint.Port); offset += sizeof(int); // Generation BinaryPrimitives.WriteInt32LittleEndian(bytes[offset..], Generation); offset += sizeof(int); var hashes = new uint[numHashes]; for (int extraBit = 0; extraBit < numHashes; extraBit++) { BinaryPrimitives.WriteInt32LittleEndian(bytes[offset..], extraBit); hashes[extraBit] = StableHash.ComputeHash(bytes); } return hashes; } /// /// Two silo addresses match if they are equal or if one generation or the other is 0. /// /// The other SiloAddress to compare this one with. /// Returns true if the two SiloAddresses are considered to match -- if they are equal or if one generation or the other is 0. internal bool Matches([NotNullWhen(true)] SiloAddress? other) { return other != null && Endpoint.Address.Equals(other.Endpoint.Address) && Endpoint.Port == other.Endpoint.Port && (Generation == other.Generation || Generation == 0 || other.Generation == 0); } /// public bool Equals([NotNullWhen(true)] SiloAddress? other) => other != null && Generation == other.Generation && Endpoint.Address.Equals(other.Endpoint.Address) && Endpoint.Port == other.Endpoint.Port; /// /// Returns if the provided value represents the same logical server as this value, otherwise . /// /// /// The other instance. /// /// /// if the provided value represents the same logical server as this value, otherwise . /// internal bool IsSameLogicalSilo([NotNullWhen(true)] SiloAddress? other) => other != null && Endpoint.Address.Equals(other.Endpoint.Address) && Endpoint.Port == other.Endpoint.Port; /// /// Returns if the provided value represents the same logical server as this value and is a successor to this server, otherwise . /// /// /// The other instance. /// /// /// if the provided value represents the same logical server as this value and is a successor to this server, otherwise . /// public bool IsSuccessorOf(SiloAddress other) => IsSameLogicalSilo(other) && other.Generation > 0 && Generation > other.Generation; /// /// Returns if the provided value represents the same logical server as this value and is a predecessor to this server, otherwise . /// /// /// The other instance. /// /// /// if the provided value represents the same logical server as this value and is a predecessor to this server, otherwise . /// public bool IsPredecessorOf(SiloAddress other) => IsSameLogicalSilo(other) && Generation > 0 && Generation < other.Generation; /// public int CompareTo(SiloAddress? other) { if (other == null) return 1; // Compare Generation first. It gives a cheap and fast way to compare, avoiding allocations // and is also semantically meaningful - older silos (with smaller Generation) will appear first in the comparison order. // Only if Generations are the same, go on to compare Ports and IPAddress (which is more expansive to compare). // Alternatively, we could compare ConsistentHashCode or UniformHashCode. int comp = Generation.CompareTo(other.Generation); if (comp != 0) return comp; comp = Endpoint.Port.CompareTo(other.Endpoint.Port); if (comp != 0) return comp; return CompareIpAddresses(Endpoint.Address, other.Endpoint.Address); } // The comparison code is taken from: http://www.codeproject.com/Articles/26550/Extending-the-IPAddress-object-to-allow-relative-c // Also note that this comparison does not handle semantic equivalence of IPv4 and IPv6 addresses. // In particular, 127.0.0.1 and::1 are semantically the same, but not syntactically. // For more information refer to: http://stackoverflow.com/questions/16618810/compare-ipv4-addresses-in-ipv6-notation // and http://stackoverflow.com/questions/22187690/ip-address-class-getaddressbytes-method-putting-octets-in-odd-indices-of-the-byt // and dual stack sockets, described at https://msdn.microsoft.com/en-us/library/system.net.ipaddress.maptoipv6(v=vs.110).aspx private static int CompareIpAddresses(IPAddress one, IPAddress two) { var f1 = one.AddressFamily; var f2 = two.AddressFamily; if (f1 != f2) return f1 < f2 ? -1 : 1; if (f1 == AddressFamily.InterNetwork) { #pragma warning disable CS0618 // Type or member is obsolete return one.Address.CompareTo(two.Address); #pragma warning restore CS0618 } Span b1 = stackalloc byte[16]; one.TryWriteBytes(b1, out var len); Debug.Assert(len == 16); Span b2 = stackalloc byte[16]; two.TryWriteBytes(b2, out len); Debug.Assert(len == 16); return b1.SequenceCompareTo(b2); } } /// /// Functionality for converting instances to and from their JSON representation. /// public sealed class SiloAddressConverter : JsonConverter { /// public override SiloAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetString() is { } str ? SiloAddress.FromParsableString(str) : null; /// public override void Write(Utf8JsonWriter writer, SiloAddress value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToUtf8String()); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/SiloAddressCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Runtime.Serialization { /// /// Serializer and deserializer for instances. /// [RegisterSerializer] public sealed class SiloAddressCodec : IFieldCodec { /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, SiloAddress? value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, typeof(SiloAddress)); IPAddressCodec.WriteField(ref writer, 0, value.Endpoint.Address); uint delta = 2; if (value.Endpoint.Port != 0) UInt16Codec.WriteField(ref writer, delta = 1, (ushort)value.Endpoint.Port); if (value.Generation != 0) Int32Codec.WriteField(ref writer, delta, value.Generation); writer.WriteEndObject(); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SiloAddress ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); Field header = default; int port = 0, generation = 0; reader.ReadFieldHeader(ref header); if (!header.HasFieldId || header.FieldIdDelta != 0) throw new RequiredFieldMissingException("Serialized SiloAddress is missing its address field."); var address = IPAddressCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (!header.IsEndBaseOrEndObject) { var id = header.FieldIdDelta; if (id == 1) { port = UInt16Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.HasFieldId) id += header.FieldIdDelta; } if (id == 2) { generation = Int32Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } reader.ConsumeEndBaseOrEndObject(ref header); } return SiloAddress.New(address, port, generation); } } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/StableHash.cs ================================================ using System; using System.Buffers.Binary; using System.IO.Hashing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace Orleans { public static class StableHash { /// /// Computes a hash digest of the input. /// /// /// The input data. /// /// /// A hash digest of the input. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe uint ComputeHash(ReadOnlySpan data) { uint hash; XxHash32.TryHash(data, new Span((byte*)&hash, sizeof(uint)), out _); return BitConverter.IsLittleEndian ? hash : BinaryPrimitives.ReverseEndianness(hash); } /// /// Computes a hash digest of the input. /// /// /// The input data. /// /// /// A hash digest of the input. /// public static uint ComputeHash(string data) => ComputeHash(BitConverter.IsLittleEndian ? MemoryMarshal.AsBytes(data.AsSpan()) : Encoding.Unicode.GetBytes(data)); } } ================================================ FILE: src/Orleans.Core.Abstractions/IDs/SystemTargetGrainId.cs ================================================ using System; using System.Buffers; using System.Buffers.Text; using System.Diagnostics; using System.Text; namespace Orleans.Runtime { /// /// Identifies a system target. /// [Immutable] public readonly struct SystemTargetGrainId : IEquatable, IComparable, ISpanFormattable { private const char SegmentSeparator = '+'; private readonly GrainId _grainId; /// /// Initializes a new instance of the struct. /// /// /// The grain id. /// private SystemTargetGrainId(GrainId grainId) => _grainId = grainId; /// /// Gets the underlying identity. /// public GrainId GrainId => _grainId; /// /// Creates a new instance. /// /// /// The grain type. /// /// /// The server which the system target exists on. /// /// /// A . /// public static SystemTargetGrainId Create(GrainType kind, SiloAddress address) => new SystemTargetGrainId(new GrainId(kind, new IdSpan(address.ToUtf8String()))); /// /// Creates a new instance. /// /// /// The grain type. /// /// /// The server which the system target exists on. /// /// /// An optional key extension. /// /// /// A . /// public static SystemTargetGrainId Create(GrainType kind, SiloAddress address, string? extraIdentifier) { var addr = address.ToUtf8String(); if (extraIdentifier is not null) { var extraLen = Encoding.UTF8.GetByteCount(extraIdentifier); var buf = new byte[addr.Length + 1 + extraLen]; addr.CopyTo(buf.AsSpan()); buf[addr.Length] = (byte)SegmentSeparator; Encoding.UTF8.GetBytes(extraIdentifier, 0, extraIdentifier.Length, buf, addr.Length + 1); addr = buf; } return new SystemTargetGrainId(new GrainId(kind, new IdSpan(addr))); } /// /// Returns if the provided instance represents a system target, if otherwise. /// /// /// The grain id. /// /// /// if the value is a system target grain id, otherwise. /// public static bool IsSystemTargetGrainId(in GrainId id) => id.Type.AsSpan().StartsWith(GrainTypePrefix.SystemTargetPrefixBytes.Span); /// /// Converts the provided to a . A return value indicates whether the operation succeeded. /// /// /// The grain id. /// /// /// The resulting system target id. /// /// /// if the value is a system target grain id, otherwise. /// public static bool TryParse(GrainId grainId, out SystemTargetGrainId systemTargetId) { if (!IsSystemTargetGrainId(grainId)) { systemTargetId = default; return false; } systemTargetId = new SystemTargetGrainId(grainId); return true; } /// /// Returns a new targeting the provided address. /// /// /// The silo address. /// /// /// A new targeting the provided address. /// public SystemTargetGrainId WithSiloAddress(SiloAddress siloAddress) { var addr = siloAddress.ToUtf8String(); var key = _grainId.Key.AsSpan(); if (key.IndexOf((byte)SegmentSeparator) is int index && index >= 0) { var extraIdentifier = key[(index + 1)..]; var buf = new byte[addr.Length + 1 + extraIdentifier.Length]; addr.CopyTo(buf.AsSpan()); buf[addr.Length] = (byte)SegmentSeparator; extraIdentifier.CopyTo(buf.AsSpan(addr.Length + 1)); addr = buf; } return new SystemTargetGrainId(new GrainId(_grainId.Type, new IdSpan(addr))); } /// /// Gets the of the system target. /// /// /// The silo address corresponding to this system target id. /// public SiloAddress GetSiloAddress() { var key = _grainId.Key.AsSpan(); if (key.IndexOf((byte)SegmentSeparator) is int index && index >= 0) { key = key[..index]; } return SiloAddress.FromUtf8String(key); } /// /// Creates a for a grain service. /// /// /// The type code. /// /// /// The system id. /// /// /// The silo address. /// /// A grain id for a grain service instance. public static GrainId CreateGrainServiceGrainId(int typeCode, string grainSystemId, SiloAddress address) => CreateGrainServiceGrainId(CreateGrainServiceGrainType(typeCode, grainSystemId), address); /// /// Creates a for a grain service. /// /// /// The type code. /// /// /// The system id. /// /// A grain id for a grain service instance. internal static GrainType CreateGrainServiceGrainType(int typeCode, string? grainSystemId) { var extraLen = grainSystemId is null ? 0 : Encoding.UTF8.GetByteCount(grainSystemId); var buf = new byte[GrainTypePrefix.GrainServicePrefix.Length + 8 + extraLen]; GrainTypePrefix.GrainServicePrefixBytes.Span.CopyTo(buf); Utf8Formatter.TryFormat(typeCode, buf.AsSpan(GrainTypePrefix.GrainServicePrefix.Length), out var len, new StandardFormat('X', 8)); Debug.Assert(len == 8); if (grainSystemId != null) Encoding.UTF8.GetBytes(grainSystemId, 0, grainSystemId.Length, buf, buf.Length - extraLen); return new GrainType(buf); } /// /// Creates a for a grain service. /// /// /// The grain type. /// /// /// The silo address. /// /// A grain id for a grain service instance. internal static GrainId CreateGrainServiceGrainId(GrainType grainType, SiloAddress address) => new GrainId(grainType, new IdSpan(address.ToUtf8String())); /// /// Creates a system target with the provided name. /// /// /// The system target grain type name. /// /// /// The grain type. /// public static GrainType CreateGrainType(string name) => GrainType.Create($"{GrainTypePrefix.SystemTargetPrefix}{name}"); /// public bool Equals(SystemTargetGrainId other) => _grainId.Equals(other._grainId); /// public override bool Equals(object? obj) => obj is SystemTargetGrainId observer && this.Equals(observer); /// public override int GetHashCode() => _grainId.GetHashCode(); /// public override string ToString() => _grainId.ToString(); string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => ((ISpanFormattable)_grainId).TryFormat(destination, out charsWritten, format, provider); /// public int CompareTo(SystemTargetGrainId other) => _grainId.CompareTo(other._grainId); /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(SystemTargetGrainId left, SystemTargetGrainId right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(SystemTargetGrainId left, SystemTargetGrainId right) => !(left == right); /// /// Compares the provided operands and returns if the left operand is less than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than the right operand, otherwise . public static bool operator <(SystemTargetGrainId left, SystemTargetGrainId right) => left.CompareTo(right) < 0; /// /// Compares the provided operands and returns if the left operand is less than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than or equal to the right operand, otherwise . public static bool operator <=(SystemTargetGrainId left, SystemTargetGrainId right) => left.CompareTo(right) <= 0; /// /// Compares the provided operands and returns if the left operand is greater than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than the right operand, otherwise . public static bool operator >(SystemTargetGrainId left, SystemTargetGrainId right) => left.CompareTo(right) > 0; /// /// Compares the provided operands and returns if the left operand is greater than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than or equal to the right operand, otherwise . public static bool operator >=(SystemTargetGrainId left, SystemTargetGrainId right) => left.CompareTo(right) >= 0; } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/IGrainLifecycle.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Orleans.Runtime { /// /// The observable grain lifecycle. /// /// /// This type is usually used as the generic parameter in as /// a means of participating in the lifecycle stages of a grain activation. /// public interface IGrainLifecycle : ILifecycleObservable { /// /// Registers a grain migration participant. /// /// The participant. void AddMigrationParticipant(IGrainMigrationParticipant participant); /// /// Unregisters a grain migration participant. /// /// The participant. void RemoveMigrationParticipant(IGrainMigrationParticipant participant); } public interface IGrainMigrationParticipant { /// /// Called on the original activation when migration is initiated, after completes. /// The participant can access and update the dehydration context. /// /// The dehydration context. void OnDehydrate(IDehydrationContext dehydrationContext); /// /// Called on the new activation after a migration, before is called. /// The participant can restore state from the migration context. /// /// The rehydration context. void OnRehydrate(IRehydrationContext rehydrationContext); } /// /// Records the state of a grain activation which is in the process of being dehydrated for migration to another location. /// public interface IDehydrationContext { /// /// Gets the keys in the context. /// IEnumerable Keys { get; } /// /// Adds a sequence of bytes to the dehydration context, associating the sequence with the provided key. /// /// The key. /// The value. void AddBytes(string key, ReadOnlySpan value); /// /// Adds a sequence of bytes to the dehydration context, associating the sequence with the provided key. /// /// The key. /// A delegate used to write the provided value to the context. /// The value to provide to . void AddBytes(string key, Action> valueWriter, T value); /// /// Attempts to a value to the dehydration context, associated with the provided key, serializing it using . /// If a serializer is found for the value, and the key has not already been added, then the value is added and the method returns . /// If no serializer exists or the key has already been added, then the value is not added and the method returns . /// /// The key. /// The value to add. bool TryAddValue(string key, T? value); } /// /// Contains the state of a grain activation which is in the process of being rehydrated after moving from another location. /// public interface IRehydrationContext { /// /// Gets the keys in the context. /// IEnumerable Keys { get; } /// /// Tries to get a sequence of bytes from the rehydration context, associated with the provided key. /// /// The key. /// The value, if present. /// if the key exists in the context, otherwise . bool TryGetBytes(string key, out ReadOnlySequence value); /// /// Tries to get a value from the rehydration context, associated with the provided key, deserializing it using . /// If a serializer is found for the value, and the key is present, then the value is deserialized and the method returns . /// If no serializer exists or the key has already been added, then the value is not added and the method returns . /// /// The key. /// The value, if present. /// if the key exists in the context, otherwise . bool TryGetValue(string key, [NotNullWhen(true)] out T? value); } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/ILifecycleObservable.cs ================================================ using System; namespace Orleans { /// /// Observable lifecycle. /// Each stage of the lifecycle is observable. All observers will be notified when the stage is reached when starting, and stopping. /// Stages are started in ascending order, and stopped in descending order. /// public interface ILifecycleObservable { /// /// Subscribe for notification when a stage is reached while starting or stopping. /// /// The name of the observer, for reporting purposes. /// The stage of to subscribe to. /// The observer. /// A disposable that can be disposed of to unsubscribe. IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer); } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/ILifecycleObserver.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace Orleans { /// /// Lifecycle observer used to handle start and stop notification. /// public interface ILifecycleObserver { /// /// Handle start notifications. /// /// /// The cancellation token which indicates that the operation should be aborted promptly when it is canceled. /// /// /// A which represents the operation. /// Task OnStart(CancellationToken cancellationToken = default); /// /// Handle stop notifications. /// /// /// The cancellation token which indicates that the operation should be stopped promptly when it is canceled. /// /// /// A which represents the operation. /// Task OnStop(CancellationToken cancellationToken = default); } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/ILifecycleParticipant.cs ================================================ namespace Orleans { /// /// Provides hook to take part in lifecycle. /// Also may act as a signal interface indicating that an object can take part in lifecycle. /// /// /// The type of lifecycle being observed. /// public interface ILifecycleParticipant where TLifecycleObservable : ILifecycleObservable { /// /// Adds the provided observer as a participant in the lifecycle. /// /// /// The observer. /// void Participate(TLifecycleObservable lifecycle); } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/ILifecycleSubject.cs ================================================  namespace Orleans { /// /// Both a lifecycle observer and observable lifecycle. /// public interface ILifecycleSubject : ILifecycleObservable, ILifecycleObserver { } } ================================================ FILE: src/Orleans.Core.Abstractions/Lifecycle/LifecycleExtensions.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans { /// /// Extensions for working with lifecycle observers. /// public static class LifecycleExtensions { /// /// Creates a disposable subscription to the lifecycle. /// /// The lifecycle observable. /// The name of the observer. /// The stage to participate in. /// The delegate called when starting the specified lifecycle stage. /// The delegate to be called when stopping the specified lifecycle stage. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, string observerName, int stage, Func onStart, Func? onStop) { if (observable is null) { throw new ArgumentNullException(nameof(observable)); } if (onStart is null) { throw new ArgumentNullException(nameof(onStart)); } if (onStop is null) { return StartupObserver.Create(observable, observerName, stage, onStart); } return observable.Subscribe(observerName, stage, new Observer(onStart, onStop)); } /// /// Creates a disposable subscription to the lifecycle. /// /// The lifecycle observable. /// The name of the observer. /// The stage to participate in. /// The delegate called when starting the specified lifecycle stage. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, string observerName, int stage, Func onStart) { return observable.Subscribe(observerName, stage, onStart, null); } /// /// Creates a disposable subscription to the lifecycle. /// /// /// The observer type, used for diagnostics. /// /// The lifecycle observable. /// The stage to participate in. /// The observer. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, int stage, ILifecycleObserver observer) { return observable.Subscribe(GetTypeName(typeof(TObserver)), stage, observer); } /// /// Creates a disposable subscription to the lifecycle. /// /// /// The observer type, used for diagnostics. /// /// The lifecycle observable. /// The stage to participate in. /// The delegate called when starting the specified lifecycle stage. /// Teh delegate to be called when stopping the specified lifecycle stage. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, int stage, Func onStart, Func onStop) { return observable.Subscribe(GetTypeName(typeof(TObserver)), stage, onStart, onStop); } /// /// Creates a disposable subscription to the lifecycle. /// /// /// The observer type, used for diagnostics. /// /// The lifecycle observable. /// The stage to participate in. /// The delegate called when starting the specified lifecycle stage. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, int stage, Func onStart) { return observable.Subscribe(GetTypeName(typeof(TObserver)), stage, onStart, null); } /// /// Creates a disposable subscription to the lifecycle. /// /// The lifecycle observable. /// The stage to participate in. /// The observer. /// A instance which can be disposed to unsubscribe the observer from the lifecycle. public static IDisposable Subscribe(this ILifecycleObservable observable, int stage, ILifecycleObserver observer) { return observable.Subscribe(GetTypeName(observer.GetType()), stage, observer); } private class Observer : ILifecycleObserver { private readonly Func onStart; private readonly Func onStop; public Observer(Func onStart, Func onStop) { this.onStart = onStart; this.onStop = onStop; } public Task OnStart(CancellationToken ct) => this.onStart(ct); public Task OnStop(CancellationToken ct) => this.onStop(ct); } private sealed class StartupObserver : ILifecycleObserver { private readonly Func _onStart; private readonly IDisposable _registration; private StartupObserver(ILifecycleObservable observable, string observerName, int stage, Func onStart) { _onStart = onStart; _registration = observable.Subscribe(observerName, stage, this); } public static IDisposable Create(ILifecycleObservable observable, string observerName, int stage, Func onStart) { var observer = new StartupObserver(observable, observerName, stage, onStart); return observer._registration; } public Task OnStart(CancellationToken ct) { var task = _onStart(ct); _registration?.Dispose(); return task; } public Task OnStop(CancellationToken ct) => Task.CompletedTask; } private static string GetTypeName(Type type) => type.FullName ?? type.Name; } } ================================================ FILE: src/Orleans.Core.Abstractions/Logging/ErrorCodes.cs ================================================ // ReSharper disable InconsistentNaming namespace Orleans { /// /// The set of error codes used by the Orleans runtime libraries for logging errors. /// public enum ErrorCode { Runtime = 100000, Runtime_Error_100001 = Runtime + 1, Runtime_Error_100002 = Runtime + 2, Runtime_Error_100003 = Runtime + 3, Runtime_Error_100004 = Runtime + 4, Runtime_Error_100005 = Runtime + 5, Runtime_Error_100006 = Runtime + 6, Runtime_Error_100007 = Runtime + 7, Runtime_Error_100008 = Runtime + 8, Runtime_Error_100009 = Runtime + 9, Runtime_Error_100010 = Runtime + 10, Runtime_Error_100011 = Runtime + 11, Runtime_Error_100012 = Runtime + 12, Runtime_Error_100013 = Runtime + 13, Runtime_Error_100014 = Runtime + 14, Runtime_Error_100015 = Runtime + 15, Runtime_Error_100016 = Runtime + 16, Runtime_Error_100017 = Runtime + 17, Runtime_Error_100018 = Runtime + 18, Runtime_Error_100019 = Runtime + 19, Runtime_Error_100020 = Runtime + 20, Runtime_Error_100021 = Runtime + 21, Runtime_Error_100022 = Runtime + 22, Runtime_Error_100023 = Runtime + 23, Runtime_Error_100024 = Runtime + 24, Runtime_Error_100025 = Runtime + 25, Runtime_Error_100026 = Runtime + 26, Runtime_Error_100027 = Runtime + 27, Runtime_Error_100028 = Runtime + 28, Runtime_Error_100029 = Runtime + 29, Runtime_Error_100030 = Runtime + 30, Runtime_Error_100031 = Runtime + 31, Runtime_Error_100032 = Runtime + 32, Runtime_Error_100033 = Runtime + 33, Runtime_Error_100034 = Runtime + 34, Runtime_Error_100035 = Runtime + 35, Runtime_Error_100036 = Runtime + 36, Runtime_Error_100037 = Runtime + 37, //Runtime_Error_100038 = Runtime + 38, Runtime_Error_100039 = Runtime + 39, Runtime_Error_100040 = Runtime + 40, Runtime_Error_100041 = Runtime + 41, Runtime_Error_100042 = Runtime + 42, Runtime_Error_100043 = Runtime + 43, Runtime_Error_100044 = Runtime + 44, Runtime_Error_100045 = Runtime + 45, Runtime_Error_100046 = Runtime + 46, Runtime_Error_100047 = Runtime + 47, Runtime_Error_100048 = Runtime + 48, Runtime_Error_100049 = Runtime + 49, Runtime_Error_100050 = Runtime + 50, Runtime_Error_100051 = Runtime + 51, Runtime_Error_100052 = Runtime + 52, Runtime_Error_100053 = Runtime + 53, Runtime_Error_100054 = Runtime + 54, Runtime_Error_100055 = Runtime + 55, Runtime_Error_100056 = Runtime + 56, Runtime_Error_100057 = Runtime + 57, Runtime_Error_100058 = Runtime + 58, Runtime_Error_100059 = Runtime + 59, Runtime_Error_100060 = Runtime + 60, Runtime_Error_100061 = Runtime + 61, Runtime_Error_100062 = Runtime + 62, Runtime_Error_100063 = Runtime + 63, Runtime_Error_100064 = Runtime + 64, Runtime_Error_100065 = Runtime + 65, Runtime_Error_100066 = Runtime + 66, Runtime_Error_100067 = Runtime + 67, Runtime_Error_100068 = Runtime + 68, Runtime_Error_100069 = Runtime + 69, Runtime_Error_100070 = Runtime + 70, Runtime_Error_100071 = Runtime + 71, Runtime_Error_100072 = Runtime + 72, Runtime_Error_100073 = Runtime + 73, Runtime_Error_100074 = Runtime + 74, Runtime_Error_100075 = Runtime + 75, Runtime_Error_100076 = Runtime + 76, Runtime_Error_100077 = Runtime + 77, Runtime_Error_100078 = Runtime + 78, Runtime_Error_100079 = Runtime + 79, Runtime_Error_100080 = Runtime + 80, Runtime_Error_100081 = Runtime + 81, Runtime_Error_100082 = Runtime + 82, Runtime_Error_100083 = Runtime + 83, Runtime_Error_100084 = Runtime + 84, Runtime_Error_100085 = Runtime + 85, Runtime_Error_100086 = Runtime + 86, Runtime_Error_100087 = Runtime + 87, Runtime_Error_100088 = Runtime + 88, Runtime_Error_100089 = Runtime + 89, Runtime_Error_100090 = Runtime + 90, Runtime_Error_100091 = Runtime + 91, Runtime_Error_100092 = Runtime + 92, Runtime_Error_100093 = Runtime + 93, Runtime_Error_100094 = Runtime + 94, Runtime_Error_100095 = Runtime + 95, Runtime_Error_100096 = Runtime + 96, Runtime_Error_100097 = Runtime + 97, Runtime_Error_100098 = Runtime + 98, Runtime_Error_100099 = Runtime + 99, Runtime_Error_100100 = Runtime + 100, Runtime_Error_100101 = Runtime + 101, Runtime_Error_100102 = Runtime + 102, Runtime_Error_100103 = Runtime + 103, Runtime_Error_100104 = Runtime + 104, Runtime_Error_100105 = Runtime + 105, Runtime_Error_100106 = Runtime + 106, Runtime_Error_100107 = Runtime + 107, Runtime_Error_100108 = Runtime + 108, Runtime_Error_100109 = Runtime + 109, Runtime_Error_100110 = Runtime + 110, Runtime_Error_100111 = Runtime + 111, Runtime_Error_100112 = Runtime + 112, Runtime_Error_100113 = Runtime + 113, Runtime_Error_100114 = Runtime + 114, Runtime_Error_100115 = Runtime + 115, Runtime_Error_100116 = Runtime + 116, Runtime_Error_100117 = Runtime + 117, Runtime_Error_100118 = Runtime + 118, Runtime_Error_100119 = Runtime + 119, Runtime_Error_100120 = Runtime + 120, Runtime_Error_100121 = Runtime + 121, Runtime_Error_100122 = Runtime + 122, Runtime_Error_100123 = Runtime + 123, Runtime_Error_100124 = Runtime + 124, Runtime_Error_100125 = Runtime + 125, Runtime_Error_100126 = Runtime + 126, Runtime_Error_100127 = Runtime + 127, Runtime_Error_100128 = Runtime + 128, Runtime_Error_100129 = Runtime + 129, Runtime_Error_100130 = Runtime + 130, Runtime_Error_100131 = Runtime + 131, Runtime_Error_100132 = Runtime + 132, Runtime_Error_100133 = Runtime + 133, Runtime_Error_100134 = Runtime + 134, Runtime_Error_100135 = Runtime + 135, Runtime_Error_100136 = Runtime + 136, Runtime_Error_100137 = Runtime + 137, Runtime_Error_100138 = Runtime + 138, Runtime_Error_100139 = Runtime + 139, Runtime_Error_100140 = Runtime + 140, Runtime_Error_100141 = Runtime + 141, Runtime_Error_100142 = Runtime + 142, Runtime_Error_100143 = Runtime + 143, Runtime_Error_100144 = Runtime + 144, Runtime_Error_100145 = Runtime + 145, Runtime_Error_100146 = Runtime + 146, Runtime_Error_100147 = Runtime + 147, Runtime_Error_100148 = Runtime + 148, Runtime_Error_100149 = Runtime + 149, Runtime_Error_100150 = Runtime + 150, Runtime_Error_100151 = Runtime + 151, Runtime_Error_100152 = Runtime + 152, Runtime_Error_100153 = Runtime + 153, Runtime_Error_100154 = Runtime + 154, Runtime_Error_100155 = Runtime + 155, Runtime_Error_100156 = Runtime + 156, Runtime_Error_100157 = Runtime + 157, Runtime_Error_100158 = Runtime + 158, Runtime_Error_100159 = Runtime + 159, Runtime_Error_100160 = Runtime + 160, Runtime_Error_100161 = Runtime + 161, Runtime_Error_100162 = Runtime + 162, Runtime_Error_100163 = Runtime + 163, Runtime_Error_100164 = Runtime + 164, Runtime_Error_100165 = Runtime + 165, Runtime_Error_100166 = Runtime + 166, Runtime_Error_100167 = Runtime + 167, Runtime_Error_100168 = Runtime + 168, Runtime_Error_100169 = Runtime + 169, Runtime_Error_100170 = Runtime + 170, Runtime_Error_100171 = Runtime + 171, Runtime_Error_100172 = Runtime + 172, Runtime_Error_100173 = Runtime + 173, Runtime_Error_100174 = Runtime + 174, Runtime_Error_100175 = Runtime + 175, Runtime_Error_100176 = Runtime + 176, Runtime_Error_100177 = Runtime + 177, Runtime_Error_100178 = Runtime + 178, Runtime_Error_100179 = Runtime + 179, Runtime_Error_100180 = Runtime + 180, Runtime_Error_100181 = Runtime + 181, Runtime_Error_100182 = Runtime + 182, Runtime_Error_100183 = Runtime + 183, Runtime_Error_100184 = Runtime + 184, Runtime_Error_100185 = Runtime + 185, Runtime_Error_100186 = Runtime + 186, Runtime_Error_100187 = Runtime + 187, Runtime_Error_100188 = Runtime + 188, Runtime_Error_100189 = Runtime + 189, Runtime_Error_100190 = Runtime + 190, Runtime_Error_100191 = Runtime + 191, Runtime_Error_100192 = Runtime + 192, Runtime_Error_100193 = Runtime + 193, Runtime_Error_100194 = Runtime + 194, Runtime_Error_100195 = Runtime + 195, Runtime_Error_100196 = Runtime + 196, Runtime_Error_100197 = Runtime + 197, Runtime_Error_100198 = Runtime + 198, Runtime_Error_100199 = Runtime + 199, Runtime_Error_100200 = Runtime + 200, Runtime_Error_100201 = Runtime + 201, Runtime_Error_100202 = Runtime + 202, Runtime_Error_100203 = Runtime + 203, Runtime_Error_100204 = Runtime + 204, Runtime_Error_100205 = Runtime + 205, Runtime_Error_100206 = Runtime + 206, Runtime_Error_100207 = Runtime + 207, Runtime_Error_100208 = Runtime + 208, Runtime_Error_100209 = Runtime + 209, Runtime_Error_100210 = Runtime + 210, Runtime_Error_100211 = Runtime + 211, Runtime_Error_100212 = Runtime + 212, Runtime_Error_100213 = Runtime + 213, Runtime_Error_100214 = Runtime + 214, Runtime_Error_100215 = Runtime + 215, Runtime_Error_100216 = Runtime + 216, Runtime_Error_100217 = Runtime + 217, Runtime_Error_100218 = Runtime + 218, Runtime_Error_100219 = Runtime + 219, Runtime_Error_100220 = Runtime + 220, Runtime_Error_100221 = Runtime + 221, Runtime_Error_100222 = Runtime + 222, Runtime_Error_100223 = Runtime + 223, Runtime_Error_100224 = Runtime + 224, Runtime_Error_100225 = Runtime + 225, Runtime_Error_100226 = Runtime + 226, Runtime_Error_100227 = Runtime + 227, Runtime_Error_100228 = Runtime + 228, Runtime_Error_100229 = Runtime + 229, Runtime_Error_100230 = Runtime + 230, Runtime_Error_100231 = Runtime + 231, Runtime_Error_100232 = Runtime + 232, Runtime_Error_100233 = Runtime + 233, Runtime_Error_100234 = Runtime + 234, Runtime_Error_100235 = Runtime + 235, Runtime_Error_100236 = Runtime + 236, Runtime_Error_100237 = Runtime + 237, Runtime_Error_100238 = Runtime + 238, Runtime_Error_100239 = Runtime + 239, Runtime_Error_100240 = Runtime + 240, Runtime_Error_100241 = Runtime + 241, Runtime_Error_100242 = Runtime + 242, Runtime_Error_100243 = Runtime + 243, Runtime_Error_100244 = Runtime + 244, Runtime_Error_100245 = Runtime + 245, Runtime_Error_100246 = Runtime + 246, Runtime_Error_100247 = Runtime + 247, Runtime_Error_100248 = Runtime + 248, Runtime_Error_100249 = Runtime + 249, Runtime_Error_100250 = Runtime + 250, Runtime_Error_100251 = Runtime + 251, Runtime_Error_100252 = Runtime + 252, Runtime_Error_100253 = Runtime + 253, Runtime_Error_100254 = Runtime + 254, Runtime_Error_100255 = Runtime + 255, Runtime_Error_100256 = Runtime + 256, Runtime_Error_100257 = Runtime + 257, Runtime_Error_100258 = Runtime + 258, Runtime_Error_100259 = Runtime + 259, Runtime_Error_100260 = Runtime + 260, Runtime_Error_100261 = Runtime + 261, Runtime_Error_100262 = Runtime + 262, Runtime_Error_100263 = Runtime + 263, Runtime_Error_100264 = Runtime + 264, Runtime_Error_100265 = Runtime + 265, Runtime_Error_100266 = Runtime + 266, Runtime_Error_100267 = Runtime + 267, Runtime_Error_100268 = Runtime + 268, Runtime_Error_100269 = Runtime + 269, Runtime_Error_100270 = Runtime + 270, Runtime_Error_100271 = Runtime + 271, Runtime_Error_100272 = Runtime + 272, Runtime_Error_100273 = Runtime + 273, Runtime_Error_100274 = Runtime + 274, Runtime_Error_100275 = Runtime + 275, Runtime_Error_100276 = Runtime + 276, Runtime_Error_100277 = Runtime + 277, Runtime_Error_100278 = Runtime + 278, Runtime_Error_100279 = Runtime + 279, Runtime_Error_100280 = Runtime + 280, Runtime_Error_100281 = Runtime + 281, Runtime_Error_100282 = Runtime + 282, Runtime_Error_100283 = Runtime + 283, Runtime_Error_100284 = Runtime + 284, Runtime_Error_100285 = Runtime + 285, Runtime_Error_100286 = Runtime + 286, Runtime_Error_100287 = Runtime + 287, Runtime_Error_100288 = Runtime + 288, Runtime_Error_100289 = Runtime + 289, Runtime_Error_100290 = Runtime + 290, Runtime_Error_100291 = Runtime + 291, Runtime_Error_100292 = Runtime + 292, Runtime_Error_100293 = Runtime + 293, Runtime_Error_100294 = Runtime + 294, Runtime_Error_100295 = Runtime + 295, Runtime_Error_100296 = Runtime + 296, Runtime_Error_100297 = Runtime + 297, Runtime_Error_100298 = Runtime + 298, Runtime_Error_100299 = Runtime + 299, Runtime_Error_100300 = Runtime + 300, Runtime_Error_100301 = Runtime + 301, Runtime_Error_100302 = Runtime + 302, Runtime_Error_100303 = Runtime + 303, Runtime_Error_100304 = Runtime + 304, Runtime_Error_100305 = Runtime + 305, Runtime_Error_100306 = Runtime + 306, Runtime_Error_100307 = Runtime + 307, Runtime_Error_100308 = Runtime + 308, Runtime_Error_100309 = Runtime + 309, Runtime_Error_100310 = Runtime + 310, Runtime_Error_100311 = Runtime + 311, Runtime_Error_100312 = Runtime + 312, ClientInitializing = Runtime + 313, ClientStarting = Runtime + 314, ClientError = Runtime + 315, Runtime_Error_100316 = Runtime + 316, Runtime_Error_100317 = Runtime + 317, Runtime_Error_100318 = Runtime + 318, Runtime_Error_100319 = Runtime + 319, Runtime_Error_100320 = Runtime + 320, Runtime_Error_100321 = Runtime + 321, GrainInvokeException = Runtime + 322, Runtime_Error_100323 = Runtime + 323, Runtime_Error_100324 = Runtime + 324, Runtime_Error_100325 = Runtime + 325, Runtime_Error_100326 = Runtime + 326, Runtime_Error_100327 = Runtime + 327, Runtime_Error_100328 = Runtime + 328, Runtime_Error_100329 = Runtime + 329, Runtime_Error_100330 = Runtime + 330, Runtime_Error_100331 = Runtime + 331, SiloBase = Runtime + 400, SiloStarting = SiloBase + 1, SiloStarted = SiloBase + 2, SiloInitializing = SiloBase + 3, SiloGcSetting = SiloBase + 4, SiloGcWarning = SiloBase + 5, SiloSetDeploymentId = SiloBase + 6, SiloSetSiloEndpoint = SiloBase + 7, SiloSetProxyEndpoint = SiloBase + 8, SiloSetSeedNode = SiloBase + 9, SiloAddSeedNode = SiloBase + 10, SiloSetPrimaryNode = SiloBase + 11, SiloSetWorkingDir = SiloBase + 12, SiloStopped = SiloBase + 13, SiloStopping = SiloBase + 14, SiloInitConfig = SiloBase + 15, SiloDebugDump = SiloBase + 16, SiloShuttingDown = SiloBase + 17, SiloShutDown = SiloBase + 18, SiloFailedToStopMembership = SiloBase + 19, SiloIgnoreErrorDuringStop = SiloBase + 20, SiloHeartbeatTimerStalled = Runtime_Error_100150, // Backward compatability SiloCannotResetHeartbeatTimer = SiloBase + 21, SiloInitializingFinished = SiloBase + 22, SiloSetSiloType = SiloBase + 23, SiloStartupEventName = SiloBase + 24, SiloStartupEventCreated = SiloBase + 25, SiloStartupEventOpened = SiloBase + 26, SiloStopInProgress = SiloBase + 27, WaitingForSiloStop = SiloBase + 28, CannotCheckRoleEnvironment = SiloBase + 29, SiloConfiguredThreadPool = SiloBase + 30, SiloFailedToConfigureThreadPool = SiloBase + 31, SetSiloLivenessType = SiloBase + 34, SiloEndpointConfigError = SiloBase + 35, SiloConfiguredServicePointManager = SiloBase + 36, SiloCallingProviderInit = SiloBase + 37, SetReminderServiceType = SiloBase + 38, SiloStartError = SiloBase + 39, SiloConfigDeprecated = SiloBase + 40, SiloShutdownEventName = SiloBase + 41, SiloShutdownEventCreated = SiloBase + 42, SiloShutdownEventOpened = SiloBase + 43, SiloShutdownEventReceived = SiloBase + 44, SiloLoadedDI = SiloBase + 45, // Not used anymore SiloFailedToLoadDI = SiloBase + 46, // Not used anymore SiloFileNotFoundLoadingDI = SiloBase + 47, // Not used anymore SiloStartupEventFailure = SiloBase + 48, SiloShutdownEventFailure = SiloBase + 49, LifecycleStartFailure = SiloBase + 50, LifecycleStopFailure = SiloBase + 51, SiloStartPerfMeasure = SiloBase + 52, LifecycleStagesReport = SiloBase + 53, CatalogBase = Runtime + 500, CatalogNonExistingActivation1 = CatalogBase + 1, Catalog_UnregisterManyAsync = CatalogBase + 2, Catalog_DestroyActivations = CatalogBase + 3, Catalog_UnknownActivation = CatalogBase + 4, Catalog_ActivationException = CatalogBase + 5, Catalog_GetApproximateSiloStatuses = CatalogBase + 6, Catalog_BeforeCollection = CatalogBase + 7, Catalog_AfterCollection = CatalogBase + 8, Catalog_ShutdownActivations_1 = CatalogBase + 9, CatalogNonExistingActivation2 = CatalogBase + 10, Catalog_BeforeCallingActivate = CatalogBase + 11, Catalog_AfterCallingActivate = CatalogBase + 12, Catalog_ErrorCallingActivate = CatalogBase + 13, Catalog_BeforeCallingDeactivate = CatalogBase + 14, Catalog_AfterCallingDeactivate = CatalogBase + 15, Catalog_ErrorCallingDeactivate = CatalogBase + 16, Catalog_MissingTypeOnCreate = CatalogBase + 17, Catalog_ResendDuplicateFailed = CatalogBase + 18, Catalog_NullGetTypeAndStrategies = CatalogBase + 19, Catalog_DuplicateActivation = CatalogBase + 20, Catalog_RegistrationFailure = CatalogBase + 21, Catalog_Warn_ActivationTooManyRequests = CatalogBase + 22, Catalog_Reject_ActivationTooManyRequests = CatalogBase + 23, Catalog_SiloStatusChangeNotification = CatalogBase + 24, Catalog_SiloStatusChangeNotification_Exception = CatalogBase + 25, Catalog_AttemptToCollectActivationEarly = CatalogBase + 26, Catalog_DeactivateActivation_Exception = CatalogBase + 27, Catalog_ActivationDirectory_Statistics = CatalogBase + 28, Catalog_UnregisterMessageTarget1 = CatalogBase + 29, Catalog_UnregisterMessageTarget2 = CatalogBase + 30, Catalog_UnregisterMessageTarget3 = CatalogBase + 31, Catalog_UnregisterMessageTarget4 = CatalogBase + 32, Catalog_Failed_SetupActivationState = CatalogBase + 33, Catalog_Failed_InvokeActivate = CatalogBase + 34, Catalog_RerouteAllQueuedMessages = CatalogBase + 35, Catalog_WaitForAllTimersToFinish_Exception = CatalogBase + 36, Catalog_ActivationCollector_BadState_1 = CatalogBase + 37, Catalog_ActivationCollector_BadState_2 = CatalogBase + 38, Catalog_DestroyActivations_Done = CatalogBase + 39, Catalog_ShutdownActivations_2 = CatalogBase + 40, Catalog_ShutdownActivations_3 = CatalogBase + 41, Catalog_DeactivateStreamResources_Exception = CatalogBase + 42, Catalog_FinishDeactivateActivation_Exception = CatalogBase + 43, Catalog_FinishGrainDeactivateAndCleanupStreams_Exception = CatalogBase + 44, Catalog_DeactivateAllActivations = CatalogBase + 45, Catalog_ActivationCollector_BadState_3 = CatalogBase + 46, Catalog_UnregisterAsync = CatalogBase + 47, Catalog_CancelledActivate = CatalogBase + 48, Catalog_DisposedObjectAccess = CatalogBase + 49, MembershipBase = Runtime + 600, MembershipCantWriteLivenessDisabled = Runtime_Error_100225, // Backward compatability MembershipNodeMigrated = MembershipBase + 1, MembershipNodeRestarted = MembershipBase + 2, MembershipStarting = MembershipBase + 3, MembershipBecomeActive = MembershipBase + 4, MembershipFinishBecomeActive = MembershipBase + 5, MembershipShutDown = MembershipBase + 6, MembershipStop = MembershipBase + 7, MembershipReadTable = MembershipBase + 8, MembershipKillMyself = MembershipBase + 9, MembershipVotingForKill = MembershipBase + 10, MembershipMarkingAsDead = MembershipBase + 11, MembershipWatchList = MembershipBase + 12, MembershipMissedPing = MembershipBase + 13, MembershipSendingPreJoinPing = MembershipBase + 14, MembershipFailedToWrite = MembershipBase + 15, MembershipFailedToWriteConditional = MembershipBase + 16, MembershipFoundMyselfDead1 = MembershipBase + 17, MembershipFoundMyselfDead2 = MembershipBase + 18, MembershipDetectedOlder = MembershipBase + 19, MembershipDetectedNewer = MembershipBase + 20, MembershipDelayedTableUpdateTimer = MembershipBase + 21, MembershipDelayedProbeOtherSilosTimer = MembershipBase + 22, MembershipFailedToReadSilo = MembershipBase + 23, MembershipDelayedIAmAliveUpdateTimer = MembershipBase + 24, MembershipMissedIAmAliveTableUpdate = MembershipBase + 25, MembershipLocalSubscriberException = MembershipBase + 26, MembershipKillMyselfLocally = MembershipBase + 27, MembershipFoundMyselfDead3 = MembershipBase + 28, MembershipMarkDeadWriteFailed = MembershipBase + 29, MembershipTableGrainInit1 = MembershipBase + 30, MembershipTableGrainInit2 = MembershipBase + 31, MembershipTableGrainInit3 = MembershipBase + 32, MembershipTableGrainInit4 = MembershipBase + 33, MembershipReadAll_1 = MembershipBase + 34, MembershipFactory1 = MembershipBase + 35, MembershipFactory2 = MembershipBase + 36, MembershipGrainBasedTable1 = MembershipBase + 37, MembershipGrainBasedTable2 = MembershipBase + 38, MembershipGrainBasedTable3 = MembershipBase + 39, MembershipFileBasedTable1 = MembershipBase + 40, MembershipFileBasedTable2 = MembershipBase + 41, MembershipFileBasedTable3 = MembershipBase + 42, MembershipFileBasedTable4 = MembershipBase + 43, MembershipPingedSiloNotInWatchList = MembershipBase + 44, MembershipReadAll_2 = MembershipBase + 45, MembershipFailedToStart = MembershipBase + 46, MembershipFailedToBecomeActive = MembershipBase + 47, MembershipFailedToStop = MembershipBase + 48, MembershipFailedToShutdown = MembershipBase + 49, MembershipFailedToKillMyself = MembershipBase + 50, MembershipFailedToSuspect = MembershipBase + 51, MembershipReadAll_Cleanup = MembershipBase + 52, MembershipShutDownFailure = MembershipBase + 53, MembershipKillMyselfFailure = MembershipBase + 54, MembershipGossipProcessingFailure = MembershipBase + 55, MembershipGossipSendFailure = MembershipBase + 56, MembershipTimerProcessingFailure = MembershipBase + 57, MembershipSendPingFailure = MembershipBase + 58, MembershipUpdateIAmAliveFailure = MembershipBase + 59, MembershipStartingIAmAliveTimer = MembershipBase + 60, MembershipJoiningPreconditionFailure = MembershipBase + 61, MembershipCleanDeadEntriesFailure = MembershipBase + 62, MembershipJoining = MembershipBase + 63, MembershipFailedToJoin = MembershipBase + 64, NSMembershipStarting = MembershipBase + 70, NSMembershipBecomeActive = MembershipBase + 71, NSMembershipFailedToBecomeActive = MembershipBase + 72, NSMembershipShutDown = MembershipBase + 73, NSMembershipStop = MembershipBase + 74, NSMembershipKillMyself = MembershipBase + 75, NSMembershipKillMyselfLocally = MembershipBase + 76, NSMembershipNotificationProcessingFailure = MembershipBase + 77, NSMembershipReadAll_1 = MembershipBase + 78, NSMembershipReadAll_2 = MembershipBase + 79, NSMembershipFoundMyselfDead2 = MembershipBase + 80, NSMembershipDetectedOlder = MembershipBase + 81, NSMembershipDetectedNewer = MembershipBase + 82, NSMembershipTimerProcessingFailure = MembershipBase + 83, NSMembershipShutDownFailure = MembershipBase + 84, NSMembershipKillMyselfFailure = MembershipBase + 85, NSMembershipNSDetails = MembershipBase + 86, SSMT_ReadRowError = MembershipBase + 87, SSMT_ReadAllError = MembershipBase + 88, SSMT_InsertRowError = MembershipBase + 89, SSMT_UpdateRowError = MembershipBase + 90, SSMT_MergeRowError = MembershipBase + 91, SSMT_EtagMismatch_Insert = MembershipBase + 92, SSMT_EtagMismatch_Update = MembershipBase + 93, PerfCounterBase = Runtime + 700, PerfCounterNotFound = PerfCounterBase + 1, PerfCounterStarting = PerfCounterBase + 2, PerfCounterStopping = PerfCounterBase + 3, PerfCounterDumpAll = PerfCounterBase + 4, PerfCounterWriteErrors = PerfCounterBase + 5, PerfCounterWriteSuccess = PerfCounterBase + 6, PerfCounterWriteTooManyErrors = PerfCounterBase + 7, PerfCounterNotRegistered = PerfCounterBase + 8, PerfCounterUnableToConnect = PerfCounterBase + 9, PerfCounterUnableToWrite = PerfCounterBase + 10, PerfCounterWriting = PerfCounterBase + 11, PerfCounterSkipping = PerfCounterBase + 12, PerfMetricsStoppingTimer = PerfCounterBase + 13, PerfMetricsStartingTimer = PerfCounterBase + 14, PerfStatistics = PerfCounterBase + 15, PerfCounterRegistering = PerfCounterBase + 16, PerfCounterTimerError = PerfCounterBase + 17, PerfCounterCategoryCheckError = PerfCounterBase + 18, PerfCounterConnectError = PerfCounterBase + 19, PerfCounterFailedToInitialize = PerfCounterBase + 20, ProxyClientBase = Runtime + 900, ProxyClient_ReceiveError = Runtime_Error_100021, // Backward compatability ProxyClient_SerializationError = Runtime_Error_100159, // Backward compatability ProxyClient_SocketSendError = Runtime_Error_100161, // Backward compatability ProxyClient_ByteCountMismatch = Runtime_Error_100163, // Backward compatability ProxyClient_CannotConnect = Runtime_Error_100178, // Backward compatability ProxyClientUnhandledExceptionWhileSending = ProxyClientBase + 1, ProxyClientUnhandledExceptionWhileReceiving = ProxyClientBase + 2, ProxyClient_CannotSend = ProxyClientBase + 3, ProxyClient_CannotSend_NoGateway = ProxyClientBase + 4, ProxyClient_DroppingMsg = ProxyClientBase + 5, ProxyClient_RejectingMsg = ProxyClientBase + 6, ProxyClient_MsgSent = ProxyClientBase + 7, ProxyClient_Connected = ProxyClientBase + 8, ProxyClient_PauseBeforeRetry = ProxyClientBase + 9, ProxyClient_MsgCtrNotRunning = ProxyClientBase + 10, ProxyClient_DeadGateway = ProxyClientBase + 11, ProxyClient_MarkGatewayDead = ProxyClientBase + 12, ProxyClient_MarkGatewayDisconnected = ProxyClientBase + 13, ProxyClient_GatewayConnStarted = ProxyClientBase + 14, ProxyClient_CreatedGatewayUnordered = ProxyClientBase + 15, ProxyClient_CreatedGatewayToGrain = ProxyClientBase + 16, ProxyClient_NewBucketIndex = ProxyClientBase + 17, ProxyClient_QueueRequest = ProxyClientBase + 18, ProxyClient_ThreadAbort = ProxyClientBase + 19, ProxyClient_OperationCancelled = ProxyClientBase + 20, ProxyClient_GetGateways = ProxyClientBase + 21, ProxyClient_NetworkError = ProxyClientBase + 22, ProxyClient_SendException = ProxyClientBase + 23, ProxyClient_OGC_TargetNotFound = ProxyClientBase + 24, ProxyClient_OGC_SendResponseFailed = ProxyClientBase + 25, ProxyClient_OGC_SendExceptionResponseFailed = ProxyClientBase + 26, ProxyClient_OGC_UnhandledExceptionInOneWayInvoke = ProxyClientBase + 27, ProxyClient_ClientInvokeCallback_Error = ProxyClientBase + 28, ProxyClient_StartDone = ProxyClientBase + 29, ProxyClient_OGC_TargetNotFound_2 = ProxyClientBase + 30, ProxyClient_AppDomain_Unload = ProxyClientBase + 31, ProxyClient_GatewayUnknownStatus = ProxyClientBase + 32, ProxyClient_FailedToUnregisterCallback = ProxyClientBase + 33, MessagingBase = Runtime + 1000, Messaging_IMA_DroppingConnection = MessagingBase + 1, Messaging_Dispatcher_DiscardRejection = MessagingBase + 2, MessagingBeginReceiveException = MessagingBase + 3, MessagingBeginAcceptSocketException = MessagingBase + 4, MessagingAcceptingSocketClosed = MessagingBase + 5, MessagingEndAcceptSocketException = MessagingBase + 6, MessagingUnexpectedSendError = MessagingBase + 7, MessagingSendingRejection = MessagingBase + 8, MessagingMessageFromUnknownActivation = MessagingBase + 9, Messaging_IMA_OpenedListeningSocket = MessagingBase + 10, Messaging_IMA_AcceptCallbackNullState = MessagingBase + 11, Messaging_IMA_AcceptCallbackUnexpectedState = MessagingBase + 12, Messaging_IMA_NewBeginReceiveException = MessagingBase + 13, Messaging_Socket_ReceiveError = MessagingBase + 14, Messaging_IMA_ClosingSocket = MessagingBase + 15, Messaging_OutgoingMS_DroppingMessage = MessagingBase + 16, MessagingProcessReceiveBufferException = MessagingBase + 17, Messaging_LargeMsg_Outgoing = MessagingBase + 18, Messaging_LargeMsg_Incoming = MessagingBase + 19, Messaging_SiloNetworkError = MessagingBase + 20, Messaging_UnableToGetSendingSocket = MessagingBase + 21, Messaging_ExceptionSending = MessagingBase + 22, Messaging_CountMismatchSending = MessagingBase + 23, Messaging_ExceptionReceiving = MessagingBase + 24, Messaging_ExceptionBeginReceiving = MessagingBase + 25, Messaging_IMA_ExceptionAccepting = MessagingBase + 26, Messaging_IMA_BadBufferReceived = MessagingBase + 27, Messaging_IMA_ActivationOverloaded = MessagingBase + 28, Messaging_SerializationError = MessagingBase + 29, Messaging_UnableToDeserializeBody = MessagingBase + 30, Messaging_Dispatcher_TryForward = MessagingBase + 31, Messaging_Dispatcher_TryForwardFailed = MessagingBase + 32, Messaging_Dispatcher_ForwardingRequests = MessagingBase + 33, Messaging_SimulatedMessageLoss = MessagingBase + 34, Messaging_Dispatcher_ReturnToOriginCluster = MessagingBase + 35, MessagingAcceptAsyncSocketException = MessagingBase + 36, Messaging_ExceptionReceiveAsync = MessagingBase + 37, Messaging_DroppingExpiredMessage = MessagingBase + 38, Messaging_DroppingBlockedMessage = MessagingBase + 39, Messaging_Inbound_Enqueue = MessagingBase + 40, Messaging_Inbound_Dequeue = MessagingBase + 41, Messaging_Dispatcher_Rejected = MessagingBase + 42, DirectoryBase = Runtime + 1100, DirectoryBothPrimaryAndBackupForGrain = DirectoryBase + 1, DirectoryPartitionPredecessorExpected = DirectoryBase + 2, DirectoryUnexpectedDelta = DirectoryBase + 4, Directory_SiloStatusChangeNotification_Exception = DirectoryBase + 5, SchedulerBase = Runtime + 1200, SchedulerWorkerPoolThreadQueueWaitTime = SchedulerBase + 1, SchedulerWorkItemGroupQueueWaitTime = SchedulerBase + 2, SchedulerStatistics = SchedulerBase + 3, SchedulerFinishShutdown = SchedulerBase + 4, SchedulerNullActivation = SchedulerBase + 5, SchedulerExceptionFromExecute = SchedulerBase + 6, SchedulerNullContext = SchedulerBase + 7, SchedulerTaskExecuteIncomplete1 = SchedulerBase + 8, WaitCalledInsideGrain = SchedulerBase + 9, SchedulerStatus = SchedulerBase + 10, WaitCalledInServerCode = SchedulerBase + 11, ExecutorTurnTooLong = SchedulerBase + 12, SchedulerTooManyPendingItems = SchedulerBase + 13, SchedulerTurnTooLong2 = SchedulerBase + 14, SchedulerTurnTooLong3 = SchedulerBase + 15, SchedulerWorkGroupShuttingDown = SchedulerBase + 16, SchedulerEnqueueWorkWhenShutdown = SchedulerBase + 17, SchedulerNotExecuteWhenShutdown = SchedulerBase + 18, SchedulerAppTurnsStopped_1 = SchedulerBase + 19, SchedulerWorkGroupStopping = SchedulerBase + 20, SchedulerSkipWorkStopping = SchedulerBase + 21, SchedulerSkipWorkCancelled = SchedulerBase + 22, SchedulerTaskRunningOnWrongScheduler1 = SchedulerBase + 23, SchedulerQueueWorkItemWrongCall = SchedulerBase + 24, SchedulerQueueTaskWrongCall = SchedulerBase + 25, SchedulerTaskExecuteIncomplete2 = SchedulerBase + 26, SchedulerTaskExecuteIncomplete3 = SchedulerBase + 27, SchedulerTaskExecuteIncomplete4 = SchedulerBase + 28, SchedulerTaskWaitIncomplete = SchedulerBase + 29, ExecutorWorkerThreadExc = SchedulerBase + 30, SchedulerQueueWorkItemWrongContext = SchedulerBase + 31, SchedulerAppTurnsStopped_2 = SchedulerBase + 32, ExecutorProcessingError = SchedulerBase + 33, GatewayBase = Runtime + 1300, GatewayClientOpenedSocket = GatewayBase + 1, GatewayClientClosedSocket = GatewayBase + 2, GatewayDroppingClient = GatewayBase + 3, GatewayTryingToSendToUnrecognizedClient = GatewayBase + 4, GatewayByteCountMismatch = GatewayBase + 5, GatewayExceptionSendingToClient = GatewayBase + 6, GatewayAcceptor_SocketClosed = GatewayBase + 7, GatewayAcceptor_ExceptionReceiving = GatewayBase + 8, GatewayManager_FoundKnownGateways = GatewayBase + 9, MessageAcceptor_Connection = GatewayBase + 10, MessageAcceptor_NotAProxiedConnection = GatewayBase + 11, MessageAcceptor_UnexpectedProxiedConnection = GatewayBase + 12, GatewayManager_NoGateways = GatewayBase + 13, GatewayNetworkError = GatewayBase + 14, GatewayFailedToParse = GatewayBase + 15, ClientRegistrarFailedToRegister = GatewayBase + 16, ClientRegistrarFailedToRegister_2 = GatewayBase + 17, ClientRegistrarFailedToUnregister = GatewayBase + 18, ClientRegistrarTimerFailed = GatewayBase + 19, GatewayAcceptor_WrongClusterId = GatewayBase + 20, GatewayManager_AllGatewaysDead = GatewayBase + 21, GatewayAcceptor_InvalidSize = GatewayBase + 22, TimerBase = Runtime + 1400, TimerChangeError = PerfCounterTimerError, // Backward compatability //TimerCallbackError = Runtime_Error_100306, // Backward compatability TimerCallbackError = Runtime_Error_100037, // Backward compatability TimerDisposeError = TimerBase + 1, TimerStopError = TimerBase + 2, TimerQueueTickError = TimerBase + 3, TimerChanging = TimerBase + 4, TimerBeforeCallback = TimerBase + 5, TimerAfterCallback = TimerBase + 6, TimerNextTick = TimerBase + 7, TimerDisposing = TimerBase + 8, TimerStopped = TimerBase + 9, Timer_TimerInsideGrainIsNotTicking = TimerBase + 10, Timer_TimerInsideGrainIsDelayed = TimerBase + 11, Timer_SafeTimerIsNotTicking = TimerBase + 12, Timer_GrainTimerCallbackError = TimerBase + 13, Timer_InvalidContext = TimerBase + 14, DispatcherBase = Runtime + 1500, Dispatcher_SelectTarget_Failed = Runtime_Error_100071, // Backward compatability Dispatcher_InvalidEnum_Direction = Runtime_Error_100072, // Backward compatability Dispatcher_NoCallbackForRejectionResp = Runtime_Error_100073, // Backward compatability Dispatcher_InvalidEnum_RejectionType = Runtime_Error_100075, // Backward compatability Dispatcher_NoCallbackForResp = Runtime_Error_100076, // Backward compatability Dispatcher_InvalidMsg_Direction = Runtime_Error_100077, // Backward compatability Dispatcher_Intermediate_GetOrCreateActivation = Runtime_Error_100147, // Backward compatability Dispatcher_NoTargetActivation = Runtime_Error_100148, // Backward compatability Dispatcher_QueueingRequestBadTargetState = Runtime_Error_100152, // Backward compatability Dispatcher_InjectingRejection = Runtime_Error_100299, // Backward compatability Dispatcher_InjectingMessageLoss = Runtime_Error_100300, // Backward compatability Dispatcher_UnknownTypeCode = Runtime_Error_100303, // Backward compatability Dispatcher_SelectTarget_FailPending = DispatcherBase + 1, Dispatcher_RegisterCallback_Replaced = DispatcherBase + 2, Dispatcher_Send_BufferResponse = DispatcherBase + 3, Dispatcher_Send_AddressedMessage = DispatcherBase + 4, Dispatcher_Receive_InvalidActivation = DispatcherBase + 5, Dispatcher_WriteGrainFailed = DispatcherBase + 6, Dispatcher_ActivationEndedTurn_Waiting = DispatcherBase + 7, Dispatcher_Retarget = DispatcherBase + 8, Dispatcher_TryAcceptMessage = DispatcherBase + 9, Dispatcher_UpdateReceiveOrder = DispatcherBase + 10, Dispatcher_ReceiveOrderCorrelation = DispatcherBase + 11, Dispatcher_AddSendOrder = DispatcherBase + 12, Dispatcher_AddSendOrderNoPrior = DispatcherBase + 13, Dispatcher_AddSendOrder_PriorIds = DispatcherBase + 14, Dispatcher_AddSendOrder_First = DispatcherBase + 15, Dispatcher_EnqueueMessage = DispatcherBase + 16, Dispatcher_AddressMsg = DispatcherBase + 17, Dispatcher_AddressMsg_GrainOrder = DispatcherBase + 18, Dispatcher_AddressMsg_NullingLastSentTo = DispatcherBase + 19, Dispatcher_AddressMsg_SMPlacement = DispatcherBase + 20, Dispatcher_AddressMsg_UnregisteredClient = DispatcherBase + 21, Dispatcher_AddressMsg_SelectTarget = DispatcherBase + 22, Dispatcher_HandleMsg = DispatcherBase + 23, Dispatcher_OnActivationCompletedRequest_Waiting = DispatcherBase + 24, IGC_DisposeError = DispatcherBase + 25, IGC_SendRequest_NullContext = DispatcherBase + 26, IGC_SniffIncomingMessage_Exc = DispatcherBase + 27, Dispatcher_DetectedDeadlock = DispatcherBase + 28, Dispatcher_ActivationOverloaded = DispatcherBase + 30, IGC_SendResponseFailed = DispatcherBase + 31, IGC_SendExceptionResponseFailed = DispatcherBase + 32, IGC_UnhandledExceptionInInvoke = DispatcherBase + 33, Dispatcher_ExtendedMessageProcessing = DispatcherBase + 34, Dispatcher_FailedToUnregisterNonExistingAct = DispatcherBase + 35, Dispatcher_NoGrainInstance = DispatcherBase + 36, Dispatcher_RuntimeStatisticsUnavailable = DispatcherBase + 37, Dispatcher_InvalidActivation = DispatcherBase + 38, InvokeWorkItem_UnhandledExceptionInInvoke = DispatcherBase + 39, Dispatcher_ErrorCreatingActivation = DispatcherBase + 40, Dispatcher_StuckActivation = DispatcherBase + 41, Dispatcher_FailedToUnregisterCallback = DispatcherBase + 42, SerializationBase = Runtime + 1600, Ser_IncompatibleIntermediateType = Runtime_Error_100033, // Backward compatability Ser_CannotConstructBaseObj = Runtime_Error_100034, // Backward compatability Ser_IncompatibleType = Runtime_Error_100035, // Backward compatability Ser_AssemblyLoadError = SerializationBase + 1, Ser_BadRegisterSerializer = SerializationBase + 2, Ser_AssemblyLoadErrorDetails = SerializationBase + 3, Ser_AssemblyLoadSuccess = SerializationBase + 4, Ser_LargeObjectAllocated = SerializationBase + 5, LoaderBase = Runtime + 1700, Loader_NotGrainAssembly = Runtime_Error_100047, // Backward compatability Loader_TypeLoadError = Runtime_Error_100048, // Backward compatability Loader_ProxyLoadError = Runtime_Error_100049, // Backward compatability Loader_AssemblyLookupFailed = LoaderBase + 1, Loader_AssemblyLookupResolved = LoaderBase + 2, Loader_LoadingFromDir = LoaderBase + 3, Loader_LoadingFromFile = LoaderBase + 4, Loader_DirNotFound = LoaderBase + 5, Loader_LoadingSerInfo = LoaderBase + 6, Loader_LoadingGrainType = LoaderBase + 7, Loader_SkippingFile = LoaderBase + 8, Loader_SkippingDynamicAssembly = LoaderBase + 9, Loader_AssemblyInspectError = LoaderBase + 10, Loader_GrainTypeFullList = LoaderBase + 11, Loader_IgnoreAbstractGrainClass = LoaderBase + 12, Loader_AssemblyInspectionError = LoaderBase + 13, Loader_FoundBinary = LoaderBase + 14, Loader_IgnoreNonPublicGrainClass = LoaderBase + 15, Loader_UnexpectedException = LoaderBase + 16, Loader_SkippingBadAssembly = LoaderBase + 17, Loader_TypeLoadError_2 = LoaderBase + 18, Loader_TypeLoadError_3 = LoaderBase + 19, Loader_TypeLoadError_4 = LoaderBase + 20, Loader_LoadAndCreateInstance_Failure = LoaderBase + 21, Loader_TryLoadAndCreateInstance_Failure = LoaderBase + 22, Loader_TypeLoadError_5 = LoaderBase + 23, Loader_AssemblyLoadError = LoaderBase + 24, PlacementBase = Runtime + 1800, Placement_RuntimeStatisticsUpdateFailure_1 = PlacementBase + 1, Placement_RuntimeStatisticsUpdateFailure_2 = PlacementBase + 2, Placement_RuntimeStatisticsUpdateFailure_3 = PlacementBase + 3, Placement_ActivationCountBasedDirector_NoSilos = PlacementBase + 4, StorageProviderBase = Runtime + 2200, StorageProvider_ReadFailed = StorageProviderBase + 2, StorageProvider_WriteFailed = StorageProviderBase + 3, StorageProvider_DeleteFailed = StorageProviderBase + 4, StorageProvider_ForceReRead = StorageProviderBase + 5, SerializationManagerBase = Runtime + 2400, SerMgr_TypeRegistrationFailure = SerializationManagerBase + 1, SerMgr_MissingRegisterMethod = SerializationManagerBase + 2, SerMgr_ErrorBindingMethods = SerializationManagerBase + 3, SerMgr_ErrorLoadingAssemblyTypes = SerializationManagerBase + 4, SerMgr_TooLongSerialize = SerializationManagerBase + 5, SerMgr_TooLongDeserialize = SerializationManagerBase + 6, SerMgr_TooLongDeepCopy = SerializationManagerBase + 7, SerMgr_IgnoreAssembly = SerializationManagerBase + 8, SerMgr_TypeRegistrationFailureIgnore = SerializationManagerBase + 9, SerMgr_ArtifactReport = SerializationManagerBase + 10, SerMgr_UnavailableSerializer = SerializationManagerBase + 11, SerMgr_SerializationMethodsMissing = SerializationManagerBase + 12, WatchdogBase = Runtime + 2600, Watchdog_ParticipantThrownException = WatchdogBase + 1, Watchdog_InternalError = WatchdogBase + 2, Watchdog_HealthCheckFailure = WatchdogBase + 3, LoggerBase = Runtime + 2700, Logger_ProcessCrashing = Runtime_Error_100002, // Backward compatability Logger_LogMessageTruncated = LoggerBase + 1, WFServiceBase = Runtime + 2800, WFService_Error_1 = WFServiceBase + 1, WFService_Error_2 = WFServiceBase + 2, WFService_Error_3 = WFServiceBase + 3, WFService_Error_4 = WFServiceBase + 4, WFService_Error_5 = WFServiceBase + 5, WFService_Error_6 = WFServiceBase + 6, WFService_Error_7 = WFServiceBase + 7, WFService_Error_8 = WFServiceBase + 8, WFService_Error_9 = WFServiceBase + 9, // Codes for the Reminder Service ReminderServiceBase = Runtime + 2900, RS_Register_TableError = ReminderServiceBase + 5, RS_Register_AlreadyRegistered = ReminderServiceBase + 7, RS_Register_InvalidPeriod = ReminderServiceBase + 8, RS_Register_NotRemindable = ReminderServiceBase + 9, RS_NotResponsible = ReminderServiceBase + 10, RS_Unregister_NotFoundLocally = ReminderServiceBase + 11, RS_Unregister_TableError = ReminderServiceBase + 12, RS_Table_Insert = ReminderServiceBase + 13, RS_Table_Remove = ReminderServiceBase + 14, RS_Tick_Delivery_Error = ReminderServiceBase + 15, RS_Not_Started = ReminderServiceBase + 16, RS_UnregisterGrain_TableError = ReminderServiceBase + 17, RS_GrainBasedTable1 = ReminderServiceBase + 18, RS_Factory1 = ReminderServiceBase + 19, RS_FailedToReadTableAndStartTimer = ReminderServiceBase + 20, RS_TableGrainInit1 = ReminderServiceBase + 21, RS_TableGrainInit2 = ReminderServiceBase + 22, RS_TableGrainInit3 = ReminderServiceBase + 23, RS_GrainBasedTable2 = ReminderServiceBase + 24, RS_ServiceStarting = ReminderServiceBase + 25, RS_ServiceStarted = ReminderServiceBase + 26, RS_ServiceStopping = ReminderServiceBase + 27, RS_RegisterOrUpdate = ReminderServiceBase + 28, RS_Unregister = ReminderServiceBase + 29, RS_Stop = ReminderServiceBase + 30, RS_RemoveFromTable = ReminderServiceBase + 31, RS_GetReminder = ReminderServiceBase + 32, RS_GetReminders = ReminderServiceBase + 33, RS_RangeChanged = ReminderServiceBase + 34, RS_LocalStop = ReminderServiceBase + 35, RS_Started = ReminderServiceBase + 36, RS_ServiceInitialLoadFailing = ReminderServiceBase + 37, RS_ServiceInitialLoadFailed = ReminderServiceBase + 38, RS_FastReminderInterval = ReminderServiceBase + 39, // Codes for the Consistent Ring Provider ConsistentRingProviderBase = Runtime + 3000, CRP_Local_Subscriber_Exception = ConsistentRingProviderBase + 1, CRP_ForGrains_Local_Subscriber_Exception_1 = ConsistentRingProviderBase + 2, CRP_Added_Silo = ConsistentRingProviderBase + 3, CRP_Removed_Silo = ConsistentRingProviderBase + 4, CRP_Notify = ConsistentRingProviderBase + 5, CRP_ForGrains_Local_Subscriber_Exception_2 = ConsistentRingProviderBase + 6, ProviderManagerBase = Runtime + 3100, Provider_InstanceConstructionError1 = ProviderManagerBase + 1, Provider_Loaded = ProviderManagerBase + 2, Provider_AssemblyLoadError = ProviderManagerBase + 3, Provider_CatalogNoStorageProvider_1 = ProviderManagerBase + 4, Provider_CatalogNoStorageProvider_2 = ProviderManagerBase + 5, Provider_CatalogStorageProviderAllocated = ProviderManagerBase + 6, Provider_NoDefaultProvider = ProviderManagerBase + 7, Provider_ConfiguredProviderNotLoaded = ProviderManagerBase + 8, Provider_ErrorFromInit = ProviderManagerBase + 9, Provider_IgnoringExplicitSet = ProviderManagerBase + 10, Provider_NotLoaded = ProviderManagerBase + 11, Provider_Manager_Already_Loaded = ProviderManagerBase + 12, Provider_CatalogNoStorageProvider_3 = ProviderManagerBase + 13, Provider_ProviderLoadedOk = ProviderManagerBase + 14, Provider_ProviderNotFound = ProviderManagerBase + 15, Provider_ProviderNotControllable = ProviderManagerBase + 16, Provider_CatalogNoLogConsistencyProvider = ProviderManagerBase + 17, Provider_CatalogLogConsistencyProviderAllocated = ProviderManagerBase + 18, Provider_ErrorFromClose = ProviderManagerBase + 19, PersistentStreamPullingAgentBase = Runtime + 3300, PersistentStreamPullingAgent_01 = PersistentStreamPullingAgentBase + 1, PersistentStreamPullingAgent_02 = PersistentStreamPullingAgentBase + 2, PersistentStreamPullingAgent_03 = PersistentStreamPullingAgentBase + 3, PersistentStreamPullingAgent_04 = PersistentStreamPullingAgentBase + 4, PersistentStreamPullingAgent_05 = PersistentStreamPullingAgentBase + 5, PersistentStreamPullingAgent_06 = PersistentStreamPullingAgentBase + 6, PersistentStreamPullingAgent_07 = PersistentStreamPullingAgentBase + 7, PersistentStreamPullingAgent_08 = PersistentStreamPullingAgentBase + 8, PersistentStreamPullingAgent_09 = PersistentStreamPullingAgentBase + 9, PersistentStreamPullingAgent_10 = PersistentStreamPullingAgentBase + 10, PersistentStreamPullingAgent_11 = PersistentStreamPullingAgentBase + 11, PersistentStreamPullingAgent_12 = PersistentStreamPullingAgentBase + 12, PersistentStreamPullingAgent_13 = PersistentStreamPullingAgentBase + 13, PersistentStreamPullingAgent_14 = PersistentStreamPullingAgentBase + 14, PersistentStreamPullingAgent_15 = PersistentStreamPullingAgentBase + 15, PersistentStreamPullingAgent_16 = PersistentStreamPullingAgentBase + 16, PersistentStreamPullingAgent_17 = PersistentStreamPullingAgentBase + 17, PersistentStreamPullingAgent_18 = PersistentStreamPullingAgentBase + 18, PersistentStreamPullingAgent_19 = PersistentStreamPullingAgentBase + 19, PersistentStreamPullingAgent_20 = PersistentStreamPullingAgentBase + 20, PersistentStreamPullingAgent_21 = PersistentStreamPullingAgentBase + 21, PersistentStreamPullingAgent_22 = PersistentStreamPullingAgentBase + 22, PersistentStreamPullingAgent_23 = PersistentStreamPullingAgentBase + 23, PersistentStreamPullingAgent_24 = PersistentStreamPullingAgentBase + 24, PersistentStreamPullingAgent_25 = PersistentStreamPullingAgentBase + 25, PersistentStreamPullingAgent_26 = PersistentStreamPullingAgentBase + 26, PersistentStreamPullingAgent_27 = PersistentStreamPullingAgentBase + 27, PersistentStreamPullingAgent_28 = PersistentStreamPullingAgentBase + 28, StreamProviderManagerBase = Runtime + 3400, StreamProvider_FailedToDispose = StreamProviderManagerBase + 1, StreamProvider_ProducerFailedToUnregister = StreamProviderManagerBase + 2, StreamProvider_NoStreamForItem = StreamProviderManagerBase + 3, StreamProvider_AddObserverException = StreamProviderManagerBase + 4, Stream_ExtensionNotInstalled = StreamProviderManagerBase + 5, Stream_ProducerIsDead = StreamProviderManagerBase + 6, StreamProvider_NoStreamForBatch = StreamProviderManagerBase + 7, StreamProvider_ConsumerFailedToUnregister = StreamProviderManagerBase + 8, Stream_ConsumerIsDead = StreamProviderManagerBase + 9, Stream_RegisterProducerFailed = StreamProviderManagerBase + 10, Stream_UnregisterProducerFailed = StreamProviderManagerBase + 11, Stream_RegisterConsumerFailed = StreamProviderManagerBase + 12, Stream_UnregisterConsumerFailed = StreamProviderManagerBase + 13, Stream_SetSubscriptionToFaultedFailed = StreamProviderManagerBase + 14, PersistentStreamPullingManagerBase = Runtime + 3500, PersistentStreamPullingManager_01 = PersistentStreamPullingManagerBase + 1, PersistentStreamPullingManager_02 = PersistentStreamPullingManagerBase + 2, PersistentStreamPullingManager_03 = PersistentStreamPullingManagerBase + 3, PersistentStreamPullingManager_04 = PersistentStreamPullingManagerBase + 4, PersistentStreamPullingManager_05 = PersistentStreamPullingManagerBase + 5, PersistentStreamPullingManager_06 = PersistentStreamPullingManagerBase + 6, PersistentStreamPullingManager_07 = PersistentStreamPullingManagerBase + 7, PersistentStreamPullingManager_08 = PersistentStreamPullingManagerBase + 8, PersistentStreamPullingManager_09 = PersistentStreamPullingManagerBase + 9, PersistentStreamPullingManager_10 = PersistentStreamPullingManagerBase + 10, PersistentStreamPullingManager_11 = PersistentStreamPullingManagerBase + 11, PersistentStreamPullingManager_12 = PersistentStreamPullingManagerBase + 12, PersistentStreamPullingManager_13 = PersistentStreamPullingManagerBase + 13, PersistentStreamPullingManager_14 = PersistentStreamPullingManagerBase + 14, PersistentStreamPullingManager_15 = PersistentStreamPullingManagerBase + 15, PersistentStreamPullingManager_16 = PersistentStreamPullingManagerBase + 16, PersistentStreamPullingManager_Starting = PersistentStreamPullingManagerBase + 17, PersistentStreamPullingManager_Stopping = PersistentStreamPullingManagerBase + 18, PersistentStreamPullingManager_Started = PersistentStreamPullingManagerBase + 19, PersistentStreamPullingManager_Stopped = PersistentStreamPullingManagerBase + 20, PersistentStreamPullingManager_AlreadyStarted = PersistentStreamPullingManagerBase + 21, PersistentStreamPullingManager_AlreadyStopped = PersistentStreamPullingManagerBase + 22, PersistentStreamPullingManager_PeriodicPrint = PersistentStreamPullingManagerBase + 23, AzureServiceRuntimeWrapper = Runtime + 3700, AzureServiceRuntime_NotLoaded = AzureServiceRuntimeWrapper + 1, AzureServiceRuntime_FailedToLoad = AzureServiceRuntimeWrapper + 2, CodeGenBase = Runtime + 3800, CodeGenCompilationFailed = CodeGenBase + 1, CodeGenCompilationSucceeded = CodeGenBase + 2, CodeGenSourceGenerated = CodeGenBase + 3, CodeGenSerializerGenerator = CodeGenBase + 4, CodeGenIgnoringTypes = CodeGenBase + 5, CodeGenDllMissing = CodeGenBase + 6, CodeGenSystemTypeRequiresSerializer = CodeGenBase + 7, MultiClusterNetworkBase = Runtime + 3900, MultiClusterNetwork_Starting = MultiClusterNetworkBase + 1, MultiClusterNetwork_Started = MultiClusterNetworkBase + 2, MultiClusterNetwork_FailedToStart = MultiClusterNetworkBase + 3, MultiClusterNetwork_LocalSubscriberException = MultiClusterNetworkBase + 4, MultiClusterNetwork_GossipCommunicationFailure = MultiClusterNetworkBase + 5, MultiClusterNetwork_NoChannelsConfigured = MultiClusterNetworkBase + 6, CancellationTokenManagerBase = Runtime + 4000, CancellationTokenCancelFailed = CancellationTokenManagerBase + 1, CancellationExtensionCreationFailed = CancellationTokenManagerBase + 2, GlobalSingleInstanceBase = Runtime + 4100, GlobalSingleInstance_ProtocolError = GlobalSingleInstanceBase + 1, GlobalSingleInstance_WarningInvalidOrigin = GlobalSingleInstanceBase + 2, GlobalSingleInstance_MaintainerException = GlobalSingleInstanceBase + 3, GlobalSingleInstance_MultipleOwners = GlobalSingleInstanceBase + 4, TypeManagerBase = Runtime + 4200, TypeManager_GetSiloGrainInterfaceMapError = TypeManagerBase + 1, TypeManager_GetClusterGrainTypeResolverError = TypeManagerBase + 2, LogConsistencyBase = Runtime + 4300, LogConsistency_UserCodeException = LogConsistencyBase + 1, LogConsistency_CaughtException = LogConsistencyBase + 2, LogConsistency_ProtocolError = LogConsistencyBase + 3, LogConsistency_ProtocolFatalError = LogConsistencyBase + 4, // Note: individual Service Fabric error codes are defined in // Microsoft.Orleans.ServiceFabric.Utilities.ErrorCode. ServiceFabricBase = Runtime + 4400, TransactionsBase = Runtime + 4500, Transactions_SendingTMRequest = TransactionsBase + 1, Transactions_ReceivedTMResponse = TransactionsBase + 2, Transactions_TMError = TransactionsBase + 3, OSBase = Runtime + 4600, OS_InvalidOS = OSBase + 1 } } // ReSharper restore InconsistentNaming ================================================ FILE: src/Orleans.Core.Abstractions/Logging/LogFormatter.cs ================================================ using System; using System.Collections.Concurrent; using System.Globalization; using System.Reflection; using System.Text; namespace Orleans.Runtime { /// /// Formats values for logging purposes. /// public static class LogFormatter { public const int MAX_LOG_MESSAGE_SIZE = 20000; private const string TIME_FORMAT = "HH:mm:ss.fff 'GMT'"; // Example: 09:50:43.341 GMT private const string DATE_FORMAT = "yyyy-MM-dd " + TIME_FORMAT; // Example: 2010-09-02 09:50:43.341 GMT - Variant of UniversalSorta­bleDateTimePat­tern private static readonly ConcurrentDictionary> exceptionDecoders = new ConcurrentDictionary>(); /// /// Utility function to convert a DateTime object into printable data format used by the Logger subsystem. /// /// The DateTime value to be printed. /// Formatted string representation of the input data, in the printable format used by the Logger subsystem. public static string PrintDate(DateTime date) { return date.ToString(DATE_FORMAT, CultureInfo.InvariantCulture); } /// /// Parses a date. /// /// The date string. /// The parsed date. public static DateTime ParseDate(string dateStr) { return DateTime.ParseExact(dateStr, DATE_FORMAT, CultureInfo.InvariantCulture); } /// /// Utility function to convert a DateTime object into printable time format used by the Logger subsystem. /// /// The DateTime value to be printed. /// Formatted string representation of the input data, in the printable format used by the Logger subsystem. public static string PrintTime(DateTime date) { return date.ToString(TIME_FORMAT, CultureInfo.InvariantCulture); } /// /// Utility function to convert an exception into printable format, including expanding and formatting any nested sub-expressions. /// /// The exception to be printed. /// Formatted string representation of the exception, including expanding and formatting any nested sub-expressions. public static string PrintException(Exception exception) { if (exception == null) return ""; var sb = new StringBuilder(); PrintException_Helper(sb, exception, 0); return sb.ToString(); } /// /// Configures the exception decoder for the specified exception type. /// /// The exception type to configure a decoder for. /// The decoder. public static void SetExceptionDecoder(Type exceptionType, Func decoder) { exceptionDecoders.TryAdd(exceptionType, decoder); } private static void PrintException_Helper(StringBuilder sb, Exception exception, int level) { if (exception == null) return; var message = exceptionDecoders.TryGetValue(exception.GetType(), out var decoder) ? decoder(exception) : exception.Message; sb.Append($"{Environment.NewLine}Exc level {level}: {exception.GetType()}: {message}"); if (exception.StackTrace is { } stack) { sb.Append($"{Environment.NewLine}{stack}"); } if (exception is ReflectionTypeLoadException typeLoadException) { var loaderExceptions = typeLoadException.LoaderExceptions; if (loaderExceptions == null || loaderExceptions.Length == 0) { sb.Append("No LoaderExceptions found"); } else { foreach (Exception? inner in loaderExceptions) { if (inner is not null) { // call recursively on all loader exceptions. Same level for all. PrintException_Helper(sb, inner, level + 1); } } } } else if (exception.InnerException != null) { if (exception is AggregateException { InnerExceptions: { Count: > 1 } innerExceptions }) { foreach (Exception inner in innerExceptions) { // call recursively on all inner exceptions. Same level for all. PrintException_Helper(sb, inner, level + 1); } } else { // call recursively on a single inner exception. PrintException_Helper(sb, exception.InnerException, level + 1); } } } } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/ClusterManifest.cs ================================================ using System; using System.Collections.Immutable; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Information about types which are available in the cluster. /// [Serializable, GenerateSerializer, Immutable] public sealed class ClusterManifest { /// /// Initializes a new instance of the class. /// /// /// The manifest version. /// /// /// The silo manifests. /// public ClusterManifest( MajorMinorVersion version, ImmutableDictionary silos) { Version = version; Silos = silos; AllGrainManifests = silos.Values.ToImmutableArray(); } /// /// Gets the version of this instance. /// [Id(0)] public MajorMinorVersion Version { get; } /// /// Gets the manifests for each silo in the cluster. /// [Id(1)] public ImmutableDictionary Silos { get; } /// /// Gets all grain manifests. /// [Id(2)] public ImmutableArray AllGrainManifests { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/GrainInterfaceProperties.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Text; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Information about a communication interface. /// [Serializable, GenerateSerializer, Immutable] public sealed class GrainInterfaceProperties { /// /// Initializes a new instance of the class. /// /// /// The interface property values. /// public GrainInterfaceProperties(ImmutableDictionary values) { this.Properties = values; } /// /// Gets the properties. /// [Id(0)] public ImmutableDictionary Properties { get; } /// /// Returns a detailed string representation of this instance. /// /// /// A detailed, string representation of this instance. /// public string ToDetailedString() { if (this.Properties is null) return string.Empty; var result = new StringBuilder("["); bool first = true; foreach (var entry in this.Properties) { if (!first) { result.Append(", "); } result.Append($"\"{entry.Key}\": \"{entry.Value}\""); first = false; } result.Append("]"); return result.ToString(); } } /// /// Well-known grain interface property keys. /// /// public static class WellKnownGrainInterfaceProperties { /// /// The version of this interface encoded as a decimal integer. /// public const string Version = "version"; /// /// The encoded corresponding to the primary implementation of an interface. /// This is used for resolving a grain type from an interface. /// public const string DefaultGrainType = "primary-grain-type"; /// /// The name of the type of this interface. Used for convention-based matching of primary implementations. /// public const string TypeName = "type-name"; } /// /// Provides grain properties. /// public interface IGrainInterfacePropertiesProvider { /// /// Adds grain interface properties to . /// /// /// The interface type. /// /// /// The interface type id. /// /// /// The properties collection which this calls to this method should populate. /// void Populate(Type interfaceType, GrainInterfaceType grainInterfaceType, Dictionary properties); } /// /// Interface for classes which provide information about a grain interface. /// public interface IGrainInterfacePropertiesProviderAttribute { /// /// Adds grain interface properties to . /// /// /// The service provider. /// /// /// The interface type. /// /// /// The properties collection which this calls to this method should populate. /// void Populate(IServiceProvider services, Type interfaceType, Dictionary properties); } /// /// Provides grain interface properties from attributes implementing . /// internal sealed class AttributeGrainInterfacePropertiesProvider : IGrainInterfacePropertiesProvider { private readonly IServiceProvider serviceProvider; /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public AttributeGrainInterfacePropertiesProvider(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } /// public void Populate(Type interfaceType, GrainInterfaceType grainInterfaceType, Dictionary properties) { foreach (var attr in interfaceType.GetCustomAttributes(inherit: true)) { if (attr is IGrainInterfacePropertiesProviderAttribute providerAttribute) { providerAttribute.Populate(this.serviceProvider, interfaceType, properties); } } } } /// /// Specifies the default grain type to use when constructing a grain reference for this interface without specifying a grain type. /// [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)] public sealed class DefaultGrainTypeAttribute : Attribute, IGrainInterfacePropertiesProviderAttribute { private readonly string grainType; /// /// Initializes a new instance of the class. /// /// /// The grain type. /// public DefaultGrainTypeAttribute(string grainType) { this.grainType = grainType; } /// void IGrainInterfacePropertiesProviderAttribute.Populate(IServiceProvider services, Type type, Dictionary properties) { properties[WellKnownGrainInterfaceProperties.DefaultGrainType] = this.grainType; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/GrainManifest.cs ================================================ using System; using System.Collections.Immutable; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Information about available grains. /// [Serializable, GenerateSerializer, Immutable] public sealed class GrainManifest { /// /// Initializes a new instance of the class. /// /// /// The grain properties. /// /// /// The interface properties. /// public GrainManifest( ImmutableDictionary grains, ImmutableDictionary interfaces) { this.Interfaces = interfaces; this.Grains = grains; } /// /// Gets the interfaces available on this silo. /// [Id(0)] public ImmutableDictionary Interfaces { get; } /// /// Gets the grain types available on this silo. /// [Id(1)] public ImmutableDictionary Grains { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/GrainProperties.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Text; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Information about a logical grain type . /// [Serializable, GenerateSerializer, Immutable] public sealed class GrainProperties { /// /// Initializes a new instance of the class. /// /// /// The underlying property collection. /// public GrainProperties(ImmutableDictionary values) { this.Properties = values; } /// /// Gets the properties. /// [Id(0)] public ImmutableDictionary Properties { get; } /// /// Returns a detailed, string representation of this instance. /// /// /// A detailed, string representation of this instance. /// public string ToDetailedString() { if (this.Properties is null) return string.Empty; var result = new StringBuilder("["); bool first = true; foreach (var entry in this.Properties) { if (!first) { result.Append(", "); } result.Append($"\"{entry.Key}\": \"{entry.Value}\""); first = false; } result.Append("]"); return result.ToString(); } } /// /// Well-known grain properties. /// /// public static class WellKnownGrainTypeProperties { /// /// The name of the placement strategy for grains of this type. /// public const string PlacementStrategy = "placement-strategy"; /// /// The name of the placement strategy for grains of this type. /// public const string PlacementFilter = "placement-filter"; /// /// The directory policy for grains of this type. /// public const string GrainDirectory = "directory-policy"; /// /// Whether or not messages to this grain are unordered. /// public const string Unordered = "unordered"; /// /// Prefix for keys which indicate of interfaces which a grain class implements. /// public const string ImplementedInterfacePrefix = "interface."; /// /// The period after which an idle grain will be deactivated. /// public const string IdleDeactivationPeriod = "idle-duration"; /// /// The value for used to specify that the grain should not be deactivated due to idleness. /// public const string IndefiniteIdleDeactivationPeriodValue = "indefinite"; /// /// The name of the primary implementation type. Used for convention-based matching of primary interface implementations. /// public const string TypeName = "type-name"; /// /// The full name of the primary implementation type. Used for prefix-based matching of implementations. /// public const string FullTypeName = "full-type-name"; /// /// The prefix for binding declarations /// public const string BindingPrefix = "binding"; /// /// The key for defining a binding type. /// public const string BindingTypeKey = "type"; /// /// The binding type for Orleans streams. /// public const string StreamBindingTypeValue = "stream"; /// /// The binding type for Broadcast Channels. /// public const string BroadcastChannelBindingTypeValue = "broadcast-channel"; /// /// The key to specify a stream binding pattern. /// public const string StreamBindingPatternKey = "pattern"; /// /// The key to specify a channel binding pattern. /// public const string BroadcastChannelBindingPatternKey = "channel-pattern"; /// /// The key to specify a stream id mapper /// public const string StreamIdMapperKey = "streamid-mapper"; /// /// The key to specify a channel id mapper /// public const string ChannelIdMapperKey = "channelid-mapper"; /// /// Whether to include the namespace name in the grain id. /// public const string StreamBindingIncludeNamespaceKey = "include-namespace"; /// /// Key type of the grain, if it implement a legacy interface. Valid values are nameof(String), nameof(Int64) and nameof(Guid) /// public const string LegacyGrainKeyType = "legacy-grain-key-type"; /// /// Whether a grain is reentrant or not. /// public const string Reentrant = "reentrant"; /// /// Specifies the name of a method used to determine if a request can interleave other requests. /// public const string MayInterleavePredicate = "may-interleave-predicate"; /// /// Whether a grain can be migrated by active-rebalancing or not. /// public const string Immovable = "immovable"; } /// /// Provides grain properties. /// public interface IGrainPropertiesProvider { /// /// Adds grain properties to . /// /// /// The grain class. /// /// /// The grain type id. /// /// /// The properties collection which calls to this method should populate. /// void Populate(Type grainClass, GrainType grainType, Dictionary properties); } /// /// Interface for classes which provide information about a grain. /// public interface IGrainPropertiesProviderAttribute { /// /// Adds grain properties to . /// /// /// The service provider. /// /// /// The grain class. /// /// /// The grain type id. /// /// /// The properties collection which calls to this method should populate. /// void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties); } /// /// Provides grain interface properties from attributes implementing . /// public sealed class AttributeGrainPropertiesProvider : IGrainPropertiesProvider { private readonly IServiceProvider serviceProvider; /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public AttributeGrainPropertiesProvider(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } /// public void Populate(Type grainClass, GrainType grainType, Dictionary properties) { foreach (var attr in grainClass.GetCustomAttributes(inherit: true)) { if (attr is IGrainPropertiesProviderAttribute providerAttribute) { providerAttribute.Populate(this.serviceProvider, grainClass, grainType, properties); } } } } /// /// Interface for classes which provide information about a grain. /// public interface IGrainBindingsProviderAttribute { /// /// Gets bindings for the type this attribute is attached to. /// /// /// The service provider. /// /// /// The grain class. /// /// /// The grain type. /// /// /// The bindings for the specified grain. /// IEnumerable> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType); } /// /// Provides grain interface properties from attributes implementing . /// public sealed class AttributeGrainBindingsProvider : IGrainPropertiesProvider { /// /// A hopefully unique name to describe bindings added by this provider. /// Binding names are meaningless and are only used to group properties for a given binding together. /// private const string BindingPrefix = WellKnownGrainTypeProperties.BindingPrefix + ".attr-"; private readonly IServiceProvider serviceProvider; /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public AttributeGrainBindingsProvider(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } /// public void Populate(Type grainClass, GrainType grainType, Dictionary properties) { var bindingIndex = 1; foreach (var attr in grainClass.GetCustomAttributes(inherit: true)) { if (!(attr is IGrainBindingsProviderAttribute providerAttribute)) { continue; } foreach (var binding in providerAttribute.GetBindings(this.serviceProvider, grainClass, grainType)) { foreach (var pair in binding) { properties[BindingPrefix + bindingIndex.ToString(CultureInfo.InvariantCulture) + '.' + pair.Key] = pair.Value; } ++bindingIndex; } } } } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/IGrainTypeProvider.cs ================================================ using System; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.Metadata { /// /// Associates a with a grain class. /// public interface IGrainTypeProvider { /// /// Returns the grain type corresponding to the class identified by . /// bool TryGetGrainType(Type type, out GrainType grainType); } /// /// Gets the corresponding for a grain class from an /// implementing on that class. /// public class AttributeGrainTypeProvider : IGrainTypeProvider { private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// /// The service provider. /// public AttributeGrainTypeProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// public bool TryGetGrainType(Type grainClass, out GrainType grainType) { foreach (var attr in grainClass.GetCustomAttributes(inherit: false)) { if (attr is IGrainTypeProviderAttribute typeProviderAttribute) { grainType = typeProviderAttribute.GetGrainType(this._serviceProvider, grainClass); return true; } } grainType = default; return false; } } /// /// Functionality which can be implemented by a custom which implements this specifies the of the /// type which it is attached to. /// public interface IGrainTypeProviderAttribute { /// /// Gets the for the attached . /// /// /// The service provider. /// /// /// The grain class. /// GrainType GetGrainType(IServiceProvider services, Type type); } } namespace Orleans { /// /// Specifies the grain type of the grain class which it is attached to. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class GrainTypeAttribute : Attribute, IGrainTypeProviderAttribute { /// /// The grain type name. /// private readonly GrainType _grainType; /// /// Initializes a new instance of the class. /// /// /// The grain type name. /// public GrainTypeAttribute(string grainType) { this._grainType = GrainType.Create(grainType); } /// public GrainType GetGrainType(IServiceProvider services, Type type) => this._grainType; } } ================================================ FILE: src/Orleans.Core.Abstractions/Manifest/MajorMinorVersion.cs ================================================ using System; namespace Orleans.Metadata { /// /// Represents a version with two components, a major (most-significant) component, and a minor (least-significant) component. /// [Serializable, GenerateSerializer, Immutable] public readonly struct MajorMinorVersion : IComparable, IEquatable { /// /// Initializes a new instance of the struct. /// /// The major version component. /// The minor version component. public MajorMinorVersion(long majorVersion, long minorVersion) { Major = majorVersion; Minor = minorVersion; } /// /// Gets the zero value. /// public static MajorMinorVersion Zero => new(0, 0); /// /// Gets the minimum value. /// public static MajorMinorVersion MinValue => new(long.MinValue, long.MinValue); /// /// Gets the most significant version component. /// [Id(0)] public long Major { get; } /// /// Gets the least significant version component. /// [Id(1)] public long Minor { get; } /// public int CompareTo(MajorMinorVersion other) { var major = Major.CompareTo(other.Major); if (major != 0) return major; return Minor.CompareTo(other.Minor); } /// public bool Equals(MajorMinorVersion other) => Major == other.Major && Minor == other.Minor; /// public override bool Equals(object? obj) => obj is MajorMinorVersion other && this.Equals(other); /// public override int GetHashCode() => HashCode.Combine(Major, Minor); /// /// Parses a . /// /// /// The string representation. /// /// /// The parsed value. /// public static MajorMinorVersion Parse(string value) { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value)); var i = value.IndexOf('.'); if (i < 0) throw new ArgumentException(nameof(value)); return new MajorMinorVersion(long.Parse(value[..i]), long.Parse(value[(i + 1)..])); } /// public override string ToString() => $"{Major}.{Minor}"; /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(MajorMinorVersion left, MajorMinorVersion right) => left.Equals(right); /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(MajorMinorVersion left, MajorMinorVersion right) => !left.Equals(right); /// /// Compares the provided operands and returns if the left operand is greater than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than or equal to the right operand, otherwise . public static bool operator >=(MajorMinorVersion left, MajorMinorVersion right) => left.CompareTo(right) >= 0; /// /// Compares the provided operands and returns if the left operand is less than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than or equal to the right operand, otherwise . public static bool operator <=(MajorMinorVersion left, MajorMinorVersion right) => left.CompareTo(right) <= 0; /// /// Compares the provided operands and returns if the left operand is greater than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than the right operand, otherwise . public static bool operator >(MajorMinorVersion left, MajorMinorVersion right) => left.CompareTo(right) > 0; /// /// Compares the provided operands and returns if the left operand is less than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than the right operand, otherwise . public static bool operator <(MajorMinorVersion left, MajorMinorVersion right) => left.CompareTo(right) < 0; } } ================================================ FILE: src/Orleans.Core.Abstractions/Orleans.Core.Abstractions.csproj ================================================ Microsoft.Orleans.Core.Abstractions Microsoft Orleans Core Abstractions Core abstractions library of Microsoft Orleans $(DefaultTargetFrameworks) true true Orleans README.md enable %(Identity) true true %(Identity) true true %(Identity) true true ================================================ FILE: src/Orleans.Core.Abstractions/Placement/ActivationCountBasedPlacement.cs ================================================ using System; namespace Orleans.Runtime { /// /// A placement strategy which attempts to achieve approximately even load based upon the number of recently-active grains on each server. /// /// /// The intention of this placement strategy is to place new grain activations on the least heavily loaded server based on the number of recently busy grains. /// It includes a mechanism in which all servers periodically publish their total activation count to all other servers. /// The placement director then selects a server which is predicted to have the fewest activations by examining the most recently /// reported activation count and a making prediction of the current activation count based upon the recent activation count made by /// the placement director on the current server. The director selects a number of servers at random when making this prediction, /// in an attempt to avoid multiple separate servers overloading the same server. By default, two servers are selected at random, /// but this value is configurable via Orleans.Runtime.ActivationCountBasedPlacementOptions. ///
    /// This algorithm is based on the thesis The Power of Two Choices in Randomized Load Balancing by Michael David Mitzenmacher , /// and is also used in NGINX for distributed load balancing, as described in the article NGINX and the "Power of Two Choices" Load-Balancing Algorithm . ///
    /// This placement strategy is configured by adding the attribute to a grain. ///
    [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class ActivationCountBasedPlacement : PlacementStrategy { /// /// Gets the singleton instance of this class. /// internal static ActivationCountBasedPlacement Singleton { get; } = new ActivationCountBasedPlacement(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/ClientObserversPlacement.cs ================================================ namespace Orleans.Runtime { [GenerateSerializer, Immutable, SuppressReferenceTracking] internal class ClientObserversPlacement : PlacementStrategy { public static ClientObserversPlacement Instance { get; } = new(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/HashBasedPlacement.cs ================================================ using System; namespace Orleans.Runtime { /// /// Places activations on compatible silos by hashing the grain identifier using a stable hash and selecting a silo from a sorted set using a modulo operation. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class HashBasedPlacement : PlacementStrategy { internal static HashBasedPlacement Singleton { get; } = new HashBasedPlacement(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/PlacementAttribute.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; using Orleans.Runtime; using static Orleans.Placement.ImmovableAttribute; namespace Orleans.Placement { /// /// Base for all placement policy marker attributes. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public abstract class PlacementAttribute : Attribute, IGrainPropertiesProviderAttribute { public PlacementStrategy PlacementStrategy { get; private set; } protected PlacementAttribute(PlacementStrategy placement) { if (placement == null) throw new ArgumentNullException(nameof(placement)); this.PlacementStrategy = placement; } /// public virtual void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { this.PlacementStrategy?.PopulateGrainProperties(services, grainClass, grainType, properties); } } /// /// Marks a grain class as using the policy. /// /// /// This is the default placement policy, so this attribute does not need to be used for normal grains. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class RandomPlacementAttribute : PlacementAttribute { public RandomPlacementAttribute() : base(RandomPlacement.Singleton) { } } /// /// Marks a grain class as using the policy. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class HashBasedPlacementAttribute : PlacementAttribute { public HashBasedPlacementAttribute() : base(HashBasedPlacement.Singleton) { } } /// /// Marks a grain class as using the policy. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class PreferLocalPlacementAttribute : PlacementAttribute { /// /// Initializes a new instance of the class. /// public PreferLocalPlacementAttribute() : base(PreferLocalPlacement.Singleton) { } } /// /// Marks a grain class as using the policy, which attempts to balance /// grain placement across servers based upon the relative number of recently active grains on each one. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class ActivationCountBasedPlacementAttribute : PlacementAttribute { /// /// Initializes a new instance of the class. /// public ActivationCountBasedPlacementAttribute() : base(ActivationCountBasedPlacement.Singleton) { } } /// /// Marks a grain class as using the policy. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class SiloRoleBasedPlacementAttribute : PlacementAttribute { public SiloRoleBasedPlacementAttribute() : base(SiloRoleBasedPlacement.Singleton) { } } /// /// Marks a grain class as using the policy. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class ResourceOptimizedPlacementAttribute : PlacementAttribute { public ResourceOptimizedPlacementAttribute() : base(ResourceOptimizedPlacement.Singleton) { } } /// /// Ensures that activations of this grain type will not be migrated automatically. /// /// Activations can still be migrated by user initiated code. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class ImmovableAttribute(ImmovableKind kind = ImmovableKind.Any) : Attribute, IGrainPropertiesProviderAttribute { /// /// The kind of immovability. /// public ImmovableKind Kind { get; } = kind; /// public void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) => properties[WellKnownGrainTypeProperties.Immovable] = ((byte)Kind).ToString(); } /// /// Emphasizes that immovability is restricted to certain components. /// [Flags] public enum ImmovableKind : byte { /// /// Activations of this grain type will not be migrated by the repartitioner. /// Repartitioner = 1, /// /// Activations of this grain type will not be migrated by the rebalancer. /// Rebalancer = 2, /// /// Activations of this grain type will not be migrated by anything. /// Any = Repartitioner | Rebalancer } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/PlacementFilterAttribute.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.Placement; /// /// Base for all placement filter marker attributes. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public abstract class PlacementFilterAttribute : Attribute, IGrainPropertiesProviderAttribute { /// /// Gets the placement filter strategy. /// public PlacementFilterStrategy PlacementFilterStrategy { get; private set; } protected PlacementFilterAttribute(PlacementFilterStrategy placement) { ArgumentNullException.ThrowIfNull(placement); PlacementFilterStrategy = placement; } /// public virtual void Populate(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) => PlacementFilterStrategy?.PopulateGrainProperties(services, grainClass, grainType, properties); } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/PlacementFilterStrategy.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.Placement; /// /// Represents a strategy for filtering silos which a grain can be placed on. /// public abstract class PlacementFilterStrategy { public int Order { get; private set; } protected PlacementFilterStrategy(int order) { Order = order; } /// /// Initializes an instance of this type using the provided grain properties. /// /// /// The grain properties. /// public void Initialize(GrainProperties properties) { var orderProperty = GetPlacementFilterGrainProperty("order", properties); if (!int.TryParse(orderProperty, out var parsedOrder)) { throw new ArgumentException("Invalid order property value."); } Order = parsedOrder; AdditionalInitialize(properties); } public virtual void AdditionalInitialize(GrainProperties properties) { } /// /// Populates grain properties to specify the preferred placement strategy. /// /// The service provider. /// The grain class. /// The grain type. /// The grain properties which will be populated by this method call. public void PopulateGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { var typeName = GetType().Name; if (properties.TryGetValue(WellKnownGrainTypeProperties.PlacementFilter, out var existingValue)) { properties[WellKnownGrainTypeProperties.PlacementFilter] = $"{existingValue},{typeName}"; } else { properties[WellKnownGrainTypeProperties.PlacementFilter] = typeName; } properties[$"{WellKnownGrainTypeProperties.PlacementFilter}.{typeName}.order"] = Order.ToString(CultureInfo.InvariantCulture); foreach (var additionalGrainProperty in GetAdditionalGrainProperties(services, grainClass, grainType, properties)) { properties[$"{WellKnownGrainTypeProperties.PlacementFilter}.{typeName}.{additionalGrainProperty.Key}"] = additionalGrainProperty.Value; } } protected string? GetPlacementFilterGrainProperty(string key, GrainProperties properties) { var typeName = GetType().Name; return properties.Properties.TryGetValue($"{WellKnownGrainTypeProperties.PlacementFilter}.{typeName}.{key}", out var value) ? value : null; } protected virtual IEnumerable> GetAdditionalGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, IReadOnlyDictionary existingProperties) => Array.Empty>(); } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/PlacementStrategy.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; namespace Orleans.Runtime { /// /// The base type for all placement strategies. /// /// /// Orleans uses a configurable placement system to decide which server to place a grain on. /// Placement directors are used to decide where a grain activation should be placed. /// Placement directors are associated with grains using a placement strategy. /// Grains indicate their preferred placement strategy using an attribute on the grain class. /// [Serializable, SerializerTransparent] public abstract class PlacementStrategy { /// /// Gets a value indicating whether or not this placement strategy requires activations to be registered in /// the grain directory. /// public virtual bool IsUsingGrainDirectory => true; /// /// Initializes an instance of this type using the provided grain properties. /// /// /// The grain properties. /// public virtual void Initialize(GrainProperties properties) { } /// /// Populates grain properties to specify the preferred placement strategy. /// /// The service provider. /// The grain class. /// The grain type. /// The grain properties which will be populated by this method call. public virtual void PopulateGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[WellKnownGrainTypeProperties.PlacementStrategy] = this.GetType().Name; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/PreferLocalPlacement.cs ================================================ using System; namespace Orleans.Runtime { /// /// The prefer local placement strategy indicates that a grain should always be placed on the local host if the grain /// is not already active elsewhere in the cluster and the local host is compatible with it. /// /// /// If the host is not compatible with the grain type or if a grain receives an incompatible request, the grain will be /// placed on a random, compatible server. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class PreferLocalPlacement : PlacementStrategy { /// /// Gets the singleton instance of this class. /// internal static PreferLocalPlacement Singleton { get; } = new PreferLocalPlacement(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/RandomPlacement.cs ================================================ using System; namespace Orleans.Runtime { /// /// The random placement strategy specifies that new activations of a grain should be placed on a random, compatible server. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class RandomPlacement : PlacementStrategy { /// /// Gets the singleton instance of this class. /// internal static RandomPlacement Singleton { get; } = new RandomPlacement(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/ResourceOptimizedPlacement.cs ================================================ namespace Orleans.Runtime; /// /// A placement strategy which attempts to optimize resource distribution across the cluster. /// /// /// It assigns weights to runtime statistics to prioritize different resources and calculates a normalized score for each silo. /// Following the power of k-choices algorithm, K silos are picked as potential targets, where K is equal to the square root of the number of silos. /// Out of those K silos, the one with the lowest score is chosen for placing the activation. Normalization ensures that each property contributes proportionally /// to the overall score. You can adjust the weights based on your specific requirements and priorities for load balancing. /// In addition to normalization, an online adaptive algorithm provides a smoothing effect (filters out high frequency components) and avoids rapid signal /// drops by transforming it into a polynomial-like decay process. This contributes to avoiding resource saturation on the silos and especially newly joined silos. /// Silos which are overloaded by definition of the load shedding mechanism are not considered as candidates for new placements. /// This placement strategy is configured by adding the attribute to a grain. /// [GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class ResourceOptimizedPlacement : PlacementStrategy { internal static readonly ResourceOptimizedPlacement Singleton = new(); } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/SiloRoleBasedPlacement.cs ================================================ using System; namespace Orleans.Runtime { /// /// The silo role placement strategy specifies that a grain should be placed on a compatible silo which has the role specified by the strategy's placement attribute. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public class SiloRoleBasedPlacement : PlacementStrategy { /// /// Gets the singleton instance of this class. /// internal static SiloRoleBasedPlacement Singleton { get; } = new SiloRoleBasedPlacement(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/StatelessWorkerPlacement.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using Orleans.Metadata; namespace Orleans.Runtime { /// /// The stateless worker placement strategy allows multiple instances of a given grain to co-exist simultaneously on any host and is reserved for stateless worker grains. /// [Serializable, GenerateSerializer] internal sealed class StatelessWorkerPlacement : PlacementStrategy { private const string MaxLocalPropertyKey = "max-local-instances"; private const string RemoveIdleWorkersPropertyKey = "remove-idle-workers"; private static readonly int DefaultMaxStatelessWorkers = Environment.ProcessorCount; /// public override bool IsUsingGrainDirectory => false; /// /// Gets the maximum number of local instances which can be simultaneously active for a given grain. /// [Id(0)] public int MaxLocal { get; private set; } /// /// When set to , idle workers will be proactively deactivated by the runtime. /// Otherwise if , than the workers will be deactivated according to collection age. /// [Id(1)] public bool RemoveIdleWorkers { get; private set; } = true; /// /// Initializes a new instance of the class. /// /// /// The maximum number of local instances which can be simultaneously active for a given grain. /// internal StatelessWorkerPlacement(int maxLocal) : this(maxLocal, true) { } /// /// Initializes a new instance of the class. /// /// /// The maximum number of local instances which can be simultaneously active for a given grain. /// /// /// Whether idle workers will be proactively deactivated by the runtime instead of only being deactivated according to collection age. /// internal StatelessWorkerPlacement(int maxLocal, bool removeIdleWorkers) { // If maxLocal was not specified on the StatelessWorkerAttribute, // we will use the defaultMaxStatelessWorkers, which is System.Environment.ProcessorCount. this.MaxLocal = maxLocal > 0 ? maxLocal : DefaultMaxStatelessWorkers; this.RemoveIdleWorkers = removeIdleWorkers; } /// /// Initializes a new instance of the class. /// public StatelessWorkerPlacement() : this(-1) { } /// public override string ToString() => $"StatelessWorkerPlacement(MaxLocal={MaxLocal}, RemoveIdleWorkers={RemoveIdleWorkers})"; /// public override void Initialize(GrainProperties properties) { base.Initialize(properties); if (properties.Properties.TryGetValue(MaxLocalPropertyKey, out var maxLocalValue) && !string.IsNullOrWhiteSpace(maxLocalValue)) { if (int.TryParse(maxLocalValue, out var maxLocal)) { MaxLocal = maxLocal; } } if (properties.Properties.TryGetValue(RemoveIdleWorkersPropertyKey, out var removeIdleValue) && !string.IsNullOrWhiteSpace(removeIdleValue)) { if (bool.TryParse(removeIdleValue, out var removeIdle)) { RemoveIdleWorkers = removeIdle; } } } /// public override void PopulateGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, Dictionary properties) { properties[MaxLocalPropertyKey] = MaxLocal.ToString(CultureInfo.InvariantCulture); properties[RemoveIdleWorkersPropertyKey] = RemoveIdleWorkers.ToString(CultureInfo.InvariantCulture); base.PopulateGrainProperties(services, grainClass, grainType, properties); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Placement/SystemTargetPlacementStrategy.cs ================================================ namespace Orleans.Runtime { /// /// The placement strategy used by system targets. /// [GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class SystemTargetPlacementStrategy : PlacementStrategy { public static SystemTargetPlacementStrategy Instance { get; } = new(); public override bool IsUsingGrainDirectory => false; } } ================================================ FILE: src/Orleans.Core.Abstractions/Properties/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } ================================================ FILE: src/Orleans.Core.Abstractions/Providers/IProviderBuilder.cs ================================================ using Microsoft.Extensions.Configuration; namespace Orleans.Providers; /// /// Interface for providers which configure Orleans services. /// /// The type of the builder, such as ISiloBuilder or IClientBuilder. public interface IProviderBuilder { /// /// Configures the provider. /// /// The builder. /// The provider name, or if no name is specified. /// The configuration section containing provider configuration. void Configure(TBuilder builder, string? name, IConfigurationSection configurationSection); } ================================================ FILE: src/Orleans.Core.Abstractions/Providers/ProviderConstants.cs ================================================ namespace Orleans.Providers { /// /// Constant values used by providers. /// public static class ProviderConstants { /// /// The default storage provider name. /// public const string DEFAULT_STORAGE_PROVIDER_NAME = "Default"; /// /// The default log consistency provider name. /// public const string DEFAULT_LOG_CONSISTENCY_PROVIDER_NAME = "Default"; public const string DEFAULT_PUBSUB_PROVIDER_NAME = "PubSubStore"; } } ================================================ FILE: src/Orleans.Core.Abstractions/Providers/ProviderGrainAttributes.cs ================================================ using System; namespace Orleans.Providers { /// /// The [Orleans.Providers.StorageProvider] attribute is used to define which storage provider to use for persistence of grain state. /// /// Specifying [Orleans.Providers.StorageProvider] property is recommended for all grains which extend Grain<T>. /// If no [Orleans.Providers.StorageProvider] attribute is specified, then a "Default" storage provider will be used. /// If a suitable storage provider cannot be located for this grain, then the grain will fail to load into the Silo. /// /// [AttributeUsage(AttributeTargets.Class)] public sealed class StorageProviderAttribute : Attribute { /// /// Gets or sets the name of the provider to be used for persisting of grain state. /// public string ProviderName { get; set; } /// /// Initializes a new instance of the class. /// public StorageProviderAttribute() { ProviderName = ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME; } } /// /// The [Orleans.Providers.LogConsistencyProvider] attribute is used to define which consistency provider to use for grains using the log-view state abstraction. /// /// Specifying [Orleans.Providers.LogConsistencyProvider] property is recommended for all grains that derive /// from LogConsistentGrain, such as JournaledGrain. /// If no [Orleans.Providers.LogConsistencyProvider] attribute is specified, then the runtime tries to locate /// one as follows. First, it looks for a /// "Default" provider in the configuration file, then it checks if the grain type defines a default. /// If a consistency provider cannot be located for this grain, then the grain will fail to load into the Silo. /// /// [AttributeUsage(AttributeTargets.Class)] public sealed class LogConsistencyProviderAttribute : Attribute { /// /// Gets or sets name of the provider to be used for consistency. /// public string ProviderName { get; set; } /// /// Initializes a new instance of the class. /// public LogConsistencyProviderAttribute() { ProviderName = ProviderConstants.DEFAULT_LOG_CONSISTENCY_PROVIDER_NAME; } } } ================================================ FILE: src/Orleans.Core.Abstractions/README.md ================================================ # Microsoft Orleans Core Abstractions ## Introduction Microsoft Orleans Core Abstractions is the foundational library for Orleans containing the public programming APIs for implementing grains and client code. This package defines the core abstractions that form the Orleans programming model, including grain interfaces, grain reference interfaces, and attributes. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Core.Abstractions ``` This package is a dependency of both client and silo (server) applications and is automatically included when you reference the Orleans SDK or the Orleans client/server metapackages. ## Example - Defining a Grain Interface ```csharp using Orleans; namespace MyGrainInterfaces; public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain interfaces](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-interfaces) - [Grain references](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-references) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/AsyncEnumerableRequest.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Diagnostics; using Orleans.Invocation; using Orleans.Serialization.Invocation; namespace Orleans.Runtime; /// /// Identifies enumeration results. /// [GenerateSerializer] public enum EnumerationResult { /// /// This result represents a heartbeat. Issue a subsequent enumeration call to receive a new result. /// Heartbeat = 1, /// /// This result represents a value from the enumeration. /// Element = 1 << 1, /// /// This result represents a sequence of values from the enumeration. /// Batch = 1 << 2, /// /// This result indicates that enumeration has completed and that no further results will be produced. /// Completed = 1 << 3, /// /// The attempt to enumerate failed because the enumerator was not found. /// MissingEnumeratorError = 1 << 4, /// /// The attempt to enumerate failed because the enumeration threw an exception. /// Error = 1 << 5, /// /// Enumeration was canceled. /// Canceled = 1 << 6, /// /// This result indicates that enumeration has completed and that no further results will be produced. /// CompletedWithElement = Completed | Element, /// /// This result indicates that enumeration has completed and that no further results will be produced. /// CompletedWithBatch = Completed | Batch, } internal static class EnumeratorResultExtensions { private const int TerminatedMask = (int)(EnumerationResult.Completed | EnumerationResult.MissingEnumeratorError | EnumerationResult.Error | EnumerationResult.Canceled); public static bool IsTerminated(this EnumerationResult value) => ((int)value & TerminatedMask) != 0; public static bool IsActive(this EnumerationResult value) => ((int)value & TerminatedMask) == 0; } /// /// Grain extension interface for grains which return from grain methods. /// public interface IAsyncEnumerableGrainExtension : IGrainExtension { /// /// Invokes an request and begins enumeration. /// /// The element type. /// The request id, generated by the caller. /// The request. /// The result of enumeration [AlwaysInterleave] public ValueTask<(EnumerationResult Status, object Value)> StartEnumeration(Guid requestId, [Immutable] IAsyncEnumerableRequest request) => StartEnumeration(requestId, request, CancellationToken.None); /// /// Invokes an request and begins enumeration. /// /// The element type. /// The request id, generated by the caller. /// The request. /// The cancellation token. /// The result of enumeration [AlwaysInterleave] public ValueTask<(EnumerationResult Status, object Value)> StartEnumeration(Guid requestId, [Immutable] IAsyncEnumerableRequest request, CancellationToken cancellationToken); /// /// Continues enumerating an value. /// /// The element type. /// The request id, generated by the caller. /// The result of enumeration [AlwaysInterleave] public ValueTask<(EnumerationResult Status, object Value)> MoveNext(Guid requestId) => MoveNext(requestId, CancellationToken.None); /// /// Continues enumerating an value. /// /// The element type. /// The request id, generated by the caller. /// The cancellation token. /// The result of enumeration [AlwaysInterleave] public ValueTask<(EnumerationResult Status, object Value)> MoveNext(Guid requestId, CancellationToken cancellationToken); /// /// Disposes an value. /// /// The request id, generated by the caller. /// A task representing the operation. [AlwaysInterleave] public ValueTask DisposeAsync(Guid requestId); } /// /// Interface for requests to a -returning methods. /// public interface IAsyncEnumerableRequest : IRequest { /// /// Gets or sets the maximum batch size for the request. /// int MaxBatchSize { get; set; } /// /// Invokes the request. /// /// The result of invocation. IAsyncEnumerable InvokeImplementation(); } /// /// Represents a request to an -returning method. /// /// The element type. [GenerateSerializer] [SuppressReferenceTracking] [ReturnValueProxy(nameof(InitializeRequest))] public abstract class AsyncEnumerableRequest : RequestBase, IAsyncEnumerable, IAsyncEnumerableRequest { /// /// The target grain instance. /// [field: NonSerialized] internal GrainReference? TargetGrain { get; private set; } /// [Id(0)] public int MaxBatchSize { get; set; } = 100; /// public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => new AsyncEnumeratorProxy(this, cancellationToken); // Called upon creation in generated code by the creating grain reference by virtue of the [ReturnValueProxy(nameof(InitializeRequest))] attribute on this class. public IAsyncEnumerable InitializeRequest(GrainReference targetGrainReference) { TargetGrain = targetGrainReference; return this; } /// public override ValueTask Invoke() => throw new NotImplementedException($"{nameof(IAsyncEnumerable)} requests can not be invoked directly"); /// public IAsyncEnumerable InvokeImplementation() => InvokeInner(); // Generated protected abstract IAsyncEnumerable InvokeInner(); } /// /// A proxy for an instance returned from a grain method. /// internal sealed class AsyncEnumeratorProxy : IAsyncEnumerator { private readonly AsyncEnumerableRequest _request; private readonly CancellationToken _cancellationToken; private readonly CancellationTokenSource? _cancellationTokenSource; private readonly IAsyncEnumerableGrainExtension _target; private readonly Guid _requestId; private (EnumerationResult State, object Value) _current; private int _batchIndex; private bool _disposed; private bool _isInitialized; private Activity? _sessionActivity; private bool IsBatch => (_current.State & EnumerationResult.Batch) != 0; private bool IsElement => (_current.State & EnumerationResult.Element) != 0; private bool IsCompleted => (_current.State & EnumerationResult.Completed) != 0; /// /// Initializes a new instance of the class. /// /// The request which this instanced proxies. public AsyncEnumeratorProxy(AsyncEnumerableRequest request, CancellationToken cancellationToken) { Debug.Assert(request.TargetGrain is not null); _request = request; var requestCancellationToken = request.GetCancellationToken(); if (requestCancellationToken == cancellationToken) { // The same token was passed to the request and the enumerator. _cancellationToken = cancellationToken; } else if (requestCancellationToken.CanBeCanceled && cancellationToken.CanBeCanceled) { // Both are distinct, cancellable tokens. _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(requestCancellationToken, cancellationToken); _cancellationToken = _cancellationTokenSource.Token; } else if (cancellationToken.CanBeCanceled) { _cancellationToken = cancellationToken; } else { Debug.Assert(requestCancellationToken.CanBeCanceled); _cancellationToken = requestCancellationToken; } _requestId = Guid.NewGuid(); _target = _request.TargetGrain.AsReference(); } public int MaxBatchSize { get; set; } = 100; /// public T Current { get { ObjectDisposedException.ThrowIf(_disposed, this); if (IsElement) { return (T)_current.Value; } if (IsBatch) { return ((List)_current.Value)[_batchIndex]; } throw new InvalidOperationException("Cannot get current value of an invalid enumerator."); } } /// public async ValueTask DisposeAsync() { if (_disposed) { return; } if (_isInitialized) { // Restore the session activity as the current activity so that DisposeAsync RPC is parented to it var previousActivity = Activity.Current; Activity.Current = _sessionActivity; try { await _target.DisposeAsync(_requestId); } catch (Exception exception) { var logger = ((GrainReference)_target).Shared.ServiceProvider.GetRequiredService>>(); logger.LogWarning(exception, "Failed to dispose async enumerator."); } finally { Activity.Current = previousActivity; } } _cancellationTokenSource?.Dispose(); // Stop the session activity after DisposeAsync completes _sessionActivity?.Stop(); _sessionActivity?.Dispose(); _disposed = true; } /// public async ValueTask MoveNextAsync() { ObjectDisposedException.ThrowIf(_disposed, this); // Enumerate the existing batch before fetching more. if (IsBatch && ++_batchIndex < ((List)_current.Value).Count) { return true; } if (IsCompleted) { return false; } var isActive = _isInitialized; // Restore the session activity as the current activity so that RPC calls are parented to it var previousActivity = Activity.Current; if (_sessionActivity is not null) { Activity.Current = _sessionActivity; } try { (EnumerationResult Status, object Value) result; while (true) { _cancellationToken.ThrowIfCancellationRequested(); if (!_isInitialized) { // Start the session activity on first enumeration call // This span wraps the entire enumeration session _sessionActivity = ActivitySources.ApplicationGrainSource.StartActivity(_request.GetActivityName(), ActivityKind.Client); _sessionActivity?.SetTag(ActivityTagKeys.AsyncEnumerableRequestId, _requestId.ToString()); // Assume the enumerator is active as soon as the call begins. isActive = true; result = await _target.StartEnumeration(_requestId, _request, _cancellationToken); _isInitialized = true; } else { result = await _target.MoveNext(_requestId, _cancellationToken); } isActive = result.Status.IsActive(); if (result.Status is EnumerationResult.Error) { _sessionActivity?.SetStatus(ActivityStatusCode.Error); ExceptionDispatchInfo.Capture((Exception)result.Value).Throw(); } else if (result.Status is EnumerationResult.Canceled) { _sessionActivity?.SetStatus(ActivityStatusCode.Error, "Canceled"); throw new OperationCanceledException(); } if (result.Status is not EnumerationResult.Heartbeat) { break; } } if (result.Status is EnumerationResult.MissingEnumeratorError) { _sessionActivity?.SetStatus(ActivityStatusCode.Error, "MissingEnumerator"); throw new EnumerationAbortedException("Enumeration aborted: the remote target does not have a record of this enumerator." + " This likely indicates that the remote grain was deactivated since enumeration begun or that the enumerator was idle for longer than the expiration period."); } Debug.Assert((result.Status & (EnumerationResult.Element | EnumerationResult.Batch | EnumerationResult.Completed)) != 0); _batchIndex = 0; _current = result; return (result.Status & (EnumerationResult.Element | EnumerationResult.Batch)) != 0; } catch when (isActive) { // If the enumerator was active, we should try to dispose it now. await _target.DisposeAsync(_requestId).AsTask() .ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing); throw; } finally { // Restore the previous activity after each call Activity.Current = previousActivity; } } } public static class AsyncEnumerableExtensions { /// /// Specifies the maximum batch size for an request. /// /// The underlying element type. /// The instance to configure. /// The batch size. /// The original instance. public static IAsyncEnumerable WithBatchSize(this IAsyncEnumerable self, int maxBatchSize) { if (self is AsyncEnumerableRequest request) { request.MaxBatchSize = maxBatchSize; return request; } return self; } } /// /// Indicates that an enumeration was aborted. /// [GenerateSerializer] public sealed class EnumerationAbortedException : Exception { /// /// Initializes a new instance of the class. /// public EnumerationAbortedException() { } /// /// Initializes a new instance of the class. /// public EnumerationAbortedException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// public EnumerationAbortedException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// [Obsolete] protected EnumerationAbortedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/GrainContextComponentExtensions.cs ================================================ using System; using Orleans.Runtime; namespace Orleans { /// /// Extensions for related to . /// public static class GrainContextComponentExtensions { /// /// Used by generated code for interfaces. /// /// /// The type of the component to get. /// /// /// The grain context. /// /// /// The grain extension. /// public static TComponent GetGrainExtension(this IGrainContext context) where TComponent : class, IGrainExtension { var binder = context.GetComponent(); if (binder is null) { throw new InvalidOperationException($"No {nameof(IGrainExtensionBinder)} is available on the current grain context."); } return binder.GetExtension(); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/GrainLifecycleStage.cs ================================================ namespace Orleans.Runtime { /// /// Stages of a grains lifecycle. /// TODO: Add more later, see ActivationInitializationStage /// Full grain lifecycle, including register, state setup, and /// stream cleanup should all eventually be triggered by the /// grain lifecycle. /// public static class GrainLifecycleStage { /// /// First valid stage in grain's lifecycle. /// public const int First = int.MinValue; /// /// Setup grain state prior to activation. /// public const int SetupState = 1000; /// /// Activate grain. /// public const int Activate = 2000; /// /// Last valid stage in grain's lifecycle. /// public const int Last = int.MaxValue; } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/GrainReference.cs ================================================ using System; using System.Reflection; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; using System.Text; using System.Diagnostics; using System.Collections.Generic; using System.Threading; namespace Orleans.Runtime { /// /// Properties common to instances with the same and . /// public class GrainReferenceShared { public GrainReferenceShared( GrainType grainType, GrainInterfaceType grainInterfaceType, ushort interfaceVersion, IGrainReferenceRuntime runtime, InvokeMethodOptions invokeMethodOptions, CodecProvider codecProvider, CopyContextPool copyContextPool, IServiceProvider serviceProvider) { this.GrainType = grainType; this.InterfaceType = grainInterfaceType; this.Runtime = runtime; this.InvokeMethodOptions = invokeMethodOptions; this.CodecProvider = codecProvider; this.CopyContextPool = copyContextPool; this.ServiceProvider = serviceProvider; this.InterfaceVersion = interfaceVersion; } /// /// Gets the grain reference runtime. /// public IGrainReferenceRuntime Runtime { get; } /// /// Gets the grain type. /// public GrainType GrainType { get; } /// /// Gets the interface type. /// public GrainInterfaceType InterfaceType { get; } /// /// Gets the common invocation options. /// public InvokeMethodOptions InvokeMethodOptions { get; } /// /// Gets the serialization codec provider. /// public CodecProvider CodecProvider { get; } /// /// Gets the serialization copy context pool. /// public CopyContextPool CopyContextPool { get; } /// /// Gets the service provider. /// public IServiceProvider ServiceProvider { get; } /// /// Gets the interface version. /// public ushort InterfaceVersion { get; } } /// /// Functionality for serializing and deserializing and derived types. /// [RegisterSerializer] internal class GrainReferenceCodec : GeneralizedReferenceTypeSurrogateCodec { private readonly IGrainFactory _grainFactory; /// /// Initializes a new instance of the class. /// /// The grain factory. /// The serializer for the surrogate type used by this class. public GrainReferenceCodec(IGrainFactory grainFactory, IValueSerializer surrogateSerializer) : base(surrogateSerializer) { _grainFactory = grainFactory; } /// public override IAddressable ConvertFromSurrogate(ref GrainReferenceSurrogate surrogate) { return _grainFactory.GetGrain(surrogate.GrainId, surrogate.GrainInterfaceType); } /// public override void ConvertToSurrogate(IAddressable value, ref GrainReferenceSurrogate surrogate) { var refValue = value.AsReference(); surrogate.GrainId = refValue.GrainId; surrogate.GrainInterfaceType = refValue.InterfaceType; } } [RegisterCopier] internal sealed class GrainReferenceCopier : ShallowCopier, IDerivedTypeCopier { } /// /// Provides specialized copier instances for grain reference types. /// internal class GrainReferenceCopierProvider : ISpecializableCopier { /// // ! Activator.CreateInstance will not return null for these arguments. public IDeepCopier GetSpecializedCopier(Type type) => (IDeepCopier)Activator.CreateInstance(typeof(TypedGrainReferenceCopier<>).MakeGenericType(type))!; /// public bool IsSupportedType(Type type) => typeof(IAddressable).IsAssignableFrom(type) && type.IsInterface; } /// /// A strongly-typed copier for grain reference instances. /// /// The grain interface type. internal class TypedGrainReferenceCopier : IDeepCopier { /// public TInterface DeepCopy(TInterface input, CopyContext context) { if (input is null) return input; if (input is GrainReference) return input; if (input is IGrainObserver observer) { GrainReferenceCodecProvider.ThrowGrainObserverInvalidException(observer); } var addressable = (IAddressable)input; var grainReference = addressable.AsReference(); return (TInterface)grainReference.Runtime.Cast(addressable, typeof(TInterface)); } } /// /// Provides specialized codec instances for grain reference types. /// internal class GrainReferenceCodecProvider : ISpecializableCodec { private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// The service provider. public GrainReferenceCodecProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; /// public IFieldCodec GetSpecializedCodec(Type type) => (IFieldCodec)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, typeof(TypedGrainReferenceCodec<>).MakeGenericType(type)); /// public bool IsSupportedType(Type type) => typeof(IAddressable).IsAssignableFrom(type); /// /// Throws an exception indicating that a parameter type is not supported. /// /// The observer. internal static void ThrowGrainObserverInvalidException(IGrainObserver observer) => throw new NotSupportedException($"IGrainObserver parameters must be GrainReference or Grain and cannot be type {observer.GetType()}. Did you forget to CreateObjectReference?"); } /// /// A strongly-typed codec for grain reference instances. /// /// The grain reference interface type. internal class TypedGrainReferenceCodec : GeneralizedReferenceTypeSurrogateCodec where T : class, IAddressable { private readonly IGrainFactory _grainFactory; /// /// Initializes a new instance of the class. /// /// The grain factory. /// The surrogate serializer. public TypedGrainReferenceCodec(IGrainFactory grainFactory, IValueSerializer surrogateSerializer) : base(surrogateSerializer) { _grainFactory = grainFactory; } /// public override T ConvertFromSurrogate(ref GrainReferenceSurrogate surrogate) { return (T)_grainFactory.GetGrain(surrogate.GrainId, surrogate.GrainInterfaceType); } /// public override void ConvertToSurrogate(T value, ref GrainReferenceSurrogate surrogate) { // Check that the typical case is false before performing the more expensive interface check if (value is not GrainReference refValue) { if (value is IGrainObserver observer) { GrainReferenceCodecProvider.ThrowGrainObserverInvalidException(observer); } refValue = (GrainReference)(object)value.AsReference(); } surrogate.GrainId = refValue.GrainId; surrogate.GrainInterfaceType = refValue.InterfaceType; } } /// /// A surrogate used to represent implementations for serialization. /// [GenerateSerializer] internal struct GrainReferenceSurrogate { /// /// Gets or sets the grain id. /// [Id(0)] public GrainId GrainId; /// /// Gets or sets the grain interface type. /// [Id(1)] public GrainInterfaceType GrainInterfaceType; } /// /// This is the base class for all grain references. /// [Alias("GrainRef")] [DefaultInvokableBaseType(typeof(ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(Task<>), typeof(TaskRequest<>))] [DefaultInvokableBaseType(typeof(Task), typeof(TaskRequest))] [DefaultInvokableBaseType(typeof(void), typeof(VoidRequest))] [DefaultInvokableBaseType(typeof(IAsyncEnumerable<>), typeof(AsyncEnumerableRequest<>))] public class GrainReference : IAddressable, IEquatable, ISpanFormattable { /// /// The grain reference functionality which is shared by all grain references of a given type. /// [NonSerialized] private readonly GrainReferenceShared _shared; /// /// The underlying grain id key. /// [NonSerialized] private readonly IdSpan _key; /// /// Gets the grain reference functionality which is shared by all grain references of a given type. /// internal GrainReferenceShared Shared => _shared ?? throw new GrainReferenceNotBoundException(this); /// /// Gets the grain reference runtime. /// internal IGrainReferenceRuntime Runtime => Shared.Runtime; /// /// Gets the grain id. /// public GrainId GrainId => GrainId.Create(_shared.GrainType, _key); /// /// Gets the interface type. /// public GrainInterfaceType InterfaceType => _shared.InterfaceType; /// /// Gets the serialization copy context pool. /// protected CopyContextPool CopyContextPool => _shared.CopyContextPool; /// /// Gets the serialization codec provider. /// protected CodecProvider CodecProvider => _shared.CodecProvider; /// Initializes a new instance of the class. /// /// The grain reference functionality which is shared by all grain references of a given type. /// /// /// The key portion of the grain id. /// protected GrainReference(GrainReferenceShared shared, IdSpan key) { _shared = shared; _key = key; } /// /// Creates a new instance for the specified . /// internal static GrainReference FromGrainId(GrainReferenceShared shared, GrainId grainId) => new(shared, grainId.Key); /// /// Creates a new grain reference which implements the specified grain interface. /// /// /// The grain interface type. /// /// A new grain reference which implements the specified interface type. public virtual TGrainInterface Cast() where TGrainInterface : IAddressable => (TGrainInterface)_shared.Runtime.Cast(this, typeof(TGrainInterface)); /// /// Tests this reference for equality to another object. /// Two grain references are equal if they both refer to the same grain. /// /// The object to test for equality against this reference. /// true if the object is equal to this reference. public override bool Equals(object? obj) { return Equals(obj as GrainReference); } /// public bool Equals(GrainReference? other) => other is not null && this.GrainId.Equals(other.GrainId); /// public override int GetHashCode() => this.GrainId.GetHashCode(); /// /// Get a uniform hash code for this grain reference. /// /// /// The uniform hash code. /// public uint GetUniformHashCode() { // GrainId already includes the hashed type code for generic arguments. return GrainId.GetUniformHashCode(); } /// /// Compares two references for equality. /// Two grain references are equal if they both refer to the same grain. /// /// First grain reference to compare. /// Second grain reference to compare. /// true if both grain references refer to the same grain (by grain identifier). public static bool operator ==(GrainReference? reference1, GrainReference? reference2) { if (reference1 is null) return reference2 is null; return reference1.Equals(reference2); } /// /// Compares two references for inequality. /// Two grain references are equal if they both refer to the same grain. /// /// First grain reference to compare. /// Second grain reference to compare. /// false if both grain references are resolved to the same grain (by grain identifier). public static bool operator !=(GrainReference? reference1, GrainReference? reference2) { if (reference1 is null) return !(reference2 is null); return !reference1.Equals(reference2); } /// /// Gets the interface version. /// public ushort InterfaceVersion => Shared.InterfaceVersion; /// /// Gets the interface name. /// public virtual string InterfaceName => InterfaceType.ToString(); /// public sealed override string ToString() => $"GrainReference:{GrainId}:{InterfaceType}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"GrainReference:{GrainId}:{InterfaceType}", out charsWritten); protected TInvokable GetInvokable() => ActivatorUtilities.GetServiceOrCreateInstance(Shared.ServiceProvider); /// /// Invokes the provided method. /// /// The underlying method return type. /// The method description. /// The result of the invocation. protected ValueTask InvokeAsync(IRequest methodDescription) { return this.Runtime.InvokeMethodAsync(this, methodDescription, methodDescription.Options); } /// /// Invokes the provided method. /// /// The method description. /// A representing the operation. protected ValueTask InvokeAsync(IRequest methodDescription) { return this.Runtime.InvokeMethodAsync(this, methodDescription, methodDescription.Options); } /// /// Invokes the provided method. /// /// The method description. protected void Invoke(IRequest methodDescription) { this.Runtime.InvokeMethod(this, methodDescription, methodDescription.Options); } } /// /// Represents a request to invoke a method on a grain. /// public interface IRequest : IInvokable { /// /// Gets the invocation options. /// InvokeMethodOptions Options { get; } /// /// Incorporates the provided invocation options. /// /// /// The options. /// void AddInvokeMethodOptions(InvokeMethodOptions options); /// /// Returns a string representation of the request. /// /// A string representation of the request. public static string ToString(IRequest request) { var result = new StringBuilder(); result.Append(request.GetInterfaceName()); if (request.GetTarget() is { } target) { result.Append("[("); result.Append(request.GetInterfaceName()); result.Append(')'); result.Append(target.ToString()); result.Append(']'); } result.Append('.'); result.Append(request.GetMethodName()); result.Append('('); var argTypes = request.GetMethod()?.GetParameters(); if (argTypes is not null) { var argumentCount = argTypes.Length; for (var n = 0; n < argumentCount; n++) { if (n > 0) { result.Append(", "); } result.Append(argTypes[n].ParameterType); } } else { var argumentCount = request.GetArgumentCount(); for (var n = 0; n < argumentCount; n++) { if (n > 0) { result.Append(", "); } result.Append(request.GetArgument(n)?.GetType()?.ToString() ?? "null"); } } result.Append(')'); return result.ToString(); } /// /// Returns a string representation of the request. /// /// A string representation of the request. public static string ToMethodCallString(IRequest request) { var result = new StringBuilder(); result.Append(request.GetInterfaceName()); result.Append('.'); result.Append(request.GetMethodName()); result.Append('('); var argumentCount = request.GetArgumentCount(); for (var n = 0; n < argumentCount; n++) { if (n > 0) { result.Append(", "); } result.Append(request.GetArgument(n)); } result.Append(')'); return result.ToString(); } } /// /// Base type used for method requests. /// [SuppressReferenceTracking] [SerializerTransparent] public abstract class RequestBase : IRequest { /// /// Gets the invocation options. /// [field: NonSerialized] public InvokeMethodOptions Options { get; protected set; } /// public virtual int GetArgumentCount() => 0; /// /// Incorporates the provided invocation options. /// /// /// The options. /// public void AddInvokeMethodOptions(InvokeMethodOptions options) { Options |= options; } /// public abstract ValueTask Invoke(); /// public abstract object GetTarget(); /// public abstract void SetTarget(ITargetHolder holder); /// public virtual object GetArgument(int index) => throw new ArgumentOutOfRangeException(message: "The request has zero arguments", null); /// public virtual void SetArgument(int index, object value) => throw new ArgumentOutOfRangeException(message: "The request has zero arguments", null); /// public abstract void Dispose(); /// public abstract string GetMethodName(); /// public abstract string GetInterfaceName(); /// public abstract string GetActivityName(); /// public abstract Type GetInterfaceType(); /// public abstract MethodInfo GetMethod(); /// public override string ToString() => IRequest.ToString(this); /// public virtual TimeSpan? GetDefaultResponseTimeout() => null; public virtual bool TryCancel() => false; public virtual CancellationToken GetCancellationToken() => default; public virtual bool IsCancellable => false; } /// /// Base class for requests for methods which return . /// [SerializerTransparent] public abstract class Request : RequestBase { public sealed override ValueTask Invoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { resultTask.GetAwaiter().GetResult(); return new ValueTask(Response.Completed); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(ValueTask resultTask) { try { await resultTask; return Response.Completed; } catch (Exception exception) { return Response.FromException(exception); } } // Generated protected abstract ValueTask InvokeInner(); } /// /// Base class for requests for methods which return . /// /// /// The underlying result type. /// [SerializerTransparent] public abstract class Request : RequestBase { /// public sealed override ValueTask Invoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { return new ValueTask(Response.FromResult(resultTask.Result)); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(ValueTask resultTask) { try { var result = await resultTask; return Response.FromResult(result); } catch (Exception exception) { return Response.FromException(exception); } } /// /// Invokes the request against the target. /// /// The invocation result. protected abstract ValueTask InvokeInner(); } /// /// Base class for requests for methods which return . /// /// /// The underlying result type. /// [SerializerTransparent] public abstract class TaskRequest : RequestBase { /// public sealed override ValueTask Invoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { return new ValueTask(Response.FromResult(resultTask.GetAwaiter().GetResult())); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(Task resultTask) { try { var result = await resultTask; return Response.FromResult(result); } catch (Exception exception) { return Response.FromException(exception); } } /// /// Invokes the request against the target. /// /// The invocation result. protected abstract Task InvokeInner(); } /// /// Base class for requests for methods which return . /// [SerializerTransparent] public abstract class TaskRequest : RequestBase { /// public sealed override ValueTask Invoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { resultTask.GetAwaiter().GetResult(); return new ValueTask(Response.Completed); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(Task resultTask) { try { await resultTask; return Response.Completed; } catch (Exception exception) { return Response.FromException(exception); } } /// /// Invokes the request against the target. /// /// The invocation result. protected abstract Task InvokeInner(); } /// /// Base class for requests for void-returning methods. /// [SerializerTransparent] public abstract class VoidRequest : RequestBase { protected VoidRequest() { // All void requests are inherently one-way. Options = InvokeMethodOptions.OneWay; } /// public sealed override ValueTask Invoke() { try { InvokeInner(); return new ValueTask(Response.Completed); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } /// /// Invokes the request against the target. /// protected abstract void InvokeInner(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/GrainReferenceNotBoundException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime { /// /// Indicates that a was not bound to the runtime before being used. /// [Serializable, GenerateSerializer] public sealed class GrainReferenceNotBoundException : OrleansException { /// /// Initializes a new instance of the class. /// /// The unbound grain reference. internal GrainReferenceNotBoundException(GrainReference grainReference) : base(CreateMessage(grainReference)) { } /// /// Initializes a new instance of the class. /// /// The message. internal GrainReferenceNotBoundException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. internal GrainReferenceNotBoundException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private GrainReferenceNotBoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } private static string CreateMessage(GrainReference grainReference) { return $"Attempted to use an invalid GrainReference, which has not been constructed by the runtime: {grainReference}."; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IAddressable.cs ================================================ namespace Orleans.Runtime { /// /// Marker interface for addressable endpoints, such as grains, observers, and other system-internal addressable endpoints /// [GenerateMethodSerializers(typeof(GrainReference))] public interface IAddressable { } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IGrainCancellationTokenRuntime.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Functionality required by and . /// internal interface IGrainCancellationTokenRuntime { /// /// Cancels the with the provided id. /// /// The grain cancellation token id. /// The grain cancellation token source being canceled. /// The grain references which are observing the cancellation token. /// A representing the operation. Task Cancel(Guid id, CancellationTokenSource tokenSource, ConcurrentDictionary grainReferences); } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IGrainExtension.cs ================================================ namespace Orleans.Runtime { /// /// Marker interface for grain extensions, used by internal runtime extension endpoints. /// [GenerateMethodSerializers(typeof(GrainReference), isExtension: true)] public interface IGrainExtension : IAddressable { } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IGrainReferenceRuntime.cs ================================================ using Orleans.CodeGeneration; using Orleans.Serialization.Invocation; using System; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Runtime logic for s to be usable. /// This service is not meant to be used directly by user code. /// public interface IGrainReferenceRuntime { /// /// Invokes the specified method on the provided grain interface. /// /// The underlying return type of the method. /// The grain reference. /// The method description. /// The invocation options. /// The result of invocation. ValueTask InvokeMethodAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options); /// /// Invokes the specified method on the provided grain interface. /// /// The grain reference. /// The method description. /// The invocation options. /// A representing the operation ValueTask InvokeMethodAsync(GrainReference reference, IInvokable request, InvokeMethodOptions options); /// /// Invokes the specified void-returning method on the provided grain interface without waiting for a response. /// /// The grain reference. /// The method description. /// The invocation options. void InvokeMethod(GrainReference reference, IInvokable request, InvokeMethodOptions options); /// /// Converts the provided to the provided . /// /// The grain. /// The resulting interface type. /// A reference to which implements . object Cast(IAddressable grain, Type interfaceType); } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IGrainRuntime.cs ================================================ using System; using Orleans.Core; using Orleans.Timers; namespace Orleans.Runtime; /// /// The gateway of the to the Orleans runtime. The should only interact with the runtime through this interface. /// public interface IGrainRuntime { /// /// Gets a unique identifier for the current silo. /// There is no semantic content to this string, but it may be useful for logging. /// string SiloIdentity { get; } /// /// Gets the silo address associated with this instance. /// SiloAddress SiloAddress { get; } /// /// Gets the grain factory. /// IGrainFactory GrainFactory { get; } /// /// Gets the timer registry. /// ITimerRegistry TimerRegistry { get; } /// /// Gets the service provider. /// IServiceProvider ServiceProvider { get; } /// /// Gets the time provider. /// TimeProvider TimeProvider => TimeProvider.System; /// /// Deactivates the provided grain when it becomes idle. /// /// The grain context. void DeactivateOnIdle(IGrainContext grainContext); /// /// Delays idle activation collection of the provided grain due to inactivity until at least the specified time has elapsed. /// /// The grain context. /// The time to delay idle activation collection for. void DelayDeactivation(IGrainContext grainContext, TimeSpan timeSpan); /// /// Gets grain storage for the provided grain. /// /// The grain state type. /// The grain context. /// The grain storage for the provided grain. IStorage GetStorage(IGrainContext grainContext); } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/IGrainTimer.cs ================================================ using System; using System.Threading; namespace Orleans.Runtime; /// /// Represents a timer belonging to a grain. /// public interface IGrainTimer : IDisposable { /// Changes the start time and the interval between method invocations for a timer, using values to measure time intervals. /// /// A representing the amount of time to delay before invoking the callback method specified when the was constructed. /// Specify to prevent the timer from restarting. /// Specify to restart the timer immediately. /// /// /// The time interval between invocations of the callback method specified when the timer was constructed. /// Specify to disable periodic signaling. /// /// The or parameter, in milliseconds, is less than -1 or greater than 4294967294. void Change(TimeSpan dueTime, TimeSpan period); } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/MembershipVersion.cs ================================================ using System; using System.Text.Json; using System.Text.Json.Serialization; namespace Orleans.Runtime { /// /// Identifies the version of a cluster membership configuration. /// [Serializable, GenerateSerializer, Immutable] [JsonConverter(typeof(MembershipVersionConverter))] public readonly struct MembershipVersion : IComparable, IEquatable { /// /// Initializes a new instance of the struct. /// /// The underlying version. public MembershipVersion(long version) { this.Value = version; } /// /// Gets the version. /// [Id(0)] public long Value { get; init; } /// /// Gets the minimum possible version. /// public static MembershipVersion MinValue => new MembershipVersion(long.MinValue); /// public int CompareTo(MembershipVersion other) => this.Value.CompareTo(other.Value); /// public bool Equals(MembershipVersion other) => this.Value == other.Value; /// public override bool Equals(object? obj) => obj is MembershipVersion other && this.Equals(other); /// public override int GetHashCode() => this.Value.GetHashCode(); /// public override string ToString() => Value != MinValue.Value ? $"{Value}" : "default"; /// /// Compares the provided operands for equality. /// /// The left operand. /// The right operand. /// if the provided values are equal, otherwise . public static bool operator ==(MembershipVersion left, MembershipVersion right) => left.Value == right.Value; /// /// Compares the provided operands for inequality. /// /// The left operand. /// The right operand. /// if the provided values are not equal, otherwise . public static bool operator !=(MembershipVersion left, MembershipVersion right) => left.Value != right.Value; /// /// Compares the provided operands and returns if the left operand is greater than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than or equal to the right operand, otherwise . public static bool operator >=(MembershipVersion left, MembershipVersion right) => left.Value >= right.Value; /// /// Compares the provided operands and returns if the left operand is less than or equal to the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than or equal to the right operand, otherwise . public static bool operator <=(MembershipVersion left, MembershipVersion right) => left.Value <= right.Value; /// /// Compares the provided operands and returns if the left operand is greater than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is greater than the right operand, otherwise . public static bool operator >(MembershipVersion left, MembershipVersion right) => left.Value > right.Value; /// /// Compares the provided operands and returns if the left operand is less than the right operand, otherwise . /// /// The left operand. /// The right operand. /// if the left operand is less than the right operand, otherwise . public static bool operator <(MembershipVersion left, MembershipVersion right) => left.Value < right.Value; } /// /// Functionality for converting instances to and from JSON. /// public sealed class MembershipVersionConverter : JsonConverter { /// public override MembershipVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new(reader.GetInt64()); /// public override void Write(Utf8JsonWriter writer, MembershipVersion value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value); } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/RequestContext.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Orleans.Core.Internal; namespace Orleans.Runtime { /// /// This class holds information regarding the request currently being processed. /// It is explicitly intended to be available to application code. /// /// /// /// The request context is represented as a property bag. /// Some values are provided by default; others are derived from messages headers in the /// request that led to the current processing. /// /// /// Information stored in is propagated from Orleans clients to Orleans grains automatically by the Orleans runtime. /// /// public static class RequestContext { internal const string CALL_CHAIN_REENTRANCY_HEADER = "#CCR"; internal const string PING_APPLICATION_HEADER = "Ping"; internal static readonly AsyncLocal CallContextData = new(); public static Guid ReentrancyId { get => Get(CALL_CHAIN_REENTRANCY_HEADER) is Guid guid ? guid : Guid.Empty; set { if (value == Guid.Empty) { Remove(CALL_CHAIN_REENTRANCY_HEADER); } else { Set(CALL_CHAIN_REENTRANCY_HEADER, value); } } } /// /// Allows reentrancy for subsequent calls issued before the returned is disposed. /// public static ReentrancySection AllowCallChainReentrancy() { var originalCallChainId = ReentrancyId; var newCallChainId = originalCallChainId == Guid.Empty ? Guid.NewGuid() : originalCallChainId; return new ReentrancySection(originalCallChainId, newCallChainId); } /// /// Suppresses reentrancy for subsequent calls issued before the returned is disposed. /// public static ReentrancySection SuppressCallChainReentrancy() => new(ReentrancyId, Guid.Empty); /// /// Retrieves a value from the request context. /// /// The key for the value to be retrieved. /// /// The value currently associated with the provided key, otherwise if no data is present for that key. /// public static object? Get(string key) { var properties = CallContextData.Value; var values = properties.Values; if (values != null && values.TryGetValue(key, out var result)) { return result; } return null; } /// /// Sets a value in the request context. /// /// The key for the value to be updated or added. /// The value to be stored into the request context. public static void Set(string key, object value) { var properties = CallContextData.Value; var values = properties.Values; if (values == null) { values = new Dictionary(1); } else { // Have to copy the actual Dictionary value, mutate it and set it back. // This is since AsyncLocal copies link to dictionary, not create a new one. // So we need to make sure that modifying the value, we doesn't affect other threads. var hadPreviousValue = values.ContainsKey(key); var newValues = new Dictionary(values.Count + (hadPreviousValue ? 0 : 1)); foreach (var pair in values) { newValues.Add(pair.Key, pair.Value); } values = newValues; } values[key] = value; CallContextData.Value = new ContextProperties { Values = values }; } /// /// Remove a value from the request context. /// /// The key for the value to be removed. /// if the value was previously in the request context and has now been removed, otherwise . public static bool Remove(string key) { var properties = CallContextData.Value; var values = properties.Values; if (values == null || values.Count == 0 || !values.ContainsKey(key)) { return false; } if (values.Count == 1) { CallContextData.Value = new ContextProperties { Values = null }; return true; } else { var newValues = new Dictionary(values); newValues.Remove(key); CallContextData.Value = new ContextProperties { Values = newValues }; return true; } } /// /// Clears the current request context. /// public static void Clear() { // Remove the key to prevent passing of its value from this point on if (!CallContextData.Value.IsDefault) { CallContextData.Value = default; } } /// /// Gets the collection of keys for the values currently in the request context. /// public static IEnumerable Keys => CallContextData.Value.Values?.Keys ?? Enumerable.Empty(); /// /// Gets the collection of entries currently in the request context. /// public static IEnumerable> Entries => CallContextData.Value.Values ?? Enumerable.Empty>(); internal readonly struct ContextProperties { public Dictionary? Values { get; init; } public bool IsDefault => Values is null; } public readonly struct ReentrancySection : IDisposable { private readonly Guid _originalReentrancyId; private readonly Guid _newReentrancyId; public ReentrancySection(Guid originalReentrancyId, Guid newReentrancyId) { _originalReentrancyId = originalReentrancyId; _newReentrancyId = newReentrancyId; if (newReentrancyId != originalReentrancyId) { ReentrancyId = newReentrancyId; } if (newReentrancyId != Guid.Empty) { var grain = RuntimeContext.Current as ICallChainReentrantGrainContext; grain?.OnEnterReentrantSection(_newReentrancyId); } } public void Dispose() { if (_newReentrancyId != Guid.Empty) { var grain = RuntimeContext.Current as ICallChainReentrantGrainContext; grain?.OnExitReentrantSection(_newReentrancyId); } if (_newReentrancyId != _originalReentrancyId) { ReentrancyId = _originalReentrancyId; } } } } } ================================================ FILE: src/Orleans.Core.Abstractions/Runtime/RuntimeContext.cs ================================================ using System; using System.Runtime.CompilerServices; namespace Orleans.Runtime { /// /// Functionality for managing the current grain context. /// internal static class RuntimeContext { /// /// The thread-local context. /// [ThreadStatic] private static IGrainContext? _threadLocalContext; /// /// Gets the current grain context. /// public static IGrainContext? Current => _threadLocalContext; /// /// Sets the current grain context. /// /// The new context. /// The current context at the time of the call. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void SetExecutionContext(IGrainContext newContext, out IGrainContext? currentContext) { currentContext = _threadLocalContext; _threadLocalContext = newContext; } /// /// Resets the current grain context to the provided original context. /// /// The original context. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ResetExecutionContext(IGrainContext? originalContext) { _threadLocalContext = originalContext; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Services/IGrainService.cs ================================================ namespace Orleans.Services { /// /// Base interface for grain services. /// public interface IGrainService : ISystemTarget { } } ================================================ FILE: src/Orleans.Core.Abstractions/Services/IGrainServiceClient.cs ================================================ namespace Orleans.Services { /// /// Base interface for grain service clients. /// /// The grain service interface type. public interface IGrainServiceClient where TGrainService : IGrainService { } } ================================================ FILE: src/Orleans.Core.Abstractions/Statistics/EnvironmentStatisticExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Orleans.Statistics; internal static class EnvironmentStatisticExtensions { public static bool IsValid(this EnvironmentStatistics statistics) => statistics.MaximumAvailableMemoryBytes > 0; } ================================================ FILE: src/Orleans.Core.Abstractions/Statistics/IAppEnvironmentStatistics.cs ================================================ using System; namespace Orleans.Statistics { /// /// Provides functionality for accessing statistics relating to the application environment. /// [Obsolete($"This functionality will be removed, use {nameof(IEnvironmentStatisticsProvider)}.{nameof(IEnvironmentStatisticsProvider.GetEnvironmentStatistics)} instead.")] public interface IAppEnvironmentStatistics { /// /// Gets the total memory usage, in bytes, if available. /// long? MemoryUsage { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/Statistics/IEnvironmentStatisticsProvider.cs ================================================ using System; using System.Runtime.InteropServices; using Orleans.Serialization; using System.Diagnostics; namespace Orleans.Statistics; /// /// Provides statistics about the current process and its execution environment. /// public interface IEnvironmentStatisticsProvider { /// /// Gets the current environment statistics. May apply filtering or processing based on the runtime configuration. /// EnvironmentStatistics GetEnvironmentStatistics(); } // This struct is intentionally 'packed' in order to avoid extra padding. // This will be created very frequently, so we reduce stack size and lower the serialization cost. // As more fields are added to this, they could be placed in such a manner that it may result in a lot of 'empty' space. /// /// Contains statistics about the current process and its execution environment. /// [Immutable] [GenerateSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] [Alias("Orleans.Statistics.EnvironmentStatistics")] [DebuggerDisplay("{ToString(),nq}")] public readonly struct EnvironmentStatistics { /// /// The system CPU usage. ///
    /// Applies Kalman filtering to smooth out short-term fluctuations. /// See ; /// ///
    /// Ranges from 0.0-100.0. [Id(0)] public readonly float FilteredCpuUsagePercentage; /// /// The amount of managed memory currently consumed by the process. ///
    /// Applies Kalman filtering to smooth out short-term fluctuations. /// See ; /// ///
    /// /// Includes fragmented memory, which is the unused memory between objects on the managed heaps. /// [Id(1)] public readonly long FilteredMemoryUsageBytes; /// /// The amount of memory currently available for allocations to the process. ///
    /// Applies Kalman filtering to smooth out short-term fluctuations. /// See ; /// ///
    /// /// Includes the currently available memory of the process and the system. /// [Id(2)] public readonly long FilteredAvailableMemoryBytes; /// /// The maximum amount of memory available to the process. /// /// /// This value is computed as the lower of two amounts: /// /// The amount of memory after which the garbage collector will begin aggressively collecting memory, defined by . /// The process' configured memory limit, defined by . /// /// Memory limits are common in containerized environments. For more information on configuring memory limits, see /// [Id(3)] public readonly long MaximumAvailableMemoryBytes; /// /// The system CPU usage. /// /// Ranges from 0.0-100.0. [Id(4)] public readonly float RawCpuUsagePercentage; /// /// The amount of managed memory currently consumed by the process. /// [Id(5)] public readonly long RawMemoryUsageBytes; /// /// The amount of memory currently available for allocations to the process. /// [Id(6)] public readonly long RawAvailableMemoryBytes; /// /// Gets the percentage of memory used relative to currently available memory, clamped between 0 and 100. /// public float MemoryUsagePercentage { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var percent = (double)RawMemoryUsageBytes / MaximumAvailableMemoryBytes * 100.0; return (float)Math.Clamp(percent, 0.0, 100.0); } } /// /// Gets the percentage of available memory relative to currently used memory, clamped between 0 and 100. /// /// /// A value of 0 indicates that all available memory is currently in use. /// A value of 100 indicates that all memory is currently available. /// public float AvailableMemoryPercentage { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var percent = (double)RawAvailableMemoryBytes / MaximumAvailableMemoryBytes * 100.0; return (float)Math.Clamp(percent, 0.0, 100.0); } } /// /// Gets the normalized memory usage (0.0 to 1.0). /// public float NormalizedMemoryUsage { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var fraction = (double)RawMemoryUsageBytes / MaximumAvailableMemoryBytes; return (float)Math.Clamp(fraction, 0.0, 1.0); } } /// /// Gets the normalized filtered memory usage (0.0 to 1.0). /// public float NormalizedFilteredMemoryUsage { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var fraction = (double)FilteredMemoryUsageBytes / MaximumAvailableMemoryBytes; return (float)Math.Clamp(fraction, 0.0, 1.0); } } /// /// Gets the normalized available memory (0.0 to 1.0). /// public float NormalizedAvailableMemory { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var fraction = (double)RawAvailableMemoryBytes / MaximumAvailableMemoryBytes; return (float)Math.Clamp(fraction, 0.0, 1.0); } } /// /// Gets the normalized filtered available memory (0.0 to 1.0). /// public float NormalizedFilteredAvailableMemory { get { if (MaximumAvailableMemoryBytes <= 0) return 0f; var fraction = (double)FilteredAvailableMemoryBytes / MaximumAvailableMemoryBytes; return (float)Math.Clamp(fraction, 0.0, 1.0); } } private static string FormatBytes(long bytes) { const long KB = 1024; const long MB = KB * 1024; const long GB = MB * 1024; if (bytes >= GB) return $"{bytes / (double)GB:F2} GB"; if (bytes >= MB) return $"{bytes / (double)MB:F2} MB"; if (bytes >= KB) return $"{bytes / (double)KB:F2} KB"; return $"{bytes} B"; } public override string ToString() => $"CpuUsage: {FilteredCpuUsagePercentage:F2}% (raw: {RawCpuUsagePercentage:F2}%) | " + $"MemoryUsage: {FormatBytes(FilteredMemoryUsageBytes)} (raw: {FormatBytes(RawMemoryUsageBytes)}) [{MemoryUsagePercentage:F2}%] | " + $"AvailableMemory: {FormatBytes(FilteredAvailableMemoryBytes)} (raw: {FormatBytes(RawAvailableMemoryBytes)}) [{AvailableMemoryPercentage:F2}%] | " + $"MaximumAvailableMemory: {FormatBytes(MaximumAvailableMemoryBytes)}"; internal EnvironmentStatistics( float cpuUsagePercentage, float rawCpuUsagePercentage, long memoryUsageBytes, long rawMemoryUsageBytes, long availableMemoryBytes, long rawAvailableMemoryBytes, long maximumAvailableMemoryBytes) { FilteredCpuUsagePercentage = Math.Clamp(cpuUsagePercentage, 0f, 100f); RawCpuUsagePercentage = Math.Clamp(rawCpuUsagePercentage, 0f, 100f); FilteredMemoryUsageBytes = memoryUsageBytes; RawMemoryUsageBytes = rawMemoryUsageBytes; FilteredAvailableMemoryBytes = availableMemoryBytes; RawAvailableMemoryBytes = rawAvailableMemoryBytes; MaximumAvailableMemoryBytes = maximumAvailableMemoryBytes; #if DEBUG Debug.Assert(MaximumAvailableMemoryBytes >= 0, $"{nameof(MaximumAvailableMemoryBytes)} must be non-negative. {this}"); Debug.Assert(RawMemoryUsageBytes >= 0, $"{nameof(RawMemoryUsageBytes)} must be non-negative. {this}"); Debug.Assert(RawAvailableMemoryBytes >= 0, $"{nameof(RawAvailableMemoryBytes)} must be non-negative. {this}"); Debug.Assert(RawMemoryUsageBytes + RawAvailableMemoryBytes <= MaximumAvailableMemoryBytes, $"Sum of {nameof(RawMemoryUsageBytes)} and {nameof(RawAvailableMemoryBytes)} must not exceed {nameof(MaximumAvailableMemoryBytes)}. {this}"); Debug.Assert(FilteredMemoryUsageBytes >= 0, $"{nameof(FilteredMemoryUsageBytes)} must be non-negative. {this}"); Debug.Assert(FilteredAvailableMemoryBytes >= 0, $"{nameof(FilteredAvailableMemoryBytes)} must be non-negative. {this}"); Debug.Assert(RawCpuUsagePercentage is >= 0.0f and <= 100.0f, $"{nameof(RawCpuUsagePercentage)} must be between 0.0 and 100.0. {this}"); Debug.Assert(FilteredCpuUsagePercentage is >= 0.0f and <= 100.0f, $"{nameof(FilteredCpuUsagePercentage)} must be between 0.0 and 100.0. {this}"); Debug.Assert(MemoryUsagePercentage is >= 0.0f and <= 100.0f, $"{nameof(MemoryUsagePercentage)} must be between 0.0 and 100.0. {this}"); Debug.Assert(AvailableMemoryPercentage is >= 0.0f and <= 100.0f, $"{nameof(AvailableMemoryPercentage)} must be between 0.0 and 100.0. {this}"); Debug.Assert(NormalizedMemoryUsage is >= 0.0f and <= 1.0f, $"{nameof(NormalizedMemoryUsage)} must be between 0.0 and 1.0. {this}"); Debug.Assert(NormalizedAvailableMemory is >= 0.0f and <= 1.0f, $"{nameof(NormalizedAvailableMemory)} must be between 0.0 and 1.0. {this}"); Debug.Assert(NormalizedFilteredMemoryUsage is >= 0.0f and <= 1.0f, $"{nameof(NormalizedFilteredMemoryUsage)} must be between 0.0 and 1.0. {this}"); Debug.Assert(NormalizedFilteredAvailableMemory is >= 0.0f and <= 1.0f, $"{nameof(NormalizedFilteredAvailableMemory)} must be between 0.0 and 1.0. {this}"); #endif } } ================================================ FILE: src/Orleans.Core.Abstractions/Statistics/IHostEnvironmentStatistics.cs ================================================ using System; namespace Orleans.Statistics { /// /// Functionality for accessing statistics relating to the hosting environment. /// [Obsolete($"This functionality will be removed, use {nameof(IEnvironmentStatisticsProvider)}.{nameof(IEnvironmentStatisticsProvider.GetEnvironmentStatistics)} instead.")] public interface IHostEnvironmentStatistics { /// /// Gets the total physical memory on the host in bytes. /// /// /// 16426476000L for 16 GB. /// long? TotalPhysicalMemory { get; } /// /// Gets the host CPU usage from 0.0-100.0. /// /// /// 70.0f for 70% CPU usage. /// float? CpuUsage { get; } /// /// Gets the total memory available for allocation on the host in bytes. /// /// /// 14426476000L for 14 GB. /// long? AvailableMemory { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/SystemTargetInterfaces/ISystemTarget.cs ================================================ using Orleans.Runtime; namespace Orleans { /// /// This is a markup interface for system targets. /// System target are internal runtime objects that share some behavior with grains, but also impose certain restrictions. In particular: /// System target are asynchronously addressable actors. /// Proxy class is being generated for ISystemTarget, just like for IGrain /// System target are scheduled by the runtime scheduler and follow turn based concurrency. /// public interface ISystemTarget : IAddressable { } } ================================================ FILE: src/Orleans.Core.Abstractions/SystemTargetInterfaces/ISystemTargetBase.cs ================================================ using Orleans.Runtime; namespace Orleans { /// /// Internal interface implemented by the SystemTarget base class that enables generation of grain references for system targets. /// internal interface ISystemTargetBase : IGrainContext { /// /// Gets the address of the server which this system target is activated on. /// SiloAddress Silo { get; } } } ================================================ FILE: src/Orleans.Core.Abstractions/SystemTargetInterfaces/IVersionManager.cs ================================================ using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans { /// /// Functionality for managing how grain interface versions are negotiated. /// public interface IVersionManager { /// /// Set the compatibility strategy. /// /// The strategy to set. Set to to revert to the default strategy provided in configuration. /// A representing the operation. Task SetCompatibilityStrategy(CompatibilityStrategy strategy); /// /// Set the selector strategy. /// /// The strategy to set. Set to to revert to the default strategy provided in configuration. /// A representing the operation. Task SetSelectorStrategy(VersionSelectorStrategy strategy); /// /// Set the compatibility strategy for a specific interface. /// /// The type of the interface. /// The strategy to set. Set to to revert to the default strategy provided in configuration. /// A representing the operation. Task SetCompatibilityStrategy(GrainInterfaceType interfaceType, CompatibilityStrategy strategy); /// /// Set the selector strategy for a specific interface. /// /// The type of the interface. /// The strategy to set. Set to to revert to the default strategy provided in configuration. /// A representing the operation. Task SetSelectorStrategy(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy); } } ================================================ FILE: src/Orleans.Core.Abstractions/Timers/GrainTimerCreationOptions.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Threading; using Orleans.Concurrency; namespace Orleans.Runtime; /// /// Options for creating grain timers. /// public readonly struct GrainTimerCreationOptions() { /// /// Initializes a new instance. /// /// /// A representing the amount of time to delay before invoking the callback method specified when the was constructed. /// Specify to prevent the timer from starting. /// Specify to start the timer immediately. /// /// /// The time interval between invocations of the callback method specified when the was constructed. /// Specify to disable periodic signaling. /// [SetsRequiredMembers] public GrainTimerCreationOptions(TimeSpan dueTime, TimeSpan period) : this() { DueTime = dueTime; Period = period; } /// /// A representing the amount of time to delay before invoking the callback method specified when the was constructed. /// Specify to prevent the timer from starting. /// Specify to start the timer immediately. /// public required TimeSpan DueTime { get; init; } /// /// The time interval between invocations of the callback method specified when the was constructed. /// Specify to disable periodic signaling. /// public required TimeSpan Period { get; init; } /// /// Gets a value indicating whether callbacks scheduled by this timer are allowed to interleave execution with other timers and grain calls. /// Defaults to . /// /// /// If this value is , the timer callback will be treated akin to a grain call. If the grain scheduling this timer is reentrant /// (i.e., it has the attributed applied to its implementation class), the timer callback will be allowed /// to interleave with other grain calls and timers regardless of the value of this property. /// If this value is , the timer callback will be allowed to interleave with other timers and grain calls. /// public bool Interleave { get; init; } /// /// Gets a value indicating whether callbacks scheduled by this timer should extend the lifetime of the grain activation. /// Defaults to . /// /// /// If this value is , timer callbacks will not extend a grain activation's lifetime. /// If a grain is only processing this timer's callbacks and no other messages, the grain will be collected after its idle collection period expires. /// If this value is , timer callback will extend a grain activation's lifetime. /// If the timer period is shorter than the grain's idle collection period, the grain will not be collected due to idleness. /// public bool KeepAlive { get; init; } } ================================================ FILE: src/Orleans.Core.Abstractions/Timers/ITimerRegistry.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Timers; /// /// Functionality for managing grain timers. /// public interface ITimerRegistry { /// /// Creates a grain timer. /// /// The grain which the timer is associated with. /// The timer callback, which will fire whenever the timer becomes due. /// The state object passed to the callback. /// /// The amount of time to delay before the is invoked. /// Specify to prevent the timer from starting. /// Specify to invoke the callback promptly. /// /// /// The time interval between invocations of . /// Specify to disable periodic signaling. /// /// /// An instance which represents the timer. /// [Obsolete("Use 'RegisterGrainTimer(grainContext, callback, state, new() { DueTime = dueTime, Period = period, Interleave = true })' instead.")] IDisposable RegisterTimer(IGrainContext grainContext, Func callback, object? state, TimeSpan dueTime, TimeSpan period); /// /// The grain which the timer is associated with. /// The type of the parameter. IGrainTimer RegisterGrainTimer(IGrainContext grainContext, Func callback, TState state, GrainTimerCreationOptions options); } ================================================ FILE: src/Orleans.Core.Abstractions/Utils/Interner.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; namespace Orleans { /// /// Constants used by the generic class. /// internal static class InternerConstants { /* Recommended cache sizes, based on expansion policy of ConcurrentDictionary // Internal implementation of ConcurrentDictionary resizes to prime numbers (not divisible by 3 or 5 or 7) 31 67 137 277 557 1,117 2,237 4,477 8,957 17,917 35,837 71,677 143,357 286,717 573,437 1,146,877 2,293,757 4,587,517 9,175,037 18,350,077 36,700,157 */ public const int SIZE_SMALL = 67; public const int SIZE_MEDIUM = 1117; public const int SIZE_LARGE = 143357; public const int SIZE_X_LARGE = 2293757; } /// /// Provide a weakly-referenced cache of interned objects. /// Interner is used to optimize garbage collection. /// We use it to store objects that are allocated frequently and may have long lifetime. /// This means those object may quickly fill gen 2 and cause frequent costly full heap collections. /// Specifically, a message that arrives to a silo and all the headers and ids inside it may stay alive long enough to reach gen 2. /// Therefore, we store all ids in interner to re-use their memory across different messages. /// /// Type of objects to be used for intern keys. /// Type of objects to be interned. internal sealed class Interner : IDisposable where TKey : IEquatable where TValue : class { private readonly Timer cacheCleanupTimer; [NonSerialized] private readonly ConcurrentDictionary> internCache; /// /// Initializes a new instance of the class. /// /// The initial size of the interner mapping. public Interner(int initialSize = InternerConstants.SIZE_SMALL) { int concurrencyLevel = Environment.ProcessorCount; // Default from ConcurrentDictionary class in .NET Core for size 31 if (initialSize >= InternerConstants.SIZE_MEDIUM) concurrencyLevel *= 4; if (initialSize >= InternerConstants.SIZE_LARGE) concurrencyLevel *= 4; concurrencyLevel = Math.Min(concurrencyLevel, 1024); this.internCache = new ConcurrentDictionary>(concurrencyLevel, initialSize); var period = TimeSpan.FromMinutes(10); var dueTime = period + TimeSpan.FromTicks(Random.Shared.Next((int)TimeSpan.TicksPerMinute)); // add some initial jitter cacheCleanupTimer = new Timer(InternCacheCleanupTimerCallback, null, dueTime, period); } /// /// Find cached copy of object with specified key, otherwise create new one using the supplied creator-function. /// /// key to find /// function to create new object and store for this key if no cached copy exists /// Object with specified key - either previous cached copy or newly created public TValue FindOrCreate(TKey key, Func creatorFunc) { // Attempt to get the existing value from cache. // If no cache entry exists, create and insert a new one using the creator function. if (!internCache.TryGetValue(key, out var cacheEntry)) { var obj = creatorFunc(key); internCache[key] = new WeakReference(obj); return obj; } // If a cache entry did exist, determine if it still holds a valid value. if (!cacheEntry.TryGetTarget(out var result)) { // Create new object and ensure the entry is still valid by re-inserting it into the cache. var obj = creatorFunc(key); cacheEntry.SetTarget(obj); return obj; } return result; } /// /// Find cached copy of object with specified key, otherwise create new one using the supplied creator-function. /// /// key to find /// function to create new object and store for this key if no cached copy exists /// State to be passed to . /// Object with specified key - either previous cached copy or newly created public TValue FindOrCreate(TKey key, Func creatorFunc, TState state) { // Attempt to get the existing value from cache. // If no cache entry exists, create and insert a new one using the creator function. if (!internCache.TryGetValue(key, out var cacheEntry)) { var obj = creatorFunc(key, state); internCache[key] = new WeakReference(obj); return obj; } // If a cache entry did exist, determine if it still holds a valid value. if (!cacheEntry.TryGetTarget(out var result)) { // Create new object and ensure the entry is still valid by re-inserting it into the cache. var obj = creatorFunc(key, state); cacheEntry.SetTarget(obj); return obj; } return result; } private void InternCacheCleanupTimerCallback(object? state) { foreach (var e in internCache) { if (!e.Value.TryGetTarget(out _)) { internCache.TryRemove(e.Key, out _); } } } /// public void Dispose() { cacheCleanupTimer?.Dispose(); } } } ================================================ FILE: src/Orleans.Core.Abstractions/Utils/PublicOrleansTaskExtensions.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans { /// /// Utility functions for dealing with instances. /// public static class PublicOrleansTaskExtensions { private static readonly Action IgnoreTaskContinuation = t => { _ = t.Exception; }; /// /// Observes and ignores a potential exception on a given Task. /// If a Task fails and throws an exception which is never observed, it will be caught by the .NET finalizer thread. /// This function awaits the given task and if the exception is thrown, it observes this exception and simply ignores it. /// This will prevent the escalation of this exception to the .NET finalizer thread. /// /// The task to be ignored. public static void Ignore(this Task task) { if (task.IsCompleted) { _ = task.Exception; } else { task.ContinueWith( IgnoreTaskContinuation, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } } } } ================================================ FILE: src/Orleans.Core.Abstractions/Utils/SpanFormattableIPEndPoint.cs ================================================ using System; using System.Net; using System.Net.Sockets; namespace Orleans; internal readonly struct SpanFormattableIPEndPoint : ISpanFormattable { private readonly IPEndPoint? _value; public SpanFormattableIPEndPoint(IPEndPoint? value) => _value = value; public override string ToString() => $"{this}"; public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { if (_value is null) { charsWritten = 0; return true; } return _value.Address.AddressFamily == AddressFamily.InterNetworkV6 ? destination.TryWrite($"[{new SpanFormattableIPAddress(_value.Address)}]:{_value.Port}", out charsWritten) : destination.TryWrite($"{new SpanFormattableIPAddress(_value.Address)}:{_value.Port}", out charsWritten); } } internal readonly struct SpanFormattableIPAddress : ISpanFormattable { private readonly IPAddress _value; public SpanFormattableIPAddress(IPAddress value) => _value = value; public override string ToString() => _value.ToString(); public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => _value.TryFormat(destination, out charsWritten); } ================================================ FILE: src/Orleans.Core.Abstractions/Utils/Utils.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { /// /// The Utils class contains a variety of utility methods for use in application and grain code. /// public static partial class Utils { /// /// Returns a human-readable text string that describes an IEnumerable collection of objects. /// /// The type of the list elements. /// The IEnumerable to describe. /// Converts the element to a string. If none specified, will be used. /// The separator to use. /// Puts elements within brackets /// A string assembled by wrapping the string descriptions of the individual /// elements with square brackets and separating them with commas. public static string EnumerableToString(IEnumerable? collection, Func? toString = null, string separator = ", ", bool putInBrackets = true) { if (collection == null) return putInBrackets ? "[]" : "null"; if (collection is ICollection { Count: 0 }) return putInBrackets ? "[]" : ""; var enumerator = collection.GetEnumerator(); if (!enumerator.MoveNext()) return putInBrackets ? "[]" : ""; var firstValue = enumerator.Current; if (!enumerator.MoveNext()) { return putInBrackets ? toString != null ? $"[{toString(firstValue)}]" : firstValue == null ? "[null]" : $"[{firstValue}]" : toString != null ? toString(firstValue) : firstValue == null ? "null" : (firstValue.ToString() ?? ""); } var sb = new StringBuilder(); if (putInBrackets) sb.Append('['); if (toString != null) sb.Append(toString(firstValue)); else if (firstValue == null) sb.Append("null"); else sb.Append($"{firstValue}"); do { sb.Append(separator); var value = enumerator.Current; if (toString != null) sb.Append(toString(value)); else if (value == null) sb.Append("null"); else sb.Append($"{value}"); } while (enumerator.MoveNext()); if (putInBrackets) sb.Append(']'); return sb.ToString(); } /// /// Returns a human-readable text string that describes a dictionary that maps objects to objects. /// /// The type of the dictionary keys. /// The type of the dictionary elements. /// The dictionary to describe. /// Converts the element to a string. If none specified, will be used. /// The separator to use. If none specified, the elements should appear separated by a new line. /// A string assembled by wrapping the string descriptions of the individual /// pairs with square brackets and separating them with commas. /// Each key-value pair is represented as the string description of the key followed by /// the string description of the value, /// separated by " -> ", and enclosed in curly brackets. public static string DictionaryToString(ICollection> dict, Func? toString = null, string? separator = null) { if (dict == null || dict.Count == 0) { return "[]"; } if (separator == null) { separator = Environment.NewLine; } var sb = new StringBuilder("["); var enumerator = dict.GetEnumerator(); int index = 0; while (enumerator.MoveNext()) { var pair = enumerator.Current; sb.Append("{"); sb.Append(pair.Key); sb.Append(" -> "); string? val; if (toString != null) val = toString(pair.Value); else val = pair.Value == null ? "null" : pair.Value.ToString(); sb.Append(val); sb.Append("}"); if (index++ < dict.Count - 1) sb.Append(separator); } sb.Append("]"); return sb.ToString(); } public static string TimeSpanToString(TimeSpan timeSpan) { //00:03:32.8289777 return $"{timeSpan.Hours}h:{timeSpan.Minutes}m:{timeSpan.Seconds}s.{timeSpan.Milliseconds}ms"; } public static long TicksToMilliSeconds(long ticks) => ticks / TimeSpan.TicksPerMillisecond; public static float AverageTicksToMilliSeconds(float ticks) => ticks / TimeSpan.TicksPerMillisecond; /// /// Parse a Uri as an IPEndpoint. /// /// The input Uri /// public static System.Net.IPEndPoint? ToIPEndPoint(this Uri uri) => uri.Scheme switch { "gwy.tcp" => new System.Net.IPEndPoint(System.Net.IPAddress.Parse(uri.Host), uri.Port), _ => null, }; /// /// Parse a Uri as a Silo address, excluding the generation identifier. /// /// The input Uri public static SiloAddress? ToGatewayAddress(this Uri uri) => uri.Scheme switch { "gwy.tcp" => SiloAddress.New(System.Net.IPAddress.Parse(uri.Host), uri.Port, 0), _ => null, }; /// /// Represent an IP end point in the gateway URI format.. /// /// The input IP end point /// public static Uri ToGatewayUri(this System.Net.IPEndPoint ep) => new($"gwy.tcp://{new SpanFormattableIPEndPoint(ep)}/0"); /// /// Represent a silo address in the gateway URI format. /// /// The input silo address /// public static Uri ToGatewayUri(this SiloAddress address) => new($"gwy.tcp://{new SpanFormattableIPEndPoint(address.Endpoint)}/{address.Generation}"); public static void SafeExecute(Action action) { try { action(); } catch { } } public static void SafeExecute(Action action, ILogger? logger = null, string? caller = null) { try { action(); } catch (Exception exc) { if (logger != null) LogIgnoredException(logger, exc, caller); } } public static async Task SafeExecuteAsync(Task task) { try { await task; } catch { } } internal static void LogIgnoredException(ILogger logger, Exception exc, string? caller) { try { if (exc is AggregateException { InnerExceptions.Count: 1 }) { Debug.Assert(exc.InnerException is not null, "AggregateException should have an inner exception."); exc = exc.InnerException; } LogWarningIgnoringException(logger, exc, new(exc), caller ?? string.Empty); } catch { // now really, really ignore. } } public static IEnumerable> BatchIEnumerable(this IEnumerable sequence, int batchSize) { var batch = new List(batchSize); foreach (var item in sequence) { batch.Add(item); // when we've accumulated enough in the batch, send it out if (batch.Count >= batchSize) { yield return batch; // batch.ToArray(); batch = new List(batchSize); } } if (batch.Count > 0) { yield return batch; //batch.ToArray(); } } [MethodImpl(MethodImplOptions.NoInlining)] public static string GetStackTrace(int skipFrames = 0) { skipFrames += 1; //skip this method from the stack trace return new System.Diagnostics.StackTrace(skipFrames).ToString(); } private readonly struct ExceptionTypeLogValue(Exception exc) { public override string? ToString() => exc.GetType().FullName; } [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100325, Level = LogLevel.Warning, Message = "Ignoring {ExceptionType} exception thrown from an action called by {Caller}." )] private static partial void LogWarningIgnoringException(ILogger logger, Exception exception, ExceptionTypeLogValue exceptionType, string caller); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Compatibility/AllVersionsCompatible.cs ================================================ using System; namespace Orleans.Versions.Compatibility { /// /// A grain interface version compatibility strategy which treats all versions of an interface compatible with any requested version. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class AllVersionsCompatible : CompatibilityStrategy { /// /// Gets the singleton instance of this class. /// public static AllVersionsCompatible Singleton { get; } = new AllVersionsCompatible(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Compatibility/BackwardCompatible.cs ================================================ using System; namespace Orleans.Versions.Compatibility { /// /// A grain interface version compatibility strategy which treats all versions of an interface compatible only with equal and lower requested versions. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class BackwardCompatible : CompatibilityStrategy { /// /// Gets the singleton instance of this class. /// public static BackwardCompatible Singleton { get; } = new BackwardCompatible(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Compatibility/ICompatibilityDirector.cs ================================================ using System; namespace Orleans.Versions.Compatibility { /// /// Functionality for grain interface compatibility directors. /// public interface ICompatibilityDirector { /// /// Returns if the current version of an interface is compatible with the requested version, otherwise. /// /// The requested interface version. /// The currently available interface version. /// if the current version of an interface is compatible with the requested version, otherwise. bool IsCompatible(ushort requestedVersion, ushort currentVersion); } /// /// Base class for all grain interface version compatibility strategies. /// [Serializable, SerializerTransparent] public abstract class CompatibilityStrategy { } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Compatibility/StrictVersionCompatible.cs ================================================ using System; namespace Orleans.Versions.Compatibility { /// /// A grain interface version compatibility strategy which treats all versions of an interface compatible only with equal requested versions. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class StrictVersionCompatible : CompatibilityStrategy { /// /// Gets the singleton instance of this class. /// public static StrictVersionCompatible Singleton { get; } = new StrictVersionCompatible(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/IVersionStore.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Versions { /// /// Functionality for accessing runtime-modifiable grain interface version strategies. /// public interface IVersionStore : IVersionManager { /// /// Gets a value indicating whether this instance is enabled. /// bool IsEnabled { get; } /// /// Gets the mapping from grain interface type to grain interface version compatibility strategy. /// /// The mapping from grain interface type to grain interface version compatibility strategy. Task> GetCompatibilityStrategies(); /// /// Gets the mapping from grain interface type to grain interface version selector strategy. /// /// The mapping from grain interface type to grain interface version selector strategy. Task> GetSelectorStrategies(); /// /// Gets the default grain interface version compatibility strategy. /// /// The default grain interface version compatibility strategy. Task GetCompatibilityStrategy(); /// /// Gets the default grain interface version selector strategy. /// /// The default grain interface version selector strategy. Task GetSelectorStrategy(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Selector/AllCompatibleVersions.cs ================================================ using System; namespace Orleans.Versions.Selector { /// /// Grain interface version selector which allows any compatible version to be chosen. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class AllCompatibleVersions : VersionSelectorStrategy { /// /// Gets the singleton instance of this class. /// public static AllCompatibleVersions Singleton { get; } = new AllCompatibleVersions(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Selector/IVersionSelector.cs ================================================ using System; using Orleans.Versions.Compatibility; namespace Orleans.Versions.Selector { /// /// Functionality for selecting which versions of a grain interface should be preferred when performing grain placement. /// public interface IVersionSelector { /// /// Returns a collection of suitable interface versions for a given request. /// /// The requested grain interface version. /// The collection of available interface versions. /// The compatibility director. /// A collection of suitable interface versions for a given request. ushort[] GetSuitableVersion(ushort requestedVersion, ushort[] availableVersions, ICompatibilityDirector compatibilityDirector); } /// /// Base class for all grain interface version selector strategies. /// [Serializable, SerializerTransparent] public abstract class VersionSelectorStrategy { } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Selector/LatestVersion.cs ================================================ using System; namespace Orleans.Versions.Selector { /// /// Grain interface version selector which always selects the highest compatible version. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class LatestVersion : VersionSelectorStrategy { /// /// Gets the singleton instance of this class. /// public static LatestVersion Singleton { get; } = new LatestVersion(); } } ================================================ FILE: src/Orleans.Core.Abstractions/Versions/Selector/MinimumVersion.cs ================================================ using System; namespace Orleans.Versions.Selector { /// /// Grain interface version selector which always selects the lowest compatible version. /// [Serializable, GenerateSerializer, Immutable, SuppressReferenceTracking] public sealed class MinimumVersion : VersionSelectorStrategy { /// /// Gets the singleton instance of this class. /// public static MinimumVersion Singleton { get; } = new MinimumVersion(); } } ================================================ FILE: src/Orleans.Core.Abstractions/build/Microsoft.Orleans.Core.Abstractions.targets ================================================ ================================================ FILE: src/Orleans.Core.Abstractions/buildMultiTargeting/Microsoft.Orleans.Core.Abstractions.targets ================================================ ================================================ FILE: src/Orleans.Core.Abstractions/buildTransitive/Microsoft.Orleans.Core.Abstractions.targets ================================================ ================================================ FILE: src/Orleans.DurableJobs/DurableJob.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Represents a durable job that will be executed at a specific time. /// [GenerateSerializer] [Alias("Orleans.DurableJobs.DurableJob")] public sealed class DurableJob { /// /// Gets the unique identifier for this durable job. /// [Id(0)] public required string Id { get; init; } /// /// Gets the name of the durable job. /// [Id(1)] public required string Name { get; init; } /// /// Gets the time when this job is due to be executed. /// [Id(2)] public DateTimeOffset DueTime { get; init; } /// /// Gets the identifier of the target grain that will handle this job. /// [Id(3)] public GrainId TargetGrainId { get; init; } /// /// Gets the identifier of the shard that manages this durable job. /// [Id(4)] public required string ShardId { get; init; } /// /// Gets optional metadata associated with this durable job. /// [Id(5)] public IReadOnlyDictionary? Metadata { get; init; } } ================================================ FILE: src/Orleans.DurableJobs/DurableJobRunResult.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Orleans.DurableJobs; /// /// Represents the result of a durable job execution. /// [GenerateSerializer] public sealed class DurableJobRunResult { /// /// Gets a value indicating whether the job execution failed. /// [MemberNotNullWhen(true, nameof(Exception))] public bool IsFailed => Status == DurableJobRunStatus.Failed; /// /// Gets a value indicating whether the job should be polled again after a delay. /// [MemberNotNullWhen(true, nameof(PollAfterDelay))] public bool IsPending => Status == DurableJobRunStatus.PollAfter; private DurableJobRunResult(DurableJobRunStatus status, TimeSpan? pollAfter, Exception? exception) { Status = status; PollAfterDelay = pollAfter; Exception = exception; } /// /// Gets the status of the job execution. /// [Id(0)] public DurableJobRunStatus Status { get; } /// /// Gets the delay before the next status check when is . /// [Id(1)] public TimeSpan? PollAfterDelay { get; } /// /// Gets the exception associated with a failed job execution when is . /// [Id(2)] public Exception? Exception { get; } private static readonly DurableJobRunResult CompletedInstance = new(DurableJobRunStatus.Completed, null, null); /// /// Gets a result indicating the job completed successfully. /// public static DurableJobRunResult Completed => CompletedInstance; /// /// Creates a result indicating the job should be polled again after the specified delay. /// /// The time to wait before checking the job status again. /// A poll-after job result. /// /// The job will remain in an inline polling loop without being re-queued. /// The polling loop will hold a concurrency slot until the job completes or fails. /// TODO: Add validation for minimum/maximum poll delays to prevent abuse. /// TODO: Consider concurrency slot management for long-running polls. /// public static DurableJobRunResult PollAfter(TimeSpan delay) { ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(delay, TimeSpan.Zero, nameof(delay)); return new(DurableJobRunStatus.PollAfter, delay, null); } /// /// Creates a result indicating the job failed. /// /// The exception that caused the failure. This will be passed to the retry policy. /// A failed job result. /// /// The exception will be passed to the retry callback to determine if the job should be retried. /// public static DurableJobRunResult Failed(Exception exception) { ArgumentNullException.ThrowIfNull(exception); return new(DurableJobRunStatus.Failed, null, exception); } } /// /// Represents the status of a durable job execution. /// public enum DurableJobRunStatus { /// /// The job completed successfully and should be removed from the queue. /// Completed, /// /// The job is still running and should be polled again after the specified delay. /// PollAfter, /// /// The job failed and should be processed through the retry policy. /// Failed } ================================================ FILE: src/Orleans.DurableJobs/Hosting/DurableJobsExtensions.cs ================================================ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration.Internal; using Orleans.Runtime; using Orleans.DurableJobs; namespace Orleans.Hosting; /// /// Extensions to for configuring durable jobs. /// public static class DurableJobsExtensions { /// /// Adds support for durable jobs to this silo. /// /// The builder. /// The silo builder. public static ISiloBuilder AddDurableJobs(this ISiloBuilder builder) => builder.ConfigureServices(services => AddDurableJobs(services)); /// /// Adds support for durable jobs to this silo. /// /// The services. public static void AddDurableJobs(this IServiceCollection services) { if (services.Any(service => service.ServiceType.Equals(typeof(LocalDurableJobManager)))) { return; } services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, LocalDurableJobManager>(); services.AddKeyedTransient(typeof(IDurableJobReceiverExtension), (sp, _) => { var grainContextAccessor = sp.GetRequiredService(); return new DurableJobReceiverExtension(grainContextAccessor.GrainContext, sp.GetRequiredService>()); }); } /// /// Configures durable jobs storage using an in-memory, non-persistent store. /// /// /// Note that this is for development and testing scenarios only and should not be used in production. /// /// The silo host builder. /// The provided , for chaining. public static ISiloBuilder UseInMemoryDurableJobs(this ISiloBuilder builder) { builder.AddDurableJobs(); builder.ConfigureServices(services => services.UseInMemoryDurableJobs()); return builder; } /// /// Configures durable jobs storage using an in-memory, non-persistent store. /// /// /// Note that this is for development and testing scenarios only and should not be used in production. /// /// The service collection. /// The provided , for chaining. internal static IServiceCollection UseInMemoryDurableJobs(this IServiceCollection services) { services.AddSingleton(sp => { var siloDetails = sp.GetRequiredService(); var membershipService = sp.GetRequiredService(); var durableJobsOptions = sp.GetRequiredService>(); return new InMemoryJobShardManager(siloDetails.SiloAddress, membershipService, durableJobsOptions.Value.MaxAdoptedCount); }); services.AddFromExisting(); return services; } } ================================================ FILE: src/Orleans.DurableJobs/Hosting/DurableJobsOptions.cs ================================================ using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.DurableJobs; namespace Orleans.Hosting; /// /// Configuration options for the durable jobs feature. /// public sealed class DurableJobsOptions { /// /// Gets or sets the duration of each job shard. Smaller values reduce latency but increase overhead. /// For optimal alignment with hour boundaries, choose durations that evenly divide 60 minutes /// (e.g., 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, or 60 minutes) to avoid bucket drift across hours. /// Default: 1 hour. /// public TimeSpan ShardDuration { get; set; } = TimeSpan.FromHours(1); /// /// Gets or sets how far in advance (before the shard's start time) the shard should /// begin processing. This prevents holding idle shards for extended periods. /// Default: 5 minutes. /// public TimeSpan ShardActivationBufferPeriod { get; set; } = TimeSpan.FromMinutes(5); /// /// Gets or sets the maximum number of jobs that can be executed concurrently on a single silo. /// Default: 10,000 × processor count. /// public int MaxConcurrentJobsPerSilo { get; set; } = 10_000 * Environment.ProcessorCount; /// /// Gets or sets the delay between overload checks when the host is overloaded. /// Job batch processing will pause for this duration before rechecking the overload status. /// Default: 5 second. /// public TimeSpan OverloadBackoffDelay { get; set; } = TimeSpan.FromSeconds(5); /// /// Gets or sets whether concurrent job slow start is enabled. /// When enabled, job concurrency is gradually increased during startup to avoid starvation /// issues that can occur before caches, connection pools, and thread pool sizing have warmed up. /// Concurrency starts at and doubles every /// until is reached. /// Default: . /// public bool ConcurrencySlowStartEnabled { get; set; } = true; /// /// Gets or sets the initial number of concurrent jobs allowed per silo when slow start is enabled. /// Concurrency will exponentially increase from this value until is reached. /// Default: . /// public int SlowStartInitialConcurrency { get; set; } = Environment.ProcessorCount; /// /// Gets or sets the interval at which concurrency is doubled during slow start ramp-up. /// Default: 10 seconds. /// public TimeSpan SlowStartInterval { get; set; } = TimeSpan.FromSeconds(10); /// /// Gets or sets the function that determines whether a failed job should be retried and when. /// The function receives the job context and the exception that caused the failure, and returns /// the time when the job should be retried, or if the job should not be retried. /// Default: Retry up to 5 times with exponential backoff (2^n seconds). /// public Func ShouldRetry { get; set; } = DefaultShouldRetry; /// /// Gets or sets the maximum number of times a shard can be adopted from a dead owner before /// being marked as poisoned. A shard that repeatedly causes silos to crash will exceed this /// threshold as it bounces between owners. When the next adoption would cause the adopted count /// to exceed this value, the shard is considered poisoned and will no longer be assigned to any silo. /// Default: 3. /// /// /// /// The adopted count is only incremented when a shard is taken from a dead silo (i.e., the previous /// owner crashed). It is NOT incremented when a silo gracefully shuts down and releases ownership. /// /// /// When a shard completes successfully (all jobs processed), the adopted count is reset to 0. /// /// public int MaxAdoptedCount { get; set; } = 3; private static DateTimeOffset? DefaultShouldRetry(IJobRunContext jobContext, Exception ex) { // Default retry logic: retry up to 5 times with exponential backoff if (jobContext.DequeueCount >= 5) { return null; } var delay = TimeSpan.FromSeconds(Math.Pow(2, jobContext.DequeueCount)); return DateTimeOffset.UtcNow.Add(delay); } } public sealed class DurableJobsOptionsValidator : IConfigurationValidator { private readonly ILogger _logger; private readonly IOptions _options; public DurableJobsOptionsValidator(ILogger logger, IOptions options) { _logger = logger; _options = options; } public void ValidateConfiguration() { var options = _options.Value; if (options.ShardDuration <= TimeSpan.Zero) { throw new OrleansConfigurationException("DurableJobsOptions.ShardDuration must be greater than zero."); } if (options.ShouldRetry is null) { throw new OrleansConfigurationException("DurableJobsOptions.ShouldRetry must not be null."); } if (options.ConcurrencySlowStartEnabled && options.SlowStartInitialConcurrency <= 0) { throw new OrleansConfigurationException("DurableJobsOptions.SlowStartInitialConcurrency must be greater than zero."); } if (options.ConcurrencySlowStartEnabled && options.SlowStartInterval <= TimeSpan.Zero) { throw new OrleansConfigurationException("DurableJobsOptions.SlowStartInterval must be greater than zero when slow start is enabled."); } if (options.ConcurrencySlowStartEnabled && options.SlowStartInitialConcurrency > options.MaxConcurrentJobsPerSilo) { _logger.LogWarning( "DurableJobsOptions.SlowStartInitialConcurrency ({SlowStartInitialConcurrency}) exceeds MaxConcurrentJobsPerSilo ({MaxConcurrentJobsPerSilo}); slow start will not be applied.", options.SlowStartInitialConcurrency, options.MaxConcurrentJobsPerSilo); } if (options.MaxAdoptedCount < 0) { throw new OrleansConfigurationException("DurableJobsOptions.MaxAdoptedCount must be greater than or equal to zero."); } _logger.LogInformation("DurableJobsOptions validated: ShardDuration={ShardDuration}", options.ShardDuration); } } ================================================ FILE: src/Orleans.DurableJobs/IDurableJobHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace Orleans.DurableJobs; /// /// Provides contextual information about a durable job execution. /// public interface IJobRunContext { /// /// Gets the durable job being executed. /// DurableJob Job { get; } /// /// Gets the unique identifier for this execution run. /// string RunId { get; } /// /// Gets the number of times this job has been dequeued for execution, including retries. /// int DequeueCount { get; } } /// /// Represents the execution context for a durable job. /// [GenerateSerializer] internal class JobRunContext : IJobRunContext { /// /// Gets the durable job being executed. /// [Id(0)] public DurableJob Job { get; } /// /// Gets the unique identifier for this execution run. /// [Id(1)] public string RunId { get; } /// /// Gets the number of times this job has been dequeued for execution, including retries. /// [Id(2)] public int DequeueCount { get; } /// /// Initializes a new instance of the class. /// /// The durable job to execute. /// The unique identifier for this execution run. /// The number of times this job has been dequeued, including retries. public JobRunContext(DurableJob job, string runId, int retryCount) { Job = job; RunId = runId; DequeueCount = retryCount; } } /// /// Defines the interface for handling durable job execution. /// Grains implement this interface to receive and process durable jobs. /// /// /// /// Grains that implement this interface can be targeted by durable jobs. /// The method is invoked when the job's due time is reached. /// /// /// The following example demonstrates a grain that implements : /// /// public class MyGrain : Grain, IDurableJobHandler /// { /// public Task ExecuteJobAsync(IJobRunContext context, CancellationToken cancellationToken) /// { /// // Process the durable job /// var jobName = context.Job.Name; /// var dueTime = context.Job.DueTime; /// /// // Perform job logic here /// /// return Task.CompletedTask; /// } /// } /// /// /// public interface IDurableJobHandler { /// /// Executes the durable job with the provided context. /// /// The context containing information about the durable job execution. /// A token to monitor for cancellation requests. /// A task that represents the asynchronous job execution operation. /// /// /// This method is invoked by the Orleans durable jobs infrastructure when a job's due time is reached. /// Implementations should handle job execution logic and can use information from the /// to access job metadata, dequeue count for retry logic, and other execution details. /// /// /// If the method throws an exception and a retry policy is configured, the job may be retried. /// The property can be used to determine if this is a retry attempt. /// /// Task ExecuteJobAsync(IJobRunContext context, CancellationToken cancellationToken); } ================================================ FILE: src/Orleans.DurableJobs/IDurableJobReceiverExtension.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Extension interface for grains that can receive durable job invocations. /// internal interface IDurableJobReceiverExtension : IGrainExtension { /// /// Handles a durable job by either starting execution or checking the status of an already running job. /// If the job identified by has not been started, it will be executed. /// If it is already running, the current status is returned. /// /// The context containing information about the durable job. /// A token to monitor for cancellation requests. /// A task that represents the asynchronous operation and contains the job execution result. [AlwaysInterleave] Task HandleDurableJobAsync(IJobRunContext context, CancellationToken cancellationToken); } /// internal sealed partial class DurableJobReceiverExtension : IDurableJobReceiverExtension { private readonly IGrainContext _grain; private readonly ILogger _logger; private readonly ConcurrentDictionary _runningJobs = new(); public DurableJobReceiverExtension(IGrainContext grain, ILogger logger) { _grain = grain; _logger = logger; } /// public Task HandleDurableJobAsync(IJobRunContext context, CancellationToken cancellationToken) { if (_runningJobs.TryGetValue(context.RunId, out var runningTask)) { return GetJobStatus(context, runningTask); } return StartJobAsync(context, cancellationToken); } private Task StartJobAsync(IJobRunContext context, CancellationToken cancellationToken) { if (_grain.GrainInstance is not IDurableJobHandler handler) { LogGrainDoesNotImplementHandler(_grain.GrainId); throw new InvalidOperationException($"Grain {_grain.GrainId} does not implement IDurableJobHandler"); } var task = handler.ExecuteJobAsync(context, cancellationToken); _runningJobs[context.RunId] = task; return GetJobStatus(context, task); } private Task GetJobStatus(IJobRunContext context, Task task) { // Cancellation is cooperative: only terminal task state is authoritative for job outcome. if (!task.IsCompleted) { return Task.FromResult(DurableJobRunResult.PollAfter(TimeSpan.FromSeconds(1))); } _runningJobs.TryRemove(context.RunId, out _); if (task.IsCompletedSuccessfully) { return Task.FromResult(DurableJobRunResult.Completed); } if (task.IsFaulted) { var ex = task.Exception!.InnerException ?? task.Exception; LogErrorExecutingDurableJob(ex, context.Job.Id, _grain.GrainId); return Task.FromResult(DurableJobRunResult.Failed(ex)); } return Task.FromCanceled(new CancellationToken(canceled: true)); } [LoggerMessage(Level = LogLevel.Error, Message = "Error executing durable job {JobId} on grain {GrainId}")] private partial void LogErrorExecutingDurableJob(Exception exception, string jobId, GrainId grainId); [LoggerMessage(Level = LogLevel.Error, Message = "Grain {GrainId} does not implement IDurableJobHandler")] private partial void LogGrainDoesNotImplementHandler(GrainId grainId); } ================================================ FILE: src/Orleans.DurableJobs/ILocalDurableJobManager.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Provides functionality for scheduling and managing jobs on the local silo. /// public interface ILocalDurableJobManager { /// /// Schedules a job to be executed at a specific time on the target grain. /// /// The request containing the job scheduling parameters. /// A cancellation token to cancel the operation. /// A representing the asynchronous operation that returns the durable job. Task ScheduleJobAsync(ScheduleJobRequest request, CancellationToken cancellationToken); /// /// Attempts to cancel a previously scheduled durable job. /// /// The durable job to cancel. /// A cancellation token to cancel the operation. /// A representing the asynchronous operation that returns if the job was successfully canceled; otherwise, . Task TryCancelDurableJobAsync(DurableJob job, CancellationToken cancellationToken); } ================================================ FILE: src/Orleans.DurableJobs/InMemoryJobQueue.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Orleans.DurableJobs; /// /// Provides an in-memory priority queue for managing durable jobs based on their due times. /// Jobs are organized into time-based buckets and enumerated asynchronously as they become due. /// internal sealed class InMemoryJobQueue : IAsyncEnumerable { private readonly PriorityQueue _queue = new(); private readonly Dictionary _jobsIdToBucket = new(); private readonly Dictionary _buckets = new(); private bool _isComplete; #if NET9_0_OR_GREATER private readonly Lock _syncLock = new(); #else private readonly object _syncLock = new(); #endif /// /// Gets the total number of jobs currently in the queue. /// public int Count => _jobsIdToBucket.Count; /// /// Adds a durable job to the queue with the specified dequeue count. /// /// The durable job to enqueue. /// The number of times this job has been dequeued previously. /// Thrown when attempting to enqueue a job to a completed queue. /// Thrown when job is null. public void Enqueue(DurableJob job, int dequeueCount) { ArgumentNullException.ThrowIfNull(job); lock (_syncLock) { if (_isComplete) throw new InvalidOperationException("Cannot enqueue job to a completed queue."); var bucket = GetJobBucket(job.DueTime); bucket.AddJob(job, dequeueCount); _jobsIdToBucket[job.Id] = bucket; } } /// /// Marks the queue as complete, preventing any further jobs from being enqueued. /// Once marked complete, the queue will finish processing remaining jobs and then terminate enumeration. /// public void MarkAsComplete() { lock (_syncLock) { _isComplete = true; } } /// /// Cancels a durable job by removing it from the queue. /// /// The unique identifier of the job to cancel. /// True if the job was found and removed; false if the job was not found. /// /// The job's bucket remains in the priority queue until processed, but the job itself is removed immediately. /// public bool CancelJob(string jobId) { lock (_syncLock) { if (_jobsIdToBucket.TryGetValue(jobId, out var bucket)) { // Try to remove from bucket (may already be dequeued) bucket.RemoveJob(jobId); _jobsIdToBucket.Remove(jobId); // Note: The bucket remains in the priority queue until processed return true; } return false; } } /// /// Reschedules a job for retry with a new due time. /// /// The context of the job to retry. /// The new due time for the job. /// /// The job is removed from its current bucket and added to a new bucket based on the specified due time. /// The dequeue count from the context is preserved. /// public void RetryJobLater(IJobRunContext jobContext, DateTimeOffset newDueTime) { var jobId = jobContext.Job.Id; var newJob = new DurableJob { Id = jobContext.Job.Id, Name = jobContext.Job.Name, DueTime = newDueTime, TargetGrainId = jobContext.Job.TargetGrainId, ShardId = jobContext.Job.ShardId, Metadata = jobContext.Job.Metadata }; lock (_syncLock) { if (_jobsIdToBucket.TryGetValue(jobId, out var oldBucket)) { oldBucket.RemoveJob(jobId); _jobsIdToBucket.Remove(jobId); var newBucket = GetJobBucket(newDueTime); newBucket.AddJob(newJob, jobContext.DequeueCount); _jobsIdToBucket[jobId] = newBucket; } } } /// /// Returns an asynchronous enumerator that yields durable jobs as they become due. /// /// A token to monitor for cancellation requests. /// /// An async enumerator that returns instances for jobs that are due. /// The enumerator checks for due jobs every second and terminates when the queue is marked complete and empty. /// public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); while (true) { JobBucket? bucketToProcess = null; DateTimeOffset bucketKey = default; lock (_syncLock) { if (Count == 0) { if (_isComplete) { yield break; // Exit if the queue is frozen and empty } } else if (_queue.Count > 0) { var nextBucket = _queue.Peek(); if (nextBucket.DueTime < DateTimeOffset.UtcNow) { // Dequeue the entire bucket to process outside the lock bucketToProcess = _queue.Dequeue(); bucketKey = bucketToProcess.DueTime; } } } if (bucketToProcess is not null) { // Process all jobs in the bucket outside the lock for better concurrency foreach (var (job, dequeueCount) in bucketToProcess.Jobs.ToList()) { // Verify job hasn't been cancelled while we were processing bool shouldYield; lock (_syncLock) { shouldYield = _jobsIdToBucket.ContainsKey(job.Id); // Keep job in _jobsIdToBucket for explicit removal via CancelJob/RetryJobLater } if (shouldYield) { yield return new JobRunContext(job, Guid.NewGuid().ToString(), dequeueCount + 1); } } // Clean up the bucket from dictionary after processing all jobs lock (_syncLock) { _buckets.Remove(bucketKey); } } else { await timer.WaitForNextTickAsync(cancellationToken); } } } private JobBucket GetJobBucket(DateTimeOffset dueTime) { // Truncate to second precision and add 1 second to normalize bucket key // This ensures all jobs within the same second (e.g., 12:00:00.000-12:00:00.999) share the same bucket (12:00:01) var key = new DateTimeOffset(dueTime.Year, dueTime.Month, dueTime.Day, dueTime.Hour, dueTime.Minute, dueTime.Second, dueTime.Offset); key = key.AddSeconds(1); if (!_buckets.TryGetValue(key, out var bucket)) { bucket = new JobBucket(key); _buckets[key] = bucket; _queue.Enqueue(bucket, key); } return bucket; } } internal sealed class JobBucket { private readonly Dictionary _jobs = new(); public int Count => _jobs.Count; public DateTimeOffset DueTime { get; private set; } public IEnumerable<(DurableJob Job, int DequeueCount)> Jobs => _jobs.Values; public JobBucket(DateTimeOffset dueTime) { DueTime = dueTime; } public void AddJob(DurableJob job, int dequeueCount) { _jobs[job.Id] = (job, dequeueCount); } public bool RemoveJob(string jobId) { return _jobs.Remove(jobId); } } ================================================ FILE: src/Orleans.DurableJobs/InMemoryJobShard.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.DurableJobs; [DebuggerDisplay("ShardId={Id}, StartTime={StartTime}, EndTime={EndTime}")] internal sealed class InMemoryJobShard : JobShard { public InMemoryJobShard(string shardId, DateTimeOffset minDueTime, DateTimeOffset maxDueTime, IDictionary? metadata) : base(shardId, minDueTime, maxDueTime) { Metadata = metadata; } protected override Task PersistAddJobAsync(string jobId, string jobName, DateTimeOffset dueTime, GrainId target, IReadOnlyDictionary? metadata, CancellationToken cancellationToken) { return Task.CompletedTask; } protected override Task PersistRemoveJobAsync(string jobId, CancellationToken cancellationToken) { return Task.CompletedTask; } protected override Task PersistRetryJobAsync(string jobId, DateTimeOffset newDueTime, CancellationToken cancellationToken) { return Task.CompletedTask; } } ================================================ FILE: src/Orleans.DurableJobs/JobShard.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Represents a shard of durable jobs that manages a collection of jobs within a specific time range. /// A job shard is responsible for storing, retrieving, and managing the lifecycle of durable jobs /// that fall within its designated time window. /// /// /// Job shards are used to partition durable jobs across time ranges to improve scalability /// and performance. Each shard has a defined start and end time that determines which jobs /// it manages. Shards can be marked as complete when all jobs within their time range /// have been processed. /// public interface IJobShard : IAsyncDisposable { /// /// Gets the unique identifier for this job shard. /// string Id { get; } /// /// Gets the start time of the time range managed by this shard. /// DateTimeOffset StartTime { get; } /// /// Gets the end time of the time range managed by this shard. /// DateTimeOffset EndTime { get; } /// /// Gets optional metadata associated with this job shard. /// IDictionary? Metadata { get; } /// /// Gets a value indicating whether this shard has been marked as complete and is no longer accepting new jobs. /// /// /// When a shard is marked as complete (via ), no new jobs can be added to it. /// bool IsAddingCompleted { get; } /// /// Consumes durable jobs from this shard in order of their due time. /// /// An asynchronous enumerable of durable job contexts. IAsyncEnumerable ConsumeDurableJobsAsync(); /// /// Gets the number of jobs currently scheduled in this shard. /// /// A task that represents the asynchronous operation. The task result contains the job count. ValueTask GetJobCountAsync(); /// /// Marks this shard as complete, preventing new jobs from being scheduled. /// /// A token to cancel the operation. /// A task that represents the asynchronous operation. Task MarkAsCompleteAsync(CancellationToken cancellationToken); /// /// Removes a durable job from this shard. /// /// The unique identifier of the job to remove. /// A token to cancel the operation. /// A task that represents the asynchronous operation. The task result contains true if the job was successfully removed, or false if the job was not found. Task RemoveJobAsync(string jobId, CancellationToken cancellationToken); /// /// Reschedules a job to be retried at a later time. /// /// The context of the job to retry. /// The new due time for the job. /// A token to cancel the operation. /// A task that represents the asynchronous operation. Task RetryJobLaterAsync(IJobRunContext jobContext, DateTimeOffset newDueTime, CancellationToken cancellationToken); /// /// Attempts to schedule a new job on this shard. /// /// The request containing the job scheduling parameters. /// A token to cancel the operation. /// A task that represents the asynchronous operation. The task result contains the durable job if successful, or null if the job could not be scheduled (e.g., the shard was marked as complete). /// Thrown when the due time is outside the shard's time range. Task TryScheduleJobAsync(ScheduleJobRequest request, CancellationToken cancellationToken); } /// /// Base implementation of that provides common functionality for job shard implementations. /// public abstract class JobShard : IJobShard { private readonly InMemoryJobQueue _jobQueue; /// public string Id { get; protected set; } /// public DateTimeOffset StartTime { get; protected set; } /// public DateTimeOffset EndTime { get; protected set; } /// public IDictionary? Metadata { get; protected set; } /// public bool IsAddingCompleted { get; protected set; } /// /// Initializes a new instance of the class. /// /// The unique identifier for this job shard. /// The start time of the time range managed by this shard. /// The end time of the time range managed by this shard. protected JobShard(string id, DateTimeOffset startTime, DateTimeOffset endTime) { Id = id; StartTime = startTime; EndTime = endTime; _jobQueue = new InMemoryJobQueue(); } /// public ValueTask GetJobCountAsync() => ValueTask.FromResult(_jobQueue.Count); /// public IAsyncEnumerable ConsumeDurableJobsAsync() { return _jobQueue; } /// public async Task TryScheduleJobAsync(ScheduleJobRequest request, CancellationToken cancellationToken) { if (IsAddingCompleted) { return null; } if (request.DueTime < StartTime || request.DueTime > EndTime) { throw new ArgumentOutOfRangeException(nameof(request), "Scheduled time is out of shard bounds."); } var jobId = Guid.NewGuid().ToString(); var job = new DurableJob { Id = jobId, TargetGrainId = request.Target, Name = request.JobName, DueTime = request.DueTime, ShardId = Id, Metadata = request.Metadata }; await PersistAddJobAsync(jobId, request.JobName, request.DueTime, request.Target, request.Metadata, cancellationToken); _jobQueue.Enqueue(job, 0); return job; } /// public async Task RemoveJobAsync(string jobId, CancellationToken cancellationToken) { await PersistRemoveJobAsync(jobId, cancellationToken); return _jobQueue.CancelJob(jobId); } /// public Task MarkAsCompleteAsync(CancellationToken cancellationToken) { IsAddingCompleted = true; _jobQueue.MarkAsComplete(); return Task.CompletedTask; } /// public async Task RetryJobLaterAsync(IJobRunContext jobContext, DateTimeOffset newDueTime, CancellationToken cancellationToken) { await PersistRetryJobAsync(jobContext.Job.Id, newDueTime, cancellationToken); _jobQueue.RetryJobLater(jobContext, newDueTime); } /// /// Enqueues a job into the in-memory queue with the specified dequeue count. /// /// The job to enqueue. /// The number of times this job has been dequeued. protected void EnqueueJob(DurableJob job, int dequeueCount) { _jobQueue.Enqueue(job, dequeueCount); } /// /// Persists the addition of a new job to the underlying storage. /// /// The unique identifier of the job. /// The name of the job. /// The time when the job should be executed. /// The grain identifier of the target grain. /// Optional metadata to associate with the job. /// A token to cancel the operation. /// A task that represents the asynchronous operation. protected abstract Task PersistAddJobAsync(string jobId, string jobName, DateTimeOffset dueTime, GrainId target, IReadOnlyDictionary? metadata, CancellationToken cancellationToken); /// /// Persists the removal of a job from the underlying storage. /// /// The unique identifier of the job to remove. /// A token to cancel the operation. /// A task that represents the asynchronous operation. protected abstract Task PersistRemoveJobAsync(string jobId, CancellationToken cancellationToken); /// /// Persists the rescheduling of a job to the underlying storage. /// /// The unique identifier of the job to retry. /// The new due time for the job. /// A token to cancel the operation. /// A task that represents the asynchronous operation. protected abstract Task PersistRetryJobAsync(string jobId, DateTimeOffset newDueTime, CancellationToken cancellationToken); /// public virtual ValueTask DisposeAsync() { GC.SuppressFinalize(this); return default; } } ================================================ FILE: src/Orleans.DurableJobs/JobShardManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Manages the lifecycle of job shards for a specific silo. /// Each silo instance has its own shard manager. /// public abstract class JobShardManager { /// /// Gets the silo address this manager is associated with. /// protected SiloAddress SiloAddress { get; } /// /// Initializes a new instance of the class. /// /// The silo address this manager represents. protected JobShardManager(SiloAddress siloAddress) { SiloAddress = siloAddress; } /// /// Assigns orphaned job shards to this silo. /// /// Maximum due time for shards to consider. /// Cancellation token. /// A list of job shards assigned to this silo. public abstract Task> AssignJobShardsAsync(DateTimeOffset maxDueTime, CancellationToken cancellationToken); /// /// Creates a new job shard owned by this silo. /// /// The minimum due time for jobs in this shard. /// The maximum due time for jobs in this shard. /// Optional metadata for the shard. /// Cancellation token. /// The newly created job shard. public abstract Task CreateShardAsync(DateTimeOffset minDueTime, DateTimeOffset maxDueTime, IDictionary metadata, CancellationToken cancellationToken); /// /// Unregisters a shard owned by this silo. /// /// The shard to unregister. /// Cancellation token. /// A task representing the asynchronous operation. public abstract Task UnregisterShardAsync(IJobShard shard, CancellationToken cancellationToken); } internal class InMemoryJobShardManager : JobShardManager { // Shared storage across all manager instances to support multi-silo scenarios private static readonly Dictionary _globalShardStore = new(); private static readonly SemaphoreSlim _asyncLock = new(1, 1); private readonly IClusterMembershipService? _membershipService; private readonly int _maxAdoptedCount; public InMemoryJobShardManager(SiloAddress siloAddress) : this(siloAddress, null, 3) { } public InMemoryJobShardManager(SiloAddress siloAddress, IClusterMembershipService? membershipService) : this(siloAddress, membershipService, 3) { } public InMemoryJobShardManager(SiloAddress siloAddress, IClusterMembershipService? membershipService, int maxAdoptedCount) : base(siloAddress) { _membershipService = membershipService; _maxAdoptedCount = maxAdoptedCount; } /// /// Clears all shards from the global store. For testing purposes only. /// internal static async Task ClearAllShardsAsync() { await _asyncLock.WaitAsync(); try { _globalShardStore.Clear(); } finally { _asyncLock.Release(); } } /// /// Gets ownership info for a shard. For testing purposes only. /// internal static async Task<(string? Owner, int AdoptedCount)?> GetOwnershipInfoAsync(string shardId) { await _asyncLock.WaitAsync(); try { if (_globalShardStore.TryGetValue(shardId, out var ownership)) { return (ownership.OwnerSiloAddress, ownership.AdoptedCount); } return null; } finally { _asyncLock.Release(); } } public override async Task> AssignJobShardsAsync(DateTimeOffset maxDueTime, CancellationToken cancellationToken) { var alreadyOwnedShards = new List(); var adoptedShards = new List(); await _asyncLock.WaitAsync(cancellationToken); try { var snapshot = _membershipService?.CurrentSnapshot; var deadSilos = new HashSet(); if (snapshot is not null) { foreach (var member in snapshot.Members.Values) { if (member.Status == SiloStatus.Dead) { deadSilos.Add(member.SiloAddress.ToString()); } } } // Assign shards from dead silos or orphaned shards foreach (var kvp in _globalShardStore) { var shardId = kvp.Key; var ownership = kvp.Value; // Skip shards that are already owned by this silo if (ownership.OwnerSiloAddress == SiloAddress.ToString()) { if (ownership.Shard.StartTime <= maxDueTime) { alreadyOwnedShards.Add(ownership.Shard); } continue; } // Check if this is an orphaned shard (gracefully released) or adopted (from dead silo) var isOrphaned = ownership.OwnerSiloAddress is null; var ownerAddress = ownership.OwnerSiloAddress; var isFromDeadSilo = ownerAddress is not null && deadSilos.Contains(ownerAddress); if (isOrphaned || isFromDeadSilo) { if (ownership.Shard.StartTime <= maxDueTime) { // If adopted from dead silo, increment adopted count if (isFromDeadSilo) { ownership.AdoptedCount++; // Check if shard is poisoned if (ownership.AdoptedCount > _maxAdoptedCount) { // Shard is poisoned - don't assign it continue; } } ownership.OwnerSiloAddress = SiloAddress.ToString(); adoptedShards.Add(ownership.Shard); } } } } finally { _asyncLock.Release(); } foreach (var shard in adoptedShards) { // Mark adopted shards as complete await shard.MarkAsCompleteAsync(CancellationToken.None); } return [.. alreadyOwnedShards, .. adoptedShards]; } public override async Task CreateShardAsync(DateTimeOffset minDueTime, DateTimeOffset maxDueTime, IDictionary metadata, CancellationToken cancellationToken) { await _asyncLock.WaitAsync(cancellationToken); try { var shardId = $"{SiloAddress}-{Guid.NewGuid()}"; var newShard = new InMemoryJobShard(shardId, minDueTime, maxDueTime, metadata); _globalShardStore[shardId] = new ShardOwnership { Shard = newShard, OwnerSiloAddress = SiloAddress.ToString() }; return newShard; } finally { _asyncLock.Release(); } } public override async Task UnregisterShardAsync(IJobShard shard, CancellationToken cancellationToken) { var jobCount = await shard.GetJobCountAsync(); await _asyncLock.WaitAsync(cancellationToken); try { // Only remove shards that have no jobs remaining if (_globalShardStore.TryGetValue(shard.Id, out var ownership)) { if (jobCount == 0) { _globalShardStore.Remove(shard.Id); } else { // Mark as unowned so another silo can pick it up ownership.OwnerSiloAddress = null; // Reset adopted count since we're gracefully releasing (not crashing) ownership.AdoptedCount = 0; } } } finally { _asyncLock.Release(); } } private sealed class ShardOwnership { public required IJobShard Shard { get; init; } public string? OwnerSiloAddress { get; set; } public int AdoptedCount { get; set; } } } ================================================ FILE: src/Orleans.DurableJobs/LocalDurableJobManager.Log.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.DurableJobs; internal partial class LocalDurableJobManager { [LoggerMessage( Level = LogLevel.Debug, Message = "Scheduling job '{JobName}' for grain {TargetGrain} at {DueTime}" )] private static partial void LogSchedulingJob(ILogger logger, string jobName, GrainId targetGrain, DateTimeOffset dueTime); [LoggerMessage( Level = LogLevel.Debug, Message = "Job '{JobName}' (ID: {JobId}) scheduled to shard {ShardId} for grain {TargetGrain}" )] private static partial void LogJobScheduled(ILogger logger, string jobName, string jobId, string shardId, GrainId targetGrain); [LoggerMessage( Level = LogLevel.Information, Message = "LocalDurableJobManager starting" )] private static partial void LogStarting(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "LocalDurableJobManager started" )] private static partial void LogStarted(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "LocalDurableJobManager stopping. Running shards: {RunningShardCount}" )] private static partial void LogStopping(ILogger logger, int runningShardCount); [LoggerMessage( Level = LogLevel.Information, Message = "LocalDurableJobManager stopped" )] private static partial void LogStopped(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Attempting to cancel job {JobId} (Name: '{JobName}') in shard {ShardId}" )] private static partial void LogCancellingJob(ILogger logger, string jobId, string jobName, string shardId); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to cancel job {JobId} (Name: '{JobName}') - shard {ShardId} not found in cache" )] private static partial void LogJobCancellationFailed(ILogger logger, string jobId, string jobName, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Job {JobId} (Name: '{JobName}') cancelled from shard {ShardId}" )] private static partial void LogJobCancelled(ILogger logger, string jobId, string jobName, string shardId); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing cluster membership update" )] private static partial void LogErrorProcessingClusterMembership(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Checking for unassigned shards" )] private static partial void LogCheckingForUnassignedShards(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Assigned {ShardCount} shard(s)" )] private static partial void LogAssignedShards(ILogger logger, int shardCount); [LoggerMessage( Level = LogLevel.Trace, Message = "No unassigned shards found" )] private static partial void LogNoShardsToAssign(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Starting shard {ShardId} (Start: {StartTime}, End: {EndTime})" )] private static partial void LogStartingShard(ILogger logger, string shardId, DateTimeOffset startTime, DateTimeOffset endTime); [LoggerMessage( Level = LogLevel.Debug, Message = "Shard {ShardId} not ready yet. Start time: {StartTime}" )] private static partial void LogShardNotReadyYet(ILogger logger, string shardId, DateTimeOffset startTime); [LoggerMessage( Level = LogLevel.Trace, Message = "Checking for pending shards to start" )] private static partial void LogCheckingPendingShards(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error in periodic shard check" )] private static partial void LogErrorInPeriodicCheck(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "Unregistered shard {ShardId}" )] private static partial void LogUnregisteredShard(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Error, Message = "Error unregistering shard {ShardId}" )] private static partial void LogErrorUnregisteringShard(ILogger logger, Exception exception, string shardId); [LoggerMessage( Level = LogLevel.Error, Message = "Error disposing shard {ShardId}" )] private static partial void LogErrorDisposingShard(ILogger logger, Exception exception, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Creating new shard for key {ShardKey}" )] private static partial void LogCreatingNewShard(ILogger logger, DateTimeOffset shardKey); } ================================================ FILE: src/Orleans.DurableJobs/LocalDurableJobManager.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Hosting; using Orleans.Internal; using Orleans.Runtime; using Orleans.Runtime.Internal; namespace Orleans.DurableJobs; /// internal partial class LocalDurableJobManager : SystemTarget, ILocalDurableJobManager, ILifecycleParticipant { private readonly JobShardManager _shardManager; private readonly ShardExecutor _shardExecutor; private readonly IAsyncEnumerable _clusterMembershipUpdates; private readonly ILogger _logger; private readonly DurableJobsOptions _options; private readonly CancellationTokenSource _cts = new(); private Task? _listenForClusterChangesTask; private Task? _periodicCheckTask; // Shard tracking state private readonly ConcurrentDictionary _shardCache = new(); private readonly ConcurrentDictionary _writeableShards = new(); private readonly ConcurrentDictionary _runningShards = new(); private readonly SemaphoreSlim _shardCreationLock = new(1, 1); private readonly SemaphoreSlim _shardCheckSignal = new(0); private static readonly IDictionary EmptyMetadata = new Dictionary(); public LocalDurableJobManager( JobShardManager shardManager, ShardExecutor shardExecutor, IClusterMembershipService clusterMembership, IOptions options, SystemTargetShared shared, ILogger logger) : base(SystemTargetGrainId.CreateGrainType("job-manager"), shared) { _shardManager = shardManager; _shardExecutor = shardExecutor; _clusterMembershipUpdates = clusterMembership.MembershipUpdates; _logger = logger; _options = options.Value; } /// public async Task ScheduleJobAsync(ScheduleJobRequest request, CancellationToken cancellationToken) { LogSchedulingJob(_logger, request.JobName, request.Target, request.DueTime); var shardKey = GetShardKey(request.DueTime); while (true) { // Fast path: shard already exists if (_writeableShards.TryGetValue(shardKey, out var existingShard)) { var job = await existingShard.TryScheduleJobAsync(request, cancellationToken); if (job is not null) { LogJobScheduled(_logger, request.JobName, job.Id, existingShard.Id, request.Target); return job; } // Shard is full or no longer writable, remove from writable shards and try again _writeableShards.TryRemove(shardKey, out _); continue; } // Slow path: need to create shard await _shardCreationLock.WaitAsync(cancellationToken); try { // Double-check after acquiring lock if (_writeableShards.TryGetValue(shardKey, out existingShard)) { continue; } // Create new shard using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token); var endTime = shardKey.Add(_options.ShardDuration); var newShard = await _shardManager.CreateShardAsync(shardKey, endTime, EmptyMetadata, linkedCts.Token); LogCreatingNewShard(_logger, shardKey); _writeableShards[shardKey] = newShard; _shardCache.TryAdd(newShard.Id, newShard); TryActivateShard(newShard); } finally { _shardCreationLock.Release(); } } } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(LocalDurableJobManager), ServiceLifecycleStage.Active, ct => Start(ct), ct => Stop(ct)); } private Task Start(CancellationToken ct) { LogStarting(_logger); using (var _ = new ExecutionContextSuppressor()) { _listenForClusterChangesTask = Task.Factory.StartNew( state => ((LocalDurableJobManager)state!).ProcessMembershipUpdates(), this, CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); _listenForClusterChangesTask.Ignore(); _periodicCheckTask = Task.Factory.StartNew( state => ((LocalDurableJobManager)state!).PeriodicShardCheck(), this, CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); _periodicCheckTask.Ignore(); } LogStarted(_logger); return Task.CompletedTask; } private async Task Stop(CancellationToken ct) { LogStopping(_logger, _runningShards.Count); _cts.Cancel(); if (_listenForClusterChangesTask is not null) { await _listenForClusterChangesTask.SuppressThrowing(); } if (_periodicCheckTask is not null) { await _periodicCheckTask.SuppressThrowing(); } await Task.WhenAll(_runningShards.Values.ToArray()); LogStopped(_logger); } /// public async Task TryCancelDurableJobAsync(DurableJob job, CancellationToken cancellationToken) { LogCancellingJob(_logger, job.Id, job.Name, job.ShardId); if (!_shardCache.TryGetValue(job.ShardId, out var shard)) { LogJobCancellationFailed(_logger, job.Id, job.Name, job.ShardId); return false; } using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token); var wasRemoved = await shard.RemoveJobAsync(job.Id, linkedCts.Token); LogJobCancelled(_logger, job.Id, job.Name, job.ShardId); return wasRemoved; } private async Task ProcessMembershipUpdates() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext); var current = new HashSet(); try { await foreach (var membershipSnapshot in _clusterMembershipUpdates.WithCancellation(_cts.Token)) { try { // Get active members var update = new HashSet(membershipSnapshot.Members.Values .Where(member => member.Status == SiloStatus.Active) .Select(member => member.SiloAddress)); // If active list has changed, trigger immediate shard check if (!current.SetEquals(update)) { current = update; _shardCheckSignal.Release(); } } catch (Exception exception) { LogErrorProcessingClusterMembership(_logger, exception); } } } catch (OperationCanceledException) { if (!_cts.Token.IsCancellationRequested) { throw; } } } private async Task PeriodicShardCheck() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext); using var timer = new PeriodicTimer(TimeSpan.FromMinutes(10)); Task timerTask = Task.CompletedTask; while (!_cts.Token.IsCancellationRequested) { try { // Wait for either periodic timer OR signal from membership changes if (timerTask.IsCompleted) { timerTask = timer.WaitForNextTickAsync(_cts.Token).AsTask(); } var signalTask = _shardCheckSignal.WaitAsync(_cts.Token); await Task.WhenAny(timerTask, signalTask); LogCheckingPendingShards(_logger); // Clean up old writable shards that have passed their time window var now = DateTimeOffset.UtcNow; foreach (var key in _writeableShards.Keys.ToArray()) { var shardEndTime = key.Add(_options.ShardDuration); if (shardEndTime < now) { _writeableShards.TryRemove(key, out _); } } // Query ShardManager for assigned shards (source of truth) var shards = await _shardManager.AssignJobShardsAsync(DateTime.UtcNow.AddHours(1), _cts.Token); if (shards.Count > 0) { LogAssignedShards(_logger, shards.Count); foreach (var shard in shards) { _shardCache.TryAdd(shard.Id, shard); if (!_runningShards.ContainsKey(shard.Id)) { TryActivateShard(shard); } } } else { LogNoShardsToAssign(_logger); } } catch (OperationCanceledException) { break; } catch (Exception ex) { LogErrorInPeriodicCheck(_logger, ex); await Task.Delay(TimeSpan.FromSeconds(5), _cts.Token).SuppressThrowing(); } } } private void TryActivateShard(IJobShard shard) { // Only start if not already running if (_runningShards.ContainsKey(shard.Id)) { return; } // Only start if it's time to start (within buffer period) if (!ShouldStartShardNow(shard)) { LogShardNotReadyYet(_logger, shard.Id, shard.StartTime); return; } if (_runningShards.TryAdd(shard.Id, Task.CompletedTask)) { LogStartingShard(_logger, shard.Id, shard.StartTime, shard.EndTime); _runningShards[shard.Id] = RunShardWithCleanupAsync(shard); } } private async Task RunShardWithCleanupAsync(IJobShard shard) { try { await _shardExecutor.RunShardAsync(shard, _cts.Token); // Unregister the shard from the manager try { await _shardManager.UnregisterShardAsync(shard, _cts.Token); LogUnregisteredShard(_logger, shard.Id); } catch (Exception ex) when (ex is not OperationCanceledException) { LogErrorUnregisteringShard(_logger, ex, shard.Id); } } finally { // Clean up tracking and dispose the shard _shardCache.TryRemove(shard.Id, out _); _runningShards.TryRemove(shard.Id, out _); try { await shard.DisposeAsync(); } catch (Exception ex) { LogErrorDisposingShard(_logger, ex, shard.Id); } } } private bool ShouldStartShardNow(IJobShard shard) { var activationTime = shard.StartTime.Subtract(_options.ShardActivationBufferPeriod); return DateTimeOffset.UtcNow >= activationTime; } private DateTimeOffset GetShardKey(DateTimeOffset scheduledTime) { var shardDurationTicks = _options.ShardDuration.Ticks; var epochTicks = scheduledTime.UtcTicks; var bucketTicks = (epochTicks / shardDurationTicks) * shardDurationTicks; return new DateTimeOffset(bucketTicks, TimeSpan.Zero); } } ================================================ FILE: src/Orleans.DurableJobs/Orleans.DurableJobs.csproj ================================================ Microsoft.Orleans.DurableJobs Microsoft Orleans Durable Jobs Library Durable Jobs library for Microsoft Orleans used on the server. README.md $(DefaultTargetFrameworks) true false $(DefineConstants) $(VersionSuffix).alpha.1 alpha.1 enable ================================================ FILE: src/Orleans.DurableJobs/README.md ================================================ # Microsoft Orleans Durable Jobs ## Introduction Microsoft Orleans Durable Jobs provides a distributed, scalable system for scheduling one-time jobs that execute at a specific time. Unlike Orleans Reminders which are designed for recurring tasks, Durable Jobs are ideal for one-time future events such as appointment notifications, delayed processing, scheduled workflow steps, and time-based triggers. **Key Features:** - **At Least One-time Execution**: Jobs are scheduled to run at least once - **Persistent**: Jobs survive grain deactivation and silo restarts - **Distributed**: Jobs are automatically distributed and rebalanced across silos - **Reliable**: Failed jobs can be automatically retried with configurable policies - **Rich Metadata**: Associate custom metadata with each job - **Cancellable**: Jobs can be canceled before execution ## Getting Started ### Installation To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.DurableJobs ``` For production scenarios with persistence, also install a storage provider: ```shell dotnet add package Microsoft.Orleans.DurableJobs.AzureStorage ``` ### Configuration #### Using In-Memory Storage (Development/Testing) ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure in-memory Durable Jobs (no persistence) .UseInMemoryDurableJobs(); }); await builder.Build().RunAsync(); ``` #### Using Azure Storage (Production) ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args); builder.UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Azure Storage Durable Jobs .UseAzureStorageDurableJobs(options => { options.Configure(o => { o.BlobServiceClient = new Azure.Storage.Blobs.BlobServiceClient("YOUR_CONNECTION_STRING"); o.ContainerName = "durable-jobs"; }); }); }); await builder.Build().RunAsync(); ``` #### Advanced Configuration ```csharp builder.UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() .UseInMemoryDurableJobs() .ConfigureServices(services => { services.Configure(options => { // Duration of each job shard (jobs are partitioned by time) options.ShardDuration = TimeSpan.FromMinutes(5); // Maximum number of jobs that can execute concurrently on each silo options.MaxConcurrentJobsPerSilo = 100; // Custom retry policy options.ShouldRetry = (context, exception) => { // Retry up to 3 times with exponential backoff if (context.DequeueCount < 3) { var delay = TimeSpan.FromSeconds(Math.Pow(2, context.DequeueCount)); return DateTimeOffset.UtcNow.Add(delay); } return null; // Don't retry }; }); }); }); ``` ## Usage Examples ### Basic Job Scheduling #### 1. Implement the IDurableJobHandler Interface ```csharp using Orleans; using Orleans.DurableJobs; public interface INotificationGrain : IGrainWithStringKey { Task ScheduleNotification(string message, DateTimeOffset sendTime); Task CancelScheduledNotification(); } public class NotificationGrain : Grain, INotificationGrain, IDurableJobHandler { private readonly ILocalDurableJobManager _jobManager; private readonly ILogger _logger; private IDurableJob? _durableJob; public NotificationGrain( ILocalDurableJobManager jobManager, ILogger logger) { _jobManager = jobManager; _logger = logger; } public async Task ScheduleNotification(string message, DateTimeOffset sendTime) { var userId = this.GetPrimaryKeyString(); var metadata = new Dictionary { ["Message"] = message }; _durableJob = await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "SendNotification", DueTime = sendTime, Metadata = metadata }, CancellationToken.None); _logger.LogInformation( "Scheduled notification for user {UserId} at {SendTime} (JobId: {JobId})", userId, sendTime, _durableJob.Id); } public async Task CancelScheduledNotification() { if (_durableJob is null) { _logger.LogWarning("No scheduled notification to cancel"); return; } var canceled = await _jobManager.TryCancelDurableJobAsync(_durableJob); _logger.LogInformation("Notification {JobId} canceled: {Canceled}", _durableJob.Id, canceled); if (canceled) { _durableJob = null; } } // This method is called when the durable job executes public Task ExecuteJobAsync(IJobRunContext context, CancellationToken cancellationToken) { var userId = this.GetPrimaryKeyString(); var message = context.Job.Metadata?["Message"]; _logger.LogInformation( "Sending notification to user {UserId}: {Message} (Job: {JobId}, Run: {RunId}, Attempt: {DequeueCount})", userId, message, context.Job.Id, context.RunId, context.DequeueCount); // Send the notification here // If this throws an exception, the job can be retried based on your retry policy _durableJob = null; return Task.CompletedTask; } } ``` #### 2. Order Workflow with Multiple Jobs ```csharp public interface IOrderGrain : IGrainWithGuidKey { Task PlaceOrder(OrderDetails details); Task CancelOrder(); } public class OrderGrain : Grain, IOrderGrain, IDurableJobHandler { private readonly ILocalDurableJobManager _jobManager; private readonly IOrderService _orderService; private readonly IGrainFactory _grainFactory; private readonly ILogger _logger; public OrderGrain( ILocalDurableJobManager jobManager, IOrderService orderService, IGrainFactory grainFactory, ILogger logger) { _jobManager = jobManager; _orderService = orderService; _grainFactory = grainFactory; _logger = logger; } public async Task PlaceOrder(OrderDetails details) { var orderId = this.GetPrimaryKey(); // Create the order await _orderService.CreateOrderAsync(orderId, details); // Schedule delivery reminder for 24 hours before delivery var reminderTime = details.DeliveryDate.AddHours(-24); await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "DeliveryReminder", DueTime = reminderTime, Metadata = new Dictionary { ["Step"] = "DeliveryReminder", ["CustomerId"] = details.CustomerId, ["OrderNumber"] = details.OrderNumber } }, CancellationToken.None); // Schedule order expiration if payment not received var expirationTime = DateTimeOffset.UtcNow.AddHours(24); await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = "OrderExpiration", DueTime = expirationTime, Metadata = new Dictionary { ["Step"] = "OrderExpiration" } }, CancellationToken.None); } public async Task CancelOrder() { var orderId = this.GetPrimaryKey(); await _orderService.CancelOrderAsync(orderId); } public async Task ExecuteJobAsync(IJobRunContext context, CancellationToken cancellationToken) { var step = context.Job.Metadata!["Step"]; var orderId = this.GetPrimaryKey(); switch (step) { case "DeliveryReminder": await HandleDeliveryReminder(context, cancellationToken); break; case "OrderExpiration": await HandleOrderExpiration(cancellationToken); break; } } private async Task HandleDeliveryReminder(IJobRunContext context, CancellationToken ct) { var customerId = context.Job.Metadata!["CustomerId"]; var orderNumber = context.Job.Metadata["OrderNumber"]; var notificationGrain = _grainFactory.GetGrain(customerId); await notificationGrain.ScheduleNotification( $"Your order #{orderNumber} will be delivered tomorrow!", DateTimeOffset.UtcNow); } private async Task HandleOrderExpiration(CancellationToken ct) { var orderId = this.GetPrimaryKey(); var order = await _orderService.GetOrderAsync(orderId, ct); if (order?.Status == OrderStatus.Pending) { await _orderService.CancelOrderAsync(orderId, ct); _logger.LogInformation("Order {OrderId} expired and canceled", orderId); } } } ``` ### Advanced Scenarios #### Job with Retry Logic ```csharp public class PaymentProcessorGrain : Grain, IDurableJobHandler { private readonly IPaymentService _paymentService; private readonly ILogger _logger; public Task ExecuteJobAsync(IJobRunContext context, CancellationToken cancellationToken) { var paymentId = context.Job.Metadata?["PaymentId"]; _logger.LogInformation( "Processing payment {PaymentId} (Attempt {Attempt})", paymentId, context.DequeueCount); try { await _paymentService.ProcessPaymentAsync(paymentId, cancellationToken); return Task.CompletedTask; } catch (TransientException ex) { _logger.LogWarning(ex, "Payment processing failed with transient error, will retry"); throw; // Let the retry policy handle it } catch (Exception ex) { _logger.LogError(ex, "Payment processing failed with permanent error"); throw; // This will not be retried if the retry policy returns null } } } ``` #### Tracking Job Completion ```csharp public class WorkflowGrain : Grain, IDurableJobHandler { private readonly Dictionary _pendingJobs = new(); public async Task ScheduleWorkflowStep(string stepName, DateTimeOffset executeAt) { var job = await _jobManager.ScheduleJobAsync( new ScheduleJobRequest { Target = this.GetGrainId(), JobName = stepName, DueTime = executeAt, Metadata = null }, CancellationToken.None); _pendingJobs[job.Id] = new TaskCompletionSource(); return job; } public async Task WaitForJobCompletion(string jobId, TimeSpan timeout) { if (_pendingJobs.TryGetValue(jobId, out var tcs)) { using var cts = new CancellationTokenSource(timeout); await tcs.Task.WaitAsync(cts.Token); } } public Task ExecuteJobAsync(IDurableJobContext context, CancellationToken cancellationToken) { // Execute the workflow step... // Mark as complete if (_pendingJobs.TryRemove(context.Job.Id, out var tcs)) { tcs.SetResult(); } return Task.CompletedTask; } } ``` ## How It Works ### Architecture Overview 1. **Job Sharding**: Jobs are partitioned into time-based shards (default: 1-minute windows) 2. **Shard Ownership**: Each shard is owned by a single silo for execution 3. **Automatic Rebalancing**: When a silo fails, its shards are automatically reassigned to healthy silos 4. **Ordered Execution**: Within a shard, jobs are processed in order of their due time 5. **Concurrency Control**: The `MaxConcurrentJobsPerSilo` setting limits concurrent job execution ### Job Lifecycle ``` ┌─────────────┐ │ Scheduled │ ──▶ Job is created and added to appropriate shard └─────────────┘ │ ▼ ┌─────────────┐ │ Waiting │ ──▶ Job waits in queue until due time └─────────────┘ │ ▼ ┌─────────────┐ │ Executing │ ──▶ Job handler is invoked on target grain └─────────────┘ │ ├──▶ Success ──▶ Job is removed │ └──▶ Failure ──▶ Retry policy decides: • Retry: Job is re-queued with new due time • No Retry: Job is removed ``` ## Configuration Reference ### DurableJobsOptions | Property | Type | Default | Description | |----------|------|---------|-------------| | `ShardDuration` | `TimeSpan` | 1 minute | Duration of each job shard. Smaller values reduce latency but increase overhead. | | `MaxConcurrentJobsPerSilo` | `int` | 100 | Maximum number of jobs that can execute simultaneously on a silo. | | `ShouldRetry` | `Func` | 3 retries with exp. backoff | Determines if a failed job should be retried. Return the new due time or `null` to not retry. | ## Best Practices 1. **Set Reasonable Concurrency Limits**: Prevent resource exhaustion ```csharp options.MaxConcurrentJobsPerSilo = 100; // Adjust based on your workload ``` 2. **Implement Idempotent Job Handlers**: Jobs may be retried, ensure handlers are idempotent ```csharp public async Task ExecuteJobAsync(IDurableJobContext context, CancellationToken ct) { var jobId = context.Job.Id; // Check if already processed if (await _state.IsProcessed(jobId)) return; // Process job... await _state.MarkProcessed(jobId); } ``` 3. **Use Metadata Wisely**: Keep metadata lightweight ```csharp // Good: Store IDs var metadata = new Dictionary { ["OrderId"] = "12345" }; // Bad: Store large objects var metadata = new Dictionary { ["Order"] = JsonSerializer.Serialize(largeOrder) }; ``` 4. **Handle Cancellation**: Respect the cancellation token ```csharp public async Task ExecuteJobAsync(IDurableJobContext context, CancellationToken ct) { await SomeLongRunningOperation(ct); } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Timers and Reminders](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.DurableJobs/ScheduleJobRequest.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.DurableJobs; /// /// Represents a request to schedule a durable job. /// public readonly struct ScheduleJobRequest { /// /// Gets the grain identifier of the target grain that will receive the durable job. /// public required GrainId Target { get; init; } /// /// Gets the name of the job for identification purposes. /// public required string JobName { get; init; } /// /// Gets the date and time when the job should be executed. /// public required DateTimeOffset DueTime { get; init; } /// /// Gets optional metadata associated with the job. /// public IReadOnlyDictionary? Metadata { get; init; } } ================================================ FILE: src/Orleans.DurableJobs/ShardExecutor.Log.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.DurableJobs; internal sealed partial class ShardExecutor { [LoggerMessage( Level = LogLevel.Debug, Message = "Waiting {Delay} for shard {ShardId} start time {StartTime}" )] private static partial void LogWaitingForShardStartTime(ILogger logger, string shardId, TimeSpan delay, DateTimeOffset startTime); [LoggerMessage( Level = LogLevel.Information, Message = "Begin processing shard {ShardId}" )] private static partial void LogBeginProcessingShard(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Executing job {JobId} (Name: '{JobName}') for grain {TargetGrain}, due at {DueTime}" )] private static partial void LogExecutingJob(ILogger logger, string jobId, string jobName, GrainId targetGrain, DateTimeOffset dueTime); [LoggerMessage( Level = LogLevel.Debug, Message = "Job {JobId} (Name: '{JobName}') executed successfully" )] private static partial void LogJobExecutedSuccessfully(ILogger logger, string jobId, string jobName); [LoggerMessage( Level = LogLevel.Error, Message = "Error executing job {JobId}" )] private static partial void LogErrorExecutingJob(ILogger logger, Exception exception, string jobId); [LoggerMessage( Level = LogLevel.Warning, Message = "Retrying job {JobId} (Name: '{JobName}') at {RetryTime}. Dequeue count: {DequeueCount}" )] private static partial void LogRetryingJob(ILogger logger, string jobId, string jobName, DateTimeOffset retryTime, int dequeueCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Polling job {JobId} (Name: '{JobName}') - will check status again in {PollDelay}" )] private static partial void LogPollingJob(ILogger logger, string jobId, string jobName, TimeSpan pollDelay); [LoggerMessage( Level = LogLevel.Warning, Message = "Job {JobId} (Name: '{JobName}') returned Failed status" )] private static partial void LogJobFailedWithResult(ILogger logger, string jobId, string jobName); [LoggerMessage( Level = LogLevel.Error, Message = "Job {JobId} (Name: '{JobName}') failed after {DequeueCount} attempts and will not be retried" )] private static partial void LogJobFailedNoRetry(ILogger logger, string jobId, string jobName, int dequeueCount); [LoggerMessage( Level = LogLevel.Information, Message = "Completed processing shard {ShardId}" )] private static partial void LogCompletedProcessingShard(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Debug, Message = "Shard {ShardId} processing cancelled" )] private static partial void LogShardCancelled(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Warning, Message = "Overload detected for shard {ShardId}, pausing job processing" )] private static partial void LogOverloadDetected(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Overload cleared for shard {ShardId}, resuming job processing" )] private static partial void LogOverloadCleared(ILogger logger, string shardId); [LoggerMessage( Level = LogLevel.Information, Message = "Slow start initiated: initial concurrency {InitialConcurrency}, target {TargetConcurrency}, interval {Interval}" )] private static partial void LogSlowStartBegin(ILogger logger, int initialConcurrency, int targetConcurrency, TimeSpan interval); [LoggerMessage( Level = LogLevel.Debug, Message = "Slow start: concurrency increased to {CurrentConcurrency} (target: {TargetConcurrency})" )] private static partial void LogSlowStartConcurrencyIncreased(ILogger logger, int currentConcurrency, int targetConcurrency); [LoggerMessage( Level = LogLevel.Information, Message = "Slow start complete: concurrency reached target {TargetConcurrency}" )] private static partial void LogSlowStartComplete(ILogger logger, int targetConcurrency); [LoggerMessage( Level = LogLevel.Error, Message = "Slow start ramp-up failed; all remaining concurrency has been released" )] private static partial void LogSlowStartError(ILogger logger, Exception exception); } ================================================ FILE: src/Orleans.DurableJobs/ShardExecutor.cs ================================================ using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Hosting; using Orleans.Runtime; using Orleans.Runtime.Messaging; namespace Orleans.DurableJobs; /// /// Handles the execution of job shards and individual durable jobs. /// internal sealed partial class ShardExecutor { private readonly IInternalGrainFactory _grainFactory; private readonly ILogger _logger; private readonly DurableJobsOptions _options; private readonly SemaphoreSlim _jobConcurrencyLimiter; private readonly IOverloadDetector _overloadDetector; private int _currentCapacity; private int _slowStartRampUpStarted; /// /// Initializes a new instance of the class. /// /// The grain factory for creating grain references. /// The durable jobs configuration options. /// The overload detector for throttling job execution. /// The logger instance. public ShardExecutor( IInternalGrainFactory grainFactory, IOptions options, IOverloadDetector overloadDetector, ILogger logger) { _grainFactory = grainFactory; _logger = logger; _options = options.Value; _overloadDetector = overloadDetector; _currentCapacity = _options.ConcurrencySlowStartEnabled && _options.SlowStartInitialConcurrency < _options.MaxConcurrentJobsPerSilo ? _options.SlowStartInitialConcurrency : _options.MaxConcurrentJobsPerSilo; _jobConcurrencyLimiter = new SemaphoreSlim(_currentCapacity); } /// /// Runs a shard, processing all jobs within it until completion or cancellation. /// /// The shard to execute. /// Cancellation token to stop processing. /// A task representing the asynchronous operation. public async Task RunShardAsync(IJobShard shard, CancellationToken cancellationToken) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext); if (Volatile.Read(ref _currentCapacity) < _options.MaxConcurrentJobsPerSilo && Interlocked.CompareExchange(ref _slowStartRampUpStarted, 1, 0) == 0) { _ = Task.Run(SlowStartRampUpAsync); } var tasks = new ConcurrentDictionary(); try { if (shard.StartTime > DateTime.UtcNow) { // Wait until the shard's start time var delay = shard.StartTime - DateTimeOffset.UtcNow; LogWaitingForShardStartTime(_logger, shard.Id, delay, shard.StartTime); await Task.Delay(delay, cancellationToken); } LogBeginProcessingShard(_logger, shard.Id); // Process all jobs in the shard await foreach (var jobContext in shard.ConsumeDurableJobsAsync().WithCancellation(cancellationToken)) { // Check for overload and pause batch processing if needed if (_overloadDetector.IsOverloaded) { LogOverloadDetected(_logger, shard.Id); while (_overloadDetector.IsOverloaded) { await Task.Delay(_options.OverloadBackoffDelay, cancellationToken); } LogOverloadCleared(_logger, shard.Id); } // Wait for concurrency slot await _jobConcurrencyLimiter.WaitAsync(cancellationToken); // Start processing the job. RunJobAsync will release the semaphore when done and remove itself from the tasks dictionary tasks[jobContext.Job.Id] = RunJobAsync(jobContext, shard, tasks, cancellationToken); } LogCompletedProcessingShard(_logger, shard.Id); } catch (OperationCanceledException) { LogShardCancelled(_logger, shard.Id); throw; } finally { // Wait for all jobs to complete await Task.WhenAll(tasks.Values); } } private async Task SlowStartRampUpAsync() { var targetCapacity = _options.MaxConcurrentJobsPerSilo; LogSlowStartBegin(_logger, Volatile.Read(ref _currentCapacity), targetCapacity, _options.SlowStartInterval); try { while (Volatile.Read(ref _currentCapacity) < targetCapacity) { await Task.Delay(_options.SlowStartInterval); while (true) { var currentCapacity = Volatile.Read(ref _currentCapacity); if (currentCapacity >= targetCapacity) { break; } var newCapacity = (int)Math.Min((long)currentCapacity * 2, targetCapacity); var toRelease = newCapacity - currentCapacity; if (toRelease <= 0) { break; } if (Interlocked.CompareExchange(ref _currentCapacity, newCapacity, currentCapacity) == currentCapacity) { _jobConcurrencyLimiter.Release(toRelease); LogSlowStartConcurrencyIncreased(_logger, newCapacity, targetCapacity); break; } } } } catch (Exception ex) { // If the ramp-up fails for any reason, release all remaining capacity to avoid being stuck at low concurrency. var currentCapacity = Volatile.Read(ref _currentCapacity); var remaining = targetCapacity - currentCapacity; if (remaining > 0) { _jobConcurrencyLimiter.Release(remaining); Interlocked.Exchange(ref _currentCapacity, targetCapacity); } LogSlowStartError(_logger, ex); return; } LogSlowStartComplete(_logger, Volatile.Read(ref _currentCapacity)); } private async Task RunJobAsync( IJobRunContext jobContext, IJobShard shard, ConcurrentDictionary runningTasks, CancellationToken cancellationToken) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding); Exception? failureException = null; try { try { LogExecutingJob(_logger, jobContext.Job.Id, jobContext.Job.Name, jobContext.Job.TargetGrainId, jobContext.Job.DueTime); var target = _grainFactory.GetGrain(jobContext.Job.TargetGrainId); var result = await target.HandleDurableJobAsync(jobContext, cancellationToken); // Handle the result based on status while (result.IsPending) { // Enter polling loop LogPollingJob(_logger, jobContext.Job.Id, jobContext.Job.Name, result.PollAfterDelay.Value); await Task.Delay(result.PollAfterDelay.Value, cancellationToken); result = await target.HandleDurableJobAsync(jobContext, cancellationToken); } if (result.Status == DurableJobRunStatus.Completed) { await shard.RemoveJobAsync(jobContext.Job.Id, cancellationToken); LogJobExecutedSuccessfully(_logger, jobContext.Job.Id, jobContext.Job.Name); } else if (result.IsFailed) { // Handle failed result through retry policy LogJobFailedWithResult(_logger, jobContext.Job.Id, jobContext.Job.Name); failureException = result.Exception; } } catch (Exception ex) when (ex is not OperationCanceledException) { LogErrorExecutingJob(_logger, ex, jobContext.Job.Id); failureException = ex; } // Cancellation is handled by shard takeover, so only non-cancellation failures are retried here. if (failureException is not null) { var retryTime = _options.ShouldRetry(jobContext, failureException); if (retryTime is not null) { LogRetryingJob(_logger, jobContext.Job.Id, jobContext.Job.Name, retryTime.Value, jobContext.DequeueCount); await shard.RetryJobLaterAsync(jobContext, retryTime.Value, cancellationToken); } else { LogJobFailedNoRetry(_logger, jobContext.Job.Id, jobContext.Job.Name, jobContext.DequeueCount); } } } finally { // Cleanup must happen even when retry persistence throws, otherwise slots leak and shard processing can stall. _jobConcurrencyLimiter.Release(); runningTasks.TryRemove(jobContext.Job.Id, out _); } } } ================================================ FILE: src/Orleans.EventSourcing/Common/ConnectionIssues.cs ================================================ using System; namespace Orleans.EventSourcing.Common { /// /// Describes a connection issue that occurred when communicating with primary storage. /// [Serializable] [GenerateSerializer] public abstract class PrimaryOperationFailed : ConnectionIssue { /// /// The exception that was caught when communicating with the primary. /// [Id(0)] public Exception Exception { get; set; } /// public override TimeSpan ComputeRetryDelay(TimeSpan? previous) { // after first fail do not backoff yet... keep it at zero if (previous == null) { return TimeSpan.Zero; } var backoff = previous.Value.TotalMilliseconds; // grows exponentially up to slowpoll interval if (previous.Value.TotalMilliseconds < slowpollinterval) backoff = (int)((backoff + Random.Shared.Next(5, 15)) * 1.5); // during slowpoll, slightly randomize if (backoff > slowpollinterval) backoff = slowpollinterval + Random.Shared.Next(1, 200); return TimeSpan.FromMilliseconds(backoff); } private const int slowpollinterval = 10000; } } ================================================ FILE: src/Orleans.EventSourcing/Common/NotificationMessage.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans.EventSourcing.Common { /// /// Base class for notification messages that are sent by log view adaptors to other /// clusters, after updating the log. All subclasses must be serializable. /// public interface INotificationMessage : ILogConsistencyProtocolMessage { ///The version number. int Version { get; } // a log-consistency provider can subclass this to add more information // for example, the log entries that were appended, or the view } /// A simple notification message containing only the version. [Serializable] [GenerateSerializer] public sealed class VersionNotificationMessage : INotificationMessage { /// [Id(0)] public int Version { get; set; } } /// A notification message containing a batch of notification messages. [Serializable] [GenerateSerializer] public sealed class BatchedNotificationMessage : INotificationMessage { /// The notification messages contained in this batch. [Id(0)] public List Notifications { get; set; } /// The version number - for a batch, this is the maximum version contained. public int Version { get { return Notifications.Aggregate(0, (v, m) => Math.Max(v, m.Version)); } } } } ================================================ FILE: src/Orleans.EventSourcing/Common/PrimaryBasedLogViewAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Orleans.EventSourcing.Common { /// /// A general template for constructing log view adaptors that are based on /// a sequentially read and written primary. We use this to construct /// a variety of different log-consistency providers, all following the same basic pattern /// (read and write latest view from/to primary, and send notifications after writing). /// /// Note that the log itself is transient, i.e. not actually saved to storage - only the latest view and some /// metadata (the log position, and write flags) is stored in the primary. /// It is safe to interleave calls to this adaptor (using grain scheduler only, of course). /// /// /// Subclasses override ReadAsync and WriteAsync to read from / write to primary. /// Calls to the primary are serialized, i.e. never interleave. /// /// /// The user-defined view of the log /// The type of the log entries /// The type of submission entries stored in pending queue public abstract class PrimaryBasedLogViewAdaptor : ILogViewAdaptor where TLogView : class, new() where TLogEntry : class where TSubmissionEntry : SubmissionEntry { /// /// Set confirmed view the initial value (a view of the empty log) /// protected abstract void InitializeConfirmedView(TLogView initialstate); /// /// Read cached global state. /// protected abstract TLogView LastConfirmedView(); /// /// Read version of cached global state. /// protected abstract int GetConfirmedVersion(); /// /// Read the latest primary state. Must block/retry until successful. /// Should not throw exceptions, but record them in /// /// protected abstract Task ReadAsync(); /// /// Apply pending entries to the primary. Must block/retry until successful. /// Should not throw exceptions, but record them in /// protected abstract Task WriteAsync(); /// /// Create a submission entry for the submitted log entry. /// Using a type parameter so we can add protocol-specific info to this class. /// /// protected abstract TSubmissionEntry MakeSubmissionEntry(TLogEntry entry); /// /// Whether this cluster supports submitting updates /// protected virtual bool SupportSubmissions { get { return true; } } /// /// Handle protocol messages. /// protected virtual Task OnMessageReceived(ILogConsistencyProtocolMessage payload) { // subclasses that define custom protocol messages must override this throw new NotImplementedException(); } public virtual Task> RetrieveLogSegment(int fromVersion, int length) { throw new NotSupportedException(); } /// /// Clear the persisted log stream completely. /// protected virtual Task ClearPrimaryLogAsync(CancellationToken cancellationToken) { throw new NotSupportedException(); } /// public virtual Task ClearLogAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (clearLogRequest is null || clearLogRequest.Task.IsCompleted) { clearLogCancellationToken = cancellationToken; clearLogRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } worker.Notify(); var clearLogTask = clearLogRequest.Task; return cancellationToken.CanBeCanceled ? clearLogTask.WaitAsync(cancellationToken) : clearLogTask; } /// /// Handle notification messages. Override this to handle notification subtypes. /// protected virtual void OnNotificationReceived(INotificationMessage payload) { var msg = payload as VersionNotificationMessage; if (msg != null) { if (msg.Version > lastVersionNotified) lastVersionNotified = msg.Version; return; } var batchmsg = payload as BatchedNotificationMessage; if (batchmsg != null) { foreach (var bm in batchmsg.Notifications) OnNotificationReceived(bm); return; } // subclass should have handled this in override throw new ProtocolTransportException(string.Format("message type {0} not handled by OnNotificationReceived", payload.GetType().FullName)); } /// /// The last version we have been notified of /// private int lastVersionNotified; /// /// Process stored notifications during worker cycle. Override to handle notification subtypes. /// protected virtual void ProcessNotifications() { if (lastVersionNotified > this.GetConfirmedVersion()) { Services.Log(LogLevel.Debug, "force refresh because of version notification v{0}", lastVersionNotified); needRefresh = true; } } /// /// Merge two notification messages, for batching. Override to handle notification subtypes. /// protected virtual INotificationMessage Merge(INotificationMessage earliermessage, INotificationMessage latermessage) { return new VersionNotificationMessage() { Version = latermessage.Version }; } /// /// The grain that is using this adaptor. /// protected ILogViewAdaptorHost Host { get; private set; } /// /// The runtime services required for implementing notifications between grain instances in different cluster. /// protected ILogConsistencyProtocolServices Services { get; private set; } /// /// Construct an instance, for the given parameters. /// protected PrimaryBasedLogViewAdaptor( ILogViewAdaptorHost host, TLogView initialstate, ILogConsistencyProtocolServices services) { Debug.Assert(host != null && services != null && initialstate != null); this.Host = host; this.Services = services; this.InitialState = Services.DeepCopy(initialstate); InitializeConfirmedView(initialstate); worker = new BatchWorkerFromDelegate(Work); } /// public virtual Task PreOnActivate() { Services.Log(LogLevel.Trace, "PreActivation Started"); // this flag indicates we have not done an initial load from storage yet // we do not act on this yet, but wait until after user OnActivate has run. needInitialRead = true; Services.Log(LogLevel.Trace, "PreActivation Complete"); return Task.CompletedTask; } public virtual Task PostOnActivate() { Services.Log(LogLevel.Trace, "PostActivation Started"); // start worker, if it has not already happened if (needInitialRead) worker.Notify(); Services.Log(LogLevel.Trace, "PostActivation Complete"); return Task.CompletedTask; } /// public virtual async Task PostOnDeactivate() { Services.Log(LogLevel.Trace, "Deactivation Started"); while (!worker.IsIdle()) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding); await worker.WaitForCurrentWorkToBeServiced(); } Services.Log(LogLevel.Trace, "Deactivation Complete"); } // the currently submitted, unconfirmed entries. private readonly List pending = new List(); /// called at beginning of WriteAsync to the current tentative state protected TLogView CopyTentativeState() { var state = TentativeView; tentativeStateInternal = null; // to avoid aliasing return state; } /// called at beginning of WriteAsync to the current batch of updates protected TSubmissionEntry[] GetCurrentBatchOfUpdates() { return pending.ToArray(); // must use a copy } /// called at beginning of WriteAsync to get current number of pending updates protected int GetNumberPendingUpdates() { return pending.Count; } /// /// Tentative State. Represents Stable State + effects of pending updates. /// Computed lazily (null if not in use) /// private TLogView tentativeStateInternal; /// /// A flag that indicates to the worker that the client wants to refresh the state /// private bool needRefresh; /// /// A flag that indicates that we have not read global state at all yet, and should do so /// private bool needInitialRead; /// /// A pending clear-log request to be processed by the worker. /// private TaskCompletionSource clearLogRequest; private CancellationToken clearLogCancellationToken; /// /// Background worker which asynchronously sends operations to the leader /// private readonly BatchWorker worker; /// /// Cached version of initial state used during initialization. And for resetting. /// protected TLogView InitialState { init; get => Services.DeepCopy(field); } /// statistics gathering. Is null unless stats collection is turned on. protected LogConsistencyStatistics stats = null; /// For use by protocols. Determines if this cluster is part of the configured multicluster. protected bool IsMyClusterJoined() { return true; } /// /// Block until this cluster is joined to the multicluster. /// protected async Task EnsureClusterJoinedAsync() { while (!IsMyClusterJoined()) { Services.Log(LogLevel.Debug, "Waiting for join"); await Task.Delay(5000); } } /// public void Submit(TLogEntry logEntry) { if (!SupportSubmissions) throw new InvalidOperationException("provider does not support submissions on cluster " + Services.MyClusterId); if (stats != null) stats.EventCounters["SubmitCalled"]++; Services.Log(LogLevel.Trace, "Submit"); SubmitInternal(DateTime.UtcNow, logEntry); worker.Notify(); } /// public void SubmitRange(IEnumerable logEntries) { if (!SupportSubmissions) throw new InvalidOperationException("Provider does not support submissions on cluster " + Services.MyClusterId); if (stats != null) stats.EventCounters["SubmitRangeCalled"]++; Services.Log(LogLevel.Trace, "SubmitRange"); var time = DateTime.UtcNow; foreach (var e in logEntries) SubmitInternal(time, e); worker.Notify(); } /// public Task TryAppend(TLogEntry logEntry) { if (!SupportSubmissions) throw new InvalidOperationException("Provider does not support submissions on cluster " + Services.MyClusterId); if (stats != null) stats.EventCounters["TryAppendCalled"]++; Services.Log(LogLevel.Trace, "TryAppend"); var promise = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); SubmitInternal(DateTime.UtcNow, logEntry, GetConfirmedVersion() + pending.Count, promise); worker.Notify(); return promise.Task; } /// public Task TryAppendRange(IEnumerable logEntries) { if (!SupportSubmissions) throw new InvalidOperationException("Provider does not support submissions on cluster " + Services.MyClusterId); if (stats != null) stats.EventCounters["TryAppendRangeCalled"]++; Services.Log(LogLevel.Trace, "TryAppendRange"); var promise = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var time = DateTime.UtcNow; var pos = GetConfirmedVersion() + pending.Count; bool first = true; foreach (var e in logEntries) { SubmitInternal(time, e, pos++, first ? promise : null); first = false; } worker.Notify(); return promise.Task; } private const int unconditional = -1; private void SubmitInternal(DateTime time, TLogEntry logentry, int conditionalPosition = unconditional, TaskCompletionSource resultPromise = null) { // create a submission entry var submissionentry = this.MakeSubmissionEntry(logentry); submissionentry.SubmissionTime = time; submissionentry.ResultPromise = resultPromise; submissionentry.ConditionalPosition = conditionalPosition; // add submission to queue pending.Add(submissionentry); // if we have a tentative state in use, update it if (this.tentativeStateInternal != null) { try { Host.UpdateView(this.tentativeStateInternal, logentry); } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(SubmitInternal), e); } } try { Host.OnViewChanged(true, false); } catch (Exception e) { Services.CaughtUserCodeException("OnViewChanged", nameof(SubmitInternal), e); } } /// public TLogView TentativeView { get { if (stats != null) stats.EventCounters["TentativeViewCalled"]++; if (tentativeStateInternal == null) CalculateTentativeState(); return tentativeStateInternal; } } /// public TLogView ConfirmedView { get { if (stats != null) stats.EventCounters["ConfirmedViewCalled"]++; return LastConfirmedView(); } } /// public int ConfirmedVersion { get { if (stats != null) stats.EventCounters["ConfirmedVersionCalled"]++; return GetConfirmedVersion(); } } /// /// Called from network /// /// /// public async Task OnProtocolMessageReceived(ILogConsistencyProtocolMessage payLoad) { var notificationMessage = payLoad as INotificationMessage; if (notificationMessage != null) { Services.Log(LogLevel.Debug, "NotificationReceived v{0}", notificationMessage.Version); OnNotificationReceived(notificationMessage); // poke worker so it will process the notifications worker.Notify(); return null; } else { //it's a protocol message return await OnMessageReceived(payLoad); } } /// /// method is virtual so subclasses can add their own events /// public virtual void EnableStatsCollection() { stats = new LogConsistencyStatistics() { EventCounters = new Dictionary(), StabilizationLatenciesInMsecs = new List() }; stats.EventCounters.Add("TentativeViewCalled", 0); stats.EventCounters.Add("ConfirmedViewCalled", 0); stats.EventCounters.Add("ConfirmedVersionCalled", 0); stats.EventCounters.Add("SubmitCalled", 0); stats.EventCounters.Add("SubmitRangeCalled", 0); stats.EventCounters.Add("TryAppendCalled", 0); stats.EventCounters.Add("TryAppendRangeCalled", 0); stats.EventCounters.Add("ConfirmSubmittedEntriesCalled", 0); stats.EventCounters.Add("SynchronizeNowCalled", 0); stats.EventCounters.Add("WritebackEvents", 0); stats.StabilizationLatenciesInMsecs = new List(); } /// /// Disable stats collection /// public void DisableStatsCollection() { stats = null; } /// /// Get states /// /// public LogConsistencyStatistics GetStats() { return stats; } private void CalculateTentativeState() { // copy the confirmed view this.tentativeStateInternal = Services.DeepCopy(LastConfirmedView()); // Now apply all operations in pending foreach (var u in this.pending) try { Host.UpdateView(this.tentativeStateInternal, u.Entry); } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(CalculateTentativeState), e); } } /// /// Clears the pending operations and resets the tentative state. /// internal void ResetTentativeState() { this.pending.Clear(); CalculateTentativeState(); } private async Task ProcessClearLogRequest() { var clearLogTask = clearLogRequest; if (clearLogTask is null || clearLogTask.Task.IsCompleted) return; try { clearLogCancellationToken.ThrowIfCancellationRequested(); await ClearPrimaryLogAsync(clearLogCancellationToken); InitializeConfirmedView(this.InitialState); ResetTentativeState(); needRefresh = needInitialRead = false; lastVersionNotified = 0; try { Host.OnViewChanged(true, true); } catch (Exception e) { Services.CaughtUserCodeException("OnViewChanged", nameof(ProcessClearLogRequest), e); } clearLogTask.TrySetResult(true); } catch (OperationCanceledException) when (clearLogCancellationToken.IsCancellationRequested) { clearLogTask.TrySetCanceled(clearLogCancellationToken); } catch (Exception exception) { clearLogTask.TrySetException(exception); } finally { if (ReferenceEquals(clearLogRequest, clearLogTask)) clearLogRequest = null; } } /// /// batch worker performs reads from and writes to global state. /// only one work cycle is active at any time. /// internal async Task Work() { await ProcessClearLogRequest(); Services.Log(LogLevel.Debug, "<1 ProcessNotifications"); var version = GetConfirmedVersion(); ProcessNotifications(); Services.Log(LogLevel.Debug, "<2 NotifyViewChanges"); NotifyViewChanges(ref version); bool haveToWrite = (pending.Count != 0); bool haveToRead = needInitialRead || (needRefresh && !haveToWrite); Services.Log(LogLevel.Debug, "<3 Storage htr={0} htw={1}", haveToRead, haveToWrite); try { if (haveToRead) { needRefresh = needInitialRead = false; // retrieving fresh version await ReadAsync(); NotifyViewChanges(ref version); } if (haveToWrite) { needRefresh = needInitialRead = false; // retrieving fresh version await UpdatePrimary(); if (stats != null) stats.EventCounters["WritebackEvents"]++; } } catch (Exception e) { // this should never happen - we are supposed to catch and store exceptions // in the correct place (LastPrimaryException or notification trackers) Services.ProtocolError($"Exception in Worker Cycle: {e}", true); } Services.Log(LogLevel.Debug, "<4 Done"); } /// /// This function stores the operations in the pending queue as a batch to the primary. /// Retries until some batch commits or there are no updates left. /// internal async Task UpdatePrimary() { int version = GetConfirmedVersion(); while (true) { try { // find stale conditional updates, remove them, and notify waiters RemoveStaleConditionalUpdates(); if (pending.Count == 0) return; // no updates to write. // try to write the updates as a batch var writeResult = await WriteAsync(); NotifyViewChanges(ref version, writeResult); // if the batch write failed due to conflicts, retry. if (writeResult == 0) continue; try { Host.OnViewChanged(false, true); } catch (Exception e) { Services.CaughtUserCodeException("OnViewChanged", nameof(UpdatePrimary), e); } // notify waiting promises of the success of conditional updates NotifyPromises(writeResult, true); // record stabilization time, for statistics if (stats != null) { var timeNow = DateTime.UtcNow; for (int i = 0; i < writeResult; i++) { var latency = timeNow - pending[i].SubmissionTime; stats.StabilizationLatenciesInMsecs.Add(latency.Milliseconds); } } // remove completed updates from queue pending.RemoveRange(0, writeResult); return; } catch (Exception e) { // this should never happen - we are supposed to catch and store exceptions // in the correct place (LastPrimaryException or notification trackers) Services.ProtocolError($"Exception in {nameof(UpdatePrimary)}: {e}", true); } } } private void NotifyViewChanges(ref int version, int numWritten = 0) { var v = GetConfirmedVersion(); bool tentativeChanged = (v != version + numWritten); bool confirmedChanged = (v != version); if (tentativeChanged || confirmedChanged) { tentativeStateInternal = null; // conservative. try { Host.OnViewChanged(tentativeChanged, confirmedChanged); } catch (Exception e) { Services.CaughtUserCodeException("OnViewChanged", nameof(NotifyViewChanges), e); } version = v; } } /// /// Store the last issue that occurred while reading or updating primary. /// Is null if successful. /// protected RecordedConnectionIssue LastPrimaryIssue; /// public async Task Synchronize() { if (stats != null) stats.EventCounters["SynchronizeNowCalled"]++; Services.Log(LogLevel.Debug, "SynchronizeNowStart"); needRefresh = true; await worker.NotifyAndWaitForWorkToBeServiced(); Services.Log(LogLevel.Debug, "SynchronizeNowComplete"); } /// public IEnumerable UnconfirmedSuffix { get { return pending.Select(te => te.Entry); } } /// public async Task ConfirmSubmittedEntries() { if (stats != null) stats.EventCounters["ConfirmSubmittedEntriesCalled"]++; Services.Log(LogLevel.Debug, "ConfirmSubmittedEntriesStart"); if (pending.Count != 0) await worker.WaitForCurrentWorkToBeServiced(); Services.Log(LogLevel.Debug, "ConfirmSubmittedEntriesEnd"); } /// /// send failure notifications /// protected void NotifyPromises(int count, bool success) { for (int i = 0; i < count; i++) { var promise = pending[i].ResultPromise; if (promise != null) promise.SetResult(success); } } /// /// go through updates and remove all the conditional updates that have already failed /// protected void RemoveStaleConditionalUpdates() { int version = GetConfirmedVersion(); bool foundFailedConditionalUpdates = false; for (int pos = 0; pos < pending.Count; pos++) { var submissionEntry = pending[pos]; if (submissionEntry.ConditionalPosition != unconditional && (foundFailedConditionalUpdates || submissionEntry.ConditionalPosition != (version + pos))) { foundFailedConditionalUpdates = true; if (submissionEntry.ResultPromise != null) submissionEntry.ResultPromise.SetResult(false); } } if (foundFailedConditionalUpdates) { pending.RemoveAll(e => e.ConditionalPosition != unconditional); tentativeStateInternal = null; try { Host.OnViewChanged(true, false); } catch (Exception e) { Services.CaughtUserCodeException("OnViewChanged", nameof(RemoveStaleConditionalUpdates), e); } } } } /// /// Base class for submission entries stored in pending queue. /// /// The type of entry for this submission public class SubmissionEntry { /// The log entry that is submitted. public TLogEntry Entry; /// A timestamp for this submission. public DateTime SubmissionTime; /// For conditional updates, a promise that resolves once it is known whether the update was successful or not. public TaskCompletionSource ResultPromise; /// For conditional updates, the log position at which this update is supposed to be applied. public int ConditionalPosition; } } ================================================ FILE: src/Orleans.EventSourcing/Common/RecordedConnectionIssue.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.EventSourcing.Common { /// /// Utility class for recording connection issues. /// It is public, not internal, because it is a useful building block for implementing other consistency providers. /// public struct RecordedConnectionIssue { /// /// The recorded connection issue, or null if none /// public ConnectionIssue Issue { get; private set; } /// /// record a connection issue, filling in timestamps etc. /// and notify the listener /// /// the connection issue to be recorded /// the listener for connection issues /// for reporting exceptions in listener public void Record(ConnectionIssue newIssue, IConnectionIssueListener listener, ILogConsistencyProtocolServices services) { newIssue.TimeStamp = DateTime.UtcNow; if (Issue != null) { newIssue.TimeOfFirstFailure = Issue.TimeOfFirstFailure; newIssue.NumberOfConsecutiveFailures = Issue.NumberOfConsecutiveFailures + 1; newIssue.RetryDelay = newIssue.ComputeRetryDelay(Issue.RetryDelay); } else { newIssue.TimeOfFirstFailure = newIssue.TimeStamp; newIssue.NumberOfConsecutiveFailures = 1; newIssue.RetryDelay = newIssue.ComputeRetryDelay(null); } Issue = newIssue; try { listener.OnConnectionIssue(newIssue); } catch (Exception e) { services.CaughtUserCodeException("OnConnectionIssue", nameof(Record), e); } } /// /// if there is a recorded issue, notify listener and clear it. /// /// the listener for connection issues /// for reporting exceptions in listener public void Resolve(IConnectionIssueListener listener, ILogConsistencyProtocolServices services) { if (Issue != null) { try { listener.OnConnectionIssueResolved(Issue); } catch (Exception e) { services.CaughtUserCodeException("OnConnectionIssueResolved", nameof(Record), e); } Issue = null; } } /// /// delays if there was an issue in last attempt, for the duration specified by the retry delay /// /// public async readonly Task DelayBeforeRetry() { if (Issue == null) return; await Task.Delay(Issue.RetryDelay); } /// public override readonly string ToString() { if (Issue == null) return ""; else return Issue.ToString(); } } } ================================================ FILE: src/Orleans.EventSourcing/Common/StringEncodedWriteVector.cs ================================================ namespace Orleans.EventSourcing.Common { public static class StringEncodedWriteVector { // BitVector of replicas is implemented as a set of replica strings encoded within a string // The bitvector is represented as the set of replica ids whose bit is 1 // This set is written as a string that contains the replica ids preceded by a comma each // // Assuming our replicas are named A, B, and BB, then // "" represents {} represents 000 // ",A" represents {A} represents 100 // ",A,B" represents {A,B} represents 110 // ",BB,A,B" represents {A,B,BB} represents 111 /// /// Gets one of the bits in writeVector /// /// The write vector which we want get the bit from /// The replica for which we want to look up the bit /// public static bool GetBit(string writeVector, string Replica) { var pos = writeVector.IndexOf(Replica); return pos != -1 && writeVector[pos - 1] == ','; } /// /// toggle one of the bits in writeVector and return the new value. /// /// The write vector in which we want to flip the bit /// The replica for which we want to flip the bit /// the state of the bit after flipping it public static bool FlipBit(ref string writeVector, string Replica) { var pos = writeVector.IndexOf(Replica); if (pos != -1 && writeVector[pos - 1] == ',') { var pos2 = writeVector.IndexOf(',', pos + 1); if (pos2 == -1) pos2 = writeVector.Length; writeVector = writeVector.Remove(pos - 1, pos2 - pos + 1); return false; } else { writeVector = string.Format(",{0}{1}", Replica, writeVector); return true; } } } } ================================================ FILE: src/Orleans.EventSourcing/CustomStorage/CustomStorageLogConsistencyOptions.cs ================================================  namespace Orleans.Configuration { public class CustomStorageLogConsistencyOptions { public string PrimaryCluster { get; set; } } } ================================================ FILE: src/Orleans.EventSourcing/CustomStorage/ICustomStorageInterface.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.EventSourcing.CustomStorage { /// /// The storage interface exposed by grains that want to use the CustomStorage log-consistency provider /// /// The type for the state of the grain. /// The type for delta objects that represent updates to the state. public interface ICustomStorageInterface { /// /// Reads the current state and version from storage /// (note that the state object may be mutated by the provider, so it must not be shared). /// /// the version number and a state object. Task> ReadStateFromStorage(); /// /// Applies the given array of deltas to storage, and returns true, if the version in storage matches the expected version. /// Otherwise, does nothing and returns false. If successful, the version of storage must be increased by the number of deltas. /// /// true if the deltas were applied, false otherwise Task ApplyUpdatesToStorage(IReadOnlyList updates, int expectedVersion); /// /// Clears the stored state in storage. /// /// A task that represents the asynchronous clear operation. Task ClearStoredState() => throw new NotSupportedException(); } } ================================================ FILE: src/Orleans.EventSourcing/CustomStorage/LogConsistencyProvider.cs ================================================ using Orleans.Storage; using Orleans.Configuration; using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; namespace Orleans.EventSourcing.CustomStorage { /// /// A log-consistency provider that relies on grain-specific custom code for /// reading states from storage, and appending deltas to storage. /// Grains that wish to use this provider must implement the /// interface, to define how state is read and how deltas are written. /// If the provider attribute "PrimaryCluster" is supplied in the provider configuration, then only the specified cluster /// accesses storage, and other clusters may not issue updates. /// public class LogConsistencyProvider : ILogViewAdaptorFactory { private readonly CustomStorageLogConsistencyOptions options; /// /// Specifies a cluster id of the primary cluster from which to access storage exclusively, null if /// storage should be accessed directly from all clusters. /// public string PrimaryCluster => options.PrimaryCluster; /// public bool UsesStorageProvider => false; public LogConsistencyProvider(CustomStorageLogConsistencyOptions options) { this.options = options; } /// public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { return new CustomStorageAdaptor(hostGrain, initialState, services, PrimaryCluster); } } public static class LogConsistencyProviderFactory { public static ILogViewAdaptorFactory Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); return ActivatorUtilities.CreateInstance(services, optionsMonitor.Get(name)); } } } ================================================ FILE: src/Orleans.EventSourcing/CustomStorage/LogViewAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Storage; using Orleans.EventSourcing.Common; namespace Orleans.EventSourcing.CustomStorage { /// /// A log consistency adaptor that uses the user-provided storage interface . /// This interface must be implemented by any grain that uses this log view adaptor. /// /// log view type /// log entry type internal class CustomStorageAdaptor : PrimaryBasedLogViewAdaptor> where TLogView : class, new() where TLogEntry : class { /// /// Initialize a new instance of CustomStorageAdaptor class /// public CustomStorageAdaptor(ILogViewAdaptorHost host, TLogView initialState, ILogConsistencyProtocolServices services, string primaryCluster) : base(host, initialState, services) { if (!(host is ICustomStorageInterface customGrainStorage)) { throw new BadProviderConfigException("Must implement ICustomStorageInterface for CustomStorageLogView provider"); } this.customGrainStorage = customGrainStorage; this.primaryCluster = primaryCluster; } private readonly string primaryCluster; private readonly ICustomStorageInterface customGrainStorage; private TLogView cached; private int version; /// protected override TLogView LastConfirmedView() { return cached; } /// protected override int GetConfirmedVersion() { return version; } /// protected override Task ClearPrimaryLogAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return this.customGrainStorage.ClearStoredState(); } /// protected override void InitializeConfirmedView(TLogView initialstate) { cached = initialstate; version = 0; } /// protected override bool SupportSubmissions { get { return true; } } /// protected override SubmissionEntry MakeSubmissionEntry(TLogEntry entry) { // no special tagging is required, thus we create a plain submission entry return new SubmissionEntry() { Entry = entry }; } [Serializable] [GenerateSerializer] internal sealed class ReadRequest : ILogConsistencyProtocolMessage { [Id(0)] public int KnownVersion { get; set; } } [Serializable] [GenerateSerializer] internal sealed class ReadResponse : ILogConsistencyProtocolMessage { [Id(0)] public int Version { get; set; } [Id(1)] public ViewType Value { get; set; } } /// protected override Task OnMessageReceived(ILogConsistencyProtocolMessage payload) { var request = (ReadRequest) payload; var response = new ReadResponse() { Version = version }; // optimization: include value only if version is newer if (version > request.KnownVersion) response.Value = cached; return Task.FromResult(response); } /// protected override async Task ReadAsync() { enter_operation("ReadAsync"); while (true) { try { // read from storage var result = await ((ICustomStorageInterface)Host).ReadStateFromStorage(); version = result.Key; cached = result.Value; Services.Log(LogLevel.Debug, "read success v{0}", version); LastPrimaryIssue.Resolve(Host, Services); break; // successful } catch (Exception e) { // unwrap inner exception that was forwarded - helpful for debugging if ((e as ProtocolTransportException)?.InnerException != null) e = ((ProtocolTransportException)e).InnerException; LastPrimaryIssue.Record(new ReadFromPrimaryFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); await LastPrimaryIssue.DelayBeforeRetry(); } exit_operation("ReadAsync"); } /// protected override async Task WriteAsync() { enter_operation("WriteAsync"); var updates = GetCurrentBatchOfUpdates().Select(submissionentry => submissionentry.Entry).ToList(); bool writesuccessful = false; bool transitionssuccessful = false; try { writesuccessful = await ((ICustomStorageInterface) Host).ApplyUpdatesToStorage(updates, version); LastPrimaryIssue.Resolve(Host, Services); } catch (Exception e) { // unwrap inner exception that was forwarded - helpful for debugging if ((e as ProtocolTransportException)?.InnerException != null) e = ((ProtocolTransportException)e).InnerException; LastPrimaryIssue.Record(new UpdatePrimaryFailed() { Exception = e }, Host, Services); } if (writesuccessful) { Services.Log(LogLevel.Debug, "write ({0} updates) success v{1}", updates.Count, version + updates.Count); // now we update the cached state by applying the same updates // in case we encounter any exceptions we will re-read the whole state from storage try { foreach (var u in updates) { version++; Host.UpdateView(this.cached, u); } transitionssuccessful = true; } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(WriteAsync), e); } } if (!writesuccessful || !transitionssuccessful) { Services.Log(LogLevel.Debug, "{0} failed {1}", writesuccessful ? "transitions" : "write", LastPrimaryIssue); while (true) // be stubborn until we can re-read the state from storage { await LastPrimaryIssue.DelayBeforeRetry(); try { var result = await ((ICustomStorageInterface)Host).ReadStateFromStorage(); version = result.Key; cached = result.Value; Services.Log(LogLevel.Debug, "read success v{0}", version); LastPrimaryIssue.Resolve(Host, Services); break; } catch (Exception e) { // unwrap inner exception that was forwarded - helpful for debugging if ((e as ProtocolTransportException)?.InnerException != null) e = ((ProtocolTransportException)e).InnerException; LastPrimaryIssue.Record(new ReadFromPrimaryFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); } } exit_operation("WriteAsync"); return writesuccessful ? updates.Count : 0; } /// /// Describes a connection issue that occurred when updating the primary storage. /// [Serializable] [GenerateSerializer] public sealed class UpdatePrimaryFailed : PrimaryOperationFailed { /// public override string ToString() { return $"update primary failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// Describes a connection issue that occurred when reading from the primary storage. /// [Serializable] [GenerateSerializer] public sealed class ReadFromPrimaryFailed : PrimaryOperationFailed { /// public override string ToString() { return $"read from primary failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// A notification message that is sent to remote instances of this grain after the primary has been /// updated, to let them know the latest version. Contains all the updates that were applied. /// [Serializable] [GenerateSerializer] protected internal sealed class UpdateNotificationMessage : INotificationMessage { /// [Id(0)] public int Version { get; set; } /// The list of updates that were applied. [Id(1)] public List Updates { get; set; } /// /// A representation of this notification message suitable for tracing. /// public override string ToString() { return string.Format("v{0} ({1} updates)", Version, Updates.Count); } } private readonly SortedList notifications = new SortedList(); /// protected override void OnNotificationReceived(INotificationMessage payload) { var um = payload as UpdateNotificationMessage; if (um != null) notifications.Add(um.Version - um.Updates.Count, um); else base.OnNotificationReceived(payload); } /// protected override void ProcessNotifications() { // discard notifications that are behind our already confirmed state while (notifications.Count > 0 && notifications.ElementAt(0).Key < version) { Services.Log(LogLevel.Debug, "discarding notification {0}", notifications.ElementAt(0).Value); notifications.RemoveAt(0); } // process notifications that reflect next global version while (notifications.Count > 0 && notifications.ElementAt(0).Key == version) { var updatenotification = notifications.ElementAt(0).Value; notifications.RemoveAt(0); // Apply all operations in pending foreach (var u in updatenotification.Updates) try { Host.UpdateView(cached, u); } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(ProcessNotifications), e); } version = updatenotification.Version; Services.Log(LogLevel.Debug, "notification success ({0} updates) v{1}", updatenotification.Updates.Count, version); } Services.Log(LogLevel.Trace, "unprocessed notifications in queue: {0}", notifications.Count); base.ProcessNotifications(); } [Conditional("DEBUG")] private void enter_operation(string name) { Services.Log(LogLevel.Trace, "/-- enter {0}", name); } [Conditional("DEBUG")] private void exit_operation(string name) { Services.Log(LogLevel.Trace, "\\-- exit {0}", name); } } } ================================================ FILE: src/Orleans.EventSourcing/Hosting/CustomStorageSiloBuilderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.EventSourcing; using Orleans.Providers; using Orleans.Runtime; using Orleans.EventSourcing.CustomStorage; using Orleans.Configuration; namespace Orleans.Hosting { public static class CustomStorageSiloBuilderExtensions { /// /// Adds a custom storage log consistency provider as default consistency provider"/> /// public static ISiloBuilder AddCustomStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder, string primaryCluster = null) { return builder.AddCustomStorageBasedLogConsistencyProvider(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, primaryCluster); } /// /// Adds a custom storage log consistency provider"/> /// public static ISiloBuilder AddCustomStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "LogStorage", string primaryCluster = null) { return builder.ConfigureServices(services => services.AddCustomStorageBasedLogConsistencyProvider(name, primaryCluster)); } internal static void AddCustomStorageBasedLogConsistencyProvider(this IServiceCollection services, string name, string primaryCluster) { services.AddLogConsistencyProtocolServicesFactory(); services.AddOptions(name) .Configure(options => options.PrimaryCluster = primaryCluster); services.ConfigureNamedOptionForLogging(name) .AddKeyedSingleton(name, (sp, key) => LogConsistencyProviderFactory.Create(sp, key as string)) .TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); } } } ================================================ FILE: src/Orleans.EventSourcing/Hosting/LogConsistencyProtocolSiloBuilderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.EventSourcing; using Orleans.Runtime; using Orleans.Runtime.LogConsistency; namespace Orleans.Hosting { internal static class LogConsistencyProtocolSiloBuilderExtensions { internal static IServiceCollection AddLogConsistencyProtocolServicesFactory(this IServiceCollection services) { services.TryAddSingleton>(serviceProvider => { var factory = ActivatorUtilities.CreateFactory(typeof(ProtocolServices), new[] { typeof(IGrainContext) }); return arg1 => (ILogConsistencyProtocolServices)factory(serviceProvider, new object[] { arg1 }); }); return services; } } } ================================================ FILE: src/Orleans.EventSourcing/Hosting/LogStorageSiloBuilderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.EventSourcing; using Orleans.Providers; using Orleans.Runtime; using Orleans.EventSourcing.LogStorage; namespace Orleans.Hosting { public static class LogStorageSiloBuilderExtensions { /// /// Adds a log storage log consistency provider as default consistency provider"/> /// public static ISiloBuilder AddLogStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder) { return builder.AddLogStorageBasedLogConsistencyProvider(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME); } /// /// Adds a log storage log consistency provider"/> /// public static ISiloBuilder AddLogStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "LogStorage") { return builder.ConfigureServices(services => services.AddLogStorageBasedLogConsistencyProvider(name)); } internal static IServiceCollection AddLogStorageBasedLogConsistencyProvider(this IServiceCollection services, string name) { services.AddLogConsistencyProtocolServicesFactory(); services.TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); return services.AddKeyedSingleton(name); } } } ================================================ FILE: src/Orleans.EventSourcing/Hosting/StateStorageSiloBuilderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.EventSourcing; using Orleans.Providers; using Orleans.Runtime; using Orleans.EventSourcing.StateStorage; namespace Orleans.Hosting { public static class StateStorageSiloBuilderExtensions { /// /// Adds a state storage log consistency provider as default consistency provider"/> /// public static ISiloBuilder AddStateStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder) { return builder.AddStateStorageBasedLogConsistencyProvider(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME); } /// /// Adds a state storage log consistency provider"/> /// public static ISiloBuilder AddStateStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "StateStorage") { return builder.ConfigureServices(services => services.AddStateStorageBasedLogConsistencyProvider(name)); } internal static IServiceCollection AddStateStorageBasedLogConsistencyProvider(this IServiceCollection services, string name) { services.AddLogConsistencyProtocolServicesFactory(); services.TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); return services.AddKeyedSingleton(name); } } } ================================================ FILE: src/Orleans.EventSourcing/JournaledGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Storage; namespace Orleans.EventSourcing { /// /// A base class for log-consistent grains using standard event-sourcing terminology. /// All operations are reentrancy-safe. /// /// The type for the grain state, i.e. the aggregate view of the event log. public abstract class JournaledGrain : JournaledGrain where TGrainState : class, new() { /// /// Initializes a new instance of the class. /// protected JournaledGrain() { } } /// /// A base class for log-consistent grains using standard event-sourcing terminology. /// All operations are reentrancy-safe. /// /// The type for the grain state, i.e. the aggregate view of the event log. /// The common base class for the events public abstract class JournaledGrain : LogConsistentGrain, ILogConsistencyProtocolParticipant, ILogViewAdaptorHost where TGrainState : class, new() where TEventBase: class { /// /// Initializes a new instance of the class. /// protected JournaledGrain() { } /// /// Raises an event. /// /// Event to raise. protected virtual void RaiseEvent(TEvent @event) where TEvent : TEventBase { if (@event == null) throw new ArgumentNullException(nameof(@event)); LogViewAdaptor.Submit(@event); } /// /// Raise multiple events, as an atomic sequence. /// /// Events to raise. protected virtual void RaiseEvents(IEnumerable events) where TEvent : TEventBase { if (events == null) throw new ArgumentNullException(nameof(events)); LogViewAdaptor.SubmitRange((IEnumerable) events); } /// /// Raise an event conditionally. /// Succeeds only if there are no conflicts, that is, no other events were raised in the meantime. /// /// Event to raise. /// if successful, if there was a conflict. protected virtual Task RaiseConditionalEvent(TEvent @event) where TEvent : TEventBase { if (@event == null) throw new ArgumentNullException(nameof(@event)); return LogViewAdaptor.TryAppend(@event); } /// /// Raise multiple events, as an atomic sequence, conditionally. /// Succeeds only if there are no conflicts, that is, no other events were raised in the meantime. /// /// Events to raise /// if successful, if there was a conflict. protected virtual Task RaiseConditionalEvents(IEnumerable events) where TEvent : TEventBase { if (events == null) throw new ArgumentNullException(nameof(events)); return LogViewAdaptor.TryAppendRange((IEnumerable) events); } /// /// Gets the current confirmed state. /// Includes only confirmed events. /// protected TGrainState State { get { return this.LogViewAdaptor.ConfirmedView; } } /// /// Gets the version of the current confirmed state. /// Equals the total number of confirmed events. /// protected int Version { get { return this.LogViewAdaptor.ConfirmedVersion; } } /// /// Called whenever the tentative state may have changed due to local or remote events. /// Override this to react to changes of the state. /// protected virtual void OnTentativeStateChanged() { } /// /// Gets the current tentative state. /// Includes both confirmed and unconfirmed events. /// protected TGrainState TentativeState { get { return this.LogViewAdaptor.TentativeView; } } /// /// Called after the confirmed state may have changed (i.e. the confirmed version number is larger). /// Override this to react to changes of the confirmed state. /// protected virtual void OnStateChanged() { // overridden by journaled grains that want to react to state changes } /// /// Waits until all previously raised events have been confirmed. /// await this after raising one or more events, to ensure events are persisted before proceeding, or to guarantee strong consistency (linearizability) even if there are multiple instances of this grain /// /// a task that completes once the events have been confirmed. protected Task ConfirmEvents() { return LogViewAdaptor.ConfirmSubmittedEntries(); } /// /// Retrieves the latest state now, and confirms all previously raised events. /// Effectively, this enforces synchronization with the global state. /// Await this before reading the state to ensure strong consistency (linearizability) even if there are multiple instances of this grain /// /// a task that completes once the log has been refreshed and the events have been confirmed. protected Task RefreshNow() { return LogViewAdaptor.Synchronize(); } /// /// Returns the current queue of unconfirmed events. /// public IEnumerable UnconfirmedEvents { get { return LogViewAdaptor.UnconfirmedSuffix; } } /// /// By default, upon activation, the journaled grain waits until it has loaded the latest /// view from storage. Subclasses can override this behavior, /// and skip the wait if desired. /// public override Task OnActivateAsync(CancellationToken cancellationToken) { return LogViewAdaptor.Synchronize(); } /// /// Retrieves a segment of the confirmed event sequence, possibly from storage. /// Throws if the events are not available to read. /// Whether events are available, and for how long, depends on the providers used and how they are configured. /// /// the position of the event sequence from which to start /// the position of the event sequence on which to end /// a task which returns the sequence of events between the two versions protected Task> RetrieveConfirmedEvents(int fromVersion, int toVersion) { if (fromVersion < 0) throw new ArgumentException("invalid range", nameof(fromVersion)); if (toVersion < fromVersion || toVersion > LogViewAdaptor.ConfirmedVersion) throw new ArgumentException("invalid range", nameof(toVersion)); return LogViewAdaptor.RetrieveLogSegment(fromVersion, toVersion); } /// /// Clears the log of all confirmed events. Reset the state to the initial state, and discards all unconfirmed events. /// Throws if the log cannot be cleared. /// protected Task ClearLogAsync(CancellationToken cancellationToken = default) { return LogViewAdaptor.ClearLogAsync(cancellationToken); } /// /// Called when the underlying persistence or replication protocol is running into some sort of connection trouble. /// Override this to monitor the health of the log-consistency protocol and/or /// to customize retry delays. /// Any exceptions thrown are caught and logged by the . /// /// The time to wait before retrying protected virtual void OnConnectionIssue(ConnectionIssue issue) { } /// /// Called when a previously reported connection issue has been resolved. /// Override this to monitor the health of the log-consistency protocol. /// Any exceptions thrown are caught and logged by the . /// protected virtual void OnConnectionIssueResolved(ConnectionIssue issue) { } /// protected void EnableStatsCollection() { LogViewAdaptor.EnableStatsCollection(); } /// protected void DisableStatsCollection() { LogViewAdaptor.DisableStatsCollection(); } /// protected LogConsistencyStatistics GetStats() { return LogViewAdaptor.GetStats(); } /// /// Defines how to apply events to the state. Unless it is overridden in the subclass, it calls /// a dynamic "Apply" function on the state, with the event as a parameter. /// All exceptions thrown by this method are caught and logged by the log view provider. /// Override this to customize how to transition the state for a given event. /// /// The state. /// The event. protected virtual void TransitionState(TGrainState state, TEventBase @event) { dynamic s = state; dynamic e = @event; s.Apply(e); } /// /// Gets the adaptor for the log-consistency protocol, which is installed by the log-consistency provider. /// internal ILogViewAdaptor LogViewAdaptor { get; private set; } /// /// Called right after grain is constructed, to install the adaptor. /// The log-consistency provider contains a factory method that constructs the adaptor with chosen types for this grain /// protected override void InstallAdaptor(ILogViewAdaptorFactory factory, object initialState, string graintypename, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) { // call the log consistency provider to construct the adaptor, passing the type argument LogViewAdaptor = factory.MakeLogViewAdaptor(this, (TGrainState)initialState, graintypename, grainStorage, services); } /// /// If there is no log-consistency provider specified, store versioned state using default storage provider /// protected override ILogViewAdaptorFactory DefaultAdaptorFactory { get { return new StateStorage.DefaultAdaptorFactory(); } } /// /// Called by adaptor to update the view when entries are appended. /// /// The log view. /// The entry. void ILogViewAdaptorHost.UpdateView(TGrainState view, TEventBase entry) { TransitionState(view, entry); } /// /// Notify log view adaptor of activation (called before user-level OnActivate) /// async Task ILogConsistencyProtocolParticipant.PreActivateProtocolParticipant() { await LogViewAdaptor.PreOnActivate(); } /// /// Notify log view adaptor of activation (called after user-level OnActivate) /// async Task ILogConsistencyProtocolParticipant.PostActivateProtocolParticipant() { await LogViewAdaptor.PostOnActivate(); } /// /// Notify log view adaptor of deactivation /// Task ILogConsistencyProtocolParticipant.DeactivateProtocolParticipant() { return LogViewAdaptor.PostOnDeactivate(); } /// /// Called by adaptor on state change. /// void ILogViewAdaptorHost.OnViewChanged(bool tentative, bool confirmed) { if (tentative) OnTentativeStateChanged(); if (confirmed) OnStateChanged(); } /// /// called by adaptor on connection issues. /// void IConnectionIssueListener.OnConnectionIssue(ConnectionIssue connectionIssue) { OnConnectionIssue(connectionIssue); } /// /// Called by adaptor when a connection issue is resolved. /// void IConnectionIssueListener.OnConnectionIssueResolved(ConnectionIssue connectionIssue) { OnConnectionIssueResolved(connectionIssue); } } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ConnectionIssues.cs ================================================ using System; namespace Orleans.EventSourcing { /// /// Represents information about connection issues encountered inside log consistency protocols. /// It is used both inside the protocol to track retry loops, and is made visible to users /// who want to monitor their log-consistent grains for communication issues. /// [Serializable] [GenerateSerializer] public abstract class ConnectionIssue { /// /// The UTC timestamp of the last time at which the issue was observed /// [Id(0)] public DateTime TimeStamp { get; set; } /// /// The UTC timestamp of the first time we observed this issue /// [Id(1)] public DateTime TimeOfFirstFailure { get; set; } /// /// The number of times we have observed this issue since the first failure /// [Id(2)] public int NumberOfConsecutiveFailures { get; set; } /// /// The delay we are waiting before the next retry /// [Id(3)] public TimeSpan RetryDelay { get; set; } /// /// Computes the retry delay based on the rest of the information. Is overridden by subclasses /// that represent specific categories of issues. /// /// The previously used retry delay /// public abstract TimeSpan ComputeRetryDelay(TimeSpan? previous); } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/IConnectionIssueListener.cs ================================================ namespace Orleans.EventSourcing { /// /// An interface that is implemented by log-consistent grains using virtual protected methods /// that can be overridden by users, in order to monitor the connection issues. /// public interface IConnectionIssueListener { /// /// Called when running into some sort of connection trouble. /// The called code can modify the retry delay if desired, to change the default. /// void OnConnectionIssue(ConnectionIssue connectionIssue); /// /// Called when a previously reported connection issue has been resolved. /// void OnConnectionIssueResolved(ConnectionIssue connectionIssue); } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogConsistencyDiagnostics.cs ================================================ using System; using System.Collections.Generic; namespace Orleans.EventSourcing { /// /// Interface for diagnostics. /// public interface ILogConsistencyDiagnostics { /// Turns on the statistics collection for this log-consistent grain. void EnableStatsCollection(); /// Turns off the statistics collection for this log-consistent grain. void DisableStatsCollection(); /// Gets the collected statistics for this log-consistent grain. LogConsistencyStatistics GetStats(); } /// /// A collection of statistics for grains using log-consistency. See /// public class LogConsistencyStatistics { /// /// A map from event names to event counts /// public Dictionary EventCounters; /// /// A list of all measured stabilization latencies /// public List StabilizationLatenciesInMsecs; } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogConsistencyProtocolGateway.cs ================================================ using System.Threading.Tasks; using Orleans.EventSourcing; using Orleans.Runtime; namespace Orleans.SystemTargetInterfaces { /// /// The protocol gateway is a relay that forwards incoming protocol messages from other clusters /// to the appropriate grain in this cluster. /// internal interface ILogConsistencyProtocolGateway : ISystemTarget { Task RelayMessage(GrainId id, ILogConsistencyProtocolMessage payload); } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogConsistencyProtocolServices.cs ================================================ using System; using System.Runtime.Serialization; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.EventSourcing { /// /// Functionality for use by log view adaptors that use custom consistency or replication protocols. /// Abstracts communication between replicas of the log-consistent grain in different clusters. /// public interface ILogConsistencyProtocolServices { /// /// The ID for this grain. /// GrainId GrainId { get; } /// /// Copies the provided argument. /// T DeepCopy(T value); /// /// The id of this cluster. Returns "I" if no multi-cluster network is present. /// /// string MyClusterId { get; } /// /// Log an error that occurred in a log-consistency protocol. /// void ProtocolError(string msg, bool throwexception); /// /// Log an exception that was caught in the log-consistency protocol. /// void CaughtException(string where, Exception e); /// /// Log an exception that occurred in user code, for some callback /// /// The name of the callback /// The context from which the callback was called /// The caught exception void CaughtUserCodeException(string callback, string where, Exception e); /// Output the specified message at the specified log level. void Log(LogLevel level, string format, params object[] args); } /// /// Exception thrown by protocol messaging layer. /// [Serializable] [GenerateSerializer] public sealed class ProtocolTransportException : OrleansException { public ProtocolTransportException() { } public ProtocolTransportException(string msg) : base(msg) { } public ProtocolTransportException(string msg, Exception exc) : base(msg, exc) { } [Obsolete] protected ProtocolTransportException(SerializationInfo info, StreamingContext context) : base(info, context) { } public override string ToString() { if (InnerException != null) return $"ProtocolTransportException: {InnerException}"; else return Message; } } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogViewAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Orleans.EventSourcing { /// /// A log view adaptor is the storage interface for , whose state is defined as a log view. /// /// There is one adaptor per grain, which is installed by when the grain is activated. /// /// /// Type for the log view /// Type for the log entry public interface ILogViewAdaptor : ILogViewRead, ILogViewUpdate, ILogConsistencyDiagnostics where TLogView : new() { /// Called during activation, right before the user-defined . Task PreOnActivate(); /// Called during activation, right after the user-defined .. Task PostOnActivate(); /// Called during deactivation, right after the user-defined . Task PostOnDeactivate(); } /// /// Interface for reading the log view. /// /// The type of the view (state of the grain). /// The type of log entries. public interface ILogViewRead { /// /// Local, tentative view of the log (reflecting both confirmed and unconfirmed entries) /// TView TentativeView { get; } /// /// Confirmed view of the log (reflecting only confirmed entries) /// TView ConfirmedView { get; } /// /// The length of the confirmed prefix of the log /// int ConfirmedVersion { get; } /// /// A list of the submitted entries that do not yet appear in the confirmed prefix. /// IEnumerable UnconfirmedSuffix { get; } /// /// Attempt to retrieve a segment of the log, possibly from storage. Throws if /// the log cannot be read, which depends on the providers used and how they are configured. /// /// the start position /// the end position /// a Task> RetrieveLogSegment(int fromVersion, int toVersion); } /// /// Interface for updating the log. /// /// The type of log entries. public interface ILogViewUpdate { /// /// Submit a single log entry to be appended to the global log, /// either at the current or at any later position. /// void Submit(TLogEntry entry); /// /// Submit a range of log entries to be appended atomically to the global log, /// either at the current or at any later position. /// void SubmitRange(IEnumerable entries); /// /// Try to append a single log entry at the current position of the log. /// /// true if the entry was appended successfully, or false /// if there was a concurrency conflict (i.e. some other entries were previously appended). /// Task TryAppend(TLogEntry entry); /// /// Try to append a range of log entries atomically at the current position of the log. /// /// true if the entries were appended successfully, or false /// if there was a concurrency conflict (i.e. some other entries were previously appended). /// Task TryAppendRange(IEnumerable entries); /// /// Confirm all submitted entries. ///Waits until all previously submitted entries appear in the confirmed prefix of the log. /// /// A task that completes after all entries are confirmed. Task ConfirmSubmittedEntries(); /// /// Get the latest log view and confirm all submitted entries. ///Waits until all previously submitted entries appear in the confirmed prefix of the log, and forces a refresh of the confirmed prefix. /// /// A task that completes after getting the latest version and confirming all entries. Task Synchronize(); /// /// Clear the log stream completely. Throws if /// the log stream does not support clearing. /// /// A cancellation token to cancel the operation. /// A task that represents the asynchronous clear operation. Task ClearLogAsync(CancellationToken cancellationToken) { throw new NotSupportedException($"The operation {nameof(ClearLogAsync)} is not supported by this implementation of {nameof(ILogViewUpdate)}."); } } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogViewAdaptorFactory.cs ================================================ using Orleans.Storage; namespace Orleans.EventSourcing { /// /// Interface to be implemented for a log-view adaptor factory /// public interface ILogViewAdaptorFactory { /// Returns true if a storage provider is required for constructing adaptors. bool UsesStorageProvider { get; } /// /// Constructs a to be installed in the given host grain. /// /// The type of the view /// The type of the log entries /// The grain that is hosting this adaptor /// The initial state for this view /// The type name of the grain /// Storage provider /// Runtime services for multi-cluster coherence protocols ILogViewAdaptor MakeLogViewAdaptor( ILogViewAdaptorHost hostGrain, TLogView initialState, string grainTypeName, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TLogView : class, new() where TLogEntry : class; } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ILogViewAdaptorHost.cs ================================================ namespace Orleans.EventSourcing { /// /// Interface implemented by all grains which use log-view consistency /// It gives the log view adaptor access to grain-specific information and callbacks. /// /// type of the log view /// type of log entries public interface ILogViewAdaptorHost : IConnectionIssueListener { /// /// Implementation of view transitions. /// Any exceptions thrown will be caught and logged as a warning./>. /// void UpdateView(TLogView view, TLogEntry entry); /// /// Notifies the host grain about state changes. /// Called by whenever the tentative or confirmed state changes. /// Implementations may vary as to whether and how much they batch change notifications. /// Any exceptions thrown will be caught and logged as a warning./>. /// void OnViewChanged(bool tentative, bool confirmed); } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/IProtocolParticipant.cs ================================================ using System.Threading.Tasks; namespace Orleans.EventSourcing { /// /// Grain interface for grains that participate in multi-cluster log-consistency protocols. /// public interface ILogConsistencyProtocolParticipant : IGrain { /// /// Called immediately before the user-level OnActivateAsync, on same scheduler. /// /// Task PreActivateProtocolParticipant(); /// /// Called immediately after the user-level OnActivateAsync, on same scheduler. /// /// Task PostActivateProtocolParticipant(); /// /// Called immediately after the user-level OnDeactivateAsync, on same scheduler. /// /// Task DeactivateProtocolParticipant(); } /// /// interface to mark classes that represent protocol messages. /// All such classes must be serializable. /// public interface ILogConsistencyProtocolMessage { } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/LogConsistentGrain.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Storage; using Orleans.Providers; namespace Orleans.EventSourcing { /// /// Base class for all grains that use log-consistency for managing the state. /// It is the equivalent of for grains using log-consistency. /// (SiloAssemblyLoader uses it to extract type) /// /// The type of the view public abstract class LogConsistentGrain : Grain, ILifecycleParticipant { /// /// called right after grain construction to install the log view adaptor /// /// The adaptor factory to use /// The initial state of the view /// The type name of the grain /// The grain storage, if needed /// Protocol services protected abstract void InstallAdaptor(ILogViewAdaptorFactory factory, object state, string grainTypeName, IGrainStorage grainStorage, ILogConsistencyProtocolServices services); /// /// Gets the default adaptor factory to use, or null if there is no default /// (in which case user MUST configure a consistency provider) /// protected abstract ILogViewAdaptorFactory DefaultAdaptorFactory { get; } public virtual void Participate(IGrainLifecycle lifecycle) { lifecycle.Subscribe>(GrainLifecycleStage.SetupState, OnSetupState, OnDeactivateState); if (this is ILogConsistencyProtocolParticipant) { lifecycle.Subscribe>(GrainLifecycleStage.Activate - 1, PreActivate); lifecycle.Subscribe>(GrainLifecycleStage.Activate + 1, PostActivate); } } private async Task OnDeactivateState(CancellationToken ct) { if (this is ILogConsistencyProtocolParticipant participant) { await participant.DeactivateProtocolParticipant(); } } private Task OnSetupState(CancellationToken ct) { if (ct.IsCancellationRequested) return Task.CompletedTask; IGrainContextAccessor grainContextAccessor = this.ServiceProvider.GetRequiredService(); Factory protocolServicesFactory = this.ServiceProvider.GetRequiredService>(); var grainContext = grainContextAccessor.GrainContext; ILogViewAdaptorFactory consistencyProvider = SetupLogConsistencyProvider(grainContext); IGrainStorage grainStorage = consistencyProvider.UsesStorageProvider ? GrainStorageHelpers.GetGrainStorage(grainContext?.GrainInstance.GetType(), this.ServiceProvider) : null; InstallLogViewAdaptor(grainContext, protocolServicesFactory, consistencyProvider, grainStorage); return Task.CompletedTask; } private async Task PreActivate(CancellationToken ct) { await ((ILogConsistencyProtocolParticipant)this).PreActivateProtocolParticipant(); } private async Task PostActivate(CancellationToken ct) { await ((ILogConsistencyProtocolParticipant)this).PostActivateProtocolParticipant(); } private void InstallLogViewAdaptor( IGrainContext grainContext, Factory protocolServicesFactory, ILogViewAdaptorFactory factory, IGrainStorage grainStorage) { // encapsulate runtime services used by consistency adaptors ILogConsistencyProtocolServices svc = protocolServicesFactory(grainContext); TView state = (TView)Activator.CreateInstance(typeof(TView)); this.InstallAdaptor(factory, state, this.GetType().FullName, grainStorage, svc); } private ILogViewAdaptorFactory SetupLogConsistencyProvider(IGrainContext activationContext) { var attr = this.GetType().GetCustomAttributes(true).FirstOrDefault(); ILogViewAdaptorFactory defaultFactory = attr != null ? this.ServiceProvider.GetKeyedService(attr.ProviderName) : this.ServiceProvider.GetService(); if (attr != null && defaultFactory == null) { var errMsg = $"Cannot find consistency provider with Name={attr.ProviderName} for grain type {this.GetType().FullName}"; throw new BadProviderConfigException(errMsg); } // use default if none found defaultFactory = defaultFactory ?? this.DefaultAdaptorFactory; if (defaultFactory == null) { var errMsg = $"No log consistency provider found loading grain type {this.GetType().FullName}"; throw new BadProviderConfigException(errMsg); }; return defaultFactory; } } } ================================================ FILE: src/Orleans.EventSourcing/LogConsistency/ProtocolServices.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.EventSourcing; using Orleans.Serialization; namespace Orleans.Runtime.LogConsistency { /// /// Functionality for use by log view adaptors that run distributed protocols. /// This class allows access to these services to providers that cannot see runtime-internals. /// It also stores grain-specific information like the grain reference, and caches /// internal class ProtocolServices : ILogConsistencyProtocolServices { private readonly ILogger log; private readonly DeepCopier deepCopier; private readonly IGrainContext grainContext; // links to the grain that owns this service object public ProtocolServices( IGrainContext grainContext, ILoggerFactory loggerFactory, DeepCopier deepCopier, ILocalSiloDetails siloDetails) { this.grainContext = grainContext; this.log = loggerFactory.CreateLogger(); this.deepCopier = deepCopier; this.MyClusterId = siloDetails.ClusterId; } public GrainId GrainId => grainContext.GrainId; public string MyClusterId { get; } public T DeepCopy(T value) => this.deepCopier.Copy(value); public void ProtocolError(string msg, bool throwexception) { log.LogError( (int)(throwexception ? ErrorCode.LogConsistency_ProtocolFatalError : ErrorCode.LogConsistency_ProtocolError), "{GrainId} Protocol Error: {Message}", grainContext.GrainId, msg); if (!throwexception) return; throw new OrleansException(string.Format("{0} (grain={1}, cluster={2})", msg, grainContext.GrainId, this.MyClusterId)); } public void CaughtException(string where, Exception e) { log.LogError( (int)ErrorCode.LogConsistency_CaughtException, e, "{GrainId} exception caught at {Location}", grainContext.GrainId, where); } public void CaughtUserCodeException(string callback, string where, Exception e) { log.LogWarning( (int)ErrorCode.LogConsistency_UserCodeException, e, "{GrainId} exception caught in user code for {Callback}, called from {Location}", grainContext.GrainId, callback, where); } public void Log(LogLevel level, string format, params object[] args) { if (log != null && log.IsEnabled(level)) { var msg = $"{grainContext.GrainId} {string.Format(format, args)}"; log.Log(level, 0, msg, null, (m, exc) => $"{m}"); } } } } ================================================ FILE: src/Orleans.EventSourcing/LogStorage/DefaultAdaptorFactory.cs ================================================ using Orleans.Storage; namespace Orleans.EventSourcing.LogStorage { internal class DefaultAdaptorFactory : ILogViewAdaptorFactory { public bool UsesStorageProvider { get { return true; } } public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostgrain, T initialstate, string graintypename, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where T : class, new() where E : class { return new LogViewAdaptor(hostgrain, initialstate, grainStorage, graintypename, services); } } } ================================================ FILE: src/Orleans.EventSourcing/LogStorage/LogConsistencyProvider.cs ================================================ using Orleans.Storage; namespace Orleans.EventSourcing.LogStorage { /// /// A log-consistency provider that stores the latest view in primary storage, using any standard storage provider. /// Supports multiple clusters connecting to the same primary storage (doing optimistic concurrency control via e-tags) /// /// The log itself is transient, i.e. not actually saved to storage - only the latest view (snapshot) and some /// metadata (the log position, and write flags) are stored in the primary. /// /// public class LogConsistencyProvider : ILogViewAdaptorFactory { /// public bool UsesStorageProvider { get { return true; } } /// /// Make log view adaptor /// /// The type of the view /// The type of the log entries /// The grain that is hosting this adaptor /// The initial state for this view /// The type name of the grain /// Storage provider /// Runtime services for multi-cluster coherence protocols public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { return new LogViewAdaptor(hostGrain, initialState, grainStorage, grainTypeName, services); } } } ================================================ FILE: src/Orleans.EventSourcing/LogStorage/LogStateWithMetaData.cs ================================================ using Orleans.EventSourcing.Common; using System; using System.Collections.Generic; namespace Orleans.EventSourcing.LogStorage { /// /// A class that extends grain state with versioning metadata, so that a grain with log-view consistency /// can use a standard storage provider. /// /// The type used for log entries [Serializable] [GenerateSerializer] public sealed class LogStateWithMetaDataAndETag : IGrainState> where TEntry : class { /// /// Gets and Sets StateAndMetaData /// [Id(0)] public LogStateWithMetaData StateAndMetaData { get; set; } /// /// Gets and Sets Etag /// [Id(1)] public string ETag { get; set; } [Id(2)] public bool RecordExists { get; set; } public LogStateWithMetaData State { get => StateAndMetaData; set => StateAndMetaData = value; } /// /// Initializes a new instance of GrainStateWithMetaDataAndETag class /// public LogStateWithMetaDataAndETag() { StateAndMetaData = new LogStateWithMetaData(); } /// /// Convert current GrainStateWithMetaDataAndETag object information to a string /// public override string ToString() { return string.Format("v{0} Flags={1} ETag={2} Data={3}", StateAndMetaData.GlobalVersion, StateAndMetaData.WriteVector, ETag, StateAndMetaData.Log); } } /// /// A class that extends grain state with versioning metadata, so that a log-consistent grain /// can use a standard storage provider. /// [Serializable] [GenerateSerializer] public sealed class LogStateWithMetaData where TEntry : class { /// /// The stored view of the log /// [Id(0)] public List Log { get; set; } /// /// The length of the log /// public int GlobalVersion { get { return Log.Count; } } /// /// Metadata that is used to avoid duplicate appends. /// Logically, this is a (string->bit) map, the keys being replica ids /// But this map is represented compactly as a simple string to reduce serialization/deserialization overhead /// Bits are read by and flipped by . /// Bits are toggled when writing, so that the retry logic can avoid appending an entry twice /// when retrying a failed append. /// [Id(1)] public string WriteVector { get; set; } /// /// Initializes a new instance of the class. /// public LogStateWithMetaData() { Log = []; WriteVector = ""; } /// /// Gets one of the bits in /// /// The replica for which we want to look up the bit /// public bool GetBit(string replica) { return StringEncodedWriteVector.GetBit(WriteVector, replica); } /// /// Toggle one of the bits in and return the new value. /// /// The replica for which we want to flip the bit /// the state of the bit after flipping it public bool FlipBit(string replica) { var str = WriteVector; var result = StringEncodedWriteVector.FlipBit(ref str, replica); WriteVector = str; return result; } } } ================================================ FILE: src/Orleans.EventSourcing/LogStorage/LogViewAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Storage; using Orleans.EventSourcing.Common; namespace Orleans.EventSourcing.LogStorage { /// /// A log view adaptor that wraps around a traditional storage adaptor, and uses batching and e-tags /// to append entries. /// /// The log itself is transient, i.e. not actually saved to storage - only the latest view and some /// metadata (the log position, and write flags) are stored. /// /// /// Type of log view /// Type of log entry internal class LogViewAdaptor : PrimaryBasedLogViewAdaptor> where TLogView : class, new() where TLogEntry : class { /// /// Initialize a StorageProviderLogViewAdaptor class /// public LogViewAdaptor(ILogViewAdaptorHost host, TLogView initialState, IGrainStorage globalGrainStorage, string grainTypeName, ILogConsistencyProtocolServices services) : base(host, initialState, services) { this.globalGrainStorage = globalGrainStorage; this.grainTypeName = grainTypeName; } private const int maxEntriesInNotifications = 200; private readonly IGrainStorage globalGrainStorage; private readonly string grainTypeName; // the object containing the entire log, as retrieved from / sent to storage private LogStateWithMetaDataAndETag GlobalLog; // the confirmed view private TLogView ConfirmedViewInternal; private int ConfirmedVersionInternal; /// protected override TLogView LastConfirmedView() { return ConfirmedViewInternal; } /// protected override int GetConfirmedVersion() { return ConfirmedVersionInternal; } /// protected override void InitializeConfirmedView(TLogView initialstate) { GlobalLog = new LogStateWithMetaDataAndETag(); ConfirmedViewInternal = initialstate; ConfirmedVersionInternal = 0; } protected override Task ClearPrimaryLogAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return this.globalGrainStorage.ClearStateAsync(grainTypeName, Services.GrainId, GlobalLog); } private void UpdateConfirmedView() { for (int i = ConfirmedVersionInternal; i < GlobalLog.StateAndMetaData.Log.Count; i++) { try { Host.UpdateView(ConfirmedViewInternal, GlobalLog.StateAndMetaData.Log[i]); } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(UpdateConfirmedView), e); } } ConfirmedVersionInternal = GlobalLog.StateAndMetaData.GlobalVersion; } /// public override Task> RetrieveLogSegment(int fromVersion, int toVersion) { // make a copy of the entries in the range asked for IReadOnlyList segment = GlobalLog.StateAndMetaData.Log.GetRange(fromVersion, (toVersion - fromVersion)); return Task.FromResult(segment); } // no special tagging is required, thus we create a plain submission entry /// protected override SubmissionEntry MakeSubmissionEntry(TLogEntry entry) { return new SubmissionEntry() { Entry = entry }; } /// protected override async Task ReadAsync() { enter_operation("ReadAsync"); while (true) { try { // for manual testing //await Task.Delay(5000); await globalGrainStorage.ReadStateAsync(grainTypeName, Services.GrainId, GlobalLog); Services.Log(LogLevel.Debug, "read success {0}", GlobalLog); UpdateConfirmedView(); LastPrimaryIssue.Resolve(Host, Services); break; // successful } catch (Exception e) { LastPrimaryIssue.Record(new ReadFromLogStorageFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); await LastPrimaryIssue.DelayBeforeRetry(); } exit_operation("ReadAsync"); } /// protected override async Task WriteAsync() { enter_operation("WriteAsync"); var updates = GetCurrentBatchOfUpdates(); bool batchsuccessfullywritten = false; var writebit = GlobalLog.StateAndMetaData.FlipBit(Services.MyClusterId); foreach (var x in updates) GlobalLog.StateAndMetaData.Log.Add(x.Entry); try { // for manual testing //await Task.Delay(5000); await globalGrainStorage.WriteStateAsync(grainTypeName, Services.GrainId, GlobalLog); batchsuccessfullywritten = true; Services.Log(LogLevel.Debug, "write ({0} updates) success {1}", updates.Length, GlobalLog); UpdateConfirmedView(); LastPrimaryIssue.Resolve(Host, Services); } catch (Exception e) { LastPrimaryIssue.Record(new UpdateLogStorageFailed() { Exception = e }, Host, Services); } if (!batchsuccessfullywritten) { Services.Log(LogLevel.Debug, "write apparently failed {0}", LastPrimaryIssue); while (true) // be stubborn until we can read what is there { await LastPrimaryIssue.DelayBeforeRetry(); try { await globalGrainStorage.ReadStateAsync(grainTypeName, Services.GrainId, GlobalLog); Services.Log(LogLevel.Debug, "read success {0}", GlobalLog); UpdateConfirmedView(); LastPrimaryIssue.Resolve(Host, Services); break; } catch (Exception e) { LastPrimaryIssue.Record(new ReadFromLogStorageFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); } // check if last apparently failed write was in fact successful if (writebit == GlobalLog.StateAndMetaData.GetBit(Services.MyClusterId)) { Services.Log(LogLevel.Debug, "last write ({0} updates) was actually a success {1}", updates.Length, GlobalLog); batchsuccessfullywritten = true; } } exit_operation("WriteAsync"); if (!batchsuccessfullywritten) return 0; return updates.Length; } /// /// Describes a connection issue that occurred when updating the primary storage. /// [Serializable] [GenerateSerializer] public sealed class UpdateLogStorageFailed : PrimaryOperationFailed { /// public override string ToString() { return $"write entire log to storage failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// Describes a connection issue that occurred when reading from the primary storage. /// [Serializable] [GenerateSerializer] public sealed class ReadFromLogStorageFailed : PrimaryOperationFailed { /// public override string ToString() { return $"read entire log from storage failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// A notification message sent to remote instances after updating this grain in storage. /// [Serializable] [GenerateSerializer] protected internal sealed class UpdateNotificationMessage : INotificationMessage { /// [Id(0)] public int Version { get; set; } /// The cluster that performed the update [Id(1)] public string Origin { get; set; } /// The list of updates that were applied [Id(2)] public List Updates { get; set; } /// The e-tag of the storage after applying the updates [Id(3)] public string ETag { get; set; } /// public override string ToString() { return string.Format("v{0} ({1} updates by {2}) etag={3}", Version, Updates.Count, Origin, ETag); } } /// protected override INotificationMessage Merge(INotificationMessage earlierMessage, INotificationMessage laterMessage) { var earlier = earlierMessage as UpdateNotificationMessage; var later = laterMessage as UpdateNotificationMessage; if (earlier != null && later != null && earlier.Origin == later.Origin && earlier.Version + later.Updates.Count == later.Version && earlier.Updates.Count + later.Updates.Count < maxEntriesInNotifications) return new UpdateNotificationMessage() { Version = later.Version, Origin = later.Origin, Updates = earlier.Updates.Concat(later.Updates).ToList(), ETag = later.ETag }; else return base.Merge(earlierMessage, laterMessage); // keep only the version number } private readonly SortedList notifications = new SortedList(); /// protected override void OnNotificationReceived(INotificationMessage payload) { var um = payload as UpdateNotificationMessage; if (um != null) notifications.Add(um.Version - um.Updates.Count, um); else base.OnNotificationReceived(payload); } /// protected override void ProcessNotifications() { // discard notifications that are behind our already confirmed state while (notifications.Count > 0 && notifications.ElementAt(0).Key < GlobalLog.StateAndMetaData.GlobalVersion) { Services.Log(LogLevel.Debug, "discarding notification {0}", notifications.ElementAt(0).Value); notifications.RemoveAt(0); } // process notifications that reflect next global version while (notifications.Count > 0 && notifications.ElementAt(0).Key == GlobalLog.StateAndMetaData.GlobalVersion) { var updateNotification = notifications.ElementAt(0).Value; notifications.RemoveAt(0); // append all operations in pending foreach (var u in updateNotification.Updates) GlobalLog.StateAndMetaData.Log.Add(u); GlobalLog.StateAndMetaData.FlipBit(updateNotification.Origin); GlobalLog.ETag = updateNotification.ETag; UpdateConfirmedView(); Services.Log(LogLevel.Debug, "notification success ({0} updates) {1}", updateNotification.Updates.Count, GlobalLog); } Services.Log(LogLevel.Trace, "unprocessed notifications in queue: {0}", notifications.Count); base.ProcessNotifications(); } #if DEBUG private bool operation_in_progress; #endif [Conditional("DEBUG")] private void enter_operation(string name) { #if DEBUG Services.Log(LogLevel.Trace, "/-- enter {0}", name); Debug.Assert(!operation_in_progress); operation_in_progress = true; #endif } [Conditional("DEBUG")] private void exit_operation(string name) { #if DEBUG Services.Log(LogLevel.Trace, "\\-- exit {0}", name); Debug.Assert(operation_in_progress); operation_in_progress = false; #endif } } } ================================================ FILE: src/Orleans.EventSourcing/Orleans.EventSourcing.csproj ================================================ Microsoft.Orleans.EventSourcing Microsoft Orleans Event-Sourcing Base types for creating Microsoft Orleans grains with event-sourced state. $(PackageTags) EventSourcing $(DefaultTargetFrameworks) true Orleans.EventSourcing OrleansEventSourcing README.md ================================================ FILE: src/Orleans.EventSourcing/README.md ================================================ # Microsoft Orleans Event Sourcing ## Introduction Microsoft Orleans Event Sourcing provides support for implementing event-sourced grains. Event sourcing is a pattern where state changes are recorded as a sequence of events rather than just storing the current state. This provides a complete history of changes and allows for powerful capabilities like replaying events, temporal querying, and more robust auditing. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.EventSourcing ``` ## Example - Creating an Event-Sourced Grain ```csharp using Orleans; using Orleans.EventSourcing; using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; // Define grain state and events namespace BankAccount; public class BankAccountState { public decimal Balance { get; set; } public string AccountHolder { get; set; } public int Version { get; set; } } public class DepositEvent { public decimal Amount { get; set; } } public class WithdrawalEvent { public decimal Amount { get; set; } } // Grain interface public interface IBankAccountGrain : IGrainWithStringKey { Task GetBalance(); Task Deposit(decimal amount); Task Withdraw(decimal amount); Task> GetHistory(); } // Event-sourced grain implementation using JournaledGrain public class BankAccountGrain : JournaledGrain, IBankAccountGrain { public async Task GetBalance() { // The state is automatically hydrated from the event log return State.Balance; } public async Task Deposit(decimal amount) { if (amount <= 0) throw new ArgumentException("Deposit amount must be positive"); // Record the event - this will be persisted and applied to state RaiseEvent(new DepositEvent { Amount = amount }); // Confirm the event is persisted await ConfirmEvents(); } public async Task Withdraw(decimal amount) { if (amount <= 0) throw new ArgumentException("Withdrawal amount must be positive"); if (State.Balance < amount) throw new InvalidOperationException("Insufficient funds"); // Record the event RaiseEvent(new WithdrawalEvent { Amount = amount }); // Confirm the event is persisted await ConfirmEvents(); } public Task> GetHistory() { // Return the complete history of events return Task.FromResult>(RetrieveConfirmedEvents(0, Version).ToList()); } // Event handlers to update the state based on events protected override void ApplyEvent(object @event) { switch (@event) { case DepositEvent deposit: State.Balance += deposit.Amount; break; case WithdrawalEvent withdrawal: State.Balance -= withdrawal.Amount; break; } } } ``` ## Example - Configuring Event Sourcing with Storage ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Orleans.EventSourcing; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure the log consistency provider for event sourcing .AddLogStorageBasedLogConsistencyProvider("LogStorage") // Configure a storage provider to store the events .AddMemoryGrainStorage("PubSubStore") .ConfigureServices(services => { // Configure default log consistency provider services.Configure(options => { options.DefaultLogConsistencyProvider = "LogStorage"; }); }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var bankAccount = client.GetGrain("account-123"); // Call grain methods await bankAccount.Deposit(100); await bankAccount.Withdraw(25); var balance = await bankAccount.GetBalance(); // Print the result Console.WriteLine($"Account balance: ${balance}"); var history = await bankAccount.GetHistory(); Console.WriteLine($"Transaction history: {history.Count} events"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Event Sourcing Overview](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/event-sourcing/overview) - [Journaled Grains](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/event-sourcing/journaled-grains) - [Replicated Grains](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/event-sourcing/replicated-grains) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.EventSourcing/StateStorage/DefaultAdaptorFactory.cs ================================================ using Orleans.Storage; namespace Orleans.EventSourcing.StateStorage { internal class DefaultAdaptorFactory : ILogViewAdaptorFactory { public bool UsesStorageProvider { get { return true; } } public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostgrain, T initialstate, string graintypename, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where T : class, new() where E : class { return new LogViewAdaptor(hostgrain, initialstate, grainStorage, graintypename, services); } } } ================================================ FILE: src/Orleans.EventSourcing/StateStorage/GrainStateWithMetaData.cs ================================================ using Orleans.EventSourcing.Common; using System; namespace Orleans.EventSourcing.StateStorage { /// /// A class that extends grain state with versioning metadata, so that a grain with log-view consistency /// can use a standard storage provider. /// /// The type used for log view [Serializable] [GenerateSerializer] public sealed class GrainStateWithMetaDataAndETag : IGrainState> where TView : class, new() { /// /// Gets and Sets StateAndMetaData /// [Id(0)] public GrainStateWithMetaData StateAndMetaData { get; set; } /// /// Gets and Sets Etag /// [Id(1)] public string ETag { get; set; } [Id(2)] public bool RecordExists { get; set; } public GrainStateWithMetaData State { get => StateAndMetaData; set => StateAndMetaData = value; } /// /// Initialize a new instance of GrainStateWithMetaDataAndETag class with an initialView /// public GrainStateWithMetaDataAndETag(TView initialview) { StateAndMetaData = new GrainStateWithMetaData(initialview); } /// /// Initializes a new instance of GrainStateWithMetaDataAndETag class /// public GrainStateWithMetaDataAndETag() { StateAndMetaData = new GrainStateWithMetaData(); } /// /// Convert current GrainStateWithMetaDataAndETag object information to a string /// public override string ToString() { return string.Format("v{0} Flags={1} ETag={2} Data={3}", StateAndMetaData.GlobalVersion, StateAndMetaData.WriteVector, ETag, StateAndMetaData.State); } } /// /// A class that extends grain state with versioning metadata, so that a log-consistent grain /// can use a standard storage provider. /// [Serializable] [GenerateSerializer] public sealed class GrainStateWithMetaData where TView : class, new() { /// /// The stored view of the log /// [Id(0)] public TView State { get; set; } /// /// The length of the log /// [Id(1)] public int GlobalVersion { get; set; } /// /// Metadata that is used to avoid duplicate appends. /// Logically, this is a (string->bit) map, the keys being replica ids /// But this map is represented compactly as a simple string to reduce serialization/deserialization overhead /// Bits are read by and flipped by . /// Bits are toggled when writing, so that the retry logic can avoid appending an entry twice /// when retrying a failed append. /// [Id(2)] public string WriteVector { get; set; } /// /// Initializes a new instance of the class. /// public GrainStateWithMetaData() { State = new TView(); GlobalVersion = 0; WriteVector = ""; } /// /// Initializes a new instance of the class. /// /// The initial state of the view public GrainStateWithMetaData(TView initialstate) { this.State = initialstate; GlobalVersion = 0; WriteVector = ""; } /// /// Gets one of the bits in /// /// The replica for which we want to look up the bit /// public bool GetBit(string Replica) { return StringEncodedWriteVector.GetBit(WriteVector, Replica); } /// /// toggle one of the bits in and return the new value. /// /// The replica for which we want to flip the bit /// the state of the bit after flipping it public bool FlipBit(string Replica) { var str = WriteVector; var rval = StringEncodedWriteVector.FlipBit(ref str, Replica); WriteVector = str; return rval; } } } ================================================ FILE: src/Orleans.EventSourcing/StateStorage/LogConsistencyProvider.cs ================================================ using Orleans.Storage; namespace Orleans.EventSourcing.StateStorage { /// /// A log-consistency provider that stores the latest view in primary storage, using any standard storage provider. /// Supports multiple clusters connecting to the same primary storage (doing optimistic concurrency control via e-tags) /// /// The log itself is transient, i.e. not actually saved to storage - only the latest view (snapshot) and some /// metadata (the log position, and write flags) are stored in the primary. /// /// public class LogConsistencyProvider : ILogViewAdaptorFactory { /// public bool UsesStorageProvider { get { return true; } } /// public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { return new LogViewAdaptor(hostGrain, initialState, grainStorage, grainTypeName, services); } } } ================================================ FILE: src/Orleans.EventSourcing/StateStorage/LogViewAdaptor.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Storage; using Orleans.EventSourcing.Common; namespace Orleans.EventSourcing.StateStorage { /// /// A log view adaptor that wraps around a traditional storage adaptor, and uses batching and e-tags /// to append entries. /// /// The log itself is transient, i.e. not actually saved to storage - only the latest view and some /// metadata (the log position, and write flags) are stored. /// /// /// Type of log view /// Type of log entry internal class LogViewAdaptor : PrimaryBasedLogViewAdaptor> where TLogView : class, new() where TLogEntry : class { /// /// Initialize a StorageProviderLogViewAdaptor class /// public LogViewAdaptor(ILogViewAdaptorHost host, TLogView initialState, IGrainStorage globalGrainStorage, string grainTypeName, ILogConsistencyProtocolServices services) : base(host, initialState, services) { this.globalGrainStorage = globalGrainStorage; this.grainTypeName = grainTypeName; } private const int maxEntriesInNotifications = 200; private readonly IGrainStorage globalGrainStorage; private readonly string grainTypeName; // stores the confirmed state including metadata private GrainStateWithMetaDataAndETag GlobalStateCache; /// protected override TLogView LastConfirmedView() { return GlobalStateCache.StateAndMetaData.State; } /// protected override int GetConfirmedVersion() { return GlobalStateCache.StateAndMetaData.GlobalVersion; } /// protected override void InitializeConfirmedView(TLogView initialstate) { GlobalStateCache = new GrainStateWithMetaDataAndETag(initialstate); } protected override Task ClearPrimaryLogAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return this.globalGrainStorage.ClearStateAsync(grainTypeName, Services.GrainId, GlobalStateCache); } // no special tagging is required, thus we create a plain submission entry /// protected override SubmissionEntry MakeSubmissionEntry(TLogEntry entry) { return new SubmissionEntry() { Entry = entry }; } /// protected override async Task ReadAsync() { enter_operation("ReadAsync"); while (true) { try { // for manual testing //await Task.Delay(5000); var readState = new GrainStateWithMetaDataAndETag(); await globalGrainStorage.ReadStateAsync(grainTypeName, Services.GrainId, readState); GlobalStateCache = readState; Services.Log(LogLevel.Debug, "read success {0}", GlobalStateCache); LastPrimaryIssue.Resolve(Host, Services); break; // successful } catch (Exception e) { LastPrimaryIssue.Record(new ReadFromStateStorageFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); await LastPrimaryIssue.DelayBeforeRetry(); } exit_operation("ReadAsync"); } /// protected override async Task WriteAsync() { enter_operation("WriteAsync"); var state = CopyTentativeState(); var updates = GetCurrentBatchOfUpdates(); bool batchsuccessfullywritten = false; var nextglobalstate = new GrainStateWithMetaDataAndETag(state); nextglobalstate.StateAndMetaData.WriteVector = GlobalStateCache.StateAndMetaData.WriteVector; nextglobalstate.StateAndMetaData.GlobalVersion = GlobalStateCache.StateAndMetaData.GlobalVersion + updates.Length; nextglobalstate.ETag = GlobalStateCache.ETag; var writebit = nextglobalstate.StateAndMetaData.FlipBit(Services.MyClusterId); try { // for manual testing //await Task.Delay(5000); await globalGrainStorage.WriteStateAsync(grainTypeName, Services.GrainId, nextglobalstate); batchsuccessfullywritten = true; GlobalStateCache = nextglobalstate; Services.Log(LogLevel.Debug, "write ({0} updates) success {1}", updates.Length, GlobalStateCache); LastPrimaryIssue.Resolve(Host, Services); } catch (Exception e) { LastPrimaryIssue.Record(new UpdateStateStorageFailed() { Exception = e }, Host, Services); } if (!batchsuccessfullywritten) { Services.Log(LogLevel.Debug, "write apparently failed {0} {1}", nextglobalstate, LastPrimaryIssue); while (true) // be stubborn until we can read what is there { await LastPrimaryIssue.DelayBeforeRetry(); try { await globalGrainStorage.ReadStateAsync(grainTypeName, Services.GrainId, GlobalStateCache); Services.Log(LogLevel.Debug, "read success {0}", GlobalStateCache); LastPrimaryIssue.Resolve(Host, Services); break; } catch (Exception e) { LastPrimaryIssue.Record(new ReadFromStateStorageFailed() { Exception = e }, Host, Services); } Services.Log(LogLevel.Debug, "read failed {0}", LastPrimaryIssue); } // check if last apparently failed write was in fact successful if (writebit == GlobalStateCache.StateAndMetaData.GetBit(Services.MyClusterId)) { GlobalStateCache = nextglobalstate; Services.Log(LogLevel.Debug, "last write ({0} updates) was actually a success {1}", updates.Length, GlobalStateCache); batchsuccessfullywritten = true; } } exit_operation("WriteAsync"); if (!batchsuccessfullywritten) return 0; return updates.Length; } /// /// Describes a connection issue that occurred when updating the primary storage. /// [Serializable] [GenerateSerializer] public sealed class UpdateStateStorageFailed : PrimaryOperationFailed { /// public override string ToString() { return $"write state to storage failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// Describes a connection issue that occurred when reading from the primary storage. /// [Serializable] [GenerateSerializer] public sealed class ReadFromStateStorageFailed : PrimaryOperationFailed { /// public override string ToString() { return $"read state from storage failed: caught {Exception.GetType().Name}: {Exception.Message}"; } } /// /// A notification message sent to remote instances after updating this grain in storage. /// [Serializable] [GenerateSerializer] protected internal sealed class UpdateNotificationMessage : INotificationMessage { /// [Id(0)] public int Version { get; set; } /// The cluster that performed the update [Id(1)] public string Origin { get; set; } /// The list of updates that were applied [Id(2)] public List Updates { get; set; } /// The e-tag of the storage after applying the updates [Id(3)] public string ETag { get; set; } /// public override string ToString() { return string.Format("v{0} ({1} updates by {2}) etag={3}", Version, Updates.Count, Origin, ETag); } } /// protected override INotificationMessage Merge(INotificationMessage earlierMessage, INotificationMessage laterMessage) { var earlier = earlierMessage as UpdateNotificationMessage; var later = laterMessage as UpdateNotificationMessage; if (earlier != null && later != null && earlier.Origin == later.Origin && earlier.Version + later.Updates.Count == later.Version && earlier.Updates.Count + later.Updates.Count < maxEntriesInNotifications) return new UpdateNotificationMessage() { Version = later.Version, Origin = later.Origin, Updates = earlier.Updates.Concat(later.Updates).ToList(), ETag = later.ETag }; else return base.Merge(earlierMessage, laterMessage); // keep only the version number } private readonly SortedList notifications = new SortedList(); /// protected override void OnNotificationReceived(INotificationMessage payload) { var um = payload as UpdateNotificationMessage; if (um != null) notifications.Add(um.Version - um.Updates.Count, um); else base.OnNotificationReceived(payload); } /// protected override void ProcessNotifications() { // discard notifications that are behind our already confirmed state while (notifications.Count > 0 && notifications.ElementAt(0).Key < GlobalStateCache.StateAndMetaData.GlobalVersion) { Services.Log(LogLevel.Debug, "discarding notification {0}", notifications.ElementAt(0).Value); notifications.RemoveAt(0); } // process notifications that reflect next global version while (notifications.Count > 0 && notifications.ElementAt(0).Key == GlobalStateCache.StateAndMetaData.GlobalVersion) { var updateNotification = notifications.ElementAt(0).Value; notifications.RemoveAt(0); // Apply all operations in pending foreach (var u in updateNotification.Updates) try { Host.UpdateView(GlobalStateCache.StateAndMetaData.State, u); } catch (Exception e) { Services.CaughtUserCodeException("UpdateView", nameof(ProcessNotifications), e); } GlobalStateCache.StateAndMetaData.GlobalVersion = updateNotification.Version; GlobalStateCache.StateAndMetaData.FlipBit(updateNotification.Origin); GlobalStateCache.ETag = updateNotification.ETag; Services.Log(LogLevel.Debug, "notification success ({0} updates) {1}", updateNotification.Updates.Count, GlobalStateCache); } Services.Log(LogLevel.Trace, "unprocessed notifications in queue: {0}", notifications.Count); base.ProcessNotifications(); } #if DEBUG private bool operation_in_progress; #endif [Conditional("DEBUG")] private void enter_operation(string name) { #if DEBUG Services.Log(LogLevel.Trace, "/-- enter {0}", name); Debug.Assert(!operation_in_progress); operation_in_progress = true; #endif } [Conditional("DEBUG")] private void exit_operation(string name) { #if DEBUG Services.Log(LogLevel.Trace, "\\-- exit {0}", name); Debug.Assert(operation_in_progress); operation_in_progress = false; #endif } } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/ConfigureKubernetesHostingOptions.cs ================================================ #nullable enable using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; namespace Orleans.Hosting.Kubernetes { internal class ConfigureKubernetesHostingOptions : IConfigureOptions, IConfigureOptions, IPostConfigureOptions, IConfigureOptions { private readonly IServiceProvider _serviceProvider; public ConfigureKubernetesHostingOptions(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void Configure(KubernetesHostingOptions options) { options.Namespace ??= Environment.GetEnvironmentVariable(KubernetesHostingOptions.PodNamespaceEnvironmentVariable) ?? ReadNamespaceFromServiceAccount(); options.PodName ??= Environment.GetEnvironmentVariable(KubernetesHostingOptions.PodNameEnvironmentVariable) ?? Environment.MachineName; options.PodIP ??= Environment.GetEnvironmentVariable(KubernetesHostingOptions.PodIPEnvironmentVariable); } public void Configure(ClusterOptions options) { var serviceIdEnvVar = Environment.GetEnvironmentVariable(KubernetesHostingOptions.ServiceIdEnvironmentVariable); if (!string.IsNullOrWhiteSpace(serviceIdEnvVar)) { options.ServiceId = serviceIdEnvVar; } var clusterIdEnvVar = Environment.GetEnvironmentVariable(KubernetesHostingOptions.ClusterIdEnvironmentVariable); if (!string.IsNullOrWhiteSpace(clusterIdEnvVar)) { options.ClusterId = clusterIdEnvVar; } } public void Configure(SiloOptions options) { var hostingOptions = _serviceProvider.GetRequiredService>().Value; if (!string.IsNullOrWhiteSpace(hostingOptions.PodName)) { options.SiloName = hostingOptions.PodName; } } public void PostConfigure(string? name, EndpointOptions options) { // Use PostConfigure to give the developer an opportunity to set SiloPort and GatewayPort using regular // Configure methods without needing to worry about ordering with respect to the UseKubernetesHosting call. if (options.AdvertisedIPAddress is null) { var hostingOptions = _serviceProvider.GetRequiredService>().Value; IPAddress? podIp = null; if (hostingOptions.PodIP is not null) { podIp = IPAddress.Parse(hostingOptions.PodIP); } else { var hostAddresses = Dns.GetHostAddresses(hostingOptions.PodName); if (hostAddresses != null) { podIp = IPAddressSelector.PickIPAddress(hostAddresses); } } if (podIp is not null) { options.AdvertisedIPAddress = podIp; } } if (options.SiloListeningEndpoint is null) { options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, options.SiloPort); } if (options.GatewayListeningEndpoint is null && options.GatewayPort > 0) { options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, options.GatewayPort); } } private static string? ReadNamespaceFromServiceAccount() { // Read the namespace from the pod's service account. var serviceAccountNamespacePath = Path.Combine($"{Path.DirectorySeparatorChar}var", "run", "secrets", "kubernetes.io", "serviceaccount", "namespace"); if (File.Exists(serviceAccountNamespacePath)) { return File.ReadAllText(serviceAccountNamespacePath).Trim(); } return null; } private static class IPAddressSelector { // IANA private IPv4 addresses private static readonly (IPAddress Address, IPAddress SubnetMask)[] PreferredRanges = new [] { (IPAddress.Parse("192.168.0.0"), IPAddress.Parse("255.255.0.0")), (IPAddress.Parse("10.0.0.0"), IPAddress.Parse("255.0.0.0")), (IPAddress.Parse("172.16.0.0"), IPAddress.Parse("255.240.0.0")), }; public static IPAddress? PickIPAddress(IReadOnlyList candidates) { IPAddress? chosen = null; foreach (var address in candidates) { if (chosen is null) { chosen = address; } else { if (CompareIPAddresses(address, chosen)) { chosen = address; } } } return chosen; // returns true if lhs is "less" (in some repeatable sense) than rhs static bool CompareIPAddresses(IPAddress lhs, IPAddress rhs) { var lhsBytes = lhs.GetAddressBytes(); var rhsBytes = rhs.GetAddressBytes(); if (lhsBytes.Length != rhsBytes.Length) { return lhsBytes.Length < rhsBytes.Length; } // Prefer IANA private IPv4 address ranges 10.x.x.x, 192.168.x.x, 172.16-31.x.x over other addresses. if (lhs.AddressFamily is AddressFamily.InterNetwork && rhs.AddressFamily is AddressFamily.InterNetwork) { var lhsPref = GetPreferredSubnetRank(lhs); var rhsPref = GetPreferredSubnetRank(rhs); if (lhsPref != rhsPref) { return lhsPref < rhsPref; } } // Compare starting from most significant octet. // 10.68.20.21 < 10.98.05.04 return lhsBytes.AsSpan().SequenceCompareTo(rhsBytes.AsSpan()) < 0; } static int GetPreferredSubnetRank(IPAddress ip) { var ipBytes = ip.GetAddressBytes(); Span masked = stackalloc byte[ipBytes.Length]; var i = 0; foreach (var (Address, SubnetMask) in PreferredRanges) { ipBytes.CopyTo(masked); var subnetMaskBytes = SubnetMask.GetAddressBytes(); if (ipBytes.Length != subnetMaskBytes.Length) { continue; } And(ipBytes, subnetMaskBytes, masked); if (masked.SequenceEqual(Address.GetAddressBytes())) { return i; } ++i; } return PreferredRanges.Length; static void And(ReadOnlySpan lhs, ReadOnlySpan rhs, Span result) { Debug.Assert(lhs.Length == rhs.Length); Debug.Assert(lhs.Length == result.Length); for (var i = 0; i < lhs.Length; i++) { result[i] = (byte)(lhs[i] & rhs[i]); } } } } } } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/KubernetesClusterAgent.cs ================================================ using k8s; using k8s.Autorest; using k8s.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; namespace Orleans.Hosting.Kubernetes { /// /// Reflects cluster configuration changes between Orleans and Kubernetes. /// public sealed partial class KubernetesClusterAgent : ILifecycleParticipant { private const string ExampleRoleBinding = """ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pod-updater rules: - apiGroups: [ "" ] resources: ["pods"] verbs: ["get", "watch", "list", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: pod-updater-binding subjects: - kind: ServiceAccount name: default apiGroup: '' roleRef: kind: Role name: pod-updater apiGroup: '' """; private readonly IOptionsMonitor _options; private readonly ClusterOptions _clusterOptions; private readonly IClusterMembershipService _clusterMembershipService; private readonly KubernetesClientConfiguration _config; private readonly k8s.Kubernetes _client; private readonly string _podLabelSelector; private readonly string _podNamespace; private readonly string _podName; private readonly ILocalSiloDetails _localSiloDetails; private readonly ILogger _logger; private readonly CancellationTokenSource _shutdownToken; private readonly SemaphoreSlim _pauseMonitoringSemaphore = new SemaphoreSlim(0); private volatile bool _enableMonitoring; private Task _runTask; public KubernetesClusterAgent( IClusterMembershipService clusterMembershipService, ILogger logger, IOptionsMonitor options, IOptions clusterOptions, ILocalSiloDetails localSiloDetails) { _localSiloDetails = localSiloDetails; _logger = logger; _shutdownToken = new CancellationTokenSource(); _options = options; _clusterOptions = clusterOptions.Value; _clusterMembershipService = clusterMembershipService; _config = _options.CurrentValue.GetClientConfiguration?.Invoke() ?? throw new ArgumentNullException(nameof(KubernetesHostingOptions) + "." + nameof(KubernetesHostingOptions.GetClientConfiguration)); _client = new k8s.Kubernetes(_config); _podLabelSelector = $"{KubernetesHostingOptions.ServiceIdLabel}={_clusterOptions.ServiceId},{KubernetesHostingOptions.ClusterIdLabel}={_clusterOptions.ClusterId}"; _podNamespace = _options.CurrentValue.Namespace; _podName = _options.CurrentValue.PodName; } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(KubernetesClusterAgent), ServiceLifecycleStage.AfterRuntimeGrainServices, OnStart, OnStop); } private async Task OnStart(CancellationToken cancellation) { var attempts = 0; while (!cancellation.IsCancellationRequested) { try { await AddClusterOptionsToPodLabels(cancellation); // Find the currently known cluster members first, before interrogating Kubernetes await _clusterMembershipService.Refresh(); var snapshot = _clusterMembershipService.CurrentSnapshot.Members; // Find the pods which correspond to this cluster var pods = await _client.ListNamespacedPodAsync( namespaceParameter: _podNamespace, labelSelector: _podLabelSelector, cancellationToken: cancellation); var clusterPods = new HashSet { _podName }; foreach (var pod in pods.Items) { clusterPods.Add(pod.Metadata.Name); } var known = new HashSet(); var knownMap = new Dictionary(); known.Add(_podName); foreach (var member in snapshot.Values) { if (member.Status == SiloStatus.Dead) { continue; } known.Add(member.Name); knownMap[member.Name] = member; } var unknownPods = new List(clusterPods.Except(known)); unknownPods.Sort(); foreach (var pod in unknownPods) { LogWarningUnknownPod(pod); // Delete the pod once it has been active long enough? } var unmatched = new List(known.Except(clusterPods)); unmatched.Sort(); foreach (var pod in unmatched) { var siloAddress = knownMap[pod]; if (siloAddress.Status is not SiloStatus.Active) { continue; } LogWarningSiloWithoutPod(siloAddress); await _clusterMembershipService.TryKill(siloAddress.SiloAddress); } break; } catch (HttpOperationException exception) when (exception.Response.StatusCode is System.Net.HttpStatusCode.Forbidden) { LogErrorInsufficientPermissions(exception); } catch (Exception exception) { LogErrorInitializing(exception); if (++attempts > _options.CurrentValue.MaxKubernetesApiRetryAttempts) { throw; } await Task.Delay(1000, cancellation); } } // Start monitoring loop ThreadPool.UnsafeQueueUserWorkItem(_ => _runTask = Task.WhenAll(Task.Run(MonitorOrleansClustering), Task.Run(MonitorKubernetesPods)), null); } private async Task AddClusterOptionsToPodLabels(CancellationToken cancellation) { // Propagate our configured cluster membership options to our pod var thisPod = await _client.ReadNamespacedPodAsync(_podName, namespaceParameter: _podNamespace, cancellationToken: cancellation); var labels = thisPod.Labels(); if (labels is null || !labels.TryGetValue(KubernetesHostingOptions.ServiceIdLabel, out var sidVal) || !string.Equals(_clusterOptions.ServiceId, sidVal, StringComparison.Ordinal) || !labels.TryGetValue(KubernetesHostingOptions.ClusterIdLabel, out var cidVal) || !string.Equals(_clusterOptions.ClusterId, cidVal, StringComparison.Ordinal)) { var patch = $$""" { "metadata": { "labels": { "{{KubernetesHostingOptions.ClusterIdLabel}}": "{{_clusterOptions.ClusterId}}", "{{KubernetesHostingOptions.ServiceIdLabel}}": "{{_clusterOptions.ServiceId}}" } } } """; await _client.PatchNamespacedPodAsync(new V1Patch(patch, V1Patch.PatchType.MergePatch), _podName, _podNamespace, cancellationToken: cancellation); } } public async Task OnStop(CancellationToken cancellationToken) { _shutdownToken.Cancel(); _enableMonitoring = false; _pauseMonitoringSemaphore.Release(); if (_runTask is not null) { await Task.WhenAny(_runTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)); } } private async Task MonitorOrleansClustering() { var previous = _clusterMembershipService.CurrentSnapshot; while (!_shutdownToken.IsCancellationRequested) { try { await foreach (var update in _clusterMembershipService.MembershipUpdates.WithCancellation(_shutdownToken.Token)) { // Determine which silos should be monitoring Kubernetes var chosenSilos = _clusterMembershipService.CurrentSnapshot.Members.Values .Where(s => s.Status == SiloStatus.Active) .OrderBy(s => s.SiloAddress) .Take(_options.CurrentValue.MaxAgents) .ToList(); if (!_enableMonitoring && chosenSilos.Exists(s => s.SiloAddress.Equals(_localSiloDetails.SiloAddress))) { _enableMonitoring = true; _pauseMonitoringSemaphore.Release(1); } else if (_enableMonitoring) { _enableMonitoring = false; } if (_enableMonitoring && _options.CurrentValue.DeleteDefunctSiloPods) { var delta = update.CreateUpdate(previous); foreach (var change in delta.Changes) { if (change.SiloAddress.Equals(_localSiloDetails.SiloAddress)) { // Ignore all changes for this silo continue; } if (change.Status == SiloStatus.Dead) { try { LogInformationDeletingDeadSiloPod(change.SiloAddress, change.Name, _podNamespace); await _client.DeleteNamespacedPodAsync(change.Name, _podNamespace); } catch (Exception exception) { // Ignore NotFound errors, as the pod may have already been deleted by other means if (exception is not HttpOperationException { Response.StatusCode: HttpStatusCode.NotFound }) { LogErrorDeletingPod(exception, change.Name, _podNamespace, change.SiloAddress); } } } } } previous = update; } } catch (OperationCanceledException) when (_shutdownToken.IsCancellationRequested) { } catch (Exception exception) { LogDebugErrorMonitoringCluster(exception); if (!_shutdownToken.IsCancellationRequested) { await Task.Delay(5000); } } } } private async Task MonitorKubernetesPods() { while (!_shutdownToken.IsCancellationRequested) { try { if (!_enableMonitoring) { // Wait on the semaphore to avoid spinning in a tight loop. await _pauseMonitoringSemaphore.WaitAsync(); continue; } if (_shutdownToken.IsCancellationRequested) { break; } var pods = _client.CoreV1.WatchListNamespacedPodAsync( namespaceParameter: _podNamespace, labelSelector: _podLabelSelector, cancellationToken: _shutdownToken.Token); await foreach (var (eventType, pod) in pods.WithCancellation(_shutdownToken.Token)) { if (!_enableMonitoring || _shutdownToken.IsCancellationRequested) { break; } if (string.Equals(pod.Metadata.Name, _podName, StringComparison.Ordinal)) { // Never declare ourselves dead this way. continue; } if (eventType == WatchEventType.Modified) { // TODO: Remember silo addresses for pods that are restarting/terminating } if (eventType == WatchEventType.Deleted) { if (this.TryMatchSilo(pod, out var member) && member.Status != SiloStatus.Dead) { LogInformationDeclaringServerDead(member.SiloAddress, pod.Metadata.Name); await _clusterMembershipService.TryKill(member.SiloAddress); } } } if (_enableMonitoring && !_shutdownToken.IsCancellationRequested) { LogDebugUnexpectedEndOfStream(); await Task.Delay(5000); } } catch (Exception exception) when (!(_shutdownToken.IsCancellationRequested && exception is OperationCanceledException)) { LogErrorMonitoringPods(exception); if (!_shutdownToken.IsCancellationRequested) { await Task.Delay(5000); } } } } private bool TryMatchSilo(V1Pod pod, out ClusterMember server) { var snapshot = _clusterMembershipService.CurrentSnapshot; foreach (var member in snapshot.Members) { if (string.Equals(member.Value.Name, pod.Metadata.Name, StringComparison.Ordinal)) { server = member.Value; return true; } } server = default; return false; } [LoggerMessage( Level = LogLevel.Warning, Message = "Pod {PodName} does not correspond to any known silos" )] private partial void LogWarningUnknownPod(string podName); [LoggerMessage( Level = LogLevel.Warning, Message = "Silo {SiloAddress} does not correspond to any known pod. Marking it as dead." )] private partial void LogWarningSiloWithoutPod(ClusterMember siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = $"Unable to monitor pods due to insufficient permissions. Ensure that this pod has an appropriate Kubernetes role binding. Here is an example role binding:\n{ExampleRoleBinding}" )] private partial void LogErrorInsufficientPermissions(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error while initializing Kubernetes cluster agent" )] private partial void LogErrorInitializing(Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "Silo {SiloAddress} is dead, proceeding to delete the corresponding pod, {PodName}, in namespace {PodNamespace}" )] private partial void LogInformationDeletingDeadSiloPod(SiloAddress siloAddress, string podName, string podNamespace); [LoggerMessage( Level = LogLevel.Error, Message = "Error deleting pod {PodName} in namespace {PodNamespace} corresponding to defunct silo {SiloAddress}" )] private partial void LogErrorDeletingPod(Exception exception, string podName, string podNamespace, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Error while monitoring cluster changes" )] private partial void LogDebugErrorMonitoringCluster(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Unexpected end of stream from Kubernetes API. Will try again." )] private partial void LogDebugUnexpectedEndOfStream(); [LoggerMessage( Level = LogLevel.Error, Message = "Error monitoring Kubernetes pods" )] private partial void LogErrorMonitoringPods(Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "Declaring server {Silo} dead since its corresponding pod, {Pod}, has been deleted" )] private partial void LogInformationDeclaringServerDead(SiloAddress silo, string pod); } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/KubernetesHostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting.Kubernetes; using Orleans.Runtime; using System; namespace Orleans.Hosting { /// /// Extensions for hosting a silo in Kubernetes. /// public static class KubernetesHostingExtensions { /// /// Adds Kubernetes hosting support. /// public static ISiloBuilder UseKubernetesHosting(this ISiloBuilder siloBuilder) { return siloBuilder.ConfigureServices(services => services.UseKubernetesHosting(configureOptions: null)); } /// /// Adds Kubernetes hosting support. /// public static ISiloBuilder UseKubernetesHosting(this ISiloBuilder siloBuilder, Action> configureOptions) { return siloBuilder.ConfigureServices(services => services.UseKubernetesHosting(configureOptions)); } /// /// Adds Kubernetes hosting support. /// public static IServiceCollection UseKubernetesHosting(this IServiceCollection services) => services.UseKubernetesHosting(configureOptions: null); /// /// Adds Kubernetes hosting support. /// public static IServiceCollection UseKubernetesHosting(this IServiceCollection services, Action> configureOptions) { configureOptions?.Invoke(services.AddOptions()); // Configure defaults based on the current environment. services.AddSingleton, ConfigureKubernetesHostingOptions>(); services.AddSingleton, ConfigureKubernetesHostingOptions>(); services.AddSingleton, ConfigureKubernetesHostingOptions>(); services.AddSingleton, ConfigureKubernetesHostingOptions>(); services.AddSingleton, KubernetesHostingOptionsValidator>(); services.AddSingleton, KubernetesClusterAgent>(); return services; } } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/KubernetesHostingOptions.cs ================================================ using k8s; using System; namespace Orleans.Hosting.Kubernetes { /// /// Options for hosting in Kubernetes. /// public sealed class KubernetesHostingOptions { private readonly Lazy _clientConfiguration; /// /// The environment variable for specifying the Kubernetes namespace which all silos in this cluster belong to. /// public const string PodNamespaceEnvironmentVariable = "POD_NAMESPACE"; /// /// The environment variable for specifying the name of the Kubernetes pod which this silo is executing in. /// public const string PodNameEnvironmentVariable = "POD_NAME"; /// /// The environment variable for specifying the IP address of this pod. /// public const string PodIPEnvironmentVariable = "POD_IP"; /// /// The environment variable for specifying . /// public const string ClusterIdEnvironmentVariable = "ORLEANS_CLUSTER_ID"; /// /// The environment variable for specifying . /// public const string ServiceIdEnvironmentVariable = "ORLEANS_SERVICE_ID"; /// /// The name of the label on the pod. /// public const string ServiceIdLabel = "orleans/serviceId"; /// /// The name of the label on the pod. /// public const string ClusterIdLabel = "orleans/clusterId"; public KubernetesHostingOptions() { _clientConfiguration = new Lazy(() => this.GetClientConfiguration()); } /// /// Gets the client configuration. /// internal KubernetesClientConfiguration ClientConfiguration => _clientConfiguration.Value; /// /// The delegate used to get an instance of . /// public Func GetClientConfiguration { get; set; } = KubernetesClientConfiguration.InClusterConfig; /// /// The number of silos in the cluster which should monitor Kubernetes. /// /// /// Setting this to a small number can reduce the load on the Kubernetes API server. /// public int MaxAgents { get; set; } = 2; /// /// Gets or sets the maximum number of attempts to retry Kubernetes API calls. /// public int MaxKubernetesApiRetryAttempts { get; set; } = 10; /// /// Whether or not to delete pods which correspond to silos which have become defunct since this silo became active. /// public bool DeleteDefunctSiloPods { get; set; } = false; /// /// The Kubernetes namespace which this silo and all other silos belong to. /// internal string Namespace { get; set; } /// /// The name of the Kubernetes pod which this silo is executing in. /// internal string PodName { get; set; } /// /// The PodIP of the Kubernetes pod which this silo is executing in. /// internal string PodIP { get; set; } } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/KubernetesHostingOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; using System.Collections.Generic; namespace Orleans.Hosting.Kubernetes { /// /// Validates . /// internal class KubernetesHostingOptionsValidator : IValidateOptions { public ValidateOptionsResult Validate(string name, KubernetesHostingOptions options) { List failures = default; if (string.IsNullOrWhiteSpace(options.Namespace)) { failures ??= new List(); failures.Add($"{nameof(KubernetesHostingOptions)}.{nameof(KubernetesHostingOptions.Namespace)} is not set. Set it via the {KubernetesHostingOptions.PodNamespaceEnvironmentVariable} environment variable"); } if (string.IsNullOrWhiteSpace(options.PodName)) { failures ??= new List(); failures.Add($"{nameof(KubernetesHostingOptions)}.{nameof(KubernetesHostingOptions.PodName)} is not set. Set it via the {KubernetesHostingOptions.PodNameEnvironmentVariable} environment variable"); } if (failures is not null) return ValidateOptionsResult.Fail(failures); return ValidateOptionsResult.Success; } } } ================================================ FILE: src/Orleans.Hosting.Kubernetes/Orleans.Hosting.Kubernetes.csproj ================================================ Microsoft.Orleans.Hosting.Kubernetes Microsoft Orleans Hosting for Kubernetes Microsoft Orleans hosting support for Kubernetes $(PackageTags) Kubernetes k8s $(DefaultTargetFrameworks) true ================================================ FILE: src/Orleans.Journaling/DurableDictionary.cs ================================================ using System.Buffers; using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableDictionary : IDictionary where K : notnull { } [DebuggerTypeProxy(typeof(IDurableDictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] internal class DurableDictionary : IDurableDictionary, IDurableStateMachine where K : notnull { private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _keyCodec; private readonly IFieldCodec _valueCodec; private const byte VersionByte = 0; private readonly Dictionary _items = []; private IStateMachineLogWriter? _storage; protected DurableDictionary(IFieldCodec keyCodec, IFieldCodec valueCodec, SerializerSessionPool serializerSessionPool) { _keyCodec = keyCodec; _valueCodec = valueCodec; _serializerSessionPool = serializerSessionPool; } public DurableDictionary([ServiceKey] string key, IStateMachineManager manager, IFieldCodec keyCodec, IFieldCodec valueCodec, SerializerSessionPool serializerSessionPool) : this(keyCodec, valueCodec, serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); manager.RegisterStateMachine(key, this); } public V this[K key] { get => _items[key]; set { ApplySet(key, value); AppendSet(key, value); } } public int Count => _items.Count; public ICollection Keys => _items.Keys; public ICollection Values => _items.Values; public bool IsReadOnly => ((ICollection>)_items).IsReadOnly; void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { _items.Clear(); _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableDictionary)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.Set: ApplySet(ReadKey(ref reader), ReadValue(ref reader)); break; case CommandType.Remove: ApplyRemove(ReadKey(ref reader)); break; case CommandType.Clear: ApplyClear(); break; case CommandType.Snapshot: ApplySnapshot(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] K ReadKey(ref Reader reader) { var field = reader.ReadFieldHeader(); return _keyCodec.ReadValue(ref reader, field); } [MethodImpl(MethodImplOptions.AggressiveInlining)] V ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _valueCodec.ReadValue(ref reader, field); } void ApplySnapshot(ref Reader reader) { var count = (int)reader.ReadVarUInt32(); _items.Clear(); _items.EnsureCapacity(count); for (var i = 0; i < count; i++) { var key = ReadKey(ref reader); var value = ReadValue(ref reader); ApplySet(key, value); } } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { // This state machine implementation appends log entries as the data structure is modified, so there is no need to perform separate writing here. } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { snapshotWriter.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Snapshot); writer.WriteVarUInt32((uint)self._items.Count); foreach (var (key, value) in self._items) { self._keyCodec.WriteField(ref writer, 0, typeof(K), key); self._valueCodec.WriteField(ref writer, 0, typeof(V), value); } writer.Commit(); }, this); } public void Clear() { ApplyClear(); GetStorage().AppendEntry(static (state, bufferWriter) => { using var session = state._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Clear); writer.Commit(); }, this); } public bool Contains(K key) => _items.ContainsKey(key); public bool Remove(K key) { if (ApplyRemove(key)) { AppendRemove(key); return true; } return false; } private void AppendRemove(K key) { GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, key) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Remove); self._keyCodec.WriteField(ref writer, 0, typeof(K), key); writer.Commit(); }, (this, key)); } IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); private void AppendSet(K key, V value) { GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, key, value) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Set); self._keyCodec.WriteField(ref writer, 0, typeof(K), key); self._valueCodec.WriteField(ref writer, 1, typeof(V), value); writer.Commit(); }, (this, key, value)); } protected virtual void OnSet(K key, V value) { } private void ApplySet(K key, V value) { _items[key] = value; OnSet(key, value); } internal bool ApplyRemove(K key) => _items.Remove(key); private void ApplyClear() => _items.Clear(); protected virtual IStateMachineLogWriter GetStorage() { Debug.Assert(_storage is not null); return _storage; } public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); public void Add(K key, V value) { _items.Add(key, value); OnSet(key, value); AppendSet(key, value); } public bool ContainsKey(K key) => _items.ContainsKey(key); public bool TryGetValue(K key, [MaybeNullWhen(false)] out V value) => _items.TryGetValue(key, out value); public void Add(KeyValuePair item) => Add(item.Key, item.Value); public bool Contains(KeyValuePair item) => _items.Contains(item); public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)_items).CopyTo(array, arrayIndex); public bool Remove(KeyValuePair item) { if (((ICollection>)_items).Remove(item)) { AppendRemove(item.Key); return true; } return false; } public IEnumerator> GetEnumerator() => ((IEnumerable>)_items).GetEnumerator(); private enum CommandType { Set = 0, Remove = 1, Clear = 2, Snapshot = 3 } } [DebuggerDisplay("{Value}", Name = "[{Key}]")] internal readonly struct DebugViewDictionaryItem { public DebugViewDictionaryItem(TKey key, TValue value) { Key = key; Value = value; } public DebugViewDictionaryItem(KeyValuePair keyValue) { Key = keyValue.Key; Value = keyValue.Value; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public TKey Key { get; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] public TValue Value { get; } } internal sealed class IDurableDictionaryDebugView where TKey : notnull { private readonly IDurableDictionary _dict; public IDurableDictionaryDebugView(IDurableDictionary dictionary) { ArgumentNullException.ThrowIfNull(dictionary); _dict = dictionary; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public DebugViewDictionaryItem[] Items { get { var keyValuePairs = new KeyValuePair[_dict.Count]; _dict.CopyTo(keyValuePairs, 0); var items = new DebugViewDictionaryItem[keyValuePairs.Length]; for (int i = 0; i < items.Length; i++) { items[i] = new DebugViewDictionaryItem(keyValuePairs[i]); } return items; } } } ================================================ FILE: src/Orleans.Journaling/DurableGrain.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace Orleans.Journaling; public abstract class DurableGrain : Grain, IGrainBase { protected DurableGrain() { StateMachineManager = ServiceProvider.GetRequiredService(); if (StateMachineManager is ILifecycleParticipant participant) { participant.Participate(((IGrainBase)this).GrainContext.ObservableLifecycle); } } protected IStateMachineManager StateMachineManager { get; } protected TStateMachine GetOrCreateStateMachine(string name) where TStateMachine : class, IDurableStateMachine => GetOrCreateStateMachine(name, static sp => sp.GetRequiredService(), ServiceProvider); protected TStateMachine GetOrCreateStateMachine(string name, Func createStateMachine, TState state) where TStateMachine : class, IDurableStateMachine { if (StateMachineManager.TryGetStateMachine(name, out var stateMachine)) { return stateMachine as TStateMachine ?? throw new InvalidOperationException($"A state machine named '{name}' already exists with an incompatible type {stateMachine.GetType()} versus {typeof(TStateMachine)}"); } var result = createStateMachine(state); StateMachineManager.RegisterStateMachine(name, result); return result; } protected ValueTask WriteStateAsync(CancellationToken cancellationToken = default) => StateMachineManager.WriteStateAsync(cancellationToken); } ================================================ FILE: src/Orleans.Journaling/DurableList.cs ================================================ using System.Buffers; using System.Collections; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableList : IList { void AddRange(IEnumerable collection); ReadOnlyCollection AsReadOnly(); } [DebuggerTypeProxy(typeof(IDurableCollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] internal sealed class DurableList : IDurableList, IDurableStateMachine { private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private const byte VersionByte = 0; private readonly List _items = []; private IStateMachineLogWriter? _storage; public DurableList([ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); } public T this[int index] { get => _items[index]; set { if ((uint)index >= (uint)_items.Count) { ThrowIndexOutOfRange(); } ApplySet(index, value); GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, index, value) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Set); writer.WriteVarUInt32((uint)index); self._codec.WriteField(ref writer, 0, typeof(T), value!); writer.Commit(); }, (this, index, value)); } } public int Count => _items.Count; bool ICollection.IsReadOnly => false; void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { _items.Clear(); _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableList)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.Add: ApplyAdd(ReadValue(ref reader)); break; case CommandType.Set: { var index = (int)reader.ReadVarUInt32(); var value = ReadValue(ref reader); ApplySet(index, value); } break; case CommandType.Insert: { var index = (int)reader.ReadVarUInt32(); var value = ReadValue(ref reader); ApplyInsert(index, value); } break; case CommandType.Remove: ApplyRemoveAt((int)reader.ReadVarUInt32()); break; case CommandType.Clear: ApplyClear(); break; case CommandType.Snapshot: ApplySnapshot(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] T ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } void ApplySnapshot(ref Reader reader) { var count = reader.ReadVarUInt32(); ApplyClear(); _items.EnsureCapacity((int)count); for (var i = 0; i < count; i++) { ApplyAdd(ReadValue(ref reader)); } } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { // This state machine implementation appends log entries as the data structure is modified, so there is no need to perform separate writing here. } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { snapshotWriter.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Snapshot); writer.WriteVarUInt32((uint)self._items.Count); foreach (var item in self._items) { self._codec.WriteField(ref writer, 0, typeof(T), item); } writer.Commit(); }, this); } public void Add(T item) { ApplyAdd(item); GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, item) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Add); self._codec.WriteField(ref writer, 0, typeof(T), item!); writer.Commit(); }, (this, item)); } public void Clear() { ApplyClear(); GetStorage().AppendEntry(static (state, bufferWriter) => { using var session = state._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Clear); writer.Commit(); }, this); } public bool Contains(T item) => _items.Contains(item); public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => _items.GetEnumerator(); public int IndexOf(T item) => _items.IndexOf(item); public void Insert(int index, T item) { ApplyInsert(index, item); GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, index, value) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Insert); writer.WriteVarUInt32((uint)index); self._codec.WriteField(ref writer, 0, typeof(T), value!); writer.Commit(); }, (this, index, item)); } public bool Remove(T item) { var index = _items.IndexOf(item); if (index >= 0) { RemoveAt(index); return true; } return false; } public void RemoveAt(int index) { ApplyRemoveAt(index); GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, index) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Remove); writer.WriteVarUInt32((uint)index); writer.Commit(); }, (this, index)); } IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); protected void ApplyAdd(T item) => _items.Add(item); protected void ApplySet(int index, T item) => _items[index] = item; protected void ApplyInsert(int index, T item) => _items.Insert(index, item); protected void ApplyRemoveAt(int index) => _items.RemoveAt(index); protected void ApplyClear() => _items.Clear(); [DoesNotReturn] private static void ThrowIndexOutOfRange() => throw new ArgumentOutOfRangeException("index", "Index was out of range. Must be non-negative and less than the size of the collection"); private IStateMachineLogWriter GetStorage() { Debug.Assert(_storage is not null); return _storage; } public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); public void AddRange(IEnumerable collection) { foreach (var element in collection) { Add(element); } } public ReadOnlyCollection AsReadOnly() => _items.AsReadOnly(); private enum CommandType { Add = 0, Set = 1, Insert = 2, Remove = 3, Clear = 4, Snapshot = 5 } } internal sealed class IDurableCollectionDebugView { private readonly ICollection _collection; public IDurableCollectionDebugView(ICollection collection) { #if NET ArgumentNullException.ThrowIfNull(collection); #else if (collection is null) { throw new ArgumentNullException(nameof(collection)); } #endif _collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { T[] items = new T[_collection.Count]; _collection.CopyTo(items, 0); return items; } } } ================================================ FILE: src/Orleans.Journaling/DurableNothing.cs ================================================ using System.Buffers; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Journaling; /// /// A durable object which does nothing, used for retiring other durable types. /// public interface IDurableNothing { } /// /// A durable object which does nothing, used for retiring other durable types. /// internal sealed class DurableNothing : IDurableNothing, IDurableStateMachine { public DurableNothing([ServiceKey] string key, IStateMachineManager manager) { ArgumentNullException.ThrowIfNullOrEmpty(key); manager.RegisterStateMachine(key, this); } void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { } public IDurableStateMachine DeepCopy() => this; } ================================================ FILE: src/Orleans.Journaling/DurableQueue.cs ================================================ using System.Buffers; using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableQueue : IEnumerable, IReadOnlyCollection { void Clear(); bool Contains(T item); void CopyTo(T[] array, int arrayIndex); T Dequeue(); void Enqueue(T item); T Peek(); bool TryDequeue([MaybeNullWhen(false)] out T item); bool TryPeek([MaybeNullWhen(false)] out T item); } [DebuggerTypeProxy(typeof(DurableQueueDebugView<>))] [DebuggerDisplay("Count = {Count}")] internal sealed class DurableQueue : IDurableQueue, IDurableStateMachine { private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private const byte VersionByte = 0; private readonly Queue _items = new(); private IStateMachineLogWriter? _storage; public DurableQueue([ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); } public int Count => _items.Count; void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { _items.Clear(); _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableQueue)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.Enqueue: ApplyEnqueue(ReadValue(ref reader)); break; case CommandType.Dequeue: _ = ApplyDequeue(); break; case CommandType.Clear: ApplyClear(); break; case CommandType.Snapshot: ApplySnapshot(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] T ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } void ApplySnapshot(ref Reader reader) { var count = (int)reader.ReadVarUInt32(); ApplyClear(); _items.EnsureCapacity(count); for (var i = 0; i < count; i++) { ApplyEnqueue(ReadValue(ref reader)); } } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { // This state machine implementation appends log entries as the data structure is modified, so there is no need to perform separate writing here. } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { snapshotWriter.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Snapshot); writer.WriteVarUInt32((uint)self._items.Count); foreach (var item in self._items) { self._codec.WriteField(ref writer, 0, typeof(T), item); } writer.Commit(); }, this); } public void Clear() { ApplyClear(); GetStorage().AppendEntry(static (state, bufferWriter) => { using var session = state._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Clear); writer.Commit(); }, this); } public T Peek() => _items.Peek(); public bool TryPeek([MaybeNullWhen(false)] out T item) => _items.TryPeek(out item); public bool Contains(T item) => _items.Contains(item); public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => _items.GetEnumerator(); public void Enqueue(T item) { ApplyEnqueue(item); GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, value) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Enqueue); self._codec.WriteField(ref writer, 0, typeof(T), value!); writer.Commit(); }, (this, item)); } public T Dequeue() { var result = ApplyDequeue(); GetStorage().AppendEntry(static (state, bufferWriter) => { var self = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Dequeue); writer.Commit(); }, this); return result; } public bool TryDequeue([MaybeNullWhen(false)] out T item) { if (ApplyTryDequeue(out item)) { GetStorage().AppendEntry(static (state, bufferWriter) => { var self = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Dequeue); writer.Commit(); }, this); return true; } return false; } IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); protected void ApplyEnqueue(T item) => _items.Enqueue(item); protected T ApplyDequeue() => _items.Dequeue(); protected bool ApplyTryDequeue([MaybeNullWhen(false)] out T value) => _items.TryDequeue(out value); protected void ApplyClear() => _items.Clear(); [DoesNotReturn] private static void ThrowIndexOutOfRange() => throw new ArgumentOutOfRangeException("index", "Index was out of range. Must be non-negative and less than the size of the collection"); private IStateMachineLogWriter GetStorage() { Debug.Assert(_storage is not null); return _storage; } public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); private enum CommandType { Enqueue = 0, Dequeue = 1, Clear = 2, Snapshot = 3, } } internal sealed class DurableQueueDebugView { private readonly DurableQueue _queue; public DurableQueueDebugView(DurableQueue queue) { ArgumentNullException.ThrowIfNull(queue); _queue = queue; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { return _queue.ToArray(); } } } ================================================ FILE: src/Orleans.Journaling/DurableSet.cs ================================================ using System.Buffers; using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableSet : ISet, IReadOnlyCollection, IReadOnlySet { new int Count { get; } new bool Contains(T item); new bool Add(T item); new bool IsProperSubsetOf(IEnumerable other); new bool IsProperSupersetOf(IEnumerable other); new bool IsSubsetOf(IEnumerable other); new bool IsSupersetOf(IEnumerable other); new bool Overlaps(IEnumerable other); new bool SetEquals(IEnumerable other); } [DebuggerTypeProxy(typeof(IDurableCollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] internal sealed class DurableSet : IDurableSet, IDurableStateMachine { private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private const byte VersionByte = 0; private readonly HashSet _items = []; private IStateMachineLogWriter? _storage; public DurableSet([ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); } public int Count => _items.Count; public bool IsReadOnly => false; void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { _items.Clear(); _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableSet)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.Add: ApplyAdd(ReadValue(ref reader)); break; case CommandType.Remove: ApplyRemove(ReadValue(ref reader)); break; case CommandType.Clear: ApplyClear(); break; case CommandType.Snapshot: ApplySnapshot(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] T ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } void ApplySnapshot(ref Reader reader) { var count = (int)reader.ReadVarUInt32(); ApplyClear(); _items.EnsureCapacity(count); for (var i = 0; i < count; i++) { ApplyAdd(ReadValue(ref reader)); } } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { // This state machine implementation appends log entries as the data structure is modified, so there is no need to perform separate writing here. } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { snapshotWriter.AppendEntry(WriteSnapshotToBufferWriter, this); } private static void WriteSnapshotToBufferWriter(DurableSet self, IBufferWriter bufferWriter) { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Snapshot); writer.WriteVarUInt32((uint)self._items.Count); foreach (var item in self._items) { self._codec.WriteField(ref writer, 0, typeof(T), item); } writer.Commit(); } public void Clear() { ApplyClear(); GetStorage().AppendEntry(static (state, bufferWriter) => { using var session = state._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Clear); writer.Commit(); }, this); } public bool Contains(T item) => _items.Contains(item); public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); public IEnumerator GetEnumerator() => _items.GetEnumerator(); public bool Add(T item) { if (ApplyAdd(item)) { GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, item) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Add); self._codec.WriteField(ref writer, 0, typeof(T), item!); writer.Commit(); }, (this, item)); return true; } return false; } public bool Remove(T item) { if (ApplyRemove(item)) { GetStorage().AppendEntry(static (state, bufferWriter) => { var (self, item) = state; using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.Remove); self._codec.WriteField(ref writer, 0, typeof(T), item!); writer.Commit(); }, (this, item)); return true; } return false; } IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); protected bool ApplyAdd(T item) => _items.Add(item); protected bool ApplyRemove(T item) => _items.Remove(item); protected void ApplyClear() => _items.Clear(); [DoesNotReturn] private static void ThrowIndexOutOfRange() => throw new ArgumentOutOfRangeException("index", "Index was out of range. Must be non-negative and less than the size of the collection"); private IStateMachineLogWriter GetStorage() { Debug.Assert(_storage is not null); return _storage; } public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); public void ExceptWith(IEnumerable other) { foreach (var item in other) { Remove(item); } } public bool IsProperSubsetOf(IEnumerable other) => _items.IsProperSubsetOf(other); public bool IsProperSupersetOf(IEnumerable other) => _items.IsProperSupersetOf(other); public bool IsSubsetOf(IEnumerable other) => _items.IsSubsetOf(other); public bool IsSupersetOf(IEnumerable other) => _items.IsSupersetOf(other); public bool Overlaps(IEnumerable other) => _items.Overlaps(other); public bool SetEquals(IEnumerable other) => _items.SetEquals(other); void ICollection.Add(T item) => Add(item); public void IntersectWith(IEnumerable other) { var initialCount = Count; _items.IntersectWith(other); if (Count != initialCount) { GetStorage().AppendEntry(WriteSnapshotToBufferWriter, this); } } public void SymmetricExceptWith(IEnumerable other) { var initialCount = Count; _items.SymmetricExceptWith(other); if (Count != initialCount) { GetStorage().AppendEntry(WriteSnapshotToBufferWriter, this); } } public void UnionWith(IEnumerable other) { foreach (var item in other) { Add(item); } } private enum CommandType { Add = 0, Remove = 1, Clear = 2, Snapshot = 3, } } ================================================ FILE: src/Orleans.Journaling/DurableState.cs ================================================ using System.Buffers; using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Orleans.Core; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; [DebuggerDisplay("{Value}")] internal sealed class DurableState : IPersistentState, IDurableStateMachine { private const byte VersionByte = 0; private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private readonly IStateMachineManager _manager; private T? _value; private ulong _version; public DurableState([ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); _manager = manager; } public Action? OnPersisted { get; set; } T IStorage.State { get => _value ??= Activator.CreateInstance(); set => _value = value; } string IStorage.Etag => $"{_version}"; bool IStorage.RecordExists => _version > 0; void IDurableStateMachine.OnWriteCompleted() { _version++; OnPersisted?.Invoke(); } void IDurableStateMachine.Reset(IStateMachineLogWriter storage) => _value = default; void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableState)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.ClearValue: ClearValue(ref reader); break; case CommandType.SetValue: SetValue(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } void SetValue(ref Reader reader) { var field = reader.ReadFieldHeader(); _value = _codec.ReadValue(ref reader, field); _version = reader.ReadVarUInt64(); } void ClearValue(ref Reader reader) { _value = default; _version = 0; } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) => WriteState(logWriter); void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) => WriteState(snapshotWriter); public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); private void WriteState(StateMachineStorageWriter writer) { writer.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.SetValue); self._codec.WriteField(ref writer, 0, typeof(T), self._value!); writer.WriteVarUInt64(self._version); writer.Commit(); }, this); } Task IStorage.ClearStateAsync() => ((IStorage)this).ClearStateAsync(CancellationToken.None); async Task IStorage.ClearStateAsync(CancellationToken cancellationToken) { _value = default; _version = 0; await _manager.WriteStateAsync(cancellationToken); } Task IStorage.WriteStateAsync() => ((IStorage)this).WriteStateAsync(CancellationToken.None); async Task IStorage.WriteStateAsync(CancellationToken cancellationToken) => await _manager.WriteStateAsync(cancellationToken); Task IStorage.ReadStateAsync() => ((IStorage)this).ReadStateAsync(CancellationToken.None); Task IStorage.ReadStateAsync(CancellationToken cancellationToken) => Task.CompletedTask; private enum CommandType { SetValue, ClearValue, } } ================================================ FILE: src/Orleans.Journaling/DurableTaskCompletionSource.cs ================================================ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableTaskCompletionSource { Task Task { get; } DurableTaskCompletionSourceState State { get; } bool TrySetCanceled(); bool TrySetException(Exception exception); bool TrySetResult(T value); } [DebuggerDisplay("Status = {Status}")] internal sealed class DurableTaskCompletionSource : IDurableTaskCompletionSource, IDurableStateMachine { private const byte SupportedVersion = 0; private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private readonly IFieldCodec _exceptionCodec; private readonly DeepCopier _copier; private readonly DeepCopier _exceptionCopier; private TaskCompletionSource _completion = new(TaskCreationOptions.RunContinuationsAsynchronously); private IStateMachineLogWriter? _storage; private DurableTaskCompletionSourceStatus _status; private T? _value; private Exception? _exception; public DurableTaskCompletionSource( [ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, DeepCopier copier, IFieldCodec exceptionCodec, DeepCopier exceptionCopier, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _copier = copier; _exceptionCodec = exceptionCodec; _exceptionCopier = exceptionCopier; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); } public bool TrySetResult(T value) { if (_status is not DurableTaskCompletionSourceStatus.Pending) { return false; } _status = DurableTaskCompletionSourceStatus.Completed; _value = _copier.Copy(value); return true; } public bool TrySetException(Exception exception) { if (_status is not DurableTaskCompletionSourceStatus.Pending) { return false; } _status = DurableTaskCompletionSourceStatus.Faulted; _exception = _exceptionCopier.Copy(exception); return true; } public bool TrySetCanceled() { if (_status is not DurableTaskCompletionSourceStatus.Pending) { return false; } _status = DurableTaskCompletionSourceStatus.Canceled; return true; } public Task Task => _completion.Task; public DurableTaskCompletionSourceState State => _status switch { DurableTaskCompletionSourceStatus.Pending => new DurableTaskCompletionSourceState { Status = DurableTaskCompletionSourceStatus.Pending }, DurableTaskCompletionSourceStatus.Completed => new DurableTaskCompletionSourceState { Status = DurableTaskCompletionSourceStatus.Completed, Value = _value }, DurableTaskCompletionSourceStatus.Faulted => new DurableTaskCompletionSourceState { Status = DurableTaskCompletionSourceStatus.Faulted, Exception = _exception }, DurableTaskCompletionSourceStatus.Canceled => new DurableTaskCompletionSourceState { Status = DurableTaskCompletionSourceStatus.Canceled }, _ => throw new InvalidOperationException($"Unexpected status, \"{_status}\""), }; private void OnValuePersisted() { switch (_status) { case DurableTaskCompletionSourceStatus.Completed: _completion.TrySetResult(_value!); break; case DurableTaskCompletionSourceStatus.Faulted: _completion.TrySetException(_exception!); break; case DurableTaskCompletionSourceStatus.Canceled: _completion.TrySetCanceled(); break; default: break; } } void IDurableStateMachine.OnRecoveryCompleted() => OnValuePersisted(); void IDurableStateMachine.OnWriteCompleted() => OnValuePersisted(); void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { // Reset the task completion source if necessary. if (_completion.Task.IsCompleted) { _completion = new(TaskCreationOptions.RunContinuationsAsynchronously); } _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != SupportedVersion) { throw new NotSupportedException($"This instance of {nameof(DurableTaskCompletionSource)} supports version {(uint)SupportedVersion} and not version {(uint)version}."); } _status = (DurableTaskCompletionSourceStatus)reader.ReadByte(); switch (_status) { case DurableTaskCompletionSourceStatus.Completed: _value = ReadValue(ref reader); break; case DurableTaskCompletionSourceStatus.Faulted: _exception = ReadException(ref reader); break; default: break; } [MethodImpl(MethodImplOptions.AggressiveInlining)] T ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } [MethodImpl(MethodImplOptions.AggressiveInlining)] Exception ReadException(ref Reader reader) { var field = reader.ReadFieldHeader(); return _exceptionCodec.ReadValue(ref reader, field); } } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { if (_status is not DurableTaskCompletionSourceStatus.Pending) { WriteState(logWriter); } } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) => WriteState(snapshotWriter); private void WriteState(StateMachineStorageWriter writer) { writer.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(DurableTaskCompletionSource.SupportedVersion); var status = self._status; writer.WriteByte((byte)status); if (status is DurableTaskCompletionSourceStatus.Completed) { self._codec.WriteField(ref writer, 0, typeof(T), self._value!); } else if (status is DurableTaskCompletionSourceStatus.Faulted) { self._exceptionCodec.WriteField(ref writer, 0, typeof(Exception), self._exception!); } writer.Commit(); }, this); } public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); } [GenerateSerializer] public enum DurableTaskCompletionSourceStatus : byte { Pending = 0, Completed, Faulted, Canceled } [GenerateSerializer, Immutable] public readonly struct DurableTaskCompletionSourceState { [Id(0)] public DurableTaskCompletionSourceStatus Status { get; init; } [Id(1)] public T? Value { get; init; } [Id(2)] public Exception? Exception { get; init; } } ================================================ FILE: src/Orleans.Journaling/DurableValue.cs ================================================ using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; public interface IDurableValue { T? Value { get; set; } } [DebuggerDisplay("{Value}")] internal sealed class DurableValue : IDurableValue, IDurableStateMachine { private const byte VersionByte = 0; private readonly SerializerSessionPool _serializerSessionPool; private readonly IFieldCodec _codec; private IStateMachineLogWriter? _storage; private T? _value; private bool _isDirty; public DurableValue([ServiceKey] string key, IStateMachineManager manager, IFieldCodec codec, SerializerSessionPool serializerSessionPool) { ArgumentNullException.ThrowIfNullOrEmpty(key); _codec = codec; _serializerSessionPool = serializerSessionPool; manager.RegisterStateMachine(key, this); } public T? Value { get => _value; set { _value = value; OnModified(); } } public Action? OnPersisted { get; set; } private void OnValuePersisted() => OnPersisted?.Invoke(); public void OnModified() => _isDirty = true; void IDurableStateMachine.OnRecoveryCompleted() => OnValuePersisted(); void IDurableStateMachine.OnWriteCompleted() => OnValuePersisted(); void IDurableStateMachine.Reset(IStateMachineLogWriter storage) { _value = default; _storage = storage; } void IDurableStateMachine.Apply(ReadOnlySequence logEntry) { using var session = _serializerSessionPool.GetSession(); var reader = Reader.Create(logEntry, session); var version = reader.ReadByte(); if (version != VersionByte) { throw new NotSupportedException($"This instance of {nameof(DurableValue)} supports version {(uint)VersionByte} and not version {(uint)version}."); } var commandType = (CommandType)reader.ReadVarUInt32(); switch (commandType) { case CommandType.SetValue: SetValue(ref reader); break; default: throw new NotSupportedException($"Command type {commandType} is not supported"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] T ReadValue(ref Reader reader) { var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } void SetValue(ref Reader reader) => _value = ReadValue(ref reader); } void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { if (_isDirty) { WriteState(logWriter); _isDirty = false; } } void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) => WriteState(snapshotWriter); public IDurableStateMachine DeepCopy() => throw new NotImplementedException(); private void WriteState(StateMachineStorageWriter writer) { writer.AppendEntry(static (self, bufferWriter) => { using var session = self._serializerSessionPool.GetSession(); var writer = Writer.Create(bufferWriter, session); writer.WriteByte(VersionByte); writer.WriteVarUInt32((uint)CommandType.SetValue); self._codec.WriteField(ref writer, 0, typeof(T), self._value!); writer.Commit(); }, this); } [DoesNotReturn] private static void ThrowIndexOutOfRange() => throw new ArgumentOutOfRangeException("index", "Index was out of range. Must be non-negative and less than the size of the collection"); private IStateMachineLogWriter GetStorage() { Debug.Assert(_storage is not null); return _storage; } private enum CommandType { SetValue, } } ================================================ FILE: src/Orleans.Journaling/HostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.Configuration; namespace Orleans.Journaling; public static class HostingExtensions { public static ISiloBuilder AddStateMachineStorage(this ISiloBuilder builder) { builder.Services.AddOptions(); builder.Services.TryAddScoped(sp => sp.GetRequiredService().Create(sp.GetRequiredService())); builder.Services.TryAddScoped(); builder.Services.TryAddKeyedScoped(typeof(IDurableDictionary<,>), KeyedService.AnyKey, typeof(DurableDictionary<,>)); builder.Services.TryAddKeyedScoped(typeof(IDurableList<>), KeyedService.AnyKey, typeof(DurableList<>)); builder.Services.TryAddKeyedScoped(typeof(IDurableQueue<>), KeyedService.AnyKey, typeof(DurableQueue<>)); builder.Services.TryAddKeyedScoped(typeof(IDurableSet<>), KeyedService.AnyKey, typeof(DurableSet<>)); builder.Services.TryAddKeyedScoped(typeof(IDurableValue<>), KeyedService.AnyKey, typeof(DurableValue<>)); builder.Services.TryAddKeyedScoped(typeof(IPersistentState<>), KeyedService.AnyKey, typeof(DurableState<>)); builder.Services.TryAddKeyedScoped(typeof(IDurableTaskCompletionSource<>), KeyedService.AnyKey, typeof(DurableTaskCompletionSource<>)); builder.Services.TryAddKeyedScoped(typeof(IDurableNothing), KeyedService.AnyKey, typeof(DurableNothing)); return builder; } } ================================================ FILE: src/Orleans.Journaling/IDurableStateMachine.cs ================================================ using System.Buffers; namespace Orleans.Journaling; /// /// Interface for a state machine which can be persisted to durable storage. /// public interface IDurableStateMachine { /// /// Resets the state machine. /// /// /// If the state machine has any volatile state, it must be cleared by this method. /// This method can be called at any point in the state machine's lifetime, including during recovery. /// void Reset(IStateMachineLogWriter storage); /// /// Called during recovery to apply the provided log entry or snapshot. /// /// The log entry or snapshot. void Apply(ReadOnlySequence entry); /// /// Notifies the state machine that all prior log entries and snapshots have been applied. /// /// /// The state machine should not expect any additional calls after this method is called, /// unless is called to reset the state machine to its initial state. /// This method will be called before any or calls. /// void OnRecoveryCompleted() { } /// /// Writes pending state changes to the log. /// /// The log writer. void AppendEntries(StateMachineStorageWriter writer); /// /// Writes a snapshot of the state machine to the provided writer. /// /// The log writer. void AppendSnapshot(StateMachineStorageWriter writer); /// /// Notifies the state machine that all prior log entries and snapshots which it has written have been written to stable storage. /// void OnWriteCompleted() { } /// /// Creates and returns a deep copy of this instance. All replicas must be independent such that changes to one do not affect any other. /// /// A replica of this instance. IDurableStateMachine DeepCopy(); } ================================================ FILE: src/Orleans.Journaling/IStateMachineLogWriter.cs ================================================ using System.Buffers; namespace Orleans.Journaling; /// /// Provides functionality for writing out-of-band log entries to the log for the state machine which holds this instance. /// public interface IStateMachineLogWriter { /// /// Appends an entry to the log for the state machine which holds this instance. /// /// The state, passed to the delegate. /// The delegate invoked to append a log entry. /// The state passed to . void AppendEntry(Action> action, TState state); /// /// Appends an entry to the log for the state machine which holds this instance. /// /// The state, passed to the delegate. /// The delegate invoked to append a log entry. /// The state passed to . void AppendEntries(Action action, TState state); } ================================================ FILE: src/Orleans.Journaling/IStateMachineManager.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Orleans.Journaling; /// /// Manages the state machines for a given grain. /// public interface IStateMachineManager { /// /// Initializes the state machine manager. /// /// The cancellation token. /// A which represents the operation. ValueTask InitializeAsync(CancellationToken cancellationToken); /// /// Registers a state machine with the manager. /// /// The state machine's stable identifier. /// The state machine instance to register. void RegisterStateMachine(string name, IDurableStateMachine stateMachine); /// /// Registers a state machine with the manager. /// /// The state machine's stable identifier. /// The state machine instance to register. bool TryGetStateMachine(string name, [NotNullWhen(true)] out IDurableStateMachine? stateMachine); /// /// Prepares and persists an update to the log. /// /// The cancellation token. /// A which represents the operation. ValueTask WriteStateAsync(CancellationToken cancellationToken); /// /// Resets this instance, removing any persistent state. /// /// The cancellation token. /// A which represents the operation. ValueTask DeleteStateAsync(CancellationToken cancellationToken); } ================================================ FILE: src/Orleans.Journaling/IStateMachineStorage.cs ================================================ namespace Orleans.Journaling; /// /// Provides storage for state machines. /// public interface IStateMachineStorage { /// /// Returns an ordered collection of all log segments belonging to this instance. /// /// The cancellation token. /// An ordered collection of all log segments belonging to this instance. IAsyncEnumerable ReadAsync(CancellationToken cancellationToken); /// /// Replaces the log with the provided value atomically. /// /// The value to write. /// The cancellation token. /// A representing the operation. ValueTask ReplaceAsync(LogExtentBuilder value, CancellationToken cancellationToken); /// /// Appends the provided segment to the log atomically. /// /// The segment to append. /// The cancellation token. /// A representing the operation. ValueTask AppendAsync(LogExtentBuilder value, CancellationToken cancellationToken); /// /// Deletes the state machine's log atomically. /// /// The cancellation token. /// A representing the operation. ValueTask DeleteAsync(CancellationToken cancellationToken); /// /// Gets a value indicating whether the state machine has requested a snapshot. /// bool IsCompactionRequested { get; } } ================================================ FILE: src/Orleans.Journaling/IStateMachineStorageProvider.cs ================================================ namespace Orleans.Journaling; public interface IStateMachineStorageProvider { IStateMachineStorage Create(IGrainContext grainContext); } ================================================ FILE: src/Orleans.Journaling/LogExtent.cs ================================================ using System.Buffers; using System.Collections; using Orleans.Serialization.Buffers; using System.Diagnostics; namespace Orleans.Journaling; /// /// Represents a log segment which has been sealed and is no longer mutable. /// public sealed class LogExtent(ArcBuffer buffer) : IDisposable { private ArcBuffer _buffer = buffer; public LogExtent() : this(new()) { } public bool IsEmpty => _buffer.Length == 0; internal EntryEnumerator Entries => EntryEnumerator.Create(this); public void Dispose() => _buffer.Dispose(); public readonly record struct Entry(StateMachineId StreamId, ReadOnlySequence Payload); internal struct EntryEnumerator : IEnumerable, IEnumerator, IDisposable { private LogExtent _logExtent; private ReadOnlySequence _current; private int _length; private EntryEnumerator(LogExtent logExtent) { _logExtent = logExtent; _current = logExtent._buffer.AsReadOnlySequence(); _length = -2; } public readonly EntryEnumerator GetEnumerator() => this; public static EntryEnumerator Create(LogExtent logSegment) => new(logSegment); public bool MoveNext() { if (_length == -1) { ThrowEnumerationNotStartedOrEnded(); } if (_length >= 0) { // Advance the cursor. _current = _current.Slice(_length); } if (_current.Length == 0) { _length = -1; return false; } var reader = Reader.Create(_current, null); _length = (int)reader.ReadVarUInt32(); _current = _current.Slice(reader.Position); return true; } public readonly Entry Current { get { if (_length < 0) { ThrowEnumerationNotStartedOrEnded(); } var slice = _current.Slice(0, _length); var reader = Reader.Create(slice, null); var id = reader.ReadVarUInt32(); return new(new(id), slice.Slice(reader.Position)); } } private readonly void ThrowEnumerationNotStartedOrEnded() { Debug.Assert(_length is (-1) or (-2)); throw new InvalidOperationException(_length == -2 ? "Enumeration has not started." : "Enumeration has completed."); } readonly object? IEnumerator.Current => Current; public void Reset() => this = new(_logExtent); public void Dispose() => _length = -1; readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } ================================================ FILE: src/Orleans.Journaling/LogExtentBuilder.ReadOnlyStream.cs ================================================ using System.Diagnostics; using System.Numerics; namespace Orleans.Journaling; public sealed partial class LogExtentBuilder { public sealed class ReadOnlyStream : Stream { private LogExtentBuilder? _builder; private int _length; private int _position; public ReadOnlyStream() { } public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => false; public override long Length => _length; public override long Position { get => _position; set => SetPosition((int)value); } public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count)); public override int Read(Span buffer) => throw new NotImplementedException(); public override long Seek(long offset, SeekOrigin origin) { switch (origin) { case SeekOrigin.Begin: SetPosition((int)offset); break; case SeekOrigin.Current: SetPosition(_position + (int)offset); break; case SeekOrigin.End: SetPosition(_length - (int)offset); break; default: throw new ArgumentOutOfRangeException(nameof(origin)); } return Position; } private void SetPosition(int value) { if (value > _length || value < 0) throw new ArgumentOutOfRangeException(nameof(value)); _position = value; } public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => new(Read(buffer.Span)); public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Task.FromResult(Read(buffer, offset, count)); public override void CopyTo(Stream destination, int bufferSize) { ValidateCopyToArguments(destination, bufferSize); _builder!.CopyToAsync(destination, bufferSize, default).AsTask().GetAwaiter().GetResult(); } public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { ValidateCopyToArguments(destination, bufferSize); if (_position != 0) throw new NotImplementedException("Position must be zero for this copy operation"); return _builder!.CopyToAsync(destination, bufferSize, cancellationToken).AsTask(); } public override void Flush() => throw GetReadOnlyException(); public override void WriteByte(byte value) => throw GetReadOnlyException(); public override void SetLength(long value) => throw GetReadOnlyException(); public override void Write(byte[] buffer, int offset, int count) => throw GetReadOnlyException(); public override void Write(ReadOnlySpan buffer) => throw GetReadOnlyException(); public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw GetReadOnlyException(); public void SetBuilder(LogExtentBuilder builder) { _builder = builder; _position = 0; _length = ComputeLength(); } public void Reset() { _builder = default; _position = 0; _length = 0; } private int ComputeLength() { Debug.Assert(_builder!._entryLengths is not null); var length = _builder!._buffer.Length; foreach (var entry in _builder!._entryLengths) { length += GetVarIntWidth(entry); } return length; } private static int GetVarIntWidth(uint value) => 1 + (int)((uint)BitOperations.Log2(value) / 7); private static NotSupportedException GetReadOnlyException() => new("This stream is read-only"); } } ================================================ FILE: src/Orleans.Journaling/LogExtentBuilder.cs ================================================ using System.Buffers; using System.Diagnostics; using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; namespace Orleans.Journaling; /// /// A mutable builder for creating log segments. /// public sealed partial class LogExtentBuilder(ArcBufferWriter buffer) : IDisposable, IBufferWriter { private readonly List _entryLengths = []; private readonly byte[] _scratch = new byte[8]; private readonly ArcBufferWriter _buffer = buffer; public LogExtentBuilder() : this(new()) { } public long Length => _buffer.Length; public byte[] ToArray() { using var memoryStream = new PooledBufferStream(); CopyTo(memoryStream, 4096); return memoryStream.ToArray(); } public StateMachineStorageWriter CreateLogWriter(StateMachineId id) => new(id, this); public bool IsEmpty => _buffer.Length == 0; internal void AppendEntry(StateMachineId id, byte[] value) => AppendEntry(id, (ReadOnlySpan)value); internal void AppendEntry(StateMachineId id, Span value) => AppendEntry(id, (ReadOnlySpan)value); internal void AppendEntry(StateMachineId id, Memory value) => AppendEntry(id, value.Span); internal void AppendEntry(StateMachineId id, ReadOnlyMemory value) => AppendEntry(id, value.Span); internal void AppendEntry(StateMachineId id, ArraySegment value) => AppendEntry(id, value.AsSpan()); internal void AppendEntry(StateMachineId id, ReadOnlySpan value) { var startOffset = _buffer.Length; var writer = Writer.Create(this, session: null); writer.WriteVarUInt64(id.Value); writer.Commit(); _buffer.Write(value); var endOffset = _buffer.Length; _entryLengths.Add((uint)(endOffset - startOffset)); } internal void AppendEntry(StateMachineId id, ReadOnlySequence value) { var startOffset = _buffer.Length; var writer = Writer.Create(this, session: null); writer.WriteVarUInt64(id.Value); writer.Commit(); _buffer.Write(value); var endOffset = _buffer.Length; _entryLengths.Add((uint)(endOffset - startOffset)); } internal void AppendEntry(StateMachineId id, Action> valueWriter, T value) { var startOffset = _buffer.Length; var writer = Writer.Create(this, session: null); writer.WriteVarUInt64(id.Value); writer.Commit(); valueWriter(value, this); var endOffset = _buffer.Length; _entryLengths.Add((uint)(endOffset - startOffset)); } public void Reset() { _buffer.Reset(); _entryLengths.Clear(); } public void Dispose() => Reset(); // Implemented on this class to prevent the need to repeatedly box & unbox _buffer. void IBufferWriter.Advance(int count) => _buffer.AdvanceWriter(count); Memory IBufferWriter.GetMemory(int sizeHint) => _buffer.GetMemory(sizeHint); Span IBufferWriter.GetSpan(int sizeHint) => _buffer.GetSpan(sizeHint); public async ValueTask CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { using var buffer = _buffer.PeekSlice(_buffer.Length); var segments = buffer.MemorySegments; var currentSegment = ReadOnlyMemory.Empty; foreach (var entryLength in _entryLengths) { await destination.WriteAsync(GetLengthBytes(_scratch, entryLength), cancellationToken); var remainingEntryLength = entryLength; while (remainingEntryLength > 0) { // Move to the next memory segment if necessary. if (currentSegment.Length == 0) { var hasNext = segments.MoveNext(); Debug.Assert(hasNext); currentSegment = segments.Current; continue; } var copyLen = Math.Min((uint)bufferSize, Math.Min(remainingEntryLength, (uint)currentSegment.Length)); await destination.WriteAsync(currentSegment[..(int)copyLen], cancellationToken); remainingEntryLength -= copyLen; currentSegment = currentSegment[(int)copyLen..]; } } } public void CopyTo(Stream destination, int bufferSize) { using var buffer = _buffer.PeekSlice(_buffer.Length); var segments = buffer.MemorySegments; var currentSegment = ReadOnlyMemory.Empty; foreach (var entryLength in _entryLengths) { destination.Write(GetLengthBytes(_scratch, entryLength).Span); var remainingEntryLength = entryLength; while (remainingEntryLength > 0) { // Move to the next memory segment if necessary. if (currentSegment.Length == 0) { var hasNext = segments.MoveNext(); Debug.Assert(hasNext); currentSegment = segments.Current; continue; } var copyLen = Math.Min((uint)bufferSize, Math.Min(remainingEntryLength, (uint)currentSegment.Length)); destination.Write(currentSegment[..(int)copyLen].Span); remainingEntryLength -= copyLen; currentSegment = currentSegment[(int)copyLen..]; } } } private static ReadOnlyMemory GetLengthBytes(byte[] scratch, uint length) { var writer = Writer.Create(scratch, null); writer.WriteVarUInt32(length); return new ReadOnlyMemory(scratch, 0, writer.Position); } } ================================================ FILE: src/Orleans.Journaling/Orleans.Journaling.csproj ================================================  Microsoft.Orleans.Journaling Microsoft Orleans Journaling Extensible persistence for grains based on replicated state machines. $(PackageTags) Persistence State Machines true $(DefaultTargetFrameworks) enable $(VersionSuffix).alpha.1 alpha.1 ================================================ FILE: src/Orleans.Journaling/Properties/AssemblyInfo.cs ================================================ using System.Diagnostics.CodeAnalysis; [assembly: Experimental("ORLEANSEXP005")] ================================================ FILE: src/Orleans.Journaling/StateMachineId.cs ================================================ namespace Orleans.Journaling; /// /// Identifies a state machine. /// /// The underlying identity value. public readonly record struct StateMachineId(ulong Value); ================================================ FILE: src/Orleans.Journaling/StateMachineManager.cs ================================================ using System.Buffers; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime.Internal; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; namespace Orleans.Journaling; internal sealed partial class StateMachineManager : IStateMachineManager, ILifecycleParticipant, ILifecycleObserver, IDisposable { private const int MinApplicationStateMachineId = 8; private static readonly StringCodec StringCodec = new(); private static readonly UInt64Codec UInt64Codec = new(); private static readonly DateTimeCodec DateTimeCodec = new(); #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif private readonly Dictionary _stateMachines = new(StringComparer.Ordinal); private readonly Dictionary _stateMachinesMap = []; private readonly IStateMachineStorage _storage; private readonly ILogger _logger; private readonly TimeProvider _timeProvider; private readonly SingleWaiterAutoResetEvent _workSignal = new() { RunContinuationsAsynchronously = true }; private readonly Queue _workQueue = new(); private readonly CancellationTokenSource _shutdownCancellation = new(); private readonly StateMachineManagerState _stateMachineIds; private readonly StateMachinesRetirementTracker _retirementTracker; private readonly TimeSpan _retirementGracePeriod; private Task? _workLoop; private ManagerState _state; private Task? _pendingWrite; private ulong _nextStateMachineId = MinApplicationStateMachineId; private LogExtentBuilder? _currentLogSegment; public StateMachineManager( IStateMachineStorage storage, ILogger logger, IOptions options, SerializerSessionPool serializerSessionPool, TimeProvider timeProvider) { _storage = storage; _logger = logger; _timeProvider = timeProvider; _retirementGracePeriod = options.Value.RetirementGracePeriod; // The list of known state machines is itself stored as a durable state machine with the implicit id 0. // This allows us to recover the list of state machines ids without having to store it separately. _stateMachineIds = new StateMachineManagerState(this, StringCodec, UInt64Codec, serializerSessionPool); _stateMachinesMap[StateMachineManagerState.Id] = _stateMachineIds; // The retirement tracker is a special internal state machine with a fixed id. // It is not stored in _stateMachineIds and does not participate in the general name->id mapping. _retirementTracker = new StateMachinesRetirementTracker(this, StringCodec, DateTimeCodec, serializerSessionPool); _stateMachinesMap[StateMachinesRetirementTracker.Id] = _retirementTracker; } public void RegisterStateMachine(string name, IDurableStateMachine stateMachine) { ArgumentNullException.ThrowIfNullOrEmpty(name); _shutdownCancellation.Token.ThrowIfCancellationRequested(); lock (_lock) { if (_stateMachines.TryGetValue(name, out var machine)) { if (machine is RetiredStateMachineVessel vessel) { // If the existing machine is a vessel for a retired one, it means the machine was loaded from a previous // log during recovery but has not been re-registered. We effectively are "staging" the resurrection of the machine. // The removal from the tracker is handled within the serialized loop. This is to prevent logical race conditions with the recovery process. // We also make sure to apply any buffered data that could have occured while the vessel took this machine's place. stateMachine.Reset(new StateMachineLogWriter(this, new(_stateMachineIds[name]))); foreach (var entry in vessel.BufferedData) { stateMachine.Apply(new ReadOnlySequence(entry)); } _stateMachines[name] = stateMachine; } else { // A real state machine is already registered with this name, this must be a developer error. throw new ArgumentException($"A state machine with the key '{name}' has already been registered."); } } else { _stateMachines.Add(name, stateMachine); } _workQueue.Enqueue(new WorkItem(WorkItemType.RegisterStateMachine, completion: null) { Context = name }); } _workSignal.Signal(); } public async ValueTask InitializeAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _shutdownCancellation.Token.ThrowIfCancellationRequested(); Debug.Assert(_workLoop is null, "InitializeAsync should only be called once."); _workLoop = Start(); Task task; lock (_lock) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); task = completion.Task; _workQueue.Enqueue(new WorkItem(WorkItemType.Initialize, completion)); } _workSignal.Signal(); await task; } private Task Start() { using var suppressExecutionContext = new ExecutionContextSuppressor(); return WorkLoop(); } private async Task WorkLoop() { var cancellationToken = _shutdownCancellation.Token; using var cancellationRegistration = cancellationToken.Register(state => ((StateMachineManager)state!)._workSignal.Signal(), this); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding); var needsRecovery = true; while (true) { try { await _workSignal.WaitAsync().ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); while (true) { if (needsRecovery) { await RecoverAsync(cancellationToken).ConfigureAwait(true); needsRecovery = false; } WorkItem workItem; lock (_lock) { if (!_workQueue.TryDequeue(out workItem)) { // Wait for the queue to be signaled again. break; } } try { // Note that the implementation of each command is inlined to avoid allocating unnecessary async state machines. // We are ok sacrificing some code organization for performance in the inner loop. if (workItem.Type is WorkItemType.AppendLog or WorkItemType.WriteSnapshot) { // TODO: decide whether it's best to snapshot or append. Eg, by summing the size of the most recent snapshots and the current log length. // If the current log length is greater than the snapshot size, then take a snapshot instead of appending more log entries. var isSnapshot = workItem.Type is WorkItemType.WriteSnapshot; LogExtentBuilder? logSegment; lock (_lock) { if (isSnapshot) { // If there are pending writes, reset them since they will be captured by the snapshot instead. // If we did not do this, the log would begin with some writes which would be followed by a snapshot which also included those writes. _currentLogSegment?.Reset(); if (_retirementTracker.Count > 0) { RetireOrResurectStateMachines(); } } _currentLogSegment ??= new(); // The map of state machine ids is itself stored as a durable state machine with the id 0. // This must be stored first, since it includes the identities of all other state machines, which are needed when replaying the log. // If we removed retired machines, this snapshot will persist that change. AppendUpdatesOrSnapshotStateMachine(_currentLogSegment, isSnapshot, 0, _stateMachineIds); foreach (var (id, stateMachine) in _stateMachinesMap) { if (id is 0 || stateMachine is null) { continue; } AppendUpdatesOrSnapshotStateMachine(_currentLogSegment, isSnapshot, id, stateMachine); } if (_currentLogSegment.IsEmpty) { logSegment = null; } else { logSegment = _currentLogSegment; _currentLogSegment = null; } } if (logSegment is not null) { if (isSnapshot) { await _storage.ReplaceAsync(logSegment, cancellationToken).ConfigureAwait(true); } else { await _storage.AppendAsync(logSegment, cancellationToken).ConfigureAwait(true); } // Notify all state machines that the operation completed. lock (_lock) { foreach (var stateMachine in _stateMachines.Values) { stateMachine.OnWriteCompleted(); } } } } else if (workItem.Type is WorkItemType.DeleteState) { // Clear storage. await _storage.DeleteAsync(cancellationToken).ConfigureAwait(true); lock (_lock) { // Reset the state machine id collection. _stateMachineIds.ResetVolatileState(); // Allocate new state machine ids for each state machine. // Doing so will trigger a reset, since _stateMachineIds will call OnSetStateMachineId, which resets the state machine in question. _nextStateMachineId = 1; foreach (var (name, stateMachine) in _stateMachines) { var id = _nextStateMachineId++; _stateMachineIds[name] = id; } } } else if (workItem.Type is WorkItemType.Initialize) { lock (_lock) { _state = ManagerState.Ready; } } else if (workItem.Type is WorkItemType.RegisterStateMachine) { lock (_lock) { if (_state is not ManagerState.Unknown) { throw new NotSupportedException("Registering a state machine after activation is not supported."); } var name = (string)workItem.Context!; if (!_stateMachineIds.ContainsKey(name)) { // Doing so will trigger a reset, since _stateMachineIds will call OnSetStateMachineId, which resets the state machine in question. _stateMachineIds[name] = _nextStateMachineId++; } } } else { Debug.Fail($"The command {workItem.Type} is unsupported"); } workItem.CompletionSource?.SetResult(); } catch (Exception exception) { workItem.CompletionSource?.SetException(exception); needsRecovery = true; } } } catch (Exception exception) { needsRecovery = true; if (cancellationToken.IsCancellationRequested) { return; } LogErrorProcessingWorkItems(_logger, exception); } } } private void RetireOrResurectStateMachines() { foreach (var (name, timestamp) in _retirementTracker) { var isDuetime = _timeProvider.GetUtcNow().UtcDateTime - timestamp >= _retirementGracePeriod; if (isDuetime && _stateMachineIds.TryGetValue(name, out var id)) { var stateMachine = _stateMachines[name]; Debug.Assert(stateMachine is not null); if (stateMachine is RetiredStateMachineVessel) { LogRemovingRetiredStateMachine(_logger, name); // Since we are permanently removing this state machine, we will clean it up by reseting it. stateMachine.Reset(new StateMachineLogWriter(this, new(id))); _stateMachinesMap.Remove(id); // We remove these from memory only, since the snapshot will persist these changes. _stateMachineIds.ApplyRemove(name); _retirementTracker.ApplyRemove(name); } else { LogRetiredStateMachineComebackDetected(_logger, name); // We remove the tracker from memory only, since the snapshot will persist the change. _retirementTracker.ApplyRemove(name); } } } } private static void AppendUpdatesOrSnapshotStateMachine(LogExtentBuilder logSegment, bool isSnapshot, ulong id, IDurableStateMachine stateMachine) { var writer = logSegment.CreateLogWriter(new(id)); if (isSnapshot) { stateMachine.AppendSnapshot(writer); } else { stateMachine.AppendEntries(writer); } } public async ValueTask DeleteStateAsync(CancellationToken cancellationToken) { Task task; lock (_lock) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); task = completion.Task; _workQueue.Enqueue(new WorkItem(WorkItemType.DeleteState, completion)); } _workSignal.Signal(); await task; } private async Task RecoverAsync(CancellationToken cancellationToken) { lock (_lock) { _stateMachineIds.ResetVolatileState(); } await foreach (var segment in _storage.ReadAsync(cancellationToken).ConfigureAwait(true)) { cancellationToken.ThrowIfCancellationRequested(); try { foreach (var entry in segment.Entries) { var stateMachine = _stateMachinesMap[entry.StreamId.Value]; stateMachine.Apply(entry.Payload); } } finally { segment.Dispose(); } } lock (_lock) { foreach ((var name, var stateMachine) in _stateMachines) { stateMachine.OnRecoveryCompleted(); if (stateMachine is RetiredStateMachineVessel) { // We can use TryAdd since recovery has finished. if (_retirementTracker.TryAdd(name, _timeProvider.GetUtcNow().UtcDateTime)) { LogRetiredStateMachineDetected(_logger, name); } } } } } public async ValueTask WriteStateAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); Task? pendingWrite; var didEnqueue = false; lock (_lock) { // If the pending write is faulted, recovery will need to be performed. // For now, await it so that we can propagate the exception consistently. if (_pendingWrite is not { IsFaulted: true }) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _pendingWrite = completion.Task; var workItemType = _storage.IsCompactionRequested switch { true => WorkItemType.WriteSnapshot, false => WorkItemType.AppendLog, }; _workQueue.Enqueue(new WorkItem(workItemType, completion)); didEnqueue = true; } pendingWrite = _pendingWrite; } if (didEnqueue) { _workSignal.Signal(); } if (pendingWrite is { } task) { await task.WaitAsync(cancellationToken); } } private void OnSetStateMachineId(string name, ulong id) { lock (_lock) { if (id >= _nextStateMachineId) { _nextStateMachineId = id + 1; } if (_stateMachines.TryGetValue(name, out var stateMachine)) { _stateMachinesMap[id] = stateMachine; stateMachine.Reset(new StateMachineLogWriter(this, new(id))); } else { var vessel = new RetiredStateMachineVessel(); // We must not make the vessel self-register with the manager, since it will // result in a late-registration after the manager is 'ready'. Instead we add it inline here. _stateMachines.Add(name, vessel); _stateMachinesMap[id] = vessel; } } } public bool TryGetStateMachine(string name, [NotNullWhen(true)] out IDurableStateMachine? stateMachine) => _stateMachines.TryGetValue(name, out stateMachine); void ILifecycleParticipant.Participate(IGrainLifecycle observer) => observer.Subscribe(GrainLifecycleStage.SetupState, this); Task ILifecycleObserver.OnStart(CancellationToken cancellationToken) => InitializeAsync(cancellationToken).AsTask(); async Task ILifecycleObserver.OnStop(CancellationToken cancellationToken) { _shutdownCancellation.Cancel(); _workSignal.Signal(); if (_workLoop is { } task) { await task.WaitAsync(cancellationToken).ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing); } } void IDisposable.Dispose() { _shutdownCancellation.Dispose(); } private sealed class StateMachineLogWriter(StateMachineManager manager, StateMachineId streamId) : IStateMachineLogWriter { private readonly StateMachineManager _manager = manager; private readonly StateMachineId _id = streamId; public void AppendEntry(Action> action, TState state) { lock (_manager._lock) { var segment = _manager._currentLogSegment ??= new(); var logWriter = segment.CreateLogWriter(_id); logWriter.AppendEntry(action, state); } } public void AppendEntries(Action action, TState state) { lock (_manager._lock) { var segment = _manager._currentLogSegment ??= new(); var logWriter = segment.CreateLogWriter(_id); action(state, logWriter); } } } private readonly struct WorkItem(StateMachineManager.WorkItemType type, TaskCompletionSource? completion) { public WorkItemType Type { get; } = type; public TaskCompletionSource? CompletionSource { get; } = completion; public object? Context { get; init; } } private enum WorkItemType { Initialize, AppendLog, WriteSnapshot, DeleteState, RegisterStateMachine } private enum ManagerState { Unknown, Ready } private sealed class StateMachineManagerState( StateMachineManager manager, IFieldCodec keyCodec, IFieldCodec valueCodec, SerializerSessionPool serializerSessionPool) : DurableDictionary(keyCodec, valueCodec, serializerSessionPool) { public const int Id = 0; private readonly StateMachineManager _manager = manager; public void ResetVolatileState() => ((IDurableStateMachine)this).Reset(new StateMachineLogWriter(_manager, new(Id))); protected override void OnSet(string key, ulong value) => _manager.OnSetStateMachineId(key, value); } /// /// Used to track state machines that are not registered via user-code anymore, until time-based purging has elapsed. /// /// Resurrecting of retired machines is supported. private sealed class StateMachinesRetirementTracker( StateMachineManager manager, IFieldCodec keyCodec, IFieldCodec valueCodec, SerializerSessionPool sessionPool) : DurableDictionary(keyCodec, valueCodec, sessionPool) { public const int Id = 1; private readonly StateMachineLogWriter _logWriter = new(manager, new(Id)); protected override IStateMachineLogWriter GetStorage() => _logWriter; } /// /// Used to keep retired machines into a purgatory state until time-based purging or if a comeback occurs. /// This keeps buffering entries and dumps them back into the log upon compaction. /// [DebuggerDisplay(nameof(RetiredStateMachineVessel))] private sealed class RetiredStateMachineVessel : IDurableStateMachine { private readonly List _bufferedData = []; public ReadOnlyCollection BufferedData => _bufferedData.AsReadOnly(); void IDurableStateMachine.AppendSnapshot(StateMachineStorageWriter snapshotWriter) { foreach (var data in _bufferedData) { snapshotWriter.AppendEntry(data); } } void IDurableStateMachine.Reset(IStateMachineLogWriter storage) => _bufferedData.Clear(); void IDurableStateMachine.Apply(ReadOnlySequence logEntry) => _bufferedData.Add(logEntry.ToArray()); void IDurableStateMachine.AppendEntries(StateMachineStorageWriter logWriter) { } IDurableStateMachine IDurableStateMachine.DeepCopy() => throw new NotSupportedException(); } [LoggerMessage( Level = LogLevel.Error, Message = "Error processing work items.")] private static partial void LogErrorProcessingWorkItems(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "State machine \"{Name}\" was not found. I have substituted a placeholder for graceful time-based retirement.")] private static partial void LogRetiredStateMachineDetected(ILogger logger, string name); [LoggerMessage( Level = LogLevel.Information, Message = "State machine \"{Name}\" was previously retired (but not removed), and has hence been re-introduced. " + "There is still time left before its permanent removal, so I will resurrect it.")] private static partial void LogRetiredStateMachineComebackDetected(ILogger logger, string name); [LoggerMessage( Level = LogLevel.Information, Message = "Removing retired state machine \"{Name}\" and its data. Operation will be durably persisted shortly after compaction has finalized.")] private static partial void LogRemovingRetiredStateMachine(ILogger logger, string name); } ================================================ FILE: src/Orleans.Journaling/StateMachineManagerOptions.cs ================================================ namespace Orleans.Journaling; /// /// Options to configure the . /// public sealed class StateMachineManagerOptions { /// /// Specifies the period of time to wait until the manager retires /// a if its not registered in the manager anymore. /// /// /// The act of retirement removes this state machine from the log. /// If the state machine is reintroduced (within the grace period), than it will not be removed by the manager. /// /// This value represents the minimum time the fate of the state machine will be postponed. /// The final decision can take longer - usually + [time until next compaction occurs]. /// /// public TimeSpan RetirementGracePeriod { get; set; } = DEFAULT_RETIREMENT_GRACE_PERIOD; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_RETIREMENT_GRACE_PERIOD = TimeSpan.FromDays(7); } ================================================ FILE: src/Orleans.Journaling/StateMachineStorageWriter.cs ================================================ using System.Buffers; namespace Orleans.Journaling; public readonly struct StateMachineStorageWriter { private readonly StateMachineId _id; private readonly LogExtentBuilder _segment; internal StateMachineStorageWriter(StateMachineId id, LogExtentBuilder segment) { _id = id; _segment = segment; } public void AppendEntry(byte[] value) => _segment.AppendEntry(_id, value); public void AppendEntry(Span value) => _segment.AppendEntry(_id, value); public void AppendEntry(Memory value) => _segment.AppendEntry(_id, value); public void AppendEntry(ReadOnlyMemory value) => _segment.AppendEntry(_id, value); public void AppendEntry(ArraySegment value) => _segment.AppendEntry(_id, value); public void AppendEntry(ReadOnlySpan value) => _segment.AppendEntry(_id, value); public void AppendEntry(ReadOnlySequence value) => _segment.AppendEntry(_id, value); public void AppendEntry(Action> valueWriter, T value) => _segment.AppendEntry(_id, valueWriter, value); } ================================================ FILE: src/Orleans.Journaling/VolatileStateMachineStorage.cs ================================================ using Orleans.Serialization.Buffers; using System.Collections.Concurrent; using System.Runtime.CompilerServices; namespace Orleans.Journaling; public sealed class VolatileStateMachineStorageProvider : IStateMachineStorageProvider { private readonly ConcurrentDictionary _storage = new(); public IStateMachineStorage Create(IGrainContext grainContext) => _storage.GetOrAdd(grainContext.GrainId, _ => new VolatileStateMachineStorage()); } /// /// An in-memory, volatile implementation of for non-durable use cases, such as development and testing. /// public sealed class VolatileStateMachineStorage : IStateMachineStorage { private readonly List _segments = []; public bool IsCompactionRequested => _segments.Count > 10; /// public async IAsyncEnumerable ReadAsync([EnumeratorCancellation] CancellationToken cancellationToken) { await Task.CompletedTask; using var buffer = new ArcBufferWriter(); foreach (var segment in _segments) { cancellationToken.ThrowIfCancellationRequested(); buffer.Write(segment); yield return new LogExtent(buffer.ConsumeSlice(segment.Length)); } } /// public ValueTask AppendAsync(LogExtentBuilder segment, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _segments.Add(segment.ToArray()); return default; } /// public ValueTask ReplaceAsync(LogExtentBuilder snapshot, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _segments.Clear(); _segments.Add(snapshot.ToArray()); return default; } public ValueTask DeleteAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _segments.Clear(); return default; } } ================================================ FILE: src/Orleans.Persistence.Memory/Hosting/MemoryGrainStorageProviderBuilder.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime.Hosting.ProviderConfiguration; using Orleans.Storage; [assembly: RegisterProvider("Memory", "GrainStorage", "Silo", typeof(MemoryGrainStorageProviderBuilder))] namespace Orleans.Runtime.Hosting.ProviderConfiguration; internal sealed class MemoryGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddMemoryGrainStorage(name, (OptionsBuilder optionsBuilder) => optionsBuilder.Configure((options, services) => { if (int.TryParse(configurationSection[nameof(options.NumStorageGrains)], out var nsg)) { options.NumStorageGrains = nsg; } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } })); } } ================================================ FILE: src/Orleans.Persistence.Memory/Hosting/MemoryGrainStorageSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Hosting; using Orleans.Storage; namespace Orleans.Hosting { /// /// Silo host builder extensions /// public static class MemoryGrainStorageSiloBuilderExtensions { /// /// Configure silo to use memory grain storage as the default grain storage. /// /// The builder. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddMemoryGrainStorageAsDefault(ob => ob.Configure(configureOptions)); } /// /// Configure silo to use memory grain storage. /// /// The builder. /// The name of the storage provider. This must match with the StorageName property specified when injecting state into a grain. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.AddMemoryGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configure silo to use memory grain storage as the default grain storage. /// /// The builder. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptions = null) { return builder.AddMemoryGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configure silo to use memory grain storage. /// /// The builder. /// The name of the storage provider. This must match with the StorageName property specified when injecting state into a grain. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryGrainStorage(this ISiloBuilder builder, string name, Action> configureOptions = null) { return builder .ConfigureServices(services => { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); services.ConfigureNamedOptionForLogging(name); services.AddGrainStorage(name, MemoryGrainStorageFactory.Create); }); } } } ================================================ FILE: src/Orleans.Persistence.Memory/Options/MemoryGrainStorageOptions.cs ================================================ using Orleans.Runtime; using Orleans.Storage; namespace Orleans.Configuration { /// /// Options for MemoryGrainStorage /// public class MemoryGrainStorageOptions : IStorageProviderSerializerOptions { /// /// Default number of queue storage grains. /// public const int NumStorageGrainsDefaultValue = 10; /// /// Gets or sets the number of store grains to use. /// public int NumStorageGrains { get; set; } = NumStorageGrainsDefaultValue; /// /// Gets or sets the stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = DEFAULT_INIT_STAGE; /// public IGrainStorageSerializer GrainStorageSerializer { get; set; } /// /// Default init stage /// public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; } /// /// Validates . /// public class MemoryGrainStorageOptionsValidator : IConfigurationValidator { private readonly MemoryGrainStorageOptions options; private readonly string name; /// /// Initializes a new instance of the class. /// /// The options. /// The name. public MemoryGrainStorageOptionsValidator(MemoryGrainStorageOptions options, string name) { this.options = options; this.name = name; } /// public void ValidateConfiguration() { if (this.options.NumStorageGrains <= 0) throw new OrleansConfigurationException( $"Configuration for {nameof(MemoryGrainStorage)} {name} is invalid. {nameof(this.options.NumStorageGrains)} must be larger than 0."); if(this.options.InitStage < ServiceLifecycleStage.RuntimeGrainServices) throw new OrleansConfigurationException( $"Configuration for {nameof(MemoryGrainStorage)} {name} is invalid. {nameof(this.options.InitStage)} must be larger than {ServiceLifecycleStage.RuntimeGrainServices} since " + $"{nameof(MemoryGrainStorage)} depends on {nameof(MemoryStorageGrain)} to have grain environment to finish set up."); } } } ================================================ FILE: src/Orleans.Persistence.Memory/Orleans.Persistence.Memory.csproj ================================================ Microsoft.Orleans.Persistence.Memory Microsoft Orleans grain persistence provider which holds state only in memory. In-memory storage for Microsoft Orleans $(DefaultTargetFrameworks) Orleans.Persistence.Memory Orleans.Persistence.Memory true ================================================ FILE: src/Orleans.Persistence.Memory/Storage/MemoryStorage.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization.Serializers; using Orleans.Storage.Internal; namespace Orleans.Storage { /// /// This is a simple in-memory grain implementation of a storage provider. /// /// /// This storage provider is ONLY intended for simple in-memory Development / Unit Test scenarios. /// This class should NOT be used in Production environment, /// because [by-design] it does not provide any resilience /// or long-term persistence capabilities. /// [DebuggerDisplay("MemoryStore:{" + nameof(name) + "}")] public partial class MemoryGrainStorage : IGrainStorage, IDisposable { private Lazy[] storageGrains; private readonly ILogger logger; private readonly IActivatorProvider _activatorProvider; private readonly IGrainStorageSerializer storageSerializer; /// Name of this storage provider instance. private readonly string name; /// /// Initializes a new instance of the class. /// /// The name. /// The options. /// The logger. /// The grain factory. /// The default grain storage serializer. public MemoryGrainStorage( string name, MemoryGrainStorageOptions options, ILogger logger, IGrainFactory grainFactory, IGrainStorageSerializer defaultGrainStorageSerializer, IActivatorProvider activatorProvider) { this.name = name; this.logger = logger; _activatorProvider = activatorProvider; this.storageSerializer = options.GrainStorageSerializer ?? defaultGrainStorageSerializer; LogDebugInit(name, options.NumStorageGrains); storageGrains = new Lazy[options.NumStorageGrains]; for (int i = 0; i < storageGrains.Length; i++) { int idx = i; // Capture variable to avoid modified closure error storageGrains[idx] = new Lazy(() => grainFactory.GetGrain(idx)); } } /// public virtual async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var key = MakeKey(grainType, grainId); LogTraceRead(key); IMemoryStorageGrain storageGrain = GetStorageGrain(key); var state = await storageGrain.ReadStateAsync>(key); if (state != null) { var loadedState = ConvertFromStorageFormat(state.State); grainState.ETag = state.ETag; grainState.State = loadedState ?? CreateInstance(); grainState.RecordExists = loadedState != null; } else { grainState.ETag = null; grainState.State = CreateInstance(); grainState.RecordExists = false; } } /// public virtual async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var key = MakeKey(grainType, grainId); LogTraceWrite(key, grainState.State!, grainState.ETag); IMemoryStorageGrain storageGrain = GetStorageGrain(key); try { var data = ConvertToStorageFormat(grainState.State); var binaryGrainState = new GrainState>(data, grainState.ETag) { RecordExists = grainState.RecordExists }; grainState.ETag = await storageGrain.WriteStateAsync(key, binaryGrainState); grainState.RecordExists = true; } catch (MemoryStorageEtagMismatchException e) { throw e.AsInconsistentStateException(); } } /// public virtual async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var key = MakeKey(grainType, grainId); LogTraceDelete(key, grainState.ETag); IMemoryStorageGrain storageGrain = GetStorageGrain(key); try { await storageGrain.DeleteStateAsync>(key, grainState.ETag); grainState.ETag = null; grainState.RecordExists = false; grainState.State = CreateInstance(); } catch (MemoryStorageEtagMismatchException e) { throw e.AsInconsistentStateException(); } } private static string MakeKey(string grainType, GrainId grainId) => $"{grainType}/{grainId}"; private IMemoryStorageGrain GetStorageGrain(string id) { var idx = (uint)id.GetHashCode() % (uint)storageGrains.Length; return storageGrains[idx].Value; } /// public void Dispose() { } /// /// Deserialize from binary data /// /// The serialized stored data internal T? ConvertFromStorageFormat(ReadOnlyMemory data) { T? dataValue = default; try { dataValue = this.storageSerializer.Deserialize(data); } catch (Exception exc) { var sb = new StringBuilder(); if (data.ToArray().Length > 0) { sb.AppendFormat("Unable to convert from storage format GrainStateEntity.Data={0}", data); } if (dataValue != null) { sb.AppendFormat("Data Value={0} Type={1}", dataValue, dataValue.GetType()); } LogError(sb, exc); throw new AggregateException(sb.ToString(), exc); } return dataValue; } /// /// Serialize to the storage format. /// /// The grain state data to be serialized /// /// See: /// http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx /// for more on the JSON serializer. /// internal ReadOnlyMemory ConvertToStorageFormat(T grainState) { // Convert to binary format return this.storageSerializer.Serialize(grainState); } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( Level = LogLevel.Debug, Message = "Init: Name={Name} NumStorageGrains={NumStorageGrains}" )] private partial void LogDebugInit(string name, int numStorageGrains); [LoggerMessage( Level = LogLevel.Trace, Message = "Read Keys={Keys}" )] private partial void LogTraceRead(string keys); [LoggerMessage( Level = LogLevel.Trace, Message = "Write Keys={Keys} Data={Data} Etag={Etag}" )] private partial void LogTraceWrite(string keys, object data, string etag); [LoggerMessage( Level = LogLevel.Trace, Message = "Delete Keys={Keys} Etag={Etag}" )] private partial void LogTraceDelete(string keys, string etag); [LoggerMessage( Level = LogLevel.Error, Message = "{Message}" )] private partial void LogError(StringBuilder message, Exception exception); } /// /// Factory for creating MemoryGrainStorage /// public static class MemoryGrainStorageFactory { /// /// Creates a new instance. /// /// The services. /// The name. /// The storage. public static MemoryGrainStorage Create(IServiceProvider services, string name) { return ActivatorUtilities.CreateInstance(services, services.GetRequiredService>().Get(name), name); } } } ================================================ FILE: src/Orleans.Persistence.Memory/Storage/MemoryStorageEtagMismatchException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Storage.Internal { /// Exception used to communicate with the storage provider, so that it throws this exception to its caller. [Serializable] [GenerateSerializer] internal sealed class MemoryStorageEtagMismatchException : Exception { /// Gets the Etag value currently held in persistent storage. [Id(0)] public string StoredEtag { get; private set; } /// Gets the Etag value currently help in memory, and attempting to be updated. [Id(1)] public string ReceivedEtag { get; private set; } /// /// Initializes a new instance of the class. /// /// The stored etag. /// The received etag. public MemoryStorageEtagMismatchException(string storedEtag, string receivedEtag) { StoredEtag = storedEtag; ReceivedEtag = receivedEtag; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. [Obsolete] private MemoryStorageEtagMismatchException(SerializationInfo info, StreamingContext context) : base(info, context) { this.StoredEtag = info.GetString(nameof(StoredEtag)); this.ReceivedEtag = info.GetString(nameof(ReceivedEtag)); } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException(nameof(info)); info.AddValue(nameof(StoredEtag), this.StoredEtag); info.AddValue(nameof(ReceivedEtag), this.ReceivedEtag); base.GetObjectData(info, context); } /// /// Converts this instance into an . /// /// A new . public InconsistentStateException AsInconsistentStateException() { var message = $"e-Tag mismatch in Memory Storage. Stored = { StoredEtag ?? "null"} Received = {ReceivedEtag}"; return new InconsistentStateException(message, StoredEtag, ReceivedEtag, this); } } } ================================================ FILE: src/Orleans.Persistence.Memory/Storage/MemoryStorageGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Storage.Internal; namespace Orleans.Storage { /// /// Implementation class for the Storage Grain used by In-memory storage provider /// Orleans.Storage.MemoryStorage /// [KeepAlive] internal partial class MemoryStorageGrain : Grain, IMemoryStorageGrain { private readonly Dictionary _store = new(); private readonly ILogger _logger; public MemoryStorageGrain(ILogger logger) { _logger = logger; } public Task> ReadStateAsync(string grainStoreKey) { LogDebugReadState(grainStoreKey); _store.TryGetValue(grainStoreKey, out var entry); return Task.FromResult((IGrainState)entry); } public Task WriteStateAsync(string grainStoreKey, IGrainState grainState) { LogDebugWriteState(grainStoreKey, grainState.ETag); var currentETag = GetETagFromStorage(grainStoreKey); ValidateEtag(currentETag, grainState.ETag, grainStoreKey, "Update"); grainState.ETag = NewEtag(); _store[grainStoreKey] = grainState; LogDebugDoneWriteState(grainStoreKey, grainState.ETag); return Task.FromResult(grainState.ETag); } public Task DeleteStateAsync(string grainStoreKey, string etag) { LogDebugDeleteState(grainStoreKey, etag); var currentETag = GetETagFromStorage(grainStoreKey); ValidateEtag(currentETag, etag, grainStoreKey, "Delete"); // Do not remove it from the dictionary, just set the value to null to remember that this item // was once in the store, and now is deleted _store[grainStoreKey] = null; return Task.CompletedTask; } private static string NewEtag() { return Guid.NewGuid().ToString("N"); } private string GetETagFromStorage(string grainStoreKey) { string currentETag = null; if (_store.TryGetValue(grainStoreKey, out var entry)) { // If the entry is null, it was removed from storage currentETag = entry != null ? ((IGrainState)entry).ETag : string.Empty; } return currentETag; } private void ValidateEtag(string currentETag, string receivedEtag, string grainStoreKey, string operation) { // if we have no current etag, we will accept the users data. // This is a mitigation for when the memory storage grain is lost due to silo crash. if (currentETag == null) return; // if this is our first write, and we have an empty etag, we're good if (string.IsNullOrEmpty(currentETag) && receivedEtag == null) return; // if current state and new state have matching etags, or we're to ignore the ETag, we're good if (receivedEtag == currentETag || receivedEtag == "*") return; // else we have an etag mismatch LogWarningEtagMismatch(operation, grainStoreKey, currentETag, receivedEtag); throw new MemoryStorageEtagMismatchException(currentETag, receivedEtag); } [LoggerMessage( Level = LogLevel.Debug, Message = "ReadStateAsync for grain: {GrainStoreKey}" )] private partial void LogDebugReadState(string grainStoreKey); [LoggerMessage( Level = LogLevel.Debug, Message = "WriteStateAsync for grain: {GrainStoreKey} eTag: {ETag}" )] private partial void LogDebugWriteState(string grainStoreKey, string etag); [LoggerMessage( Level = LogLevel.Debug, Message = "Done WriteStateAsync for grain: {GrainStoreKey} eTag: {ETag}" )] private partial void LogDebugDoneWriteState(string grainStoreKey, string etag); [LoggerMessage( Level = LogLevel.Debug, Message = "DeleteStateAsync for grain: {GrainStoreKey} eTag: {ETag}" )] private partial void LogDebugDeleteState(string grainStoreKey, string etag); [LoggerMessage( Level = LogLevel.Warning, EventId = 0, Message = "Etag mismatch during {Operation} for grain {GrainStoreKey}: Expected = {Expected} Received = {Received}" )] private partial void LogWarningEtagMismatch(string operation, string grainStoreKey, string expected, string received); } } ================================================ FILE: src/Orleans.Persistence.Memory/Storage/MemoryStorageWithLatency.cs ================================================ using System; using System.Diagnostics; using System.Threading.Tasks; using Orleans.Runtime; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Serialization.Serializers; namespace Orleans.Storage { /// /// Options for the storage provider. /// public class MemoryStorageWithLatencyOptions : MemoryGrainStorageOptions { /// /// The default latency. /// public static readonly TimeSpan DefaultLatency = TimeSpan.FromMilliseconds(200); /// /// Gets or sets the latency. /// /// The latency. public TimeSpan Latency { get; set; } = DefaultLatency; /// /// Gets or sets a value indicating whether to mock calls instead of issuing real storage calls. /// /// if the provider should mock calls; otherwise, . public bool MockCallsOnly { get;set; } } /// /// This is a simple in-memory implementation of a storage provider which presents fixed latency of storage calls. /// This class is useful for system testing and investigation of the effects of storage latency. /// /// /// This storage provider is ONLY intended for simple in-memory Test scenarios. /// This class should NOT be used in Production environment, /// because [by-design] it does not provide any resilience /// or long-term persistence capabilities. /// [DebuggerDisplay("MemoryStore:{Name},WithLatency:{latency}")] public class MemoryGrainStorageWithLatency :IGrainStorage { private readonly MemoryGrainStorage baseGranStorage; private readonly MemoryStorageWithLatencyOptions options; /// Default constructor. public MemoryGrainStorageWithLatency( string name, MemoryStorageWithLatencyOptions options, ILoggerFactory loggerFactory, IGrainFactory grainFactory, IActivatorProvider activatorProvider, IGrainStorageSerializer defaultGrainStorageSerializer) { this.baseGranStorage = new MemoryGrainStorage( name, options, loggerFactory.CreateLogger(), grainFactory, defaultGrainStorageSerializer, activatorProvider); this.options = options; } /// Read state data function for this storage provider. /// public Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { return MakeFixedLatencyCall(() => baseGranStorage.ReadStateAsync(grainType, grainId, grainState)); } /// Write state data function for this storage provider. /// public Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { return MakeFixedLatencyCall(() => baseGranStorage.WriteStateAsync(grainType, grainId, grainState)); } /// Delete / Clear state data function for this storage provider. /// public Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { return MakeFixedLatencyCall(() => baseGranStorage.ClearStateAsync(grainType, grainId, grainState)); } private async Task MakeFixedLatencyCall(Func action) { var sw = Stopwatch.StartNew(); Exception error = null; try { if (this.options.MockCallsOnly) { // Simulated call with slight delay await Task.Delay(10); } else { // Make the real call await action(); } } catch (Exception exc) { error = exc; } do { // Work out the remaining time to wait so that this operation exceeds the required Latency. // Also adds an extra fudge factor to account for any system clock resolution edge cases. var extraDelay = TimeSpan.FromTicks( 5 * TimeSpan.TicksPerMillisecond + this.options.Latency.Ticks - sw.Elapsed.Ticks); if (extraDelay > TimeSpan.Zero) { await Task.Delay(extraDelay); } else { break; } } while (true); Debug.Assert(sw.Elapsed >= this.options.Latency, "sw.Elapsed >= this.options.Latency"); if (error != null) { // Wrap in AggregateException so that the original error stack trace is preserved. throw new AggregateException(error); } } } } ================================================ FILE: src/Orleans.Reminders/Constants/ReminderOptionsDefaults.cs ================================================ using Orleans.Hosting; namespace Orleans; internal static class ReminderOptionsDefaults { /// /// Minimum period for registering a reminder ... we want to enforce a lower bound . /// /// Increase this period, reminders are supposed to be less frequent ... we use 2 seconds just to reduce the running time of the unit tests public const uint MinimumReminderPeriodMinutes = 1; /// /// Period (in minutes) between refreshing local reminder list to reflect the global reminder table every . /// public const uint RefreshReminderListPeriodMinutes = 5; /// /// The maximum amount of time (in minutes) to attempt to initialize reminders giving up . /// public const uint InitializationTimeoutMinutes = 5; } ================================================ FILE: src/Orleans.Reminders/Constants/RemindersConstants.cs ================================================ namespace Orleans; internal static class RemindersConstants { internal const string LocalReminderService = nameof(LocalReminderService); } ================================================ FILE: src/Orleans.Reminders/ErrorCodes.cs ================================================ // ReSharper disable InconsistentNaming namespace Orleans.Reminders; /// /// The set of error codes used by the Orleans runtime libraries for logging errors. For Reminders. /// public enum RSErrorCode { ReminderServiceBase = /* Runtime */ 100000 + 2900, RS_Register_TableError = ReminderServiceBase + 5, RS_Register_AlreadyRegistered = ReminderServiceBase + 7, RS_Register_InvalidPeriod = ReminderServiceBase + 8, RS_Register_NotRemindable = ReminderServiceBase + 9, RS_NotResponsible = ReminderServiceBase + 10, RS_Unregister_NotFoundLocally = ReminderServiceBase + 11, RS_Unregister_TableError = ReminderServiceBase + 12, RS_Table_Insert = ReminderServiceBase + 13, RS_Table_Remove = ReminderServiceBase + 14, RS_Tick_Delivery_Error = ReminderServiceBase + 15, RS_Not_Started = ReminderServiceBase + 16, RS_UnregisterGrain_TableError = ReminderServiceBase + 17, RS_GrainBasedTable1 = ReminderServiceBase + 18, RS_Factory1 = ReminderServiceBase + 19, RS_FailedToReadTableAndStartTimer = ReminderServiceBase + 20, RS_TableGrainInit1 = ReminderServiceBase + 21, RS_TableGrainInit2 = ReminderServiceBase + 22, RS_TableGrainInit3 = ReminderServiceBase + 23, RS_GrainBasedTable2 = ReminderServiceBase + 24, RS_ServiceStarting = ReminderServiceBase + 25, RS_ServiceStarted = ReminderServiceBase + 26, RS_ServiceStopping = ReminderServiceBase + 27, RS_RegisterOrUpdate = ReminderServiceBase + 28, RS_Unregister = ReminderServiceBase + 29, RS_Stop = ReminderServiceBase + 30, RS_RemoveFromTable = ReminderServiceBase + 31, RS_GetReminder = ReminderServiceBase + 32, RS_GetReminders = ReminderServiceBase + 33, RS_RangeChanged = ReminderServiceBase + 34, RS_LocalStop = ReminderServiceBase + 35, RS_Started = ReminderServiceBase + 36, RS_ServiceInitialLoadFailing = ReminderServiceBase + 37, RS_ServiceInitialLoadFailed = ReminderServiceBase + 38, RS_FastReminderInterval = ReminderServiceBase + 39, } ================================================ FILE: src/Orleans.Reminders/GrainReminderExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Timers; #nullable enable namespace Orleans; /// /// Extension methods for accessing reminders from a or implementation. /// public static class GrainReminderExtensions { /// /// Registers a persistent, reliable reminder to send regular notifications (reminders) to the grain. /// The grain must implement the Orleans.IRemindable interface, and reminders for this grain will be sent to the ReceiveReminder callback method. /// If the current grain is deactivated when the timer fires, a new activation of this grain will be created to receive this reminder. /// If an existing reminder with the same name already exists, that reminder will be overwritten with this new reminder. /// Reminders will always be received by one activation of this grain, even if multiple activations exist for this grain. /// /// The grain instance. /// Name of this reminder /// Due time for this reminder /// Frequency period for this reminder /// Promise for Reminder handle. public static Task RegisterOrUpdateReminder(this Grain grain, string reminderName, TimeSpan dueTime, TimeSpan period) => RegisterOrUpdateReminder(grain is IRemindable, grain?.GrainContext, reminderName, dueTime, period); /// /// Registers a persistent, reliable reminder to send regular notifications (reminders) to the grain. /// The grain must implement the Orleans.IRemindable interface, and reminders for this grain will be sent to the ReceiveReminder callback method. /// If the current grain is deactivated when the timer fires, a new activation of this grain will be created to receive this reminder. /// If an existing reminder with the same name already exists, that reminder will be overwritten with this new reminder. /// Reminders will always be received by one activation of this grain, even if multiple activations exist for this grain. /// /// The grain instance. /// Name of this reminder /// Due time for this reminder /// Frequency period for this reminder /// Promise for Reminder handle. public static Task RegisterOrUpdateReminder(this IGrainBase grain, string reminderName, TimeSpan dueTime, TimeSpan period) => RegisterOrUpdateReminder(grain is IRemindable, grain?.GrainContext, reminderName, dueTime, period); private static Task RegisterOrUpdateReminder(bool remindable, IGrainContext? grainContext, string reminderName, TimeSpan dueTime, TimeSpan period) { ArgumentNullException.ThrowIfNull(grainContext, "grain"); if (string.IsNullOrWhiteSpace(reminderName)) throw new ArgumentNullException(nameof(reminderName)); if (!remindable) throw new InvalidOperationException($"Grain {grainContext.GrainId} is not '{nameof(IRemindable)}'. A grain should implement {nameof(IRemindable)} to use the persistent reminder service"); return GetReminderRegistry(grainContext).RegisterOrUpdateReminder(grainContext.GrainId, reminderName, dueTime, period); } /// /// Unregisters a previously registered reminder. /// /// The grain instance. /// Reminder to unregister. /// Completion promise for this operation. public static Task UnregisterReminder(this Grain grain, IGrainReminder reminder) => UnregisterReminder(grain?.GrainContext, reminder); /// /// Unregisters a previously registered reminder. /// /// The grain instance. /// Reminder to unregister. /// Completion promise for this operation. public static Task UnregisterReminder(this IGrainBase grain, IGrainReminder reminder) => UnregisterReminder(grain?.GrainContext, reminder); private static Task UnregisterReminder(IGrainContext? grainContext, IGrainReminder reminder) { ArgumentNullException.ThrowIfNull(grainContext, "grain"); return GetReminderRegistry(grainContext).UnregisterReminder(grainContext.GrainId, reminder); } /// /// Returns a previously registered reminder. /// /// The grain instance. /// Reminder to return /// Promise for Reminder handle. public static Task GetReminder(this Grain grain, string reminderName) => GetReminder(grain?.GrainContext, reminderName); /// /// Returns a previously registered reminder. /// /// A grain. /// Reminder to return /// Promise for Reminder handle. public static Task GetReminder(this IGrainBase grain, string reminderName) => GetReminder(grain?.GrainContext, reminderName); private static Task GetReminder(IGrainContext? grainContext, string reminderName) { ArgumentNullException.ThrowIfNull(grainContext, "grain"); if (string.IsNullOrWhiteSpace(reminderName)) throw new ArgumentNullException(nameof(reminderName)); return GetReminderRegistry(grainContext).GetReminder(grainContext.GrainId, reminderName); } /// /// Returns a list of all reminders registered by the grain. /// /// Promise for list of Reminders registered for this grain. public static Task> GetReminders(this Grain grain) => GetReminders(grain?.GrainContext); /// /// Returns a list of all reminders registered by the grain. /// /// Promise for list of Reminders registered for this grain. public static Task> GetReminders(this IGrainBase grain) => GetReminders(grain?.GrainContext); private static Task> GetReminders(IGrainContext? grainContext) { ArgumentNullException.ThrowIfNull(grainContext, "grain"); return GetReminderRegistry(grainContext).GetReminders(grainContext.GrainId); } /// /// Gets the . /// private static IReminderRegistry GetReminderRegistry(IGrainContext grainContext) { return grainContext.ActivationServices.GetRequiredService(); } } ================================================ FILE: src/Orleans.Reminders/Hosting/MemoryReminderTableBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime.Hosting.ProviderConfiguration; [assembly: RegisterProvider("Memory", "Reminders", "Silo", typeof(MemoryReminderTableBuilder))] namespace Orleans.Runtime.Hosting.ProviderConfiguration; internal sealed class MemoryReminderTableBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseInMemoryReminderService(); } } ================================================ FILE: src/Orleans.Reminders/Hosting/SiloBuilderReminderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Configuration.Internal; using System.Linq; using Orleans.Runtime.ReminderService; using Orleans.Timers; namespace Orleans.Hosting; public static class SiloBuilderReminderExtensions { /// /// Adds support for reminders to this silo. /// /// The builder. /// The silo builder. public static ISiloBuilder AddReminders(this ISiloBuilder builder) => builder.ConfigureServices(services => AddReminders(services)); /// /// Add support for reminders to this client. /// /// The services. public static void AddReminders(this IServiceCollection services) { if (services.Any(service => service.ServiceType.Equals(typeof(LocalReminderService)))) { return; } services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, LocalReminderService>(); services.AddSingleton(); } } ================================================ FILE: src/Orleans.Reminders/Hosting/SiloBuilderReminderMemoryExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration.Internal; using Orleans.Runtime; using Orleans.Runtime.ReminderService; namespace Orleans.Hosting { /// /// Extensions to for configuring the in-memory reminder provider. /// public static class SiloBuilderReminderMemoryExtensions { /// /// Configures reminder storage using an in-memory, non-persistent store. /// /// /// Note that this is for development and testing scenarios only and should not be used in production. /// /// The silo host builder. /// The provided , for chaining. public static ISiloBuilder UseInMemoryReminderService(this ISiloBuilder builder) { builder.AddReminders(); // The reminder table is a reference to a singleton IReminderTableGrain. builder.ConfigureServices(services => services.UseInMemoryReminderService()); return builder; } /// /// Configures reminder storage using an in-memory, non-persistent store. /// /// /// Note that this is for development and testing scenarios only and should not be used in production. /// /// The service collection. /// The provided , for chaining. internal static IServiceCollection UseInMemoryReminderService(this IServiceCollection services) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, InMemoryReminderTable>(); return services; } } } ================================================ FILE: src/Orleans.Reminders/Options/ReminderOptions.cs ================================================ using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Orleans.Reminders; using Orleans.Runtime; namespace Orleans.Hosting; /// /// Options for the reminder service. /// public sealed class ReminderOptions { /// /// Gets or sets the minimum period for reminders. /// /// /// High-frequency reminders are dangerous for production systems. /// public TimeSpan MinimumReminderPeriod { get; set; } = TimeSpan.FromMinutes(ReminderOptionsDefaults.MinimumReminderPeriodMinutes); /// /// Gets or sets the period between reminder table refreshes. /// /// Refresh the reminder table every 5 minutes by default. public TimeSpan RefreshReminderListPeriod { get; set; } = TimeSpan.FromMinutes(ReminderOptionsDefaults.RefreshReminderListPeriodMinutes); /// /// Gets or sets the maximum amount of time to attempt to initialize reminders before giving up. /// /// Attempt to initialize for 5 minutes before giving up by default. public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromMinutes(ReminderOptionsDefaults.InitializationTimeoutMinutes); } /// /// Validator for . /// internal sealed partial class ReminderOptionsValidator : IConfigurationValidator { private readonly ILogger logger; private readonly IOptions options; /// /// Initializes a new instance of the class. /// /// /// The logger. /// /// /// The reminder options. /// public ReminderOptionsValidator(ILogger logger, IOptions reminderOptions) { this.logger = logger; options = reminderOptions; } /// public void ValidateConfiguration() { if (options.Value.MinimumReminderPeriod < TimeSpan.Zero) { throw new OrleansConfigurationException($"{nameof(ReminderOptions)}.{nameof(ReminderOptions.MinimumReminderPeriod)} must not be less than {TimeSpan.Zero}"); } if (options.Value.MinimumReminderPeriod.TotalMinutes < ReminderOptionsDefaults.MinimumReminderPeriodMinutes) { LogWarnFastReminderInterval(options.Value.MinimumReminderPeriod, ReminderOptionsDefaults.MinimumReminderPeriodMinutes); } } [LoggerMessage( Level = LogLevel.Warning, EventId = (int)RSErrorCode.RS_FastReminderInterval, Message = $"{nameof(ReminderOptions)}.{nameof(ReminderOptions.MinimumReminderPeriod)} is {{MinimumReminderPeriod}} (default {{MinimumReminderPeriodMinutes}}. High-Frequency reminders are unsuitable for production use." )] private partial void LogWarnFastReminderInterval(TimeSpan minimumReminderPeriod, uint minimumReminderPeriodMinutes); } ================================================ FILE: src/Orleans.Reminders/Orleans.Reminders.csproj ================================================ Microsoft.Orleans.Reminders Microsoft Orleans Reminders Library Reminders library for Microsoft Orleans used on the server. $(DefaultTargetFrameworks) true false $(DefineConstants);ORLEANS_REMINDERS_PROVIDER ================================================ FILE: src/Orleans.Reminders/ReminderService/GrainBasedReminderTable.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Reminders; namespace Orleans.Runtime.ReminderService { [Reentrant] [KeepAlive] internal sealed partial class ReminderTableGrain : Grain, IReminderTableGrain, IGrainMigrationParticipant { private readonly ILogger _logger; private Dictionary> _reminderTable = new(); public ReminderTableGrain(ILogger logger) { _logger = logger; } public override Task OnActivateAsync(CancellationToken cancellationToken) { LogDebugActivated(); return Task.CompletedTask; } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { LogDebugDeactivated(); return Task.CompletedTask; } public Task TestOnlyClearTable() { LogDebugTestOnlyClearTable(); _reminderTable.Clear(); return Task.CompletedTask; } public Task ReadRows(GrainId grainId) { var result = _reminderTable.TryGetValue(grainId, out var reminders) ? new ReminderTableData(reminders.Values) : new(); return Task.FromResult(result); } public Task ReadRows(uint begin, uint end) { var range = RangeFactory.CreateRange(begin, end); var list = new List(); foreach (var e in _reminderTable) if (range.InRange(e.Key)) list.AddRange(e.Value.Values); LogTraceSelectedReminders(list.Count, new(_reminderTable), range, new(list)); var result = new ReminderTableData(list); LogDebugReadReminders(result.Reminders.Count, new(result.Reminders)); return Task.FromResult(result); } public Task ReadRow(GrainId grainId, string reminderName) { ReminderEntry result = null; if (_reminderTable.TryGetValue(grainId, out var reminders)) { reminders.TryGetValue(reminderName, out result); } if (result is null) { LogTraceReminderNotFound(grainId, reminderName); } else { LogTraceReadRow(grainId, reminderName, result); } return Task.FromResult(result); } public Task UpsertRow(ReminderEntry entry) { entry.ETag = Guid.NewGuid().ToString(); var d = CollectionsMarshal.GetValueRefOrAddDefault(_reminderTable, entry.GrainId, out _) ??= new(); ref var entryRef = ref CollectionsMarshal.GetValueRefOrAddDefault(d, entry.ReminderName, out _); var old = entryRef; // tracing purposes only entryRef = entry; LogTraceUpsertedEntry(entry, old); return Task.FromResult(entry.ETag); } public Task RemoveRow(GrainId grainId, string reminderName, string eTag) { LogDebugRemoveRow(grainId, reminderName, eTag); if (_reminderTable.TryGetValue(grainId, out var data) && data.TryGetValue(reminderName, out var e) && e.ETag == eTag) { if (data.Count > 1) { data.Remove(reminderName); } else { _reminderTable.Remove(grainId); } return Task.FromResult(true); } LogWarningRemoveRow(grainId, reminderName, eTag, new(_reminderTable)); return Task.FromResult(false); } void IGrainMigrationParticipant.OnDehydrate(IDehydrationContext dehydrationContext) { dehydrationContext.TryAddValue("table", _reminderTable); } void IGrainMigrationParticipant.OnRehydrate(IRehydrationContext rehydrationContext) { if (rehydrationContext.TryGetValue("table", out Dictionary> table)) { _reminderTable = table; } } [LoggerMessage( Level = LogLevel.Debug, Message = "Activated" )] private partial void LogDebugActivated(); [LoggerMessage( Level = LogLevel.Debug, Message = "Deactivated" )] private partial void LogDebugDeactivated(); [LoggerMessage( Level = LogLevel.Debug, Message = "TestOnlyClearTable" )] private partial void LogDebugTestOnlyClearTable(); private readonly struct TotalCountLogRecord(Dictionary> reminderTable) { public override string ToString() => reminderTable.Values.Sum(r => r.Count).ToString(); } private readonly struct RemindersLogRecord(IEnumerable reminders) { public override string ToString() => Utils.EnumerableToString(reminders); } [LoggerMessage( Level = LogLevel.Trace, Message = "Selected {SelectCount} out of {TotalCount} reminders from memory for {Range}. Selected: {Reminders}" )] private partial void LogTraceSelectedReminders(int selectCount, TotalCountLogRecord totalCount, IRingRange range, RemindersLogRecord reminders); [LoggerMessage( Level = LogLevel.Debug, Message = "Read {ReminderCount} reminders from memory: {Reminders}" )] private partial void LogDebugReadReminders(int reminderCount, RemindersLogRecord reminders); [LoggerMessage( Level = LogLevel.Trace, Message = "Reminder not found for grain {Grain} reminder {ReminderName}" )] private partial void LogTraceReminderNotFound(GrainId grain, string reminderName); [LoggerMessage( Level = LogLevel.Trace, Message = "Read for grain {Grain} reminder {ReminderName} row {Reminder}" )] private partial void LogTraceReadRow(GrainId grain, string reminderName, ReminderEntry reminder); [LoggerMessage( Level = LogLevel.Trace, Message = "Upserted entry {Updated}, replaced {Replaced}" )] private partial void LogTraceUpsertedEntry(ReminderEntry updated, ReminderEntry replaced); [LoggerMessage( Level = LogLevel.Debug, Message = "RemoveRow Grain = {Grain}, ReminderName = {ReminderName}, eTag = {ETag}" )] private partial void LogDebugRemoveRow(GrainId grain, string reminderName, string eTag); private readonly struct NewValuesLogRecord(Dictionary> reminderTable) { public override string ToString() => Utils.EnumerableToString(reminderTable.Values.SelectMany(x => x.Values)); } [LoggerMessage( Level = LogLevel.Warning, EventId = (int)RSErrorCode.RS_Table_Remove, Message = "RemoveRow failed for Grain = {Grain}, ReminderName = {ReminderName}, eTag = {ETag}. Table now is: {NewValues}" )] private partial void LogWarningRemoveRow(GrainId grain, string reminderName, string eTag, NewValuesLogRecord newValues); } } ================================================ FILE: src/Orleans.Reminders/ReminderService/InMemoryReminderTable.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime.ReminderService { internal sealed class InMemoryReminderTable : IReminderTable, ILifecycleParticipant { internal const long ReminderTableGrainId = 12345; private readonly IReminderTableGrain reminderTableGrain; private bool isAvailable; public InMemoryReminderTable(IGrainFactory grainFactory) { this.reminderTableGrain = grainFactory.GetGrain(ReminderTableGrainId); } public Task Init() => Task.CompletedTask; public Task ReadRow(GrainId grainId, string reminderName) { this.ThrowIfNotAvailable(); return this.reminderTableGrain.ReadRow(grainId, reminderName); } public Task ReadRows(GrainId grainId) { this.ThrowIfNotAvailable(); return this.reminderTableGrain.ReadRows(grainId); } public Task ReadRows(uint begin, uint end) { return this.isAvailable ? this.reminderTableGrain.ReadRows(begin, end) : Task.FromResult(new ReminderTableData()); } public Task RemoveRow(GrainId grainId, string reminderName, string eTag) { this.ThrowIfNotAvailable(); return this.reminderTableGrain.RemoveRow(grainId, reminderName, eTag); } public Task TestOnlyClearTable() { this.ThrowIfNotAvailable(); return this.reminderTableGrain.TestOnlyClearTable(); } public Task UpsertRow(ReminderEntry entry) { this.ThrowIfNotAvailable(); return this.reminderTableGrain.UpsertRow(entry); } private void ThrowIfNotAvailable() { if (!this.isAvailable) throw new InvalidOperationException("The reminder service is not currently available."); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { Task OnApplicationServicesStart(CancellationToken ct) { this.isAvailable = true; return Task.CompletedTask; } Task OnApplicationServicesStop(CancellationToken ct) { this.isAvailable = false; return Task.CompletedTask; } lifecycle.Subscribe( nameof(InMemoryReminderTable), ServiceLifecycleStage.ApplicationServices, OnApplicationServicesStart, OnApplicationServicesStop); } } } ================================================ FILE: src/Orleans.Reminders/ReminderService/LocalReminderService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.CodeGeneration; using Orleans.GrainReferences; using Orleans.Hosting; using Orleans.Internal; using Orleans.Metadata; using Orleans.Runtime.ConsistentRing; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime.ReminderService { internal sealed partial class LocalReminderService : GrainService, IReminderService, ILifecycleParticipant { private const int InitialReadRetryCountBeforeFastFailForUpdates = 2; private static readonly TimeSpan InitialReadMaxWaitTimeForUpdates = TimeSpan.FromSeconds(20); private static readonly TimeSpan InitialReadRetryPeriod = TimeSpan.FromSeconds(30); private readonly ILogger logger; private readonly ReminderOptions reminderOptions; private readonly Dictionary localReminders = new(); private readonly IReminderTable reminderTable; private readonly TaskCompletionSource startedTask; private readonly IAsyncTimerFactory asyncTimerFactory; private readonly IAsyncTimer listRefreshTimer; // timer that refreshes our list of reminders to reflect global reminder table private readonly GrainReferenceActivator _referenceActivator; private readonly GrainInterfaceType _grainInterfaceType; private long localTableSequence; private uint initialReadCallCount = 0; private Task runTask; public LocalReminderService( GrainReferenceActivator referenceActivator, GrainInterfaceTypeResolver interfaceTypeResolver, IReminderTable reminderTable, IAsyncTimerFactory asyncTimerFactory, IOptions reminderOptions, IConsistentRingProvider ringProvider, SystemTargetShared shared) : base( SystemTargetGrainId.CreateGrainServiceGrainId(GrainInterfaceUtils.GetGrainClassTypeCode(typeof(IReminderService)), null, shared.SiloAddress), ringProvider, shared) { _referenceActivator = referenceActivator; _grainInterfaceType = interfaceTypeResolver.GetGrainInterfaceType(typeof(IRemindable)); this.reminderOptions = reminderOptions.Value; this.reminderTable = reminderTable; this.asyncTimerFactory = asyncTimerFactory; ReminderInstruments.RegisterActiveRemindersObserve(() => localReminders.Count); startedTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this.logger = shared.LoggerFactory.CreateLogger(); this.listRefreshTimer = asyncTimerFactory.Create(this.reminderOptions.RefreshReminderListPeriod, "ReminderService.ReminderListRefresher"); shared.ActivationDirectory.RecordNewTarget(this); } void ILifecycleParticipant.Participate(ISiloLifecycle observer) { observer.Subscribe( nameof(LocalReminderService), ServiceLifecycleStage.BecomeActive, async ct => { try { await this.QueueTask(() => Initialize(ct)); } catch (Exception exception) { LogErrorActivatingReminderService(exception); throw; } }, async ct => { try { await this.QueueTask(Stop).WaitAsync(ct); } catch (Exception exception) { LogErrorStoppingReminderService(exception); throw; } }); observer.Subscribe( nameof(LocalReminderService), ServiceLifecycleStage.Active, async ct => { using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(this.reminderOptions.InitializationTimeout); try { await this.QueueTask(Start).WaitAsync(cts.Token); } catch (Exception exception) { LogErrorStartingReminderService(exception); throw; } }, ct => Task.CompletedTask); } /// /// Attempt to retrieve reminders, that are my responsibility, from the global reminder table when starting this silo (reminder service instance) /// /// private async Task Initialize(CancellationToken cancellationToken) { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(this.reminderOptions.InitializationTimeout); // Confirm that it can access the underlying store, as after this the ReminderService will load in the background, without the opportunity to prevent the Silo from starting await reminderTable.StartAsync(cts.Token); } public async override Task Stop() { await base.Stop(); if (listRefreshTimer != null) { listRefreshTimer.Dispose(); if (this.runTask is Task task) { await task; } } foreach (LocalReminderData r in localReminders.Values) { r.StopReminder(); } await reminderTable.StopAsync(); // For a graceful shutdown, also handover reminder responsibilities to new owner, and update the ReminderTable // currently, this is taken care of by periodically reading the reminder table } public async Task RegisterOrUpdateReminder(GrainId grainId, string reminderName, TimeSpan dueTime, TimeSpan period) { var entry = new ReminderEntry { GrainId = grainId, ReminderName = reminderName, StartAt = DateTime.UtcNow.Add(dueTime), Period = period, }; LogDebugRegisterOrUpdateReminder(entry); await DoResponsibilitySanityCheck(grainId, "RegisterReminder"); var newEtag = await reminderTable.UpsertRow(entry); if (newEtag != null) { LogDebugRegisterReminder(entry, localTableSequence); entry.ETag = newEtag; StartAndAddTimer(entry); if (logger.IsEnabled(LogLevel.Trace)) PrintReminders(); return new ReminderData(grainId, reminderName, newEtag); } LogErrorRegisterReminder(entry); throw new ReminderException($"Could not register reminder {entry} to reminder table due to a race. Please try again later."); } /// /// Stop the reminder locally, and remove it from the external storage system /// /// /// public async Task UnregisterReminder(IGrainReminder reminder) { var remData = (ReminderData)reminder; LogDebugUnregisterReminder(reminder, localTableSequence); var grainId = remData.GrainId; string reminderName = remData.ReminderName; string eTag = remData.ETag; await DoResponsibilitySanityCheck(grainId, "RemoveReminder"); // it may happen that we dont have this reminder locally ... even then, we attempt to remove the reminder from the reminder // table ... the periodic mechanism will stop this reminder at any silo's LocalReminderService that might have this reminder locally // remove from persistent/memory store var success = await reminderTable.RemoveRow(grainId, reminderName, eTag); if (success) { bool removed = TryStopPreviousTimer(grainId, reminderName); if (removed) { LogStoppedReminder(reminder); if (logger.IsEnabled(LogLevel.Trace)) PrintReminders($"After removing {reminder}."); } else { // no-op LogRemovedReminderFromTable(reminder); } } else { LogErrorUnregisterReminder(reminder); throw new ReminderException($"Could not unregister reminder {reminder} from the reminder table, due to tag mismatch. You can retry."); } } public async Task GetReminder(GrainId grainId, string reminderName) { LogDebugGetReminder(grainId, reminderName); var entry = await reminderTable.ReadRow(grainId, reminderName); return entry == null ? null : entry.ToIGrainReminder(); } public async Task> GetReminders(GrainId grainId) { LogDebugGetReminders(grainId); var tableData = await reminderTable.ReadRows(grainId); return tableData.Reminders.Select(entry => entry.ToIGrainReminder()).ToList(); } /// /// Attempt to retrieve reminders from the global reminder table /// private Task ReadAndUpdateReminders() { if (StoppedCancellationTokenSource.IsCancellationRequested) return Task.CompletedTask; RemoveOutOfRangeReminders(); // try to retrieve reminders from all my subranges var rangeSerialNumberCopy = RangeSerialNumber; LogTraceRingRange(RingRange, RangeSerialNumber, localReminders.Count); var acks = new List(); foreach (var range in RangeFactory.GetSubRanges(RingRange)) { acks.Add(ReadTableAndStartTimers(range, rangeSerialNumberCopy)); } var task = Task.WhenAll(acks); if (logger.IsEnabled(LogLevel.Trace)) task.ContinueWith(_ => PrintReminders(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); return task; } private void RemoveOutOfRangeReminders() { var remindersOutOfRange = 0; foreach (var r in localReminders) { if (RingRange.InRange(r.Key.GrainId)) continue; remindersOutOfRange++; LogTraceRemovingReminder(r.Value); // remove locally r.Value.StopReminder(); localReminders.Remove(r.Key); } if (remindersOutOfRange > 0) { LogInfoRemovedLocalReminders(remindersOutOfRange); } } public override Task OnRangeChange(IRingRange oldRange, IRingRange newRange, bool increased) { _ = base.OnRangeChange(oldRange, newRange, increased); if (Status == GrainServiceStatus.Started) return ReadAndUpdateReminders(); LogIgnoringRangeChange(Status); return Task.CompletedTask; } private async Task RunAsync() { await Task.Yield(); TimeSpan? overrideDelay = RandomTimeSpan.Next(InitialReadRetryPeriod); while (await listRefreshTimer.NextTick(overrideDelay)) { try { overrideDelay = null; switch (Status) { case GrainServiceStatus.Booting: await DoInitialReadAndUpdateReminders(); break; case GrainServiceStatus.Started: await ReadAndUpdateReminders(); break; default: listRefreshTimer.Dispose(); return; } } catch (Exception exception) { LogWarningReadingReminders(exception); overrideDelay = RandomTimeSpan.Next(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20)); } } } protected override async Task StartInBackground() { await DoInitialReadAndUpdateReminders(); this.runTask = RunAsync(); } private async Task DoInitialReadAndUpdateReminders() { try { if (StoppedCancellationTokenSource.IsCancellationRequested) return; initialReadCallCount++; await this.ReadAndUpdateReminders(); Status = GrainServiceStatus.Started; startedTask.TrySetResult(true); } catch (Exception ex) { if (StoppedCancellationTokenSource.IsCancellationRequested) return; if (initialReadCallCount <= InitialReadRetryCountBeforeFastFailForUpdates) { LogWarningInitialLoadFailing(ex, initialReadCallCount); } else { LogErrorInitialLoadFailed(ex, initialReadCallCount); startedTask.TrySetException(new OrleansException("ReminderService failed initial load of reminders and cannot guarantee that the service will be eventually start without manual intervention or restarting the silo.", ex)); } } } private async Task ReadTableAndStartTimers(ISingleRange range, int rangeSerialNumberCopy) { LogDebugReadingRows(range); localTableSequence++; long cachedSequence = localTableSequence; try { var table = await reminderTable.ReadRows(range.Begin, range.End); // get all reminders, even the ones we already have if (rangeSerialNumberCopy < RangeSerialNumber) { LogDebugRangeChangedWhileFromTable(RangeSerialNumber, rangeSerialNumberCopy); return; } if (StoppedCancellationTokenSource.IsCancellationRequested) return; // If null is a valid value, it means that there's nothing to do. if (table is null) return; var remindersNotInTable = new Dictionary(); // shallow copy foreach (var r in localReminders) if (range.InRange(r.Key.GrainId)) remindersNotInTable.Add(r.Key, r.Value); LogDebugReadRemindersFromTable(range, table.Reminders.Count, localTableSequence, cachedSequence); foreach (var entry in table.Reminders) { var key = new ReminderIdentity(entry.GrainId, entry.ReminderName); if (localReminders.TryGetValue(key, out var localRem)) { if (cachedSequence > localRem.LocalSequenceNumber) // info read from table is same or newer than local info { if (localRem.IsRunning) // if ticking { LogTraceInTableInLocalOldTicking(localRem); // it might happen that our local reminder is different than the one in the table, i.e., eTag is different // if so, stop the local timer for the old reminder, and start again with new info if (!localRem.ETag.Equals(entry.ETag)) // this reminder needs a restart { LogTraceLocalReminderNeedsRestart(localRem); localRem.StopReminder(); localReminders.Remove(localRem.Identity); StartAndAddTimer(entry); } } else // if not ticking { // no-op LogTraceInTableInLocalOldNotTicking(localRem); } } else // cachedSequence < localRem.LocalSequenceNumber ... // info read from table is older than local info { if (localRem.IsRunning) // if ticking { // no-op LogTraceInTableInLocalNewerTicking(localRem); } else // if not ticking { // no-op LogTraceInTableInLocalNewerNotTicking(localRem); } } } else // exists in table, but not locally { LogTraceInTableNotInLocal(entry); // create and start the reminder StartAndAddTimer(entry); } // keep a track of extra reminders ... this 'reminder' is useful, so remove it from extra list remindersNotInTable.Remove(key); } // foreach reminder read from table int remindersCountBeforeRemove = localReminders.Count; // foreach reminder that is not in global table, but exists locally foreach (var kv in remindersNotInTable) { var reminder = kv.Value; if (cachedSequence < reminder.LocalSequenceNumber) { // no-op LogTraceNotInTableInLocalNewer(reminder); } else // cachedSequence > reminder.LocalSequenceNumber { LogTraceNotInTableInLocalOld(reminder); // remove locally reminder.StopReminder(); localReminders.Remove(reminder.Identity); } } LogDebugRemovedRemindersFromLocalTable(localReminders.Count - remindersCountBeforeRemove); } catch (Exception exc) { LogErrorFailedToReadTableAndStartTimer(exc); throw; } } private void StartAndAddTimer(ReminderEntry entry) { // it might happen that we already have a local reminder with a different eTag // if so, stop the local timer for the old reminder, and start again with new info // Note: it can happen here that we restart a reminder that has the same eTag as what we just registered ... its a rare case, and restarting it doesn't hurt, so we don't check for it if (localReminders.TryGetValue(new(entry.GrainId, entry.ReminderName), out var prevReminder)) // if found locally { LogDebugLocallyStoppingReminder(prevReminder, entry); prevReminder.StopReminder(); localReminders.Remove(prevReminder.Identity); } var newReminder = new LocalReminderData(entry, this); localTableSequence++; newReminder.LocalSequenceNumber = localTableSequence; localReminders.Add(newReminder.Identity, newReminder); newReminder.StartTimer(); LogDebugStartedReminder(entry); } // stop without removing it. will remove later. private bool TryStopPreviousTimer(GrainId grainId, string reminderName) { // we stop the locally running timer for this reminder if (!localReminders.TryGetValue(new(grainId, reminderName), out var localRem)) return false; // if we have it locally localTableSequence++; // move to next sequence localRem.LocalSequenceNumber = localTableSequence; localRem.StopReminder(); return true; } private Task DoResponsibilitySanityCheck(GrainId grainId, string debugInfo) { switch (Status) { case GrainServiceStatus.Booting: // if service didn't finish the initial load, it could still be loading normally or it might have already // failed a few attempts and callers should not be hold waiting for it to complete var task = this.startedTask.Task; if (task.IsCompleted) { // task at this point is already Faulted task.GetAwaiter().GetResult(); } else { return WaitForInitCompletion(); async Task WaitForInitCompletion() { try { // wait for the initial load task to complete (with a timeout) await task.WaitAsync(InitialReadMaxWaitTimeForUpdates); } catch (TimeoutException ex) { throw new OrleansException("Reminder Service is still initializing and it is taking a long time. Please retry again later.", ex); } CheckRange(); } } break; case GrainServiceStatus.Started: break; case GrainServiceStatus.Stopped: throw new OperationCanceledException("ReminderService has been stopped."); default: throw new InvalidOperationException("status"); } CheckRange(); return Task.CompletedTask; void CheckRange() { if (!RingRange.InRange(grainId)) { LogWarningNotResponsible(debugInfo, grainId, RingRange); // For now, we still let the caller proceed without throwing an exception... the periodical mechanism will take care of reminders being registered at the wrong silo // otherwise, we can either reject the request, or re-route the request } } } // Note: The list of reminders can be huge in production! private void PrintReminders(string msg = null) { if (!logger.IsEnabled(LogLevel.Trace)) return; var str = $"{(msg ?? "Current list of reminders:")}{Environment.NewLine}{Utils.EnumerableToString(localReminders, null, Environment.NewLine)}"; logger.LogTrace("{Message}", str); } private IRemindable GetGrain(GrainId grainId) => (IRemindable)_referenceActivator.CreateReference(grainId, _grainInterfaceType); private sealed class LocalReminderData { private readonly IRemindable remindable; private readonly DateTime firstTickTime; // time for the first tick of this reminder private readonly TimeSpan period; private readonly ILogger logger; private readonly IAsyncTimer timer; private ValueStopwatch stopwatch; private Task runTask; internal LocalReminderData(ReminderEntry entry, LocalReminderService reminderService) { Identity = new ReminderIdentity(entry.GrainId, entry.ReminderName); firstTickTime = entry.StartAt; period = entry.Period; remindable = reminderService.GetGrain(entry.GrainId); ETag = entry.ETag; LocalSequenceNumber = -1; logger = reminderService.logger; this.timer = reminderService.asyncTimerFactory.Create(period, ""); } public ReminderIdentity Identity { get; } public string ETag { get; } /// /// Locally, we use this for resolving races between the periodic table reader, and any concurrent local register/unregister requests /// public long LocalSequenceNumber { get; set; } /// /// Gets a value indicating whether this instance is running. /// public bool IsRunning => runTask is Task task && !task.IsCompleted; public void StartTimer() { if (runTask is null) { using var suppressExecutionContext = new ExecutionContextSuppressor(); this.runTask = this.RunAsync(); } else { throw new InvalidOperationException($"{nameof(StartTimer)} may only be called once per instance and has already been called on this instance."); } } public void StopReminder() { timer.Dispose(); } private async Task RunAsync() { TimeSpan? dueTimeSpan = CalculateDueTime(); while (await this.timer.NextTick(dueTimeSpan)) { try { await OnTimerTick(); ReminderInstruments.TicksDelivered.Add(1); } catch (Exception exception) { LogWarningFiringReminder(logger, Identity.ReminderName, Identity.GrainId, exception); } dueTimeSpan = CalculateDueTime(); } } private TimeSpan CalculateDueTime() { TimeSpan dueTimeSpan; var now = DateTime.UtcNow; if (now < firstTickTime) // if the time for first tick hasn't passed yet { dueTimeSpan = firstTickTime.Subtract(now); // then duetime is duration between now and the first tick time } else // the first tick happened in the past ... compute duetime based on the first tick time, and period { // formula used: // due = period - 'time passed since last tick (==sinceLast)' // due = period - ((Now - FirstTickTime) % period) // explanation of formula: // (Now - FirstTickTime) => gives amount of time since first tick happened // (Now - FirstTickTime) % period => gives amount of time passed since the last tick should have triggered var sinceFirstTick = now.Subtract(firstTickTime); var sinceLastTick = TimeSpan.FromTicks(sinceFirstTick.Ticks % period.Ticks); dueTimeSpan = period.Subtract(sinceLastTick); // in corner cases, dueTime can be equal to period ... so, take another mod dueTimeSpan = TimeSpan.FromTicks(dueTimeSpan.Ticks % period.Ticks); } // If the previous tick took no percievable time, be sure to wait at least one period until the next tick. // If the previous tick took one period or greater, then we will skip up to one period. // That is preferable over double-firing for fast ticks, which are expected to be more common. if (dueTimeSpan <= TimeSpan.FromMilliseconds(30)) { dueTimeSpan = period; } return dueTimeSpan; } public async Task OnTimerTick() { var before = DateTime.UtcNow; var status = new TickStatus(firstTickTime, period, before); LogTraceTriggeringTick(logger, this, status, before); try { if (stopwatch.IsRunning) { stopwatch.Stop(); var tardiness = stopwatch.Elapsed - period; ReminderInstruments.TardinessSeconds.Record(Math.Max(0, tardiness.TotalSeconds)); } await remindable.ReceiveReminder(Identity.ReminderName, status); stopwatch.Restart(); var after = DateTime.UtcNow; LogTraceTickTriggered(logger, this, (after - before).TotalSeconds, after + period); } catch (Exception exc) { var after = DateTime.UtcNow; LogErrorDeliveringReminderTick(logger, this, after + period, exc); // What to do with repeated failures to deliver a reminder's ticks? } } public override string ToString() => $"[{Identity.ReminderName}, {Identity.GrainId}, {period}, {LogFormatter.PrintDate(firstTickTime)}, {ETag}, {LocalSequenceNumber}, {(timer == null ? "Not_ticking" : "Ticking")}]"; } private readonly struct ReminderIdentity : IEquatable { public readonly GrainId GrainId; public readonly string ReminderName; public ReminderIdentity(GrainId grainId, string reminderName) { if (grainId.IsDefault) throw new ArgumentNullException(nameof(grainId)); if (string.IsNullOrWhiteSpace(reminderName)) throw new ArgumentException("The reminder name is either null or whitespace.", nameof(reminderName)); this.GrainId = grainId; this.ReminderName = reminderName; } public readonly bool Equals(ReminderIdentity other) => GrainId.Equals(other.GrainId) && ReminderName.Equals(other.ReminderName); public override readonly bool Equals(object other) => other is ReminderIdentity id && Equals(id); public override readonly int GetHashCode() => HashCode.Combine(GrainId, ReminderName); } [LoggerMessage( Level = LogLevel.Error, Message = "Error activating reminder service." )] private partial void LogErrorActivatingReminderService(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error stopping reminder service." )] private partial void LogErrorStoppingReminderService(Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error starting reminder service." )] private partial void LogErrorStartingReminderService(Exception exception); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_RegisterOrUpdate, Message = "RegisterOrUpdateReminder: {Entry}" )] private partial void LogDebugRegisterOrUpdateReminder(ReminderEntry entry); [LoggerMessage( Level = LogLevel.Debug, Message = "Registered reminder {Entry} in table, assigned localSequence {LocalSequence}" )] private partial void LogDebugRegisterReminder(ReminderEntry entry, long localSequence); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.RS_Register_TableError, Message = "Could not register reminder {Entry} to reminder table due to a race. Please try again later." )] private partial void LogErrorRegisterReminder(ReminderEntry entry); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_Unregister, Message = "UnregisterReminder: {Entry}, LocalTableSequence: {LocalTableSequence}" )] private partial void LogDebugUnregisterReminder(IGrainReminder entry, long localTableSequence); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_Stop, Message = "Stopped reminder {Entry}" )] private partial void LogStoppedReminder(IGrainReminder entry); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_RemoveFromTable, Message = "Removed reminder from table which I didn't have locally: {Entry}." )] private partial void LogRemovedReminderFromTable(IGrainReminder entry); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.RS_Unregister_TableError, Message = "Could not unregister reminder {Reminder} from the reminder table, due to tag mismatch. You can retry." )] private partial void LogErrorUnregisterReminder(IGrainReminder reminder); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_GetReminder, Message = "GetReminder: GrainId={GrainId} ReminderName={ReminderName}" )] private partial void LogDebugGetReminder(GrainId grainId, string reminderName); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_GetReminders, Message = "GetReminders: GrainId={GrainId}" )] private partial void LogDebugGetReminders(GrainId grainId); [LoggerMessage( Level = LogLevel.Trace, Message = "My range {RingRange}, RangeSerialNumber {RangeSerialNumber}. Local reminders count {LocalRemindersCount}" )] private partial void LogTraceRingRange(IRingRange ringRange, int rangeSerialNumber, int localRemindersCount); [LoggerMessage( Level = LogLevel.Trace, Message = "Not in my range anymore, so removing. {Reminder}" )] private partial void LogTraceRemovingReminder(LocalReminderData reminder); [LoggerMessage( Level = LogLevel.Information, Message = "Removed {RemovedCount} local reminders that are now out of my range." )] private partial void LogInfoRemovedLocalReminders(int removedCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Ignoring range change until ReminderService is Started -- Current status = {Status}" )] private partial void LogIgnoringRangeChange(GrainServiceStatus status); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception while reading reminders" )] private partial void LogWarningReadingReminders(Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.RS_ServiceInitialLoadFailing, Message = "ReminderService failed initial load of reminders and will retry. Attempt #{AttemptNumber}" )] private partial void LogWarningInitialLoadFailing(Exception exception, uint attemptNumber); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.RS_ServiceInitialLoadFailed, Message = "ReminderService failed initial load of reminders and cannot guarantee that the service will be eventually start without manual intervention or restarting the silo. Attempt #{AttemptNumber}" )] private partial void LogErrorInitialLoadFailed(Exception exception, uint attemptNumber); [LoggerMessage( Level = LogLevel.Debug, Message = "Reading rows from {Range}" )] private partial void LogDebugReadingRows(IRingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "My range changed while reading from the table, ignoring the results. Another read has been started. RangeSerialNumber {RangeSerialNumber}, RangeSerialNumberCopy {RangeSerialNumberCopy}." )] private partial void LogDebugRangeChangedWhileFromTable(int rangeSerialNumber, int rangeSerialNumberCopy); [LoggerMessage( Level = LogLevel.Debug, Message = "For range {Range}, I read in {ReminderCount} reminders from table. LocalTableSequence {LocalTableSequence}, CachedSequence {CachedSequence}" )] private partial void LogDebugReadRemindersFromTable(IRingRange range, int reminderCount, long localTableSequence, long cachedSequence); [LoggerMessage( Level = LogLevel.Trace, Message = "In table, In local, Old, & Ticking {LocalReminder}" )] private partial void LogTraceInTableInLocalOldTicking(LocalReminderData localReminder); [LoggerMessage( Level = LogLevel.Trace, Message = "{LocalReminder} Needs a restart" )] private partial void LogTraceLocalReminderNeedsRestart(LocalReminderData localReminder); [LoggerMessage( Level = LogLevel.Trace, Message = "In table, In local, Old, & Not Ticking {LocalReminder}" )] private partial void LogTraceInTableInLocalOldNotTicking(LocalReminderData localReminder); [LoggerMessage( Level = LogLevel.Trace, Message = "In table, In local, Newer, & Ticking {LocalReminder}" )] private partial void LogTraceInTableInLocalNewerTicking(LocalReminderData localReminder); [LoggerMessage( Level = LogLevel.Trace, Message = "In table, In local, Newer, & Not Ticking {LocalReminder}" )] private partial void LogTraceInTableInLocalNewerNotTicking(LocalReminderData localReminder); [LoggerMessage( Level = LogLevel.Trace, Message = "In table, Not in local, {Reminder}" )] private partial void LogTraceInTableNotInLocal(ReminderEntry reminder); [LoggerMessage( Level = LogLevel.Trace, Message = "Not in table, In local, Newer, {Reminder}" )] private partial void LogTraceNotInTableInLocalNewer(LocalReminderData reminder); [LoggerMessage( Level = LogLevel.Trace, Message = "Not in table, In local, Old, so removing. {Reminder}" )] private partial void LogTraceNotInTableInLocalOld(LocalReminderData reminder); [LoggerMessage( Level = LogLevel.Debug, Message = "Removed {RemovedCount} reminders from local table" )] private partial void LogDebugRemovedRemindersFromLocalTable(int removedCount); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.RS_FailedToReadTableAndStartTimer, Message = "Failed to read rows from table." )] private partial void LogErrorFailedToReadTableAndStartTimer(Exception exception); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_LocalStop, Message = "Locally stopping reminder {PreviousReminder} as it is different than newly registered reminder {Reminder}" )] private partial void LogDebugLocallyStoppingReminder(LocalReminderData previousReminder, ReminderEntry reminder); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.RS_Started, Message = "Started reminder {Reminder}." )] private partial void LogDebugStartedReminder(ReminderEntry reminder); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.RS_NotResponsible, Message = "I shouldn't have received request '{Request}' for {GrainId}. It is not in my responsibility range: {Range}" )] private partial void LogWarningNotResponsible(string request, GrainId grainId, IRingRange range); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception firing reminder \"{ReminderName}\" for grain {GrainId}" )] private static partial void LogWarningFiringReminder(ILogger logger, string reminderName, GrainId grainId, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Triggering tick for {Instance}, status {Status}, now {CurrentTime}" )] private static partial void LogTraceTriggeringTick(ILogger logger, LocalReminderData instance, TickStatus status, DateTime currentTime); [LoggerMessage( Level = LogLevel.Trace, Message = "Tick triggered for {Instance}, dt {DueTime} sec, next@~ {NextDueTime}" )] private static partial void LogTraceTickTriggered(ILogger logger, LocalReminderData instance, double dueTime, DateTime nextDueTime); [LoggerMessage( Level = LogLevel.Error, Message = "Could not deliver reminder tick for {Instance}, next {NextDueTime}." )] private static partial void LogErrorDeliveringReminderTick(ILogger logger, LocalReminderData instance, DateTime nextDueTime, Exception exception); } } ================================================ FILE: src/Orleans.Reminders/ReminderService/ReminderRegistry.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Hosting; using Orleans.Runtime.Services; using Orleans.Timers; #nullable enable namespace Orleans.Runtime.ReminderService { internal sealed class ReminderRegistry : GrainServiceClient, IReminderRegistry { private IServiceProvider? serviceProvider; private readonly ReminderOptions options; public ReminderRegistry(IServiceProvider serviceProvider, IOptions options) : base(serviceProvider) { this.serviceProvider = serviceProvider; this.options = options.Value; } public Task RegisterOrUpdateReminder(GrainId callingGrainId, string reminderName, TimeSpan dueTime, TimeSpan period) { // Perform input volatility checks if (dueTime == Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException(nameof(dueTime), "Cannot use InfiniteTimeSpan dueTime to create a reminder"); if (dueTime.Ticks < 0) throw new ArgumentOutOfRangeException(nameof(dueTime), "Cannot use negative dueTime to create a reminder"); if (period == Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException(nameof(period), "Cannot use InfiniteTimeSpan period to create a reminder"); if (period.Ticks < 0) throw new ArgumentOutOfRangeException(nameof(period), "Cannot use negative period to create a reminder"); var minReminderPeriod = options.MinimumReminderPeriod; if (period < minReminderPeriod) throw new ArgumentException($"Cannot register reminder {reminderName} as requested period ({period}) is less than minimum allowed reminder period ({minReminderPeriod})"); if (string.IsNullOrEmpty(reminderName)) throw new ArgumentException("Cannot use null or empty name for the reminder", nameof(reminderName)); EnsureReminderServiceRegisteredAndInGrainContext(); return GetGrainService(callingGrainId).RegisterOrUpdateReminder(callingGrainId, reminderName, dueTime, period); } public Task UnregisterReminder(GrainId callingGrainId, IGrainReminder reminder) { EnsureReminderServiceRegisteredAndInGrainContext(); return GetGrainService(callingGrainId).UnregisterReminder(reminder); } public Task GetReminder(GrainId callingGrainId, string reminderName) { if (string.IsNullOrEmpty(reminderName)) throw new ArgumentException("Cannot use null or empty name for the reminder", nameof(reminderName)); EnsureReminderServiceRegisteredAndInGrainContext(); return GetGrainService(callingGrainId).GetReminder(callingGrainId, reminderName); } public Task> GetReminders(GrainId callingGrainId) { EnsureReminderServiceRegisteredAndInGrainContext(); return GetGrainService(callingGrainId).GetReminders(callingGrainId); } private void EnsureReminderServiceRegisteredAndInGrainContext() { if (RuntimeContext.Current is null) ThrowInvalidContext(); if (serviceProvider != null) ValidateServiceProvider(); } private void ValidateServiceProvider() { if (serviceProvider is { } sp && sp.GetService() is null) { throw new OrleansConfigurationException( "The reminder service has not been configured. Reminders can be configured using extension methods from the following packages:" + "\n * Microsoft.Orleans.Reminders.AzureStorage via ISiloBuilder.UseAzureTableReminderService(...)" + "\n * Microsoft.Orleans.Reminders.AdoNet via ISiloBuilder.UseAdoNetReminderService(...)" + "\n * Microsoft.Orleans.Reminders.DynamoDB via via ISiloBuilder.UseDynamoDBReminderService(...)" + "\n * Microsoft.Orleans.OrleansRuntime via ISiloBuilder.UseInMemoryReminderService(...) (Note: for development purposes only)" + "\n * Others, see: https://www.nuget.org/packages?q=Microsoft.Orleans.Reminders."); } serviceProvider = null; } private static void ThrowInvalidContext() { throw new InvalidOperationException("Attempted to access grain from a non-grain context, such as a background thread, which is invalid." + " Ensure that you are only accessing grain functionality from within the context of a grain."); } } } ================================================ FILE: src/Orleans.Reminders/SystemTargetInterfaces/IReminderService.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Services; namespace Orleans { /// /// Functionality for managing reminders. /// public interface IReminderService : IGrainService { /// /// Starts the service. /// /// A representing the operation. Task Start(); /// /// Stops the service. /// /// A representing the operation. Task Stop(); /// /// Registers a new reminder or updates an existing one. /// /// A reference to the grain which the reminder is being registered or updated on behalf of. /// The reminder name. /// The amount of time to delay before firing the reminder initially. /// The time interval between invocations of the reminder. /// The reminder. Task RegisterOrUpdateReminder(GrainId grainId, string reminderName, TimeSpan dueTime, TimeSpan period); /// /// Unregisters the specified reminder. /// /// The reminder. /// A representing the operation. Task UnregisterReminder(IGrainReminder reminder); /// /// Gets the reminder registered to the specified grain with the provided name. /// /// A reference to the grain which the reminder is registered on. /// The name of the reminder. /// The reminder. Task GetReminder(GrainId grainId, string reminderName); /// /// Gets all reminders registered for the specified grain. /// /// A reference to the grain. /// A list of all registered reminders for the specified grain. Task> GetReminders(GrainId grainId); } } ================================================ FILE: src/Orleans.Reminders/SystemTargetInterfaces/IReminderTable.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans { /// /// Interface for implementations of the underlying storage for reminder data: /// Azure Table, SQL, development emulator grain, and a mock implementation. /// Defined as a grain interface for the development emulator grain case. /// public interface IReminderTable { /// /// Initializes this instance. /// /// A representing the work performed. Task StartAsync(CancellationToken cancellationToken = default) #pragma warning disable CS0618 // Type or member is obsolete => Init(); #pragma warning restore CS0618 // Type or member is obsolete /// /// Initializes this instance. /// /// A representing the work performed. [Obsolete("Implement and use StartAsync instead")] Task Init() => Task.CompletedTask; /// /// Reads the reminder table entries associated with the specified grain. /// /// The grain ID. /// The reminder table entries associated with the specified grain. Task ReadRows(GrainId grainId); /// /// Returns all rows that have their in the range (begin, end]. /// If begin is greater or equal to end, returns all entries with hash greater begin or hash less or equal to end. /// /// The exclusive lower bound. /// The inclusive upper bound. /// The reminder table entries which fall within the specified range. Task ReadRows(uint begin, uint end); /// /// Reads the specified entry. /// /// The grain ID. /// Name of the reminder. /// The reminder table entry. Task ReadRow(GrainId grainId, string reminderName); /// /// Upserts the specified entry. /// /// The entry. /// The row's new ETag. Task UpsertRow(ReminderEntry entry); /// /// Removes a row from the table. /// /// The grain ID. /// The reminder name. /// /// The ETag. /// true if a row with and existed and was removed successfully, false otherwise Task RemoveRow(GrainId grainId, string reminderName, string eTag); /// /// Clears the table. /// /// A representing the work performed. Task TestOnlyClearTable(); /// /// Stops the reminder table. /// /// The cancellation token. /// A representing the work performed. Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; } /// /// Reminder table interface for grain based implementation. /// internal interface IReminderTableGrain : IGrainWithIntegerKey { Task ReadRows(GrainId grainId); Task ReadRows(uint begin, uint end); Task ReadRow(GrainId grainId, string reminderName); Task UpsertRow(ReminderEntry entry); Task RemoveRow(GrainId grainId, string reminderName, string eTag); Task TestOnlyClearTable(); } /// /// Represents a collection of reminder table entries. /// [Serializable] [GenerateSerializer] public sealed class ReminderTableData { /// /// Initializes a new instance of the class. /// /// The entries. public ReminderTableData(IEnumerable list) { Reminders = new List(list); } /// /// Initializes a new instance of the class. /// /// The entry. public ReminderTableData(ReminderEntry entry) { Reminders = new[] { entry }; } /// /// Initializes a new instance of the class. /// public ReminderTableData() { Reminders = Array.Empty(); } /// /// Gets the reminders. /// /// The reminders. [Id(0)] public IList Reminders { get; private set; } /// /// Returns a that represents this instance. /// /// A that represents this instance. public override string ToString() => $"[{Reminders.Count} reminders: {Utils.EnumerableToString(Reminders)}."; } /// /// Represents a reminder table entry. /// [Serializable] [GenerateSerializer] public sealed class ReminderEntry { /// /// Gets or sets the grain ID of the grain that created the reminder. Forms the reminder /// primary key together with . /// [Id(0)] public GrainId GrainId { get; set; } /// /// Gets or sets the name of the reminder. Forms the reminder primary key together with /// . /// [Id(1)] public string ReminderName { get; set; } /// /// Gets or sets the time when the reminder was supposed to tick in the first time /// [Id(2)] public DateTime StartAt { get; set; } /// /// Gets or sets the time period for the reminder /// [Id(3)] public TimeSpan Period { get; set; } /// /// Gets or sets the ETag. /// /// The ETag. [Id(4)] public string ETag { get; set; } /// public override string ToString() => $""; /// /// Returns an representing the data in this instance. /// /// The . internal IGrainReminder ToIGrainReminder() => new ReminderData(GrainId, ReminderName, ETag); } [Serializable, GenerateSerializer, Immutable] internal sealed class ReminderData : IGrainReminder { [Id(0)] public readonly GrainId GrainId; [Id(1)] public string ReminderName { get; } [Id(2)] public readonly string ETag; internal ReminderData(GrainId grainId, string reminderName, string eTag) { GrainId = grainId; ReminderName = reminderName; ETag = eTag; } public override string ToString() => $""; } } ================================================ FILE: src/Orleans.Reminders/Timers/IRemindable.cs ================================================ using System; using System.Runtime.Serialization; using System.Threading.Tasks; namespace Orleans { /// /// Callback interface that grains must implement in order to be able to register and receive Reminders. /// public interface IRemindable : IGrain { /// /// Receive a new Reminder. /// /// Name of this Reminder /// Status of this Reminder tick /// Completion promise which the grain will resolve when it has finished processing this Reminder tick. Task ReceiveReminder(string reminderName, Runtime.TickStatus status); } namespace Runtime { /// /// Handle for a persistent Reminder. /// public interface IGrainReminder { /// /// Gets the name of this reminder. /// string ReminderName { get; } } /// /// The status of a tick when the tick is delivered to the registrar grain. /// In case of failures, it may happen that a tick is not delivered on time. The app can notice such missed ticks as follows. /// Upon receiving a tick, the app can calculate the theoretical number of ticks since start of the reminder as: /// curCount = (Now - FirstTickTime) / Period /// The app can keep track of it as 'count'. Upon receiving a tick, the number of missed ticks = curCount - count - 1 /// Thereafter, the app can set count = curCount /// [Serializable, GenerateSerializer, Immutable] public readonly struct TickStatus { /// /// Gets the time at which the first tick of this reminder is due, or was triggered. /// [Id(0)] public DateTime FirstTickTime { get; } /// /// Gets the period of the reminder. /// [Id(1)] public TimeSpan Period { get; } /// /// Gets the time on the runtime silo when the silo initiated the delivery of this tick. /// [Id(2)] public DateTime CurrentTickTime { get; } /// /// Creates a new instance. /// /// The time at which the first tick of the reminder is due. /// The period of the reminder. /// The time when delivery of the current tick was initiated. /// public TickStatus(DateTime firstTickTime, TimeSpan period, DateTime timeStamp) { FirstTickTime = firstTickTime; Period = period; CurrentTickTime = timeStamp; } /// public override string ToString() => $"<{FirstTickTime}, {Period}, {CurrentTickTime}>"; } /// /// Exception related to Orleans Reminder functions or Reminder service. /// [Serializable, GenerateSerializer] public sealed class ReminderException : OrleansException { /// /// Initializes a new instance of the class. /// /// The message. public ReminderException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] public ReminderException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } } ================================================ FILE: src/Orleans.Reminders/Timers/IReminderRegistry.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Services; namespace Orleans.Timers { /// /// Functionality for managing reminders. /// public interface IReminderRegistry : IGrainServiceClient { /// /// Register or update the reminder with the specified name for the currently active grain. /// /// The ID of the the currently executing grain /// The reminder name. /// The amount of time to delay before initially invoking the reminder. /// The time interval between invocations of the reminder. /// The reminder. Task RegisterOrUpdateReminder(GrainId callingGrainId, string reminderName, TimeSpan dueTime, TimeSpan period); /// /// Unregisters a reminder from the currently active grain. /// /// The ID of the the currently executing grain /// The reminder to unregister. /// A representing the operation. Task UnregisterReminder(GrainId callingGrainId, IGrainReminder reminder); /// /// Gets the reminder with the specified name which is registered to the currently active grain. /// /// The ID of the the currently executing grain /// The reminder name. /// The reminder. Task GetReminder(GrainId callingGrainId, string reminderName); /// /// Gets all reminders which are currently registered to the active grain. /// /// The ID of the the currently executing grain /// All reminders which are currently registered to the active grain. Task> GetReminders(GrainId callingGrainId); } } ================================================ FILE: src/Orleans.Reminders.Abstractions/Orleans.Reminders.Abstractions.csproj ================================================ Microsoft.Orleans.Reminders.Abstractions Microsoft Orleans Reminders Abstractions Reminders abstractions library for Microsoft Orleans $(DefaultTargetFrameworks) true Orleans false true $(DefineConstants);ORLEANS_REMINDERS_PROVIDER ================================================ FILE: src/Orleans.Runtime/Activation/ActivationDataActivatorProvider.cs ================================================ #nullable enable using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime { internal partial class ActivationDataActivatorProvider : IGrainContextActivatorProvider { private readonly IServiceProvider _serviceProvider; private readonly IActivationWorkingSet _activationWorkingSet; private readonly ILogger _workItemGroupLogger; private readonly ILogger _grainLogger; private readonly ILogger _activationTaskSchedulerLogger; private readonly IOptions _schedulingOptions; private readonly GrainTypeSharedContextResolver _sharedComponentsResolver; private readonly GrainClassMap _grainClassMap; private readonly ILoggerFactory _loggerFactory; private readonly GrainReferenceActivator _grainReferenceActivator; public ActivationDataActivatorProvider( GrainClassMap grainClassMap, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, GrainReferenceActivator grainReferenceActivator, GrainTypeSharedContextResolver sharedComponentsResolver, IActivationWorkingSet activationWorkingSet, ILogger grainLogger, ILogger workItemGroupLogger, ILogger activationTaskSchedulerLogger, IOptions schedulingOptions) { _activationWorkingSet = activationWorkingSet; _workItemGroupLogger = workItemGroupLogger; _grainLogger = grainLogger; _activationTaskSchedulerLogger = activationTaskSchedulerLogger; _schedulingOptions = schedulingOptions; _sharedComponentsResolver = sharedComponentsResolver; _grainClassMap = grainClassMap; _serviceProvider = serviceProvider; _loggerFactory = loggerFactory; _grainReferenceActivator = grainReferenceActivator; } public bool TryGet(GrainType grainType, [NotNullWhen(true)] out IGrainContextActivator? activator) { if (!_grainClassMap.TryGetGrainClass(grainType, out var grainClass) || !typeof(IGrain).IsAssignableFrom(grainClass)) { activator = null; return false; } var sharedContext = _sharedComponentsResolver.GetComponents(grainType); var instanceActivator = sharedContext.GetComponent(); if (instanceActivator is null) { throw new InvalidOperationException($"Could not find a suitable {nameof(IGrainActivator)} implementation for grain type {grainType}"); } var innerActivator = new ActivationDataActivator( instanceActivator, _serviceProvider, sharedContext, _grainLogger, _workItemGroupLogger, _activationTaskSchedulerLogger, _schedulingOptions); if (sharedContext.PlacementStrategy is StatelessWorkerPlacement) { activator = new StatelessWorkerActivator(sharedContext, innerActivator); } else { activator = innerActivator; } return true; } private partial class ActivationDataActivator : IGrainContextActivator { private readonly ILogger _workItemGroupLogger; private readonly ILogger _activationTaskSchedulerLogger; private readonly IOptions _schedulingOptions; private readonly IGrainActivator _grainActivator; private readonly IServiceProvider _serviceProvider; private readonly GrainTypeSharedContext _sharedComponents; private readonly ILogger _grainLogger; private readonly Func _createWorkItemGroup; private readonly Action _startActivation; public ActivationDataActivator( IGrainActivator grainActivator, IServiceProvider serviceProvider, GrainTypeSharedContext sharedComponents, ILogger grainLogger, ILogger workItemGroupLogger, ILogger activationTaskSchedulerLogger, IOptions schedulingOptions) { _workItemGroupLogger = workItemGroupLogger; _activationTaskSchedulerLogger = activationTaskSchedulerLogger; _schedulingOptions = schedulingOptions; _grainActivator = grainActivator; _serviceProvider = serviceProvider; _sharedComponents = sharedComponents; _grainLogger = grainLogger; _createWorkItemGroup = context => new WorkItemGroup( context, _workItemGroupLogger, _activationTaskSchedulerLogger, _schedulingOptions); _startActivation = state => ((ActivationData)state!).Start(_grainActivator); } public IGrainContext CreateContext(GrainAddress activationAddress) { var context = new ActivationData( activationAddress, _createWorkItemGroup, _serviceProvider, _sharedComponents); using var ecSuppressor = ExecutionContext.SuppressFlow(); _ = Task.Factory.StartNew( _startActivation, context, CancellationToken.None, TaskCreationOptions.DenyChildAttach, context.ActivationTaskScheduler); return context; } [LoggerMessage( Level = LogLevel.Error, Message = "Failed to dispose grain '{GrainId}'." )] private static partial void LogErrorFailedToDisposeGrain(ILogger logger, Exception exception, GrainId grainId); } } internal class StatelessWorkerActivator : IGrainContextActivator { private readonly IGrainContextActivator _innerActivator; private readonly GrainTypeSharedContext _sharedContext; public StatelessWorkerActivator(GrainTypeSharedContext sharedContext, IGrainContextActivator innerActivator) { _innerActivator = innerActivator; _sharedContext = sharedContext; } public IGrainContext CreateContext(GrainAddress address) => new StatelessWorkerGrainContext(address, _sharedContext, _innerActivator); } } ================================================ FILE: src/Orleans.Runtime/Activation/ConfigureDefaultGrainActivator.cs ================================================ using System; using Orleans.Metadata; namespace Orleans.Runtime { internal class ConfigureDefaultGrainActivator : IConfigureGrainTypeComponents { private readonly IServiceProvider _serviceProvider; private readonly GrainClassMap _grainClassMap; public ConfigureDefaultGrainActivator(GrainClassMap grainClassMap, IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _grainClassMap = grainClassMap; } public void Configure(GrainType grainType, GrainProperties properties, GrainTypeSharedContext shared) { if (shared.GetComponent() is not null) return; if (!_grainClassMap.TryGetGrainClass(grainType, out var grainClass)) { return; } var instanceActivator = new DefaultGrainActivator(_serviceProvider, grainClass); shared.SetComponent(instanceActivator); } } } ================================================ FILE: src/Orleans.Runtime/Activation/DefaultGrainActivator.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime { /// /// The default implementation. /// public class DefaultGrainActivator : IGrainActivator { private readonly ObjectFactory _grainInstanceFactory; private readonly GrainConstructorArgumentFactory _argumentFactory; private readonly Type _grainClass; /// /// Initializes a new instance. /// /// The service provider. /// The grain class. public DefaultGrainActivator(IServiceProvider serviceProvider, Type grainClass) { _argumentFactory = new GrainConstructorArgumentFactory(serviceProvider, grainClass); _grainInstanceFactory = ActivatorUtilities.CreateFactory(grainClass, _argumentFactory.ArgumentTypes); _grainClass = grainClass; } /// public object CreateInstance(IGrainContext context) { try { var args = _argumentFactory.CreateArguments(context); return _grainInstanceFactory(context.ActivationServices, args); } catch (Exception exception) { throw new InvalidOperationException( $"Failed to create an instance of grain type '{_grainClass}'. See {nameof(Exception.InnerException)} for details.", exception); } } /// public async ValueTask DisposeInstance(IGrainContext context, object instance) { switch (instance) { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync(); break; case IDisposable disposable: disposable.Dispose(); break; } } } } ================================================ FILE: src/Orleans.Runtime/Activation/GrainContextAccessor.cs ================================================ namespace Orleans.Runtime { internal class GrainContextAccessor : IGrainContextAccessor { private readonly HostedClient _hostedClient; public GrainContextAccessor(HostedClient hostedClient) { _hostedClient = hostedClient; } public IGrainContext GrainContext => RuntimeContext.Current ?? _hostedClient; } } ================================================ FILE: src/Orleans.Runtime/Activation/IGrainActivator.cs ================================================ using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Creates a grain instance for a given grain context. /// public interface IGrainActivator { /// /// Returns a new grain instance for the provided grain context. /// /// The grain context. /// The grain instance. object CreateInstance(IGrainContext context); /// /// Disposes the provided grain instance which is associated with the provided grain context. /// /// The grain context. /// The grain instance. /// A representing the work performed. ValueTask DisposeInstance(IGrainContext context, object instance); } } ================================================ FILE: src/Orleans.Runtime/Activation/IGrainContextActivator.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Concurrency; using Orleans.Configuration; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime.Placement; using Orleans.Serialization.Invocation; using Orleans.Serialization.Session; namespace Orleans.Runtime { /// /// The central point for creating grain contexts. /// public sealed class GrainContextActivator { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly IGrainContextActivatorProvider[] _activatorProviders; private readonly IConfigureGrainContextProvider[] _configuratorProviders; private readonly GrainPropertiesResolver _resolver; private ImmutableDictionary _activators = ImmutableDictionary.Empty; /// /// Initializes a new instance of the class. /// /// The grain context activator providers. /// The providers. /// The grain properties resolver. public GrainContextActivator( IEnumerable providers, IEnumerable configureContextActions, GrainPropertiesResolver grainPropertiesResolver) { _resolver = grainPropertiesResolver; _activatorProviders = providers.ToArray(); _configuratorProviders = configureContextActions.ToArray(); } /// /// Creates a new grain context for the provided grain address. /// /// The grain address. /// The grain context. public IGrainContext CreateInstance(GrainAddress address) { var grainId = address.GrainId; if (!_activators.TryGetValue(grainId.Type, out var activator)) { activator = this.CreateActivator(grainId.Type); } var result = activator.Activator.CreateContext(address); foreach (var configure in activator.ConfigureActions) { configure.Configure(result); } return result; } private (IGrainContextActivator, IConfigureGrainContext[]) CreateActivator(GrainType grainType) { lock (_lockObj) { if (!_activators.TryGetValue(grainType, out var configuredActivator)) { IGrainContextActivator unconfiguredActivator = null; foreach (var provider in this._activatorProviders) { if (provider.TryGet(grainType, out unconfiguredActivator)) { break; } } if (unconfiguredActivator is null) { throw new InvalidOperationException($"Unable to find an {nameof(IGrainContextActivatorProvider)} for grain type {grainType}"); } var properties = _resolver.GetGrainProperties(grainType); List configureActions = new List(); foreach (var provider in _configuratorProviders) { if (provider.TryGetConfigurator(grainType, properties, out var configurator)) { configureActions.Add(configurator); } } configuredActivator = (unconfiguredActivator, configureActions.ToArray()); _activators = _activators.SetItem(grainType, configuredActivator); } return configuredActivator; } } } /// /// Provides a for a specified grain type. /// public interface IGrainContextActivatorProvider { /// /// Returns a grain context activator for the given grain type. /// /// Type of the grain. /// The grain context activator. /// if an appropriate activator was found, otherwise . bool TryGet(GrainType grainType, [NotNullWhen(true)] out IGrainContextActivator activator); } /// /// Creates a grain context for the given grain address. /// public interface IGrainContextActivator { /// /// Creates a grain context for the given grain address. /// /// The grain address. /// The newly created grain context. public IGrainContext CreateContext(GrainAddress address); } /// /// Provides a instance for the provided grain type. /// public interface IConfigureGrainContextProvider { /// /// Provides a instance for the provided grain type. /// /// Type of the grain. /// The grain properties. /// The configuration provider. /// if a configuration provider was found, otherwise. bool TryGetConfigurator(GrainType grainType, GrainProperties properties, [NotNullWhen(true)] out IConfigureGrainContext configurator); } /// /// Configures the provided grain context. /// public interface IConfigureGrainContext { /// /// Configures the provided grain context. /// /// The grain context. void Configure(IGrainContext context); } /// /// Resolves components which are common to all instances of a given grain type. /// public class GrainTypeSharedContextResolver { private readonly ConcurrentDictionary _components = new(); private readonly IConfigureGrainTypeComponents[] _configurators; private readonly GrainPropertiesResolver _grainPropertiesResolver; private readonly Func _createFunc; private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. /// /// The grain type component configuration providers. /// The grain properties resolver. /// The service provider. public GrainTypeSharedContextResolver( IEnumerable configurators, GrainPropertiesResolver grainPropertiesResolver, IServiceProvider serviceProvider) { _configurators = configurators.ToArray(); _grainPropertiesResolver = grainPropertiesResolver; _serviceProvider = serviceProvider; _createFunc = Create; } /// /// Returns shared grain components for the provided grain type. /// /// The grain type. /// The shared context for all grains of the provided type. public GrainTypeSharedContext GetComponents(GrainType grainType) => _components.GetOrAdd(grainType, _createFunc); private GrainTypeSharedContext Create(GrainType grainType) { var result = ActivatorUtilities.CreateInstance(_serviceProvider, grainType); var properties = _grainPropertiesResolver.GetGrainProperties(grainType); foreach (var configurator in _configurators) { configurator.Configure(grainType, properties, result); } return result; } } /// /// Configures shared components which are common for all instances of a given grain type. /// public interface IConfigureGrainTypeComponents { /// /// Configures shared components which are common for all instances of a given grain type. /// /// The grain type. /// The grain properties. /// The shared context for all grains of the specified type. void Configure(GrainType grainType, GrainProperties properties, GrainTypeSharedContext shared); } internal class ReentrantSharedComponentsConfigurator : IConfigureGrainTypeComponents { public void Configure(GrainType grainType, GrainProperties properties, GrainTypeSharedContext shared) { if (properties.Properties.TryGetValue(WellKnownGrainTypeProperties.Reentrant, out var value) && bool.Parse(value)) { var component = shared.GetComponent(); if (component is null) { component = new GrainCanInterleave(); shared.SetComponent(component); } component.MayInterleavePredicates.Add(ReentrantPredicate.Instance); } } } internal class MayInterleaveConfiguratorProvider : IConfigureGrainContextProvider { private readonly GrainClassMap _grainClassMap; public MayInterleaveConfiguratorProvider(GrainClassMap grainClassMap) { _grainClassMap = grainClassMap; } public bool TryGetConfigurator(GrainType grainType, GrainProperties properties, out IConfigureGrainContext configurator) { if (properties.Properties.TryGetValue(WellKnownGrainTypeProperties.MayInterleavePredicate, out _) && _grainClassMap.TryGetGrainClass(grainType, out var grainClass)) { var predicate = GetMayInterleavePredicate(grainClass); configurator = new MayInterleaveConfigurator(predicate); return true; } configurator = null; return false; } /// /// Returns interleave predicate depending on whether class is marked with or not. /// /// Grain class. private static IMayInterleavePredicate GetMayInterleavePredicate(Type grainType) { var attribute = grainType.GetCustomAttribute(); if (attribute is null) { return null; } // here var callbackMethodName = attribute.CallbackMethodName; var method = grainType.GetMethod(callbackMethodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy); if (method == null) { throw new InvalidOperationException( $"Class {grainType.FullName} doesn't declare public method " + $"with name {callbackMethodName} specified in MayInterleave attribute"); } if (method.ReturnType != typeof(bool) || method.GetParameters().Length != 1 || method.GetParameters()[0].ParameterType != typeof(IInvokable)) { throw new InvalidOperationException( $"Wrong signature of callback method {callbackMethodName} " + $"specified in MayInterleave attribute for grain class {grainType.FullName}. \n" + $"Expected: public bool {callbackMethodName}(IInvokable req)"); } if (method.IsStatic) { return new MayInterleaveStaticPredicate(method.CreateDelegate>()); } var predicateType = typeof(MayInterleaveInstancedPredicate<>).MakeGenericType(grainType); return (IMayInterleavePredicate)Activator.CreateInstance(predicateType, method); } } internal interface IMayInterleavePredicate { bool Invoke(object instance, IInvokable bodyObject); } internal class ReentrantPredicate : IMayInterleavePredicate { private ReentrantPredicate() { } public static ReentrantPredicate Instance { get; } = new(); public bool Invoke(object _, IInvokable bodyObject) => true; } internal class MayInterleaveStaticPredicate : IMayInterleavePredicate { private readonly Func _mayInterleavePredicate; public MayInterleaveStaticPredicate(Func mayInterleavePredicate) { _mayInterleavePredicate = mayInterleavePredicate; } public bool Invoke(object _, IInvokable bodyObject) => _mayInterleavePredicate(bodyObject); } internal class MayInterleaveInstancedPredicate : IMayInterleavePredicate where T : class { private readonly Func _mayInterleavePredicate; public MayInterleaveInstancedPredicate(MethodInfo mayInterleavePredicateInfo) { _mayInterleavePredicate = mayInterleavePredicateInfo.CreateDelegate>(); } public bool Invoke(object instance, IInvokable bodyObject) => _mayInterleavePredicate(instance as T, bodyObject); } internal class MayInterleaveConfigurator : IConfigureGrainContext { private readonly IMayInterleavePredicate _mayInterleavePredicate; public MayInterleaveConfigurator(IMayInterleavePredicate mayInterleavePredicate) { _mayInterleavePredicate = mayInterleavePredicate; } public void Configure(IGrainContext context) { var component = context.GetComponent(); if (component is null) { component = new GrainCanInterleave(); context.SetComponent(component); } component.MayInterleavePredicates.Add(_mayInterleavePredicate); } } internal class GrainCanInterleave { public List MayInterleavePredicates { get; } = new List(); public bool MayInterleave(object instance, Message message) { foreach (var predicate in this.MayInterleavePredicates) { if (predicate.Invoke(instance, message.BodyObject as IInvokable)) return true; } return false; } } } ================================================ FILE: src/Orleans.Runtime/Cancellation/CancellationSourcesExtension.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Threading; using System.Diagnostics; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// Contains list of cancellation token source corresponding to the tokens /// passed to the related grain activation. /// internal partial class CancellationSourcesExtension : ICancellationSourcesExtension, IDisposable { private readonly ConcurrentDictionary _cancellationTokens = new ConcurrentDictionary(); private readonly ILogger _logger; private readonly IGrainCancellationTokenRuntime _cancellationTokenRuntime; private readonly Timer _cleanupTimer; private readonly Func _createToken; private static readonly TimeSpan _cleanupFrequency = TimeSpan.FromMinutes(7); /// /// Initializes a new instance of the class. /// /// The logger factory. /// The cancellation runtime. public CancellationSourcesExtension(ILoggerFactory loggerFactory, IGrainCancellationTokenRuntime cancellationRuntime) { _logger = loggerFactory.CreateLogger(); _cancellationTokenRuntime = cancellationRuntime; _cleanupTimer = new Timer(obj => ((CancellationSourcesExtension)obj).ExpireTokens(), this, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); _createToken = id => new Entry(new GrainCancellationToken(id, false, _cancellationTokenRuntime)); } /// public Task CancelRemoteToken(Guid tokenId) { if (!_cancellationTokens.TryGetValue(tokenId, out var entry)) { LogCancellationFailed(tokenId); // Record the cancellation anyway, in case the call which would have registered the cancellation is still pending. this.RecordCancellationToken(tokenId, isCancellationRequested: true); return Task.CompletedTask; } entry.Touch(); var token = entry.Token; return token.Cancel(); } /// /// Adds to the grain extension so that it can be canceled through remote call to the CancellationSourcesExtension. /// /// /// internal static void RegisterCancellationTokens( IGrainContext target, IInvokable request) { var argumentCount = request.GetArgumentCount(); for (var i = 0; i < argumentCount; i++) { var arg = request.GetArgument(i); if (arg is not GrainCancellationToken grainToken) { continue; } var cancellationExtension = (CancellationSourcesExtension)target.GetGrainExtension(); // Replacing the half baked GrainCancellationToken that came from the wire with locally fully created one. request.SetArgument(i, cancellationExtension.RecordCancellationToken(grainToken.Id, grainToken.IsCancellationRequested)); } } private GrainCancellationToken RecordCancellationToken(Guid tokenId, bool isCancellationRequested) { if (_cancellationTokens.TryGetValue(tokenId, out var entry)) { entry.Touch(); return entry.Token; } entry = _cancellationTokens.GetOrAdd(tokenId, _createToken); if (isCancellationRequested) { entry.Token.Cancel(); } return entry.Token; } private void ExpireTokens() { var now = Stopwatch.GetTimestamp(); foreach (var token in _cancellationTokens) { if (token.Value.IsExpired(_cleanupFrequency, now)) { _cancellationTokens.TryRemove(token.Key, out _); } } } /// public void Dispose() { _cleanupTimer.Dispose(); } private class Entry { private long _createdTime; public Entry(GrainCancellationToken token) { Token = token; _createdTime = Stopwatch.GetTimestamp(); } public void Touch() => _createdTime = Stopwatch.GetTimestamp(); public GrainCancellationToken Token { get; } public bool IsExpired(TimeSpan expiry, long nowTimestamp) { var untouchedTime = TimeSpan.FromSeconds((nowTimestamp - _createdTime) / (double)Stopwatch.Frequency); return untouchedTime >= expiry; } } [LoggerMessage( EventId = (int)ErrorCode.CancellationTokenCancelFailed, Level = LogLevel.Warning, Message = "Received a cancel call for token with id '{TokenId}', but the token was not found." )] private partial void LogCancellationFailed(Guid tokenId); } } ================================================ FILE: src/Orleans.Runtime/Cancellation/GrainCallCancellationManager.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime; /// /// Remote interface for cancelling grain calls. /// internal interface IGrainCallCancellationManagerSystemTarget : ISystemTarget { /// /// Cancels a collection of grain calls. /// ValueTask CancelCallsAsync([Immutable] List cancellationRequests); } [GenerateSerializer, Immutable] internal struct GrainCallCancellationRequest(GrainId targetGrainId, GrainId sourceGrainId, CorrelationId messageId) { [Id(0)] public GrainId TargetGrainId { get; set; } = targetGrainId; [Id(1)] public GrainId SourceGrainId { get; set; } = sourceGrainId; [Id(2)] public CorrelationId MessageId { get; set; } = messageId; } /// /// Cancels grain calls issued to remote hosts and handles cancellation requests from other hosts. /// internal partial class GrainCallCancellationManager : SystemTarget, IGrainCallCancellationManagerSystemTarget, IGrainCallCancellationManager, ILifecycleParticipant { private const int MaxBatchSize = 1_000; private readonly ConcurrentDictionary WorkItemChannel, CancellationTokenSource Cts)> _workers = new(); private readonly CancellationTokenSource _shuttingDownCts = new(); private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly Catalog _catalog; private readonly ActivationDirectory _activationDirectory; private readonly IClusterMembershipService _clusterMembershipService; #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif private readonly Task? _membershipUpdatesTask; private IInternalGrainFactory? _grainFactory; public GrainCallCancellationManager( ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, Catalog catalog, ActivationDirectory activationDirectory, IClusterMembershipService clusterMembershipService, SystemTargetShared shared) : base(Constants.CancellationManagerType, shared) { _serviceProvider = serviceProvider; _logger = loggerFactory.CreateLogger(); _catalog = catalog; _activationDirectory = activationDirectory; _clusterMembershipService = clusterMembershipService; using (new ExecutionContextSuppressor()) { _membershipUpdatesTask = Task.Factory.StartNew( state => ((GrainCallCancellationManager)state!).ProcessMembershipUpdates(), this, CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); _membershipUpdatesTask.Ignore(); } shared.ActivationDirectory.RecordNewTarget(this); } private IInternalGrainFactory GrainFactory => _grainFactory ??= _serviceProvider.GetRequiredService(); public ValueTask CancelCallsAsync(List cancellationRequests) { foreach (var request in cancellationRequests) { // Try to directly call the cancellation method locally if (_activationDirectory.FindTarget(request.TargetGrainId) is IGrainCallCancellationExtension extension) { extension.CancelRequestAsync(request.SourceGrainId, request.MessageId).Ignore(); } else { // Fall back to a regular grain call. GrainFactory.GetGrain(request.TargetGrainId).CancelRequestAsync(request.SourceGrainId, request.MessageId).Ignore(); } } return ValueTask.CompletedTask; } public void SignalCancellation(SiloAddress? targetSilo, GrainId targetGrainId, GrainId sourceGrainId, CorrelationId messageId) { if (targetSilo is not null && GetOrCreateWorker(targetSilo).Writer.TryWrite(new GrainCallCancellationRequest(targetGrainId, sourceGrainId, messageId))) { return; } var request = GrainFactory.GetGrain(targetGrainId).CancelRequestAsync(sourceGrainId, messageId); request.Ignore(); } private async Task ProcessMembershipUpdates() { await Task.Yield(); try { LogDebugMonitoringClusterMembershipUpdates(_logger); var previousSnapshot = _clusterMembershipService.CurrentSnapshot; await foreach (var snapshot in _clusterMembershipService.MembershipUpdates.WithCancellation(_shuttingDownCts.Token)) { try { var diff = snapshot.CreateUpdate(previousSnapshot); previousSnapshot = snapshot; foreach (var change in diff.Changes) { if (change.Status is SiloStatus.Dead or SiloStatus.None && _workers.TryGetValue(change.SiloAddress, out var worker)) { worker.WorkItemChannel.Writer.TryComplete(); try { worker.Cts.Cancel(throwOnFirstException: false); } catch { } } } } catch (Exception exception) { LogErrorProcessingClusterMembershipUpdates(_logger, exception); } } } finally { LogDebugNoLongerMonitoringClusterMembershipUpdates(_logger); } } private async Task PumpCancellationQueue(SiloAddress targetSilo, Channel workItems, CancellationToken cancellationToken) { try { var remote = GrainFactory.GetSystemTarget(Constants.CancellationManagerType, targetSilo); await Task.Yield(); LogDebugStartingCancellationWorker(_logger, targetSilo); var batch = new List(); var reader = workItems.Reader; while (await reader.WaitToReadAsync(cancellationToken)) { try { // Collect a batch of work items. while (batch.Count < MaxBatchSize && reader.TryRead(out var workItem)) { batch.Add(workItem); } // Attempt to cancel the batch. await remote.CancelCallsAsync(batch).AsTask().WaitAsync(cancellationToken); LogDebugCancelledRequests(_logger, batch.Count, targetSilo); batch.Clear(); } catch (Exception exception) { if (cancellationToken.IsCancellationRequested) { break; } LogErrorCancellingRequests(_logger, exception, batch.Count, targetSilo); await Task.Delay(5_000, cancellationToken); } } } catch { if (!cancellationToken.IsCancellationRequested) { throw; } } finally { // Remove ourselves and clean up. RemoveWorker(targetSilo); LogDebugExitingCancellationWorker(_logger, targetSilo); } } private (ChannelWriter Writer, CancellationTokenSource Cts) GetOrCreateWorker(SiloAddress targetSilo) { if (!_workers.TryGetValue(targetSilo, out var worker)) { lock (_lock) { if (!_workers.TryGetValue(targetSilo, out worker)) { using var _ = new ExecutionContextSuppressor(); var cts = CancellationTokenSource.CreateLinkedTokenSource(_shuttingDownCts.Token); var channel = Channel.CreateUnbounded(); var pumpTask = Task.Factory.StartNew( () => PumpCancellationQueue(targetSilo, channel, cts.Token), CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); pumpTask.Ignore(); worker = (pumpTask, channel, cts); var didAdd = _workers.TryAdd(targetSilo, worker); Debug.Assert(didAdd); } } } return (worker.WorkItemChannel.Writer, worker.Cts); } private void RemoveWorker(SiloAddress targetSilo) { if (_workers.TryRemove(targetSilo, out var entry)) { LogDebugTargetSiloNoLongerActive(_logger, targetSilo); entry.Cts.Dispose(); } } private Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; private async Task StopAsync(CancellationToken cancellationToken) { var tasks = new List(); if (_membershipUpdatesTask is { } task) { tasks.Add(task); } foreach (var (_, worker) in _workers) { worker.WorkItemChannel.Writer.TryComplete(); try { worker.Cts.Cancel(throwOnFirstException: false); } catch { } tasks.Add(worker.PumpTask); } try { _shuttingDownCts.Cancel(throwOnFirstException: false); } catch (Exception exception) { LogWarningErrorSignalingShutdown(_logger, exception); } await Task.WhenAll(tasks).WaitAsync(cancellationToken).SuppressThrowing(); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { _ = GrainFactory; lifecycle.Subscribe( nameof(GrainCallCancellationManager), ServiceLifecycleStage.RuntimeGrainServices, ct => this.RunOrQueueTask(() => StartAsync(ct)), ct => this.RunOrQueueTask(() => StopAsync(ct))); } [LoggerMessage( Level = LogLevel.Debug, Message = "Monitoring cluster membership updates" )] private static partial void LogDebugMonitoringClusterMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting cancellation worker for target silo {SiloAddress}" )] private static partial void LogDebugStartingCancellationWorker(ILogger logger, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Cancelled {Count} requests to target silo {SiloAddress}" )] private static partial void LogDebugCancelledRequests(ILogger logger, int count, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Error while cancelling {Count} requests to {SiloAddress}" )] private static partial void LogErrorCancellingRequests(ILogger logger, Exception exception, int count, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Exiting cancellation worker for target silo {SiloAddress}" )] private static partial void LogDebugExitingCancellationWorker(ILogger logger, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Target silo '{SiloAddress}' is no longer active, so this cancellation activation worker is terminating" )] private static partial void LogDebugTargetSiloNoLongerActive(ILogger logger, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Error signaling shutdown." )] private static partial void LogWarningErrorSignalingShutdown(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "No longer monitoring cluster membership updates" )] private static partial void LogDebugNoLongerMonitoringClusterMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing cluster membership updates" )] private static partial void LogErrorProcessingClusterMembershipUpdates(ILogger logger, Exception exception); } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationCollector.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime.Internal; using Orleans.Statistics; namespace Orleans.Runtime { /// /// Identifies activations that have been idle long enough to be deactivated. /// internal partial class ActivationCollector : IActivationWorkingSetObserver, ILifecycleParticipant, IDisposable { private readonly TimeSpan shortestAgeLimit; private readonly ConcurrentDictionary buckets = new(); private readonly CancellationTokenSource _shutdownCts = new(); private DateTime nextTicket; private static readonly List nothing = new(0); private readonly ILogger logger; private int collectionNumber; // internal for testing internal int _activationCount; private readonly PeriodicTimer _collectionTimer; private Task _collectionLoopTask; private readonly IEnvironmentStatisticsProvider _environmentStatisticsProvider; private readonly GrainCollectionOptions _grainCollectionOptions; private readonly PeriodicTimer _memBasedDeactivationTimer; private Task _memBasedDeactivationLoopTask; /// /// Initializes a new instance of the class. /// /// The time provider. /// The options. /// The logger. public ActivationCollector( TimeProvider timeProvider, IOptions options, ILogger logger, IEnvironmentStatisticsProvider environmentStatisticsProvider) { _grainCollectionOptions = options.Value; shortestAgeLimit = new(_grainCollectionOptions.ClassSpecificCollectionAge.Values.Aggregate(_grainCollectionOptions.CollectionAge.Ticks, (a, v) => Math.Min(a, v.Ticks))); nextTicket = MakeTicketFromDateTime(timeProvider.GetUtcNow().UtcDateTime); this.logger = logger; _collectionTimer = new PeriodicTimer(_grainCollectionOptions.CollectionQuantum); _environmentStatisticsProvider = environmentStatisticsProvider; if (_grainCollectionOptions.EnableActivationSheddingOnMemoryPressure) { _memBasedDeactivationTimer = new PeriodicTimer(_grainCollectionOptions.MemoryUsagePollingPeriod); } } // Return the number of activations that were used (touched) in the last recencyPeriod. public int GetNumRecentlyUsed(TimeSpan recencyPeriod) { var now = DateTime.UtcNow; int sum = 0; foreach (var bucket in buckets) { // Ticket is the date time when this bucket should be collected (last touched time plus age limit) // For now we take the shortest age limit as an approximation of the per-type age limit. DateTime ticket = bucket.Key; var timeTillCollection = ticket - now; var timeSinceLastUsed = shortestAgeLimit - timeTillCollection; if (timeSinceLastUsed <= recencyPeriod) { sum += bucket.Value.Items.Count; } } return sum; } /// /// Collects all eligible grain activations which have been idle for at least . /// /// The age limit. /// A representing the work performed. public Task CollectActivations(TimeSpan ageLimit, CancellationToken cancellationToken) => CollectActivationsImpl(false, ageLimit, cancellationToken); /// /// Schedules the provided grain context for collection if it becomes idle for the specified duration. /// /// /// The grain context. /// /// /// The current idle collection time for the grain. /// public void ScheduleCollection(ICollectibleGrainContext item, TimeSpan timeout, DateTime now) { lock (item) { if (item.IsExemptFromCollection) { return; } DateTime ticket = MakeTicketFromTimeSpan(timeout, now); if (default != item.CollectionTicket) { throw new InvalidOperationException("Call CancelCollection before calling ScheduleCollection."); } Add(item, ticket); } } /// /// Tries the cancel idle activation collection. /// /// The grain context. /// if collection was canceled, otherwise. public bool TryCancelCollection(ICollectibleGrainContext item) { if (item is null) return false; if (item.IsExemptFromCollection) return false; lock (item) { DateTime ticket = item.CollectionTicket; if (default == ticket) return false; if (IsExpired(ticket)) return false; // first, we attempt to remove the ticket. Bucket bucket; if (!buckets.TryGetValue(ticket, out bucket) || !bucket.TryRemove(item)) return false; } return true; } /// /// Tries the reschedule collection. /// /// The grain context. /// if collection was canceled, otherwise. public bool TryRescheduleCollection(ICollectibleGrainContext item) { if (item.IsExemptFromCollection) return false; lock (item) { if (TryRescheduleCollection_Impl(item, item.CollectionAgeLimit)) return true; item.CollectionTicket = default; return false; } } private bool TryRescheduleCollection_Impl(ICollectibleGrainContext item, TimeSpan timeout) { // note: we expect the activation lock to be held. if (default == item.CollectionTicket) return false; ThrowIfTicketIsInvalid(item.CollectionTicket); if (IsExpired(item.CollectionTicket)) return false; DateTime oldTicket = item.CollectionTicket; DateTime newTicket = MakeTicketFromTimeSpan(timeout, DateTime.UtcNow); // if the ticket value doesn't change, then the source and destination bucket are the same and there's nothing to do. if (newTicket.Equals(oldTicket)) return true; Bucket bucket; if (!buckets.TryGetValue(oldTicket, out bucket) || !bucket.TryRemove(item)) { // fail: item is not associated with currentKey. return false; } // it shouldn't be possible for Add to throw an exception here, as only one concurrent competitor should be able to reach to this point in the method. Add(item, newTicket); return true; } private bool DequeueQuantum(out List items, DateTime now) { DateTime key; lock (buckets) { if (nextTicket > now) { items = null; return false; } key = nextTicket; nextTicket += _grainCollectionOptions.CollectionQuantum; } Bucket bucket; if (!buckets.TryRemove(key, out bucket)) { items = nothing; return true; } items = bucket.CancelAll(); return true; } /// public override string ToString() { var now = DateTime.UtcNow; var all = buckets.ToList(); var bucketsText = Utils.EnumerableToString(all.OrderBy(bucket => bucket.Key), bucket => $"{Utils.TimeSpanToString(bucket.Key - now)}->{bucket.Value.Items.Count} items"); return $"<#Activations={all.Sum(b => b.Value.Items.Count)}, #Buckets={all.Count}, buckets={bucketsText}>"; } /// /// Scans for activations that are due for collection. /// /// A list of activations that are due for collection. public List ScanStale() { var now = DateTime.UtcNow; List condemned = null; while (DequeueQuantum(out var activations, now)) { // At this point, all tickets associated with activations are cancelled and any attempts to reschedule will fail silently. // If the activation is to be reactivated, it's our job to clear the activation's copy of the ticket. foreach (var activation in activations) { lock (activation) { activation.CollectionTicket = default; if (!activation.IsValid) { // This is not an error scenario because the activation may have become invalid between the time // we captured a snapshot in 'DequeueQuantum' and now. We are not be able to observe such changes. // Do nothing: don't collect, don't reschedule. } else if (activation.KeepAliveUntil > now) { var keepAliveDuration = activation.KeepAliveUntil - now; var timeout = TimeSpan.FromTicks(Math.Max(keepAliveDuration.Ticks, activation.CollectionAgeLimit.Ticks)); ScheduleCollection(activation, timeout, now); } else if (!activation.IsInactive || !activation.IsStale()) { ScheduleCollection(activation, activation.CollectionAgeLimit, now); } else { // Atomically set Deactivating state, to disallow any new requests or new timer ticks to be dispatched on this activation. condemned ??= []; condemned.Add(activation); } } } } return condemned ?? nothing; } /// /// Scans for activations that have been idle for the specified age limit. /// /// The age limit. /// The grain activations which have been idle for at least the specified age limit. public List ScanAll(TimeSpan ageLimit) { List condemned = null; var now = DateTime.UtcNow; foreach (var kv in buckets) { var bucket = kv.Value; foreach (var kvp in bucket.Items) { var activation = kvp.Value; lock (activation) { if (!activation.IsValid) { // Do nothing: don't collect, don't reschedule. } else if (activation.KeepAliveUntil > now) { // do nothing } else if (!activation.IsInactive) { // do nothing } else { if (activation.GetIdleness() >= ageLimit) { if (bucket.TryRemove(activation)) { condemned ??= []; condemned.Add(activation); } // someone else has already deactivated the activation, so there's nothing to do. } else { // activation is not idle long enough for collection. do nothing. } } } } } return condemned ?? nothing; } // Internal for testing. It's expected that when this returns true, activation shedding will occur. internal bool IsMemoryOverloaded(out int surplusActivationCount) { var stats = _environmentStatisticsProvider.GetEnvironmentStatistics(); var limit = _grainCollectionOptions.MemoryUsageLimitPercentage / 100f; var usage = stats.NormalizedMemoryUsage; if (usage <= limit) { // High memory pressure is not detected, so we do not need to deactivate any activations. surplusActivationCount = 0; return false; } // Calculate the surplus activations based the memory usage target. var activationCount = _activationCount; var target = _grainCollectionOptions.MemoryUsageTargetPercentage / 100f; surplusActivationCount = (int)Math.Max(0, activationCount - Math.Floor(activationCount * target / usage)); if (surplusActivationCount <= 0) { surplusActivationCount = 0; return false; } var surplusActivationPercentage = 100 * (1 - target / usage); LogCurrentHighMemoryPressureStats(stats.MemoryUsagePercentage, _grainCollectionOptions.MemoryUsageLimitPercentage, deactivationTarget: surplusActivationCount, activationCount, surplusActivationPercentage); return true; } /// /// Deactivates activations in due time order /// internal for testing /// internal async Task DeactivateInDueTimeOrder(int count, CancellationToken cancellationToken) { var watch = ValueStopwatch.StartNew(); var number = Interlocked.Increment(ref collectionNumber); long memBefore = GC.GetTotalMemory(false) / (1024 * 1024); // MB LogBeforeCollection(number, memBefore, _activationCount, this); var candidates = new List(count); // snapshot to avoid concurrency collection modification issues var bucketSnapshot = buckets.ToArray(); foreach (var bucket in bucketSnapshot.OrderBy(b => b.Key)) { foreach (var item in bucket.Value.Items) { if (candidates.Count >= count) { break; } var activation = item.Value; candidates.Add(activation); } if (candidates.Count >= count) { break; } } CatalogInstruments.ActivationCollections.Add(1); if (candidates.Count > 0) { LogCollectActivations(new(candidates)); var reason = new DeactivationReason( DeactivationReasonCode.HighMemoryPressure, $"Process memory utilization exceeded the configured limit of '{_grainCollectionOptions.MemoryUsageLimitPercentage}'. Detected memory usage is {memBefore} MB."); await DeactivateActivationsFromCollector(candidates, cancellationToken, reason); } long memAfter = GC.GetTotalMemory(false) / (1024 * 1024); watch.Stop(); LogAfterCollection(number, memAfter, _activationCount, candidates.Count, this, watch.Elapsed); } private static DeactivationReason GetDeactivationReason() { var reasonText = "This activation has become idle."; var reason = new DeactivationReason(DeactivationReasonCode.ActivationIdle, reasonText); return reason; } private void ThrowIfTicketIsInvalid(DateTime ticket) { if (ticket.Ticks == 0) throw new ArgumentException("Empty ticket is not allowed in this context."); if (0 != ticket.Ticks % _grainCollectionOptions.CollectionQuantum.Ticks) { throw new ArgumentException(string.Format("invalid ticket ({0})", ticket)); } } private bool IsExpired(DateTime ticket) { return ticket < nextTicket; } public DateTime MakeTicketFromDateTime(DateTime timestamp) { // Round the timestamp to the next _grainCollectionOptions.CollectionQuantum. e.g. if the _grainCollectionOptions.CollectionQuantum is 1 minute and the timestamp is 3:45:22, then the ticket will be 3:46. // Note that TimeStamp.Ticks and DateTime.Ticks both return a long. var ticketTicks = ((timestamp.Ticks - 1) / _grainCollectionOptions.CollectionQuantum.Ticks + 1) * _grainCollectionOptions.CollectionQuantum.Ticks; if (ticketTicks > DateTime.MaxValue.Ticks) { return DateTime.MaxValue; } var ticket = new DateTime(ticketTicks, DateTimeKind.Utc); if (ticket < nextTicket) { throw new ArgumentException(string.Format("The earliest collection that can be scheduled from now is for {0}", new DateTime(nextTicket.Ticks - _grainCollectionOptions.CollectionQuantum.Ticks + 1, DateTimeKind.Utc))); } return ticket; } private DateTime MakeTicketFromTimeSpan(TimeSpan timeout, DateTime now) { if (timeout < _grainCollectionOptions.CollectionQuantum) { throw new ArgumentException(string.Format("timeout must be at least {0}, but it is {1}", _grainCollectionOptions.CollectionQuantum, timeout), nameof(timeout)); } return MakeTicketFromDateTime(now + timeout); } private void Add(ICollectibleGrainContext item, DateTime ticket) { // note: we expect the activation lock to be held. item.CollectionTicket = ticket; var bucket = buckets.GetOrAdd(ticket, _ => new Bucket()); bucket.Add(item); } void IActivationWorkingSetObserver.OnAdded(IActivationWorkingSetMember member) { if (member is ICollectibleGrainContext activation) { Interlocked.Increment(ref _activationCount); if (activation.CollectionTicket == default) { ScheduleCollection(activation, activation.CollectionAgeLimit, DateTime.UtcNow); } else { TryRescheduleCollection(activation); } } } void IActivationWorkingSetObserver.OnActive(IActivationWorkingSetMember member) { // We do not need to do anything when a grain becomes active, since we can lazily handle it when scanning its bucket instead. // This reduces the amount of unnecessary work performed. } void IActivationWorkingSetObserver.OnEvicted(IActivationWorkingSetMember member) { if (member is ICollectibleGrainContext activation && activation.CollectionTicket == default) { TryRescheduleCollection(activation); } } void IActivationWorkingSetObserver.OnDeactivating(IActivationWorkingSetMember member) { if (member is ICollectibleGrainContext activation) { TryCancelCollection(activation); } } void IActivationWorkingSetObserver.OnDeactivated(IActivationWorkingSetMember member) { Interlocked.Decrement(ref _activationCount); _ = TryCancelCollection(member as ICollectibleGrainContext); } private Task Start(CancellationToken cancellationToken) { using var _ = new ExecutionContextSuppressor(); _collectionLoopTask = RunActivationCollectionLoop(); if (_grainCollectionOptions.EnableActivationSheddingOnMemoryPressure) { _memBasedDeactivationLoopTask = RunMemoryBasedDeactivationLoop(); } return Task.CompletedTask; } private async Task Stop(CancellationToken cancellationToken) { using var registration = cancellationToken.Register(() => _shutdownCts.Cancel()); _collectionTimer.Dispose(); _memBasedDeactivationTimer?.Dispose(); if (_collectionLoopTask is Task task) { await task.WaitAsync(cancellationToken); } if (_memBasedDeactivationLoopTask is Task deactivationLoopTask) { await deactivationLoopTask.WaitAsync(cancellationToken); } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(ActivationCollector), ServiceLifecycleStage.RuntimeServices, Start, Stop); } private async Task RunActivationCollectionLoop() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); var cancellationToken = _shutdownCts.Token; while (await _collectionTimer.WaitForNextTickAsync()) { try { await this.CollectActivationsImpl(true, ageLimit: default, cancellationToken); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { // most probably shutdown } catch (Exception exception) { LogErrorWhileCollectingActivations(exception); } } } private async Task RunMemoryBasedDeactivationLoop() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); var cancellationToken = _shutdownCts.Token; int lastGen2GcCount = 0; try { while (await _memBasedDeactivationTimer.WaitForNextTickAsync(cancellationToken)) { try { var currentGen2GcCount = GC.CollectionCount(2); // note: GC.CollectionCount(2) will return 0 if no gen2 gc happened yet and we rely on this behavior: // high memory pressure situation cannot occur until gen2 occurred at least once if (currentGen2GcCount <= lastGen2GcCount) { // No Gen2 GC since last deactivation cycle. // Wait for Gen2 GC between cycles to be sure that continue; } if (!IsMemoryOverloaded(out var surplusActivationCount)) { continue; } lastGen2GcCount = currentGen2GcCount; await DeactivateInDueTimeOrder(surplusActivationCount, cancellationToken); } catch (Exception exception) { // Ignore cancellation exceptions during shutdown. if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested) { break; } LogErrorWhileCollectingActivations(exception); } } } catch (Exception exception) { if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested) { // Ignore cancellation exceptions during shutdown. } else { throw; } } } private async Task CollectActivationsImpl(bool scanStale, TimeSpan ageLimit, CancellationToken cancellationToken) { var watch = ValueStopwatch.StartNew(); var number = Interlocked.Increment(ref collectionNumber); long memBefore = GC.GetTotalMemory(false) / (1024 * 1024); LogBeforeCollection(number, memBefore, _activationCount, this); List list = scanStale ? ScanStale() : ScanAll(ageLimit); CatalogInstruments.ActivationCollections.Add(1); if (list is { Count: > 0 }) { LogCollectActivations(new(list)); await DeactivateActivationsFromCollector(list, cancellationToken); } long memAfter = GC.GetTotalMemory(false) / (1024 * 1024); watch.Stop(); LogAfterCollection(number, memAfter, _activationCount, list?.Count ?? 0, this, watch.Elapsed); } private async Task DeactivateActivationsFromCollector(List list, CancellationToken cancellationToken, DeactivationReason? deactivationReason = null) { LogDeactivateActivationsFromCollector(list.Count); CatalogInstruments.ActivationShutdownViaCollection(); deactivationReason ??= GetDeactivationReason(); var options = new ParallelOptions { // Avoid passing the cancellation token, since we want all of these activations to be deactivated, even if cancellation is triggered. CancellationToken = CancellationToken.None, MaxDegreeOfParallelism = Environment.ProcessorCount * 512 }; await Parallel.ForEachAsync(list, options, async (activationData, token) => { // Continue deactivation when ready. activationData.Deactivate(deactivationReason.Value, cancellationToken); await activationData.Deactivated.ConfigureAwait(false); }).WaitAsync(cancellationToken); } public void Dispose() { _collectionTimer.Dispose(); _shutdownCts.Dispose(); _memBasedDeactivationTimer?.Dispose(); } private class Bucket { public ConcurrentDictionary Items { get; } = new(ReferenceEqualsComparer.Default); public void Add(ICollectibleGrainContext item) { if (!Items.TryAdd(item, item)) { throw new InvalidOperationException("item is already associated with this bucket"); } } public bool TryRemove(ICollectibleGrainContext item) { lock (item) { if (item.CollectionTicket == default) { return false; } item.CollectionTicket = default; } return Items.TryRemove(item, out _); } public List CancelAll() { List result = null; foreach (var pair in Items) { // Attempt to cancel the item. if we succeed, it wasn't already cancelled and we can return it. otherwise, we silently ignore it. var item = pair.Value; lock (item) { if (item.CollectionTicket == default) { continue; } item.CollectionTicket = default; } result ??= []; result.Add(pair.Value); } return result ?? nothing; } } [LoggerMessage( Level = LogLevel.Information, Message = "High memory pressure detected ({MemoryUsagePercentage:F2}% > {MemoryUsageLimitPercentage:F2}%). Deactivating up to {DeactivationTarget:N0}/{ActivationCount:N0} ({SurplusActivationPercentage:F2}%) grains to free memory." )] private partial void LogCurrentHighMemoryPressureStats(double memoryUsagePercentage, double memoryUsageLimitPercentage, int deactivationTarget, int activationCount, double surplusActivationPercentage); [LoggerMessage( Level = LogLevel.Error, Message = "Error while collecting activations." )] private partial void LogErrorWhileCollectingActivations(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.Catalog_BeforeCollection, Level = LogLevel.Debug, Message = "Before collection #{CollectionNumber}: memory: {MemoryBefore}MB, #activations: {ActivationCount}, collector: {CollectorStatus}" )] private partial void LogBeforeCollection(int collectionNumber, long memoryBefore, int activationCount, ActivationCollector collectorStatus); [LoggerMessage( Level = LogLevel.Trace, Message = "CollectActivations {Activations}" )] private partial void LogCollectActivations(ActivationsLogValue activations); private struct ActivationsLogValue(List list) { public override string ToString() => list.ToStrings(d => d.GrainId.ToString() + d.ActivationId); } [LoggerMessage( EventId = (int)ErrorCode.Catalog_AfterCollection, Level = LogLevel.Debug, Message = "After collection #{CollectionNumber} memory: {MemoryAfter}MB, #activations: {ActivationCount}, collected {CollectedCount} activations, collector: {CollectorStatus}, collection time: {CollectionTime}" )] private partial void LogAfterCollection(int collectionNumber, long memoryAfter, int activationCount, int collectedCount, ActivationCollector collectorStatus, TimeSpan collectionTime); [LoggerMessage( EventId = (int)ErrorCode.Catalog_ShutdownActivations_1, Level = LogLevel.Information, Message = "Deactivating '{Count}' idle activations." )] private partial void LogDeactivateActivationsFromCollector(int count); } } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationData.cs ================================================ #nullable enable using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Core.Internal; using Orleans.Diagnostics; using Orleans.GrainDirectory; using Orleans.Internal; using Orleans.Runtime.Placement; using Orleans.Runtime.Scheduler; using Orleans.Serialization.Invocation; using Orleans.Serialization.Session; using Orleans.Serialization.TypeSystem; namespace Orleans.Runtime; /// /// Maintains additional per-activation state that is required for Orleans internal operations. /// MUST lock this object for any concurrent access /// MUST lock on `this` object because there are locks taken on ActivationData instances in various places in the codebase such as ActivationCollector.ScheduleCollection. /// Consider: compartmentalize by usage, e.g., using separate interfaces for data for catalog, etc. /// [DebuggerDisplay("GrainId = {GrainId}, State = {State}, Waiting = {WaitingCount}, Executing = {IsCurrentlyExecuting}")] internal sealed partial class ActivationData : IGrainContext, ICollectibleGrainContext, IGrainExtensionBinder, IActivationWorkingSetMember, IGrainTimerRegistry, IGrainManagementExtension, IGrainCallCancellationExtension, ICallChainReentrantGrainContext, IAsyncDisposable, IDisposable { private const string GrainAddressMigrationContextKey = "sys.addr"; private readonly GrainTypeSharedContext _shared; private readonly IServiceScope _serviceScope; private readonly WorkItemGroup _workItemGroup; private readonly List<(Message Message, CoarseStopwatch QueuedTime)> _waitingRequests = new(); private readonly Dictionary _runningRequests = new(); private readonly SingleWaiterAutoResetEvent _workSignal = new() { RunContinuationsAsynchronously = true }; private GrainLifecycle? _lifecycle; private Queue? _pendingOperations; private Message? _blockingRequest; private bool _isInWorkingSet = true; private CoarseStopwatch _busyDuration; private CoarseStopwatch _idleDuration; private GrainReference? _selfReference; // Values which are needed less frequently and do not warrant living directly on activation for object size reasons. // The values in this field are typically used to represent termination state of an activation or features which are not // used by all grains, such as grain timers. private ActivationDataExtra? _extras; // The task representing this activation's message loop. // This field is assigned and never read and exists only for debugging purposes (eg, in memory dumps, to associate a loop task with an activation). #pragma warning disable IDE0052 // Remove unread private members private Task? _messageLoopTask; #pragma warning restore IDE0052 // Remove unread private members private Activity? _activationActivity; /// /// Constants for activity error event names used during activation lifecycle. /// private static class ActivityErrorEvents { public const string InstanceCreateFailed = "instance-create-failed"; public const string DirectoryRegisterFailed = "directory-register-failed"; public const string ActivationCancelled = "activation-cancelled"; public const string ActivationFailed = "activation-failed"; public const string ActivationError = "activation-error"; public const string OnActivateFailed = "on-activate-failed"; public const string OnDeactivateFailed = "on-deactivate-failed"; public const string RehydrateError = "rehydrate-error"; public const string DehydrateError = "dehydrate-error"; } public ActivationData( GrainAddress grainAddress, Func createWorkItemGroup, IServiceProvider applicationServices, GrainTypeSharedContext shared) { ArgumentNullException.ThrowIfNull(grainAddress); ArgumentNullException.ThrowIfNull(createWorkItemGroup); ArgumentNullException.ThrowIfNull(applicationServices); ArgumentNullException.ThrowIfNull(shared); _shared = shared; Address = grainAddress; _serviceScope = applicationServices.CreateScope(); Debug.Assert(_serviceScope != null, "_serviceScope must not be null."); _workItemGroup = createWorkItemGroup(this); Debug.Assert(_workItemGroup != null, "_workItemGroup must not be null."); } internal void SetActivationActivity(Activity activity) { _activationActivity = activity; } /// /// Gets the activity context for the activation activity, if available. /// This allows child activities to be properly parented during activation lifecycle operations. /// internal ActivityContext? GetActivationActivityContext() { return _activationActivity?.Context; } public void Start(IGrainActivator grainActivator) { Debug.Assert(Equals(ActivationTaskScheduler, TaskScheduler.Current)); // locking on `this` is intentional as there are other places in the codebase taking locks on ActivationData instances lock (this) { try { var instance = grainActivator.CreateInstance(this); SetGrainInstance(instance); _activationActivity?.AddEvent(new ActivityEvent("instance-created")); } catch (Exception exception) { SetActivityError(_activationActivity, exception, ActivityErrorEvents.InstanceCreateFailed); Deactivate(new(DeactivationReasonCode.ActivationFailed, exception, "Error constructing grain instance."), _activationActivity?.Context, CancellationToken.None); } _messageLoopTask = RunMessageLoop(); } } public ActivationTaskScheduler ActivationTaskScheduler => _workItemGroup.TaskScheduler; public IGrainRuntime GrainRuntime => _shared.Runtime; public object? GrainInstance { get; private set; } public GrainAddress Address { get; private set; } public GrainReference GrainReference => _selfReference ??= _shared.GrainReferenceActivator.CreateReference(GrainId, default); public ActivationState State { get; private set; } = ActivationState.Creating; public PlacementStrategy PlacementStrategy => _shared.PlacementStrategy; public DateTime CollectionTicket { get; set; } public IServiceProvider ActivationServices => _serviceScope.ServiceProvider; public ActivationId ActivationId => Address.ActivationId; public IGrainLifecycle ObservableLifecycle { get { if (_lifecycle is { } lifecycle) return lifecycle; lock (this) { return _lifecycle ??= new GrainLifecycle(_shared.Logger); } } } internal GrainTypeSharedContext Shared => _shared; public GrainId GrainId => Address.GrainId; public bool IsExemptFromCollection => _shared.CollectionAgeLimit == Timeout.InfiniteTimeSpan; public DateTime KeepAliveUntil { get; set; } = DateTime.MinValue; public bool IsValid => State is ActivationState.Valid; // Currently, the only supported multi-activation grain is one using the StatelessWorkerPlacement strategy. internal bool IsStatelessWorker => PlacementStrategy is StatelessWorkerPlacement; /// /// Returns a value indicating whether or not this placement strategy requires activations to be registered in /// the grain directory. /// internal bool IsUsingGrainDirectory => PlacementStrategy.IsUsingGrainDirectory; public int WaitingCount => _waitingRequests.Count; public bool IsInactive => !IsCurrentlyExecuting && _waitingRequests.Count == 0; public bool IsCurrentlyExecuting => _runningRequests.Count > 0; public IWorkItemScheduler Scheduler => _workItemGroup; public Task Deactivated => GetDeactivationCompletionSource().Task; public SiloAddress? ForwardingAddress { get => _extras?.ForwardingAddress; set { lock (this) { _extras ??= new(); _extras.ForwardingAddress = value; } } } /// /// Gets the previous directory registration for this grain, if known. /// This is used to update the grain directory to point to the new registration during activation. /// public GrainAddress? PreviousRegistration { get => _extras?.PreviousRegistration; set { lock (this) { _extras ??= new(); _extras.PreviousRegistration = value; } } } private Exception? DeactivationException => _extras?.DeactivationReason.Exception; private DeactivationReason DeactivationReason { get => _extras?.DeactivationReason ?? default; set { lock (this) { _extras ??= new(); _extras.DeactivationReason = value; } } } private HashSet? Timers { get => _extras?.Timers; set { lock (this) { _extras ??= new(); _extras.Timers = value; } } } private DateTime? DeactivationStartTime { get => _extras?.DeactivationStartTime; set { lock (this) { _extras ??= new(); _extras.DeactivationStartTime = value; } } } private bool IsStuckDeactivating { get => _extras?.IsStuckDeactivating ?? false; set { lock (this) { _extras ??= new(); _extras.IsStuckDeactivating = value; } } } private bool IsStuckProcessingMessage { get => _extras?.IsStuckProcessingMessage ?? false; set { lock (this) { _extras ??= new(); _extras.IsStuckProcessingMessage = value; } } } private DehydrationContextHolder? DehydrationContext { get => _extras?.DehydrationContext; set { lock (this) { _extras ??= new(); _extras.DehydrationContext = value; } } } public TimeSpan CollectionAgeLimit => _shared.CollectionAgeLimit; public object? GetTarget() => GrainInstance; object? ITargetHolder.GetComponent(Type componentType) { var result = GetComponent(componentType); if (result is null && typeof(IGrainExtension).IsAssignableFrom(componentType)) { var implementation = ActivationServices.GetKeyedService(componentType); if (implementation is not { } typedResult) { throw new GrainExtensionNotInstalledException($"No extension of type {componentType} is installed on this instance and no implementations are registered for automated install"); } SetComponent(componentType, typedResult); result = typedResult; } return result; } public TComponent? GetComponent() where TComponent : class => GetComponent(typeof(TComponent)) as TComponent; public object? GetComponent(Type componentType) { object? result; if (componentType.IsAssignableFrom(GrainInstance?.GetType())) { result = GrainInstance; } else if (componentType.IsAssignableFrom(GetType())) { result = this; } else if (_extras is { } components && components.TryGetValue(componentType, out var resultObj)) { result = resultObj; } else if (_shared.GetComponent(componentType) is { } sharedComponent) { result = sharedComponent; } else if (ActivationServices.GetService(componentType) is { } component) { SetComponent(componentType, component); result = component; } else { result = null; } return result; } public void SetComponent(TComponent? instance) where TComponent : class => SetComponent(typeof(TComponent), instance); public void SetComponent(Type componentType, object? instance) { if (componentType.IsAssignableFrom(GrainInstance?.GetType())) { throw new ArgumentException("Cannot override a component which is implemented by this grain"); } else if (componentType.IsAssignableFrom(GetType())) { throw new ArgumentException("Cannot override a component which is implemented by this grain context"); } lock (this) { if (instance == null) { _extras?.Remove(componentType); return; } _extras ??= new(); _extras[componentType] = instance; } } internal void SetGrainInstance(object grainInstance) { ArgumentNullException.ThrowIfNull(grainInstance); lock (this) { if (GrainInstance is not null) { throw new InvalidOperationException("Grain instance is already set."); } if (State is not ActivationState.Creating) { throw new InvalidOperationException("Grain instance can only be set during creation."); } GrainInstance = grainInstance; _shared.OnCreateActivation(this); GetComponent()?.OnCreateActivation(this); if (grainInstance is ILifecycleParticipant participant) { participant.Participate(ObservableLifecycle); } } } public void SetState(ActivationState state) { State = state; } /// /// Check whether this activation is overloaded. /// Returns LimitExceededException if overloaded, otherwise nullc> /// /// Returns LimitExceededException if overloaded, otherwise nullc> public LimitExceededException? CheckOverloaded() { string limitName = nameof(SiloMessagingOptions.MaxEnqueuedRequestsHardLimit); int maxRequestsHardLimit = _shared.MessagingOptions.MaxEnqueuedRequestsHardLimit; int maxRequestsSoftLimit = _shared.MessagingOptions.MaxEnqueuedRequestsSoftLimit; if (IsStatelessWorker) { limitName = nameof(SiloMessagingOptions.MaxEnqueuedRequestsHardLimit_StatelessWorker); maxRequestsHardLimit = _shared.MessagingOptions.MaxEnqueuedRequestsHardLimit_StatelessWorker; maxRequestsSoftLimit = _shared.MessagingOptions.MaxEnqueuedRequestsSoftLimit_StatelessWorker; } if (maxRequestsHardLimit <= 0 && maxRequestsSoftLimit <= 0) return null; // No limits are set int count = GetRequestCount(); if (maxRequestsHardLimit > 0 && count > maxRequestsHardLimit) // Hard limit { LogRejectActivationTooManyRequests(_shared.Logger, count, this, maxRequestsHardLimit); return new LimitExceededException(limitName, count, maxRequestsHardLimit, ToString()); } if (maxRequestsSoftLimit > 0 && count > maxRequestsSoftLimit) // Soft limit { LogWarnActivationTooManyRequests(_shared.Logger, count, this, maxRequestsSoftLimit); return null; } return null; } internal int GetRequestCount() { lock (this) { return _runningRequests.Count + WaitingCount; } } internal List DequeueAllWaitingRequests() { lock (this) { var result = new List(_waitingRequests.Count); foreach (var (message, _) in _waitingRequests) { // Local-only messages are not allowed to escape the activation. if (message.IsLocalOnly) { continue; } result.Add(message); } _waitingRequests.Clear(); return result; } } /// /// Returns how long this activation has been idle. /// public TimeSpan GetIdleness() => _idleDuration.Elapsed; /// /// Returns whether this activation has been idle long enough to be collected. /// public bool IsStale() => GetIdleness() >= _shared.CollectionAgeLimit; public void DelayDeactivation(TimeSpan timespan) { if (timespan == TimeSpan.MaxValue || timespan == Timeout.InfiniteTimeSpan) { // otherwise creates negative time. KeepAliveUntil = DateTime.MaxValue; } else if (timespan <= TimeSpan.Zero) { // reset any current keepAliveUntil ResetKeepAliveRequest(); } else { KeepAliveUntil = GrainRuntime.TimeProvider.GetUtcNow().UtcDateTime + timespan; } } public void ResetKeepAliveRequest() => KeepAliveUntil = DateTime.MinValue; private void ScheduleOperation(object operation) { lock (this) { _pendingOperations ??= new(); _pendingOperations.Enqueue(operation); } _workSignal.Signal(); } private void CancelPendingOperations() { lock (this) { // If the grain is currently activating, cancel that operation. if (_pendingOperations is not { Count: > 0 } operations) { return; } var opCount = operations.Count; // We could be using an ArrayPool here, but we would need to filter out the // non-command types, which means we would need to loop over _pendingOperations, which // defeats the purpose of making a snapshot. // Note: CopyTo does not use the queue's enumerator, so its safe to take a snapshot this way. var array = ArrayPool.Shared.Rent(opCount); operations.CopyTo(array, 0); var snapshot = new Span(array, 0, opCount); try { foreach (var op in snapshot) { if (op is Command cmd) { try { cmd.Cancel(); } catch (Exception exception) { if (exception is not ObjectDisposedException) { LogErrorCancellingOperation(_shared.Logger, exception, cmd); } } } } } finally { ArrayPool.Shared.Return(array, true); } } } public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken = default) { lock (this) { if (State is not (ActivationState.Activating or ActivationState.Valid or ActivationState.Deactivating)) { return; } // If migration has not already been started, set a migration context to capture any state which should be transferred. // Doing this signals to the deactivation process that a migration is occurring, so it is important that this happens before we begin deactivation. DehydrationContext ??= new(_shared.SerializerSessionPool, requestContext); if (State is not ActivationState.Deactivating) { // Start deactivating the grain to prepare for migration. Deactivate(new DeactivationReason(DeactivationReasonCode.Migrating, "Migrating to a new location."), cancellationToken); } } } public void Deactivate(DeactivationReason reason, ActivityContext? activityContext, CancellationToken cancellationToken = default) { var currentActivity = Activity.Current; var deactivateActivity = activityContext is { } parent ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.DeactivateGrain, ActivityKind.Internal, parentContext:parent) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.DeactivateGrain); lock (this) { try { var state = State; if (deactivateActivity is { IsAllDataRequested: true }) { deactivateActivity.SetTag(ActivityTagKeys.GrainState, state); } if (state is ActivationState.Invalid) { deactivateActivity?.Stop(); return; } if (DeactivationReason.ReasonCode == DeactivationReasonCode.None) { DeactivationReason = reason; } if (deactivateActivity is { IsAllDataRequested: true }) { deactivateActivity.SetTag(ActivityTagKeys.DeactivationReason, DeactivationReason); } if (!DeactivationStartTime.HasValue) { DeactivationStartTime = GrainRuntime.TimeProvider.GetUtcNow().UtcDateTime; } if (state is ActivationState.Creating or ActivationState.Activating or ActivationState.Valid) { CancelPendingOperations(); _shared.InternalRuntime.ActivationWorkingSet.OnDeactivating(this); SetState(ActivationState.Deactivating); var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(_shared.InternalRuntime.CollectionOptions.Value.DeactivationTimeout); ScheduleOperation(new Command.Deactivate(cts, state, deactivateActivity)); } else { deactivateActivity?.Stop(); } } catch (Exception ex) { SetActivityError(deactivateActivity, ex, "Error deactivating grain"); deactivateActivity?.Stop(); throw; } finally { Activity.Current = currentActivity; } } } public void Deactivate(DeactivationReason reason, CancellationToken cancellationToken = default) => Deactivate(reason, Activity.Current?.Context, cancellationToken); private void DeactivateStuckActivation() { IsStuckProcessingMessage = true; var msg = $"Activation {this} has been processing request {_blockingRequest} since {_busyDuration} and is likely stuck."; var reason = new DeactivationReason(DeactivationReasonCode.ActivationUnresponsive, msg); // Mark the grain as deactivating so that messages are forwarded instead of being invoked Deactivate(reason, cancellationToken: default); // Try to remove this activation from the catalog and directory // This leaves this activation dangling, stuck processing the current request until it eventually completes // (which likely will never happen at this point, since if the grain was deemed stuck then there is probably some kind of // application bug, perhaps a deadlock) UnregisterMessageTarget(); _shared.InternalRuntime.GrainLocator.Unregister(Address, UnregistrationCause.Force).Ignore(); } void IGrainTimerRegistry.OnTimerCreated(IGrainTimer timer) { lock (this) { Timers ??= new HashSet(); Timers.Add(timer); } } void IGrainTimerRegistry.OnTimerDisposed(IGrainTimer timer) { lock (this) // need to lock since dispose can be called on finalizer thread, outside grain context (not single threaded). { if (Timers is null) { return; } Timers.Remove(timer); } } private void DisposeTimers() { lock (this) { if (Timers is null) { return; } // Need to set Timers to null since OnTimerDisposed mutates the timers set if it is not null. var timers = Timers; Timers = null; // Dispose all timers. foreach (var timer in timers) { timer.Dispose(); } } } public void AnalyzeWorkload(DateTime now, IMessageCenter messageCenter, MessageFactory messageFactory, SiloMessagingOptions options) { var slowRunningRequestDuration = options.RequestProcessingWarningTime; var longQueueTimeDuration = options.RequestQueueDelayWarningTime; List? diagnostics = null; lock (this) { if (State != ActivationState.Valid) { return; } if (_blockingRequest is not null) { var message = _blockingRequest; TimeSpan? timeSinceQueued = default; if (_runningRequests.TryGetValue(message, out var waitTime)) { timeSinceQueued = waitTime.Elapsed; } var executionTime = _busyDuration.Elapsed; if (executionTime >= slowRunningRequestDuration && !message.IsLocalOnly) { GetStatusList(ref diagnostics); if (timeSinceQueued.HasValue) { diagnostics.Add($"Message {message} was enqueued {timeSinceQueued} ago and has now been executing for {executionTime}."); } else { diagnostics.Add($"Message {message} has been executing for {executionTime}."); } var response = messageFactory.CreateDiagnosticResponseMessage(message, isExecuting: true, isWaiting: false, diagnostics); messageCenter.SendMessage(response); } } foreach (var running in _runningRequests) { var message = running.Key; var runDuration = running.Value; if (ReferenceEquals(message, _blockingRequest) || message.IsLocalOnly) { continue; } // Check how long they've been executing. var executionTime = runDuration.Elapsed; if (executionTime >= slowRunningRequestDuration) { // Interleaving message X has been executing for a long time GetStatusList(ref diagnostics); var messageDiagnostics = new List(diagnostics) { $"Interleaving message {message} has been executing for {executionTime}." }; var response = messageFactory.CreateDiagnosticResponseMessage(message, isExecuting: true, isWaiting: false, messageDiagnostics); messageCenter.SendMessage(response); } } var queueLength = 1; foreach (var pair in _waitingRequests) { var message = pair.Message; if (message.IsLocalOnly) { continue; } var queuedTime = pair.QueuedTime.Elapsed; if (queuedTime >= longQueueTimeDuration) { // Message X has been enqueued on the target grain for Y and is currently position QueueLength in queue for processing. GetStatusList(ref diagnostics); var messageDiagnostics = new List(diagnostics) { $"Message {message} has been enqueued on the target grain for {queuedTime} and is currently position {queueLength} in queue for processing." }; var response = messageFactory.CreateDiagnosticResponseMessage(message, isExecuting: false, isWaiting: true, messageDiagnostics); messageCenter.SendMessage(response); } queueLength++; } } void GetStatusList([NotNull] ref List? diagnostics) { if (diagnostics is not null) return; diagnostics = new List { ToDetailedString(), $"TaskScheduler status: {_workItemGroup.DumpStatus()}" }; } } public override string ToString() => $"[Activation: {Address.SiloAddress}/{GrainId}{ActivationId}{GetActivationInfoString()} State={State}]"; internal string ToDetailedString(bool includeExtraDetails = false) { lock (this) { var currentlyExecuting = includeExtraDetails ? _blockingRequest : null; return @$"[Activation: {Address.SiloAddress}/{GrainId}{ActivationId} {GetActivationInfoString()} State={State} NonReentrancyQueueSize={WaitingCount} NumRunning={_runningRequests.Count} IdlenessTimeSpan={GetIdleness()} CollectionAgeLimit={_shared.CollectionAgeLimit}{(currentlyExecuting != null ? " CurrentlyExecuting=" : null)}{currentlyExecuting}]"; } } private string GetActivationInfoString() { var placement = PlacementStrategy?.GetType().Name; var grainTypeName = _shared.GrainTypeName ?? GrainInstance switch { { } grainInstance => RuntimeTypeNameFormatter.Format(grainInstance.GetType()), _ => null }; return grainTypeName is null ? $"#Placement={placement}" : $"#GrainType={grainTypeName} Placement={placement}"; } public void Dispose() => DisposeAsync().AsTask().Wait(); public async ValueTask DisposeAsync() { _extras ??= new(); if (_extras.IsDisposing) return; _extras.IsDisposing = true; CancelPendingOperations(); lock (this) { _shared.InternalRuntime.ActivationWorkingSet.OnDeactivated(this); SetState(ActivationState.Invalid); } DisposeTimers(); try { var activator = _shared.GetComponent(typeof(IGrainActivator)) as IGrainActivator; if (activator != null && GrainInstance is { } instance) { await activator.DisposeInstance(this, instance); } } catch (ObjectDisposedException) { } try { _shared.OnDestroyActivation(this); GetComponent()?.OnDestroyActivation(this); } catch (ObjectDisposedException) { } await DisposeAsync(_serviceScope); } private static async ValueTask DisposeAsync(object obj) { try { if (obj is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else if (obj is IDisposable disposable) { disposable.Dispose(); } } catch { // Ignore. } } bool IEquatable.Equals(IGrainContext? other) => ReferenceEquals(this, other); public (TExtension, TExtensionInterface) GetOrSetExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { TExtension implementation; if (GetComponent() is object existing) { if (existing is TExtension typedResult) { implementation = typedResult; } else { throw new InvalidCastException($"Cannot cast existing extension of type {existing.GetType()} to target type {typeof(TExtension)}"); } } else { implementation = newExtensionFunc(); SetComponent(implementation); } var reference = GrainReference.Cast(); return (implementation, reference); } public TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension { if (GetComponent() is TExtensionInterface result) { return result; } var implementation = ActivationServices.GetKeyedService(typeof(TExtensionInterface)); if (implementation is not TExtensionInterface typedResult) { throw new GrainExtensionNotInstalledException($"No extension of type {typeof(TExtensionInterface)} is installed on this instance and no implementations are registered for automated install"); } SetComponent(typedResult); return typedResult; } bool IActivationWorkingSetMember.IsCandidateForRemoval(bool wouldRemove) { const int IdlenessLowerBound = 10_000; lock (this) { var inactive = IsInactive && _idleDuration.ElapsedMilliseconds > IdlenessLowerBound; // This instance will remain in the working set if it is either not pending removal or if it is currently active. _isInWorkingSet = !wouldRemove || !inactive; return inactive; } } private async Task RunMessageLoop() { // Note that this loop never terminates. That might look strange, but there is a reason for it: // a grain must always accept and process any incoming messages. How a grain processes // those messages is up to the grain's state to determine. If the grain has not yet // completed activating, it will let the messages continue to queue up until it completes activation. // If the grain failed to activate, messages will be responded to with a rejection. // If the grain has terminated, messages will be forwarded on to a new instance of this grain. // The loop will eventually be garbage collected when the grain gets deactivated and there are no // rooted references to it. while (true) { try { if (!IsCurrentlyExecuting) { bool hasPendingOperations; lock (this) { hasPendingOperations = _pendingOperations is { Count: > 0 }; } if (hasPendingOperations) { await ProcessOperationsAsync(); } } ProcessPendingRequests(); await _workSignal.WaitAsync(); } catch (Exception exception) { _shared.InternalRuntime.MessagingTrace.LogError(exception, "Error in grain message loop"); } } void ProcessPendingRequests() { var i = 0; do { Message? message = null; lock (this) { if (_waitingRequests.Count <= i) { break; } message = _waitingRequests[i].Message; // If the activation is not valid, reject all pending messages except for local-only messages. // Local-only messages are used for internal system operations and should not be rejected while the grain is valid or deactivating. if (State != ActivationState.Valid && !(message.IsLocalOnly && State is ActivationState.Deactivating)) { ProcessRequestsToInvalidActivation(); break; } try { if (!MayInvokeRequest(message)) { // The activation is not able to process this message right now, so try the next message. ++i; if (_blockingRequest != null) { var currentRequestActiveTime = _busyDuration.Elapsed; if (currentRequestActiveTime > _shared.MaxRequestProcessingTime && !IsStuckProcessingMessage) { DeactivateStuckActivation(); } else if (currentRequestActiveTime > _shared.MaxWarningRequestProcessingTime) { // Consider: Handle long request detection for reentrant activations -- this logic only works for non-reentrant activations LogWarningDispatcher_ExtendedMessageProcessing( _shared.Logger, currentRequestActiveTime, new(this), _blockingRequest, message); } } continue; } // If the current message is incompatible, deactivate this activation and eventually forward the message to a new incarnation. if (message.InterfaceVersion > 0) { var compatibilityDirector = _shared.InternalRuntime.CompatibilityDirectorManager.GetDirector(message.InterfaceType); var currentVersion = _shared.InternalRuntime.GrainVersionManifest.GetLocalVersion(message.InterfaceType); if (!compatibilityDirector.IsCompatible(message.InterfaceVersion, currentVersion)) { // Add this activation to cache invalidation headers. message.AddToCacheInvalidationHeader(Address, validAddress: null); var reason = new DeactivationReason( DeactivationReasonCode.IncompatibleRequest, $"Received incompatible request for interface {message.InterfaceType} version {message.InterfaceVersion}. This activation supports interface version {currentVersion}."); var activityContext = message.RequestContextData.TryGetActivityContext(); Deactivate(reason, activityContext, cancellationToken: default); return; } } } catch (Exception exception) { if (!message.IsLocalOnly) { _shared.InternalRuntime.MessageCenter.RejectMessage(message, Message.RejectionTypes.Transient, exception); } _waitingRequests.RemoveAt(i); continue; } // Process this message, removing it from the queue. _waitingRequests.RemoveAt(i); Debug.Assert(State == ActivationState.Valid || message.IsLocalOnly); RecordRunning(message, message.IsAlwaysInterleave); } // Start invoking the message outside of the lock InvokeIncomingRequest(message); } while (true); } void RecordRunning(Message message, bool isInterleavable) { var stopwatch = CoarseStopwatch.StartNew(); _runningRequests.Add(message, stopwatch); if (_blockingRequest != null || isInterleavable) return; // This logic only works for non-reentrant activations // Consider: Handle long request detection for reentrant activations. _blockingRequest = message; _busyDuration = stopwatch; } void ProcessRequestsToInvalidActivation() { if (State is ActivationState.Creating or ActivationState.Activating) { // Do nothing until the activation becomes either valid or invalid return; } if (State is ActivationState.Deactivating) { // Determine whether to declare this activation as stuck var deactivatingTime = GrainRuntime.TimeProvider.GetUtcNow().UtcDateTime - DeactivationStartTime!.Value; if (deactivatingTime > _shared.MaxRequestProcessingTime && !IsStuckDeactivating) { IsStuckDeactivating = true; if (DeactivationReason.Description is { Length: > 0 } && DeactivationReason.ReasonCode != DeactivationReasonCode.ActivationUnresponsive) { DeactivationReason = new(DeactivationReasonCode.ActivationUnresponsive, $"{DeactivationReason.Description}. Activation {this} has been deactivating since {DeactivationStartTime.Value} and is likely stuck"); } } if (!IsStuckDeactivating && !IsStuckProcessingMessage) { // Do not forward messages while the grain is still deactivating and has not been declared stuck, since they // will be forwarded to the same grain instance. return; } } if (DeactivationException is null || ForwardingAddress is { }) { // Either this was a duplicate activation or it was at some point successfully activated // Forward all pending messages RerouteAllQueuedMessages(); } else { // Reject all pending messages RejectAllQueuedMessages(); } } bool MayInvokeRequest(Message incoming) { if (!IsCurrentlyExecuting) { return true; } // Otherwise, allow request invocation if the grain is reentrant or the message can be interleaved if (incoming.IsAlwaysInterleave) { return true; } if (_blockingRequest is null) { return true; } if (_blockingRequest.IsReadOnly && incoming.IsReadOnly) { return true; } // Handle call-chain reentrancy if (incoming.GetReentrancyId() is Guid id && IsReentrantSection(id)) { return true; } if (GetComponent() is GrainCanInterleave canInterleave) { try { return canInterleave.MayInterleave(GrainInstance, incoming) || canInterleave.MayInterleave(GrainInstance, _blockingRequest); } catch (Exception exception) { LogErrorInvokingMayInterleavePredicate(_shared.Logger, exception, this, incoming); throw; } } return false; } async Task ProcessOperationsAsync() { object? op = null; while (true) { lock (this) { Debug.Assert(_pendingOperations is not null); // Remove the previous operation. // Operations are not removed until they are completed, allowing for them to see each other. // Eg, a deactivation request can see any on-going activation request and cancel it. if (op is not null) { _pendingOperations.Dequeue(); } // Try to get the next operation. if (!_pendingOperations.TryPeek(out op)) { _pendingOperations = null; return; } } try { switch (op) { case Command.Rehydrate command: RehydrateInternal(command.Context); break; case Command.Activate command: await ActivateAsync(command.RequestContext, command.CancellationToken).SuppressThrowing(); break; case Command.Deactivate command: await FinishDeactivating(command, command.CancellationToken).SuppressThrowing(); break; case Command.Delay command: await Task.Delay(command.Duration, GrainRuntime.TimeProvider, command.CancellationToken).SuppressThrowing(); break; default: throw new NotSupportedException($"Encountered unknown operation of type {op?.GetType().ToString() ?? "null"} {op}."); } } catch (Exception exception) { LogErrorInProcessOperationsAsync(_shared.Logger, exception, this); } finally { if (op is not null) { await DisposeAsync(op); } } } } } private void RehydrateInternal(IRehydrationContext context) { Activity? rehydrateSpan = null; try { LogRehydratingGrain(_shared.Logger, this); var grainMigrationParticipant = GrainInstance as IGrainMigrationParticipant; if (grainMigrationParticipant is not null) { // Start a span for rehydration rehydrateSpan = _activationActivity is not null ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivationRehydrate, ActivityKind.Internal, _activationActivity.Context) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivationRehydrate, ActivityKind.Internal); rehydrateSpan?.SetTag(ActivityTagKeys.GrainId, GrainId.ToString()); rehydrateSpan?.SetTag(ActivityTagKeys.GrainType, _shared.GrainTypeName); rehydrateSpan?.SetTag(ActivityTagKeys.SiloId, _shared.Runtime.SiloAddress.ToString()); rehydrateSpan?.SetTag(ActivityTagKeys.ActivationId, ActivationId.ToString()); } lock (this) { if (State != ActivationState.Creating) { LogIgnoringRehydrateAttempt(_shared.Logger, this, State); rehydrateSpan?.SetTag(ActivityTagKeys.RehydrateIgnored, true); rehydrateSpan?.SetTag(ActivityTagKeys.RehydrateIgnoredReason, $"State is {State}"); return; } if (context.TryGetValue(GrainAddressMigrationContextKey, out GrainAddress? previousRegistration) && previousRegistration is not null) { PreviousRegistration = previousRegistration; LogPreviousActivationAddress(_shared.Logger, previousRegistration); rehydrateSpan?.SetTag(ActivityTagKeys.RehydratePreviousRegistration, previousRegistration.ToFullString()); } if (_lifecycle is { } lifecycle) { foreach (var participant in lifecycle.GetMigrationParticipants()) { participant.OnRehydrate(context); } } grainMigrationParticipant?.OnRehydrate(context); } LogRehydratedGrain(_shared.Logger); rehydrateSpan?.AddEvent(new ActivityEvent("rehydrated")); } catch (Exception exception) { LogErrorRehydratingActivation(_shared.Logger, exception); SetActivityError(rehydrateSpan, exception, ActivityErrorEvents.RehydrateError); } finally { rehydrateSpan?.Dispose(); } } private void OnDehydrate(IDehydrationContext context) { LogDehydratingActivation(_shared.Logger); lock (this) { Debug.Assert(context is not null); if (IsUsingGrainDirectory) { context.TryAddValue(GrainAddressMigrationContextKey, Address); } Activity? dehydrateSpan = null; try { // Get the parent activity context from the dehydration context holder (captured when migration was initiated) var parentContext = DehydrationContext?.MigrationActivityContext; var grainMigrationParticipant = GrainInstance as IGrainMigrationParticipant; if (grainMigrationParticipant is not null) { // Start a span for dehydration, parented to the migration request that triggered it dehydrateSpan = parentContext.HasValue ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivationDehydrate, ActivityKind.Internal, parentContext.Value) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivationDehydrate, ActivityKind.Internal); if (dehydrateSpan is { IsAllDataRequested: true }) { dehydrateSpan.SetTag(ActivityTagKeys.GrainId, GrainId.ToString()); dehydrateSpan.SetTag(ActivityTagKeys.GrainType, _shared.GrainTypeName); dehydrateSpan.SetTag(ActivityTagKeys.SiloId, _shared.Runtime.SiloAddress.ToString()); dehydrateSpan.SetTag(ActivityTagKeys.ActivationId, ActivationId.ToString()); if (ForwardingAddress is { } fwd) { dehydrateSpan.SetTag(ActivityTagKeys.MigrationTargetSilo, fwd.ToString()); } } } // Note that these calls are in reverse order from Rehydrate, not for any particular reason other than symmetry. grainMigrationParticipant?.OnDehydrate(context); if (_lifecycle is { } lifecycle) { foreach (var participant in lifecycle.GetMigrationParticipants()) { participant.OnDehydrate(context); } } } catch (Exception exception) { LogErrorDehydratingActivation(_shared.Logger, exception); SetActivityError(dehydrateSpan, exception, ActivityErrorEvents.DehydrateError); } finally { dehydrateSpan?.Dispose(); } } LogDehydratedActivation(_shared.Logger); } /// /// Handle an incoming message and queue/invoke appropriate handler /// /// private void InvokeIncomingRequest(Message message) { MessagingProcessingInstruments.OnDispatcherMessageProcessedOk(message); _shared.InternalRuntime.MessagingTrace.OnScheduleMessage(message); try { var task = _shared.InternalRuntime.RuntimeClient.Invoke(this, message); // Note: This runs for all outcomes - both Success or Fault if (task.IsCompleted) { OnCompletedRequest(message); } else { _ = OnCompleteAsync(this, message, task); } } catch { OnCompletedRequest(message); } static async ValueTask OnCompleteAsync(ActivationData activation, Message message, Task task) { try { await task; } catch { } finally { activation.OnCompletedRequest(message); } } } /// /// Invoked when an activation has finished a transaction and may be ready for additional transactions /// /// The message that has just completed processing. private void OnCompletedRequest(Message message) { lock (this) { _runningRequests.Remove(message); // If the message is meant to keep the activation active, reset the idle timer and ensure the activation // is in the activation working set. if (message.IsKeepAlive) { _idleDuration = CoarseStopwatch.StartNew(); if (!_isInWorkingSet) { _isInWorkingSet = true; _shared.InternalRuntime.ActivationWorkingSet.OnActive(this); } } // The below logic only works for non-reentrant activations if (_blockingRequest is null || message.Equals(_blockingRequest)) { _blockingRequest = null; _busyDuration = default; } } // Signal the message pump to see if there is another request which can be processed now that this one has completed _workSignal.Signal(); } public void ReceiveMessage(object message) => ReceiveMessage((Message)message); public void ReceiveMessage(Message message) { _shared.InternalRuntime.MessagingTrace.OnDispatcherReceiveMessage(message); // Don't process messages that have already timed out if (message.IsExpired) { MessagingProcessingInstruments.OnDispatcherMessageProcessedError(message); _shared.InternalRuntime.MessagingTrace.OnDropExpiredMessage(message, MessagingInstruments.Phase.Dispatch); return; } if (message.Direction == Message.Directions.Response) { ReceiveResponse(message); } else // Request or OneWay { ReceiveRequest(message); } } private void ReceiveResponse(Message message) { lock (this) { if (State == ActivationState.Invalid) { _shared.InternalRuntime.MessagingTrace.OnDispatcherReceiveInvalidActivation(message, State); // Always process responses _shared.InternalRuntime.RuntimeClient.ReceiveResponse(message); return; } MessagingProcessingInstruments.OnDispatcherMessageProcessedOk(message); _shared.InternalRuntime.RuntimeClient.ReceiveResponse(message); } } private void ReceiveRequest(Message message) { var overloadException = CheckOverloaded(); if (overloadException != null && !message.IsLocalOnly) { MessagingProcessingInstruments.OnDispatcherMessageProcessedError(message); _shared.InternalRuntime.MessageCenter.RejectMessage(message, Message.RejectionTypes.Overloaded, overloadException, "Target activation is overloaded " + this); return; } lock (this) { _waitingRequests.Add((message, CoarseStopwatch.StartNew())); } _workSignal.Signal(); } /// /// Rejects all messages enqueued for the provided activation. /// private void RejectAllQueuedMessages() { lock (this) { List msgs = DequeueAllWaitingRequests(); if (msgs == null || msgs.Count <= 0) return; LogRejectAllQueuedMessages(_shared.Logger, msgs.Count, this); _shared.InternalRuntime.GrainLocator.InvalidateCache(Address); _shared.InternalRuntime.MessageCenter.ProcessRequestsToInvalidActivation( msgs, Address, forwardingAddress: ForwardingAddress, failedOperation: DeactivationReason.Description, exc: DeactivationException, rejectMessages: true); } } private void RerouteAllQueuedMessages() { lock (this) { List msgs = DequeueAllWaitingRequests(); if (msgs is not { Count: > 0 }) { return; } // If deactivation was caused by a transient failure, allow messages to be forwarded. if (DeactivationReason.ReasonCode.IsTransientError()) { foreach (var msg in msgs) { msg.ForwardCount = Math.Max(msg.ForwardCount - 1, 0); } } if (_shared.Logger.IsEnabled(LogLevel.Debug)) { if (ForwardingAddress is { } address) { LogReroutingMessages(_shared.Logger, msgs.Count, this, address); } else { LogReroutingMessagesNoForwarding(_shared.Logger, msgs.Count, this); } } _shared.InternalRuntime.GrainLocator.InvalidateCache(Address); _shared.InternalRuntime.MessageCenter.ProcessRequestsToInvalidActivation(msgs, Address, ForwardingAddress, DeactivationReason.Description, DeactivationException); } } #region Activation public void Rehydrate(IRehydrationContext context) { ScheduleOperation(new Command.Rehydrate(context)); } public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(_shared.InternalRuntime.CollectionOptions.Value.ActivationTimeout); ScheduleOperation(new Command.Activate(requestContext, cts)); } private async Task ActivateAsync(Dictionary? requestContextData, CancellationToken cancellationToken) { if (State != ActivationState.Creating) { LogIgnoringActivateAttempt(_shared.Logger, this, State); return; } _activationActivity?.AddEvent(new ActivityEvent("activation-start")); try { if (IsUsingGrainDirectory) { bool success; Exception? registrationException; // Start directory registration activity as a child of the activation activity using (var registerSpan = _activationActivity is not null ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.RegisterDirectoryEntry, ActivityKind.Internal, _activationActivity.Context) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.RegisterDirectoryEntry, ActivityKind.Internal)) { registerSpan?.SetTag(ActivityTagKeys.GrainId, GrainId.ToString()); registerSpan?.SetTag(ActivityTagKeys.SiloId, _shared.Runtime.SiloAddress.ToString()); registerSpan?.SetTag(ActivityTagKeys.ActivationId, ActivationId.ToString()); registerSpan?.SetTag(ActivityTagKeys.DirectoryPreviousRegistrationPresent, PreviousRegistration is not null); var previousRegistration = PreviousRegistration; try { while (true) { LogRegisteringGrain(_shared.Logger, this, previousRegistration); var result = await _shared.InternalRuntime.GrainLocator .Register(Address, previousRegistration).WaitAsync(cancellationToken); if (Address.Matches(result)) { Address = result; success = true; _activationActivity?.AddEvent(new ActivityEvent("directory-register-success")); registerSpan?.AddEvent(new ActivityEvent("success")); registerSpan?.SetTag(ActivityTagKeys.DirectoryRegisteredAddress, result.ToFullString()); } else if (result?.SiloAddress is { } registeredSilo && registeredSilo.Equals(Address.SiloAddress)) { previousRegistration = result; LogAttemptToRegisterWithPreviousActivation(_shared.Logger, GrainId, result); _activationActivity?.AddEvent(new ActivityEvent("directory-register-retry-previous")); registerSpan?.AddEvent(new ActivityEvent("retry-previous")); continue; } else { ForwardingAddress = result?.SiloAddress; if (ForwardingAddress is { } address) { DeactivationReason = new(DeactivationReasonCode.DuplicateActivation, $"This grain is active on another host ({address})."); } success = false; CatalogInstruments.ActivationConcurrentRegistrationAttempts.Add(1); LogDuplicateActivation( _shared.Logger, Address, ForwardingAddress, GrainInstance?.GetType(), new(Address), WaitingCount); _activationActivity?.AddEvent(new ActivityEvent("duplicate-activation")); registerSpan?.AddEvent(new ActivityEvent("duplicate")); if (ForwardingAddress is { } fwd) { registerSpan?.SetTag(ActivityTagKeys.DirectoryForwardingAddress, fwd.ToString()); } } break; } registrationException = null; } catch (Exception exception) { registrationException = exception; if (!cancellationToken.IsCancellationRequested) { LogFailedToRegisterGrain(_shared.Logger, registrationException, this); } success = false; _activationActivity?.AddEvent(new ActivityEvent("directory-register-failed")); SetActivityError(registerSpan, exception, ActivityErrorEvents.DirectoryRegisterFailed); } } if (!success) { Deactivate(new(DeactivationReasonCode.DirectoryFailure, registrationException, "Failed to register activation in grain directory.")); // Activation failed. if (registrationException is not null) { SetActivityError(_activationActivity, registrationException, ActivityErrorEvents.ActivationCancelled); } else { SetActivityError(_activationActivity, ActivityErrorEvents.ActivationCancelled); } return; } } lock (this) { SetState(ActivationState.Activating); } _activationActivity?.AddEvent(new ActivityEvent("state-activating")); LogActivatingGrain(_shared.Logger, this); try { RequestContextExtensions.Import(requestContextData); try { if (_lifecycle is { } lifecycle) { _activationActivity?.AddEvent(new ActivityEvent("lifecycle-start")); await lifecycle.OnStart(cancellationToken).WaitAsync(cancellationToken); _activationActivity?.AddEvent(new ActivityEvent("lifecycle-started")); } } catch (Exception exception) { LogErrorStartingLifecycle(_shared.Logger, exception, this); _activationActivity?.AddEvent(new ActivityEvent("lifecycle-start-failed")); throw; } if (GrainInstance is IGrainBase grainBase) { // Start a span for OnActivateAsync execution using var onActivateSpan = _activationActivity is not null ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.OnActivate, ActivityKind.Internal, _activationActivity.Context) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.OnActivate, ActivityKind.Internal); if (onActivateSpan is { IsAllDataRequested: true }) { onActivateSpan.SetTag(ActivityTagKeys.GrainId, GrainId.ToString()); onActivateSpan.SetTag(ActivityTagKeys.GrainType, _shared.GrainTypeName ?? GrainInstance.GetType().FullName); onActivateSpan.SetTag(ActivityTagKeys.SiloId, _shared.Runtime.SiloAddress.ToString()); onActivateSpan.SetTag(ActivityTagKeys.ActivationId, ActivationId.ToString()); } try { await grainBase.OnActivateAsync(cancellationToken).WaitAsync(cancellationToken); } catch (Exception exception) { if (cancellationToken.IsCancellationRequested && exception is ObjectDisposedException or OperationCanceledException) { CatalogInstruments.ActivationFailedToActivate.Add(1); // This captures the case where user code in OnActivateAsync doesn't use the passed cancellation token // and makes a call that tries to resolve the scoped IServiceProvider or other type that has been disposed because of cancellation, // or a direct OperationCanceledException from cancellation. if (exception is ObjectDisposedException ode) { LogActivationDisposedObjectAccessed(_shared.Logger, ode.ObjectName, this); Deactivate( new(DeactivationReasonCode.RuntimeRequested, ode, $"Disposed object {ode.ObjectName} referenced after cancellation of activation was requested."), CancellationToken.None); } else { Deactivate( new(DeactivationReasonCode.RuntimeRequested, exception, "Activation was cancelled by the runtime."), CancellationToken.None); } SetActivityError(_activationActivity, exception, ActivityErrorEvents.ActivationCancelled); LogActivationCancelled(_shared.Logger, this, cancellationToken.IsCancellationRequested, DeactivationReason.ReasonCode, DeactivationReason.Description, ForwardingAddress); _activationActivity?.Dispose(); _activationActivity = null; return; } LogErrorInGrainMethod(_shared.Logger, exception, nameof(IGrainBase.OnActivateAsync), this); SetActivityError(onActivateSpan, exception, ActivityErrorEvents.OnActivateFailed); throw; } } lock (this) { if (State is ActivationState.Activating) { SetState(ActivationState.Valid); _shared.InternalRuntime.ActivationWorkingSet.OnActivated(this); } } _activationActivity?.AddEvent(new ActivityEvent("state-valid")); _activationActivity?.Dispose(); _activationActivity = null; LogFinishedActivatingGrain(_shared.Logger, this); } catch (Exception exception) { CatalogInstruments.ActivationFailedToActivate.Add(1); var sourceException = (exception as OrleansLifecycleCanceledException)?.InnerException ?? exception; LogErrorActivatingGrain(_shared.Logger, sourceException, this); if (!cancellationToken.IsCancellationRequested) { ScheduleOperation(new Command.Delay(TimeSpan.FromSeconds(5))); } Deactivate(new(DeactivationReasonCode.ActivationFailed, sourceException, "Failed to activate grain."), CancellationToken.None); SetActivityError(_activationActivity, ActivityErrorEvents.ActivationFailed); _activationActivity?.Dispose(); _activationActivity = null; return; } } catch (Exception exception) { LogActivationFailed(_shared.Logger, exception, this); Deactivate(new(DeactivationReasonCode.ApplicationError, exception, "Failed to activate grain."), CancellationToken.None); SetActivityError(_activationActivity, ActivityErrorEvents.ActivationError); _activationActivity?.Dispose(); _activationActivity = null; } finally { _workSignal.Signal(); } } private void SetActivityError(Activity? erroredActivity, string? errorEventName) { if (erroredActivity is { } activity) { activity.SetStatus(ActivityStatusCode.Error, errorEventName); } } private void SetActivityError(Activity? erroredActivity, Exception exception, string? errorEventName) { if (erroredActivity is { } activity) { activity.SetStatus(ActivityStatusCode.Error, errorEventName); activity.SetTag(ActivityTagKeys.ExceptionType, exception.GetType().FullName); activity.SetTag(ActivityTagKeys.ExceptionMessage, exception.Message); } } #endregion #region Deactivation /// /// Completes the deactivation process. /// private async Task FinishDeactivating(Command.Deactivate deactivateCommand, CancellationToken cancellationToken) { using var _ = deactivateCommand.Activity; var migrating = false; var encounteredError = false; try { LogCompletingDeactivation(_shared.Logger, this); // Stop timers from firing. DisposeTimers(); // If the grain was valid when deactivation started, call OnDeactivateAsync. if (deactivateCommand.PreviousState == ActivationState.Valid) { if (GrainInstance is IGrainBase grainBase) { // Start a span for OnActivateAsync execution using var onDeactivateSpan = deactivateCommand.Activity is not null ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.OnDeactivate, ActivityKind.Internal, parentContext:deactivateCommand.Activity.Context) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.OnDeactivate, ActivityKind.Internal); if (onDeactivateSpan is { IsAllDataRequested: true }) { onDeactivateSpan.SetTag(ActivityTagKeys.GrainId, GrainId.ToString()); onDeactivateSpan.SetTag(ActivityTagKeys.GrainType, _shared.GrainTypeName ?? GrainInstance.GetType().FullName); onDeactivateSpan.SetTag(ActivityTagKeys.SiloId, _shared.Runtime.SiloAddress.ToString()); onDeactivateSpan.SetTag(ActivityTagKeys.ActivationId, ActivationId.ToString()); onDeactivateSpan.SetTag(ActivityTagKeys.DeactivationReason, DeactivationReason.ToString()); } try { LogBeforeOnDeactivateAsync(_shared.Logger, this); await grainBase.OnDeactivateAsync(DeactivationReason, cancellationToken).WaitAsync(cancellationToken); LogAfterOnDeactivateAsync(_shared.Logger, this); } catch (Exception exception) { LogErrorInGrainMethod(_shared.Logger, exception, nameof(IGrainBase.OnDeactivateAsync), this); SetActivityError(onDeactivateSpan, exception, ActivityErrorEvents.OnDeactivateFailed); // Swallow the exception and continue with deactivation. encounteredError = true; } } } try { if (_lifecycle is { } lifecycle) { // Stops the lifecycle stages which were previously started. // Stages which were never started are ignored. await lifecycle.OnStop(cancellationToken).WaitAsync(cancellationToken); } } catch (Exception exception) { LogErrorStartingLifecycle(_shared.Logger, exception, this); // Swallow the exception and continue with deactivation. encounteredError = true; } if (!encounteredError && DehydrationContext is { } context && _shared.MigrationManager is { } migrationManager && !cancellationToken.IsCancellationRequested) { migrating = await StartMigrationAsync(context, migrationManager, cancellationToken); } // If the instance is being deactivated due to a directory failure, we should not unregister it. var isDirectoryFailure = DeactivationReason.ReasonCode is DeactivationReasonCode.DirectoryFailure; var isShuttingDown = DeactivationReason.ReasonCode is DeactivationReasonCode.ShuttingDown; if (!migrating && IsUsingGrainDirectory && !cancellationToken.IsCancellationRequested && !isDirectoryFailure && !isShuttingDown) { // Unregister from directory. // If the grain was migrated, the new activation will perform a check-and-set on the registration itself. try { await _shared.InternalRuntime.GrainLocator.Unregister(Address, UnregistrationCause.Force).WaitAsync(cancellationToken); } catch (Exception exception) { if (!cancellationToken.IsCancellationRequested) { LogFailedToUnregisterActivation(_shared.Logger, exception, this); } } } else if (isDirectoryFailure) { // Optimization: forward to the same host to restart activation without needing to invalidate caches. ForwardingAddress ??= Address.SiloAddress; } } catch (Exception ex) { SetActivityError(deactivateCommand.Activity, ex, "Error in FinishDeactivating"); LogErrorDeactivating(_shared.Logger, ex, this); } if (IsStuckDeactivating) { CatalogInstruments.ActivationShutdownViaDeactivateStuckActivation(); } else if (migrating) { CatalogInstruments.ActivationShutdownViaMigration(); } else if (_isInWorkingSet) { CatalogInstruments.ActivationShutdownViaDeactivateOnIdle(); } else { CatalogInstruments.ActivationShutdownViaCollection(); } UnregisterMessageTarget(); try { await DisposeAsync(); } catch (Exception exception) { SetActivityError(deactivateCommand.Activity, exception, "Error in FinishDeactivating"); LogExceptionDisposing(_shared.Logger, exception, this); } // Signal deactivation GetDeactivationCompletionSource().TrySetResult(true); _workSignal.Signal(); async ValueTask StartMigrationAsync(DehydrationContextHolder context, IActivationMigrationManager migrationManager, CancellationToken cancellationToken) { try { if (ForwardingAddress is null) { var selectedAddress = await PlaceMigratingGrainAsync(context.RequestContext, cancellationToken); if (selectedAddress is null) { return false; } ForwardingAddress = selectedAddress; } // Populate the dehydration context. if (context.RequestContext is { } requestContext) { RequestContextExtensions.Import(requestContext); } OnDehydrate(context.MigrationContext); // Send the dehydration context to the target host. await migrationManager.MigrateAsync(ForwardingAddress, GrainId, context.MigrationContext).AsTask().WaitAsync(cancellationToken); _shared.InternalRuntime.GrainLocator.UpdateCache(GrainId, ForwardingAddress); return true; } catch (Exception exception) { LogFailedToMigrateActivation(_shared.Logger, exception, this); return false; } } } private TaskCompletionSource GetDeactivationCompletionSource() { lock (this) { _extras ??= new(); return _extras.DeactivationTask ??= new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } } ValueTask IGrainManagementExtension.DeactivateOnIdle() { Deactivate(new(DeactivationReasonCode.ApplicationRequested, $"{nameof(IGrainManagementExtension.DeactivateOnIdle)} was called."), CancellationToken.None); return default; } async ValueTask IGrainManagementExtension.MigrateOnIdle() { var requestContextData = RequestContext.CallContextData?.Value.Values; var selectedAddress = await PlaceMigratingGrainAsync(requestContextData, CancellationToken.None); if (selectedAddress is null) { return; } // Only migrate if a different silo was selected. ForwardingAddress = selectedAddress; LogDebugMigrating(_shared.Logger, GrainId, ForwardingAddress); Migrate(requestContextData, cancellationToken: CancellationToken.None); } private async ValueTask PlaceMigratingGrainAsync(Dictionary? requestContextData, CancellationToken cancellationToken) { try { var placementService = _shared.Runtime.ServiceProvider.GetRequiredService(); var selectedAddress = await placementService.PlaceGrainAsync(GrainId, requestContextData, PlacementStrategy); if (selectedAddress is null) { // No appropriate silo was selected for this grain. LogDebugPlacementStrategyFailedToSelectDestination(_shared.Logger, PlacementStrategy, GrainId); return null; } else if (selectedAddress.Equals(_shared.Runtime.SiloAddress)) { // This could be because this is the only (compatible) silo for the grain or because the placement director chose this // silo for some other reason. LogDebugPlacementStrategySelectedCurrentSilo(_shared.Logger, PlacementStrategy, GrainId); return null; } return selectedAddress; } catch (Exception exception) { LogErrorSelectingMigrationDestination(_shared.Logger, exception, GrainId); return null; } } private void UnregisterMessageTarget() { _shared.InternalRuntime.Catalog.UnregisterMessageTarget(this); } void ICallChainReentrantGrainContext.OnEnterReentrantSection(Guid reentrancyId) { var tracker = GetComponent(); if (tracker is null) { tracker = new ReentrantRequestTracker(); SetComponent(tracker); } tracker.EnterReentrantSection(reentrancyId); } void ICallChainReentrantGrainContext.OnExitReentrantSection(Guid reentrancyId) { var tracker = GetComponent(); if (tracker is null) { throw new InvalidOperationException("Attempted to exit reentrant section without entering it."); } tracker.LeaveReentrantSection(reentrancyId); } private bool IsReentrantSection(Guid reentrancyId) { if (reentrancyId == Guid.Empty) { return false; } var tracker = GetComponent(); if (tracker is null) { return false; } return tracker.IsReentrantSectionActive(reentrancyId); } ValueTask IGrainCallCancellationExtension.CancelRequestAsync(GrainId senderGrainId, CorrelationId messageId) => this.RunOrQueueTask(static state => state.self.CancelRequestAsyncCore(state.senderGrainId, state.messageId), (self: this, senderGrainId, messageId)); private ValueTask CancelRequestAsyncCore(GrainId senderGrainId, CorrelationId messageId) { if (!TryCancelRequest()) { // The message being canceled may not have arrived yet, so retry a few times. return RetryCancellationAfterDelay(); } return ValueTask.CompletedTask; async ValueTask RetryCancellationAfterDelay() { var attemptsRemaining = 3; do { await Task.Delay(1_000); } while (!TryCancelRequest() && --attemptsRemaining > 0); } bool TryCancelRequest() { Message? message = null; var wasWaiting = false; lock (this) { // Check the running requests. foreach (var candidate in _runningRequests.Keys) { if (candidate.Id == messageId && candidate.SendingGrain == senderGrainId) { message = candidate; break; } } if (message is null) { // Check the waiting requests. for (var i = 0; i < _waitingRequests.Count; i++) { var candidate = _waitingRequests[i].Message; if (candidate.Id == messageId && candidate.SendingGrain == senderGrainId) { message = candidate; _waitingRequests.RemoveAt(i); wasWaiting = true; break; } } } } var didCancel = false; if (message is not null && message.BodyObject is IInvokable request) { if (wasWaiting) { // If the request was waiting, then we necessarily did manage to cancel it, so send the response now. _shared.InternalRuntime.RuntimeClient.SendResponse(message, Response.FromException(new OperationCanceledException())); didCancel = true; } else { didCancel = TryCancelInvokable(request) || !request.IsCancellable; } } return didCancel; } bool TryCancelInvokable(IInvokable request) { try { return request.TryCancel(); } catch (Exception exception) { LogErrorCancellationCallbackFailed(Shared.Logger, exception); return true; } } } [LoggerMessage( Level = LogLevel.Warning, Message = "One or more cancellation callbacks failed." )] private static partial void LogErrorCancellationCallbackFailed(ILogger logger, Exception exception); #endregion /// /// Additional properties which are not needed for the majority of an activation's lifecycle. /// private class ActivationDataExtra : Dictionary { private const int IsStuckProcessingMessageFlag = 1 << 0; private const int IsStuckDeactivatingFlag = 1 << 1; private const int IsDisposingFlag = 1 << 2; private byte _flags; public HashSet? Timers { get => GetValueOrDefault>(nameof(Timers)); set => SetOrRemoveValue(nameof(Timers), value); } /// /// During rehydration, this may contain the address for the previous (recently dehydrated) activation of this grain. /// public GrainAddress? PreviousRegistration { get => GetValueOrDefault(nameof(PreviousRegistration)); set => SetOrRemoveValue(nameof(PreviousRegistration), value); } /// /// If State == Invalid, this may contain a forwarding address for incoming messages /// public SiloAddress? ForwardingAddress { get => GetValueOrDefault(nameof(ForwardingAddress)); set => SetOrRemoveValue(nameof(ForwardingAddress), value); } /// /// A which completes when a grain has deactivated. /// public TaskCompletionSource? DeactivationTask { get => GetDeactivationInfoOrDefault()?.DeactivationTask; set => EnsureDeactivationInfo().DeactivationTask = value; } public DateTime? DeactivationStartTime { get => GetDeactivationInfoOrDefault()?.DeactivationStartTime; set => EnsureDeactivationInfo().DeactivationStartTime = value; } public DeactivationReason DeactivationReason { get => GetDeactivationInfoOrDefault()?.DeactivationReason ?? default; set => EnsureDeactivationInfo().DeactivationReason = value; } /// /// When migrating to another location, this contains the information to preserve across activations. /// public DehydrationContextHolder? DehydrationContext { get => GetValueOrDefault(nameof(DehydrationContext)); set => SetOrRemoveValue(nameof(DehydrationContext), value); } private DeactivationInfo? GetDeactivationInfoOrDefault() => GetValueOrDefault(nameof(DeactivationInfo)); private DeactivationInfo EnsureDeactivationInfo() { ref var info = ref CollectionsMarshal.GetValueRefOrAddDefault(this, nameof(DeactivationInfo), out _); info ??= new DeactivationInfo(); return (DeactivationInfo)info; } public bool IsStuckProcessingMessage { get => GetFlag(IsStuckProcessingMessageFlag); set => SetFlag(IsStuckProcessingMessageFlag, value); } public bool IsStuckDeactivating { get => GetFlag(IsStuckDeactivatingFlag); set => SetFlag(IsStuckDeactivatingFlag, value); } public bool IsDisposing { get => GetFlag(IsDisposingFlag); set => SetFlag(IsDisposingFlag, value); } private void SetFlag(int flag, bool value) { if (value) { _flags |= (byte)flag; } else { _flags &= (byte)~flag; } } private bool GetFlag(int flag) => (_flags & flag) != 0; private T? GetValueOrDefault(object key) { TryGetValue(key, out var result); return (T?)result; } private void SetOrRemoveValue(object key, object? value) { if (value is null) { Remove(key); } else { base[key] = value; } } private sealed class DeactivationInfo { public DateTime? DeactivationStartTime; public DeactivationReason DeactivationReason; public TaskCompletionSource? DeactivationTask; } } private abstract class Command(CancellationTokenSource cts) : IDisposable { private bool _disposed; private readonly CancellationTokenSource _cts = cts; public CancellationToken CancellationToken => _cts.Token; public virtual void Cancel() { lock (this) { if (_disposed) return; _cts.Cancel(); } } public virtual void Dispose() { try { lock (this) { _disposed = true; _cts.Dispose(); } } catch { // Ignore. } GC.SuppressFinalize(this); } public sealed class Deactivate(CancellationTokenSource cts, ActivationState previousState, Activity? activity) : Command(cts) { public ActivationState PreviousState { get; } = previousState; public Activity? Activity { get; } = activity; } public sealed class Activate(Dictionary? requestContext, CancellationTokenSource cts) : Command(cts) { public Dictionary? RequestContext { get; } = requestContext; } public sealed class Rehydrate(IRehydrationContext context) : Command(new()) { public readonly IRehydrationContext Context = context; public override void Dispose() { base.Dispose(); (Context as IDisposable)?.Dispose(); } } public sealed class Delay(TimeSpan duration) : Command(new()) { public TimeSpan Duration { get; } = duration; } } internal class ReentrantRequestTracker : Dictionary { public void EnterReentrantSection(Guid reentrancyId) { Debug.Assert(reentrancyId != Guid.Empty); ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(this, reentrancyId, out _); ++count; } public void LeaveReentrantSection(Guid reentrancyId) { Debug.Assert(reentrancyId != Guid.Empty); ref var count = ref CollectionsMarshal.GetValueRefOrNullRef(this, reentrancyId); if (Unsafe.IsNullRef(ref count)) { return; } if (--count <= 0) { Remove(reentrancyId); } } public bool IsReentrantSectionActive(Guid reentrancyId) { Debug.Assert(reentrancyId != Guid.Empty); return TryGetValue(reentrancyId, out var count) && count > 0; } } private class DehydrationContextHolder(SerializerSessionPool sessionPool, Dictionary? requestContext) { public readonly MigrationContext MigrationContext = new(sessionPool); public readonly Dictionary? RequestContext = requestContext; /// /// The activity context from the grain call that initiated the migration. /// This is used to parent the dehydrate span to the migration request trace. /// public ActivityContext? MigrationActivityContext { get; set; } = Activity.Current?.Context; } [LoggerMessage( EventId = (int)ErrorCode.Catalog_Reject_ActivationTooManyRequests, Level = LogLevel.Warning, Message = "Overload - {Count} enqueued requests for activation {Activation}, exceeding hard limit rejection threshold of {HardLimit}" )] private static partial void LogRejectActivationTooManyRequests(ILogger logger, int count, ActivationData activation, int hardLimit); [LoggerMessage( EventId = (int)ErrorCode.Catalog_Warn_ActivationTooManyRequests, Level = LogLevel.Warning, Message = "Hot - {Count} enqueued requests for activation {Activation}, exceeding soft limit warning threshold of {SoftLimit}" )] private static partial void LogWarnActivationTooManyRequests(ILogger logger, int count, ActivationData activation, int softLimit); [LoggerMessage( Level = LogLevel.Warning, Message = "Error while cancelling on-going operation '{Operation}'." )] private static partial void LogErrorCancellingOperation(ILogger logger, Exception exception, object operation); [LoggerMessage( Level = LogLevel.Debug, Message = "Migrating {GrainId} to {SiloAddress}" )] private static partial void LogDebugMigrating(ILogger logger, GrainId grainId, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Error while selecting a migration destination for {GrainId}" )] private static partial void LogErrorSelectingMigrationDestination(ILogger logger, Exception exception, GrainId grainId); [LoggerMessage( Level = LogLevel.Debug, Message = "Placement strategy {PlacementStrategy} failed to select a destination for migration of {GrainId}" )] private static partial void LogDebugPlacementStrategyFailedToSelectDestination(ILogger logger, PlacementStrategy placementStrategy, GrainId grainId); [LoggerMessage( Level = LogLevel.Debug, Message = "Placement strategy {PlacementStrategy} selected the current silo as the destination for migration of {GrainId}" )] private static partial void LogDebugPlacementStrategySelectedCurrentSilo(ILogger logger, PlacementStrategy placementStrategy, GrainId grainId); [LoggerMessage( Level = LogLevel.Error, Message = "Error invoking MayInterleave predicate on grain {Grain} for message {Message}" )] private static partial void LogErrorInvokingMayInterleavePredicate(ILogger logger, Exception exception, ActivationData grain, Message message); [LoggerMessage( Level = LogLevel.Error, Message = "Error in ProcessOperationsAsync for grain activation '{Activation}'." )] private static partial void LogErrorInProcessOperationsAsync(ILogger logger, Exception exception, ActivationData activation); [LoggerMessage( Level = LogLevel.Debug, Message = "Rehydrating grain '{GrainContext}' from previous activation." )] private static partial void LogRehydratingGrain(ILogger logger, ActivationData grainContext); [LoggerMessage( Level = LogLevel.Warning, Message = "Ignoring attempt to rehydrate grain '{GrainContext}' in the '{State}' state." )] private static partial void LogIgnoringRehydrateAttempt(ILogger logger, ActivationData grainContext, ActivationState state); [LoggerMessage( Level = LogLevel.Debug, Message = "Previous activation address was {PreviousRegistration}" )] private static partial void LogPreviousActivationAddress(ILogger logger, GrainAddress previousRegistration); [LoggerMessage( Level = LogLevel.Debug, Message = "Rehydrated grain from previous activation" )] private static partial void LogRehydratedGrain(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error while rehydrating activation" )] private static partial void LogErrorRehydratingActivation(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Dehydrating grain activation" )] private static partial void LogDehydratingActivation(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Dehydrated grain activation" )] private static partial void LogDehydratedActivation(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error while dehydrating activation" )] private static partial void LogErrorDehydratingActivation(ILogger logger, Exception exception); [LoggerMessage( EventId = (int)ErrorCode.Catalog_RerouteAllQueuedMessages, Level = LogLevel.Debug, Message = "Rejecting {Count} messages from invalid activation {Activation}." )] private static partial void LogRejectAllQueuedMessages(ILogger logger, int count, ActivationData activation); [LoggerMessage( Level = LogLevel.Debug, Message = "Registering grain '{Grain}' in activation directory. Previous known registration is '{PreviousRegistration}'.")] private static partial void LogRegisteringGrain(ILogger logger, ActivationData grain, GrainAddress? previousRegistration); [LoggerMessage( Level = LogLevel.Debug, Message = "The grain directory has an existing entry pointing to a different activation of this grain, '{GrainId}', on this silo: '{PreviousRegistration}'." + " This may indicate that the previous activation was deactivated but the directory was not successfully updated." + " The directory will be updated to point to this activation." )] private static partial void LogAttemptToRegisterWithPreviousActivation(ILogger logger, GrainId grainId, GrainAddress previousRegistration); [LoggerMessage( EventId = (int)ErrorCode.Dispatcher_ExtendedMessageProcessing, Level = LogLevel.Warning, Message = "Current request has been active for {CurrentRequestActiveTime} for grain {Grain}. Currently executing {BlockingRequest}. Trying to enqueue {Message}.")] private static partial void LogWarningDispatcher_ExtendedMessageProcessing( ILogger logger, TimeSpan currentRequestActiveTime, ActivationDataLogValue grain, Message blockingRequest, Message message); private readonly struct ActivationDataLogValue(ActivationData activation, bool includeExtraDetails = false) { public override string ToString() => activation.ToDetailedString(includeExtraDetails); } [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100064, Level = LogLevel.Warning, Message = "Failed to register grain {Grain} in grain directory")] private static partial void LogFailedToRegisterGrain(ILogger logger, Exception exception, ActivationData grain); [LoggerMessage( Level = LogLevel.Debug, Message = "Ignoring activation request for {Grain} because this grain is in the '{State}' state")] private static partial void LogIgnoringActivateAttempt(ILogger logger, ActivationData grain, ActivationState state); [LoggerMessage( EventId = (int)ErrorCode.Catalog_BeforeCallingActivate, Level = LogLevel.Debug, Message = "Activating grain {Grain}")] private static partial void LogActivatingGrain(ILogger logger, ActivationData grain); [LoggerMessage( Level = LogLevel.Error, Message = "Error starting lifecycle for activation '{Activation}'")] private static partial void LogErrorStartingLifecycle(ILogger logger, Exception exception, ActivationData activation); [LoggerMessage( Level = LogLevel.Error, Message = "Error thrown from {MethodName} for activation '{Activation}'")] private static partial void LogErrorInGrainMethod(ILogger logger, Exception exception, string methodName, ActivationData activation); [LoggerMessage( EventId = (int)ErrorCode.Catalog_AfterCallingActivate, Level = LogLevel.Debug, Message = "Finished activating grain {Grain}")] private static partial void LogFinishedActivatingGrain(ILogger logger, ActivationData grain); [LoggerMessage( EventId = (int)ErrorCode.Catalog_ErrorCallingActivate, Level = LogLevel.Error, Message = "Error activating grain {Grain}")] private static partial void LogErrorActivatingGrain(ILogger logger, Exception exception, ActivationData grain); [LoggerMessage( EventId = (int)ErrorCode.Catalog_DisposedObjectAccess, Level = LogLevel.Warning, Message = "Disposed object {ObjectName} accessed in OnActivateAsync for grain {Grain}. Ensure the cancellationToken is passed to all async methods or they have .WaitAsync(cancellationToken) called on them.")] private static partial void LogActivationDisposedObjectAccessed(ILogger logger, string objectName, ActivationData grain); [LoggerMessage( EventId = (int)ErrorCode.Catalog_CancelledActivate, Level = LogLevel.Information, Message = "Activation was cancelled for {Grain}. CancellationRequested={CancellationRequested}, DeactivationReasonCode={DeactivationReasonCode}, DeactivationReason={DeactivationReason}, ForwardingAddress={ForwardingAddress}" )] private static partial void LogActivationCancelled(ILogger logger, ActivationData grain, bool cancellationRequested, DeactivationReasonCode deactivationReasonCode, string? deactivationReason, SiloAddress? forwardingAddress); [LoggerMessage( Level = LogLevel.Trace, Message = "Completing deactivation of '{Activation}'")] private static partial void LogCompletingDeactivation(ILogger logger, ActivationData activation); [LoggerMessage( EventId = (int)ErrorCode.Catalog_BeforeCallingDeactivate, Level = LogLevel.Debug, Message = "About to call OnDeactivateAsync for '{Activation}'")] private static partial void LogBeforeOnDeactivateAsync(ILogger logger, ActivationData activation); [LoggerMessage( EventId = (int)ErrorCode.Catalog_AfterCallingDeactivate, Level = LogLevel.Debug, Message = "Returned from calling '{Activation}' OnDeactivateAsync method")] private static partial void LogAfterOnDeactivateAsync(ILogger logger, ActivationData activation); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to unregister activation '{Activation}' from directory")] private static partial void LogFailedToUnregisterActivation(ILogger logger, Exception exception, ActivationData activation); [LoggerMessage( EventId = (int)ErrorCode.Catalog_DeactivateActivation_Exception, Level = LogLevel.Warning, Message = "Error deactivating '{Activation}'")] private static partial void LogErrorDeactivating(ILogger logger, Exception exception, ActivationData activation); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception disposing activation '{Activation}'")] private static partial void LogExceptionDisposing(ILogger logger, Exception exception, ActivationData activation); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to migrate activation '{Activation}'")] private static partial void LogFailedToMigrateActivation(ILogger logger, Exception exception, ActivationData activation); private readonly struct FullAddressLogRecord(GrainAddress address) { public override string ToString() => address.ToFullString(); } [LoggerMessage( EventId = (int)ErrorCode.Catalog_DuplicateActivation, Level = LogLevel.Debug, Message = "Tried to create a duplicate activation {Address}, but we'll use {ForwardingAddress} instead. GrainInstance type is {GrainInstanceType}. Full activation address is {FullAddress}. We have {WaitingCount} messages to forward")] private static partial void LogDuplicateActivation( ILogger logger, GrainAddress address, SiloAddress? forwardingAddress, Type? grainInstanceType, FullAddressLogRecord fullAddress, int waitingCount); [LoggerMessage( EventId = (int)ErrorCode.Catalog_RerouteAllQueuedMessages, Level = LogLevel.Debug, Message = "Rerouting {NumMessages} messages from invalid grain activation {Grain} to {ForwardingAddress}")] private static partial void LogReroutingMessages(ILogger logger, int numMessages, ActivationData grain, SiloAddress forwardingAddress); [LoggerMessage( EventId = (int)ErrorCode.Catalog_RerouteAllQueuedMessages, Level = LogLevel.Debug, Message = "Rerouting {NumMessages} messages from invalid grain activation {Grain}")] private static partial void LogReroutingMessagesNoForwarding(ILogger logger, int numMessages, ActivationData grain); [LoggerMessage( Level = LogLevel.Error, Message = "Activation of grain {Grain} failed")] private static partial void LogActivationFailed(ILogger logger, Exception exception, ActivationData grain); } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationDirectory.cs ================================================ #nullable enable using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime; internal sealed class ActivationDirectory : IEnumerable>, IAsyncDisposable, IDisposable { private int _activationsCount; private readonly ConcurrentDictionary _activations = new(); public ActivationDirectory() { CatalogInstruments.RegisterActivationCountObserve(() => Count); } public int Count => _activationsCount; public IGrainContext? FindTarget(GrainId key) { _activations.TryGetValue(key, out var result); return result; } public void RecordNewTarget(IGrainContext target) { if (_activations.TryAdd(target.GrainId, target)) { Interlocked.Increment(ref _activationsCount); } } public bool RemoveTarget(IGrainContext target) { if (_activations.TryRemove(KeyValuePair.Create(target.GrainId, target))) { Interlocked.Decrement(ref _activationsCount); return true; } return false; } public IEnumerator> GetEnumerator() => _activations.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); async ValueTask IAsyncDisposable.DisposeAsync() { var tasks = new List(); foreach (var (_, value) in _activations) { try { if (value is IAsyncDisposable asyncDisposable) { tasks.Add(asyncDisposable.DisposeAsync().AsTask()); } else if (value is IDisposable disposable) { disposable.Dispose(); } } catch { // Ignore exceptions during disposal. } } await Task.WhenAll(tasks).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } void IDisposable.Dispose() { foreach (var (_, value) in _activations) { try { if (value is IDisposable disposable) { disposable.Dispose(); } } catch { // Ignore exceptions during disposal. } } } } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationMigrationManager.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Threading.Tasks.Sources; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; using Orleans.Diagnostics; using Orleans.Internal; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime; /// /// Remote interface for migrating grain activations to a silo. /// internal interface IActivationMigrationManagerSystemTarget : ISystemTarget { /// /// Accepts migrating grains on a best-effort basis. /// ValueTask AcceptMigratingGrains([Immutable] List migratingGrains); } [GenerateSerializer, Immutable] internal struct GrainMigrationPackage { [Id(0)] public GrainId GrainId { get; set; } [Id(1)] public MigrationContext MigrationContext { get; set; } } /// /// Functionality for migrating an activation to a new location. /// internal interface IActivationMigrationManager { /// /// Attempts to migrate a grain to the specified target. /// /// The migration target. /// The grain being migrated. /// Information about the grain being migrated, which will be consumed by the new activation. ValueTask MigrateAsync(SiloAddress target, GrainId grainId, MigrationContext migrationContext); } /// /// Migrates grain activations to target hosts and handles migration requests from other hosts. /// internal sealed partial class ActivationMigrationManager : SystemTarget, IActivationMigrationManagerSystemTarget, IActivationMigrationManager, ILifecycleParticipant { private const int MaxBatchSize = 1_000; private readonly ConcurrentDictionary WorkItemChannel)> _workers = new(); private readonly ObjectPool _workItemPool = ObjectPool.Create(new MigrationWorkItem.ObjectPoolPolicy()); private readonly CancellationTokenSource _shuttingDownCts = new(); private readonly ILogger _logger; private readonly IInternalGrainFactory _grainFactory; private readonly Catalog _catalog; private readonly IClusterMembershipService _clusterMembershipService; #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif #pragma warning disable IDE0052 // Remove unread private members. Justification: this field is only for diagnostic purposes. private readonly Task? _membershipUpdatesTask; #pragma warning restore IDE0052 // Remove unread private members public ActivationMigrationManager( ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory, IInternalGrainFactory grainFactory, Catalog catalog, SystemTargetShared shared, IClusterMembershipService clusterMembershipService) : base(Constants.ActivationMigratorType, shared) { _grainFactory = grainFactory; _logger = loggerFactory.CreateLogger(); _catalog = catalog; _clusterMembershipService = clusterMembershipService; shared.ActivationDirectory.RecordNewTarget(this); { using var _ = new ExecutionContextSuppressor(); _membershipUpdatesTask = Task.Factory.StartNew( state => ((ActivationMigrationManager)state!).ProcessMembershipUpdates(), this, CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); _membershipUpdatesTask.Ignore(); } } public async ValueTask AcceptMigratingGrains(List migratingGrains) { var activations = new List(); var currentActivity = Activity.Current; foreach (var package in migratingGrains) { // If the activation does not exist, create it and provide it with the migration context while doing so. // If the activation already exists or cannot be created, it is too late to perform migration, so ignore the request. var context = _catalog.GetOrCreateActivation(package.GrainId, requestContextData: null, package.MigrationContext); if (context is ActivationData activation) { activations.Add(activation); } Activity.Current = currentActivity; } // Wait for all activations to become active or reach a terminal state. // This ensures that the activation has completed registration in the directory (or is abandoned) before we return. // Otherwise, there could be a race where the original silo removes the activation from its catalog, receives a new message for that activation, // and re-activates it before the new activation on this silo has been registered with the directory. using var waitActivity = ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.WaitMigration); while (true) { var allActiveOrTerminal = true; foreach (var activation in activations) { lock (activation) { if (activation.State is not (ActivationState.Valid or ActivationState.Invalid)) { allActiveOrTerminal = false; break; } } } if (allActiveOrTerminal) { break; } // Wait a short amount of time and poll the activations again. await Task.Delay(5); } } public ValueTask MigrateAsync(SiloAddress targetSilo, GrainId grainId, MigrationContext migrationContext) { var workItem = _workItemPool.Get(); var migrationPackage = new GrainMigrationPackage { GrainId = grainId, MigrationContext = migrationContext }; workItem.Initialize(migrationPackage); var workItemWriter = GetOrCreateWorker(targetSilo); if (!workItemWriter.TryWrite(workItem)) { workItem.SetException(new SiloUnavailableException($"Silo {targetSilo} is no longer active")); } return workItem.AsValueTask(); } private async Task ProcessMembershipUpdates() { await Task.Yield(); try { LogDebugMonitoringUpdates(); var previousSnapshot = _clusterMembershipService.CurrentSnapshot; await foreach (var snapshot in _clusterMembershipService.MembershipUpdates) { try { var diff = snapshot.CreateUpdate(previousSnapshot); previousSnapshot = snapshot; foreach (var change in diff.Changes) { if (change.Status.IsTerminating()) { RemoveWorker(change.SiloAddress); } } } catch (Exception exception) { LogErrorProcessingMembershipUpdates(exception); } } } finally { LogDebugNoLongerMonitoring(); } } private async Task PumpMigrationQueue(SiloAddress targetSilo, Channel workItems) { try { var remote = _grainFactory.GetSystemTarget(Constants.ActivationMigratorType, targetSilo); await Task.Yield(); LogDebugStartingWorker(targetSilo); var items = new List(); var batch = new List(); var reader = workItems.Reader; while (await reader.WaitToReadAsync()) { try { // Collect a batch of work items. while (batch.Count < MaxBatchSize && reader.TryRead(out var workItem)) { items.Add(workItem); batch.Add(workItem.Value); } // Attempt to migrate the batch. await remote.AcceptMigratingGrains(batch).AsTask().WaitAsync(_shuttingDownCts.Token); foreach (var item in items) { item.SetCompleted(); } LogDebugMigratedActivations(items.Count, targetSilo); } catch (Exception exception) { if (!_shuttingDownCts.IsCancellationRequested) { LogErrorMigratingActivations(exception, items.Count, targetSilo); } foreach (var item in items) { item.SetException(exception); } // If the silo is terminating, we should stop trying to migrate activations to it. if (_clusterMembershipService.CurrentSnapshot.GetSiloStatus(targetSilo).IsTerminating()) { break; } } finally { items.Clear(); batch.Clear(); } } // Remove ourselves and clean up. RemoveWorker(targetSilo); } finally { LogDebugExitingWorker(targetSilo); } } private ChannelWriter GetOrCreateWorker(SiloAddress targetSilo) { if (!_workers.TryGetValue(targetSilo, out var entry)) { lock (_lock) { if (!_workers.TryGetValue(targetSilo, out entry)) { using var _ = new ExecutionContextSuppressor(); var channel = Channel.CreateUnbounded(); var pumpTask = Task.Factory.StartNew( () => PumpMigrationQueue(targetSilo, channel), CancellationToken.None, TaskCreationOptions.None, WorkItemGroup.TaskScheduler).Unwrap(); pumpTask.Ignore(); entry = (pumpTask, channel); var didAdd = _workers.TryAdd(targetSilo, entry); Debug.Assert(didAdd); } } } return entry.WorkItemChannel.Writer; } private void RemoveWorker(SiloAddress targetSilo) { if (_workers.TryRemove(targetSilo, out var entry)) { LogDebugTargetSilo(targetSilo); entry.WorkItemChannel.Writer.TryComplete(); var exception = new SiloUnavailableException($"Silo {targetSilo} is no longer active"); while (entry.WorkItemChannel.Reader.TryRead(out var item)) { item.SetException(exception); } } } private Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; private async Task StopAsync(CancellationToken cancellationToken) { var workerTasks = new List(); foreach (var (_, value) in _workers) { value.WorkItemChannel.Writer.TryComplete(); workerTasks.Add(value.PumpTask); } try { _shuttingDownCts.Cancel(); } catch (Exception exception) { LogWarningSignalShutdownError(exception); } await Task.WhenAll(workerTasks).WaitAsync(cancellationToken).SuppressThrowing(); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(ActivationMigrationManager), ServiceLifecycleStage.RuntimeGrainServices, ct => this.RunOrQueueTask(() => StartAsync(ct)), ct => this.RunOrQueueTask(() => StopAsync(ct))); } private class MigrationWorkItem : IValueTaskSource { private ManualResetValueTaskSourceCore _core = new() { RunContinuationsAsynchronously = true }; private GrainMigrationPackage _migrationPackage; public void Initialize(GrainMigrationPackage package) => _migrationPackage = package; public void Reset() => _core.Reset(); public GrainMigrationPackage Value => _migrationPackage; public void SetCompleted() => _core.SetResult(0); public void SetException(Exception exception) => _core.SetException(exception); public ValueTask AsValueTask() => new (this, _core.Version); public void GetResult(short token) { try { _core.GetResult(token); } finally { Reset(); } } public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); public sealed class ObjectPoolPolicy : IPooledObjectPolicy { public MigrationWorkItem Create() => new(); public bool Return(MigrationWorkItem obj) { obj.Reset(); return true; } } } // Log value types private readonly struct SiloAddressLogValue { private readonly SiloAddress _silo; public SiloAddressLogValue(SiloAddress silo) => _silo = silo; public override string ToString() => _silo.ToStringWithHashCode(); } // Logger methods at end of class [LoggerMessage( Level = LogLevel.Debug, Message = "Monitoring cluster membership updates")] private partial void LogDebugMonitoringUpdates(); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing cluster membership updates" )] private partial void LogErrorProcessingMembershipUpdates(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "No longer monitoring cluster membership updates")] private partial void LogDebugNoLongerMonitoring(); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting migration worker for target silo {SiloAddress}")] private partial void LogDebugStartingWorker(SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Migrated {Count} activations to target silo {SiloAddress}")] private partial void LogDebugMigratedActivations(int count, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Error while migrating {Count} grain activations to {SiloAddress}")] private partial void LogErrorMigratingActivations(Exception exception, int count, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Exiting migration worker for target silo {SiloAddress}")] private partial void LogDebugExitingWorker(SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Target silo {SiloAddress} is no longer active, so this migration activation worker is terminating" )] private partial void LogDebugTargetSilo(SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Error signaling shutdown" )] private partial void LogWarningSignalShutdownError(Exception exception); } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationState.cs ================================================ namespace Orleans.Runtime { internal enum ActivationState { /// /// Activation is being created /// Creating, /// /// Activation is in the middle of activation process. /// Activating, /// /// Activation was successfully activated and ready to process requests. /// Valid, /// /// Activation is in the middle of deactivation process. /// Deactivating, /// /// Tombstone for an activation which has terminated. /// Invalid } } ================================================ FILE: src/Orleans.Runtime/Catalog/ActivationWorkingSet.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Runtime.Internal; namespace Orleans.Runtime { /// /// Maintains a list of activations which are recently active. /// internal sealed partial class ActivationWorkingSet : IActivationWorkingSet, ILifecycleParticipant { private class MemberState { public bool IsIdle { get; set; } } private readonly ConcurrentDictionary _members = new(); private readonly ILogger _logger; private readonly IAsyncTimer _scanPeriodTimer; private readonly List _observers; private int _activeCount; private Task _runTask; public ActivationWorkingSet( IAsyncTimerFactory asyncTimerFactory, ILogger logger, IEnumerable observers) { _logger = logger; _scanPeriodTimer = asyncTimerFactory.Create(TimeSpan.FromMilliseconds(5_000), nameof(ActivationWorkingSet) + "." + nameof(MonitorWorkingSet)); _observers = observers.ToList(); CatalogInstruments.RegisterActivationWorkingSetObserve(() => Count); } public int Count => _activeCount; public void OnActivated(IActivationWorkingSetMember member) { Debug.Assert(member is not ICollectibleGrainContext collectible || collectible.IsValid); if (_members.TryAdd(member, new MemberState())) { Interlocked.Increment(ref _activeCount); foreach (var observer in _observers) { observer.OnAdded(member); } return; } throw new InvalidOperationException($"Member {member} is already a member of the working set"); } public void OnActive(IActivationWorkingSetMember member) { if (_members.TryGetValue(member, out var state)) { state.IsIdle = false; } else if (_members.TryAdd(member, new())) { Interlocked.Increment(ref _activeCount); } foreach (var observer in _observers) { observer.OnActive(member); } } public void OnEvicted(IActivationWorkingSetMember member) { if (_members.TryRemove(member, out _)) { Interlocked.Decrement(ref _activeCount); foreach (var observer in _observers) { observer.OnEvicted(member); } } } public void OnDeactivating(IActivationWorkingSetMember member) { OnEvicted(member); foreach (var observer in _observers) { observer.OnDeactivating(member); } } public void OnDeactivated(IActivationWorkingSetMember member) { OnEvicted(member); foreach (var observer in _observers) { observer.OnDeactivated(member); } } private async Task MonitorWorkingSet() { while (await _scanPeriodTimer.NextTick()) { foreach (var pair in _members) { try { VisitMember(pair.Key, pair.Value); } catch (Exception exception) { LogExceptionVisitingWorkingSetMember(exception, pair.Key); } } } } private void VisitMember(IActivationWorkingSetMember member, MemberState state) { var wouldRemove = state.IsIdle; if (member.IsCandidateForRemoval(wouldRemove)) { if (wouldRemove) { OnEvicted(member); } else { state.IsIdle = true; foreach (var observer in _observers) { observer.OnIdle(member); } } } else { state.IsIdle = false; foreach (var observer in _observers) { observer.OnActive(member); } } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(ActivationWorkingSet), ServiceLifecycleStage.BecomeActive, ct => { using var _ = new ExecutionContextSuppressor(); _runTask = Task.Run(MonitorWorkingSet); return Task.CompletedTask; }, async ct => { _scanPeriodTimer.Dispose(); if (_runTask is Task task) { await task.WaitAsync(ct).SuppressThrowing(); } }); } [LoggerMessage( Level = LogLevel.Error, Message = "Exception visiting working set member {Member}" )] private partial void LogExceptionVisitingWorkingSetMember(Exception exception, IActivationWorkingSetMember member); } /// /// Manages the set of recently active instances. /// public interface IActivationWorkingSet { /// /// Returns the number of grain activations which were recently active. /// public int Count { get; } /// /// Adds a new member to the working set. /// void OnActivated(IActivationWorkingSetMember member); /// /// Signals that a member is active and should be in the working set. /// void OnActive(IActivationWorkingSetMember member); /// /// Signals that a member has begun to deactivate. /// /// void OnDeactivating(IActivationWorkingSetMember member); /// /// Signals that a members has deactivated. /// void OnDeactivated(IActivationWorkingSetMember member); } /// /// Represents an activation from the perspective of . /// public interface IActivationWorkingSetMember { /// /// Returns if the member is eligible for removal, otherwise. /// /// if the member is eligible for removal, otherwise. /// /// If this method returns and is , the member must be removed from the working set and is eligible to be added again via a call to . /// bool IsCandidateForRemoval(bool wouldRemove); } /// /// An observer. /// public interface IActivationWorkingSetObserver { /// /// Called when an activation is added to the working set. /// void OnAdded(IActivationWorkingSetMember member) { } /// /// Called when an activation becomes active. /// void OnActive(IActivationWorkingSetMember member) { } /// /// Called when an activation becomes idle. /// void OnIdle(IActivationWorkingSetMember member) { } /// /// Called when an activation is removed from the working set. /// void OnEvicted(IActivationWorkingSetMember member) { } /// /// Called when an activation starts deactivating. /// void OnDeactivating(IActivationWorkingSetMember member) { } /// /// Called when an activation is deactivated. /// void OnDeactivated(IActivationWorkingSetMember member) { } } } ================================================ FILE: src/Orleans.Runtime/Catalog/Catalog.cs ================================================ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.GrainDirectory; using Orleans.Runtime.GrainDirectory; using System.Diagnostics; using Orleans.Diagnostics; namespace Orleans.Runtime { internal sealed partial class Catalog : SystemTarget, ICatalog, ILifecycleParticipant { private readonly SiloAddress _siloAddress; private readonly ActivationCollector activationCollector; private readonly GrainDirectoryResolver grainDirectoryResolver; private readonly ActivationDirectory activations; private readonly IServiceProvider serviceProvider; private readonly ILogger logger; private readonly GrainContextActivator grainActivator; private ISiloStatusOracle _siloStatusOracle; // Lock striping is used for activation creation to reduce contention private const int LockCount = 32; // Must be a power of 2 private const int LockMask = LockCount - 1; #if NET9_0_OR_GREATER private readonly Lock[] _locks = new Lock[LockCount]; #else private readonly object[] _locks = new object[LockCount]; #endif public Catalog( ILocalSiloDetails localSiloDetails, GrainDirectoryResolver grainDirectoryResolver, ActivationDirectory activationDirectory, ActivationCollector activationCollector, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, GrainContextActivator grainActivator, SystemTargetShared shared) : base(Constants.CatalogType, shared) { this._siloAddress = localSiloDetails.SiloAddress; this.grainDirectoryResolver = grainDirectoryResolver; this.activations = activationDirectory; this.serviceProvider = serviceProvider; this.grainActivator = grainActivator; this.logger = loggerFactory.CreateLogger(); this.activationCollector = activationCollector; // Initialize lock striping array for (var i = 0; i < LockCount; i++) { _locks[i] = new(); } GC.GetTotalMemory(true); // need to call once w/true to ensure false returns OK value MessagingProcessingInstruments.RegisterActivationDataAllObserve(() => { long counter = 0; foreach (var activation in activations) { if (activation.Value is ActivationData data) { counter += data.GetRequestCount(); } } return counter; }); shared.ActivationDirectory.RecordNewTarget(this); } /// /// Gets the lock for a specific grain ID using consistent hashing. /// /// The grain ID to get the lock for. /// The lock object for the specified grain ID. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if NET9_0_OR_GREATER private Lock GetStripedLock(in GrainId grainId) #else private object GetStripedLock(in GrainId grainId) #endif { var hash = grainId.GetUniformHashCode(); var lockIndex = (int)(hash & LockMask); return _locks[lockIndex]; } /// /// Unregister message target and stop delivering messages to it /// /// public void UnregisterMessageTarget(IGrainContext activation) { if (activations.RemoveTarget(activation)) { LogTraceUnregisteredActivation(activation); // this should be removed once we've refactored the deactivation code path. For now safe to keep. activationCollector.TryCancelCollection(activation as ICollectibleGrainContext); CatalogInstruments.ActivationsDestroyed.Add(1); } } /// /// FOR TESTING PURPOSES ONLY!! /// /// internal int UnregisterGrainForTesting(GrainId grain) { var activation = activations.FindTarget(grain); if (activation is null) return 0; UnregisterMessageTarget(activation); return 1; } /// /// If activation already exists, return it. /// Otherwise, creates a new activation, begins rehydrating it and activating it, then returns it. /// /// /// There is no guarantee about the validity of the activation which is returned. /// Activations are responsible for handling any messages which they receive. /// /// The grain identity. /// Optional request context data. /// Optional rehydration context. /// public IGrainContext GetOrCreateActivation( in GrainId grainId, Dictionary requestContextData, MigrationContext rehydrationContext) { if (TryGetGrainContext(grainId, out var result)) { rehydrationContext?.Dispose(); return result; } else if (grainId.IsSystemTarget()) { rehydrationContext?.Dispose(); return null; } lock (GetStripedLock(grainId)) { if (TryGetGrainContext(grainId, out result)) { rehydrationContext?.Dispose(); return result; } if (_siloStatusOracle.CurrentStatus == SiloStatus.Active) { var address = new GrainAddress { SiloAddress = Silo, GrainId = grainId, ActivationId = ActivationId.NewId(), MembershipVersion = MembershipVersion.MinValue, }; result = this.grainActivator.CreateInstance(address); activations.RecordNewTarget(result); } } // End lock if (result is null) { rehydrationContext?.Dispose(); return UnableToCreateActivation(this, grainId); } // Start activation span with parent context from request if available var parentContext = requestContextData.TryGetActivityContext(); var activationActivity = parentContext.HasValue ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivateGrain, ActivityKind.Internal, parentContext.Value) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.ActivateGrain, ActivityKind.Internal); if (activationActivity is not null) { activationActivity.SetTag(ActivityTagKeys.GrainId, grainId.ToString()); activationActivity.SetTag(ActivityTagKeys.GrainType, grainId.Type.ToString()); activationActivity.SetTag(ActivityTagKeys.SiloId, Silo.ToString()); activationActivity.SetTag(ActivityTagKeys.ActivationCause, rehydrationContext is null ? "new" : "rehydrate"); if (result is ActivationData act) { activationActivity.SetTag(ActivityTagKeys.ActivationId, act.ActivationId.ToString()); act.SetActivationActivity(activationActivity); activationActivity.AddEvent(new ActivityEvent("creating")); } } CatalogInstruments.ActivationsCreated.Add(1); // Rehydration occurs before activation. if (rehydrationContext is not null) { result.Rehydrate(rehydrationContext); } // Initialize the new activation asynchronously. result.Activate(requestContextData); return result; [MethodImpl(MethodImplOptions.NoInlining)] static IGrainContext UnableToCreateActivation(Catalog self, GrainId grainId) { // Did not find and did not start placing new var status = self._siloStatusOracle.CurrentStatus; var isTerminating = status.IsTerminating(); if (status is not SiloStatus.Active) { self.LogDebugUnableToCreateActivationWhenNotActive(grainId); } else { self.LogDebugUnableToCreateActivation(grainId); } CatalogInstruments.NonExistentActivations.Add(1); var grainLocator = self.serviceProvider.GetRequiredService(); grainLocator.InvalidateCache(grainId); if (!isTerminating) { // Unregister the target activation so we don't keep getting spurious messages. // The time delay (one minute, as of this writing) is to handle the unlikely but possible race where // this request snuck ahead of another request, with new placement requested, for the same activation. // If the activation registration request from the new placement somehow sneaks ahead of this deregistration, // we want to make sure that we don't unregister the activation we just created. var address = new GrainAddress { SiloAddress = self.Silo, GrainId = grainId }; _ = self.UnregisterNonExistentActivation(address); } return null; } } private async Task UnregisterNonExistentActivation(GrainAddress address) { try { var grainLocator = serviceProvider.GetRequiredService(); await grainLocator.Unregister(address, UnregistrationCause.NonexistentActivation); } catch (Exception exc) { LogFailedToUnregisterNonExistingActivation(address, exc); } } /// /// Try to get runtime data for an activation /// private bool TryGetGrainContext(GrainId grainId, out IGrainContext data) { data = activations.FindTarget(grainId); return data != null; } /// /// Gracefully deactivates activations, waiting for them to complete /// complete and commit outstanding transactions before deleting it. /// To be called not from within Activation context, so can be awaited. /// internal async Task DeactivateActivations(DeactivationReason reason, List list, CancellationToken cancellationToken) { if (list == null || list.Count == 0) return; LogDebugDeactivateActivations(list.Count); var options = new ParallelOptions { CancellationToken = CancellationToken.None, MaxDegreeOfParallelism = Environment.ProcessorCount * 512 }; await Parallel.ForEachAsync(list, options, (activation, _) => { if (activation.GrainId.Type.IsSystemTarget()) { return ValueTask.CompletedTask; } activation.Deactivate(reason, cancellationToken); return new (activation.Deactivated); }).WaitAsync(cancellationToken); } public async Task DeactivateAllActivations(CancellationToken cancellationToken) { LogDebugDeactivateAllActivations(); LogDebugDeactivateActivations(activations.Count); var reason = new DeactivationReason(DeactivationReasonCode.ShuttingDown, "This process is terminating."); var options = new ParallelOptions { CancellationToken = CancellationToken.None, MaxDegreeOfParallelism = Environment.ProcessorCount * 512 }; await Parallel.ForEachAsync(activations, options, (kv, _) => { if (kv.Key.IsSystemTarget()) { return ValueTask.CompletedTask; } var activation = kv.Value; activation.Deactivate(reason, cancellationToken); return new (activation.Deactivated); }).WaitAsync(cancellationToken); } public async Task DeleteActivations(List addresses, DeactivationReasonCode reasonCode, string reasonText) { var tasks = new List(addresses.Count); var deactivationReason = new DeactivationReason(reasonCode, reasonText); await Parallel.ForEachAsync(addresses, (activationAddress, cancellationToken) => { if (TryGetGrainContext(activationAddress.GrainId, out var grainContext)) { grainContext.Deactivate(deactivationReason); return new ValueTask(grainContext.Deactivated); } return ValueTask.CompletedTask; }); } // TODO move this logic in the LocalGrainDirectory internal void OnSiloStatusChange(ILocalGrainDirectory directory, SiloAddress updatedSilo, SiloStatus status) { // ignore joining events and also events on myself. if (updatedSilo.Equals(_siloAddress)) return; // We deactivate those activations when silo goes either of ShuttingDown/Stopping/Dead states, // since this is what Directory is doing as well. Directory removes a silo based on all those 3 statuses, // thus it will only deliver a "remove" notification for a given silo once to us. Therefore, we need to react the fist time we are notified. // We may review the directory behavior in the future and treat ShuttingDown differently ("drain only") and then this code will have to change a well. if (!status.IsTerminating()) return; if (status == SiloStatus.Dead) { this.RuntimeClient.BreakOutstandingMessagesToSilo(updatedSilo); } var activationsToShutdown = new List(); try { // scan all activations in activation directory and deactivate the ones that the removed silo is their primary partition owner. // Note: No lock needed here since ActivationDirectory uses ConcurrentDictionary which provides thread-safe enumeration foreach (var activation in activations) { try { var activationData = activation.Value; var placementStrategy = activationData.GetComponent(); var isUsingGrainDirectory = placementStrategy is { IsUsingGrainDirectory: true }; if (!isUsingGrainDirectory || !grainDirectoryResolver.IsUsingDefaultDirectory(activationData.GrainId.Type)) continue; if (!updatedSilo.Equals(directory.GetPrimaryForGrain(activationData.GrainId))) continue; activationsToShutdown.Add(activationData); } catch (Exception exc) { LogErrorCatalogSiloStatusChangeNotification(new(updatedSilo), exc); } } if (activationsToShutdown.Count > 0) { LogInfoCatalogSiloStatusChangeNotification(activationsToShutdown.Count, new(updatedSilo)); } } finally { // outside the lock. if (activationsToShutdown.Count > 0) { var reasonText = $"This activation is being deactivated due to a failure of server {updatedSilo}, since it was responsible for this activation's grain directory registration."; var reason = new DeactivationReason(DeactivationReasonCode.DirectoryFailure, reasonText); StartDeactivatingActivations(reason, activationsToShutdown, CancellationToken.None); } } void StartDeactivatingActivations(DeactivationReason reason, List list, CancellationToken cancellationToken) { if (list == null || list.Count == 0) return; LogDebugDeactivateActivations(list.Count); foreach (var activation in list) { activation.Deactivate(reason, cancellationToken); } } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { // Do nothing, just ensure that this instance is created so that it can register itself in the activation directory. _siloStatusOracle = serviceProvider.GetRequiredService(); } private readonly struct SiloAddressLogValue(SiloAddress silo) { public override string ToString() => silo.ToStringWithHashCode(); } [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Catalog_SiloStatusChangeNotification_Exception, Message = "Catalog has thrown an exception while handling removal of silo {Silo}" )] private partial void LogErrorCatalogSiloStatusChangeNotification(SiloAddressLogValue silo, Exception exception); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.Catalog_SiloStatusChangeNotification, Message = "Catalog is deactivating {Count} activations due to a failure of silo {Silo}, since it is a primary directory partition to these grain ids." )] private partial void LogInfoCatalogSiloStatusChangeNotification(int count, SiloAddressLogValue silo); [LoggerMessage( Level = LogLevel.Trace, Message = "Unregistered activation {Activation}")] private partial void LogTraceUnregisteredActivation(IGrainContext activation); [LoggerMessage( Level = LogLevel.Debug, Message = "DeactivateActivations: {Count} activations.")] private partial void LogDebugDeactivateActivations(int count); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.Catalog_DeactivateAllActivations, Message = "DeactivateAllActivations." )] private partial void LogDebugDeactivateAllActivations(); [LoggerMessage( Level = LogLevel.Debug, Message = "Unable to create activation for grain {GrainId} because this silo is not active.")] private partial void LogDebugUnableToCreateActivationWhenNotActive(GrainId grainId); [LoggerMessage( EventId = (int)ErrorCode.CatalogNonExistingActivation2, Level = LogLevel.Debug, Message = "Unable to create activation for grain {GrainId}" )] private partial void LogDebugUnableToCreateActivation(GrainId grainId); [LoggerMessage( EventId = (int)ErrorCode.Dispatcher_FailedToUnregisterNonExistingAct, Level = LogLevel.Warning, Message = "Failed to unregister non-existent activation {Address}" )] private partial void LogFailedToUnregisterNonExistingActivation(GrainAddress address, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Catalog/GrainLifecycle.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal class GrainLifecycle(ILogger logger) : LifecycleSubject(logger), IGrainLifecycle { private static readonly ImmutableDictionary StageNames = GetStageNames(typeof(GrainLifecycleStage)); private List _migrationParticipants; public IEnumerable GetMigrationParticipants() => _migrationParticipants ?? (IEnumerable)[]; public void AddMigrationParticipant(IGrainMigrationParticipant participant) { lock (this) { _migrationParticipants ??= []; _migrationParticipants.Add(participant); } } public void RemoveMigrationParticipant(IGrainMigrationParticipant participant) { lock (this) { if (_migrationParticipants is null) return; _migrationParticipants.Remove(participant); } } protected override string GetStageName(int stage) { if (StageNames.TryGetValue(stage, out var result)) return result; return base.GetStageName(stage); } } } ================================================ FILE: src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Placement; using Orleans.Serialization.Session; using Orleans.Serialization.TypeSystem; namespace Orleans.Runtime; /// /// Functionality which is shared between all instances of a grain type. /// public sealed class GrainTypeSharedContext { private readonly IServiceProvider _serviceProvider; private readonly Dictionary _components = new(); private InternalGrainRuntime? _internalGrainRuntime; public GrainTypeSharedContext( GrainType grainType, IClusterManifestProvider clusterManifestProvider, GrainClassMap grainClassMap, PlacementStrategyResolver placementStrategyResolver, IOptions messagingOptions, IOptions collectionOptions, IOptions schedulingOptions, IOptions statelessWorkerOptions, IGrainRuntime grainRuntime, ILoggerFactory loggerFactory, GrainReferenceActivator grainReferenceActivator, IServiceProvider serviceProvider, SerializerSessionPool serializerSessionPool) { if (!grainClassMap.TryGetGrainClass(grainType, out var grainClass)) { throw new KeyNotFoundException($"Could not find corresponding grain class for grain of type {grainType}"); } SerializerSessionPool = serializerSessionPool; GrainTypeName = RuntimeTypeNameFormatter.Format(grainClass); Logger = loggerFactory.CreateLogger("Orleans.Grain"); MessagingOptions = messagingOptions.Value; GrainReferenceActivator = grainReferenceActivator; _serviceProvider = serviceProvider; MaxWarningRequestProcessingTime = messagingOptions.Value.ResponseTimeout.Multiply(5); MaxRequestProcessingTime = messagingOptions.Value.MaxRequestProcessingTime; PlacementStrategy = placementStrategyResolver.GetPlacementStrategy(grainType); var grainDirectoryResolver = serviceProvider.GetRequiredService(); GrainDirectory = PlacementStrategy.IsUsingGrainDirectory ? grainDirectoryResolver.Resolve(grainType) : null; SchedulingOptions = schedulingOptions.Value; StatelessWorkerOptions = statelessWorkerOptions.Value; Runtime = grainRuntime; MigrationManager = _serviceProvider.GetService(); CollectionAgeLimit = GetCollectionAgeLimit( grainType, grainClass, clusterManifestProvider.LocalGrainManifest, collectionOptions.Value); } /// /// Gets the grain instance type name, if available. /// public string? GrainTypeName { get; } private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainClass, GrainManifest siloManifest, GrainCollectionOptions collectionOptions) { if (siloManifest.Grains.TryGetValue(grainType, out var properties) && properties.Properties.TryGetValue(WellKnownGrainTypeProperties.IdleDeactivationPeriod, out var idleTimeoutString)) { if (string.Equals(idleTimeoutString, WellKnownGrainTypeProperties.IndefiniteIdleDeactivationPeriodValue)) { return Timeout.InfiniteTimeSpan; } if (TimeSpan.TryParse(idleTimeoutString, out var result)) { return result; } } if (collectionOptions.ClassSpecificCollectionAge.TryGetValue(grainClass.FullName!, out var specified)) { return specified; } return collectionOptions.CollectionAge; } /// /// Gets a component. /// /// The type specified in the corresponding call. public TComponent? GetComponent() { if (typeof(TComponent) == typeof(PlacementStrategy) && PlacementStrategy is TComponent component) { return component; } if (typeof(TComponent) == typeof(ILogger)) { return (TComponent)Logger; } if (_components is null) return default; _components.TryGetValue(typeof(TComponent), out var resultObj); return (TComponent?)resultObj; } /// /// Gets a component. /// /// The type of the component. /// The component with the specified type. public object? GetComponent(Type componentType) { if (componentType == typeof(PlacementStrategy)) { return PlacementStrategy; } if (componentType == typeof(ILogger)) { return Logger; } if (_components is null) return default; _components.TryGetValue(componentType, out var resultObj); return resultObj; } /// /// Registers a component. /// /// The type which can be used as a key to . public void SetComponent(TComponent? instance) { if (instance == null) { _components.Remove(typeof(TComponent)); return; } _components[typeof(TComponent)] = instance; } /// /// Gets the duration after which idle grains are eligible for collection. /// public TimeSpan CollectionAgeLimit { get; } /// /// Gets the logger. /// public ILogger Logger { get; } /// /// Gets the serializer session pool. /// public SerializerSessionPool SerializerSessionPool { get; } /// /// Gets the silo messaging options. /// public SiloMessagingOptions MessagingOptions { get; } /// /// Gets the grain reference activator. /// public GrainReferenceActivator GrainReferenceActivator { get; } /// /// Gets the maximum amount of time we expect a request to continue processing before it is considered hung. /// public TimeSpan MaxRequestProcessingTime { get; } /// /// Gets the maximum amount of time we expect a request to continue processing before a warning may be logged. /// public TimeSpan MaxWarningRequestProcessingTime { get; } /// /// Gets the placement strategy used by grains of this type. /// public PlacementStrategy PlacementStrategy { get; } /// /// Gets the grain directory used by grains of this type, if this type uses a grain directory. /// public IGrainDirectory? GrainDirectory { get; } /// /// Gets the scheduling options. /// public SchedulingOptions SchedulingOptions { get; } /// /// Gets the stateless worker options. /// public StatelessWorkerOptions StatelessWorkerOptions { get; } /// /// Gets the grain runtime. /// public IGrainRuntime Runtime { get; } /// /// Gets the local activation migration manager. /// internal IActivationMigrationManager? MigrationManager { get; } /// /// Gets the internal grain runtime. /// internal InternalGrainRuntime InternalRuntime => _internalGrainRuntime ??= _serviceProvider.GetRequiredService(); /// /// Called on creation of an activation. /// /// The grain activation. public void OnCreateActivation(IGrainContext grainContext) { GrainInstruments.IncrementGrainCounts(GrainTypeName); } /// /// Called when an activation is disposed. /// /// The grain activation. public void OnDestroyActivation(IGrainContext grainContext) { GrainInstruments.DecrementGrainCounts(GrainTypeName); } } internal interface IActivationLifecycleObserver { void OnCreateActivation(IGrainContext grainContext); void OnDestroyActivation(IGrainContext grainContext); } ================================================ FILE: src/Orleans.Runtime/Catalog/IActivationCollector.cs ================================================ namespace Orleans.Runtime { internal interface IActivationCollector { /// /// Schedule collection. /// /// The activation to be scheduled. /// void ScheduleCollection(ActivationData item); /// /// Attempt to reschedule collection. /// /// The activation to be rescheduled. /// bool TryRescheduleCollection(ActivationData item); } } ================================================ FILE: src/Orleans.Runtime/Catalog/ICatalog.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Remote interface to grain and activation state /// internal interface ICatalog : ISystemTarget { /// /// Delete activations from this silo /// /// /// /// /// Task DeleteActivations(List activationAddresses, DeactivationReasonCode reasonCode, string reasonText); } } ================================================ FILE: src/Orleans.Runtime/Catalog/IncomingRequestMonitor.cs ================================================ using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Messaging; namespace Orleans.Runtime { /// /// Monitors currently-active requests and sends status notifications to callers for long-running and blocked requests. /// internal sealed class IncomingRequestMonitor : ILifecycleParticipant, IActivationWorkingSetObserver { private static readonly TimeSpan DefaultAnalysisPeriod = TimeSpan.FromSeconds(10); private static readonly TimeSpan InactiveGrainIdleness = TimeSpan.FromMinutes(1); private readonly IAsyncTimer _scanPeriodTimer; private readonly IServiceProvider _serviceProvider; private readonly MessageFactory _messageFactory; private readonly IOptionsMonitor _messagingOptions; private readonly ConcurrentDictionary _recentlyUsedActivations = new ConcurrentDictionary(ReferenceEqualsComparer.Default); private bool _enabled = true; private Task _runTask; public IncomingRequestMonitor( IAsyncTimerFactory asyncTimerFactory, IServiceProvider serviceProvider, MessageFactory messageFactory, IOptionsMonitor siloMessagingOptions) { _scanPeriodTimer = asyncTimerFactory.Create(TimeSpan.FromSeconds(1), nameof(IncomingRequestMonitor)); _serviceProvider = serviceProvider; _messageFactory = messageFactory; _messagingOptions = siloMessagingOptions; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkRecentlyUsed(ActivationData activation) { if (!_enabled) { return; } _recentlyUsedActivations.TryAdd(activation, true); } public void OnActive(IActivationWorkingSetMember member) { if (member is ActivationData activation) { MarkRecentlyUsed(activation); } } public void OnIdle(IActivationWorkingSetMember member) { if (member is ActivationData activation) { _recentlyUsedActivations.TryRemove(activation, out _); } } public void OnEvicted(IActivationWorkingSetMember member) => OnIdle(member); void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(IncomingRequestMonitor), ServiceLifecycleStage.BecomeActive, ct => { _runTask = Task.Run(this.Run); return Task.CompletedTask; }, async ct => { _scanPeriodTimer.Dispose(); if (_runTask is Task task) { await task.WaitAsync(ct).SuppressThrowing(); } }); } private async Task Run() { var options = _messagingOptions.CurrentValue; var optionsPeriod = options.GrainWorkloadAnalysisPeriod; TimeSpan nextDelay = optionsPeriod > TimeSpan.Zero ? optionsPeriod : DefaultAnalysisPeriod; var messageCenter = _serviceProvider.GetRequiredService(); while (await _scanPeriodTimer.NextTick(nextDelay)) { options = _messagingOptions.CurrentValue; optionsPeriod = options.GrainWorkloadAnalysisPeriod; if (optionsPeriod <= TimeSpan.Zero) { // Scanning is disabled. Wake up and check again soon. nextDelay = DefaultAnalysisPeriod; if (_enabled) { _enabled = false; } _recentlyUsedActivations.Clear(); continue; } nextDelay = optionsPeriod; if (!_enabled) { _enabled = true; } var iteration = 0; var now = DateTime.UtcNow; foreach (var activationEntry in _recentlyUsedActivations) { var activation = activationEntry.Key; lock (activation) { activation.AnalyzeWorkload(now, messageCenter, _messageFactory, options); } // Yield execution frequently if (++iteration % 100 == 0) { await Task.Yield(); now = DateTime.UtcNow; } } } } } } ================================================ FILE: src/Orleans.Runtime/Catalog/LocalActivationStatusChecker.cs ================================================ #nullable enable namespace Orleans.Runtime; internal sealed class LocalActivationStatusChecker(ActivationDirectory activationDirectory) : ILocalActivationStatusChecker { public bool IsLocallyActivated(GrainId grainId) => activationDirectory.FindTarget(grainId) is { } activation && (activation is not ActivationData activationData || activationData.IsValid); } ================================================ FILE: src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime; internal partial class StatelessWorkerGrainContext : IGrainContext, IAsyncDisposable, IActivationLifecycleObserver { private static readonly object CollectIdleWorkersSentinel = new(); private readonly GrainTypeSharedContext _shared; private readonly IGrainContextActivator _innerActivator; private readonly int _maxWorkers; private readonly List _workers = []; private readonly ConcurrentQueue<(WorkItemType Type, object State)> _workItems = new(); private readonly SingleWaiterAutoResetEvent _workSignal = new() { RunContinuationsAsynchronously = false }; #pragma warning disable IDE0052 // Remove unread private members /// /// The representing the invocation. /// This is written once but never otherwise accessed. The purpose of retaining this field is for /// debugging, where being able to identify the message loop task corresponding to an activation can /// be useful. /// private readonly Task _messageLoopTask; #pragma warning restore IDE0052 // Remove unread private members private GrainReference? _grainReference; // Idle worker removal fields private Timer? _inspectionTimer; private double _previousError = 0d; private double _integralTerm = 0d; private int _detectedIdleCyclesCount = 0; private readonly int _minIdleCyclesBeforeRemoval; private readonly bool _isIdleWorkerRemovalStrategy; public StatelessWorkerGrainContext( GrainAddress address, GrainTypeSharedContext sharedContext, IGrainContextActivator innerActivator) { Address = address; _shared = sharedContext; _innerActivator = innerActivator; var strategy = (StatelessWorkerPlacement)_shared.PlacementStrategy; var options = _shared.StatelessWorkerOptions; _isIdleWorkerRemovalStrategy = strategy.RemoveIdleWorkers && options.RemoveIdleWorkers; if (_isIdleWorkerRemovalStrategy) { _minIdleCyclesBeforeRemoval = options.MinIdleCyclesBeforeRemoval > 0 ? options.MinIdleCyclesBeforeRemoval : 1; _inspectionTimer = new Timer( static state => ((StatelessWorkerGrainContext)state!).EnqueueWorkItem(WorkItemType.CollectIdleWorkers, CollectIdleWorkersSentinel), this, options.IdleWorkersInspectionPeriod, options.IdleWorkersInspectionPeriod); } _maxWorkers = strategy.MaxLocal; _messageLoopTask = Task.Run(RunMessageLoop); } public GrainReference GrainReference => _grainReference ??= _shared.GrainReferenceActivator.CreateReference(GrainId, default); public GrainId GrainId => Address.GrainId; public object? GrainInstance => null; public ActivationId ActivationId => Address.ActivationId; public GrainAddress Address { get; } public IServiceProvider ActivationServices => throw new NotImplementedException(); public IGrainLifecycle ObservableLifecycle => throw new NotImplementedException(); public IWorkItemScheduler Scheduler => throw new NotImplementedException(); public PlacementStrategy PlacementStrategy => _shared.PlacementStrategy; public Task Deactivated { get { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); EnqueueWorkItem(WorkItemType.DeactivatedTask, new DeactivatedTaskWorkItemState(completion)); return completion.Task; } } public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { } public void ReceiveMessage(object message) => EnqueueWorkItem(WorkItemType.Message, message); public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken) => EnqueueWorkItem(WorkItemType.Deactivate, new DeactivateWorkItemState(deactivationReason, cancellationToken)); public async ValueTask DisposeAsync() { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); EnqueueWorkItem(WorkItemType.DisposeAsync, new DisposeAsyncWorkItemState(completion)); await completion.Task; } private void EnqueueWorkItem(WorkItemType type, object state) { _workItems.Enqueue(new(type, state)); _workSignal.Signal(); } public bool Equals([AllowNull] IGrainContext other) => other is not null && ActivationId.Equals(other.ActivationId); public object? GetComponent(Type componentType) { if (componentType.IsAssignableFrom(GetType())) return this; return _shared.GetComponent(componentType); } public void SetComponent(TComponent? instance) where TComponent : class { if (typeof(TComponent) != typeof(GrainCanInterleave)) { throw new ArgumentException($"Cannot set a component of type '{typeof(TComponent)}' on a {nameof(StatelessWorkerGrainContext)}"); } _shared.SetComponent(instance); } public object? GetTarget() => throw new NotImplementedException(); private async Task RunMessageLoop() { while (true) { try { while (_workItems.TryDequeue(out var workItem)) { switch (workItem.Type) { case WorkItemType.Message: ReceiveMessageInternal(workItem.State); break; case WorkItemType.Deactivate: { var state = (DeactivateWorkItemState)workItem.State; DeactivateInternal(state.DeactivationReason, state.CancellationToken); break; } case WorkItemType.DeactivatedTask: { var state = (DeactivatedTaskWorkItemState)workItem.State; _ = DeactivatedTaskInternal(state.Completion); break; } case WorkItemType.DisposeAsync: { var state = (DisposeAsyncWorkItemState)workItem.State; _ = DisposeAsyncInternal(state.Completion); break; } case WorkItemType.OnDestroyActivation: { var grainContext = (ActivationData)workItem.State; _workers.Remove(grainContext); if (_workers.Count == 0) { // When the last worker is destroyed, we can consider the stateless worker grain activation to be destroyed as well _shared.InternalRuntime.Catalog.UnregisterMessageTarget(this); if (_isIdleWorkerRemovalStrategy) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); EnqueueWorkItem(WorkItemType.DisposeAsync, new DisposeAsyncWorkItemState(completion)); // DO NOT await this as it would deadlock the work loop! _ = completion.Task.ContinueWith(t => { if (t.Exception is { } ex) { LogErrorInMessageLoop(_shared.Logger, ex); } }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Current); } } break; } case WorkItemType.CollectIdleWorkers: CollectIdleWorkers(); break; default: throw new NotSupportedException($"Work item of type {workItem.Type} is not supported"); } } await _workSignal.WaitAsync(); } catch (Exception exception) { LogErrorInMessageLoop(_shared.Logger, exception); } } } private void CollectIdleWorkers() { // These parameter values were tuned using a genetic algorithm, for potential re-tuning see: // https://github.com/ledjon-behluli/Orleans.FullyAdaptiveStatelessWorkerSimulations/blob/main/Orleans.FullyAdaptiveStatelessWorkerSimulations/Program.cs // For more info see: // https://www.ledjonbehluli.com/posts/orleans_adaptive_stateless_worker/ const double Kp = 0.433; const double Ki = 0.468; const double Kd = 0.480; var averageWaitingCount = _workers.Count > 0 ? _workers.Average(w => w.WaitingCount) : 0d; var error = -averageWaitingCount; // Our target is 0 waiting count: 0 - avgWC = -avgWC _integralTerm += error; var derivativeTerm = error - _previousError; _previousError = error; var controlSignal = Kp * error + Ki * _integralTerm + Kd * derivativeTerm; _detectedIdleCyclesCount = controlSignal < 0 ? ++_detectedIdleCyclesCount : 0; if (_detectedIdleCyclesCount >= _minIdleCyclesBeforeRemoval) { var inactiveWorkers = _workers.Where(w => w.IsInactive).ToImmutableArray(); if (inactiveWorkers.Length > 0) { var worker = inactiveWorkers[Random.Shared.Next(inactiveWorkers.Length)]; worker.Deactivate(new(DeactivationReasonCode.RuntimeRequested, "Worker deactivated due to inactivity.")); var antiWindUpFactor = (double)(inactiveWorkers.Length - 1) / inactiveWorkers.Length; _integralTerm *= antiWindUpFactor; _detectedIdleCyclesCount = 0; } } } private void ReceiveMessageInternal(object message) { try { ActivationData? worker = null; ActivationData? minimumWaitingCountWorker = null; var minimumWaitingCount = int.MaxValue; // Make sure there is at least one worker if (_workers.Count == 0) { worker = CreateWorker(message); } else { // Check to see if we have any inactive workers, prioritizing // them in the order they were created to minimize the number // of workers spawned for (var i = 0; i < _workers.Count; i++) { if (_workers[i].IsInactive) { worker = _workers[i]; break; } else { // Track the worker with the lowest value for WaitingCount, // this is used if all workers are busy if (_workers[i].WaitingCount < minimumWaitingCount) { minimumWaitingCount = _workers[i].WaitingCount; minimumWaitingCountWorker = _workers[i]; } } } if (worker is null) { if (_workers.Count >= _maxWorkers) { // Pick the one with the lowest waiting count worker = minimumWaitingCountWorker; } // If there are no workers, make one. worker ??= CreateWorker(message); } } worker.ReceiveMessage(message); } catch (Exception exception) when (message is Message msg) { _shared.InternalRuntime.MessageCenter.RejectMessage( msg, Message.RejectionTypes.Transient, exception, "Exception while creating grain context"); } } private ActivationData CreateWorker(object? message) { var address = GrainAddress.GetAddress(Address.SiloAddress, Address.GrainId, ActivationId.NewId()); var newWorker = (ActivationData)_innerActivator.CreateContext(address); // Observe the create/destroy lifecycle of the activation newWorker.SetComponent(this); // If this is a new worker and there is a message in scope, try to get the request context and activate the worker var requestContext = (message as Message)?.RequestContextData ?? []; var cancellation = new CancellationTokenSource(_shared.InternalRuntime.CollectionOptions.Value.ActivationTimeout); newWorker.Activate(requestContext, cancellation.Token); _workers.Add(newWorker); return newWorker; } private void DeactivateInternal(DeactivationReason reason, CancellationToken cancellationToken) { foreach (var worker in _workers) { worker.Deactivate(reason, cancellationToken); } } private async Task DeactivatedTaskInternal(TaskCompletionSource completion) { try { var tasks = new List(_workers.Count); foreach (var worker in _workers) { tasks.Add(worker.Deactivated); } await Task.WhenAll(tasks); completion.TrySetResult(); } catch (Exception exception) { completion.TrySetException(exception); } } private async Task DisposeAsyncInternal(TaskCompletionSource completion) { if (_inspectionTimer != null) { await _inspectionTimer.DisposeAsync().AsTask() .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); _inspectionTimer = null; } try { var tasks = new List(_workers.Count); var deactivationReason = new DeactivationReason(DeactivationReasonCode.RuntimeRequested, "Stateless worker grain context is being disposed."); foreach (var worker in _workers) { try { worker.Deactivate(deactivationReason); tasks.Add(worker.Deactivated); } catch (Exception exception) { tasks.Add(Task.FromException(exception)); } } await Task.WhenAll(tasks); completion.TrySetResult(); } catch (Exception exception) { completion.TrySetException(exception); } } public void OnCreateActivation(IGrainContext grainContext) { } public void OnDestroyActivation(IGrainContext grainContext) => EnqueueWorkItem(WorkItemType.OnDestroyActivation, grainContext); public void Rehydrate(IRehydrationContext context) => // Migration is not supported, but we need to dispose of the context if it's provided (context as IDisposable)?.Dispose(); public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken) { // Migration is not supported. Do nothing: the contract is that this method attempts migration, but does not guarantee it will occur. } private enum WorkItemType { Message, Deactivate, DeactivatedTask, DisposeAsync, OnDestroyActivation, CollectIdleWorkers } private record ActivateWorkItemState(Dictionary? RequestContext, CancellationToken CancellationToken); private record DeactivateWorkItemState(DeactivationReason DeactivationReason, CancellationToken CancellationToken); private record DeactivatedTaskWorkItemState(TaskCompletionSource Completion); private record DisposeAsyncWorkItemState(TaskCompletionSource Completion); [LoggerMessage( Level = LogLevel.Error, Message = "Error in stateless worker message loop" )] private static partial void LogErrorInMessageLoop(ILogger logger, Exception exception); } ================================================ FILE: src/Orleans.Runtime/Catalog/StreamResourceTestControl.cs ================================================ namespace Orleans.Runtime { internal static class StreamResourceTestControl { internal static bool TestOnlySuppressStreamCleanupOnDeactivate; } } ================================================ FILE: src/Orleans.Runtime/Catalog/SystemTargetShared.cs ================================================ #nullable enable using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainReferences; using Orleans.Runtime.Scheduler; using Orleans.Timers; namespace Orleans.Runtime; internal sealed class SystemTargetShared( InsideRuntimeClient runtimeClient, ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory, IOptions schedulingOptions, GrainReferenceActivator grainReferenceActivator, ITimerRegistry timerRegistry, ActivationDirectory activations) { private readonly ILogger _workItemGroupLogger = loggerFactory.CreateLogger(); private readonly ILogger _activationTaskSchedulerLogger = loggerFactory.CreateLogger(); public SiloAddress SiloAddress => localSiloDetails.SiloAddress; public ILoggerFactory LoggerFactory => loggerFactory; public GrainReferenceActivator GrainReferenceActivator => grainReferenceActivator; public ITimerRegistry TimerRegistry => timerRegistry; public RuntimeMessagingTrace MessagingTrace { get; } = new(loggerFactory); public InsideRuntimeClient RuntimeClient => runtimeClient; public ActivationDirectory ActivationDirectory => activations; public WorkItemGroup CreateWorkItemGroup(SystemTarget systemTarget) { ArgumentNullException.ThrowIfNull(systemTarget); return new WorkItemGroup( systemTarget, _workItemGroupLogger, _activationTaskSchedulerLogger, schedulingOptions); } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/ActivationCountBasedPlacementOptions.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Settings which regulate the placement of grains across a cluster when using . /// public class ActivationCountBasedPlacementOptions { /// /// Gets or sets the number of silos randomly selected for consideration when using activation count placement policy. /// Only used with Activation Count placement policy. /// public int ChooseOutOf { get; set; } = DEFAULT_ACTIVATION_COUNT_PLACEMENT_CHOOSE_OUT_OF; /// /// The default number of silos to choose from when making placement decisions. /// public const int DEFAULT_ACTIVATION_COUNT_PLACEMENT_CHOOSE_OUT_OF = 2; } /// /// Validates properties. /// internal class ActivationCountBasedPlacementOptionsValidator : IConfigurationValidator { private readonly ActivationCountBasedPlacementOptions options; public ActivationCountBasedPlacementOptionsValidator(IOptions options) { this.options = options.Value; } /// public void ValidateConfiguration() { if (this.options.ChooseOutOf <= 0) { throw new OrleansConfigurationException( $"The value of {nameof(ActivationCountBasedPlacementOptions)}.{nameof(this.options.ChooseOutOf)} must be greater than 0."); } } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/ActivationRebalancerOptions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration; /// /// Options for configuring activation rebalancing. /// public sealed class ActivationRebalancerOptions { /// /// The due time for the rebalancer to start the very first session. /// public TimeSpan RebalancerDueTime { get; set; } = DEFAULT_REBALANCER_DUE_TIME; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_REBALANCER_DUE_TIME = TimeSpan.FromSeconds(60); /// /// The time between two consecutive rebalancing cycles within a session. /// /// It must be greater than 2 x . public TimeSpan SessionCyclePeriod { get; set; } = DEFAULT_SESSION_CYCLE_PERIOD; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_SESSION_CYCLE_PERIOD = TimeSpan.FromSeconds(15); /// /// The maximum, consecutive number of cycles, yielding no significant improvement to the cluster's entropy. /// /// This value is inclusive, i.e. if this value is 'n', then the 'n+1' cycle will stop the current rebalancing session. public int MaxStagnantCycles { get; set; } = DEFAULT_MAX_STAGNANT_CYCLES; /// /// The default value of . /// public const int DEFAULT_MAX_STAGNANT_CYCLES = 3; /// /// The minimum change in the entropy of the cluster that is considered an improvement. /// When a total of n-consecutive stagnant cycles pass, during which the change in entropy is less than /// the quantum, then the current rebalancing session will stop. The change is a normalized value /// being relative to the maximum possible entropy. /// /// Allowed range: (0-0.1] public double EntropyQuantum { get; set; } = DEFAULT_ENTROPY_QUANTUM; /// /// The default value of . /// public const double DEFAULT_ENTROPY_QUANTUM = 0.0001d; /// /// Represents the allowed entropy deviation between the cluster's current entropy, against the theoretical maximum. /// Values lower than this are practically considered as "maximum", and the current rebalancing session will stop. /// This acts as a base rate if is set to . /// /// Allowed range is: (0-0.1] public double AllowedEntropyDeviation { get; set; } = DEFAULT_ALLOWED_ENTROPY_DEVIATION; /// /// The default value of . /// public const double DEFAULT_ALLOWED_ENTROPY_DEVIATION = 0.0001d; /// /// Determines whether should be scaled dynamically /// based on the total number of activations. When set to , the allowed entropy /// deviation will increase logarithmically after reaching , /// and will cap at . /// /// This is in place because a deviation of say 10 activations has far lesser /// impact on a total of 100,000 activations than it does for say 1,000 activations. public bool ScaleAllowedEntropyDeviation { get; set; } = DEFAULT_SCALE_ALLOWED_ENTROPY_DEVIATION; /// /// The default value of . /// public const bool DEFAULT_SCALE_ALLOWED_ENTROPY_DEVIATION = true; /// /// The maximum value allowed when is . /// public const double MAX_SCALED_ENTROPY_DEVIATION = 0.1d; /// /// Determines the number of activations that must be active during any rebalancing cycle, in order for /// (if, and only if, its ) to begin scaling the . /// /// /// Allowed range: [1000-∞) /// Values lower than the default are discouraged. /// public int ScaledEntropyDeviationActivationThreshold { get; set; } = DEFAULT_SCALED_ENTROPY_DEVIATION_ACTIVATION_THRESHOLD; /// /// The default value of . /// public const int DEFAULT_SCALED_ENTROPY_DEVIATION_ACTIVATION_THRESHOLD = 10_000; /// /// Represents the weight that is given to the number of rebalancing cycles that have passed during a rebalancing session. /// Changing this value has a far greater impact on the migration rate than , and is suitable for controlling the session duration. /// Pick higher values if you want a faster migration rate. /// /// Allowed range: (0-1] public double CycleNumberWeight { get; set; } = DEFAULT_CYCLE_NUMBER_WEIGHT; /// /// The default value of . /// public const double DEFAULT_CYCLE_NUMBER_WEIGHT = 0.1d; /// /// Represents the weight that is given to the number of silos in the cluster during a rebalancing session. /// Changing this value has a far lesser impact on the migration rate than , and is suitable for fine-tuning. /// Pick lower values if you want a faster migration rate. /// /// Allowed range: [0-1] public double SiloNumberWeight { get; set; } = DEFAULT_SILO_NUMBER_WEIGHT; /// /// The default value of . /// public const double DEFAULT_SILO_NUMBER_WEIGHT = 0.1d; /// /// The maximum allowed number of activations that can be migrated at any given cycle. /// public int ActivationMigrationCountLimit { get; set; } = DEFAULT_ACTIVATION_MIGRATION_COUNT_LIMIT; /// /// The default value for . /// The default is practically no limit. /// public const int DEFAULT_ACTIVATION_MIGRATION_COUNT_LIMIT = int.MaxValue; } internal sealed class ActivationRebalancerOptionsValidator( IOptions options, IOptions publisherOptions) : IConfigurationValidator { private readonly ActivationRebalancerOptions _options = options.Value; private readonly DeploymentLoadPublisherOptions _publisherOptions = publisherOptions.Value; public void ValidateConfiguration() { if (_options.SessionCyclePeriod < 2 * _publisherOptions.DeploymentLoadPublisherRefreshTime) { throw new OrleansConfigurationException( $"{nameof(ActivationRebalancerOptions.SessionCyclePeriod)} must be at least " + $"{$"2 x {nameof(DeploymentLoadPublisherOptions.DeploymentLoadPublisherRefreshTime)}"}"); } if (_options.MaxStagnantCycles <= 0) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.MaxStagnantCycles)} must be greater than 0"); } if (_options.EntropyQuantum <= 0d || _options.EntropyQuantum > 0.1d) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.EntropyQuantum)} must be in greater than 0, and less or equal 0.1"); } if (_options.AllowedEntropyDeviation <= 0d || _options.AllowedEntropyDeviation > 0.1d) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.AllowedEntropyDeviation)} must be in greater than 0, and less or equal 0.1"); } if (_options.CycleNumberWeight <= 0d || _options.CycleNumberWeight > 1d) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.CycleNumberWeight)} must be in greater than 0, and less or equal to 1"); } if (_options.SiloNumberWeight < 0d || _options.SiloNumberWeight > 1d) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.SiloNumberWeight)} must be in greater than or equal to 0, and less or equal to 1"); } if (_options.ActivationMigrationCountLimit < 1) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.ActivationMigrationCountLimit)} must be greater than 0"); } if (_options.ScaledEntropyDeviationActivationThreshold < 1_000) { throw new OrleansConfigurationException($"{nameof(ActivationRebalancerOptions.ScaledEntropyDeviationActivationThreshold)} must be greater than or equal to 1000"); } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/ActivationRepartitionerOptions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration; public sealed class ActivationRepartitionerOptions { /// /// /// The maximum number of edges to retain in-memory during a repartitioning round. An edge represents how many calls were made from one grain to another. /// /// /// If this number is N, it does not mean that N activations will be migrated after a repartitioning round. /// It also does not mean that if any activation ranked very high, that it will rank high at the next cycle. /// At the most extreme case, the number of activations that will be migrated, will equal this number, so this should give you some idea as to setting a reasonable value for this. /// /// /// /// In order to preserve memory, the most heaviest links are recorded in a probabilistic way, so there is an inherent error associated with that. /// That error is inversely proportional to this value, so values under 100 are not recommended. If you notice that the system is not converging fast enough, do consider increasing this number. /// public int MaxEdgeCount { get; set; } = DEFAULT_MAX_EDGE_COUNT; /// /// The default value of . /// public const int DEFAULT_MAX_EDGE_COUNT = 10_000; /// /// The minimum time between initiating a repartitioning round. /// /// The actual due time is picked randomly between this and . public TimeSpan MinRoundPeriod { get; set; } = DEFAULT_MINUMUM_ROUND_PERIOD; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_MINUMUM_ROUND_PERIOD = TimeSpan.FromMinutes(1); /// /// The maximum time between initiating a repartitioning round. /// /// /// The actual due time is picked randomly between this and . /// For optimal results, you should aim to give this an extra 10 seconds multiplied by the maximum anticipated silo count in the cluster. /// public TimeSpan MaxRoundPeriod { get; set; } = DEFAULT_MAXIMUM_ROUND_PERIOD; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_MAXIMUM_ROUND_PERIOD = TimeSpan.FromMinutes(2); /// /// The minimum time needed for a silo to recover from a previous repartitioning round. /// Until this time has elapsed, this silo will not take part in any repartitioning attempt from another silo. /// public TimeSpan RecoveryPeriod { get; set; } = DEFAULT_RECOVERY_PERIOD; /// /// The default value of . /// public static readonly TimeSpan DEFAULT_RECOVERY_PERIOD = TimeSpan.FromMinutes(1); /// /// The maximum number of unprocessed edges to buffer. If this number is exceeded, the oldest edges will be discarded. /// public int MaxUnprocessedEdges { get; set; } = DEFAULT_MAX_UNPROCESSED_EDGES; /// /// The default value of . /// public const int DEFAULT_MAX_UNPROCESSED_EDGES = 100_000; /// /// Gets or sets a value indicating whether to enable the local vertex filter. This filter tracks which /// vertices are well-partitioned (moving them from the local host would be detrimental) and collapses them /// into a single per-silo vertex to reduce the space required to track edges involving that vertex. The result /// is a reduction in accuracy but a potentially significant increase in effectiveness of the repartitioner, since /// well-partitioned edges will not dominate the top-K data structure, leaving sufficient room to track /// non-well-partitioned vertices. This is enabled by default. /// public bool AnchoringFilterEnabled { get; set; } = DEFAULT_ANCHORING_FILTER_ENABLED; /// /// The default value of . /// public const bool DEFAULT_ANCHORING_FILTER_ENABLED = true; /// /// The maximum allowed error rate when is set to , otherwise this does not apply. /// /// Allowed range: [0.001 - 0.01](0.1% - 1%) public double ProbabilisticFilteringMaxAllowedErrorRate { get; set; } = DEFAULT_PROBABILISTIC_FILTERING_MAX_ALLOWED_ERROR; /// /// The default value of . /// public const double DEFAULT_PROBABILISTIC_FILTERING_MAX_ALLOWED_ERROR = 0.01d; /// /// /// Determines how long anchoring history is retained. A lower generation count keeps the filter fresher, but might forget anchored grains too quickly. /// A higher generation count retains history longer, but increases the memory usage and the risk of saturation. /// /// /// Because the filter decays exactly once per repartitioning round, the actual time a "cold" grain remains anchored is directly tied to /// and . /// For example with the default of 3 generations and round periods randomly firing between 1-2 mins, a grain that goes completely /// "cold" will be retained in the filter for 4.5 mins (on average) before being dropped. /// /// /// /// /// Setting this value to 1 effectively disables retention. The filter will be compltely cleared at the start of every /// repartitioning round, meaning a grain must be continuusly active in the exact current cycle to remain anchored. /// This makes the filter highly reactive to current traffic, but prone to forgetting anchored grains during brief idle periods. /// /// public int AnchoringFilterGenerations { get; set; } = DEFAULT_ANCHORING_FILTER_GENERATIONS; /// /// The default value of . /// public const int DEFAULT_ANCHORING_FILTER_GENERATIONS = 3; } internal sealed class ActivationRepartitionerOptionsValidator(IOptions options) : IConfigurationValidator { private readonly ActivationRepartitionerOptions _options = options.Value; public void ValidateConfiguration() { if (_options.MaxEdgeCount <= 0) { ThrowMustBeGreaterThanZero(nameof(ActivationRepartitionerOptions.MaxEdgeCount)); } if (_options.MaxUnprocessedEdges <= 0) { ThrowMustBeGreaterThanZero(nameof(ActivationRepartitionerOptions.MaxUnprocessedEdges)); } if (_options.MinRoundPeriod < TimeSpan.Zero) { ThrowMustBeGreaterThanOrEqualToZero(nameof(ActivationRepartitionerOptions.MinRoundPeriod)); } if (_options.MaxRoundPeriod <= TimeSpan.Zero) { ThrowMustBeGreaterThanZero(nameof(ActivationRepartitionerOptions.MaxRoundPeriod)); } if (_options.RecoveryPeriod < TimeSpan.Zero) { ThrowMustBeGreaterThanOrEqualToZero(nameof(ActivationRepartitionerOptions.RecoveryPeriod)); } if (_options.MaxRoundPeriod < _options.MinRoundPeriod) { ThrowMustBeGreaterThanOrEqualTo(nameof(ActivationRepartitionerOptions.MaxRoundPeriod), nameof(ActivationRepartitionerOptions.MinRoundPeriod)); } if (_options.MinRoundPeriod < _options.RecoveryPeriod) { ThrowMustBeGreaterThanOrEqualTo(nameof(ActivationRepartitionerOptions.MinRoundPeriod), nameof(ActivationRepartitionerOptions.RecoveryPeriod)); } if (_options.ProbabilisticFilteringMaxAllowedErrorRate < 0.001d || _options.ProbabilisticFilteringMaxAllowedErrorRate > 0.01d) { throw new OrleansConfigurationException($"{nameof(ActivationRepartitionerOptions.ProbabilisticFilteringMaxAllowedErrorRate)} must be inclusive between [0.001 - 0.01](0.1% - 1%)"); } if (_options.AnchoringFilterGenerations <= 0) { ThrowMustBeGreaterThanZero(nameof(ActivationRepartitionerOptions.AnchoringFilterGenerations)); } } private static void ThrowMustBeGreaterThanOrEqualToZero(string propertyName) => throw new OrleansConfigurationException($"{propertyName} must be greater than or equal to 0."); private static void ThrowMustBeGreaterThanZero(string propertyName) => throw new OrleansConfigurationException($"{propertyName} must be greater than 0."); private static void ThrowMustBeGreaterThanOrEqualTo(string name1, string name2) => throw new OrleansConfigurationException($"{name1} must be greater than or equal to {name2}."); } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/ConsistentRingOptions.cs ================================================ namespace Orleans.Configuration { /// /// Configuration options for consistent hashing algorithm, used to balance resource allocations across the cluster. /// public class ConsistentRingOptions { /// /// Gets or sets the number of registrations a silo maintains in a consistent hash ring. This affects the probabilistic /// balancing of resource allocations across the cluster. More virtual buckets increase the probability of evenly balancing /// while minimally increasing management cost. /// public int NumVirtualBucketsConsistentRing { get; set; } = DEFAULT_NUM_VIRTUAL_RING_BUCKETS; /// /// The default number of virtual ring buckets. /// public const int DEFAULT_NUM_VIRTUAL_RING_BUCKETS = 30; /// /// Gets or sets a value indicating whether to enable the use of virtual buckets. /// public bool UseVirtualBucketsConsistentRing { get; set; } = DEFAULT_USE_VIRTUAL_RING_BUCKETS; /// /// The default value for . /// public const bool DEFAULT_USE_VIRTUAL_RING_BUCKETS = true; } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/DeploymentLoadPublisherOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Options for configuring deployment load publishing. /// public class DeploymentLoadPublisherOptions { /// /// Interval in which deployment statistics are published. /// public TimeSpan DeploymentLoadPublisherRefreshTime { get; set; } = DEFAULT_DEPLOYMENT_LOAD_PUBLISHER_REFRESH_TIME; public static readonly TimeSpan DEFAULT_DEPLOYMENT_LOAD_PUBLISHER_REFRESH_TIME = TimeSpan.FromSeconds(1); } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/GrainCollectionOptions.cs ================================================ using System; using System.Collections.Generic; namespace Orleans.Configuration { /// /// Silo options for idle grain collection. /// public class GrainCollectionOptions { /// /// Regulates the periodic collection of inactive grains. /// public TimeSpan CollectionQuantum { get; set; } = DEFAULT_COLLECTION_QUANTUM; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_COLLECTION_QUANTUM = TimeSpan.FromMinutes(1); /// /// Gets or sets the default period of inactivity necessary for a grain to be available for collection and deactivation. /// public TimeSpan CollectionAge { get; set; } = TimeSpan.FromMinutes(15); /// /// Period of inactivity necessary for a grain to be available for collection and deactivation by grain type. /// public Dictionary ClassSpecificCollectionAge { get; set; } = new Dictionary(); /// /// Timeout value before giving up when trying to activate a grain. /// public TimeSpan ActivationTimeout { get; set; } = DEFAULT_ACTIVATION_TIMEOUT; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_ACTIVATION_TIMEOUT = TimeSpan.FromSeconds(30); /// /// Timeout value before giving up when trying to deactivate a grain activation /// (waiting for all timers to stop and calling Grain.OnDeactivate()) /// public TimeSpan DeactivationTimeout { get; set; } = DEFAULT_DEACTIVATION_TIMEOUT; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_DEACTIVATION_TIMEOUT = TimeSpan.FromSeconds(30); /// /// Indicates if memory pressure should trigger grain activation shedding. /// /// /// If set to true, the silo will shed grain activations when memory usage exceeds the specified limits. /// See , , and for configuration. /// public bool EnableActivationSheddingOnMemoryPressure { get; set; } /// /// The interval at which memory usage is polled. /// public TimeSpan MemoryUsagePollingPeriod { get; set; } = TimeSpan.FromSeconds(5); /// /// The memory usage percentage (0–100) at which grain collection is triggered. /// Must be greater than 0 and less than or equal to 100. /// public double MemoryUsageLimitPercentage { get; set; } = 80; /// /// The target memory usage percentage (0–100) to reach after grain collection. /// Must be greater than 0, less than or equal to 100, and less than . /// public double MemoryUsageTargetPercentage { get; set; } = 75; } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/GrainDirectoryOptions.cs ================================================ using System; using Orleans.Runtime.GrainDirectory; namespace Orleans.Configuration { public class GrainDirectoryOptions { /// /// Configuration type that controls the type of the grain directory caching algorithm that silo use. /// public enum CachingStrategyType { /// Don't cache. None, /// Standard fixed-size LRU. LRU, /// Adaptive caching with fixed maximum size and refresh. This option should be used in production. [Obsolete("Adaptive caching is deprecated in favor of LRU and will be removed in a future version. This value is now an alias for LRU.")] Adaptive, /// Custom cache implementation, configured by registering an implementation in the dependency injection container. Custom } /// /// Gets or sets the caching strategy to use. /// The options are None, which means don't cache directory entries locally; /// LRU, which indicates that a standard fixed-size least recently used strategy should be used; and /// Adaptive, which indicates that an adaptive strategy with a fixed maximum size should be used. /// The LRU strategy is used by default. /// public CachingStrategyType CachingStrategy { get; set; } = DEFAULT_CACHING_STRATEGY; /// /// The default value for . /// public const CachingStrategyType DEFAULT_CACHING_STRATEGY = CachingStrategyType.LRU; /// /// Gets or sets the maximum number of grains to cache directory information for. /// public int CacheSize { get; set; } = DEFAULT_CACHE_SIZE; /// /// The default value for . /// public const int DEFAULT_CACHE_SIZE = 1_000_000; /// /// Gets or sets the initial (minimum) time, in seconds, to keep a cache entry before revalidating. /// [Obsolete("InitialCacheTTL is deprecated and will be removed in a future version.")] public TimeSpan InitialCacheTTL { get; set; } = DEFAULT_INITIAL_CACHE_TTL; /// /// The default value for . /// [Obsolete("DEFAULT_INITIAL_CACHE_TTL is deprecated and will be removed in a future version.")] public static readonly TimeSpan DEFAULT_INITIAL_CACHE_TTL = TimeSpan.FromSeconds(30); /// /// Gets or sets the maximum time, in seconds, to keep a cache entry before revalidating. /// [Obsolete("MaximumCacheTTL is deprecated and will be removed in a future version.")] public TimeSpan MaximumCacheTTL { get; set; } = DEFAULT_MAXIMUM_CACHE_TTL; /// /// The default value for . /// [Obsolete("DEFAULT_MAXIMUM_CACHE_TTL is deprecated and will be removed in a future version.")] public static readonly TimeSpan DEFAULT_MAXIMUM_CACHE_TTL = TimeSpan.FromSeconds(240); /// /// Gets or sets the factor by which cache entry TTLs should be extended when they are found to be stable. /// [Obsolete("CacheTTLExtensionFactor is deprecated and will be removed in a future version.")] public double CacheTTLExtensionFactor { get; set; } = DEFAULT_TTL_EXTENSION_FACTOR; /// /// The default value for . /// [Obsolete("DEFAULT_TTL_EXTENSION_FACTOR is deprecated and will be removed in a future version.")] public const double DEFAULT_TTL_EXTENSION_FACTOR = 2.0; /// /// Gets or sets the time span between when we have added an entry for an activation to the grain directory and when we are allowed /// to conditionally remove that entry. /// Conditional deregistration is used for lazy clean-up of activations whose prompt deregistration failed for some reason (e.g., message failure). /// This should always be at least one minute, since we compare the times on the directory partition, so message delays and clcks skues have /// to be allowed. /// public TimeSpan LazyDeregistrationDelay { get; set; } = DEFAULT_UNREGISTER_RACE_DELAY; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_UNREGISTER_RACE_DELAY = TimeSpan.FromMinutes(1); } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/OptionsLogger.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal class SiloOptionsLogger : OptionsLogger, ILifecycleParticipant { public SiloOptionsLogger(ILogger logger, IServiceProvider services) : base(logger, services) { } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.First, this.OnStart); } public Task OnStart(CancellationToken token) { this.LogOptions(); return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/ResourceOptimizedPlacementOptions.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration; /// /// Settings which regulate the placement of grains across a cluster when using . /// /// All 'weight' properties, are relative to each other. public sealed class ResourceOptimizedPlacementOptions { /// /// The importance of the CPU usage by the silo. /// /// /// A higher value results in the placement favoring silos with lower cpu usage. /// Valid range is [0-100] /// public int CpuUsageWeight { get; set; } = DEFAULT_CPU_USAGE_WEIGHT; /// /// The default value of . /// public const int DEFAULT_CPU_USAGE_WEIGHT = 40; /// /// The importance of the memory usage by the silo. /// /// /// A higher value results in the placement favoring silos with lower memory usage. /// Valid range is [0-100] /// public int MemoryUsageWeight { get; set; } = DEFAULT_MEMORY_USAGE_WEIGHT; /// /// The default value of . /// public const int DEFAULT_MEMORY_USAGE_WEIGHT = 20; /// /// The importance of the available memory to the silo. /// /// /// A higher values results in the placement favoring silos with higher available memory. /// Valid range is [0-100] /// public int AvailableMemoryWeight { get; set; } = DEFAULT_AVAILABLE_MEMORY_WEIGHT; /// /// The default value of . /// public const int DEFAULT_AVAILABLE_MEMORY_WEIGHT = 20; /// /// The importance of the maximum available memory to the silo. /// /// /// A higher values results in the placement favoring silos with higher maximum available memory. /// This may have an impact in clusters with resources distributed unevenly across silos. /// This relates strongly to the physical memory in the silo, and the configured memory limit (if it has been configured). /// Valid range is [0-100] /// public int MaxAvailableMemoryWeight { get; set; } = DEFAULT_MAX_AVAILABLE_MEMORY_WEIGHT; /// /// The default value of . /// public const int DEFAULT_MAX_AVAILABLE_MEMORY_WEIGHT = 5; /// /// The importance of the current activation count to the silo. /// /// /// A higher values results in the placement favoring silos with lower activation count. /// Valid range is [0-100] /// public int ActivationCountWeight { get; set; } = DEFAULT_ACTIVATION_COUNT_WEIGHT; /// /// The default value of . /// public const int DEFAULT_ACTIVATION_COUNT_WEIGHT = 15; /// /// The specified margin for which: if two silos (one of them being the local to the current pending activation), have a utilization score that should be considered "the same" within this margin. /// /// When this value is 0, then the policy will always favor the silo with the lower resource utilization, even if that silo is remote to the current pending activation. /// When this value is 100, then the policy will always favor the local silo, regardless of its relative utilization score. This policy essentially becomes equivalent to . /// /// /// /// Do favor a lower value for this e.g: 5-10 /// Valid range is [0-100] /// public int LocalSiloPreferenceMargin { get; set; } = DEFAULT_LOCAL_SILO_PREFERENCE_MARGIN; /// /// The default value of . /// public const int DEFAULT_LOCAL_SILO_PREFERENCE_MARGIN = 5; } internal sealed class ResourceOptimizedPlacementOptionsValidator (IOptions options) : IConfigurationValidator { private readonly ResourceOptimizedPlacementOptions _options = options.Value; public void ValidateConfiguration() { if (_options.CpuUsageWeight < 0 || _options.CpuUsageWeight > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.CpuUsageWeight)); } if (_options.MemoryUsageWeight < 0 || _options.MemoryUsageWeight > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.MemoryUsageWeight)); } if (_options.AvailableMemoryWeight < 0 || _options.AvailableMemoryWeight > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.AvailableMemoryWeight)); } if (_options.MaxAvailableMemoryWeight < 0 || _options.MaxAvailableMemoryWeight > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.MaxAvailableMemoryWeight)); } if (_options.ActivationCountWeight < 0 || _options.ActivationCountWeight > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.ActivationCountWeight)); } if (_options.LocalSiloPreferenceMargin < 0 || _options.LocalSiloPreferenceMargin > 100) { ThrowOutOfRange(nameof(ResourceOptimizedPlacementOptions.LocalSiloPreferenceMargin)); } static void ThrowOutOfRange(string propertyName) => throw new OrleansConfigurationException($"{propertyName} must be inclusive between [0-100]"); } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/SchedulingOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Options for configuring scheduler behavior. /// public class SchedulingOptions { /// /// Gets or sets the work item queuing delay threshold, at which a warning log message is written. /// That is, if the delay between enqueuing the work item and executing the work item is greater than DelayWarningThreshold, a warning log is written. /// public TimeSpan DelayWarningThreshold { get; set; } = DEFAULT_DELAY_WARNING_THRESHOLD; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_DELAY_WARNING_THRESHOLD = TimeSpan.FromMilliseconds(10000); // 10 seconds /// /// Gets or sets the soft time limit on the duration of activation macro-turn (a number of micro-turns). /// If an activation was running its micro-turns longer than this, we will give up the thread. /// If this is set to zero or a negative number, then the full work queue is drained (MaxWorkItemsPerTurn allowing). /// public TimeSpan ActivationSchedulingQuantum { get; set; } = DEFAULT_ACTIVATION_SCHEDULING_QUANTUM; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_ACTIVATION_SCHEDULING_QUANTUM = TimeSpan.FromMilliseconds(100); /// /// Gets or sets the soft time limit to generate trace warning when the micro-turn executes longer then this period in CPU. /// public TimeSpan TurnWarningLengthThreshold { get; set; } = DEFAULT_TURN_WARNING_THRESHOLD; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_TURN_WARNING_THRESHOLD = TimeSpan.FromMilliseconds(1_000); /// /// Gets or sets the per work group limit of how many items can be queued up before warnings are generated. /// public int MaxPendingWorkItemsSoftLimit { get; set; } = DEFAULT_MAX_PENDING_ITEMS_SOFT_LIMIT; /// /// The default value for . /// public const int DEFAULT_MAX_PENDING_ITEMS_SOFT_LIMIT = 0; /// /// Gets or sets the period of time after which to log errors for tasks scheduled to stopped activations. /// public TimeSpan StoppedActivationWarningInterval { get; set; } = TimeSpan.FromMinutes(1); } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/SiloMessagingOptions.cs ================================================ using System; using System.Diagnostics; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Specifies global messaging options that are silo related. /// public class SiloMessagingOptions : MessagingOptions { /// /// . /// private TimeSpan systemResponseTimeout = TimeSpan.FromSeconds(30); /// /// Gets or sets the number of parallel queues and attendant threads used by the silo to send outbound /// messages (requests, responses, and notifications) to other silos. /// If this attribute is not specified, then System.Environment.ProcessorCount is used. /// public int SiloSenderQueues { get; set; } /// /// Gets or sets the number of parallel queues and attendant threads used by the silo Gateway to send outbound /// messages (requests, responses, and notifications) to clients that are connected to it. /// If this attribute is not specified, then System.Environment.ProcessorCount is used. /// public int GatewaySenderQueues { get; set; } /// /// Gets or sets the maximal number of times a message is being forwarded from one silo to another. /// Forwarding is used internally by the runtime as a recovery mechanism when silos fail and the membership is unstable. /// In such times the messages might not be routed correctly to destination, and runtime attempts to forward such messages a number of times before rejecting them. /// public int MaxForwardCount { get; set; } = 2; /// /// Gets or sets the period of time a gateway will wait before dropping a disconnected client. /// public TimeSpan ClientDropTimeout { get; set; } = Constants.DEFAULT_CLIENT_DROP_TIMEOUT; /// /// Gets or sets the interval in which the list of connected clients is refreshed. /// public TimeSpan ClientRegistrationRefresh { get; set; } = DEFAULT_CLIENT_REGISTRATION_REFRESH; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_CLIENT_REGISTRATION_REFRESH = TimeSpan.FromMinutes(5); /// /// Gets or sets the period of time a gateway will wait after notifying connected client before continuing the /// shutdown process /// public TimeSpan ClientGatewayShutdownNotificationTimeout { get; set; } = DEFAULT_CLIENT_GW_NOTIFICATION_TIMEOUT; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_CLIENT_GW_NOTIFICATION_TIMEOUT = TimeSpan.Zero; /// /// Gets or sets the per grain threshold for pending requests. Generated warning when exceeded. /// public int MaxEnqueuedRequestsSoftLimit { get; set; } = DEFAULT_MAX_ENQUEUED_REQUESTS_SOFT_LIMIT; /// /// The default value for . /// public const int DEFAULT_MAX_ENQUEUED_REQUESTS_SOFT_LIMIT = 0; /// /// Gets or sets the per grain threshold for pending requests. Requests are rejected when exceeded. /// public int MaxEnqueuedRequestsHardLimit { get; set; } = DEFAULT_MAX_ENQUEUED_REQUESTS_HARD_LIMIT; /// /// The default value for . /// public const int DEFAULT_MAX_ENQUEUED_REQUESTS_HARD_LIMIT = 0; /// /// Gets or sets the per grain threshold for pending requests for stateless workers. Generated warning when exceeded. /// public int MaxEnqueuedRequestsSoftLimit_StatelessWorker { get; set; } = DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_SOFT_LIMIT; /// /// The default value for . /// public const int DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_SOFT_LIMIT = 0; /// /// Gets or sets the per grain threshold for pending requests for stateless workers. Requests are rejected when exceeded. /// public int MaxEnqueuedRequestsHardLimit_StatelessWorker { get; set; } = DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_HARD_LIMIT; /// /// The default value for . /// public const int DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_HARD_LIMIT = 0; /// /// Gets or sets the maximum time that a request can take before the activation is reported as "blocked" /// public TimeSpan MaxRequestProcessingTime { get; set; } = DEFAULT_MAX_REQUEST_PROCESSING_TIME; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_MAX_REQUEST_PROCESSING_TIME = TimeSpan.FromHours(2); /// /// Gets or sets a value indicating whether it is assumed that all hosts are identical in terms of the grain interfaces and classes which they support. /// /// /// For testing purposes only. /// public bool AssumeHomogenousSilosForTesting { get; set; } = false; /// /// Gets or sets the period of time the silo will wait to reroute queued messages before it continues shutting down. /// [Obsolete("Unused, will be removed in a future version.")] public TimeSpan ShutdownRerouteTimeout { get; set; } = DEFAULT_SHUTDOWN_REROUTE_TIMEOUT; /// /// The default value for . /// [Obsolete("Unused, will be removed in a future version.")] public static readonly TimeSpan DEFAULT_SHUTDOWN_REROUTE_TIMEOUT = TimeSpan.Zero; /// /// Gets or sets the default timeout before an internal system request is assumed to have failed. /// public TimeSpan SystemResponseTimeout { get { return Debugger.IsAttached ? ResponseTimeoutWithDebugger : this.systemResponseTimeout; } set { this.systemResponseTimeout = value; } } /// /// Gets or sets the period of time between analyzing currently executing activation workloads. /// public TimeSpan GrainWorkloadAnalysisPeriod { get; set; } = TimeSpan.FromSeconds(5); /// /// Gets or sets the period after which a currently executing request is deemed to be slow. /// /// /// This should be set to a value at least shorter than /// so that there is sufficient time for a long-running request to be detected and a status message to be sent to the client before the client times out. /// public TimeSpan RequestProcessingWarningTime { get; set; } = TimeSpan.FromSeconds(20); /// /// Gets or sets the period after which an enqueued request is deemed to be delayed. /// /// /// This should be set to a value at least shorter than /// so that there is sufficient time for a delayed request to be detected and a status message to be sent to the client before the client times out. /// public TimeSpan RequestQueueDelayWarningTime { get; set; } = TimeSpan.FromSeconds(20); /// /// Gets or sets the time to wait for all queued message sent to OutboundMessageQueue before MessageCenter stop and OutboundMessageQueue stop. /// public TimeSpan WaitForMessageToBeQueuedForOutboundTime { get; set; } = DEFAULT_WAIT_FOR_MESSAGE_TO_BE_QUEUED_FOR_OUTBOUND_TIME; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_WAIT_FOR_MESSAGE_TO_BE_QUEUED_FOR_OUTBOUND_TIME = TimeSpan.Zero; } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/SiloMessagingOptionsValidator.cs ================================================ using Microsoft.Extensions.Options; namespace Orleans.Configuration { internal class SiloMessagingOptionsValidator : IValidateOptions { public ValidateOptionsResult Validate(string name, SiloMessagingOptions options) { if (options.MaxForwardCount > 255) { return ValidateOptionsResult.Fail($"Value for {nameof(SiloMessagingOptions)}.{nameof(SiloMessagingOptions.MaxForwardCount)} must not be greater than 255."); } return ValidateOptionsResult.Success; } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Options/StatelessWorkerOptions.cs ================================================ using System; namespace Orleans.Configuration; /// /// Options that apply globally to stateless worker grains. /// public class StatelessWorkerOptions { /// /// When set to , idle workers will be proactively deactivated by the runtime. /// Otherwise if , than the workers will be deactivated according to . /// /// You can read more on this here public bool RemoveIdleWorkers { get; set; } = DEFAULT_REMOVE_IDLE_WORKERS; /// /// The default value for . /// public const bool DEFAULT_REMOVE_IDLE_WORKERS = true; /// /// The time to inspect for idle workers. /// /// This setting has no effect if is . public TimeSpan IdleWorkersInspectionPeriod { get; set; } = DEFAULT_IDLE_WORKERS_INSPECTION_PERIOD; /// /// The default value for . /// public static readonly TimeSpan DEFAULT_IDLE_WORKERS_INSPECTION_PERIOD = TimeSpan.FromMilliseconds(500); /// /// The minimum, consecutive number of idle cycles any given worker must exhibit before it is deemed enough to remove the worker. /// public int MinIdleCyclesBeforeRemoval { get; set; } = DEFAULT_MIN_IDLE_CYCLES_BEFORE_REMOVAL; /// /// The default value for . /// public const int DEFAULT_MIN_IDLE_CYCLES_BEFORE_REMOVAL = 1; } ================================================ FILE: src/Orleans.Runtime/Configuration/SiloConnectionOptions.cs ================================================ using System; using Microsoft.AspNetCore.Connections; namespace Orleans.Configuration { /// /// Options for configuring silo networking. /// Implements the /// /// public class SiloConnectionOptions : SiloConnectionOptions.ISiloConnectionBuilderOptions { private readonly ConnectionBuilderDelegates siloOutboundDelegates = new ConnectionBuilderDelegates(); private readonly ConnectionBuilderDelegates siloInboundDelegates = new ConnectionBuilderDelegates(); private readonly ConnectionBuilderDelegates gatewayInboundDelegates = new ConnectionBuilderDelegates(); /// /// Configures silo outbound connections. /// /// The configuration delegate. public void ConfigureSiloOutboundConnection(Action configure) => this.siloOutboundDelegates.Add(configure); /// /// Configures silo inbound connections from other silos. /// /// The configuration delegate. public void ConfigureSiloInboundConnection(Action configure) => this.siloInboundDelegates.Add(configure); /// /// Configures silo inbound connections from clients. /// /// The configuration delegate. public void ConfigureGatewayInboundConnection(Action configure) => this.gatewayInboundDelegates.Add(configure); /// void ISiloConnectionBuilderOptions.ConfigureSiloOutboundBuilder(IConnectionBuilder builder) => this.siloOutboundDelegates.Invoke(builder); /// void ISiloConnectionBuilderOptions.ConfigureSiloInboundBuilder(IConnectionBuilder builder) => this.siloInboundDelegates.Invoke(builder); /// void ISiloConnectionBuilderOptions.ConfigureGatewayInboundBuilder(IConnectionBuilder builder) => this.gatewayInboundDelegates.Invoke(builder); /// /// Options for silo networking. /// public interface ISiloConnectionBuilderOptions { /// /// Configures the silo outbound connection builder. /// /// The builder. public void ConfigureSiloOutboundBuilder(IConnectionBuilder builder); /// /// Configures the silo inbound connection builder. /// /// The builder. public void ConfigureSiloInboundBuilder(IConnectionBuilder builder); /// /// Configures the silo gateway connection builder. /// /// The builder. public void ConfigureGatewayInboundBuilder(IConnectionBuilder builder); } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Validators/GrainCollectionOptionsValidator.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Runtime; namespace Orleans.Configuration { internal class GrainCollectionOptionsValidator : IConfigurationValidator { private readonly GrainCollectionOptions options; public GrainCollectionOptionsValidator(IOptions options) { this.options = options.Value; } public void ValidateConfiguration() { if (this.options.CollectionQuantum <= TimeSpan.Zero) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.CollectionQuantum)} is set to {options.CollectionQuantum}. " + $"{nameof(GrainCollectionOptions.CollectionQuantum)} must be greater than 0"); } if (this.options.CollectionAge <= this.options.CollectionQuantum) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.CollectionAge)} is set to {options.CollectionAge}. " + $"{nameof(GrainCollectionOptions.CollectionAge)} must be greater than {nameof(GrainCollectionOptions.CollectionQuantum)}, " + $"which is set to {this.options.CollectionQuantum}"); } foreach(var classSpecificCollectionAge in this.options.ClassSpecificCollectionAge) { if (classSpecificCollectionAge.Value <= this.options.CollectionQuantum) { throw new OrleansConfigurationException( $"{classSpecificCollectionAge.Key} CollectionAgeLimit is set to {classSpecificCollectionAge.Value}. " + $"CollectionAgeLimit must be greater than {nameof(GrainCollectionOptions.CollectionQuantum)}, " + $"which is set to {this.options.CollectionQuantum}"); } } ValidateHighMemoryPressureSettings(); } private void ValidateHighMemoryPressureSettings() { if (!options.EnableActivationSheddingOnMemoryPressure) { return; } if (options.MemoryUsagePollingPeriod <= TimeSpan.Zero) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.MemoryUsagePollingPeriod)} is set to {options.MemoryUsagePollingPeriod}. " + $"{nameof(GrainCollectionOptions.MemoryUsagePollingPeriod)} must be greater than 0"); } if (options.MemoryUsageLimitPercentage < 0 || options.MemoryUsageLimitPercentage > 100) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.MemoryUsageLimitPercentage)} is set to {options.MemoryUsageLimitPercentage}. " + $"{nameof(GrainCollectionOptions.MemoryUsageLimitPercentage)} must be between 0 and 100"); } if (options.MemoryUsageTargetPercentage < 0 || options.MemoryUsageTargetPercentage > 100) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.MemoryUsageTargetPercentage)} is set to {options.MemoryUsageTargetPercentage}. " + $"{nameof(GrainCollectionOptions.MemoryUsageTargetPercentage)} must be between 0 and 100"); } if (options.MemoryUsageTargetPercentage >= options.MemoryUsageLimitPercentage) { throw new OrleansConfigurationException( $"{nameof(GrainCollectionOptions.MemoryUsageTargetPercentage)} is set to {options.MemoryUsageTargetPercentage}. " + $"{nameof(GrainCollectionOptions.MemoryUsageTargetPercentage)} must be less than {nameof(GrainCollectionOptions.MemoryUsageLimitPercentage)}, " + $"which is set to {options.MemoryUsageLimitPercentage}"); } } } } ================================================ FILE: src/Orleans.Runtime/Configuration/Validators/SiloClusteringValidator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Validators; namespace Orleans.Runtime.Configuration { /// /// Validates basic cluster membership configuration. /// internal class SiloClusteringValidator : IConfigurationValidator { private readonly IServiceProvider serviceProvider; public SiloClusteringValidator(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } /// public void ValidateConfiguration() { var clusteringTableProvider = this.serviceProvider.GetService(); if (clusteringTableProvider == null) { throw new OrleansConfigurationException(ClientClusteringValidator.ClusteringNotConfigured); } var clusterMembershipOptions = this.serviceProvider.GetRequiredService>().Value; if (clusterMembershipOptions.LivenessEnabled) { if (clusterMembershipOptions.NumVotesForDeathDeclaration > clusterMembershipOptions.NumProbedSilos) { throw new OrleansConfigurationException($"{nameof(ClusterMembershipOptions)}.{nameof(ClusterMembershipOptions.NumVotesForDeathDeclaration)} ({clusterMembershipOptions.NumVotesForDeathDeclaration}) must be less than or equal to {nameof(ClusterMembershipOptions)}.{nameof(ClusterMembershipOptions.NumProbedSilos)} ({clusterMembershipOptions.NumProbedSilos})."); } if (clusterMembershipOptions.NumVotesForDeathDeclaration <= 0) { throw new OrleansConfigurationException($"{nameof(ClusterMembershipOptions)}.{nameof(ClusterMembershipOptions.NumVotesForDeathDeclaration)} ({clusterMembershipOptions.NumVotesForDeathDeclaration}) must be greater than 0."); } } } } } ================================================ FILE: src/Orleans.Runtime/ConsistentRing/ConsistentRingProvider.cs ================================================ using System; using System.Collections.Generic; using System.Text; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.ConsistentRing { /// /// We use the 'backward/clockwise' definition to assign responsibilities on the ring. /// E.g. in a ring of nodes {5, 10, 15} the responsible for key 7 is 10 (the node is responsible for its preceding range). /// The backwards/clockwise approach is consistent with many overlays, e.g., Chord, Cassandra, etc. /// Note: MembershipOracle uses 'forward/counter-clockwise' definition to assign responsibilities. /// E.g. in a ring of nodes {5, 10, 15}, the responsible of key 7 is node 5 (the node is responsible for its succeeding range). /// internal sealed partial class ConsistentRingProvider : IConsistentRingProvider, ISiloStatusListener, IDisposable { // internal, so that unit tests can access them internal SiloAddress MyAddress { get; } private IRingRange myRange; /// list of silo members sorted by the hash value of their address private readonly List membershipRingList = new(); private readonly ILogger log; private bool isRunning; private readonly int myKey; private readonly List statusListeners = new(); private readonly ISiloStatusOracle _siloStatusOracle; private (IRingRange OldRange, IRingRange NewRange, bool Increased) lastNotification; public ConsistentRingProvider(SiloAddress siloAddr, ILoggerFactory loggerFactory, ISiloStatusOracle siloStatusOracle) { log = loggerFactory.CreateLogger(); MyAddress = siloAddr; _siloStatusOracle = siloStatusOracle; myKey = MyAddress.GetConsistentHashCode(); myRange = RangeFactory.CreateFullRange(); // i am responsible for the whole range lastNotification = (myRange, myRange, true); // add myself to the list of members AddServer(MyAddress); Start(); siloStatusOracle.SubscribeToSiloStatusEvents(this); } /// /// Returns the silo that this silo thinks is the primary owner of the key /// /// /// public SiloAddress GetPrimaryTargetSilo(uint key) { return CalculateTargetSilo(key); } public IRingRange GetMyRange() { return myRange; // its immutable, so no need to clone } private void Start() { isRunning = true; } private void Stop() { isRunning = false; } internal void AddServer(SiloAddress silo) { lock (membershipRingList) { if (membershipRingList.Contains(silo)) return; // we already have this silo int myOldIndex = membershipRingList.IndexOf(MyAddress); if (!(membershipRingList.Count == 0 || myOldIndex != -1)) throw new OrleansException(string.Format("{0}: Couldn't find my position in the ring {1}.", MyAddress, Utils.EnumerableToString(membershipRingList))); // insert new silo in the sorted order int hash = silo.GetConsistentHashCode(); // Find the last silo with hash smaller than the new silo, and insert the latter after (this is why we have +1 here) the former. // Notice that FindLastIndex might return -1 if this should be the first silo in the list, but then // 'index' will get 0, as needed. int index = membershipRingList.FindLastIndex(siloAddr => siloAddr.GetConsistentHashCode() < hash) + 1; membershipRingList.Insert(index, silo); // relating to triggering handler ... new node took over some of my responsibility if (index == myOldIndex || // new node was inserted in my place (myOldIndex == 0 && index == membershipRingList.Count - 1)) // I am the first node, and the new server is the last node { IRingRange oldRange = myRange; myRange = RangeFactory.CreateRange(unchecked((uint)hash), unchecked((uint)myKey)); NotifyLocalRangeSubscribers(oldRange, myRange, false); } LogDebugAddedServer(log, new(silo), this); } } public override string ToString() { lock (membershipRingList) { if (membershipRingList.Count == 1) return $"[{membershipRingList[0]:H} -> {RangeFactory.CreateFullRange()}]"; var sb = new StringBuilder().Append('['); for (int i = 0; i < membershipRingList.Count; i++) { SiloAddress curr = membershipRingList[i]; SiloAddress next = membershipRingList[(i + 1) % membershipRingList.Count]; IRingRange range = RangeFactory.CreateRange(unchecked((uint)curr.GetConsistentHashCode()), unchecked((uint)next.GetConsistentHashCode())); sb.Append($"{curr:H} -> {range}, "); } return sb.Append(']').ToString(); } } internal void RemoveServer(SiloAddress silo) { lock (membershipRingList) { int indexOfFailedSilo = membershipRingList.IndexOf(silo); if (indexOfFailedSilo < 0) return; // we have already removed this silo membershipRingList.RemoveAt(indexOfFailedSilo); // related to triggering handler int myNewIndex = membershipRingList.IndexOf(MyAddress); if (myNewIndex == -1) throw new OrleansException($"{MyAddress}: Couldn't find my position in the ring {this}."); bool wasMyPred = ((myNewIndex == indexOfFailedSilo) || (myNewIndex == 0 && indexOfFailedSilo == membershipRingList.Count)); // no need for '- 1' if (wasMyPred) // failed node was our predecessor { LogDebugFailedServerWasMyPredecessor(log, wasMyPred, this); IRingRange oldRange = myRange; if (membershipRingList.Count == 1) // i'm the only one left { myRange = RangeFactory.CreateFullRange(); NotifyLocalRangeSubscribers(oldRange, myRange, true); } else { int myNewPredIndex = myNewIndex == 0 ? membershipRingList.Count - 1 : myNewIndex - 1; int myPredecessorsHash = membershipRingList[myNewPredIndex].GetConsistentHashCode(); myRange = RangeFactory.CreateRange(unchecked((uint)myPredecessorsHash), unchecked((uint)myKey)); NotifyLocalRangeSubscribers(oldRange, myRange, true); } } LogDebugRemovedServer(log, silo, new(silo), this); } } public bool SubscribeToRangeChangeEvents(IRingRangeListener observer) { (IRingRange OldRange, IRingRange NewRange, bool Increased) notification; lock (statusListeners) { if (statusListeners.Contains(observer)) return false; statusListeners.Add(observer); notification = lastNotification; } observer.RangeChangeNotification(notification.OldRange, notification.NewRange, notification.Increased); return true; } public bool UnSubscribeFromRangeChangeEvents(IRingRangeListener observer) { lock (statusListeners) { return statusListeners.Remove(observer); } } private void NotifyLocalRangeSubscribers(IRingRange old, IRingRange now, bool increased) { LogDebugNotifyLocalRangeSubscribers(log, old, now, increased); IRingRangeListener[] copy; lock (statusListeners) { lastNotification = (old, now, increased); copy = statusListeners.ToArray(); } foreach (IRingRangeListener listener in copy) { try { listener.RangeChangeNotification(old, now, increased); } catch (Exception exc) { LogWarningErrorNotifyingListener(log, exc, listener.GetType().FullName, increased ? "expansion" : "contraction", old, now); } } } public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { // This silo's status has changed if (updatedSilo.Equals(MyAddress)) { if (status.IsTerminating()) { Stop(); } } else // Status change for some other silo { if (status.IsTerminating()) { RemoveServer(updatedSilo); } else if (status == SiloStatus.Active) // do not do anything with SiloStatus.Created or SiloStatus.Joining -- wait until it actually becomes active { AddServer(updatedSilo); } } } /// /// Finds the silo that owns the given hash value. /// This routine will always return a non-null silo address unless the excludeThisSiloIfStopping parameter is true, /// this is the only silo known, and this silo is stopping. /// /// /// /// private SiloAddress CalculateTargetSilo(uint hash, bool excludeThisSiloIfStopping = true) { SiloAddress siloAddress = null; lock (membershipRingList) { // excludeMySelf from being a TargetSilo if we're not running and the excludeThisSIloIfStopping flag is true. see the comment in the Stop method. bool excludeMySelf = excludeThisSiloIfStopping && !isRunning; if (membershipRingList.Count == 0) { // If the membership ring is empty, then we're the owner by default unless we're stopping. return excludeMySelf ? null : MyAddress; } // use clockwise ... current code in membershipOracle.CalculateTargetSilo() does counter-clockwise ... // if you want to stick to counter-clockwise, change the responsibility definition in 'In()' method & responsibility defs in OrleansReminderMemory // need to implement a binary search, but for now simply traverse the list of silos sorted by their hashes for (int index = 0; index < membershipRingList.Count; ++index) { var siloAddr = membershipRingList[index]; if (IsSiloNextInTheRing(siloAddr, hash, excludeMySelf)) { siloAddress = siloAddr; break; } } if (siloAddress == null) { // if not found in traversal, then first silo should be returned (we are on a ring) // if you go back to their counter-clockwise policy, then change the 'In()' method in OrleansReminderMemory siloAddress = membershipRingList[0]; // vs [membershipRingList.Count - 1]; for counter-clockwise policy // Make sure it's not us... if (siloAddress.Equals(MyAddress) && excludeMySelf) { // vs [membershipRingList.Count - 2]; for counter-clockwise policy siloAddress = membershipRingList.Count > 1 ? membershipRingList[1] : null; } } } LogTraceCalculatedRingPartitionOwner(log, MyAddress, siloAddress, hash, new(siloAddress)); return siloAddress; } private bool IsSiloNextInTheRing(SiloAddress siloAddr, uint hash, bool excludeMySelf) { return siloAddr.GetConsistentHashCode() >= hash && (!siloAddr.Equals(MyAddress) || !excludeMySelf); } public void Dispose() { _siloStatusOracle.UnSubscribeFromSiloStatusEvents(this); } private readonly struct SiloAddressWithHashLogRecord(SiloAddress siloAddress) { public override string ToString() => siloAddress.ToStringWithHashCode(); } private readonly struct ConsistentHashCodeLogRecord(SiloAddress siloAddress) { public override string ToString() => siloAddress?.GetConsistentHashCode().ToString(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Added Server {SiloAddress}. Current view: {CurrentView}" )] private static partial void LogDebugAddedServer(ILogger logger, SiloAddressWithHashLogRecord siloAddress, ConsistentRingProvider currentView); [LoggerMessage( Level = LogLevel.Debug, Message = "Failed server was my predecessor? {WasPredecessor}, updated view {CurrentView}" )] private static partial void LogDebugFailedServerWasMyPredecessor(ILogger logger, bool wasPredecessor, ConsistentRingProvider currentView); [LoggerMessage( Level = LogLevel.Debug, Message = "Removed Server {SiloAddress} hash {Hash}. Current view {CurrentView}" )] private static partial void LogDebugRemovedServer(ILogger logger, SiloAddress siloAddress, SiloAddressWithHashLogRecord hash, ConsistentRingProvider currentView); [LoggerMessage( Level = LogLevel.Debug, Message = "NotifyLocalRangeSubscribers about old {OldRange} new {NewRange} increased? {IsIncreased}" )] private static partial void LogDebugNotifyLocalRangeSubscribers(ILogger logger, IRingRange oldRange, IRingRange newRange, bool isIncreased); [LoggerMessage( EventId = (int)ErrorCode.CRP_Local_Subscriber_Exception, Level = LogLevel.Warning, Message = "Error notifying listener '{ListenerType}' of ring range {AdjustmentKind} from '{OldRange}' to '{NewRange}'." )] private static partial void LogWarningErrorNotifyingListener(ILogger logger, Exception exception, string listenerType, string adjustmentKind, IRingRange oldRange, IRingRange newRange); [LoggerMessage( Level = LogLevel.Trace, Message = "Silo {SiloAddress} calculated ring partition owner silo {OwnerAddress} for key {Key}: {Key} --> {OwnerHash}" )] private static partial void LogTraceCalculatedRingPartitionOwner(ILogger logger, SiloAddress siloAddress, SiloAddress ownerAddress, uint key, ConsistentHashCodeLogRecord ownerHash); } } ================================================ FILE: src/Orleans.Runtime/ConsistentRing/IConsistentRingProvider.cs ================================================ namespace Orleans.Runtime.ConsistentRing { // someday, this will be the only provider for the ring, i.e., directory service will use this internal interface IConsistentRingProvider { /// /// Get the responsibility range of the current silo /// /// IRingRange GetMyRange(); // the following two are similar to the ISiloStatusOracle interface ... this replaces the 'OnRangeChanged' because OnRangeChanged only supports one subscription /// /// Subscribe to receive range change notifications /// /// An observer interface to receive range change notifications. /// bool value indicating that subscription succeeded or not. bool SubscribeToRangeChangeEvents(IRingRangeListener observer); /// /// Unsubscribe from receiving range change notifications /// /// An observer interface to receive range change notifications. /// bool value indicating that unsubscription succeeded or not bool UnSubscribeFromRangeChangeEvents(IRingRangeListener observer); /// /// Get the silo responsible for according to consistent hashing /// /// /// SiloAddress GetPrimaryTargetSilo(uint key); } // similar to ISiloStatusListener internal interface IRingRangeListener { void RangeChangeNotification(IRingRange old, IRingRange now, bool increased); } } ================================================ FILE: src/Orleans.Runtime/ConsistentRing/SimpleConsistentRingProvider.cs ================================================ using System.Threading; namespace Orleans.Runtime { /// /// Aids in the construction of a consistent hash ring by maintaining an up-to-date reference to the next silo in the ring. /// internal class SimpleConsistentRingProvider { private readonly SiloAddress _localSilo; private readonly IClusterMembershipService _clusterMembershipService; #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private VersionedSuccessor _successor = new VersionedSuccessor(MembershipVersion.MinValue, null); public SimpleConsistentRingProvider(ILocalSiloDetails localSiloDetails, IClusterMembershipService clusterMembershipService) { _localSilo = localSiloDetails.SiloAddress; _clusterMembershipService = clusterMembershipService; FindSuccessor(_clusterMembershipService.CurrentSnapshot); } /// /// Gets the of the active silo with the smallest consistent hash code value which is larger /// than this silo's, or if no such silo exists, then the active silo with the absolute smallest consistent hash code, /// or if there are no other active silos in the cluster. /// public SiloAddress Successor { get { var snapshot = _clusterMembershipService.CurrentSnapshot; var (successorVersion, successor) = _successor; if (successorVersion < snapshot.Version) { lock (_lockObj) { successor = FindSuccessor(snapshot); _successor = new VersionedSuccessor(snapshot.Version, successor); } } return successor; } } private SiloAddress FindSuccessor(ClusterMembershipSnapshot snapshot) { var (successorVersion, successor) = _successor; if (successorVersion >= snapshot.Version) { return successor; } // Find the silo with the smallest hashcode which is larger than this silo's. (SiloAddress Silo, int HashCode) firstInRing = (default(SiloAddress), int.MaxValue); (SiloAddress Silo, int HashCode) candidate = (default(SiloAddress), int.MaxValue); var localSiloHashCode = _localSilo.GetConsistentHashCode(); foreach (var member in snapshot.Members.Values) { if (member.SiloAddress.Equals(_localSilo)) { continue; } if (member.Status != SiloStatus.Active) { continue; } var memberHashCode = member.SiloAddress.GetConsistentHashCode(); // It is possible that the local silo is last in the ring, therefore we also find the first silo in the ring, // which would be the local silo's successor in that case. if (memberHashCode < firstInRing.HashCode) { firstInRing = (member.SiloAddress, memberHashCode); } // This member comes before this silo in the ring, but is not the first in the ring. if (memberHashCode < localSiloHashCode) { continue; } // This member comes after this silo in the ring, but before the current candidate. // Therefore, this member is the new candidate. if (memberHashCode < candidate.HashCode) { candidate = (member.SiloAddress, memberHashCode); } } // The result is either the silo with the smallest hashcode that is larger than this silo's, // or the first silo in the ring, or null in the case that there are no other active silos. successor = candidate.Silo ?? firstInRing.Silo; return successor; } private sealed class VersionedSuccessor { public VersionedSuccessor(MembershipVersion membershipVersion, SiloAddress siloAddress) { MembershipVersion = membershipVersion; SiloAddress = siloAddress; } public void Deconstruct(out MembershipVersion version, out SiloAddress siloAddress) { version = MembershipVersion; siloAddress = SiloAddress; } public MembershipVersion MembershipVersion { get; } public SiloAddress SiloAddress { get; } } } } ================================================ FILE: src/Orleans.Runtime/ConsistentRing/VirtualBucketsRingProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.ConsistentRing { /// /// We use the 'backward/clockwise' definition to assign responsibilities on the ring. /// E.g. in a ring of nodes {5, 10, 15} the responsible for key 7 is 10 (the node is responsible for its preceding range). /// The backwards/clockwise approach is consistent with many overlays, e.g., Chord, Cassandra, etc. /// Note: MembershipOracle uses 'forward/counter-clockwise' definition to assign responsibilities. /// E.g. in a ring of nodes {5, 10, 15}, the responsible of key 7 is node 5 (the node is responsible for its succeeding range). /// internal sealed partial class VirtualBucketsRingProvider : IConsistentRingProvider, ISiloStatusListener, IDisposable { private readonly List statusListeners = new(); private readonly SortedDictionary bucketsMap = new(); private (uint Hash, SiloAddress SiloAddress)[] sortedBucketsList; // flattened sorted bucket list for fast lock-free calculation of CalculateTargetSilo private readonly ILogger logger; private readonly SiloAddress myAddress; private readonly int numBucketsPerSilo; private readonly ISiloStatusOracle _siloStatusOracle; private bool running; private IRingRange myRange; private (IRingRange OldRange, IRingRange NewRange, bool Increased) lastNotification; internal VirtualBucketsRingProvider(SiloAddress siloAddress, ILoggerFactory loggerFactory, int numVirtualBuckets, ISiloStatusOracle siloStatusOracle) { numBucketsPerSilo = numVirtualBuckets; _siloStatusOracle = siloStatusOracle; if (numBucketsPerSilo <= 0) throw new IndexOutOfRangeException($"numBucketsPerSilo is out of the range. numBucketsPerSilo = {numBucketsPerSilo}"); logger = loggerFactory.CreateLogger(); myAddress = siloAddress; running = true; myRange = RangeFactory.CreateFullRange(); lastNotification = (myRange, myRange, true); LogDebugStarting(logger, nameof(VirtualBucketsRingProvider), new(siloAddress)); ConsistentRingInstruments.RegisterRingSizeObserve(() => GetRingSize()); ConsistentRingInstruments.RegisterMyRangeRingPercentageObserve(() => (float)((IRingRangeInternal)myRange).RangePercentage()); ConsistentRingInstruments.RegisterAverageRingPercentageObserve(() => { int size = GetRingSize(); return size == 0 ? 0 : ((float)100.0 / size); }); // add myself to the list of members AddServer(myAddress); siloStatusOracle.SubscribeToSiloStatusEvents(this); } private void Stop() { running = false; } public IRingRange GetMyRange() { return myRange; } private int GetRingSize() { lock (bucketsMap) { return bucketsMap.Values.Distinct().Count(); } } public bool SubscribeToRangeChangeEvents(IRingRangeListener observer) { (IRingRange OldRange, IRingRange NewRange, bool Increased) notification; lock (statusListeners) { if (statusListeners.Contains(observer)) return false; notification = lastNotification; statusListeners.Add(observer); } observer.RangeChangeNotification(notification.OldRange, notification.NewRange, notification.Increased); return true; } public bool UnSubscribeFromRangeChangeEvents(IRingRangeListener observer) { lock (statusListeners) { return statusListeners.Remove(observer); } } private void NotifyLocalRangeSubscribers(IRingRange old, IRingRange now, bool increased) { LogTraceNotifyLocalRangeSubscribers(logger, old, now, increased); IRingRangeListener[] copy; lock (statusListeners) { lastNotification = (old, now, increased); copy = statusListeners.ToArray(); } foreach (IRingRangeListener listener in copy) { try { listener.RangeChangeNotification(old, now, increased); } catch (Exception exc) { LogWarningErrorNotifyingListener(logger, exc, listener.GetType().FullName, increased ? "expansion" : "contraction", old, now); } } } private void AddServer(SiloAddress silo) { var hashes = silo.GetUniformHashCodes(numBucketsPerSilo); lock (bucketsMap) { foreach (var hash in hashes) { if (bucketsMap.TryGetValue(hash, out var other)) { // If two silos conflict, take the lesser of the two (usually the older one; that is, the lower epoch) if (silo.CompareTo(other) > 0) continue; } bucketsMap[hash] = silo; } var myOldRange = myRange; var myNewRange = UpdateRange(); LogTraceAddedServer(logger, new(silo), this); NotifyLocalRangeSubscribers(myOldRange, myNewRange, true); } } private void RemoveServer(SiloAddress silo) { lock (bucketsMap) { if (!bucketsMap.ContainsValue(silo)) return; // we have already removed this silo var hashes = silo.GetUniformHashCodes(numBucketsPerSilo); foreach (var hash in hashes) { if (bucketsMap.Remove(hash, out var removedSilo) && !removedSilo.Equals(silo)) { // since hash collisions are very rare, it's better to remove bucket and then retroactively // add it if silos were different rather than doing 2 lookups (1st for checking if silos are // equal, then 2nd to remove bucket) each time bucketsMap[hash] = removedSilo; } } var myOldRange = this.myRange; var myNewRange = UpdateRange(); LogTraceRemovedServer(logger, new(silo), this); NotifyLocalRangeSubscribers(myOldRange, myNewRange, true); } } private IRingRange UpdateRange() { var bucketsList = new (uint, SiloAddress)[bucketsMap.Count]; var idx = 0; foreach (var pair in bucketsMap) bucketsList[idx++] = (pair.Key, pair.Value); var myNewRange = CalculateRange(bucketsList, myAddress); // capture my range and sortedBucketsList for later lock-free access. myRange = myNewRange; sortedBucketsList = bucketsList; return myNewRange; } private static IRingRange CalculateRange((uint Hash, SiloAddress SiloAddress)[] list, SiloAddress silo) { var ranges = new List(); for (int i = 0; i < list.Length; i++) { var curr = list[i]; var next = list[(i + 1) % list.Length]; // 'backward/clockwise' definition to assign responsibilities on the ring. if (next.SiloAddress.Equals(silo)) { IRingRange range = RangeFactory.CreateRange(curr.Hash, next.Hash); ranges.Add(range); } } return RangeFactory.CreateRange(ranges); } // for debugging/logging public override string ToString() { var sortedList = GetRanges(); sortedList.Sort((t1, t2) => t1.Value.RangePercentage().CompareTo(t2.Value.RangePercentage())); return Utils.EnumerableToString(sortedList, kv => $"{kv.Key} -> {kv.Value}"); } // Internal: for testing/logging only! internal List<(SiloAddress Key, IRingRangeInternal Value)> GetRanges() { List silos; (uint, SiloAddress)[] snapshotBucketsList; lock (bucketsMap) { silos = bucketsMap.Values.Distinct().ToList(); snapshotBucketsList = sortedBucketsList; } var ranges = new List<(SiloAddress, IRingRangeInternal)>(silos.Count); foreach (var silo in silos) { var range = (IRingRangeInternal)CalculateRange(snapshotBucketsList, silo); ranges.Add((silo, range)); } return ranges; } public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { // This silo's status has changed if (updatedSilo.Equals(myAddress)) { if (status.IsTerminating()) { Stop(); } } else // Status change for some other silo { if (status.IsTerminating()) { RemoveServer(updatedSilo); } else if (status == SiloStatus.Active) // do not do anything with SiloStatus.Created or SiloStatus.Joining -- wait until it actually becomes active { AddServer(updatedSilo); } } } public SiloAddress GetPrimaryTargetSilo(uint key) { return CalculateTargetSilo(key); } /// /// Finds the silo that owns the given hash value. /// This routine will always return a non-null silo address unless the excludeThisSiloIfStopping parameter is true, /// this is the only silo known, and this silo is stopping. /// /// /// /// private SiloAddress CalculateTargetSilo(uint hash, bool excludeThisSiloIfStopping = true) { // put a private reference to point to sortedBucketsList, // so if someone is changing the sortedBucketsList reference, we won't get it changed in the middle of our operation. // The tricks of writing lock-free code! var snapshotBucketsList = sortedBucketsList; // excludeMySelf from being a TargetSilo if we're not running and the excludeThisSIloIfStopping flag is true. see the comment in the Stop method. bool excludeMySelf = excludeThisSiloIfStopping && !running; if (snapshotBucketsList.Length == 0) { // If the membership ring is empty, then we're the owner by default unless we're stopping. return excludeMySelf ? null : myAddress; } // use clockwise ... current code in membershipOracle.CalculateTargetSilo() does counter-clockwise ... // if you want to stick to counter-clockwise, change the responsibility definition in 'In()' method & responsibility defs in OrleansReminderMemory // need to implement a binary search, but for now simply traverse the list of silos sorted by their hashes (uint Hash, SiloAddress SiloAddress) s = default; foreach (var tuple in snapshotBucketsList) { if (tuple.Hash >= hash && // <= hash for counter-clockwise responsibilities (!tuple.SiloAddress.Equals(myAddress) || !excludeMySelf)) { s = tuple; break; } } if (s.SiloAddress == null) { // if not found in traversal, then first silo should be returned (we are on a ring) // if you go back to their counter-clockwise policy, then change the 'In()' method in OrleansReminderMemory s = snapshotBucketsList[0]; // vs [membershipRingList.Count - 1]; for counter-clockwise policy // Make sure it's not us... if (s.SiloAddress.Equals(myAddress) && excludeMySelf) { // vs [membershipRingList.Count - 2]; for counter-clockwise policy s = snapshotBucketsList.Length > 1 ? snapshotBucketsList[1] : default; } } LogTraceCalculatedRingPartitionOwner(logger, s.SiloAddress, hash, s.Hash); return s.SiloAddress; } public void Dispose() { _siloStatusOracle.UnSubscribeFromSiloStatusEvents(this); } private readonly struct SiloAddressLogValue { private readonly SiloAddress _siloAddress; public SiloAddressLogValue(SiloAddress siloAddress) { _siloAddress = siloAddress; } public override string ToString() => _siloAddress.ToStringWithHashCode(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting {Name} on silo {SiloAddress}." )] private static partial void LogDebugStarting(ILogger logger, string name, SiloAddressLogValue siloAddress); [LoggerMessage( EventId = (int)ErrorCode.CRP_Notify, Level = LogLevel.Trace, Message = "NotifyLocalRangeSubscribers about old {Old} new {New} increased? {IsIncrease}" )] private static partial void LogTraceNotifyLocalRangeSubscribers(ILogger logger, IRingRange old, IRingRange @new, bool isIncrease); [LoggerMessage( EventId = (int)ErrorCode.CRP_Local_Subscriber_Exception, Level = LogLevel.Warning, Message = "Error notifying listener '{ListenerType}' of ring range {AdjustmentKind} from '{OldRange}' to '{NewRange}'." )] private static partial void LogWarningErrorNotifyingListener(ILogger logger, Exception exception, string listenerType, string adjustmentKind, IRingRange oldRange, IRingRange newRange); [LoggerMessage( EventId = (int)ErrorCode.CRP_Added_Silo, Level = LogLevel.Trace, Message = "Added Server {SiloAddress}. Current view: {CurrentView}" )] private static partial void LogTraceAddedServer(ILogger logger, SiloAddressLogValue siloAddress, VirtualBucketsRingProvider currentView); [LoggerMessage( EventId = (int)ErrorCode.CRP_Removed_Silo, Level = LogLevel.Trace, Message = "Removed Server {SiloAddress}. Current view: {CurrentView}" )] private static partial void LogTraceRemovedServer(ILogger logger, SiloAddressLogValue siloAddress, VirtualBucketsRingProvider currentView); [LoggerMessage( Level = LogLevel.Trace, Message = "Calculated ring partition owner silo {Owner} for key {Key}: {Key} --> {OwnerHash}" )] private static partial void LogTraceCalculatedRingPartitionOwner(ILogger logger, SiloAddress owner, uint key, uint ownerHash); } } ================================================ FILE: src/Orleans.Runtime/Core/FatalErrorHandler.cs ================================================ using System; using Microsoft.Extensions.Logging; using System.Diagnostics; using Microsoft.Extensions.Options; using Orleans.Configuration; using System.Threading; namespace Orleans.Runtime { internal partial class FatalErrorHandler : IFatalErrorHandler { private readonly ILogger log; private readonly ClusterMembershipOptions clusterMembershipOptions; public FatalErrorHandler( ILogger log, IOptions clusterMembershipOptions) { this.log = log; this.clusterMembershipOptions = clusterMembershipOptions.Value; } public bool IsUnexpected(Exception exception) { return !(exception is ThreadAbortException); } public void OnFatalException(object sender, string context, Exception exception) { LogFatalError(this.log, exception, sender, context); var msg = @$"FATAL EXCEPTION from {sender?.ToString() ?? "null"}. Context: {context ?? "null" }. Exception: {(exception != null ? LogFormatter.PrintException(exception) : "null")}.\nCurrent stack: {Environment.StackTrace}"; Console.Error.WriteLine(msg); // Allow some time for loggers to flush. Thread.Sleep(2000); if (Debugger.IsAttached) Debugger.Break(); Environment.FailFast(msg, exception); } [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Logger_ProcessCrashing, Message = "Fatal error from {Sender}. Context: {Context}" )] private static partial void LogFatalError(ILogger logger, Exception exception, object sender, string context); } } ================================================ FILE: src/Orleans.Runtime/Core/GrainRuntime.cs ================================================ #nullable enable using System; using Orleans.Core; using Orleans.Timers; using Orleans.Storage; namespace Orleans.Runtime; internal class GrainRuntime : IGrainRuntime { private readonly IServiceProvider _serviceProvider; private readonly ITimerRegistry _timerRegistry; private readonly IGrainFactory _grainFactory; public GrainRuntime( ILocalSiloDetails localSiloDetails, IGrainFactory grainFactory, ITimerRegistry timerRegistry, IServiceProvider serviceProvider, TimeProvider timeProvider) { SiloAddress = localSiloDetails.SiloAddress; SiloIdentity = SiloAddress.ToString(); _grainFactory = grainFactory; _timerRegistry = timerRegistry; _serviceProvider = serviceProvider; TimeProvider = timeProvider; } public string SiloIdentity { get; } public SiloAddress SiloAddress { get; } public IGrainFactory GrainFactory { get { CheckRuntimeContext(RuntimeContext.Current); return _grainFactory; } } public ITimerRegistry TimerRegistry { get { CheckRuntimeContext(RuntimeContext.Current); return _timerRegistry; } } public IServiceProvider ServiceProvider { get { CheckRuntimeContext(RuntimeContext.Current); return _serviceProvider; } } public TimeProvider TimeProvider { get; } public void DeactivateOnIdle(IGrainContext grainContext) { CheckRuntimeContext(grainContext); grainContext.Deactivate(new(DeactivationReasonCode.ApplicationRequested, $"{nameof(DeactivateOnIdle)} was called.")); } public void DelayDeactivation(IGrainContext grainContext, TimeSpan timeSpan) { CheckRuntimeContext(grainContext); if (grainContext is not ICollectibleGrainContext collectibleContext) { throw new NotSupportedException($"Grain context {grainContext} does not implement {nameof(ICollectibleGrainContext)} and therefore {nameof(DelayDeactivation)} is not supported"); } collectibleContext.DelayDeactivation(timeSpan); } public IStorage GetStorage(IGrainContext grainContext) { ArgumentNullException.ThrowIfNull(grainContext); var grainType = grainContext.GrainInstance?.GetType() ?? throw new ArgumentNullException(nameof(IGrainContext.GrainInstance)); IGrainStorage grainStorage = GrainStorageHelpers.GetGrainStorage(grainType, ServiceProvider); return new StateStorageBridge("state", grainContext, grainStorage); } public static void CheckRuntimeContext(IGrainContext? context) { if (context is null) { // Move exceptions into local functions to help inlining this method. ThrowMissingContext(); void ThrowMissingContext() => throw new InvalidOperationException("Activation access violation. A non-activation thread attempted to access activation services."); } if (context is ActivationData activation && activation.State == ActivationState.Invalid) { // Move exceptions into local functions to help inlining this method. ThrowInvalidActivation(activation); void ThrowInvalidActivation(ActivationData activationData) => throw new InvalidOperationException($"Attempt to access an invalid activation: {activationData}"); } } } ================================================ FILE: src/Orleans.Runtime/Core/HostedClient.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.GrainReferences; using Orleans.Internal; using Orleans.Runtime.Messaging; using Orleans.Serialization; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// A client which is hosted within a silo. /// internal sealed partial class HostedClient : IGrainContext, IGrainExtensionBinder, IDisposable, ILifecycleParticipant { #if NET9_0_OR_GREATER private readonly Lock lockObj = new(); #else private readonly object lockObj = new(); #endif private readonly Channel incomingMessages; private readonly IGrainReferenceRuntime grainReferenceRuntime; private readonly InvokableObjectManager invokableObjects; private readonly InsideRuntimeClient runtimeClient; private readonly ILogger logger; private readonly IInternalGrainFactory grainFactory; private readonly MessageCenter siloMessageCenter; private readonly MessagingTrace messagingTrace; private readonly ConcurrentDictionary _extensions = new ConcurrentDictionary(); private readonly ConcurrentDictionary _components = new(); private readonly IServiceScope _serviceProviderScope; private bool disposing; private Task? messagePump; public HostedClient( InsideRuntimeClient runtimeClient, ILocalSiloDetails siloDetails, ILogger logger, IGrainReferenceRuntime grainReferenceRuntime, IInternalGrainFactory grainFactory, MessageCenter messageCenter, MessagingTrace messagingTrace, DeepCopier deepCopier, GrainReferenceActivator referenceActivator, InterfaceToImplementationMappingCache interfaceToImplementationMappingCache) { this.incomingMessages = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false, }); this.runtimeClient = runtimeClient; this.grainReferenceRuntime = grainReferenceRuntime; this.grainFactory = grainFactory; this.invokableObjects = new InvokableObjectManager( this, runtimeClient, deepCopier, messagingTrace, runtimeClient.ServiceProvider.GetRequiredService>(), interfaceToImplementationMappingCache, logger); this.siloMessageCenter = messageCenter; this.messagingTrace = messagingTrace; this.logger = logger; this.ClientId = CreateHostedClientGrainId(siloDetails.SiloAddress); this.Address = Gateway.GetClientActivationAddress(this.ClientId.GrainId, siloDetails.SiloAddress); this.GrainReference = referenceActivator.CreateReference(this.ClientId.GrainId, default); _serviceProviderScope = runtimeClient.ServiceProvider.CreateScope(); } public static ClientGrainId CreateHostedClientGrainId(SiloAddress siloAddress) => ClientGrainId.Create($"hosted-{siloAddress.ToParsableString()}"); /// public ClientGrainId ClientId { get; } public GrainReference GrainReference { get; } public GrainId GrainId => this.ClientId.GrainId; public object? GrainInstance => null; public ActivationId ActivationId => this.Address.ActivationId; public GrainAddress Address { get; } public IServiceProvider ActivationServices => _serviceProviderScope.ServiceProvider; public IGrainLifecycle ObservableLifecycle => throw new NotImplementedException(); public IWorkItemScheduler Scheduler => throw new NotImplementedException(); public bool IsExemptFromCollection => true; /// public override string ToString() => $"{nameof(HostedClient)}_{this.Address}"; /// public IAddressable CreateObjectReference(IAddressable obj) { if (obj is GrainReference) throw new ArgumentException("Argument obj is already a grain reference."); var observerId = ObserverGrainId.Create(this.ClientId); var grainReference = this.grainFactory.GetGrain(observerId.GrainId); if (!this.invokableObjects.TryRegister(obj, observerId)) { throw new ArgumentException( string.Format("Failed to add new observer {0} to localObjects collection.", grainReference), nameof(grainReference)); } return grainReference; } /// public void DeleteObjectReference(IAddressable obj) { if (obj is not GrainReference reference) { throw new ArgumentException("Argument reference is not a grain reference."); } if (!ObserverGrainId.TryParse(reference.GrainId, out var observerId)) { throw new ArgumentException($"Reference {reference.GrainId} is not an observer reference"); } if (!invokableObjects.TryDeregister(observerId)) { throw new ArgumentException("Reference is not associated with a local object.", "reference"); } } public object? GetComponent(Type componentType) { if (componentType.IsAssignableFrom(GetType())) return this; if (_components.TryGetValue(componentType, out var result)) { return result; } else if (componentType == typeof(PlacementStrategy)) { return ClientObserversPlacement.Instance; } lock (lockObj) { if (ActivationServices.GetService(componentType) is { } activatedComponent) { return _components.GetOrAdd(componentType, activatedComponent); } } return default; } public void SetComponent(TComponent? instance) where TComponent : class { if (this is TComponent) { throw new ArgumentException("Cannot override a component which is implemented by the client context"); } lock (lockObj) { if (instance == null) { _components.Remove(typeof(TComponent), out _); return; } _components[typeof(TComponent)] = instance; } } /// public bool TryDispatchToClient(Message message) { if (!ClientGrainId.TryParse(message.TargetGrain, out var targetClient) || !this.ClientId.Equals(targetClient)) { return false; } if (message.IsExpired) { this.messagingTrace.OnDropExpiredMessage(message, MessagingInstruments.Phase.Receive); return true; } this.ReceiveMessage(message); return true; } public void ReceiveMessage(object message) { var msg = (Message)message; if (msg.Direction == Message.Directions.Response) { // Requests are made through the runtime client, so deliver responses to the runtime client so that the request callback can be executed. this.runtimeClient.ReceiveResponse(msg); } else { // Requests against client objects are scheduled for execution on the client. this.incomingMessages.Writer.TryWrite(msg); } } /// void IDisposable.Dispose() { if (this.disposing) return; this.disposing = true; _serviceProviderScope.Dispose(); Utils.SafeExecute(() => this.siloMessageCenter.SetHostedClient(null)); Utils.SafeExecute(() => this.incomingMessages.Writer.TryComplete()); Utils.SafeExecute(() => this.messagePump?.GetAwaiter().GetResult()); } private void Start() { this.messagePump = Task.Run(this.RunClientMessagePump); } private async Task RunClientMessagePump() { var reader = this.incomingMessages.Reader; while (true) { try { var more = await reader.WaitToReadAsync(); if (!more) { LogDebugShuttingDown(this.logger); break; } while (reader.TryRead(out var message)) { if (message == null) continue; switch (message.Direction) { case Message.Directions.OneWay: case Message.Directions.Request: this.invokableObjects.Dispatch(message); break; default: LogErrorUnsupportedMessage(this.logger, message); break; } } } catch (Exception exception) { LogErrorMessagePumpException(this.logger, exception); } } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe("HostedClient", ServiceLifecycleStage.RuntimeGrainServices, OnStart, OnStop); Task OnStart(CancellationToken cancellation) { if (cancellation.IsCancellationRequested) return Task.CompletedTask; // Register with the message center so that we can receive messages. this.siloMessageCenter.SetHostedClient(this); // Start pumping messages. this.Start(); return Task.CompletedTask; } async Task OnStop(CancellationToken cancellation) { this.incomingMessages.Writer.TryComplete(); if (this.messagePump != null) { await messagePump.WaitAsync(cancellation).SuppressThrowing(); } } } public bool Equals(IGrainContext? other) => ReferenceEquals(this, other); public (TExtension, TExtensionInterface) GetOrSetExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { (TExtension, TExtensionInterface) result; if (this.TryGetExtension(out result)) { return result; } lock (this.lockObj) { if (this.TryGetExtension(out result)) { return result; } var implementation = newExtensionFunc(); var reference = this.grainFactory.CreateObjectReference(implementation); _extensions[typeof(TExtensionInterface)] = (implementation, reference); result = (implementation, reference); return result; } } private bool TryGetExtension(out (TExtension, TExtensionInterface) result) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { if (_extensions.TryGetValue(typeof(TExtensionInterface), out var existing)) { if (existing.Implementation is TExtension typedResult) { result = (typedResult, existing.Reference.AsReference()); return true; } throw new InvalidCastException($"Cannot cast existing extension of type {existing.Implementation} to target type {typeof(TExtension)}"); } result = default; return false; } private bool TryGetExtension([NotNullWhen(true)] out TExtensionInterface? result) where TExtensionInterface : IGrainExtension { if (_extensions.TryGetValue(typeof(TExtensionInterface), out var existing)) { result = (TExtensionInterface)existing.Implementation; return true; } result = default; return false; } public TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension { if (this.TryGetExtension(out var result)) { return result; } lock (this.lockObj) { if (this.TryGetExtension(out result)) { return result; } var implementation = this.ActivationServices.GetKeyedService(typeof(TExtensionInterface)); if (implementation is null) { throw new GrainExtensionNotInstalledException($"No extension of type {typeof(TExtensionInterface)} is installed on this instance and no implementations are registered for automated install"); } var reference = this.GrainReference.Cast(); _extensions[typeof(TExtensionInterface)] = (implementation, reference); result = (TExtensionInterface)implementation; return result; } } public object? GetTarget() => throw new NotImplementedException(); public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { } public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken) { } public Task Deactivated => Task.CompletedTask; public void Rehydrate(IRehydrationContext context) { // Migration is not supported, but we need to dispose of the context if it's provided (context as IDisposable)?.Dispose(); } public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken) { // Migration is not supported. Do nothing: the contract is that this method attempts migration, but does not guarantee it will occur. } [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(Runtime.HostedClient)} completed processing all messages. Shutting down.")] private static partial void LogDebugShuttingDown(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100327, Level = LogLevel.Error, Message = "Message not supported: {Message}")] private static partial void LogErrorUnsupportedMessage(ILogger logger, Message message); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100326, Level = LogLevel.Error, Message = "RunClientMessagePump has thrown an exception. Continuing.")] private static partial void LogErrorMessagePumpException(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Core/IFatalErrorHandler.cs ================================================ using System; namespace Orleans.Runtime { /// /// Interface for controlling how fatal errors (such as a silo being declared defunct) are handled. /// public interface IFatalErrorHandler { /// /// Determines whether the specified exception is unexpected. /// /// The exception. /// if the specified exception is unexpected; otherwise, . bool IsUnexpected(Exception exception); /// /// Called when a fatal exception occurs. /// /// The sender. /// The context. /// The exception. void OnFatalException(object sender = null, string context = null, Exception exception = null); } } ================================================ FILE: src/Orleans.Runtime/Core/IHealthCheckParticipant.cs ================================================ namespace Orleans.Runtime { /// /// Interface for health check participants /// public interface IHealthCheckParticipant : IHealthCheckable { } } ================================================ FILE: src/Orleans.Runtime/Core/InsideRuntimeClient.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.CodeGeneration; using Orleans.Configuration; using Orleans.Diagnostics; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Messaging; using Orleans.Serialization; using Orleans.Serialization.Invocation; using Orleans.Storage; using static Orleans.Internal.StandardExtensions; namespace Orleans.Runtime { /// /// Internal class for system grains to get access to runtime object /// internal sealed partial class InsideRuntimeClient : IRuntimeClient, ILifecycleParticipant { private readonly ILogger logger; private readonly ILogger invokeExceptionLogger; private readonly ILoggerFactory loggerFactory; private readonly SiloMessagingOptions messagingOptions; private readonly ConcurrentDictionary<(GrainId, CorrelationId), CallbackData> callbacks; private readonly InterfaceToImplementationMappingCache interfaceToImplementationMapping; private readonly SharedCallbackData sharedCallbackData; private readonly SharedCallbackData systemSharedCallbackData; private readonly PeriodicTimer callbackTimer; private GrainLocator grainLocator; private MessageCenter messageCenter; private List grainCallFilters; private readonly DeepCopier _deepCopier; private readonly ApplicationRequestInstruments _applicationRequestInstruments; private IGrainCallCancellationManager _cancellationManager; private HostedClient hostedClient; private HostedClient HostedClient => this.hostedClient ??= this.ServiceProvider.GetRequiredService(); private readonly MessageFactory messageFactory; private IGrainReferenceRuntime grainReferenceRuntime; private Task callbackTimerTask; private readonly MessagingTrace messagingTrace; private readonly DeepCopier responseCopier; public InsideRuntimeClient( ILocalSiloDetails siloDetails, IServiceProvider serviceProvider, MessageFactory messageFactory, ILoggerFactory loggerFactory, IOptions messagingOptions, MessagingTrace messagingTrace, GrainReferenceActivator referenceActivator, GrainInterfaceTypeResolver interfaceIdResolver, GrainInterfaceTypeToGrainTypeResolver interfaceToTypeResolver, DeepCopier deepCopier, TimeProvider timeProvider, InterfaceToImplementationMappingCache interfaceToImplementationMapping, OrleansInstruments orleansInstruments) { TimeProvider = timeProvider; this.interfaceToImplementationMapping = interfaceToImplementationMapping; this._deepCopier = deepCopier; this._applicationRequestInstruments = new(orleansInstruments); this.ServiceProvider = serviceProvider; this.MySilo = siloDetails.SiloAddress; this.callbacks = new ConcurrentDictionary<(GrainId, CorrelationId), CallbackData>(); this.messageFactory = messageFactory; this.ConcreteGrainFactory = new GrainFactory(this, referenceActivator, interfaceIdResolver, interfaceToTypeResolver); this.logger = loggerFactory.CreateLogger(); this.invokeExceptionLogger = loggerFactory.CreateLogger($"{typeof(Grain).FullName}.InvokeException"); this.loggerFactory = loggerFactory; this.messagingOptions = messagingOptions.Value; this.messagingTrace = messagingTrace; this.responseCopier = deepCopier.GetCopier(); var period = Max(TimeSpan.FromMilliseconds(1), Min(this.messagingOptions.ResponseTimeout, TimeSpan.FromSeconds(1))); this.callbackTimer = new PeriodicTimer(period, timeProvider); var callbackDataLogger = loggerFactory.CreateLogger(); this.sharedCallbackData = new SharedCallbackData( msg => this.UnregisterCallback(msg.SendingGrain, msg.Id), callbackDataLogger, this.messagingOptions.ResponseTimeout, this.messagingOptions.CancelRequestOnTimeout, this.messagingOptions.WaitForCancellationAcknowledgement, cancellationManager: null); this.systemSharedCallbackData = new SharedCallbackData( msg => this.UnregisterCallback(msg.SendingGrain, msg.Id), callbackDataLogger, this.messagingOptions.SystemResponseTimeout, cancelOnTimeout: false, waitForCancellationAcknowledgement: this.messagingOptions.WaitForCancellationAcknowledgement, cancellationManager: null); } public IServiceProvider ServiceProvider { get; } public IInternalGrainFactory InternalGrainFactory => this.ConcreteGrainFactory; private SiloAddress MySilo { get; } public GrainFactory ConcreteGrainFactory { get; } private GrainLocator GrainLocator => this.grainLocator ?? (this.grainLocator = this.ServiceProvider.GetRequiredService()); private List GrainCallFilters => this.grainCallFilters ??= new List(this.ServiceProvider.GetServices()); private MessageCenter MessageCenter => this.messageCenter ?? (this.messageCenter = this.ServiceProvider.GetRequiredService()); public IGrainReferenceRuntime GrainReferenceRuntime => this.grainReferenceRuntime ?? (this.grainReferenceRuntime = this.ServiceProvider.GetRequiredService()); public void SendRequest( GrainReference target, IInvokable request, IResponseCompletionSource context, InvokeMethodOptions options) { var cancellationToken = request.GetCancellationToken(); cancellationToken.ThrowIfCancellationRequested(); var message = this.messageFactory.CreateMessage(request, options); message.InterfaceType = target.InterfaceType; message.InterfaceVersion = target.InterfaceVersion; // fill in sender if (message.SendingSilo == null) message.SendingSilo = MySilo; IGrainContext sendingActivation = RuntimeContext.Current; if (sendingActivation == null) { var clientAddress = this.HostedClient.Address; message.SendingGrain = clientAddress.GrainId; } else { message.SendingGrain = sendingActivation.GrainId; } // fill in destination var targetGrainId = target.GrainId; message.TargetGrain = targetGrainId; SharedCallbackData sharedData; if (SystemTargetGrainId.TryParse(targetGrainId, out var systemTargetGrainId)) { message.TargetSilo = systemTargetGrainId.GetSiloAddress(); message.IsSystemMessage = true; sharedData = this.systemSharedCallbackData; } else { sharedData = this.sharedCallbackData; } if (this.messagingOptions.DropExpiredMessages && message.IsExpirableMessage()) { message.TimeToLive = request.GetDefaultResponseTimeout() ?? sharedData.ResponseTimeout; } var oneWay = (options & InvokeMethodOptions.OneWay) != 0; if (!oneWay) { Debug.Assert(context is not null); // Register a callback for the request. var callbackData = new CallbackData(sharedData, context, message, _applicationRequestInstruments); callbacks.TryAdd((message.SendingGrain, message.Id), callbackData); callbackData.SubscribeForCancellation(cancellationToken); } else { context?.Complete(); } this.messagingTrace.OnSendRequest(message); this.MessageCenter.AddressAndSendMessage(message); } public void SendResponse(Message request, Response response) { OrleansInsideRuntimeClientEvent.Log.SendResponse(request); // Don't process messages that have already timed out if (request.IsExpired) { this.messagingTrace.OnDropExpiredMessage(request, MessagingInstruments.Phase.Respond); return; } this.MessageCenter.SendResponse(request, response); } /// /// UnRegister a callback. /// private void UnregisterCallback(GrainId grainId, CorrelationId correlationId) { callbacks.TryRemove((grainId, correlationId), out _); } public void SniffIncomingMessage(Message message) { try { if (message.CacheInvalidationHeader is { } cacheUpdates) { lock (cacheUpdates) { foreach (var update in cacheUpdates) { GrainLocator.UpdateCache(update); } } } #if false //// 1: //// Also record sending activation address for responses only in the cache. //// We don't record sending addresses for requests, since it is not clear that this silo ever wants to send messages to the grain sending this request. //// However, it is sure that this silo does send messages to the sender of a reply. //// In most cases it will already have its address cached, unless it had a wrong outdated address cached and now this is a fresher address. //// It is anyway always safe to cache the replier address. //// 2: //// after further thought decided not to do it. //// It seems to better not bother caching the sender of a response at all, //// and instead to take a very occasional hit of a full remote look-up instead of this small but non-zero hit on every response. //if (message.Direction.Equals(Message.Directions.Response) && message.Result.Equals(Message.ResponseTypes.Success)) //{ // ActivationAddress sender = message.SendingAddress; // // just make sure address we are about to cache is OK and cachable. // if (sender.IsComplete && !sender.Grain.IsClient && !sender.Grain.IsSystemTargetType && !sender.Activation.IsSystemTargetType) // { // directory.AddCacheEntry(sender); // } //} #endif } catch (Exception exc) { LogWarningSniffIncomingMessage(this.logger, exc); } } public async Task Invoke(IGrainContext target, Message message) { try { // Don't process messages that have already timed out if (message.IsExpired) { this.messagingTrace.OnDropExpiredMessage(message, MessagingInstruments.Phase.Invoke); return; } if (message.RequestContextData is { Count: > 0 }) { RequestContextExtensions.Import(message.RequestContextData); } Response response; try { switch (message.BodyObject) { case IInvokable invokable: { invokable.SetTarget(target); CancellationSourcesExtension.RegisterCancellationTokens(target, invokable); if (GrainCallFilters is { Count: > 0 } || target.GrainInstance is IIncomingGrainCallFilter) { var invoker = new GrainMethodInvoker(message, target, invokable, GrainCallFilters, this.interfaceToImplementationMapping, this.responseCopier); await invoker.Invoke(); response = invoker.Response; } else { response = await invokable.Invoke(); response = this.responseCopier.Copy(response); } invokable.Dispose(); break; } default: throw new NotSupportedException($"Request {message.BodyObject} of type {message.BodyObject?.GetType()} is not supported"); } } catch (Exception exc1) { response = Response.FromException(exc1); } if (response.Exception is { } invocationException) { LogGrainInvokeException(this.invokeExceptionLogger, message.Direction != Message.Directions.OneWay ? LogLevel.Debug : LogLevel.Warning, invocationException, message); // If a grain allowed an inconsistent state exception to escape and the exception originated from // this activation, then deactivate it. if (invocationException is InconsistentStateException ise && ise.IsSourceActivation) { // Mark the exception so that it doesn't deactivate any other activations. ise.IsSourceActivation = false; LogDeactivatingInconsistentState(this.invokeExceptionLogger, target, invocationException); if (target is ActivationData ad && message.RequestContextData.TryGetActivityContext() is { } ac) { ad.Deactivate(new DeactivationReason(DeactivationReasonCode.ApplicationError, LogFormatter.PrintException(invocationException)), ac); } else { target.Deactivate(new DeactivationReason(DeactivationReasonCode.ApplicationError, LogFormatter.PrintException(invocationException))); } } } if (message.Direction != Message.Directions.OneWay) { SafeSendResponse(message, response); } return; } catch (Exception exc2) { LogWarningInvokeException(this.logger, exc2, message); if (message.Direction != Message.Directions.OneWay) { SafeSendExceptionResponse(message, exc2); } } } private void SafeSendResponse(Message message, Response response) { try { SendResponse(message, (Response)this._deepCopier.Copy(response)); } catch (Exception exc) { LogWarningResponseFailed(this.logger, exc); SendResponse(message, Response.FromException(exc)); } } private void SafeSendExceptionResponse(Message message, Exception ex) { try { SendResponse(message, Response.FromException(ex)); } catch (Exception exc1) { try { LogWarningSendExceptionResponseFailed(this.logger, exc1); SendResponse(message, Response.FromException(exc1)); } catch (Exception exc2) { LogWarningUnhandledExceptionInInvoke(this.logger, exc2); } } } public void ReceiveResponse(Message message) { OrleansInsideRuntimeClientEvent.Log.ReceiveResponse(message); if (message.Result is Message.ResponseTypes.Rejection) { if (!message.TargetSilo.Matches(this.MySilo)) { // gatewayed message - gateway back to sender LogTraceNoCallbackForRejection(this.logger, message); this.MessageCenter.AddressAndSendMessage(message); return; } LogHandleMessage(this.logger, message); var rejection = (RejectionResponse)message.BodyObject; switch (rejection.RejectionType) { case Message.RejectionTypes.Overloaded: break; case Message.RejectionTypes.Unrecoverable: // Fall through & reroute case Message.RejectionTypes.Transient: if (message.CacheInvalidationHeader is null) { // Remove from local directory cache. Note that SendingGrain is the original target, since message is the rejection response. // If CacheInvalidationHeader is present, we already did this. Otherwise, we left this code for backward compatibility. // It should be retired as we move to use CacheMgmtHeader in all relevant places. this.GrainLocator.InvalidateCache(message.SendingGrain); } break; case Message.RejectionTypes.CacheInvalidation when message.HasCacheInvalidationHeader: // The message targeted an invalid (eg, defunct) activation and this response serves only to invalidate this silo's activation cache. return; default: LogErrorUnsupportedRejectionType(this.logger, rejection.RejectionType); break; } } else if (message.Result == Message.ResponseTypes.Status) { var status = (StatusResponse)message.BodyObject; callbacks.TryGetValue((message.TargetGrain, message.Id), out var callback); var request = callback?.Message; if (request is not null) { callback.OnStatusUpdate(status); if (status.Diagnostics != null && status.Diagnostics.Count > 0) { LogInformationReceivedStatusUpdate(this.logger, request, status.Diagnostics); } } else { if (messagingOptions.CancelUnknownRequestOnStatusUpdate) { // Cancel the call since the caller has abandoned it. // Note that the target and sender arguments are swapped because this is a response to the original request. _cancellationManager.SignalCancellation( message.SendingSilo, targetGrainId: message.SendingGrain, sendingGrainId: message.TargetGrain, messageId: message.Id); } if (status.Diagnostics != null && status.Diagnostics.Count > 0 && logger.IsEnabled(LogLevel.Debug)) { var diagnosticsString = string.Join("\n", status.Diagnostics); this.logger.LogDebug("Received status update for unknown request. Message: {StatusMessage}. Status: {Diagnostics}", message, diagnosticsString); } } return; } CallbackData callbackData; bool found = callbacks.TryRemove((message.TargetGrain, message.Id), out callbackData); if (found) { // IMPORTANT: we do not schedule the response callback via the scheduler, since the only thing it does // is to resolve/break the resolver. The continuations/waits that are based on this resolution will be scheduled as work items. callbackData.DoCallback(message); } else { LogDebugNoCallbackForResponse(this.logger, message); } } public string CurrentActivationIdentity => RuntimeContext.Current?.Address.ToString() ?? this.HostedClient.ToString(); public TimeProvider TimeProvider { get; } /// public TimeSpan GetResponseTimeout() => this.sharedCallbackData.ResponseTimeout; /// public void SetResponseTimeout(TimeSpan timeout) => this.sharedCallbackData.ResponseTimeout = timeout; public IAddressable CreateObjectReference(IAddressable obj) { if (RuntimeContext.Current is null) return this.HostedClient.CreateObjectReference(obj); throw new InvalidOperationException("Cannot create a local object reference from a grain."); } public void DeleteObjectReference(IAddressable obj) { if (RuntimeContext.Current is null) { this.HostedClient.DeleteObjectReference(obj); } else { throw new InvalidOperationException("Cannot delete a local object reference from a grain."); } } private async Task OnRuntimeInitializeStop(CancellationToken tc) { this.callbackTimer.Dispose(); if (this.callbackTimerTask is { } task) { await task.WaitAsync(tc); } } private Task OnRuntimeInitializeStart(CancellationToken tc) { var stopWatch = ValueStopwatch.StartNew(); this.callbackTimerTask = Task.Run(MonitorCallbackExpiry); LogDebugSiloStartPerfMeasure(this.logger, new(stopWatch)); return Task.CompletedTask; } private readonly struct ValueStopwatchLogValue(ValueStopwatch stopWatch) { override public string ToString() { stopWatch.Stop(); return stopWatch.Elapsed.ToString(); } } public void BreakOutstandingMessagesToSilo(SiloAddress deadSilo) { foreach (var callback in callbacks) { if (deadSilo.Equals(callback.Value.Message.TargetSilo)) { callback.Value.OnTargetSiloFail(); } } } public void Participate(ISiloLifecycle lifecycle) { _cancellationManager = this.ServiceProvider.GetRequiredService(); sharedCallbackData.CancellationManager = _cancellationManager; systemSharedCallbackData.CancellationManager = _cancellationManager; lifecycle.Subscribe(ServiceLifecycleStage.RuntimeInitialize, OnRuntimeInitializeStart, OnRuntimeInitializeStop); } public int GetRunningRequestsCount(GrainInterfaceType grainInterfaceType) => this.callbacks.Count(c => c.Value.Message.InterfaceType == grainInterfaceType); private async Task MonitorCallbackExpiry() { while (await callbackTimer.WaitForNextTickAsync()) { try { var currentStopwatchTicks = ValueStopwatch.GetTimestamp(); foreach (var (_, callback) in callbacks) { if (callback.IsCompleted) { continue; } if (callback.IsExpired(currentStopwatchTicks)) { callback.OnTimeout(); } } } catch (Exception ex) { LogWarningWhileProcessingCallbackExpiry(this.logger, ex); } } } [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.IGC_SniffIncomingMessage_Exc, Message = "SniffIncomingMessage has thrown exception. Ignoring.")] private static partial void LogWarningSniffIncomingMessage(ILogger logger, Exception exception); [LoggerMessage( EventId = (int)ErrorCode.GrainInvokeException, Message = "Exception during Grain method call of message {Message}: ")] private static partial void LogGrainInvokeException(ILogger logger, LogLevel level, Exception exception, Message message); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.Runtime_Error_100329, Message = "Exception during Invoke of message {Message}")] private static partial void LogWarningInvokeException(ILogger logger, Exception exception, Message message); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.IGC_SendResponseFailed, Message = "Exception trying to send a response")] private static partial void LogWarningResponseFailed(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.IGC_SendExceptionResponseFailed, Message = "Exception trying to send an exception response")] private static partial void LogWarningSendExceptionResponseFailed(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.IGC_UnhandledExceptionInInvoke, Message = "Exception trying to send an exception. Ignoring and not trying to send again.")] private static partial void LogWarningUnhandledExceptionInInvoke(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)ErrorCode.Dispatcher_NoCallbackForRejectionResp, Message = "No callback for rejection response message: {Message}")] private static partial void LogTraceNoCallbackForRejection(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.Dispatcher_HandleMsg, Message = "HandleMessage {Message}")] private static partial void LogHandleMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Information, Message = "Deactivating {Target} due to inconsistent state.")] private static partial void LogDeactivatingInconsistentState(ILogger logger, IGrainContext target, Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Dispatcher_InvalidEnum_RejectionType, Message = "Unsupported rejection type: {RejectionType}")] private static partial void LogErrorUnsupportedRejectionType(ILogger logger, Message.RejectionTypes rejectionType); [LoggerMessage( Level = LogLevel.Information, Message = "Received status update for pending request, Request: {RequestMessage}. Status: {Diagnostics}")] private static partial void LogInformationReceivedStatusUpdate(ILogger logger, Message requestMessage, IEnumerable diagnostics); [LoggerMessage( Level = LogLevel.Information, Message = "Received status update for unknown request. Message: {StatusMessage}. Status: {Diagnostics}")] private static partial void LogInformationReceivedStatusUpdateUnknownRequest(ILogger logger, Message statusMessage, IEnumerable diagnostics); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.Dispatcher_NoCallbackForResp, Message = "No callback for response message {Message}")] private static partial void LogDebugNoCallbackForResponse(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.SiloStartPerfMeasure, Message = "Start InsideRuntimeClient took {ElapsedMs} milliseconds" )] private static partial void LogDebugSiloStartPerfMeasure(ILogger logger, ValueStopwatchLogValue elapsedMs); [LoggerMessage( Level = LogLevel.Warning, Message = "Error while processing callback expiry." )] private static partial void LogWarningWhileProcessingCallbackExpiry(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Core/InternalClusterClient.cs ================================================ using System; namespace Orleans.Runtime { /// /// Client for communicating with clusters of Orleans silos. /// internal class InternalClusterClient : IInternalClusterClient { private readonly IRuntimeClient runtimeClient; private readonly IInternalGrainFactory grainFactory; /// /// Initializes a new instance of the class. /// public InternalClusterClient(IRuntimeClient runtimeClient, IInternalGrainFactory grainFactory) { this.runtimeClient = runtimeClient; this.grainFactory = grainFactory; } /// public IGrainFactory GrainFactory => this.grainFactory; /// public IServiceProvider ServiceProvider => this.runtimeClient.ServiceProvider; /// public TGrainInterface GetGrain(Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey { return this.grainFactory.GetGrain(primaryKey, grainClassNamePrefix); } /// public TGrainInterface GetGrain(long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey { return this.grainFactory.GetGrain(primaryKey, grainClassNamePrefix); } /// public TGrainInterface GetGrain(string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey { return this.grainFactory.GetGrain(primaryKey, grainClassNamePrefix); } /// public TGrainInterface GetGrain(Guid primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey { return this.grainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix); } /// public TGrainInterface GetGrain(long primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey { return this.grainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix); } /// public TGrainObserverInterface CreateObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver { return this.grainFactory.CreateObjectReference(obj); } /// public void DeleteObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver { this.grainFactory.DeleteObjectReference(obj); } /// public TGrainObserverInterface CreateObjectReference(IAddressable obj) where TGrainObserverInterface : IAddressable { return this.grainFactory.CreateObjectReference(obj); } /// TGrainInterface IInternalGrainFactory.GetSystemTarget(GrainType grainType, SiloAddress destination) { return this.grainFactory.GetSystemTarget(grainType, destination); } /// TGrainInterface IInternalGrainFactory.GetSystemTarget(GrainId grainId) { return this.grainFactory.GetSystemTarget(grainId); } /// TGrainInterface IInternalGrainFactory.Cast(IAddressable grain) { return this.grainFactory.Cast(grain); } /// object IInternalGrainFactory.Cast(IAddressable grain, Type interfaceType) { return this.grainFactory.Cast(grain, interfaceType); } /// TGrainInterface IGrainFactory.GetGrain(GrainId grainId) { return this.grainFactory.GetGrain(grainId); } /// IAddressable IGrainFactory.GetGrain(GrainId grainId) { return this.grainFactory.GetGrain(grainId); } /// public IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey) { return this.grainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); } /// public IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey) { return this.grainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); } /// public IGrain GetGrain(Type grainInterfaceType, string grainPrimaryKey) { return this.grainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); } /// public IGrain GetGrain(Type grainInterfaceType, Guid grainPrimaryKey, string keyExtension) { return this.grainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); } /// public IGrain GetGrain(Type grainInterfaceType, long grainPrimaryKey, string keyExtension) { return this.grainFactory.GetGrain(grainInterfaceType, grainPrimaryKey); } /// public IAddressable GetGrain(GrainId grainId, GrainInterfaceType interfaceId) { return this.grainFactory.GetGrain(grainId, interfaceId); } /// public IAddressable GetGrain(Type interfaceType, IdSpan grainKey, string grainClassNamePrefix) { return this.grainFactory.GetGrain(interfaceType, grainKey, grainClassNamePrefix); } /// public IAddressable GetGrain(Type interfaceType, IdSpan grainKey) { return this.grainFactory.GetGrain(interfaceType, grainKey); } } } ================================================ FILE: src/Orleans.Runtime/Core/InternalGrainRuntime.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Messaging; using Orleans.Runtime.Versions; using Orleans.Runtime.Versions.Compatibility; namespace Orleans.Runtime { /// /// Shared runtime services which grains use. /// internal class InternalGrainRuntime( MessageCenter messageCenter, Catalog catalog, GrainVersionManifest versionManifest, RuntimeMessagingTrace messagingTrace, GrainLocator grainLocator, CompatibilityDirectorManager compatibilityDirectorManager, IOptions collectionOptions, ILocalGrainDirectory localGrainDirectory, IActivationWorkingSet activationWorkingSet) { public InsideRuntimeClient RuntimeClient { get; } = catalog.RuntimeClient; public MessageCenter MessageCenter { get; } = messageCenter; public Catalog Catalog { get; } = catalog; public GrainVersionManifest GrainVersionManifest { get; } = versionManifest; public RuntimeMessagingTrace MessagingTrace { get; } = messagingTrace; public CompatibilityDirectorManager CompatibilityDirectorManager { get; } = compatibilityDirectorManager; public GrainLocator GrainLocator { get; } = grainLocator; public IOptions CollectionOptions { get; } = collectionOptions; public ILocalGrainDirectory LocalGrainDirectory { get; } = localGrainDirectory; public IActivationWorkingSet ActivationWorkingSet { get; } = activationWorkingSet; } } ================================================ FILE: src/Orleans.Runtime/Core/ManagementGrain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Metadata; using Orleans.Placement.Repartitioning; using Orleans.Providers; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.MembershipService; using Orleans.Versions; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime.Management { /// /// Implementation class for the Orleans management grain. /// [StatelessWorker, Reentrant] internal partial class ManagementGrain : Grain, IManagementGrain { private readonly IInternalGrainFactory internalGrainFactory; private readonly ISiloStatusOracle siloStatusOracle; private readonly IVersionStore versionStore; private readonly MembershipTableManager membershipTableManager; private readonly GrainManifest siloManifest; private readonly ClusterManifest clusterManifest; private readonly ILogger logger; private readonly Catalog catalog; private readonly GrainLocator grainLocator; public ManagementGrain( IInternalGrainFactory internalGrainFactory, ISiloStatusOracle siloStatusOracle, IVersionStore versionStore, ILogger logger, MembershipTableManager membershipTableManager, IClusterManifestProvider clusterManifestProvider, Catalog catalog, GrainLocator grainLocator) { this.membershipTableManager = membershipTableManager; this.siloManifest = clusterManifestProvider.LocalGrainManifest; this.clusterManifest = clusterManifestProvider.Current; this.internalGrainFactory = internalGrainFactory; this.siloStatusOracle = siloStatusOracle; this.versionStore = versionStore; this.logger = logger; this.catalog = catalog; this.grainLocator = grainLocator; } public async Task> GetHosts(bool onlyActive = false) { await this.membershipTableManager.Refresh(); return this.siloStatusOracle.GetApproximateSiloStatuses(onlyActive); } public async Task GetDetailedHosts(bool onlyActive = false) { await this.membershipTableManager.Refresh(); var table = this.membershipTableManager.MembershipTableSnapshot; MembershipEntry[] result; if (onlyActive) { result = table.Entries .Where(item => item.Value.Status == SiloStatus.Active) .Select(x => x.Value) .ToArray(); } else { result = table.Entries .Select(x => x.Value) .ToArray(); } return result; } public Task ForceGarbageCollection(SiloAddress[] siloAddresses) { var silos = GetSiloAddresses(siloAddresses); LogInformationForceGarbageCollection(new(silos)); List actionPromises = PerformPerSiloAction(silos, s => GetSiloControlReference(s).ForceGarbageCollection()); return Task.WhenAll(actionPromises); } public Task ForceActivationCollection(SiloAddress[] siloAddresses, TimeSpan ageLimit) { var silos = GetSiloAddresses(siloAddresses); return Task.WhenAll(GetSiloAddresses(silos).Select(s => GetSiloControlReference(s).ForceActivationCollection(ageLimit))); } public async Task ForceActivationCollection(TimeSpan ageLimit) { Dictionary hosts = await GetHosts(true); SiloAddress[] silos = hosts.Keys.ToArray(); await ForceActivationCollection(silos, ageLimit); } public Task ForceRuntimeStatisticsCollection(SiloAddress[] siloAddresses) { var silos = GetSiloAddresses(siloAddresses); LogInformationForceRuntimeStatisticsCollection(new(silos)); List actionPromises = PerformPerSiloAction( silos, s => GetSiloControlReference(s).ForceRuntimeStatisticsCollection()); return Task.WhenAll(actionPromises); } public Task GetRuntimeStatistics(SiloAddress[] siloAddresses) { var silos = GetSiloAddresses(siloAddresses); LogDebugGetRuntimeStatistics(new(silos)); var promises = new List>(); foreach (SiloAddress siloAddress in silos) promises.Add(GetSiloControlReference(siloAddress).GetRuntimeStatistics()); return Task.WhenAll(promises); } public async Task GetSimpleGrainStatistics(SiloAddress[] hostsIds) { var all = GetSiloAddresses(hostsIds).Select(s => GetSiloControlReference(s).GetSimpleGrainStatistics()).ToList(); await Task.WhenAll(all); return all.SelectMany(s => s.Result).ToArray(); } public async Task GetSimpleGrainStatistics() { Dictionary hosts = await GetHosts(true); SiloAddress[] silos = hosts.Keys.ToArray(); return await GetSimpleGrainStatistics(silos); } public async Task GetDetailedGrainStatistics(string[] types = null, SiloAddress[] hostsIds = null) { if (hostsIds == null) { Dictionary hosts = await GetHosts(true); hostsIds = hosts.Keys.ToArray(); } var all = GetSiloAddresses(hostsIds).Select(s => GetSiloControlReference(s).GetDetailedGrainStatistics(types)).ToList(); await Task.WhenAll(all); return all.SelectMany(s => s.Result).ToArray(); } public async Task GetGrainActivationCount(GrainReference grainReference) { Dictionary hosts = await GetHosts(true); List hostsIds = hosts.Keys.ToList(); var tasks = new List>(); foreach (var silo in hostsIds) tasks.Add(GetSiloControlReference(silo).GetDetailedGrainReport(grainReference.GrainId)); await Task.WhenAll(tasks); return tasks.Select(s => s.Result).Select(CountActivations).Sum(); static int CountActivations(DetailedGrainReport report) => report.LocalActivation is { Length: > 0 } ? 1 : 0; } public async Task SetCompatibilityStrategy(CompatibilityStrategy strategy) { await SetStrategy( store => store.SetCompatibilityStrategy(strategy), siloControl => siloControl.SetCompatibilityStrategy(strategy)); } public async Task SetSelectorStrategy(VersionSelectorStrategy strategy) { await SetStrategy( store => store.SetSelectorStrategy(strategy), siloControl => siloControl.SetSelectorStrategy(strategy)); } public async Task SetCompatibilityStrategy(GrainInterfaceType interfaceType, CompatibilityStrategy strategy) { CheckIfIsExistingInterface(interfaceType); await SetStrategy( store => store.SetCompatibilityStrategy(interfaceType, strategy), siloControl => siloControl.SetCompatibilityStrategy(interfaceType, strategy)); } public async Task SetSelectorStrategy(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy) { CheckIfIsExistingInterface(interfaceType); await SetStrategy( store => store.SetSelectorStrategy(interfaceType, strategy), siloControl => siloControl.SetSelectorStrategy(interfaceType, strategy)); } public async Task GetTotalActivationCount() { Dictionary hosts = await GetHosts(true); List silos = hosts.Keys.ToList(); var tasks = new List>(); foreach (var silo in silos) tasks.Add(GetSiloControlReference(silo).GetActivationCount()); await Task.WhenAll(tasks); int sum = 0; foreach (Task task in tasks) sum += task.Result; return sum; } public Task SendControlCommandToProvider(string providerName, int command, object arg) where T : IControllable { return ExecutePerSiloCall(isc => isc.SendControlCommandToProvider(providerName, command, arg), $"SendControlCommandToProvider of type {typeof(T).FullName} and name {providerName} command {command}."); } public ValueTask GetActivationAddress(IAddressable reference) { var grainReference = reference as GrainReference; var grainId = grainReference.GrainId; GrainProperties grainProperties = default; if (!siloManifest.Grains.TryGetValue(grainId.Type, out grainProperties)) { var grainManifest = clusterManifest.AllGrainManifests .SelectMany(m => m.Grains.Where(g => g.Key == grainId.Type)) .FirstOrDefault(); if (grainManifest.Value != null) { grainProperties = grainManifest.Value; } else { throw new ArgumentException($"Unable to find Grain type '{grainId.Type}'. Make sure it is added to the Application Parts Manager at the Silo configuration."); } } if (grainProperties != default && grainProperties.Properties.TryGetValue(WellKnownGrainTypeProperties.PlacementStrategy, out string placementStrategy)) { if (placementStrategy == nameof(StatelessWorkerPlacement)) { throw new InvalidOperationException( $"Grain '{grainReference.ToString()}' is a Stateless Worker. This type of grain can't be looked up by this method" ); } } if (grainLocator.TryLookupInCache(grainId, out var result)) { return new ValueTask(result?.SiloAddress); } return LookupAsync(grainId, grainLocator); static async ValueTask LookupAsync(GrainId grainId, GrainLocator grainLocator) { var result = await grainLocator.Lookup(grainId); return result?.SiloAddress; } } private void CheckIfIsExistingInterface(GrainInterfaceType interfaceType) { GrainInterfaceType lookupId; if (GenericGrainInterfaceType.TryParse(interfaceType, out var generic)) { lookupId = generic.Value; } else { lookupId = interfaceType; } if (!this.siloManifest.Interfaces.TryGetValue(lookupId, out _)) { throw new ArgumentException($"Interface '{interfaceType} not found", nameof(interfaceType)); } } private async Task SetStrategy(Func storeFunc, Func applyFunc) { await storeFunc(versionStore); var silos = GetSiloAddresses(null); var actionPromises = PerformPerSiloAction( silos, s => applyFunc(GetSiloControlReference(s))); try { await Task.WhenAll(actionPromises); } catch (Exception) { // ignored: silos that failed to set the new strategy will reload it from the storage // in the future. } } private async Task ExecutePerSiloCall(Func> action, string actionToLog) { var silos = await GetHosts(true); LogDebugExecutingAction(actionToLog, new(silos)); var actionPromises = new List>(); foreach (SiloAddress siloAddress in silos.Keys.ToArray()) actionPromises.Add(action(GetSiloControlReference(siloAddress))); return await Task.WhenAll(actionPromises); } private SiloAddress[] GetSiloAddresses(SiloAddress[] silos) { if (silos != null && silos.Length > 0) return silos; return this.siloStatusOracle .GetApproximateSiloStatuses(true).Keys.ToArray(); } /// /// Perform an action for each silo. /// /// /// Because SiloControl contains a reference to a system target, each method call using that reference /// will get routed either locally or remotely to the appropriate silo instance auto-magically. /// /// List of silos to perform the action for /// The action function to be performed for each silo /// Array containing one Task for each silo the action was performed for private static List PerformPerSiloAction(SiloAddress[] siloAddresses, Func perSiloAction) { var requestsToSilos = new List(); foreach (SiloAddress siloAddress in siloAddresses) requestsToSilos.Add(perSiloAction(siloAddress)); return requestsToSilos; } private ISiloControl GetSiloControlReference(SiloAddress silo) { return this.internalGrainFactory.GetSystemTarget(Constants.SiloControlType, silo); } public async ValueTask> GetActiveGrains(GrainType grainType) { var hosts = await GetHosts(true); var tasks = new List>>(); foreach (var siloAddress in hosts.Keys) { tasks.Add(GetSiloControlReference(siloAddress).GetActiveGrains(grainType)); } await Task.WhenAll(tasks); var results = new List(); foreach (var promise in tasks) { results.AddRange(await promise); } return results; } public async Task> GetGrainCallFrequencies(SiloAddress[] hostsIds = null) { if (hostsIds == null) { var hosts = await GetHosts(true); hostsIds = [.. hosts.Keys]; } var results = new List(); foreach (var host in hostsIds) { var siloPartitioner = IActivationRepartitionerSystemTarget.GetReference(internalGrainFactory, host); var frequencies = await siloPartitioner.GetGrainCallFrequencies(); foreach (var frequency in frequencies) { results.Add(new GrainCallFrequency { SourceGrain = frequency.Item1.Source.Id, TargetGrain = frequency.Item1.Target.Id, SourceHost = frequency.Item1.Source.Silo, TargetHost = frequency.Item1.Target.Silo, CallCount = frequency.Item2 }); } } return results; } public async ValueTask ResetGrainCallFrequencies(SiloAddress[] hostsIds = null) { if (hostsIds == null) { var hosts = await GetHosts(true); hostsIds = [.. hosts.Keys]; } foreach (var host in hostsIds) { var siloBalancer = IActivationRepartitionerSystemTarget.GetReference(internalGrainFactory, host); await siloBalancer.ResetCounters(); } } private readonly struct SiloAddressesLogValue(SiloAddress[] siloAddresses) { public override string ToString() => Utils.EnumerableToString(siloAddresses); } [LoggerMessage( EventId = 0, Level = LogLevel.Information, Message = "Forcing garbage collection on {SiloAddresses}")] private partial void LogInformationForceGarbageCollection(SiloAddressesLogValue siloAddresses); [LoggerMessage( EventId = 0, Level = LogLevel.Information, Message = "Forcing runtime statistics collection on {SiloAddresses}")] private partial void LogInformationForceRuntimeStatisticsCollection(SiloAddressesLogValue siloAddresses); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "GetRuntimeStatistics on {SiloAddresses}")] private partial void LogDebugGetRuntimeStatistics(SiloAddressesLogValue siloAddresses); private readonly struct SiloAddressesKeysLogValue(Dictionary silos) { public override string ToString() => Utils.EnumerableToString(silos.Keys); } [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Executing {Action} against {SiloAddresses}" )] private partial void LogDebugExecutingAction(string action, SiloAddressesKeysLogValue siloAddresses); } } ================================================ FILE: src/Orleans.Runtime/Core/SystemStatus.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Orleans.Runtime { /// /// System status values and current register /// internal sealed class SystemStatus : IEquatable { private enum InternalSystemStatus { Unknown = 0, Creating, Created, Starting, Running, Stopping, ShuttingDown, Terminated, }; /// Status = Unknown [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Unknown = new SystemStatus(InternalSystemStatus.Unknown); /// Status = Creating [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Creating = new SystemStatus(InternalSystemStatus.Creating); /// Status = Created [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Created = new SystemStatus(InternalSystemStatus.Created); /// Status = Starting [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Starting = new SystemStatus(InternalSystemStatus.Starting); /// Status = Running [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Running = new SystemStatus(InternalSystemStatus.Running); /// Status = Stopping [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Stopping = new SystemStatus(InternalSystemStatus.Stopping); /// Status = ShuttingDown [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus ShuttingDown = new SystemStatus(InternalSystemStatus.ShuttingDown); /// Status = Terminated [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly SystemStatus Terminated = new SystemStatus(InternalSystemStatus.Terminated); private readonly InternalSystemStatus value; private SystemStatus(InternalSystemStatus name) { this.value = name; } /// public override string ToString() { return this.value.ToString(); } /// public override int GetHashCode() { return this.value.GetHashCode(); } /// public override bool Equals(object obj) { var ss = obj as SystemStatus; return ss != null && this.Equals(ss); } /// public bool Equals(SystemStatus other) { return (other != null) && this.value.Equals(other.value); } } } ================================================ FILE: src/Orleans.Runtime/Core/SystemTarget.cs ================================================ #nullable enable using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; using Orleans.Serialization.Invocation; namespace Orleans.Runtime { /// /// Base class for various system services, such as grain directory, reminder service, etc. /// Made public for GrainService to inherit from it. /// Can be turned to internal after a refactoring that would remove the inheritance relation. /// public abstract partial class SystemTarget : ISystemTarget, ISystemTargetBase, IGrainContext, IGrainExtensionBinder, ISpanFormattable, IDisposable, IGrainTimerRegistry, IGrainCallCancellationExtension { private readonly Func _handleRequest; private readonly SystemTargetGrainId _id; private readonly SystemTargetShared _shared; private readonly HashSet _timers = []; private readonly Dictionary _runningRequests = []; private readonly Dictionary _components = []; /// Silo address of the system target. public SiloAddress Silo => _shared.SiloAddress; internal GrainAddress ActivationAddress { get; } internal ActivationId ActivationId { get; set; } private readonly ILogger _logger; internal InsideRuntimeClient RuntimeClient => _shared.RuntimeClient; /// public GrainReference GrainReference { get => field ??= _shared.GrainReferenceActivator.CreateReference(_id.GrainId, default); private set; } /// public GrainId GrainId => _id.GrainId; /// object IGrainContext.GrainInstance => this; /// ActivationId IGrainContext.ActivationId => ActivationId; /// GrainAddress IGrainContext.Address => ActivationAddress; private RuntimeMessagingTrace MessagingTrace => _shared.MessagingTrace; /// /// Obsolete parameterless constructor for . /// /// Breaking change: This constructor is obsolete and will throw a if called. /// It is present only to satisfy certain reflection-based scenarios and should not be used in application code. /// /// /// If you are using reflection or serialization frameworks that require a parameterless constructor, /// update your code to use the constructor with parameters: . /// /// [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [Obsolete("Do not call the empty constructor.")] protected SystemTarget() => throw new NotSupportedException(); internal SystemTarget(GrainType grainType, SystemTargetShared shared) : this(SystemTargetGrainId.Create(grainType, shared.SiloAddress), shared) { } internal SystemTarget(SystemTargetGrainId grainId, SystemTargetShared shared) { _id = grainId; _shared = shared; _handleRequest = state => this.HandleNewRequest((Message)state!); ActivationId = ActivationId.GetDeterministic(grainId.GrainId); ActivationAddress = GrainAddress.GetAddress(Silo, _id.GrainId, ActivationId); _logger = shared.LoggerFactory.CreateLogger(GetType()); WorkItemGroup = _shared.CreateWorkItemGroup(this); if (!Constants.IsSingletonSystemTarget(GrainId.Type)) { GrainInstruments.IncrementSystemTargetCounts(Constants.SystemTargetName(GrainId.Type)); } } internal WorkItemGroup WorkItemGroup { get; } /// public IServiceProvider ActivationServices => RuntimeClient.ServiceProvider; /// IGrainLifecycle IGrainContext.ObservableLifecycle => throw new NotImplementedException("IGrainContext.ObservableLifecycle is not implemented by SystemTarget"); /// public IWorkItemScheduler Scheduler => WorkItemGroup; /// /// Gets the component with the specified type. /// /// The component type. /// The component with the specified type. public TComponent? GetComponent() { TComponent? result; if (this is TComponent instanceResult) { result = instanceResult; } else if (_components.TryGetValue(typeof(TComponent), out var resultObj)) { result = (TComponent)resultObj; } else if (typeof(TComponent) == typeof(PlacementStrategy)) { result = (TComponent)(object)SystemTargetPlacementStrategy.Instance; } else { result = default; } return result; } /// public void SetComponent(TComponent? instance) where TComponent : class { if (this is TComponent) { throw new ArgumentException("Cannot override a component which is implemented by this grain"); } if (instance == null) { _components.Remove(typeof(TComponent)); return; } _components[typeof(TComponent)] = instance; } internal async Task HandleNewRequest(Message request) { try { await RuntimeClient.Invoke(this, request); } catch { // Ignore. } finally { lock (_runningRequests) { _runningRequests.Remove(request); } } } internal void HandleResponse(Message response) { RuntimeClient.ReceiveResponse(response); } /// /// Registers a timer to send regular callbacks to this system target. /// /// The timer callback, which will fire whenever the timer becomes due. /// The state object passed to the callback. /// /// The amount of time to delay before the is invoked. /// Specify to prevent the timer from starting. /// Specify to invoke the callback promptly. /// /// /// The time interval between invocations of . /// Specify to disable periodic signaling. /// /// /// An object which will cancel the timer upon disposal. /// public IGrainTimer RegisterTimer(Func callback, object? state, TimeSpan dueTime, TimeSpan period) { ArgumentNullException.ThrowIfNull(callback); var timer = _shared.TimerRegistry .RegisterGrainTimer(this, static (state, _) => state.Callback(state.State), (Callback: callback, State: state), new() { DueTime = dueTime, Period = period, Interleave = true }); return timer; } /// /// Registers a timer to send regular callbacks to this system target. /// /// The timer callback, which will fire whenever the timer becomes due. /// /// The amount of time to delay before the is invoked. /// Specify to prevent the timer from starting. /// Specify to invoke the callback promptly. /// /// /// The time interval between invocations of . /// Specify to disable periodic signaling. /// /// /// An object which will cancel the timer upon disposal. /// public IGrainTimer RegisterGrainTimer(Func callback, TimeSpan dueTime, TimeSpan period) { CheckRuntimeContext(); ArgumentNullException.ThrowIfNull(callback); var timer = _shared.TimerRegistry .RegisterGrainTimer(this, (state, ct) => state(ct), callback, new() { DueTime = dueTime, Period = period, Interleave = true }); return timer; } /// /// Registers a timer to send regular callbacks to this grain. /// This timer will keep the current grain from being deactivated. /// /// The timer callback, which will fire whenever the timer becomes due. /// The state object passed to the callback. /// /// The amount of time to delay before the is invoked. /// Specify to prevent the timer from starting. /// Specify to invoke the callback promptly. /// /// /// The time interval between invocations of . /// Specify to disable periodic signaling. /// /// /// An object which will cancel the timer upon disposal. /// public IGrainTimer RegisterGrainTimer(Func callback, TState state, TimeSpan dueTime, TimeSpan period) { CheckRuntimeContext(); ArgumentNullException.ThrowIfNull(callback); var timer = _shared.TimerRegistry .RegisterGrainTimer(this, callback, state, new() { DueTime = dueTime, Period = period, Interleave = true }); return timer; } /// public sealed override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"[SystemTarget: {Silo}/{_id}{ActivationId}]", out charsWritten); /// Adds details about message currently being processed internal string ToDetailedString() { lock (_runningRequests) { return $"{this} CurrentlyExecuting={_runningRequests}"; } } /// bool IEquatable.Equals(IGrainContext? other) => ReferenceEquals(this, other); /// public (TExtension, TExtensionInterface) GetOrSetExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { TExtension implementation; if (GetComponent() is object existing) { if (existing is TExtension typedResult) { implementation = typedResult; } else { throw new InvalidCastException($"Cannot cast existing extension of type {existing.GetType()} to target type {typeof(TExtension)}"); } } else { implementation = newExtensionFunc(); SetComponent(implementation); } var reference = GrainReference.Cast(); return (implementation, reference); } /// object? ITargetHolder.GetComponent(Type componentType) { object? result; if (componentType.IsAssignableFrom(GetType())) { result = this; } else if (_components.TryGetValue(componentType, out var resultObj)) { result = resultObj; } else if (componentType == typeof(PlacementStrategy)) { result = SystemTargetPlacementStrategy.Instance; } else if (typeof(IGrainExtension).IsAssignableFrom(componentType)) { var implementation = ActivationServices.GetKeyedService(componentType); if (implementation is null) { throw new GrainExtensionNotInstalledException($"No extension of type {componentType} is installed on this instance and no implementations are registered for automated install"); } _components[componentType] = implementation; result = implementation; } else { result = default; } return result; } /// public TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension { if (GetComponent() is TExtensionInterface result) { return result; } var implementation = ActivationServices.GetKeyedService(typeof(TExtensionInterface)); if (!(implementation is TExtensionInterface typedResult)) { throw new GrainExtensionNotInstalledException($"No extension of type {typeof(TExtensionInterface)} is installed on this instance and no implementations are registered for automated install"); } SetComponent(typedResult); return typedResult; } /// public void ReceiveMessage(object message) { var msg = (Message)message; switch (msg.Direction) { case Message.Directions.Request: case Message.Directions.OneWay: { MessagingTrace.OnEnqueueMessageOnActivation(msg, this); using var suppressExecutionContext = new ExecutionContextSuppressor(); var task = new Task(_handleRequest, msg); lock (_runningRequests) { _runningRequests[msg] = task; } task.Start(WorkItemGroup.TaskScheduler); break; } default: LogInvalidMessage(_logger, msg); break; } } /// public object? GetTarget() => this; /// public void Activate(Dictionary? requestContext, CancellationToken cancellationToken) { } /// public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken) { } /// public Task Deactivated => Task.CompletedTask; public void Dispose() { if (!Constants.IsSingletonSystemTarget(GrainId.Type)) { GrainInstruments.DecrementSystemTargetCounts(Constants.SystemTargetName(GrainId.Type)); } StopAllTimers(); } public void Rehydrate(IRehydrationContext context) { // Migration is not supported, but we need to dispose of the context if it's provided (context as IDisposable)?.Dispose(); } public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken) { // Migration is not supported. Do nothing: the contract is that this method attempts migration, but does not guarantee it will occur. } void IGrainTimerRegistry.OnTimerCreated(IGrainTimer timer) { lock (_timers) { _timers.Add(timer); } } void IGrainTimerRegistry.OnTimerDisposed(IGrainTimer timer) { lock (_timers) { _timers.Remove(timer); } } private void StopAllTimers() { List timers; lock (_timers) { timers = [.. _timers]; _timers.Clear(); } foreach (var timer in timers) { timer.Dispose(); } } internal void CheckRuntimeContext() { var context = RuntimeContext.Current; if (context is null) { ThrowMissingContext(); void ThrowMissingContext() => throw new InvalidOperationException($"Access violation: attempted to access context '{this}' from null context."); } if (!ReferenceEquals(context, this)) { ThrowAccessViolation(context); void ThrowAccessViolation(IGrainContext? currentContext) => throw new InvalidOperationException($"Access violation: attempt to access context '{this}' from different context, '{currentContext}'."); } } [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Runtime_Error_100097, Message = "Invalid message: {Message}" )] private static partial void LogInvalidMessage(ILogger logger, Message Message); ValueTask IGrainCallCancellationExtension.CancelRequestAsync(GrainId senderGrainId, CorrelationId messageId) => this.RunOrQueueTask(static state => state.self.CancelRequestAsyncCore(state.senderGrainId, state.messageId), (self: this, senderGrainId, messageId)); private ValueTask CancelRequestAsyncCore(GrainId senderGrainId, CorrelationId messageId) { if (!TryCancelRequest()) { // The message being canceled may not have arrived yet, so retry a few times. return RetryCancellationAfterDelay(); } return ValueTask.CompletedTask; async ValueTask RetryCancellationAfterDelay() { var attemptsRemaining = 3; do { await Task.Delay(1_000); if (TryCancelRequest()) { return; } } while (--attemptsRemaining > 0); } bool TryCancelRequest() { Message? message = null; lock (_runningRequests) { // Check the running requests. foreach (var runningRequest in _runningRequests.Keys) { if (runningRequest.Id == messageId && runningRequest.SendingGrain == senderGrainId) { message = runningRequest; break; } } } var didCancel = false; if (message is not null) { if (message.BodyObject is IInvokable invokableRequest) { didCancel = TryCancelInvokable(invokableRequest) || !invokableRequest.IsCancellable; } else { // Assume the request is not cancellable. didCancel = true; } } return didCancel; } bool TryCancelInvokable(IInvokable request) { try { return request.TryCancel(); } catch (Exception exception) { LogErrorCancellationCallbackFailed(_logger, exception); return true; } } } [LoggerMessage( Level = LogLevel.Warning, Message = "One or more cancellation callbacks failed." )] private static partial void LogErrorCancellationCallbackFailed(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Development/DevelopmentSiloBuilderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.LeaseProviders; namespace Orleans.Runtime.Development { /// /// extensions to configure an in-memory lease provider. /// public static class DevelopmentSiloBuilderExtensions { /// /// Configures silo with test/development features. /// /// /// Not for production use. This is for development and test scenarios only. /// /// The builder. /// The builder. public static ISiloBuilder UseInMemoryLeaseProvider(this ISiloBuilder builder) { return builder.ConfigureServices(UseInMemoryLeaseProvider); } private static void UseInMemoryLeaseProvider(IServiceCollection services) { services.AddTransient(); } } } ================================================ FILE: src/Orleans.Runtime/Development/InMemoryLeaseProvider.cs ================================================ using Orleans.LeaseProviders; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Runtime.Development { /// /// In memory lease provider for development and test use. /// This provider stores lease information in memory an can be lost if grain /// becomes inactive or if silo crashes. This implementation is only intended /// for test or local development purposes - NOT FOR PRODUCTION USE. /// public class InMemoryLeaseProvider : ILeaseProvider { private readonly IDevelopmentLeaseProviderGrain leaseProvider; /// /// Initializes a new instance of the class. /// /// The grain factory. public InMemoryLeaseProvider(IGrainFactory grainFactory) { this.leaseProvider = GetLeaseProviderGrain(grainFactory); } /// public async Task Acquire(string category, LeaseRequest[] leaseRequests) { try { return await this.leaseProvider.Acquire(category, leaseRequests); } catch (Exception ex) { return leaseRequests.Select(request => new AcquireLeaseResult(new AcquiredLease(request.ResourceKey), ResponseCode.TransientFailure, ex)).ToArray(); } } /// public Task Release(string category, AcquiredLease[] acquiredLeases) { return this.leaseProvider.Release(category, acquiredLeases); } /// public async Task Renew(string category, AcquiredLease[] acquiredLeases) { try { return await this.leaseProvider.Renew(category, acquiredLeases); } catch (Exception ex) { return acquiredLeases.Select(request => new AcquireLeaseResult(new AcquiredLease(request.ResourceKey), ResponseCode.TransientFailure, ex)).ToArray(); } } private static IDevelopmentLeaseProviderGrain GetLeaseProviderGrain(IGrainFactory grainFactory) { return grainFactory.GetGrain(0); } } internal interface IDevelopmentLeaseProviderGrain : ILeaseProvider, IGrainWithIntegerKey { /// /// Forgets about all leases. Used to simulate loss of this grain or to force rebalance of queues /// /// Task Reset(); } /// /// Grain that stores lease information in memory. /// TODO: Consider making this a stateful grain, as a production viable implementation of lease provider that works with storage /// providers. /// internal class DevelopmentLeaseProviderGrain : Grain, IDevelopmentLeaseProviderGrain { private readonly Dictionary, Lease> leases = new Dictionary, Lease>(); public Task Acquire(string category, LeaseRequest[] leaseRequests) { return Task.FromResult(leaseRequests.Select(request => Acquire(category, request)).ToArray()); } public Task Release(string category, AcquiredLease[] acquiredLeases) { foreach(AcquiredLease lease in acquiredLeases) { Release(category, lease); } return Task.CompletedTask; } public Task Renew(string category, AcquiredLease[] acquiredLeases) { return Task.FromResult(acquiredLeases.Select(lease => Renew(category, lease)).ToArray()); } public Task Reset() { this.leases.Clear(); return Task.CompletedTask; } private AcquireLeaseResult Acquire(string category, LeaseRequest leaseRequest) { DateTime now = DateTime.UtcNow; Lease lease = this.leases.GetValueOrAddNew(Tuple.Create(category, leaseRequest.ResourceKey)); if(lease.ExpiredUtc < now) { lease.ExpiredUtc = now + leaseRequest.Duration; return new AcquireLeaseResult(new AcquiredLease(leaseRequest.ResourceKey, leaseRequest.Duration, lease.Token, now), ResponseCode.OK, null); } return new AcquireLeaseResult(new AcquiredLease(leaseRequest.ResourceKey), ResponseCode.LeaseNotAvailable, new OrleansException("Lease not available")); } private void Release(string category, AcquiredLease acquiredLease) { Tuple leaseKey = Tuple.Create(category, acquiredLease.ResourceKey); if (this.leases.TryGetValue(leaseKey, out Lease lease) && lease.Token == acquiredLease.Token) { leases.Remove(leaseKey); } } private AcquireLeaseResult Renew(string category, AcquiredLease acquiredLease) { DateTime now = DateTime.UtcNow; // if lease exists, and we have the right token, and lease has not expired, renew. if (!this.leases.TryGetValue(Tuple.Create(category, acquiredLease.ResourceKey), out Lease lease) || lease.Token != acquiredLease.Token) { return new AcquireLeaseResult(new AcquiredLease(acquiredLease.ResourceKey), ResponseCode.InvalidToken, new OrleansException("Invalid token provided, caller is not the owner.")); } // we don't care if lease has expired or not as long as owner has not changed. lease.ExpiredUtc = now + acquiredLease.Duration; return new AcquireLeaseResult(new AcquiredLease(acquiredLease.ResourceKey, acquiredLease.Duration, lease.Token, now), ResponseCode.OK, null); } private class Lease { private DateTime expiredUtc; public DateTime ExpiredUtc { get { return expiredUtc; } set { expiredUtc = value; Token = Guid.NewGuid().ToString(); } } public string Token { get; private set; } } } } ================================================ FILE: src/Orleans.Runtime/Facet/GrainConstructorArgumentFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime { /// /// Constructs instances of a grain class using constructor dependency injection. /// public class GrainConstructorArgumentFactory { private static readonly Type FacetMarkerInterfaceType = typeof(IFacetMetadata); private static readonly MethodInfo GetFactoryMethod = typeof(GrainConstructorArgumentFactory).GetMethod(nameof(GetArgumentFactory), BindingFlags.NonPublic | BindingFlags.Static); private readonly List> _argumentFactories; /// /// Initializes a new instance. /// /// The service provider. /// The grain type. public GrainConstructorArgumentFactory(IServiceProvider serviceProvider, Type grainType) { _argumentFactories = new List>(); // Find the constructor - supports only single public constructor. var parameters = grainType.GetConstructors().FirstOrDefault()?.GetParameters() ?? Enumerable.Empty(); var types = new List(); foreach (var parameter in parameters) { // Look for attribute with a facet marker interface - supports only single facet attribute var attribute = parameter.GetCustomAttributes() .FirstOrDefault(static attribute => FacetMarkerInterfaceType.IsInstanceOfType(attribute)); if (attribute is null) continue; // Since the IAttributeToFactoryMapper is specific to the attribute specialization, we create a generic method to provide a attribute independent call pattern. var getFactory = GetFactoryMethod.MakeGenericMethod(attribute.GetType()); var argumentFactory = (Factory)getFactory.Invoke(this, [serviceProvider, parameter, attribute, grainType]); // Record the argument factory _argumentFactories.Add(argumentFactory); // Record the argument type types.Add(parameter.ParameterType); } ArgumentTypes = types.ToArray(); } /// /// Gets the constructor argument types. /// public Type[] ArgumentTypes { get; } /// /// Creates the arguments for the grain constructor. /// /// The grain context. /// The constructor arguments. public object[] CreateArguments(IGrainContext grainContext) { var i = 0; var results = new object[_argumentFactories.Count]; foreach (var argumentFactory in _argumentFactories) { results[i++] = argumentFactory(grainContext); } return results; } private static Factory GetArgumentFactory(IServiceProvider services, ParameterInfo parameter, IFacetMetadata metadata, Type type) where TMetadata : IFacetMetadata { var factoryMapper = services.GetService>(); if (factoryMapper is null) { throw new OrleansException($"Missing attribute mapper for attribute {metadata.GetType()} used in grain constructor for grain type {type}."); } var factory = factoryMapper.GetFactory(parameter, (TMetadata)metadata); if (factory is null) { throw new OrleansException($"Attribute mapper {factoryMapper.GetType()} failed to create a factory for grain type {type}."); } return factory; } } } ================================================ FILE: src/Orleans.Runtime/Facet/IAttributeToFactoryMapper.cs ================================================ using System.Reflection; namespace Orleans.Runtime { /// /// Responsible for mapping a facet metadata to a cachable factory. /// public interface IAttributeToFactoryMapper where TMetadata : IFacetMetadata { /// /// Responsible for mapping a facet metadata to a cachable factory from the parameter and facet metadata. /// /// The parameter info. /// The metadata. /// The factory used to create facet instances for a grain. Factory GetFactory(ParameterInfo parameter, TMetadata metadata); } } ================================================ FILE: src/Orleans.Runtime/Facet/IFacetMetadata.cs ================================================  namespace Orleans { /// /// Marker interface for facets /// public interface IFacetMetadata { } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/IPersistentState.cs ================================================ using Orleans.Core; namespace Orleans.Runtime { /// /// Provides access to grain state with functionality to save, clear, and refresh the state. /// /// The underlying state type. /// public interface IPersistentState : IStorage { } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/IPersistentStateConfiguration.cs ================================================ namespace Orleans.Runtime { /// /// Configuration for persistent state. /// /// public interface IPersistentStateConfiguration { /// /// Gets the name of the state. /// /// The name of the state. string StateName { get; } /// /// Gets the name of the storage provider. /// /// The name of the storage provider. string StorageName { get; } } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/IPersistentStateFactory.cs ================================================ namespace Orleans.Runtime { /// /// Factory for constructing instances for a grain. /// public interface IPersistentStateFactory { /// /// Creates a persistent state instance for the provided grain. /// /// The underlying state type. /// The grain context. /// The state facet configuration. /// A persistent state instance for the provided grain with the specified configuration. IPersistentState Create(IGrainContext context, IPersistentStateConfiguration config); } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/PersistentStateAttribute.cs ================================================ using System; namespace Orleans.Runtime { /// /// Specifies options for the constructor argument which it is applied to. /// /// /// /// [AttributeUsage(AttributeTargets.Parameter)] public class PersistentStateAttribute : Attribute, IFacetMetadata, IPersistentStateConfiguration { /// /// Initializes a new instance of the class. /// /// Name of the state. /// Name of the storage provider. public PersistentStateAttribute(string stateName, string storageName = null) { ArgumentNullException.ThrowIfNull(stateName); this.StateName = stateName; this.StorageName = storageName; } /// /// Gets the name of the state. /// /// The name of the state. public string StateName { get; } /// /// Gets the name of the storage provider. /// /// The name of the storage provider. public string StorageName { get; } } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/PersistentStateAttributeMapper.cs ================================================ using System; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; namespace Orleans { /// /// Attribute mapper which maps persistent state attributes to a corresponding factory instance. /// public class PersistentStateAttributeMapper : IAttributeToFactoryMapper { private static readonly MethodInfo CreateMethodInfo = typeof(IPersistentStateFactory).GetMethod("Create"); /// public Factory GetFactory(ParameterInfo parameter, PersistentStateAttribute attribute) { IPersistentStateConfiguration config = attribute; // set state name to parameter name, if not already specified if (string.IsNullOrEmpty(config.StateName)) { config = new PersistentStateConfiguration() { StateName = parameter.Name, StorageName = attribute.StorageName }; } if (!parameter.ParameterType.IsGenericType || !typeof(IPersistentState<>).Equals(parameter.ParameterType.GetGenericTypeDefinition())) { throw new ArgumentException( $"Parameter '{parameter.Name}' on the constructor for '{parameter.Member.DeclaringType}' has an unsupported type, '{parameter.ParameterType}'. " + $"It must be an instance of generic type '{typeof(IPersistentState<>)}' because it has an associated [PersistentState(...)] attribute.", parameter.Name); } // use generic type args to define collection type. MethodInfo genericCreate = CreateMethodInfo.MakeGenericMethod(parameter.ParameterType.GetGenericArguments()); return context => Create(context, genericCreate, config); } private static object Create(IGrainContext context, MethodInfo genericCreate, IPersistentStateConfiguration config) { IPersistentStateFactory factory = context.ActivationServices.GetRequiredService(); object[] args = [context, config]; return genericCreate.Invoke(factory, args); } private class PersistentStateConfiguration : IPersistentStateConfiguration { public string StateName { get; set; } public string StorageName { get; set; } } } } ================================================ FILE: src/Orleans.Runtime/Facet/Persistent/PersistentStateStorageFactory.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Core; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using Orleans.Storage; #nullable enable namespace Orleans.Runtime { /// /// Creates instances for grains. /// /// public class PersistentStateFactory : IPersistentStateFactory { /// public IPersistentState Create(IGrainContext context, IPersistentStateConfiguration cfg) { var storageProvider = !string.IsNullOrWhiteSpace(cfg.StorageName) ? context.ActivationServices.GetKeyedService(cfg.StorageName) : context.ActivationServices.GetService(); if (storageProvider == null) { ThrowMissingProviderException(context, cfg); } var fullStateName = GetFullStateName(context, cfg); return new PersistentState(fullStateName, context, storageProvider); } protected virtual string GetFullStateName(IGrainContext context, IPersistentStateConfiguration cfg) { return cfg.StateName; } [DoesNotReturn] private static void ThrowMissingProviderException(IGrainContext context, IPersistentStateConfiguration cfg) { string errMsg; if (string.IsNullOrEmpty(cfg.StorageName)) { errMsg = $"No default storage provider found loading grain type {context.GrainId.Type}."; } else { errMsg = $"No storage provider named \"{cfg.StorageName}\" found loading grain type {context.GrainId.Type}."; } throw new BadProviderConfigException(errMsg); } } internal sealed class PersistentState : StateStorageBridge, IPersistentState, ILifecycleObserver { public PersistentState(string stateName, IGrainContext context, IGrainStorage storageProvider) : base(stateName, context, storageProvider) { var lifecycle = context.ObservableLifecycle; lifecycle.Subscribe(RuntimeTypeNameFormatter.Format(GetType()), GrainLifecycleStage.SetupState, this); lifecycle.AddMigrationParticipant(this); } public Task OnStart(CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; } // No need to load state if it has been loaded already via rehydration. if (IsStateInitialized) { return Task.CompletedTask; } return ReadStateAsync(); } public Task OnStop(CancellationToken cancellationToken = default) => Task.CompletedTask; } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/CachedGrainLocator.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.Internal; namespace Orleans.Runtime.GrainDirectory { /// /// Implementation of that uses stores. /// internal class CachedGrainLocator : IGrainLocator, ILifecycleParticipant, CachedGrainLocator.ITestAccessor { private readonly GrainDirectoryResolver grainDirectoryResolver; private readonly IGrainDirectoryCache cache; private readonly CancellationTokenSource shutdownToken = new CancellationTokenSource(); private readonly IClusterMembershipService clusterMembershipService; private Task listenToClusterChangeTask; internal interface ITestAccessor { MembershipVersion LastMembershipVersion { get; set; } } MembershipVersion ITestAccessor.LastMembershipVersion { get; set; } public CachedGrainLocator( IServiceProvider serviceProvider, GrainDirectoryResolver grainDirectoryResolver, IClusterMembershipService clusterMembershipService, IOptions grainDirectoryOptions) { this.grainDirectoryResolver = grainDirectoryResolver; this.clusterMembershipService = clusterMembershipService; this.cache = GrainDirectoryCacheFactory.CreateCustomGrainDirectoryCache(serviceProvider, grainDirectoryOptions.Value); } public async ValueTask Lookup(GrainId grainId) { var grainType = grainId.Type; if (grainType.IsClient() || grainType.IsSystemTarget()) { ThrowUnsupportedGrainType(grainId); } // Check cache first if (TryLookupInCache(grainId, out var cachedResult)) { return cachedResult; } var entry = await GetGrainDirectory(grainId.Type).Lookup(grainId); // Nothing found if (entry is null) { return null; } // Check if the entry is pointing to a dead silo if (IsKnownDeadSilo(entry)) { // Remove it from the directory await GetGrainDirectory(grainId.Type).Unregister(entry); entry = null; } else { // Add to the local cache and return it this.cache.AddOrUpdate(entry, 0); } return entry; } public async Task Register(GrainAddress address, GrainAddress previousAddress) { var grainType = address.GrainId.Type; if (grainType.IsClient() || grainType.IsSystemTarget()) { ThrowUnsupportedGrainType(address.GrainId); } address = new() { GrainId = address.GrainId, ActivationId = address.ActivationId, SiloAddress = address.SiloAddress, MembershipVersion = clusterMembershipService.CurrentSnapshot.Version }; var result = await GetGrainDirectory(grainType).Register(address, previousAddress); if (result is null) { // If the registration failed, we return a null address return null; } // Check if the entry point to a dead silo if (IsKnownDeadSilo(result)) { // Remove outdated entry and retry to register await GetGrainDirectory(grainType).Unregister(result); result = await GetGrainDirectory(grainType).Register(address, previousAddress); } // Cache update this.cache.AddOrUpdate(result, (int)result.MembershipVersion.Value); return result; } public async Task Unregister(GrainAddress address, UnregistrationCause cause) { // Remove from local cache first so we don't return it anymore this.cache.Remove(address); // Remove from grain directory which may take significantly longer await GetGrainDirectory(address.GrainId.Type).Unregister(address); // There is the potential for a lookup to race with the Unregister and add the bad entry back to the cache. if (this.cache.LookUp(address.GrainId, out var entry, out _) && entry.Equals(address)) { this.cache.Remove(address); } } public void Participate(ISiloLifecycle lifecycle) { Task OnStart(CancellationToken ct) { this.listenToClusterChangeTask = ListenToClusterChange(); return Task.CompletedTask; }; async Task OnStop(CancellationToken ct) { this.shutdownToken.Cancel(); if (listenToClusterChangeTask != default && !ct.IsCancellationRequested) { await listenToClusterChangeTask.WaitAsync(ct).SuppressThrowing(); } }; lifecycle.Subscribe(nameof(CachedGrainLocator), ServiceLifecycleStage.RuntimeGrainServices, OnStart, OnStop); } private IGrainDirectory GetGrainDirectory(GrainType grainType) => this.grainDirectoryResolver.Resolve(grainType); private async Task ListenToClusterChange() { var previousSnapshot = this.clusterMembershipService.CurrentSnapshot; ((ITestAccessor)this).LastMembershipVersion = previousSnapshot.Version; var updates = this.clusterMembershipService.MembershipUpdates.WithCancellation(this.shutdownToken.Token); await foreach (var snapshot in updates) { // Active filtering: detect silos that went down and try to clean proactively the directory var changes = snapshot.CreateUpdate(previousSnapshot).Changes; var deadSilos = changes .Where(member => member.Status.IsTerminating()) .Select(member => member.SiloAddress) .ToList(); if (deadSilos.Count > 0) { var tasks = new List(); foreach (var directory in this.grainDirectoryResolver.Directories) { tasks.Add(directory.UnregisterSilos(deadSilos)); } await Task.WhenAll(tasks).WaitAsync(this.shutdownToken.Token); } ((ITestAccessor)this).LastMembershipVersion = snapshot.Version; } } private bool IsKnownDeadSilo(GrainAddress grainAddress) => IsKnownDeadSilo(grainAddress.SiloAddress, grainAddress.MembershipVersion); private bool IsKnownDeadSilo(SiloAddress siloAddress, MembershipVersion membershipVersion) { var current = this.clusterMembershipService.CurrentSnapshot; // Check if the target silo is in the cluster if (current.Members.TryGetValue(siloAddress, out var value)) { // It is, check if it's alive return value.Status.IsTerminating(); } // We didn't find it in the cluster. If the silo entry is too old, it has been cleaned in the membership table: the entry isn't valid anymore. // Otherwise, maybe the membership service isn't up to date yet. The entry should be valid return current.Version > membershipVersion; } private static void ThrowUnsupportedGrainType(GrainId grainId) => throw new InvalidOperationException($"Unsupported grain type for grain {grainId}"); public void UpdateCache(GrainId grainId, SiloAddress siloAddress) => cache.AddOrUpdate(new GrainAddress { GrainId = grainId, SiloAddress = siloAddress }, 0); public void InvalidateCache(GrainId grainId) => cache.Remove(grainId); public void InvalidateCache(GrainAddress address) => cache.Remove(address); public bool TryLookupInCache(GrainId grainId, out GrainAddress address) { var grainType = grainId.Type; if (grainType.IsClient() || grainType.IsSystemTarget()) { ThrowUnsupportedGrainType(grainId); } if (this.cache.LookUp(grainId, out address, out var version)) { // If the silo is dead, remove the entry if (IsKnownDeadSilo(address.SiloAddress, new MembershipVersion(version))) { address = default; this.cache.Remove(grainId); } else { // Entry found and valid -> return it return true; } } return false; } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/ClientDirectory.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Messaging; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime.GrainDirectory; /// /// A directory for routes to clients (external clients and hosted clients). /// /// /// maintains routing information for all known clients and offers consumers the ability to lookup /// clients by their . /// To accomplish this, monitors locally connected clients and cluster membership changes. In addition, /// known routes are periodically shared with remote silos in a ring-fashion. Each silo will push updates to the next silo in the ring. /// When a silo receives an update, it incorporates it into its routing table. If the update caused a change in the routing table, then /// the silo will propagate its updates routing table to the next silo. This process continues until all silos converge. /// Each maintains an internal version number which represents its view of the locally connected clients. /// This version number is propagated around the ring during updates and is used to determine when a remote silo's set of locally connected clients /// has updated. /// The process of removing defunct clients is left to the implementation on each silo. /// internal sealed partial class ClientDirectory : SystemTarget, ILocalClientDirectory, IRemoteClientDirectory, ILifecycleParticipant { private readonly SimpleConsistentRingProvider _consistentRing; private readonly IInternalGrainFactory _grainFactory; private readonly ILogger _logger; private readonly IAsyncTimer _refreshTimer; private readonly SiloAddress _localSilo; private readonly IClusterMembershipService _clusterMembershipService; private readonly SiloMessagingOptions _messagingOptions; private readonly CancellationTokenSource _shutdownCts = new(); #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly GrainId _localHostedClientId; private readonly IConnectedClientCollection _connectedClients; private Action _schedulePublishUpdate; private Task? _runTask; private MembershipVersion _observedMembershipVersion = MembershipVersion.MinValue; private long _observedConnectedClientsVersion = -1; private long _localVersion = 1; private IRemoteClientDirectory[] _remoteDirectories = Array.Empty(); private ImmutableHashSet _localClients = ImmutableHashSet.Empty; private ImmutableDictionary> _currentSnapshot = ImmutableDictionary>.Empty; private ImmutableDictionary ConnectedClients, long Version)> _table = ImmutableDictionary ConnectedClients, long Version)>.Empty; // For synchronization with remote silos. private Task? _nextPublishTask; private SiloAddress? _previousSuccessor; private ImmutableDictionary ConnectedClients, long Version)>? _publishedTable; public ClientDirectory( IInternalGrainFactory grainFactory, ILocalSiloDetails siloDetails, IOptions messagingOptions, ILoggerFactory loggerFactory, IClusterMembershipService clusterMembershipService, IAsyncTimerFactory timerFactory, IConnectedClientCollection connectedClients, SystemTargetShared shared) : base(Constants.ClientDirectoryType, shared) { _consistentRing = new SimpleConsistentRingProvider(siloDetails, clusterMembershipService); _grainFactory = grainFactory; _localSilo = siloDetails.SiloAddress; _clusterMembershipService = clusterMembershipService; _messagingOptions = messagingOptions.Value; _logger = loggerFactory.CreateLogger(); _refreshTimer = timerFactory.Create(_messagingOptions.ClientRegistrationRefresh, "ClientDirectory.RefreshTimer"); _connectedClients = connectedClients; _localHostedClientId = HostedClient.CreateHostedClientGrainId(_localSilo).GrainId; _schedulePublishUpdate = SchedulePublishUpdates; shared.ActivationDirectory.RecordNewTarget(this); } public ValueTask> Lookup(GrainId grainId) { if (TryLocalLookup(grainId, out var clientRoutes)) { return new ValueTask>(clientRoutes); } return LookupClientAsync(grainId); async ValueTask> LookupClientAsync(GrainId grainId) { var seed = Random.Shared.Next(); var attemptsRemaining = 5; List? result = null; while (attemptsRemaining-- > 0 && _remoteDirectories is var remoteDirectories && remoteDirectories.Length > 0) { try { // Cycle through remote directories. var remoteDirectory = remoteDirectories[(ushort)seed++ % remoteDirectories.Length]; // Ask the remote directory for updates to our view. var versionVector = _table.ToImmutableDictionary(e => e.Key, e => e.Value.Version); var delta = await remoteDirectory.GetClientRoutes(versionVector); // If updates were found, update our view if (delta is not null && delta.Count > 0) { UpdateRoutingTable(delta); } } catch (Exception exception) when (attemptsRemaining > 0) { LogErrorCallingRemoteClientDirectory(exception); } // Try again to find the requested client's routes. // Note that this occurs whether the remote update call succeeded or failed. if (TryLocalLookup(grainId, out result) && result.Count > 0) { break; } } if (ShouldPublish()) { _schedulePublishUpdate(); } // Try one last time to find the requested client's routes. if (result is null && !TryLocalLookup(grainId, out result)) { result = []; } return result; } } public bool TryLocalLookup(GrainId grainId, [NotNullWhen(true)] out List? addresses) { EnsureRefreshed(); if (_currentSnapshot.TryGetValue(grainId, out var clientRoutes) && clientRoutes.Count > 0) { addresses = clientRoutes; return true; } addresses = null; return false; } private void EnsureRefreshed() { if (IsStale()) { lock (_lockObj) { if (IsStale()) { UpdateRoutingTable(update: null); } } } bool IsStale() { return _observedMembershipVersion < _clusterMembershipService.CurrentSnapshot.Version || _observedConnectedClientsVersion != _connectedClients.Version; } } public Task OnUpdateClientRoutes(ImmutableDictionary ConnectedClients, long Version)> update) { UpdateRoutingTable(update); if (ShouldPublish()) { LogDebugClientTableUpdated(); _schedulePublishUpdate(); } else { LogDebugClientTableNotUpdated(); } return Task.CompletedTask; } public Task ConnectedClients, long Version)>> GetClientRoutes(ImmutableDictionary knownRoutes) { EnsureRefreshed(); // Return a collection containing all missing or out-dated routes, based on the known-routes version vector provided by the caller. var table = _table; var resultBuilder = ImmutableDictionary.CreateBuilder ConnectedClients, long Version)>(); foreach (var entry in table) { var silo = entry.Key; var routes = entry.Value; var version = routes.Version; if (!knownRoutes.TryGetValue(silo, out var knownVersion) || knownVersion < version) { resultBuilder[silo] = routes; } } return Task.FromResult(resultBuilder.ToImmutable()); } private void UpdateRoutingTable(ImmutableDictionary ConnectedClients, long Version)>? update) { lock (_lockObj) { var membershipSnapshot = _clusterMembershipService.CurrentSnapshot; var table = default(ImmutableDictionary ConnectedClients, long Version)>.Builder); // Incorporate updates. if (update is not null) { foreach (var pair in update) { var silo = pair.Key; var updatedView = pair.Value; // Include only updates for non-defunct silos. if ((!_table.TryGetValue(silo, out var localView) || localView.Version < updatedView.Version) && !membershipSnapshot.GetSiloStatus(silo).IsTerminating()) { table ??= _table.ToBuilder(); table[silo] = updatedView; } } } // Ensure that the remote directories are up-to-date. if (membershipSnapshot.Version > _observedMembershipVersion) { var remotesBuilder = new List(membershipSnapshot.Members.Count); foreach (var member in membershipSnapshot.Members.Values) { if (member.SiloAddress.Equals(_localSilo)) continue; if (member.Status != SiloStatus.Active) continue; remotesBuilder.Add(_grainFactory.GetSystemTarget(Constants.ClientDirectoryType, member.SiloAddress)); } _remoteDirectories = remotesBuilder.ToArray(); } // Remove defunct silos. foreach (var member in membershipSnapshot.Members.Values) { var silo = member.SiloAddress; if (member.Status.IsTerminating()) { // Remove the silo only if it is in the table. This prevents us from rebuilding data structures unnecessarily. if (_table.ContainsKey(silo)) { table ??= _table.ToBuilder(); table.Remove(silo); } } else if (member.Status == SiloStatus.Active) { // If the silo has just become active and we have not yet received a set of connected clients from it, // add the hosted client automatically, to expedite the process. if (!_table.ContainsKey(silo) && (table is null || !table.ContainsKey(silo))) { table ??= _table.ToBuilder(); // Note that it is added with version 0, which is below the initial version generated by each silo, 1. table[silo] = (ImmutableHashSet.Create(HostedClient.CreateHostedClientGrainId(silo).GrainId), 0); } } } _observedMembershipVersion = membershipSnapshot.Version; // Update locally connected clients. var (clients, version) = GetConnectedClients(_localClients, _localVersion); if (version > _localVersion) { table ??= _table.ToBuilder(); table[_localSilo] = (clients, version); _localClients = clients; _localVersion = version; } // If there were changes to the routing table then the table and snapshot need to be rebuilt. if (table is not null) { _table = table.ToImmutable(); var clientsBuilder = ImmutableDictionary.CreateBuilder>(); foreach (var entry in _table) { foreach (var client in entry.Value.ConnectedClients) { if (!clientsBuilder.TryGetValue(client, out var clientRoutes)) { clientRoutes = clientsBuilder[client] = []; } clientRoutes.Add(Gateway.GetClientActivationAddress(client, entry.Key)); } } _currentSnapshot = clientsBuilder.ToImmutable(); } } } /// /// Gets the collection of locally connected clients. /// private (ImmutableHashSet Clients, long Version) GetConnectedClients(ImmutableHashSet previousClients, long previousVersion) { var connectedClientsVersion = _connectedClients.Version; if (connectedClientsVersion <= _observedConnectedClientsVersion) { return (previousClients, previousVersion); } var clients = ImmutableHashSet.CreateBuilder(); clients.Add(_localHostedClientId); foreach (var client in _connectedClients.GetConnectedClientIds()) { clients.Add(client); } // Regardless of whether changes occurred, mark this version as observed. _observedConnectedClientsVersion = connectedClientsVersion; // If no changes actually occurred, avoid signaling a change. if (clients.Count == previousClients.Count && previousClients.SetEquals(clients)) { return (previousClients, previousVersion); } return (clients.ToImmutable(), previousVersion + 1); } private async Task Run() { var membershipUpdates = _clusterMembershipService.MembershipUpdates.GetAsyncEnumerator(_shutdownCts.Token); Task? membershipTask = null; Task? timerTask = _refreshTimer.NextTick(RandomTimeSpan.Next(_messagingOptions.ClientRegistrationRefresh)); while (!_shutdownCts.IsCancellationRequested) { try { membershipTask ??= membershipUpdates.MoveNextAsync().AsTask(); timerTask ??= _refreshTimer.NextTick(); // Wait for either of the tasks to complete. await Task.WhenAny(membershipTask, timerTask); if (timerTask.IsCompleted) { if (!await timerTask) { break; } timerTask = null; } if (membershipTask.IsCompleted) { if (!await membershipTask) { break; } membershipTask = null; } if (ShouldPublish()) { await PublishUpdates(); } } catch (OperationCanceledException) when (_shutdownCts.IsCancellationRequested) { // Ignore during shutdown. break; } catch (Exception exception) { LogErrorPublishingClientRoutingTable(exception); } } } private bool ShouldPublish() { EnsureRefreshed(); lock (_lockObj) { if (_nextPublishTask is Task task && !task.IsCompleted) { return false; } if (!ReferenceEquals(_table, _publishedTable)) { return true; } // If there is no successor, or the successor is equal to the successor the last time the table was published, // then there is no need to publish. var successor = _consistentRing.Successor; if (successor is null || successor.Equals(_previousSuccessor)) { return false; } return true; } } private void SchedulePublishUpdates() { lock (_lockObj) { if (_nextPublishTask is Task task && !task.IsCompleted) { return; } _nextPublishTask = this.RunOrQueueTask(PublishUpdates); } } private async Task PublishUpdates() { // Publish clients to the next two silos in the ring var successor = _consistentRing.Successor; if (successor is null) { return; } if (successor.Equals(_previousSuccessor)) { _publishedTable = null; } var newRoutes = _table; var previousRoutes = _publishedTable; if (ReferenceEquals(previousRoutes, newRoutes)) { LogDebugSkippingPublishingRoutes(); return; } // Try to find the minimum amount of information required to update the successor. var builder = newRoutes.ToBuilder(); if (previousRoutes is not null) { foreach (var pair in previousRoutes) { var silo = pair.Key; var (_, version) = pair.Value; if (silo.Equals(successor)) { // No need to publish updates to the silo which originated them. continue; } if (!builder.TryGetValue(silo, out var published)) { continue; } if (version == published.Version) { // The target has already seen the latest version for this silo. builder.Remove(silo); } } } try { LogDebugPublishingRoutes(successor); var remote = _grainFactory.GetSystemTarget(Constants.ClientDirectoryType, successor); await remote.OnUpdateClientRoutes(_table).WaitAsync(_shutdownCts.Token); // Record the current lower bound of what the successor knows, so that it can be used to minimize // data transfer next time an update is performed. if (ReferenceEquals(_publishedTable, previousRoutes)) { _publishedTable = newRoutes; _previousSuccessor = successor; } LogDebugSuccessfullyPublishedRoutes(successor); _nextPublishTask = null; if (ShouldPublish()) { _schedulePublishUpdate(); } } catch (Exception exception) { LogErrorPublishingClientRoutingTableToSilo(exception, successor); } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(ClientDirectory), ServiceLifecycleStage.RuntimeGrainServices, ct => { this.RunOrQueueTask(() => _runTask = this.Run()).Ignore(); return Task.CompletedTask; }, async ct => { _shutdownCts.Cancel(); _refreshTimer?.Dispose(); if (_runTask is Task task) { await task.WaitAsync(ct).SuppressThrowing(); } if (_nextPublishTask is Task publishTask) { await publishTask.WaitAsync(ct).SuppressThrowing(); } }); } internal class TestAccessor(ClientDirectory instance) { public Action SchedulePublishUpdate { get => instance._schedulePublishUpdate; set => instance._schedulePublishUpdate = value; } public long ObservedConnectedClientsVersion { get => instance._observedConnectedClientsVersion; set => instance._observedConnectedClientsVersion = value; } public Task PublishUpdates() => instance.PublishUpdates(); } [LoggerMessage( Level = LogLevel.Error, Message = "Exception calling remote client directory" )] private partial void LogErrorCallingRemoteClientDirectory(Exception exception); [LoggerMessage( EventId = 0, Level = LogLevel.Error, Message = "Exception publishing client routing table")] private partial void LogErrorPublishingClientRoutingTable(Exception exception); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Skipping publishing of routes because target silo already has them")] private partial void LogDebugSkippingPublishingRoutes(); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Publishing routes to {Silo}")] private partial void LogDebugPublishingRoutes(SiloAddress silo); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Successfully published routes to {Silo}")] private partial void LogDebugSuccessfullyPublishedRoutes(SiloAddress silo); [LoggerMessage( EventId = 0, Level = LogLevel.Error, Message = "Exception publishing client routing table to silo {SiloAddress}")] private partial void LogErrorPublishingClientRoutingTableToSilo(Exception exception, SiloAddress siloAddress); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Client table updated, publishing to successor" )] private partial void LogDebugClientTableUpdated(); [LoggerMessage( EventId = 0, Level = LogLevel.Debug, Message = "Client table not updated" )] private partial void LogDebugClientTableNotUpdated(); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/ClientGrainLocator.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.GrainDirectory; namespace Orleans.Runtime.GrainDirectory { /// /// Implementation of that uses the in memory distributed directory of Orleans /// internal class ClientGrainLocator : IGrainLocator { private readonly SiloAddress _localSiloAddress; private readonly ILocalClientDirectory _clientDirectory; public ClientGrainLocator(ILocalSiloDetails localSiloDetails, ILocalClientDirectory clientDirectory) { _localSiloAddress = localSiloDetails.SiloAddress; _clientDirectory = clientDirectory; } public async ValueTask Lookup(GrainId grainId) { if (!ClientGrainId.TryParse(grainId, out var clientGrainId)) { ThrowNotClientGrainId(grainId); } var results = await _clientDirectory.Lookup(clientGrainId.GrainId); return SelectAddress(results, grainId); } private GrainAddress SelectAddress(List results, GrainId grainId) { GrainAddress unadjustedResult = null; if (results is { Count: > 0 }) { foreach (var location in results) { if (location.SiloAddress.Equals(_localSiloAddress)) { unadjustedResult = location; break; } } if (unadjustedResult == null) { unadjustedResult = results[Random.Shared.Next(results.Count)]; } } if (unadjustedResult is not null) { return GrainAddress.GetAddress(unadjustedResult.SiloAddress, grainId, unadjustedResult.ActivationId); } return null; } public Task Register(GrainAddress address, GrainAddress previousAddress) => throw new InvalidOperationException($"Cannot register client grain explicitly"); public Task Unregister(GrainAddress address, UnregistrationCause cause) => throw new InvalidOperationException($"Cannot unregister client grain explicitly"); private static void ThrowNotClientGrainId(GrainId grainId) => throw new InvalidOperationException($"{grainId} is not a client id"); public void UpdateCache(GrainId grainId, SiloAddress siloAddress) { } public void InvalidateCache(GrainId grainId) { } public void InvalidateCache(GrainAddress address) { } public bool TryLookupInCache(GrainId grainId, out GrainAddress address) { if (!ClientGrainId.TryParse(grainId, out var clientGrainId)) { ThrowNotClientGrainId(grainId); } if (_clientDirectory.TryLocalLookup(clientGrainId.GrainId, out var addresses)) { address = SelectAddress(addresses, grainId); return address is not null; } address = null; return false; } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/DhtGrainLocator.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Orleans.GrainDirectory; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime.GrainDirectory { /// /// Implementation of that uses the in memory distributed directory of Orleans /// internal class DhtGrainLocator : IGrainLocator { private readonly ILocalGrainDirectory _localGrainDirectory; private readonly IGrainContext _grainContext; #if NET9_0_OR_GREATER private readonly Lock _initLock = new(); #else private readonly object _initLock = new(); #endif private BatchedDeregistrationWorker _forceWorker; private BatchedDeregistrationWorker _neaWorker; public DhtGrainLocator( ILocalGrainDirectory localGrainDirectory, IGrainContext grainContext) { _localGrainDirectory = localGrainDirectory; _grainContext = grainContext; } public async ValueTask Lookup(GrainId grainId) => (await _localGrainDirectory.LookupAsync(grainId)).Address; public async Task Register(GrainAddress address, GrainAddress previousAddress) => (await _localGrainDirectory.RegisterAsync(address, currentRegistration: previousAddress)).Address; public Task Unregister(GrainAddress address, UnregistrationCause cause) { EnsureInitialized(); // If this ever gets more complicated, we can use a list or internally manage things within a single worker. var worker = cause switch { UnregistrationCause.Force => _forceWorker, UnregistrationCause.NonexistentActivation => _neaWorker, _ => throw new ArgumentOutOfRangeException($"Deregistration cause {cause} is unknown and is not supported. This is a bug."), }; return worker.Unregister(address); void EnsureInitialized() { // Unfortunately, for now we need to perform this initialization lazily, since a SystemTarget does not become valid // until it's registered with the Catalog (see Catalog.RegisterSystemTarget), which can happen after this instance // is constructed. if (_forceWorker is not null && _neaWorker is not null) { return; } lock (_initLock) { if (_forceWorker is not null && _neaWorker is not null) { return; } _forceWorker = new BatchedDeregistrationWorker(_localGrainDirectory, _grainContext, UnregistrationCause.Force); _neaWorker = new BatchedDeregistrationWorker(_localGrainDirectory, _grainContext, UnregistrationCause.NonexistentActivation); } } } public static DhtGrainLocator FromLocalGrainDirectory(LocalGrainDirectory localGrainDirectory) => new(localGrainDirectory, localGrainDirectory.RemoteGrainDirectory); public void UpdateCache(GrainId grainId, SiloAddress siloAddress) => _localGrainDirectory.AddOrUpdateCacheEntry(grainId, siloAddress); public void InvalidateCache(GrainId grainId) => _localGrainDirectory.InvalidateCacheEntry(grainId); public void InvalidateCache(GrainAddress address) => _localGrainDirectory.InvalidateCacheEntry(address); public bool TryLookupInCache(GrainId grainId, out GrainAddress address) => _localGrainDirectory.TryCachedLookup(grainId, out address); private class BatchedDeregistrationWorker { private const int OperationBatchSizeLimit = 2_000; private readonly ILocalGrainDirectory _localGrainDirectory; private readonly IGrainContext _grainContext; private readonly UnregistrationCause _cause; private readonly Channel<(TaskCompletionSource tcs, GrainAddress address)> _queue; #pragma warning disable IDE0052 // Remove unread private members private readonly Task _runTask; #pragma warning restore IDE0052 // Remove unread private members public BatchedDeregistrationWorker( ILocalGrainDirectory localGrainDirectory, IGrainContext grainContext, UnregistrationCause cause) { _localGrainDirectory = localGrainDirectory; _grainContext = grainContext; _cause = cause; _queue = Channel.CreateUnbounded<(TaskCompletionSource tcs, GrainAddress address)>(new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }); _runTask = _grainContext.RunOrQueueTask(() => ProcessDeregistrationQueue()); } public Task Unregister(GrainAddress address) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _queue.Writer.TryWrite((tcs, address)); return tcs.Task; } private async Task ProcessDeregistrationQueue() { var operations = new List>(); var addresses = new List(); var reader = _queue.Reader; while (await reader.WaitToReadAsync()) { // Process a batch of work. try { operations.Clear(); addresses.Clear(); while (operations.Count < OperationBatchSizeLimit && reader.TryRead(out var op)) { operations.Add(op.tcs); addresses.Add(op.address); } if (operations.Count > 0) { await _localGrainDirectory.UnregisterManyAsync(addresses, _cause); foreach (var op in operations) { op.TrySetResult(true); } } } catch (Exception ex) { foreach (var op in operations) { op.TrySetException(ex); } } } } } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/DirectoryMembershipService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Runtime.Internal; using Orleans.Runtime.Utilities; #nullable enable namespace Orleans.Runtime.GrainDirectory; internal sealed partial class DirectoryMembershipService : IAsyncDisposable { private readonly IInternalGrainFactory _grainFactory; private readonly ILogger _logger; private readonly CancellationTokenSource _shutdownCts = new(); private readonly Task _runTask; private readonly AsyncEnumerable _viewUpdates; public DirectoryMembershipSnapshot CurrentView { get; private set; } = DirectoryMembershipSnapshot.Default; public IAsyncEnumerable ViewUpdates => _viewUpdates; public ClusterMembershipService ClusterMembershipService { get; } public async ValueTask RefreshViewAsync(MembershipVersion version, CancellationToken cancellationToken) { _ = ClusterMembershipService.Refresh(version, cancellationToken); if (CurrentView.Version <= version) { await foreach (var view in _viewUpdates.WithCancellation(cancellationToken)) { if (view.Version >= version) { break; } } } return CurrentView; } public DirectoryMembershipService(ClusterMembershipService clusterMembershipService, IInternalGrainFactory grainFactory, ILogger logger) { _viewUpdates = new( DirectoryMembershipSnapshot.Default, (previous, proposed) => proposed.Version >= previous.Version, update => CurrentView = update); ClusterMembershipService = clusterMembershipService; _grainFactory = grainFactory; _logger = logger; using var _ = new ExecutionContextSuppressor(); _runTask = Task.Run(ProcessMembershipUpdates); } private async Task ProcessMembershipUpdates() { try { while (!_shutdownCts.IsCancellationRequested) { try { await foreach (var update in ClusterMembershipService.MembershipUpdates.WithCancellation(_shutdownCts.Token)) { var view = new DirectoryMembershipSnapshot(update, _grainFactory); _viewUpdates.Publish(view); } } catch (Exception exception) { if (!_shutdownCts.IsCancellationRequested) { LogErrorProcessingMembershipUpdates(exception); } } } } finally { _viewUpdates.Dispose(); } } public async ValueTask DisposeAsync() { _shutdownCts.Cancel(); await _runTask.SuppressThrowing(); } [LoggerMessage( Level = LogLevel.Error, Message = "Error processing membership updates." )] private partial void LogErrorProcessingMembershipUpdates(Exception exception); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/DirectoryMembershipSnapshot.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Orleans.Configuration; using Orleans.Runtime.Utilities; #nullable enable namespace Orleans.Runtime.GrainDirectory; internal sealed class DirectoryMembershipSnapshot { internal const int PartitionsPerSilo = ConsistentRingOptions.DEFAULT_NUM_VIRTUAL_RING_BUCKETS; private readonly ImmutableArray<(uint Start, int MemberIndex, int PartitionIndex)> _ringBoundaries; private readonly RingRangeCollection[] _rangesByMember; private readonly ImmutableArray> _partitionsByMember; private readonly ImmutableArray> _rangesByMemberPartition; public DirectoryMembershipSnapshot(ClusterMembershipSnapshot snapshot, IInternalGrainFactory grainFactory) : this(snapshot, grainFactory, static (silo, count) => silo.GetUniformHashCodes(count)) { } internal DirectoryMembershipSnapshot(ClusterMembershipSnapshot snapshot, IInternalGrainFactory grainFactory, Func getRingBoundaries) { var sortedActiveMembers = ImmutableArray.CreateBuilder(snapshot.Members.Count(static m => m.Value.Status == SiloStatus.Active)); foreach (var member in snapshot.Members) { // Only active members are part of directory membership. if (member.Value.Status == SiloStatus.Active) { sortedActiveMembers.Add(member.Key); } } sortedActiveMembers.Sort(static (left, right) => left.CompareTo(right)); var hashIndexPairs = ImmutableArray.CreateBuilder<(uint Hash, int MemberIndex, int PartitionIndex)>(PartitionsPerSilo * sortedActiveMembers.Count); var memberPartitions = ImmutableArray.CreateBuilder>(); for (var memberIndex = 0; memberIndex < sortedActiveMembers.Count; memberIndex++) { var activeMember = sortedActiveMembers[memberIndex]; var hashCodes = getRingBoundaries(activeMember, PartitionsPerSilo).ToList(); hashCodes.Sort(); Debug.Assert(hashCodes.Count == PartitionsPerSilo); var partitionReferences = ImmutableArray.CreateBuilder(PartitionsPerSilo); for (var partitionIndex = 0; partitionIndex < hashCodes.Count; partitionIndex++) { var hashCode = hashCodes[partitionIndex]; hashIndexPairs.Add((hashCode, memberIndex, partitionIndex)); partitionReferences.Add(grainFactory?.GetSystemTarget(GrainDirectoryPartition.CreateGrainId(activeMember, partitionIndex).GrainId)!); } memberPartitions.Add(partitionReferences.ToImmutable()); } _partitionsByMember = memberPartitions.ToImmutable(); hashIndexPairs.Sort(static (left, right) => { var hashCompare = left.Hash.CompareTo(right.Hash); if (hashCompare != 0) { return hashCompare; } var partitionCompare = left.PartitionIndex.CompareTo(right.PartitionIndex); if (partitionCompare != 0) { return partitionCompare; } return left.MemberIndex.CompareTo(right.MemberIndex); }); Dictionary.Builder> rangesByMemberPartitionBuilders = []; for (var i = 0; i < hashIndexPairs.Count; i++) { var (_, memberIndex, _) = hashIndexPairs[i]; ref var builder = ref CollectionsMarshal.GetValueRefOrAddDefault(rangesByMemberPartitionBuilders, memberIndex, out _); builder ??= ImmutableArray.CreateBuilder(PartitionsPerSilo); var (entryStart, _, _) = hashIndexPairs[i]; var (nextStart, _, _) = hashIndexPairs[(i + 1) % hashIndexPairs.Count]; var range = (entryStart == nextStart) switch { true when hashIndexPairs.Count == 1 => RingRange.Full, true => RingRange.Empty, _ => RingRange.Create(entryStart, nextStart) }; builder.Add(range); } var rangesByMemberPartition = ImmutableArray.CreateBuilder>(sortedActiveMembers.Count); for (var i = 0; i < sortedActiveMembers.Count; i++) { rangesByMemberPartition.Add(rangesByMemberPartitionBuilders[i].ToImmutable()); } _rangesByMemberPartition = rangesByMemberPartition.ToImmutable(); // Remove empty ranges. if (hashIndexPairs.Count > 1) { for (var i = 1; i < hashIndexPairs.Count;) { if (hashIndexPairs[i].Hash == hashIndexPairs[i - 1].Hash) { hashIndexPairs.RemoveAt(i); } else { i++; } } } _ringBoundaries = hashIndexPairs.ToImmutable(); Members = sortedActiveMembers.ToImmutable(); _rangesByMember = new RingRangeCollection[Members.Length]; ClusterMembershipSnapshot = snapshot; } public static DirectoryMembershipSnapshot Default { get; } = new DirectoryMembershipSnapshot( new ClusterMembershipSnapshot(ImmutableDictionary.Empty, MembershipVersion.MinValue), null!); public MembershipVersion Version => ClusterMembershipSnapshot.Version; public ImmutableArray Members { get; } public RingRange GetRange(SiloAddress address, int partitionIndex) { ArgumentOutOfRangeException.ThrowIfLessThan(partitionIndex, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(partitionIndex, PartitionsPerSilo - 1); var memberIndex = TryGetMemberIndex(address); if (memberIndex < 0) { return RingRange.Empty; } var ranges = GetMemberRangesByPartition(memberIndex); if (partitionIndex >= ranges.Length) { return RingRange.Empty; } return ranges[partitionIndex]; } public RingRangeCollection GetMemberRanges(SiloAddress address) { var memberIndex = TryGetMemberIndex(address); if (memberIndex < 0) { return RingRangeCollection.Empty; } var range = _rangesByMember[memberIndex]; if (range.IsDefault) { range = _rangesByMember[memberIndex] = RingRangeCollection.Create(GetMemberRangesByPartition(memberIndex)); } return range; } public ImmutableArray GetMemberRangesByPartition(SiloAddress address) { var memberIndex = TryGetMemberIndex(address); if (memberIndex < 0) { return []; } return GetMemberRangesByPartition(memberIndex); } private ImmutableArray GetMemberRangesByPartition(int memberIndex) { ArgumentOutOfRangeException.ThrowIfLessThan(memberIndex, 0); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(memberIndex, _rangesByMemberPartition.Length); return _rangesByMemberPartition[memberIndex]; } public RangeCollection RangeOwners => new(this); public ClusterMembershipSnapshot ClusterMembershipSnapshot { get; } private (RingRange Range, int MemberIndex, int PartitionIndex) GetRangeInfo(int index) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _ringBoundaries.Length); ArgumentOutOfRangeException.ThrowIfLessThan(index, 0); var range = GetRangeCore(index); var boundary = _ringBoundaries[index]; return (range, boundary.MemberIndex, boundary.PartitionIndex); } private RingRange GetRangeCore(int index) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _ringBoundaries.Length); ArgumentOutOfRangeException.ThrowIfLessThan(index, 0); var (entryStart, _, _) = _ringBoundaries[index]; var (nextStart, _, _) = _ringBoundaries[(index + 1) % _ringBoundaries.Length]; if (entryStart == nextStart) { // Handle hash collisions by making subsequent adjacent ranges empty. if (_ringBoundaries.Length == 1) { return RingRange.Full; } else { // Handle hash collisions by making subsequent adjacent ranges empty. return RingRange.Empty; } } return RingRange.Create(entryStart, nextStart); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int TryGetMemberIndex(SiloAddress? address) { if (address is null) { return -1; } return SearchAlgorithms.BinarySearch( Members.Length, (this, address), static (index, state) => { var (snapshot, address) = state; var candidate = snapshot.Members[index]; return candidate.CompareTo(address); }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetOwner(GrainId grainId, [NotNullWhen(true)] out SiloAddress? owner, [NotNullWhen(true)] out IGrainDirectoryPartition? partitionReference) => TryGetOwner(grainId.GetUniformHashCode(), out owner, out partitionReference); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetOwner(uint hashCode, [NotNullWhen(true)] out SiloAddress? owner, [NotNullWhen(true)] out IGrainDirectoryPartition? partitionReference) { var index = SearchAlgorithms.RingRangeBinarySearch( _ringBoundaries.Length, this, static (collection, index) => collection.GetRangeCore(index), hashCode); if (index >= 0) { var (_, memberIndex, partitionIndex) = _ringBoundaries[index]; owner = Members[memberIndex]; partitionReference = _partitionsByMember[memberIndex][partitionIndex]; return true; } Debug.Assert(Members.Length == 0); owner = null; partitionReference = null; return false; } public readonly struct RangeCollection(DirectoryMembershipSnapshot snapshot) : IReadOnlyList<(RingRange Range, int MemberIndex, int PartitionIndex)> { public int Count => snapshot._ringBoundaries.Length; public (RingRange Range, int MemberIndex, int PartitionIndex) this[int index] => snapshot.GetRangeInfo(index); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator<(RingRange Range, int MemberIndex, int PartitionIndex)> IEnumerable<(RingRange Range, int MemberIndex, int PartitionIndex)>.GetEnumerator() => GetEnumerator(); public RangeCollectionEnumerator GetEnumerator() => new(snapshot); public struct RangeCollectionEnumerator(DirectoryMembershipSnapshot snapshot) : IEnumerator<(RingRange Range, int MemberIndex, int PartitionIndex)> { private int _index = 0; public readonly (RingRange Range, int MemberIndex, int PartitionIndex) Current => snapshot.GetRangeInfo(_index - 1); readonly (RingRange Range, int MemberIndex, int PartitionIndex) IEnumerator<(RingRange Range, int MemberIndex, int PartitionIndex)>.Current => Current; readonly object IEnumerator.Current => Current; public void Dispose() => _index = int.MaxValue; public bool MoveNext() { if (_index >= 0 && _index++ < snapshot._ringBoundaries.Length) { return true; } return false; } public void Reset() => _index = 0; } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/DirectoryResult.cs ================================================ using System.Diagnostics.CodeAnalysis; #nullable enable namespace Orleans.Runtime; internal static class DirectoryResult { public static DirectoryResult FromResult(T result, MembershipVersion version) => new DirectoryResult(result, version); public static DirectoryResult RefreshRequired(MembershipVersion version) => new DirectoryResult(default, version); } [GenerateSerializer, Alias("DirectoryResult`1"), Immutable] internal readonly struct DirectoryResult(T? result, MembershipVersion version) { [Id(0)] private readonly T? _result = result; [Id(1)] public readonly MembershipVersion Version = version; public bool TryGetResult(MembershipVersion version, [NotNullWhen(true)] out T? result) { if (Version == version) { result = _result!; return true; } result = default; return false; } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/DistributedGrainDirectory.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.GrainDirectory; using Orleans.Internal; using Orleans.Runtime.Internal; using Orleans.Runtime.Scheduler; #nullable enable namespace Orleans.Runtime.GrainDirectory; /* The grain directory in Orleans is a key-value store where the key is a grain identifier and the value is a registration entry which points to an active silo which (potentially) hosts the grain. The directory is partitioned using a consistent hash ring with ranges being assigned to the active silos in the cluster. Grain identifiers are hashed to find the silo which is owns the section of the ring corresponding to its hash. Each active silo owns a pre-configured number of ranges, defaulting to 30 ranges per silo. This is similar to the scheme used by Amazon Dynamo (see https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf) and Apache Cassandra (see https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/architecture/archDataDistributeVnodesUsing.html), where multiple "virtual nodes" (ranges) are created for each node (host). The size of a partition is determined by the distance between its hash and the hash of the next partition. Range ownership is determined by cluster membership configuration. Cluster membership configurations are called "views" and each view has a monotonically increasing version number. As silos join and leave the cluster, successive views are created, resulting in changes to range ownership. This is known as a view change. Directory partitions have two modes of operation: normal operation and view change. During normal operation, directory partitions process requests locally without coordination with other hosts. During a view changes, hosts coordinate with each other to transfer ownership of directory ranges. Once this transfer is complete, normal operation resumes. The two-phase design of the directory follows the Virtual Synchrony methodology (see https://www.microsoft.com/en-us/research/publication/virtually-synchronous-methodology-for-dynamic-service-replication/) and has some similarities to Vertical Paxos (see https://www.microsoft.com/en-us/research/publication/vertical-paxos-and-primary-backup-replication/). Both proceed in two phases: a normal operation phase where a fixed set of processes handle requests without failures, and a view change phase where state and control are transferred between views during membership changes. When a view change occurs, a partition can either grow or shrink. If a new silo has joined the cluster, then the partition may shrink to make room for the new silo's partition. If a silo has left the cluster, then the partition may grow to take over the leaving silo's partition. When a partition shrinks, the previous owner seals the lost range and creates a snapshot containing the directory entries in that range. The new range owner (whose partition has grown, potentially from zero) requests the snapshot from the previous owner and applies it locally. Once the snapshot has been applied, the new owner can begin servicing requests. The new owner acknowledges the transfer to the previous owner so the previous owner can delete the snapshot. The previous owner also deletes the snapshot if it sees that the snapshot transfer has been abandoned. When a host crashes without first handing off its directory partitions, the hosts which subsequently own the partitions previously owned by the crashed silo must perform recovery. Recovery involves asking every active silo in the cluster for the grain registrations they own. Registrations for evicted silos do not need to be recovered, since registrations are only valid for active silos. The recovery procedure ensures that there is no data loss and that the directory remains consistent (no duplicate grain activations). Cluster membership guarantees monotonicity, but it does not guarantee that all silos see all membership views: it is possible for silos to skip intermediate membership view, for example if membership changes rapidly. When this happens, snapshot transfers are abandoned and recovery must be performed instead of the normal partition-to-partition hand-over, since the silo does not know with certainty which partition was the previous owner. A future improvement to cluster membership may reduce or eliminate this scenario by ensuring that all views are seen by all silos. Directory partitions (implemented in GrainDirectoryPartition) use versioned range locks to prevent invalid access to ranges during view changes. Range locks are created during view change and are released when the view change is complete. These locks are analogous to the 'wedges' used in the Virtual Synchrony methodology. It is possible for a range to be split among multiple silos during a view change. This adds some complexity to the view change procedure since each partition must potentially coordinate with multiple other partitions to complete the view change. All requests to a directory partition include the view number of the caller, and all responses from the directory include the view number of the directory partition. When the directory partition sees a view number higher than its own, it refreshes its view and initiates view change. Similarly, when a caller sees a response with a higher view number than its own, it refreshes its view and retries the request if necessary. This ensures that all requests are processed by the correct owner of the directory partition. */ internal sealed partial class DistributedGrainDirectory : SystemTarget, IGrainDirectory, IGrainDirectoryClient, ILifecycleParticipant, DistributedGrainDirectory.ITestHooks { private readonly DirectoryMembershipService _membershipService; private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly ImmutableArray _partitions; private readonly CancellationTokenSource _stoppedCts = new(); internal CancellationToken OnStoppedToken => _stoppedCts.Token; internal ClusterMembershipSnapshot ClusterMembershipSnapshot => _membershipService.CurrentView.ClusterMembershipSnapshot; // The recovery membership value is used to avoid a race between concurrent registration & recovery operations which could lead to lost registrations. // This could occur when a new activation is created and begins registering itself with a host which crashes. Concurrently, the new owner initiates // recovery and asks all silos for their activations. When this silo processes this request, it will have the activation in its internal // 'ActivationDirectory' even though these activations may not yet have completed registration. Therefore, multiple silos may return an entry for the same // grain. By ensuring that any registration occurred at a version at least as high as the recovery version, we avoid this issue. This could be made more // precise by also tracking the sets of ranges which need to be recovered, but that complicates things somewhat since it would require tracking the ranges // for each recovery version. private long _recoveryMembershipVersion; private Task _runTask = Task.CompletedTask; private ActivationDirectory _localActivations; private GrainDirectoryResolver? _grainDirectoryResolver; public DistributedGrainDirectory( DirectoryMembershipService membershipService, ILogger logger, IServiceProvider serviceProvider, IInternalGrainFactory grainFactory, SystemTargetShared shared) : base(Constants.GrainDirectoryType, shared) { _localActivations = shared.ActivationDirectory; _serviceProvider = serviceProvider; _membershipService = membershipService; _logger = logger; var partitions = ImmutableArray.CreateBuilder(DirectoryMembershipSnapshot.PartitionsPerSilo); for (var i = 0; i < DirectoryMembershipSnapshot.PartitionsPerSilo; i++) { partitions.Add(new GrainDirectoryPartition(i, this, grainFactory, shared)); } _partitions = partitions.ToImmutable(); shared.ActivationDirectory.RecordNewTarget(this); } public async Task Lookup(GrainId grainId) => await InvokeAsync( grainId, static (partition, version, grainId, cancellationToken) => partition.LookupAsync(version, grainId), grainId, CancellationToken.None); public async Task Register(GrainAddress address) => await InvokeAsync( address.GrainId, static (partition, version, address, cancellationToken) => partition.RegisterAsync(version, address, null), address, CancellationToken.None); public async Task Unregister(GrainAddress address) => await InvokeAsync( address.GrainId, static (partition, version, address, cancellationToken) => partition.DeregisterAsync(version, address), address, CancellationToken.None); public async Task Register(GrainAddress address, GrainAddress? previousAddress) => await InvokeAsync( address.GrainId, static (partition, version, state, cancellationToken) => partition.RegisterAsync(version, state.Address, state.PreviousAddress), (Address: address, PreviousAddress: previousAddress), CancellationToken.None); public Task UnregisterSilos(List siloAddresses) => Task.CompletedTask; private async Task InvokeAsync( GrainId grainId, Func>> func, TState state, CancellationToken cancellationToken, [CallerMemberName] string operation = "") { DirectoryResult invokeResult; var view = _membershipService.CurrentView; var attempts = 0; const int MaxAttempts = 10; var delay = TimeSpan.FromMilliseconds(10); while (true) { cancellationToken.ThrowIfCancellationRequested(); var initialRecoveryMembershipVersion = _recoveryMembershipVersion; if (view.Version.Value < initialRecoveryMembershipVersion || !view.TryGetOwner(grainId, out var owner, out var partitionReference)) { // If there are no members, bail out with the default return value. if (view.Members.Length == 0 && view.Version.Value > 0) { return default!; } var targetVersion = Math.Max(view.Version.Value + 1, initialRecoveryMembershipVersion); view = await _membershipService.RefreshViewAsync(new(targetVersion), cancellationToken); continue; } #if false if (logger.IsEnabled(LogLevel.Trace)) { logger.LogTrace("Invoking '{Operation}' on '{Owner}' for grain '{GrainId}'.", operation, owner, grainId); } #endif try { RequestContext.Set("gid", partitionReference.GetGrainId()); invokeResult = await func(partitionReference, view.Version, state, cancellationToken); } catch (OrleansMessageRejectionException) when (attempts < MaxAttempts && !cancellationToken.IsCancellationRequested) { // This likely indicates that the target silo has been declared dead. ++attempts; await Task.Delay(delay, cancellationToken); delay *= 1.5; continue; } if (initialRecoveryMembershipVersion != _recoveryMembershipVersion) { // If the recovery version changed, perform a view refresh and re-issue the operation. // See the comment on the declaration of '_recoveryMembershipVersionValue' for more details. continue; } if (!invokeResult.TryGetResult(view.Version, out var result)) { // The remote replica has a newer view of membership and is no longer the owner of the grain specified in the request. // Refresh membership and re-evaluate. view = await _membershipService.RefreshViewAsync(invokeResult.Version, cancellationToken); continue; } LogTraceInvokedOperation(_logger, operation, owner, grainId, result); return result; } } public async ValueTask>> RecoverRegisteredActivations(MembershipVersion membershipVersion, RingRange range, SiloAddress siloAddress, int partitionIndex) { foreach (var partition in _partitions) { partition.OnRecoveringPartition(membershipVersion, range, siloAddress, partitionIndex).Ignore(); } return await GetRegisteredActivations(membershipVersion, range, false); } public async ValueTask>> GetRegisteredActivations(MembershipVersion membershipVersion, RingRange range, bool isValidation) { if (!isValidation) { LogDebugCollectingRegisteredActivations(_logger, range, membershipVersion); } var recoveryMembershipVersion = _recoveryMembershipVersion; if (recoveryMembershipVersion < membershipVersion.Value) { // Ensure that the value is immediately visible to any thread registering an activation. Interlocked.CompareExchange(ref _recoveryMembershipVersion, membershipVersion.Value, recoveryMembershipVersion); } List result = []; List deactivationTasks = []; var stopwatch = CoarseStopwatch.StartNew(); using var cts = new CancellationTokenSource(); cts.Cancel(); foreach (var (grainId, activation) in _localActivations) { var directory = GetGrainDirectory(activation, _grainDirectoryResolver!); if (directory == this) { var address = activation.Address; if (!range.Contains(address.GrainId)) { continue; } if (address.MembershipVersion == MembershipVersion.MinValue || activation is ActivationData activationData && !activationData.IsValid) { // Validation does not require that the grain is deactivated, skip it instead. //if (isValidation) continue; try { // This activation has not completed registration or is not currently active. // Abort the activation with a pre-canceled cancellation token so that it skips directory deregistration. // TODO: Expand validity check to non-ActivationData activations. //logger.LogWarning("Deactivating activation '{Activation}' due to failure of a directory range owner.", activation); activation.Deactivate(new DeactivationReason(DeactivationReasonCode.DirectoryFailure, "This activation's directory partition was salvaged while registration status was in-doubt."), cts.Token); deactivationTasks.Add(activation.Deactivated); } catch (Exception exception) { LogWarningFailedToDeactivateActivation(_logger, exception, activation); } } else { if (!isValidation) { LogTraceSendingActivationForRecovery(_logger, activation.GrainId, range, membershipVersion); } result.Add(activation.Address); } } } await Task.WhenAll(deactivationTasks); if (!isValidation) { LogDebugSubmittingRegisteredActivations(_logger, result.Count, range, membershipVersion, deactivationTasks.Count, stopwatch.ElapsedMilliseconds); } return result.AsImmutable(); static IGrainDirectory? GetGrainDirectory(IGrainContext grainContext, GrainDirectoryResolver grainDirectoryResolver) { if (grainContext is ActivationData activationData) { return activationData.Shared.GrainDirectory; } else if (grainContext is SystemTarget systemTarget) { return null; } else if (grainContext.GetComponent() is { IsUsingGrainDirectory: true }) { return grainDirectoryResolver.Resolve(grainContext.GrainId.Type); } return null; } } internal ValueTask RefreshViewAsync(MembershipVersion version, CancellationToken cancellationToken) => _membershipService.RefreshViewAsync(version, cancellationToken); void ILifecycleParticipant.Participate(ISiloLifecycle observer) { _grainDirectoryResolver = _serviceProvider.GetRequiredService(); observer.Subscribe(nameof(DistributedGrainDirectory), ServiceLifecycleStage.RuntimeInitialize, OnRuntimeInitializeStart, OnRuntimeInitializeStop); // Transition into 'ShuttingDown'/'Stopping' stage, removing ourselves from directory membership, but allow some time for hand-off before transitioning to 'Dead'. observer.Subscribe(nameof(DistributedGrainDirectory), ServiceLifecycleStage.BecomeActive - 1, _ => Task.CompletedTask, OnShuttingDown); Task OnRuntimeInitializeStart(CancellationToken cancellationToken) { using var _ = new ExecutionContextSuppressor(); WorkItemGroup.QueueAction(() => _runTask = ProcessMembershipUpdates()); return Task.CompletedTask; } async Task OnRuntimeInitializeStop(CancellationToken cancellationToken) { _stoppedCts.Cancel(); if (_runTask is { } task) { // Try to wait for hand-off to complete. await this.RunOrQueueTask(async () => await task.WaitAsync(cancellationToken).SuppressThrowing()); } } async Task OnShuttingDown(CancellationToken token) { var tasks = new List(_partitions.Length); foreach (var partition in _partitions) { tasks.Add(partition.OnShuttingDown(token)); } await Task.WhenAll(tasks).SuppressThrowing(); } } private async Task ProcessMembershipUpdates() { // Ensure all child tasks are completed before exiting, tracking them here. List tasks = []; var previousUpdate = ClusterMembershipSnapshot.Default; while (!_stoppedCts.IsCancellationRequested) { try { DirectoryMembershipSnapshot previous = _membershipService.CurrentView; var previousRanges = RingRangeCollection.Empty; await foreach (var update in _membershipService.ViewUpdates.WithCancellation(_stoppedCts.Token)) { tasks.RemoveAll(t => t.IsCompleted); var changes = update.ClusterMembershipSnapshot.CreateUpdate(previousUpdate); foreach (var change in changes.Changes) { if (change.Status == SiloStatus.Dead) { foreach (var partition in _partitions) { tasks.Add(partition.OnSiloRemovedFromClusterAsync(change)); } } } var current = update; var currentRanges = current.GetMemberRanges(Silo); foreach (var partition in _partitions) { tasks.Add(partition.ProcessMembershipUpdateAsync(current)); } var deltaSize = currentRanges.SizePercent - previousRanges.SizePercent; var meanSizePercent = current.Members.Length > 0 ? 100.0 / current.Members.Length : 0f; var deviationFromMean = Math.Abs(meanSizePercent - currentRanges.SizePercent); LogDebugUpdatedView(previous.Version, current.Version, currentRanges.SizePercent, deltaSize, deviationFromMean); previousUpdate = update.ClusterMembershipSnapshot; previous = current; previousRanges = currentRanges; } } catch (Exception exception) { if (!_stoppedCts.IsCancellationRequested) { LogErrorProcessingMembershipUpdates(exception); } } } await Task.WhenAll(tasks).SuppressThrowing(); } SiloAddress? ITestHooks.GetPrimaryForGrain(GrainId grainId) { _membershipService.CurrentView.TryGetOwner(grainId, out var owner, out _); return owner; } async Task ITestHooks.GetLocalRecord(GrainId grainId) { var view = _membershipService.CurrentView; if (view.TryGetOwner(grainId, out var owner, out var partitionReference) && Silo.Equals(owner)) { var result = await partitionReference.LookupAsync(view.Version, grainId); if (result.TryGetResult(view.Version, out var address)) { return address; } } return null; } internal interface ITestHooks { SiloAddress? GetPrimaryForGrain(GrainId grainId); Task GetLocalRecord(GrainId grainId); } [LoggerMessage( Level = LogLevel.Debug, Message = "Updated view from '{PreviousVersion}' to '{Version}'. Now responsible for {Range:0.00}% (Δ {DeltaPercent:0.00}%). {DeviationFromMean:0.00}% from ideal share." )] private partial void LogDebugUpdatedView(MembershipVersion previousVersion, MembershipVersion version, double range, double deltaPercent, double deviationFromMean); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing membership updates." )] private partial void LogErrorProcessingMembershipUpdates(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Invoked '{Operation}' on '{Owner}' for grain '{GrainId}' and received result '{Result}'." )] private static partial void LogTraceInvokedOperation(ILogger logger, string operation, SiloAddress owner, GrainId grainId, object result); [LoggerMessage( Level = LogLevel.Debug, Message = "Collecting registered activations for range {Range} at version {MembershipVersion}." )] private static partial void LogDebugCollectingRegisteredActivations(ILogger logger, RingRange range, MembershipVersion membershipVersion); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to deactivate activation {Activation}" )] private static partial void LogWarningFailedToDeactivateActivation(ILogger logger, Exception exception, IGrainContext activation); [LoggerMessage( Level = LogLevel.Trace, Message = "Sending activation '{Activation}' for recovery because its in the requested range {Range} (version {Version})." )] private static partial void LogTraceSendingActivationForRecovery(ILogger logger, GrainId activation, RingRange range, MembershipVersion version); [LoggerMessage( Level = LogLevel.Debug, Message = "Submitting {Count} registered activations for range {Range} at version {MembershipVersion}. Deactivated {DeactivationCount} in-doubt registrations. Took {ElapsedMilliseconds}ms" )] private static partial void LogDebugSubmittingRegisteredActivations(ILogger logger, int count, RingRange range, MembershipVersion membershipVersion, int deactivationCount, long elapsedMilliseconds); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GenericGrainDirectoryResolver.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.GrainDirectory; using Orleans.Metadata; namespace Orleans.Runtime.GrainDirectory { internal class GenericGrainDirectoryResolver : IGrainDirectoryResolver { private readonly IServiceProvider _services; private GrainDirectoryResolver _resolver; public GenericGrainDirectoryResolver(IServiceProvider services) { _services = services; } public bool TryResolveGrainDirectory(GrainType grainType, GrainProperties properties, out IGrainDirectory grainDirectory) { if (GenericGrainType.TryParse(grainType, out var constructed) && constructed.IsConstructed) { var generic = constructed.GetUnconstructedGrainType().GrainType; var resolver = GetResolver(); if (resolver.TryGetNonDefaultGrainDirectory(generic, out grainDirectory)) { return true; } } grainDirectory = default; return false; } private GrainDirectoryResolver GetResolver() => _resolver ??= _services.GetRequiredService(); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryCacheFactory.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; namespace Orleans.Runtime.GrainDirectory { /// /// Creates instances. /// public static class GrainDirectoryCacheFactory { /// /// Creates a new grain directory cache instance. /// /// The services. /// The options. /// The newly created instance. public static IGrainDirectoryCache CreateGrainDirectoryCache(IServiceProvider services, GrainDirectoryOptions options) { if (options.CacheSize <= 0) return new NullGrainDirectoryCache(); switch (options.CachingStrategy) { case GrainDirectoryOptions.CachingStrategyType.None: return new NullGrainDirectoryCache(); case GrainDirectoryOptions.CachingStrategyType.LRU: #pragma warning disable CS0618 // Type or member is obsolete case GrainDirectoryOptions.CachingStrategyType.Adaptive: #pragma warning restore CS0618 // Type or member is obsolete return new LruGrainDirectoryCache(options.CacheSize); case GrainDirectoryOptions.CachingStrategyType.Custom: default: return services.GetRequiredService(); } } internal static IGrainDirectoryCache CreateCustomGrainDirectoryCache(IServiceProvider services, GrainDirectoryOptions options) { var grainDirectoryCache = services.GetService(); if (grainDirectoryCache != null) { return grainDirectoryCache; } else { return new LruGrainDirectoryCache(options.CacheSize); } } } internal sealed class NullGrainDirectoryCache : IGrainDirectoryCache { public void AddOrUpdate(GrainAddress value, int version) { } public bool Remove(GrainId key) => false; public bool Remove(GrainAddress key) => false; public void Clear() { } public bool LookUp(GrainId key, out GrainAddress result, out int version) { result = default; version = default; return false; } public IEnumerable<(GrainAddress ActivationAddress, int Version)> KeyValues { get { yield break; } } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryHandoffManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime.Scheduler; #nullable enable namespace Orleans.Runtime.GrainDirectory { /// /// Most methods of this class are synchronized since they might be called both /// from LocalGrainDirectory on CacheValidator.SchedulingContext and from RemoteGrainDirectory. /// internal sealed partial class GrainDirectoryHandoffManager { private static readonly TimeSpan RetryDelay = TimeSpan.FromMilliseconds(250); private const int MAX_OPERATION_DEQUEUE = 2; private readonly LocalGrainDirectory localDirectory; private readonly ISiloStatusOracle siloStatusOracle; private readonly IInternalGrainFactory grainFactory; private readonly ILogger logger; private readonly Factory createPartion; private readonly Queue<(string name, object state, Func action)> pendingOperations = new(); private readonly AsyncLock executorLock = new AsyncLock(); internal GrainDirectoryHandoffManager( LocalGrainDirectory localDirectory, ISiloStatusOracle siloStatusOracle, IInternalGrainFactory grainFactory, Factory createPartion, ILoggerFactory loggerFactory) { logger = loggerFactory.CreateLogger(); this.localDirectory = localDirectory; this.siloStatusOracle = siloStatusOracle; this.grainFactory = grainFactory; this.createPartion = createPartion; } internal void ProcessSiloAddEvent(SiloAddress addedSilo) { lock (this) { LogDebugProcessingSiloAddEvent(logger, addedSilo); // check if this is our immediate successor (i.e., if I should hold this silo's copy) // (if yes, adjust local and/or copied directory partitions by splitting them between old successors and the new one) // NOTE: We need to move part of our local directory to the new silo if it is an immediate successor. var successor = localDirectory.FindSuccessor(localDirectory.MyAddress); if (successor is null || !successor.Equals(addedSilo)) { LogDebugNotImmediateSuccessor(logger, addedSilo); return; } // split my local directory and send to my new immediate successor his share LogDebugSplittingPartition(logger, addedSilo); var splitPartListSingle = localDirectory.DirectoryPartition.Split( grain => { var s = localDirectory.CalculateGrainDirectoryPartition(grain); return s != null && !localDirectory.MyAddress.Equals(s); }); EnqueueOperation($"{nameof(ProcessSiloAddEvent)}({addedSilo})", addedSilo, (t, state) => t.ProcessAddedSiloAsync((SiloAddress)state, splitPartListSingle)); } } private async Task ProcessAddedSiloAsync(SiloAddress addedSilo, List splitPartListSingle) { if (!this.localDirectory.Running) return; if (this.siloStatusOracle.GetApproximateSiloStatus(addedSilo) == SiloStatus.Active) { if (splitPartListSingle.Count > 0) { LogDebugSendingEntries(logger, splitPartListSingle.Count, addedSilo); } await localDirectory.GetDirectoryReference(addedSilo).AcceptSplitPartition(splitPartListSingle); } else { LogWarningSiloNotActive(logger, addedSilo); return; } if (splitPartListSingle.Count > 0) { LogDebugRemovingEntries(logger, splitPartListSingle.Count); foreach (var activationAddress in splitPartListSingle) { localDirectory.DirectoryPartition.RemoveGrain(activationAddress.GrainId); } } } internal void AcceptExistingRegistrations(List singleActivations) { if (singleActivations.Count == 0) return; EnqueueOperation(nameof(AcceptExistingRegistrations), singleActivations, (t, state) => t.AcceptExistingRegistrationsAsync((List)state)); } private async Task AcceptExistingRegistrationsAsync(List singleActivations) { if (!this.localDirectory.Running) return; LogDebugAcceptingRegistrations(logger, singleActivations.Count); var tasks = singleActivations.Select(addr => this.localDirectory.RegisterAsync(addr, previousAddress: null, 1)).ToArray(); try { await Task.WhenAll(tasks); } catch (Exception exception) { LogWarningExceptionRegistering(logger, exception); throw; } finally { Dictionary>? duplicates = null; for (var i = tasks.Length - 1; i >= 0; i--) { // Retry failed tasks next time. if (!tasks[i].IsCompletedSuccessfully) continue; // Record the applications which lost the registration race (duplicate activations). var winner = tasks[i].Result; if (winner.Address is not { } winnerAddress || !winnerAddress.Equals(singleActivations[i])) { var duplicate = singleActivations[i]; (CollectionsMarshal.GetValueRefOrAddDefault(duplicates ??= new(), duplicate.SiloAddress!, out _) ??= new()).Add(duplicate); } // Remove tasks which completed. singleActivations.RemoveAt(i); } // Destroy any duplicate activations. DestroyDuplicateActivations(duplicates); } } private void DestroyDuplicateActivations(Dictionary>? duplicates) { if (duplicates == null || duplicates.Count == 0) return; EnqueueOperation(nameof(DestroyDuplicateActivations), duplicates, (t, state) => t.DestroyDuplicateActivationsAsync((Dictionary>)state)); } private async Task DestroyDuplicateActivationsAsync(Dictionary> duplicates) { while (duplicates.Count > 0) { var pair = duplicates.FirstOrDefault(); if (this.siloStatusOracle.GetApproximateSiloStatus(pair.Key) == SiloStatus.Active) { LogDebugDestroyingDuplicates(logger, duplicates.Count, pair.Key, new(pair.Value)); var remoteCatalog = this.grainFactory.GetSystemTarget(Constants.CatalogType, pair.Key); await remoteCatalog.DeleteActivations(pair.Value, DeactivationReasonCode.DuplicateActivation, "This grain has been activated elsewhere"); } duplicates.Remove(pair.Key); } } private void EnqueueOperation(string name, object state, Func action) { lock (this) { this.pendingOperations.Enqueue((name, state, action)); if (this.pendingOperations.Count <= 2) { this.localDirectory.RemoteGrainDirectory.WorkItemGroup.QueueTask(ExecutePendingOperations, localDirectory.RemoteGrainDirectory); } } } private async Task ExecutePendingOperations() { using (await executorLock.LockAsync()) { var dequeueCount = 0; while (true) { // Get the next operation, or exit if there are none. (string Name, object State, Func Action) op; lock (this) { if (this.pendingOperations.Count == 0) break; op = this.pendingOperations.Peek(); } dequeueCount++; try { await op.Action(this, op.State); // Success, reset the dequeue count dequeueCount = 0; } catch (Exception exception) { if (dequeueCount < MAX_OPERATION_DEQUEUE) { LogWarningOperationFailedRetry(logger, exception, op.Name); await Task.Delay(RetryDelay); } else { LogWarningOperationFailedNoRetry(logger, exception, op.Name); } } if (dequeueCount == 0 || dequeueCount >= MAX_OPERATION_DEQUEUE) { lock (this) { // Remove the operation from the queue if it was a success // or if we tried too many times this.pendingOperations.Dequeue(); } } } } } [LoggerMessage( Level = LogLevel.Debug, Message = "Processing silo add event for {AddedSilo}" )] private static partial void LogDebugProcessingSiloAddEvent(ILogger logger, SiloAddress addedSilo); [LoggerMessage( Level = LogLevel.Debug, Message = "{AddedSilo} is not my immediate successor." )] private static partial void LogDebugNotImmediateSuccessor(ILogger logger, SiloAddress addedSilo); [LoggerMessage( Level = LogLevel.Debug, Message = "Splitting my partition between me and {AddedSilo}" )] private static partial void LogDebugSplittingPartition(ILogger logger, SiloAddress addedSilo); [LoggerMessage( Level = LogLevel.Debug, Message = "Sending {Count} single activation entries to {AddedSilo}" )] private static partial void LogDebugSendingEntries(ILogger logger, int count, SiloAddress addedSilo); [LoggerMessage( Level = LogLevel.Warning, Message = "Silo {AddedSilo} is no longer active and therefore cannot receive this partition split" )] private static partial void LogWarningSiloNotActive(ILogger logger, SiloAddress addedSilo); [LoggerMessage( Level = LogLevel.Debug, Message = "Removing {Count} single activation after partition split" )] private static partial void LogDebugRemovingEntries(ILogger logger, int count); [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(AcceptExistingRegistrations)}: accepting {{Count}} single-activation registrations" )] private static partial void LogDebugAcceptingRegistrations(ILogger logger, int count); [LoggerMessage( Level = LogLevel.Warning, Message = $"Exception registering activations in {nameof(AcceptExistingRegistrations)}" )] private static partial void LogWarningExceptionRegistering(ILogger logger, Exception exception); private readonly struct GrainAddressesLogValue(List grainAddresses) { public override string ToString() => string.Join("\n * ", grainAddresses.Select(_ => _)); } [LoggerMessage( Level = LogLevel.Debug, Message = $"{nameof(DestroyDuplicateActivations)} will destroy {{Count}} duplicate activations on silo {{SiloAddress}}: {{Duplicates}}" )] private static partial void LogDebugDestroyingDuplicates(ILogger logger, int count, SiloAddress siloAddress, GrainAddressesLogValue duplicates); [LoggerMessage( Level = LogLevel.Warning, Message = "{Operation} failed, will be retried" )] private static partial void LogWarningOperationFailedRetry(ILogger logger, Exception exception, string operation); [LoggerMessage( Level = LogLevel.Warning, Message = "{Operation} failed, will NOT be retried" )] private static partial void LogWarningOperationFailedNoRetry(ILogger logger, Exception exception, string operation); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryPartition.Interface.cs ================================================ using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; #nullable enable namespace Orleans.Runtime.GrainDirectory; internal sealed partial class GrainDirectoryPartition { async ValueTask> IGrainDirectoryPartition.RegisterAsync(MembershipVersion version, GrainAddress address, GrainAddress? currentRegistration) { ArgumentNullException.ThrowIfNull(address); LogRegisterAsync(version, address, currentRegistration); // Ensure that the current membership version is new enough. await WaitForRange(address.GrainId, version); if (!IsOwner(CurrentView, address.GrainId)) { return DirectoryResult.RefreshRequired(CurrentView.Version); } DebugAssertOwnership(address.GrainId); return DirectoryResult.FromResult(RegisterCore(address, currentRegistration), version); } async ValueTask> IGrainDirectoryPartition.LookupAsync(MembershipVersion version, GrainId grainId) { LogLookupAsync(version, grainId); // Ensure we can serve the request. await WaitForRange(grainId, version); if (!IsOwner(CurrentView, grainId)) { return DirectoryResult.RefreshRequired(CurrentView.Version); } return DirectoryResult.FromResult(LookupCore(grainId), version); } async ValueTask> IGrainDirectoryPartition.DeregisterAsync(MembershipVersion version, GrainAddress address) { ArgumentNullException.ThrowIfNull(address); LogDeregisterAsync(version, address); await WaitForRange(address.GrainId, version); if (!IsOwner(CurrentView, address.GrainId)) { return DirectoryResult.RefreshRequired(CurrentView.Version); } DebugAssertOwnership(address.GrainId); return DirectoryResult.FromResult(DeregisterCore(address), version); } private bool DeregisterCore(GrainAddress address) { if (_directory.TryGetValue(address.GrainId, out var existing) && (existing.Matches(address) || IsSiloDead(existing))) { return _directory.Remove(address.GrainId); } return false; } internal GrainAddress? LookupCore(GrainId grainId) { if (_directory.TryGetValue(grainId, out var existing) && !IsSiloDead(existing)) { return existing; } return null; } private GrainAddress RegisterCore(GrainAddress newAddress, GrainAddress? existingAddress) { ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_directory, newAddress.GrainId, out _); if (existing is null || existing.Matches(existingAddress) || IsSiloDead(existing)) { if (newAddress.MembershipVersion != CurrentView.Version) { // Set the membership version to match the view number in which it was registered. newAddress = new() { GrainId = newAddress.GrainId, SiloAddress = newAddress.SiloAddress, ActivationId = newAddress.ActivationId, MembershipVersion = CurrentView.Version }; } existing = newAddress; } return existing; } private bool IsSiloDead(GrainAddress existing) => _owner.ClusterMembershipSnapshot.GetSiloStatus(existing.SiloAddress) == SiloStatus.Dead; [LoggerMessage( Level = LogLevel.Trace, Message = "RegisterAsync('{Version}', '{Address}', '{ExistingAddress}')" )] private partial void LogRegisterAsync(MembershipVersion version, GrainAddress address, GrainAddress? existingAddress); [LoggerMessage( Level = LogLevel.Trace, Message = "LookupAsync('{Version}', '{GrainId}')" )] private partial void LogLookupAsync(MembershipVersion version, GrainId grainId); [LoggerMessage( Level = LogLevel.Trace, Message = "DeregisterAsync('{Version}', '{Address}')" )] private partial void LogDeregisterAsync(MembershipVersion version, GrainAddress address); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryPartition.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Internal; using Orleans.Runtime.Scheduler; using Orleans.Runtime.Utilities; #nullable enable namespace Orleans.Runtime.GrainDirectory; /// /// Represents a single contiguous partition of the distributed grain directory. /// internal sealed partial class GrainDirectoryPartition : SystemTarget, IGrainDirectoryPartition, IGrainDirectoryTestHooks { internal static SystemTargetGrainId CreateGrainId(SiloAddress siloAddress, int partitionIndex) => SystemTargetGrainId.Create(Constants.GrainDirectoryPartitionType, siloAddress, partitionIndex.ToString(CultureInfo.InvariantCulture)); private readonly Dictionary _directory = []; private readonly int _partitionIndex; private readonly DistributedGrainDirectory _owner; private readonly IInternalGrainFactory _grainFactory; private readonly CancellationTokenSource _drainSnapshotsCts = new(); private readonly SiloAddress _id; private readonly ILogger _logger; private readonly TaskCompletionSource _snapshotsDrainedTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly AsyncEnumerable _viewUpdates = new( DirectoryMembershipSnapshot.Default, (previous, proposed) => proposed.Version >= previous.Version, _ => { }); // Ranges which cannot be served currently, eg because the partition is currently transferring them from a previous owner. // Requests in these ranges must wait for the range to become available. private readonly List<(RingRange Range, MembershipVersion Version, TaskCompletionSource Completion)> _rangeLocks = []; // Ranges which were previously at least partially owned by this partition, but which are pending transfer to a new partition. private readonly List _partitionSnapshots = []; // Tracked for diagnostic purposes only. private readonly List _viewChangeTasks = []; private CancellationToken ShutdownToken => _owner.OnStoppedToken; private RingRange _currentRange; /// The index of this partition on this silo. Each silo hosts a fixed number of dynamically sized partitions. public GrainDirectoryPartition( int partitionIndex, DistributedGrainDirectory owner, IInternalGrainFactory grainFactory, SystemTargetShared shared) : base(CreateGrainId(shared.SiloAddress, partitionIndex), shared) { _partitionIndex = partitionIndex; _owner = owner; _grainFactory = grainFactory; _id = shared.SiloAddress; _logger = shared.LoggerFactory.CreateLogger(); shared.ActivationDirectory.RecordNewTarget(this); } // The current directory membership snapshot. public DirectoryMembershipSnapshot CurrentView { get; private set; } = DirectoryMembershipSnapshot.Default; public async ValueTask RefreshViewAsync(MembershipVersion version, CancellationToken cancellationToken) { _ = _owner.RefreshViewAsync(version, cancellationToken); if (CurrentView.Version <= version) { await foreach (var view in _viewUpdates.WithCancellation(cancellationToken)) { if (view.Version >= version) { break; } } } return CurrentView; } async ValueTask IGrainDirectoryPartition.GetSnapshotAsync(MembershipVersion version, MembershipVersion rangeVersion, RingRange range) { LogTraceGetSnapshotAsync(_logger, version, rangeVersion, range); // Wait for the range to be unlocked. await WaitForRange(range, version); ShutdownToken.ThrowIfCancellationRequested(); List partitionAddresses = []; foreach (var partitionSnapshot in _partitionSnapshots) { if (partitionSnapshot.DirectoryMembershipVersion != rangeVersion) { continue; } // Only include addresses which are in the requested range. foreach (var address in partitionSnapshot.GrainAddresses) { if (range.Contains(address.GrainId)) { partitionAddresses.Add(address); } } var rangeSnapshot = new GrainDirectoryPartitionSnapshot(rangeVersion, partitionAddresses); LogDebugTransferringEntries(_logger, partitionAddresses.Count, range, rangeVersion); return rangeSnapshot; } LogWarningRequestForSnapshot(_logger, version, rangeVersion, range); return null; } ValueTask IGrainDirectoryPartition.AcknowledgeSnapshotTransferAsync(SiloAddress silo, int partitionIndex, MembershipVersion rangeVersion) { RemoveSnapshotTransferPartner( (silo, partitionIndex, rangeVersion), snapshotFilter: (state, snapshot) => snapshot.DirectoryMembershipVersion == state.rangeVersion, partnerFilter: (state, silo, partitionIndex) => silo.Equals(state.silo) && partitionIndex == state.partitionIndex); return new(true); } private void RemoveSnapshotTransferPartner(TState state, Func snapshotFilter, Func partnerFilter) { for (var i = 0; i < _partitionSnapshots.Count; ++i) { var partitionSnapshot = _partitionSnapshots[i]; if (!snapshotFilter(state, partitionSnapshot)) { continue; } var partners = partitionSnapshot.TransferPartners; partners.RemoveWhere(p => partnerFilter(state, p.SiloAddress, p.PartitionIndex)); if (partners.Count == 0) { _partitionSnapshots.RemoveAt(i); --i; LogDebugRemovingSnapshot(_logger, partitionSnapshot.DirectoryMembershipVersion, string.Join(", ", _partitionSnapshots.Select(s => s.DirectoryMembershipVersion))); // If shutdown has been requested and there are no more pending snapshots, signal completion. if (_drainSnapshotsCts.IsCancellationRequested && _partitionSnapshots.Count == 0) { _snapshotsDrainedTcs.TrySetResult(); } } } } [Conditional("DEBUG")] private void DebugAssertOwnership(GrainId grainId) => DebugAssertOwnership(CurrentView, grainId); [Conditional("DEBUG")] private void DebugAssertOwnership(DirectoryMembershipSnapshot view, GrainId grainId) { if (!view.TryGetOwner(grainId, out var owner, out var partitionReference)) { Debug.Fail($"Could not find owner for grain grain '{grainId}' in view '{view}'."); } if (!_id.Equals(owner)) { Debug.Fail($"'{_id}' expected to be the owner of grain '{grainId}', but the owner is '{owner}'."); } if (!GrainId.Equals(partitionReference.GetGrainId())) { Debug.Fail($"'{GrainId}' expected to be the owner of grain '{grainId}', but the owner is '{partitionReference.GetGrainId()}'."); } } private bool IsOwner(DirectoryMembershipSnapshot view, GrainId grainId) => view.TryGetOwner(grainId, out _, out var partitionReference) && GrainId.Equals(partitionReference.GetGrainId()); private ValueTask WaitForRange(GrainId grainId, MembershipVersion version) => WaitForRange(RingRange.FromPoint(grainId.GetUniformHashCode()), version); private ValueTask WaitForRange(RingRange range, MembershipVersion version) { GrainRuntime.CheckRuntimeContext(this); Task? completion = null; if (CurrentView.Version < version || TryGetIntersectingLock(range, version, out completion)) { return WaitForRangeCore(range, version, completion); } return ValueTask.CompletedTask; bool TryGetIntersectingLock(RingRange range, MembershipVersion version, [NotNullWhen(true)] out Task? completion) { foreach (var rangeLock in _rangeLocks) { if (rangeLock.Version <= version && range.Intersects(rangeLock.Range)) { completion = rangeLock.Completion.Task; return true; } } completion = null; return false; } async ValueTask WaitForRangeCore(RingRange range, MembershipVersion version, Task? task) { if (task is not null) { await task; } if (CurrentView.Version < version) { await RefreshViewAsync(version, ShutdownToken); } while (TryGetIntersectingLock(range, version, out var completion)) { await completion.WaitAsync(ShutdownToken); } } } public IGrainDirectoryPartition GetPartitionReference(SiloAddress address, int partitionIndex) => _grainFactory.GetSystemTarget(CreateGrainId(address, partitionIndex).GrainId); internal async Task OnShuttingDown(CancellationToken token) { await this.RunOrQueueTask(async () => { _drainSnapshotsCts.Cancel(); if (_partitionSnapshots.Count > 0) { await _snapshotsDrainedTcs.Task.WaitAsync(token).SuppressThrowing(); } }); } internal Task OnSiloRemovedFromClusterAsync(ClusterMember change) => this.QueueAction( static state => state.Self.OnSiloRemovedFromCluster(state.Change), (Self: this, Change: change), nameof(OnSiloRemovedFromCluster)); private void OnSiloRemovedFromCluster(ClusterMember change) { GrainRuntime.CheckRuntimeContext(this); var toRemove = new List(); foreach (var entry in _directory) { if (change.SiloAddress.Equals(entry.Value.SiloAddress)) { toRemove.Add(entry.Value); } } if (toRemove.Count > 0) { LogDebugDeletingEntries(_logger, toRemove.Count, change.SiloAddress); foreach (var grainAddress in toRemove) { DeregisterCore(grainAddress); } } RemoveSnapshotTransferPartner( change.SiloAddress, snapshotFilter: (state, snapshot) => true, partnerFilter: (state, silo, partitionIndex) => silo.Equals(state)); } internal Task OnRecoveringPartition(MembershipVersion version, RingRange range, SiloAddress siloAddress, int partitionIndex) => this.QueueTask( async () => { try { await WaitForRange(range, version); } catch (Exception exception) { LogWarningErrorWaitingForRangeToUnlock(_logger, exception); } // Remove all snapshots that are associated with the given partition prior or equal to the specified version. RemoveSnapshotTransferPartner( (Version: version, SiloAddress: siloAddress, PartitionIndex: partitionIndex), snapshotFilter: (state, snapshot) => snapshot.DirectoryMembershipVersion <= state.Version, partnerFilter: (state, silo, partitionIndex) => partitionIndex == state.PartitionIndex && silo.Equals(state.SiloAddress)); }); internal Task ProcessMembershipUpdateAsync(DirectoryMembershipSnapshot current) => this.QueueAction( static state => state.Self.ProcessMembershipUpdate(state.Current), (Self: this, Current: current), nameof(ProcessMembershipUpdate)); private void ProcessMembershipUpdate(DirectoryMembershipSnapshot current) { GrainRuntime.CheckRuntimeContext(this); _viewChangeTasks.RemoveAll(task => task.IsCompleted); LogTraceObservedMembershipVersion(_logger, current.Version); var previous = CurrentView; CurrentView = current; var previousRange = previous.GetRange(_id, _partitionIndex); _currentRange = current.GetRange(_id, _partitionIndex); var removedRange = previousRange.Difference(_currentRange).SingleOrDefault(); var addedRange = _currentRange.Difference(previousRange).SingleOrDefault(); #if DEBUG Debug.Assert(addedRange.IsEmpty ^ removedRange.IsEmpty || addedRange.IsEmpty && removedRange.IsEmpty); // Either the range grew or it shrank, but not both. Debug.Assert(previousRange.Difference(_currentRange).Count() < 2); Debug.Assert(_currentRange.Difference(previousRange).Count() < 2); Debug.Assert(_currentRange.Size == previousRange.Size + addedRange.Size - removedRange.Size); Debug.Assert(!removedRange.Intersects(addedRange)); Debug.Assert(!removedRange.Intersects(_currentRange)); Debug.Assert(removedRange.IsEmpty || removedRange.Intersects(previousRange)); Debug.Assert(!addedRange.Intersects(removedRange)); Debug.Assert(addedRange.IsEmpty || addedRange.Intersects(_currentRange)); Debug.Assert(!addedRange.Intersects(previousRange)); Debug.Assert(previousRange.IsEmpty || _currentRange.IsEmpty || previousRange.Start == _currentRange.Start); #endif if (!removedRange.IsEmpty) { _viewChangeTasks.Add(ReleaseRangeAsync(previous, current, removedRange)); } if (!addedRange.IsEmpty) { _viewChangeTasks.Add(AcquireRangeAsync(previous, current, addedRange)); } _viewUpdates.Publish(current); } private async Task ReleaseRangeAsync(DirectoryMembershipSnapshot previous, DirectoryMembershipSnapshot current, RingRange removedRange) { GrainRuntime.CheckRuntimeContext(this); var (tcs, sw) = LockRange(removedRange, current.Version); LogDebugRelinquishingOwnership(_logger, removedRange, current.Version); try { // Snapshot & remove everything not in the current range. // The new owner will have the opportunity to retrieve the snapshot as they take ownership. List removedAddresses = []; HashSet<(SiloAddress, int)> transferPartners = []; // Wait for the range being removed to become valid. await WaitForRange(removedRange, previous.Version); GrainRuntime.CheckRuntimeContext(this); foreach (var (range, ownerIndex, partitionIndex) in current.RangeOwners) { if (range.Intersects(removedRange)) { var owner = current.Members[ownerIndex]; Debug.Assert(!_id.Equals(owner)); transferPartners.Add((owner, partitionIndex)); } } // Collect all addresses that are not in the owned range. foreach (var entry in _directory) { if (removedRange.Contains(entry.Key)) { removedAddresses.Add(entry.Value); } } // Remove these addresses from the partition. foreach (var address in removedAddresses) { if (transferPartners.Count > 0) { LogTraceEvictingEntry(_logger, address); } _directory.Remove(address.GrainId); } var isContiguous = current.Version.Value == previous.Version.Value + 1; if (!isContiguous) { LogDebugEncounteredNonContiguousUpdate(_logger, previous.Version, current.Version, removedRange); return; } if (transferPartners.Count == 0) { LogDebugNoTransferPartners(_logger, removedRange, current.Version); return; } _partitionSnapshots.Add(new PartitionSnapshotState(previous.Version, removedAddresses, transferPartners)); } finally { UnlockRange(removedRange, current.Version, tcs, sw.Elapsed, "release"); } } private async Task AcquireRangeAsync(DirectoryMembershipSnapshot previous, DirectoryMembershipSnapshot current, RingRange addedRange) { GrainRuntime.CheckRuntimeContext(this); // Suspend the range and transfer state from the previous owners. // If the predecessor becomes unavailable or membership advances quickly, we will declare data loss and unlock the range. var (tcs, sw) = LockRange(addedRange, current.Version); try { CoarseStopwatch stopwatch = default; LogDebugAcquiringRange(_logger, addedRange); stopwatch = CoarseStopwatch.StartNew(); // The view change is contiguous if the new version is exactly one greater than the previous version. // If not, we have missed some updates, so we must declare a potential data loss event. var isContiguous = current.Version.Value == previous.Version.Value + 1; bool success; if (isContiguous) { // Transfer subranges from previous owners. var tasks = new List>(); foreach (var previousOwner in previous.Members) { var previousOwnerRanges = previous.GetMemberRangesByPartition(previousOwner); for (var partitionIndex = 0; partitionIndex < previousOwnerRanges.Length; partitionIndex++) { var previousOwnerRange = previousOwnerRanges[partitionIndex]; if (previousOwnerRange.Intersects(addedRange)) { tasks.Add(TransferSnapshotAsync(current, addedRange, previousOwner, partitionIndex, previous.Version)); } } } // Note: there should be no 'await' points before this point. // An await before this point would result in ranges not being locked synchronously. await Task.WhenAll(tasks).WaitAsync(ShutdownToken).SuppressThrowing(); if (ShutdownToken.IsCancellationRequested) { return; } success = tasks.All(t => t.Result); } else { LogDebugNonContiguousViewChange(_logger, previous.Version, current.Version); success = false; } var recovered = false; if (!success) { // Wait for previous versions to be unlocked before proceeding. await WaitForRange(addedRange, previous.Version); await RecoverPartitionRange(current, addedRange); recovered = true; } LogDebugCompletedTransferringEntries(_logger, addedRange, current.Version, stopwatch.ElapsedMilliseconds, recovered); } finally { UnlockRange(addedRange, current.Version, tcs, sw.Elapsed, "acquire"); } } private (TaskCompletionSource Lock, ValueStopwatch Stopwatch) LockRange(RingRange range, MembershipVersion version) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _rangeLocks.Add((range, version, tcs)); return (tcs, ValueStopwatch.StartNew()); } private void UnlockRange(RingRange range, MembershipVersion version, TaskCompletionSource tcs, TimeSpan heldDuration, string operationName) { DirectoryInstruments.RangeLockHeldDuration.Record((long)heldDuration.TotalMilliseconds); if (ShutdownToken.IsCancellationRequested) { // If the partition is stopped, the range is never unlocked and the task is cancelled instead. tcs.SetCanceled(ShutdownToken); } else { tcs.SetResult(); _rangeLocks.Remove((range, version, tcs)); } } private async Task TransferSnapshotAsync(DirectoryMembershipSnapshot current, RingRange addedRange, SiloAddress previousOwner, int partitionIndex, MembershipVersion previousVersion) { try { var stopwatch = ValueStopwatch.StartNew(); LogTraceRequestingEntries(_logger, addedRange, previousOwner, previousVersion); var partition = GetPartitionReference(previousOwner, partitionIndex); // Alternatively, the previous owner could push the snapshot. The pull-based approach is used here because it is simpler. var snapshot = await partition.GetSnapshotAsync(current.Version, previousVersion, addedRange).AsTask().WaitAsync(ShutdownToken); if (snapshot is null) { LogWarningExpectedValidSnapshot(_logger, previousOwner, addedRange); return false; } // The acknowledgement step lets the previous owner know that the snapshot has been received so that it can proceed. InvokeOnClusterMember( previousOwner, async () => await partition.AcknowledgeSnapshotTransferAsync(_id, _partitionIndex, previousVersion), false, nameof(IGrainDirectoryPartition.AcknowledgeSnapshotTransferAsync)).Ignore(); // Wait for previous versions to be unlocked before proceeding. await WaitForRange(addedRange, previousVersion); // Incorporate the values into the grain directory. foreach (var entry in snapshot.GrainAddresses) { DebugAssertOwnership(current, entry.GrainId); LogTraceReceivedEntry(_logger, entry, previousOwner, previousVersion); _directory[entry.GrainId] = entry; } LogDebugTransferredEntries(_logger, snapshot.GrainAddresses.Count, addedRange, previousOwner); DirectoryInstruments.SnapshotTransferCount.Add(1); DirectoryInstruments.SnapshotTransferDuration.Record((long)stopwatch.Elapsed.TotalMilliseconds); return true; } catch (Exception exception) { if (exception is SiloUnavailableException) { LogWarningRemoteHostUnavailable(_logger, addedRange); } else { LogWarningErrorTransferringOwnership(_logger, exception, addedRange); } return false; } } private async Task RecoverPartitionRange(DirectoryMembershipSnapshot current, RingRange addedRange) { var stopwatch = ValueStopwatch.StartNew(); GrainRuntime.CheckRuntimeContext(this); LogDebugRecoveringActivations(_logger, addedRange, current.Version); await foreach (var activations in GetRegisteredActivations(current, addedRange, isValidation: false)) { GrainRuntime.CheckRuntimeContext(this); foreach (var entry in activations) { DebugAssertOwnership(current, entry.GrainId); LogTraceRecoveredEntry(_logger, entry, current.Version); _directory[entry.GrainId] = entry; } } DirectoryInstruments.RangeRecoveryCount.Add(1); DirectoryInstruments.RangeRecoveryDuration.Record((long)stopwatch.Elapsed.TotalMilliseconds); LogDebugCompletedRecoveringActivations(_logger, addedRange, current.Version, stopwatch.Elapsed); } private async IAsyncEnumerable> GetRegisteredActivations(DirectoryMembershipSnapshot current, RingRange range, bool isValidation) { // Membership is guaranteed to be at least as recent as the current view. var clusterMembershipSnapshot = _owner.ClusterMembershipSnapshot; Debug.Assert(clusterMembershipSnapshot.Version >= current.Version); var tasks = new List>>(); foreach (var member in clusterMembershipSnapshot.Members.Values) { if (member.Status is not (SiloStatus.Active or SiloStatus.Joining or SiloStatus.ShuttingDown)) { continue; } tasks.Add(GetRegisteredActivationsFromClusterMember(current.Version, range, member.SiloAddress, isValidation)); } await Task.WhenAll(tasks).WaitAsync(ShutdownToken).SuppressThrowing(); if (ShutdownToken.IsCancellationRequested) { yield break; } foreach (var task in tasks) { yield return await task; } async Task> GetRegisteredActivationsFromClusterMember(MembershipVersion version, RingRange range, SiloAddress siloAddress, bool isValidation) { var stopwatch = ValueStopwatch.StartNew(); var client = _grainFactory.GetSystemTarget(Constants.GrainDirectoryType, siloAddress); var result = await InvokeOnClusterMember( siloAddress, async () => { var innerSw = ValueStopwatch.StartNew(); Immutable> result = default; if (isValidation) { result = await client.GetRegisteredActivations(version, range, isValidation: true); } else { result = await client.RecoverRegisteredActivations(version, range, _id, _partitionIndex); } return result; }, new Immutable>([]), nameof(GetRegisteredActivations)); LogDebugRecoveredEntries(_logger, result.Value.Count, siloAddress, range, version, stopwatch.Elapsed.TotalMilliseconds); return result.Value; } } private async Task InvokeOnClusterMember(SiloAddress siloAddress, Func> func, T defaultValue, string operationName) { GrainRuntime.CheckRuntimeContext(this); var clusterMembershipSnapshot = _owner.ClusterMembershipSnapshot; while (!ShutdownToken.IsCancellationRequested) { if (clusterMembershipSnapshot.GetSiloStatus(siloAddress) is not (SiloStatus.Active or SiloStatus.Joining or SiloStatus.ShuttingDown)) { break; } try { return await func(); } catch (Exception ex) { if (ex is not OrleansMessageRejectionException) { LogErrorErrorInvokingOperation(_logger, ex, operationName, siloAddress); } await _owner.RefreshViewAsync(default, CancellationToken.None); if (_owner.ClusterMembershipSnapshot.Version == clusterMembershipSnapshot.Version) { await Task.Delay(TimeSpan.FromMilliseconds(100)); } clusterMembershipSnapshot = _owner.ClusterMembershipSnapshot; } } ShutdownToken.ThrowIfCancellationRequested(); return defaultValue; } async ValueTask IGrainDirectoryTestHooks.CheckIntegrityAsync() { GrainRuntime.CheckRuntimeContext(this); var current = CurrentView; var range = _currentRange; Debug.Assert(range.Equals(current.GetRange(_id, _partitionIndex))); await WaitForRange(RingRange.Full, current.Version); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _rangeLocks.Add((RingRange.Full, current.Version, tcs)); try { foreach (var entry in _directory) { if (!range.Contains(entry.Key)) { Debug.Fail($"Invariant violated. This host is not the owner of grain '{entry.Key}'."); } DebugAssertOwnership(current, entry.Key); } var missing = 0; var mismatched = 0; var total = 0; await foreach (var activationList in GetRegisteredActivations(current, range, isValidation: true)) { total += activationList.Count; foreach (var entry in activationList) { if (!IsOwner(current, entry.GrainId)) { // The view has been refreshed since the request for registered activations was made. if (current.Version <= current.Version) { Debug.Fail("Invariant violated. This host was sent a registration which it should not have been."); } continue; } if (_directory.TryGetValue(entry.GrainId, out var existingEntry)) { if (!existingEntry.Equals(entry)) { ++mismatched; LogErrorIntegrityViolation(_logger, entry, existingEntry); Debug.Fail($"Integrity violation: Recovered entry '{entry}' does not match existing entry '{existingEntry}'."); } } else { ++missing; LogErrorIntegrityViolation(_logger, entry); Debug.Fail($"Integrity violation: Recovered entry '{entry}' not found in directory."); } } } } finally { if (ShutdownToken.IsCancellationRequested) { tcs.SetCanceled(ShutdownToken); } else { tcs.SetResult(); } _rangeLocks.Remove((RingRange.Full, current.Version, tcs)); } } private sealed record class PartitionSnapshotState( MembershipVersion DirectoryMembershipVersion, List GrainAddresses, HashSet<(SiloAddress SiloAddress, int PartitionIndex)> TransferPartners); [LoggerMessage( Level = LogLevel.Trace, Message = "GetSnapshotAsync('{Version}', '{RangeVersion}', '{Range}')" )] private static partial void LogTraceGetSnapshotAsync(ILogger logger, MembershipVersion version, MembershipVersion rangeVersion, RingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "Transferring '{Count}' entries in range '{Range}' from version '{Version}' snapshot." )] private static partial void LogDebugTransferringEntries(ILogger logger, int count, RingRange range, MembershipVersion version); [LoggerMessage( Level = LogLevel.Warning, Message = "Received a request for a snapshot which this partition does not have, version '{Version}', range version '{RangeVersion}', range '{Range}'." )] private static partial void LogWarningRequestForSnapshot(ILogger logger, MembershipVersion version, MembershipVersion rangeVersion, RingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "Removing version '{Version}' snapshot. Current snapshots: [{CurrentSnapshots}]." )] private static partial void LogDebugRemovingSnapshot(ILogger logger, MembershipVersion version, string currentSnapshots); [LoggerMessage( Level = LogLevel.Debug, Message = "Deleting '{Count}' entries located on now-defunct silo '{SiloAddress}'." )] private static partial void LogDebugDeletingEntries(ILogger logger, int count, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Warning, Message = "Error waiting for range to unlock." )] private static partial void LogWarningErrorWaitingForRangeToUnlock(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Observed membership version '{Version}'." )] private static partial void LogTraceObservedMembershipVersion(ILogger logger, MembershipVersion version); [LoggerMessage( Level = LogLevel.Debug, Message = "Relinquishing ownership of range '{Range}' at version '{Version}'." )] private static partial void LogDebugRelinquishingOwnership(ILogger logger, RingRange range, MembershipVersion version); [LoggerMessage( Level = LogLevel.Trace, Message = "Evicting entry '{Address}' to snapshot." )] private static partial void LogTraceEvictingEntry(ILogger logger, GrainAddress address); [LoggerMessage( Level = LogLevel.Debug, Message = "Encountered non-contiguous update from '{Previous}' to '{Current}' while releasing range '{Range}'. Dropping snapshot." )] private static partial void LogDebugEncounteredNonContiguousUpdate(ILogger logger, MembershipVersion previous, MembershipVersion current, RingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "No transfer partners for snapshot of range '{Range}' at version '{Version}'. Dropping snapshot." )] private static partial void LogDebugNoTransferPartners(ILogger logger, RingRange range, MembershipVersion version); [LoggerMessage( Level = LogLevel.Debug, Message = "Acquiring range '{Range}'." )] private static partial void LogDebugAcquiringRange(ILogger logger, RingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "Non-contiguous view change detected: '{PreviousVersion}' to '{CurrentVersion}'. Performing recovery." )] private static partial void LogDebugNonContiguousViewChange(ILogger logger, MembershipVersion previousVersion, MembershipVersion currentVersion); [LoggerMessage( Level = LogLevel.Debug, Message = "Completed transferring entries for range '{Range}' at version '{Version}' took {Elapsed}ms.{Recovered}" )] private static partial void LogDebugCompletedTransferringEntries(ILogger logger, RingRange range, MembershipVersion version, long elapsed, bool recovered); [LoggerMessage( Level = LogLevel.Trace, Message = "Requesting entries for ranges '{Range}' from '{PreviousOwner}' at version '{PreviousVersion}'." )] private static partial void LogTraceRequestingEntries(ILogger logger, RingRange range, SiloAddress previousOwner, MembershipVersion previousVersion); [LoggerMessage( Level = LogLevel.Warning, Message = "Expected a valid snapshot from previous owner '{PreviousOwner}' for part of ranges '{Range}', but found none." )] private static partial void LogWarningExpectedValidSnapshot(ILogger logger, SiloAddress previousOwner, RingRange range); [LoggerMessage( Level = LogLevel.Trace, Message = "Received '{Entry}' via snapshot from '{PreviousOwner}' for version '{Version}'." )] private static partial void LogTraceReceivedEntry(ILogger logger, GrainAddress entry, SiloAddress previousOwner, MembershipVersion version); [LoggerMessage( Level = LogLevel.Debug, Message = "Transferred '{Count}' entries for range '{Range}' from '{PreviousOwner}'." )] private static partial void LogDebugTransferredEntries(ILogger logger, int count, RingRange range, SiloAddress previousOwner); [LoggerMessage( Level = LogLevel.Warning, Message = "Remote host became unavailable while transferring ownership of range '{Range}'. Recovery will be performed." )] private static partial void LogWarningRemoteHostUnavailable(ILogger logger, RingRange range); [LoggerMessage( Level = LogLevel.Warning, Message = "Error transferring ownership of range '{Range}'. Recovery will be performed." )] private static partial void LogWarningErrorTransferringOwnership(ILogger logger, Exception exception, RingRange range); [LoggerMessage( Level = LogLevel.Debug, Message = "Recovering activations from range '{Range}' at version '{Version}'." )] private static partial void LogDebugRecoveringActivations(ILogger logger, RingRange range, MembershipVersion version); [LoggerMessage( Level = LogLevel.Trace, Message = "Recovered '{Entry}' for version '{Version}'." )] private static partial void LogTraceRecoveredEntry(ILogger logger, GrainAddress entry, MembershipVersion version); [LoggerMessage( Level = LogLevel.Debug, Message = "Completed recovering activations from range '{Range}' at version '{Version}' took '{Elapsed}'." )] private static partial void LogDebugCompletedRecoveringActivations(ILogger logger, RingRange range, MembershipVersion version, TimeSpan elapsed); [LoggerMessage( Level = LogLevel.Debug, Message = "Recovered '{Count}' entries from silo '{SiloAddress}' for ranges '{Range}' at version '{Version}' in {ElapsedMilliseconds}ms." )] private static partial void LogDebugRecoveredEntries(ILogger logger, int count, SiloAddress siloAddress, RingRange range, MembershipVersion version, double elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, Message = "Error invoking operation '{Operation}' on silo '{SiloAddress}'." )] private static partial void LogErrorErrorInvokingOperation(ILogger logger, Exception exception, string operation, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Integrity violation: Recovered entry '{RecoveredRecord}' does not match existing entry '{LocalRecord}'." )] private static partial void LogErrorIntegrityViolation(ILogger logger, GrainAddress recoveredRecord, GrainAddress localRecord); [LoggerMessage( Level = LogLevel.Error, Message = "Integrity violation: Recovered entry '{RecoveredRecord}' not found in directory." )] private static partial void LogErrorIntegrityViolation(ILogger logger, GrainAddress recoveredRecord); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryPartitionSnapshot.cs ================================================ using System.Collections.Generic; #nullable enable namespace Orleans.Runtime.GrainDirectory; [GenerateSerializer, Alias(nameof(GrainDirectoryPartitionSnapshot)), Immutable] internal sealed class GrainDirectoryPartitionSnapshot( MembershipVersion directoryMembershipVersion, List grainAddresses) { [Id(0)] public MembershipVersion DirectoryMembershipVersion { get; } = directoryMembershipVersion; [Id(1)] public List GrainAddresses { get; } = grainAddresses; } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainDirectoryResolver.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.GrainDirectory; using Orleans.Metadata; using Orleans.Runtime.Hosting; namespace Orleans.Runtime.GrainDirectory { internal class GrainDirectoryResolver { private readonly Dictionary directoryPerName = new Dictionary(); private readonly ConcurrentDictionary directoryPerType = new(); private readonly GrainPropertiesResolver grainPropertiesResolver; private readonly IGrainDirectoryResolver[] resolvers; private readonly Func getGrainDirectoryInternal; public GrainDirectoryResolver( IServiceProvider serviceProvider, GrainPropertiesResolver grainPropertiesResolver, IEnumerable resolvers) { this.getGrainDirectoryInternal = GetGrainDirectoryPerType; this.resolvers = resolvers.ToArray(); // Load all registered directories var services = serviceProvider.GetGrainDirectories(); foreach (var svc in services) { this.directoryPerName[svc.Name] = serviceProvider.GetRequiredKeyedService(svc.Name); } this.directoryPerName.TryGetValue(GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY, out var defaultDirectory); this.DefaultGrainDirectory = defaultDirectory; this.grainPropertiesResolver = grainPropertiesResolver; } public IReadOnlyCollection Directories => this.directoryPerName.Values; public IGrainDirectory DefaultGrainDirectory { get; } public IGrainDirectory Resolve(GrainType grainType) => this.directoryPerType.GetOrAdd(grainType, this.getGrainDirectoryInternal); public bool IsUsingDefaultDirectory(GrainType grainType) => Resolve(grainType) == null; private IGrainDirectory GetGrainDirectoryPerType(GrainType grainType) { if (this.TryGetNonDefaultGrainDirectory(grainType, out var result)) { return result; } return this.DefaultGrainDirectory; } internal bool TryGetNonDefaultGrainDirectory(GrainType grainType, out IGrainDirectory directory) { this.grainPropertiesResolver.TryGetGrainProperties(grainType, out var properties); foreach (var resolver in this.resolvers) { if (resolver.TryResolveGrainDirectory(grainType, properties, out directory)) { return true; } } if (properties is not null && properties.Properties.TryGetValue(WellKnownGrainTypeProperties.GrainDirectory, out var directoryName) && !string.IsNullOrWhiteSpace(directoryName)) { if (this.directoryPerName.TryGetValue(directoryName, out directory)) { return true; } else { throw new KeyNotFoundException($"Could not resolve grain directory {directoryName} for grain type {grainType}"); } } directory = null; return false; } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainLocator.cs ================================================ #nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Orleans.GrainDirectory; namespace Orleans.Runtime.GrainDirectory { /// /// Provides functionality for locating grain activations in a cluster and registering the location of grain activations. /// internal class GrainLocator { private readonly GrainLocatorResolver _grainLocatorResolver; public GrainLocator(GrainLocatorResolver grainLocatorResolver) { _grainLocatorResolver = grainLocatorResolver; } public ValueTask Lookup(GrainId grainId) => GetGrainLocator(grainId.Type).Lookup(grainId); public Task Register(GrainAddress address, GrainAddress? previousRegistration) => GetGrainLocator(address.GrainId.Type).Register(address, previousRegistration); public Task Unregister(GrainAddress address, UnregistrationCause cause) => GetGrainLocator(address.GrainId.Type).Unregister(address, cause); public bool TryLookupInCache(GrainId grainId, [NotNullWhen(true)] out GrainAddress? address) => GetGrainLocator(grainId.Type).TryLookupInCache(grainId, out address); public void InvalidateCache(GrainId grainId) => GetGrainLocator(grainId.Type).InvalidateCache(grainId); public void InvalidateCache(GrainAddress address) => GetGrainLocator(address.GrainId.Type).InvalidateCache(address); private IGrainLocator GetGrainLocator(GrainType grainType) => _grainLocatorResolver.GetGrainLocator(grainType); public void UpdateCache(GrainId grainId, SiloAddress siloAddress) => GetGrainLocator(grainId.Type).UpdateCache(grainId, siloAddress); public void UpdateCache(GrainAddressCacheUpdate update) { if (update.ValidGrainAddress is { } validAddress) { Debug.Assert(validAddress.SiloAddress is not null); UpdateCache(validAddress.GrainId, validAddress.SiloAddress); } else { InvalidateCache(update.InvalidGrainAddress); } } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/GrainLocatorResolver.cs ================================================ using System; using System.Collections.Concurrent; using Microsoft.Extensions.DependencyInjection; using Orleans.GrainDirectory; namespace Orleans.Runtime.GrainDirectory { internal class GrainLocatorResolver { private readonly ConcurrentDictionary resolvedLocators = new(); private readonly Func getLocatorInternal; private readonly IServiceProvider _servicesProvider; private readonly GrainDirectoryResolver grainDirectoryResolver; private readonly CachedGrainLocator cachedGrainLocator; private readonly DhtGrainLocator dhtGrainLocator; private ClientGrainLocator _clientGrainLocator; public GrainLocatorResolver( IServiceProvider servicesProvider, GrainDirectoryResolver grainDirectoryResolver, CachedGrainLocator cachedGrainLocator, DhtGrainLocator dhtGrainLocator) { this.getLocatorInternal = GetGrainLocatorInternal; _servicesProvider = servicesProvider; this.grainDirectoryResolver = grainDirectoryResolver; this.cachedGrainLocator = cachedGrainLocator; this.dhtGrainLocator = dhtGrainLocator; } public IGrainLocator GetGrainLocator(GrainType grainType) => resolvedLocators.GetOrAdd(grainType, this.getLocatorInternal); public IGrainLocator GetGrainLocatorInternal(GrainType grainType) { IGrainLocator result; if (grainType.IsClient()) { result = this._clientGrainLocator ??= _servicesProvider.GetRequiredService(); } else if (this.grainDirectoryResolver.IsUsingDefaultDirectory(grainType)) { result = this.dhtGrainLocator; } else { result = this.cachedGrainLocator; } return result; } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/IGrainDirectoryCache.cs ================================================ using System.Collections.Generic; namespace Orleans.Runtime.GrainDirectory { /// /// Caches grain directory entries. /// public interface IGrainDirectoryCache { /// /// Adds a new entry with the given version into the cache: key (grain) --> value /// The new entry will override any existing entry under the given key, /// regardless of the stored version /// /// value to add /// version for the value void AddOrUpdate(GrainAddress value, int version); /// /// Removes an entry from the cache given its key /// /// key to remove /// True if the entry was in the cache and the removal was successful bool Remove(GrainId key); /// /// Removes an entry from the cache given its key /// /// key to remove /// True if the entry was in the cache and the removal was successful bool Remove(GrainAddress key); /// /// Clear the cache, deleting all entries. /// void Clear(); /// /// Looks up the cached value and version by the given key /// /// key for the lookup /// value if the key is found, undefined otherwise /// version of cached value if the key is found, undefined otherwise /// true if the given key is in the cache bool LookUp(GrainId key, out GrainAddress result, out int version); /// /// Returns list of key-value-version tuples stored currently in the cache. /// IEnumerable<(GrainAddress ActivationAddress, int Version)> KeyValues { get; } } internal static class GrainDirectoryCacheExtensions { /// /// Looks up the cached value by the given key. /// /// grain directory cache to look up results from /// key for the lookup /// value if the key is found, undefined otherwise /// true if the given key is in the cache public static bool LookUp(this IGrainDirectoryCache cache, GrainId key, out GrainAddress result) { return cache.LookUp(key, out result, out _); } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/IGrainDirectoryPartition.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; #nullable enable namespace Orleans.Runtime.GrainDirectory; [Alias("IGrainDirectoryPartition")] internal interface IGrainDirectoryPartition : ISystemTarget { [Alias("RegisterAsync")] ValueTask> RegisterAsync(MembershipVersion version, GrainAddress address, GrainAddress? currentRegistration); [Alias("LookupAsync")] ValueTask> LookupAsync(MembershipVersion version, GrainId grainId); [Alias("DeregisterAsync")] ValueTask> DeregisterAsync(MembershipVersion version, GrainAddress address); [Alias("GetSnapshotAsync")] ValueTask GetSnapshotAsync(MembershipVersion version, MembershipVersion rangeVersion, RingRange range); [Alias("AcknowledgeSnapshotTransferAsync")] ValueTask AcknowledgeSnapshotTransferAsync(SiloAddress silo, int partitionIndex, MembershipVersion version); } [Alias("IGrainDirectoryClient")] internal interface IGrainDirectoryClient : ISystemTarget { [Alias("GetRegisteredActivations")] ValueTask>> GetRegisteredActivations(MembershipVersion membershipVersion, RingRange range, bool isValidation); [Alias("RecoverRegisteredActivations")] ValueTask>> RecoverRegisteredActivations(MembershipVersion membershipVersion, RingRange range, SiloAddress siloAddress, int partitionId); } [Alias("IGrainDirectoryTestHooks")] internal interface IGrainDirectoryTestHooks : ISystemTarget { [Alias("CheckIntegrityAsync")] ValueTask CheckIntegrityAsync(); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/IGrainDirectoryResolver.cs ================================================ using System.Diagnostics.CodeAnalysis; using Orleans.GrainDirectory; using Orleans.Metadata; namespace Orleans.Runtime.GrainDirectory { /// /// Associates an instance with a . /// public interface IGrainDirectoryResolver { /// /// Gets an instance for the provided . /// /// Type of the grain. /// The properties. /// The grain directory. /// true if an appropriate grain directory was found, otherwise. bool TryResolveGrainDirectory(GrainType grainType, GrainProperties properties, [NotNullWhen(true)] out IGrainDirectory grainDirectory); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/ILocalClientDirectory.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Runtime.GrainDirectory { internal interface ILocalClientDirectory { bool TryLocalLookup(GrainId grainId, out List addresses); ValueTask> Lookup(GrainId grainId); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/ILocalGrainDirectory.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Orleans.GrainDirectory; #nullable enable namespace Orleans.Runtime.GrainDirectory { internal interface ILocalGrainDirectory : IDhtGrainDirectory { /// /// Starts the local portion of the directory service. /// void Start(); /// /// Stops the local portion of the directory service. /// Task StopAsync(); RemoteGrainDirectory RemoteGrainDirectory { get; } RemoteGrainDirectory CacheValidator { get; } /// /// Removes the record for an non-existing activation from the directory service. /// This is used when a request is received for an activation that cannot be found, /// to lazily clean up the remote directory. /// The timestamp is used to prevent removing a valid entry in a possible (but unlikely) /// race where a request is received for a new activation before the request that causes the /// new activation to be created. /// Note that this method is a no-op if the global configuration parameter DirectoryLazyDeregistrationDelay /// is a zero or negative TimeSpan. /// This method must be called from a scheduler thread. /// /// The address of the activation to remove. /// the silo from which the message to the non-existing activation was sent Task UnregisterAfterNonexistingActivation(GrainAddress address, SiloAddress origin); /// /// Fetches locally known directory information for a grain. /// If there is no local information, either in the cache or in this node's directory partition, /// then this method will return false and leave the list empty. /// /// The ID of the grain to look up. /// An output parameter that receives the list of locally-known activations of the grain. /// True if remote addresses are complete within freshness constraint bool LocalLookup(GrainId grain, out AddressAndTag addresses); /// /// Invalidates cache entry for the given activation address. /// This method is intended to be called whenever a directory client tries to access /// an activation returned from the previous directory lookup and gets a reject from the target silo /// notifying him that the activation does not exist. /// /// The address of the activation that needs to be invalidated in the directory cache for the given grain. void InvalidateCacheEntry(GrainAddress activation); /// /// Invalidates cache entry for the given grain. /// void InvalidateCacheEntry(GrainId grainId); /// /// Adds or updates a cache entry for the given activation address. /// This method is intended to be called whenever a placement decision is made. /// void AddOrUpdateCacheEntry(GrainId grainId, SiloAddress siloAddress); /// /// For testing purposes only. /// Returns the silo that this silo thinks is the primary owner of directory information for /// the provided grain ID. /// /// /// SiloAddress? GetPrimaryForGrain(GrainId grain); /// /// Returns the directory information held in a local directory partition for the provided grain ID. /// The result will be null if no information is held. /// /// /// AddressAndTag GetLocalDirectoryData(GrainId grain); /// /// For testing and troubleshooting purposes only. /// Returns the directory information held in a local directory cache for the provided grain ID. /// The result will be null if no information is held. /// /// /// GrainAddress? GetLocalCacheData(GrainId grain); /// /// Attempts to find the specified grain in the directory cache. /// bool TryCachedLookup(GrainId grainId, [NotNullWhen(true)] out GrainAddress? address); /// /// For determining message forwarding logic, we sometimes check if a silo is part of this cluster or not /// /// the address of the silo /// true if the silo is known to be part of this cluster bool IsSiloInCluster(SiloAddress silo); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/IRemoteClientDirectory.cs ================================================ using System.Collections.Immutable; using System.Threading.Tasks; namespace Orleans.Runtime.GrainDirectory { internal interface IRemoteClientDirectory : ISystemTarget { Task OnUpdateClientRoutes(ImmutableDictionary ConnectedClients, long Version)> update); Task ConnectedClients, long Version)>> GetClientRoutes(ImmutableDictionary knownRoutes); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/IRemoteGrainDirectory.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.GrainDirectory; #nullable enable namespace Orleans.Runtime { /// /// Per-silo system interface for managing the distributed, partitioned grain-silo-activation directory. /// internal interface IRemoteGrainDirectory : ISystemTarget, IDhtGrainDirectory { /// /// Records a bunch of new grain activations. /// This method should be called only remotely during handoff. /// /// The addresses of the grains to register /// Task RegisterMany(List addresses); /// /// Fetch the updated information on the given list of grains. /// This method should be called only remotely to refresh directory caches. /// /// list of grains and generation (version) numbers. The latter denote the versions of /// the lists of activations currently held by the invoker of this method. /// list of tuples holding a grain, generation number of the list of activations, and the list of activations. /// If the generation number of the invoker matches the number of the destination, the list is null. If the destination does not /// hold the information on the grain, generation counter -1 is returned (and the list of activations is null) Task> LookUpMany(List<(GrainId GrainId, int Version)> grainAndETagList); /// /// Registers activations from a split partition with this directory. /// /// The single-activation registrations from the split partition. /// Task AcceptSplitPartition(List singleActivations); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/LocalGrainDirectory.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.Runtime.Scheduler; #nullable enable namespace Orleans.Runtime.GrainDirectory { internal sealed partial class LocalGrainDirectory : ILocalGrainDirectory, ISiloStatusListener, ILifecycleParticipant { private readonly ILogger log; private readonly SiloAddress? seed; private readonly ISiloStatusOracle siloStatusOracle; private readonly IInternalGrainFactory grainFactory; #if NET9_0_OR_GREATER private readonly Lock writeLock = new(); #else private readonly object writeLock = new(); #endif private readonly IServiceProvider _serviceProvider; private DirectoryMembership directoryMembership = DirectoryMembership.Default; // Consider: move these constants into an appropriate place internal const int HOP_LIMIT = 6; // forward a remote request no more than 5 times public static readonly TimeSpan RETRY_DELAY = TimeSpan.FromMilliseconds(200); // Pause 200ms between forwards to let the membership directory settle down internal bool Running; private Catalog? _catalog; internal SiloAddress MyAddress { get; } internal IGrainDirectoryCache DirectoryCache { get; } internal LocalGrainDirectoryPartition DirectoryPartition { get; } public RemoteGrainDirectory RemoteGrainDirectory { get; } public RemoteGrainDirectory CacheValidator { get; } internal GrainDirectoryHandoffManager HandoffManager { get; } public LocalGrainDirectory( IServiceProvider serviceProvider, ILocalSiloDetails siloDetails, ISiloStatusOracle siloStatusOracle, IInternalGrainFactory grainFactory, Factory grainDirectoryPartitionFactory, IOptions developmentClusterMembershipOptions, IOptions grainDirectoryOptions, ILoggerFactory loggerFactory, SystemTargetShared systemTargetShared) { this.log = loggerFactory.CreateLogger(); MyAddress = siloDetails.SiloAddress; this.siloStatusOracle = siloStatusOracle; this.grainFactory = grainFactory; DirectoryCache = GrainDirectoryCacheFactory.CreateGrainDirectoryCache(serviceProvider, grainDirectoryOptions.Value); var primarySiloEndPoint = developmentClusterMembershipOptions.Value.PrimarySiloEndpoint; if (primarySiloEndPoint != null) { this.seed = this.MyAddress.Endpoint.Equals(primarySiloEndPoint) ? this.MyAddress : SiloAddress.New(primarySiloEndPoint, 0); } DirectoryPartition = grainDirectoryPartitionFactory(); HandoffManager = new GrainDirectoryHandoffManager(this, siloStatusOracle, grainFactory, grainDirectoryPartitionFactory, loggerFactory); RemoteGrainDirectory = new RemoteGrainDirectory(this, Constants.DirectoryServiceType, systemTargetShared); CacheValidator = new RemoteGrainDirectory(this, Constants.DirectoryCacheValidatorType, systemTargetShared); // add myself to the list of members AddServer(MyAddress); DirectoryInstruments.RegisterDirectoryPartitionSizeObserve(() => DirectoryPartition.Count); DirectoryInstruments.RegisterMyPortionRingDistanceObserve(() => RingDistanceToSuccessor()); DirectoryInstruments.RegisterMyPortionRingPercentageObserve(() => (((float)this.RingDistanceToSuccessor()) / ((float)(int.MaxValue * 2L))) * 100); DirectoryInstruments.RegisterMyPortionAverageRingPercentageObserve(() => { var ring = this.directoryMembership.MembershipRingList; return ring.Count == 0 ? 0 : ((float)100 / (float)ring.Count); }); DirectoryInstruments.RegisterRingSizeObserve(() => this.directoryMembership.MembershipRingList.Count); _serviceProvider = serviceProvider; } public void Start() { LogDebugStart(); Running = true; siloStatusOracle.SubscribeToSiloStatusEvents(this); } // Note that this implementation stops processing directory change requests (Register, Unregister, etc.) when the Stop event is raised. // This means that there may be a short period during which no silo believes that it is the owner of directory information for a set of // grains (for update purposes), which could cause application requests that require a new activation to be created to time out. // The alternative would be to allow the silo to process requests after it has handed off its partition, in which case those changes // would receive successful responses but would not be reflected in the eventual state of the directory. // It's easy to change this, if we think the trade-off is better the other way. public Task StopAsync() { // This will cause remote write requests to be forwarded to the silo that will become the new owner. // Requests might bounce back and forth for a while as membership stabilizes, but they will either be served by the // new owner of the grain, or will wind up failing. In either case, we avoid requests succeeding at this silo after we've // begun stopping, which could cause them to not get handed off to the new owner. //mark Running as false will exclude myself from CalculateGrainDirectoryPartition(grainId) Running = false; DirectoryPartition.Clear(); DirectoryCache.Clear(); return Task.CompletedTask; } private void AddServer(SiloAddress silo) { lock (this.writeLock) { var existing = this.directoryMembership; if (existing.MembershipCache.Contains(silo)) { // we have already cached this silo return; } // insert new silo in the sorted order long hash = silo.GetConsistentHashCode(); // Find the last silo with hash smaller than the new silo, and insert the latter after (this is why we have +1 here) the former. // Notice that FindLastIndex might return -1 if this should be the first silo in the list, but then // 'index' will get 0, as needed. int index = existing.MembershipRingList.FindLastIndex(siloAddr => siloAddr.GetConsistentHashCode() < hash) + 1; this.directoryMembership = new DirectoryMembership( existing.MembershipRingList.Insert(index, silo), existing.MembershipCache.Add(silo)); HandoffManager.ProcessSiloAddEvent(silo); AdjustLocalDirectory(silo, dead: false); AdjustLocalCache(silo, dead: false); LogDebugSiloAddedSilo(MyAddress, silo); } } private void RemoveServer(SiloAddress silo, SiloStatus status) { lock (this.writeLock) { try { // Only notify the catalog once. Order is important: call BEFORE updating membershipRingList. _catalog = _serviceProvider.GetRequiredService(); _catalog.OnSiloStatusChange(this, silo, status); } catch (Exception exc) { LogErrorCatalogSiloStatusChangeNotificationException(exc, new(silo)); } var existing = this.directoryMembership; if (!existing.MembershipCache.Contains(silo)) { // we have already removed this silo return; } this.directoryMembership = new DirectoryMembership( existing.MembershipRingList.Remove(silo), existing.MembershipCache.Remove(silo)); AdjustLocalDirectory(silo, dead: true); AdjustLocalCache(silo, dead: true); LogDebugSiloRemovedSilo(MyAddress, silo); } } /// /// Adjust local directory following the addition/removal of a silo /// private void AdjustLocalDirectory(SiloAddress silo, bool dead) { // Determine which activations to remove. var activationsToRemove = new List<(GrainId, ActivationId)>(); foreach (var entry in this.DirectoryPartition.GetItems()) { if (entry.Value.Activation is { } address) { // Include any activations from dead silos and from predecessors. if (dead && address.SiloAddress!.Equals(silo) || address.SiloAddress!.IsPredecessorOf(silo)) { activationsToRemove.Add((entry.Key, address.ActivationId)); } } } // Remove all defunct activations. foreach (var activation in activationsToRemove) { DirectoryPartition.RemoveActivation(activation.Item1, activation.Item2); } } /// Adjust local cache following the removal of a silo by dropping: /// 1) entries that point to activations located on the removed silo /// 2) entries for grains that are now owned by this silo (me) /// 3) entries for grains that were owned by this removed silo - we currently do NOT do that. /// If we did 3, we need to do that BEFORE we change the membershipRingList (based on old Membership). /// We don't do that since first cache refresh handles that. /// Second, since Membership events are not guaranteed to be ordered, we may remove a cache entry that does not really point to a failed silo. /// To do that properly, we need to store for each cache entry who was the directory owner that registered this activation (the original partition owner). private void AdjustLocalCache(SiloAddress silo, bool dead) { // remove all records of activations located on the removed silo foreach (var tuple in DirectoryCache.KeyValues) { var activationAddress = tuple.ActivationAddress; // 2) remove entries now owned by me (they should be retrieved from my directory partition) if (MyAddress.Equals(CalculateGrainDirectoryPartition(activationAddress.GrainId))) { DirectoryCache.Remove(activationAddress.GrainId); continue; } // 1) remove entries that point to activations located on the removed silo // For dead silos, remove any activation registered to that silo or one of its predecessors. // For new silos, remove any activation registered to one of its predecessors. if (activationAddress.SiloAddress!.IsPredecessorOf(silo) || dead && activationAddress.SiloAddress.Equals(silo)) { DirectoryCache.Remove(activationAddress.GrainId); } } } internal SiloAddress? FindPredecessor(SiloAddress silo) { var existing = directoryMembership.MembershipRingList; int index = existing.IndexOf(silo); if (index == -1) { LogWarningFindPredecessorSiloNotInList(silo); return null; } return existing.Count > 1 ? existing[(index == 0 ? existing.Count : index) - 1] : null; } internal SiloAddress? FindSuccessor(SiloAddress silo) { var existing = directoryMembership.MembershipRingList; int index = existing.IndexOf(silo); if (index == -1) { LogWarningFindSuccessorSiloNotInList(silo); return null; } return existing.Count > 1 ? existing[(index + 1) % existing.Count] : null; } public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { // This silo's status has changed if (!Equals(updatedSilo, MyAddress)) // Status change for some other silo { if (status.IsTerminating()) { // QueueAction up the "Remove" to run on a system turn CacheValidator.WorkItemGroup.QueueAction(() => RemoveServer(updatedSilo, status)); } else if (status == SiloStatus.Active) // do not do anything with SiloStatus.Starting -- wait until it actually becomes active { // QueueAction up the "Remove" to run on a system turn CacheValidator.WorkItemGroup.QueueAction(() => AddServer(updatedSilo)); } } } private bool IsValidSilo(SiloAddress? silo) => siloStatusOracle.IsFunctionalDirectory(silo); /// /// Finds the silo that owns the directory information for the given grain ID. /// This method will only be null when I'm the only silo in the cluster and I'm shutting down /// /// /// public SiloAddress? CalculateGrainDirectoryPartition(GrainId grainId) { // give a special treatment for special grains if (grainId.IsSystemTarget()) { if (Constants.SystemMembershipTableType.Equals(grainId.Type)) { if (seed == null) { var errorMsg = $"Development clustering cannot run without a primary silo. " + $"Please configure {nameof(DevelopmentClusterMembershipOptions)}.{nameof(DevelopmentClusterMembershipOptions.PrimarySiloEndpoint)} " + "or provide a primary silo address to the UseDevelopmentClustering extension. " + "Alternatively, you may want to use reliable membership, such as Azure Table."; throw new ArgumentException(errorMsg, "grainId = " + grainId); } } LogTraceSystemTargetLookup(MyAddress, grainId, MyAddress); // every silo owns its system targets return MyAddress; } SiloAddress? siloAddress = null; int hash = unchecked((int)grainId.GetUniformHashCode()); // excludeMySelf from being a TargetSilo if we're not running and the excludeThisSIloIfStopping flag is true. see the comment in the Stop method. // excludeThisSIloIfStopping flag was removed because we believe that flag complicates things unnecessarily. We can add it back if it turns out that flag // is doing something valuable. bool excludeMySelf = !Running; var existing = this.directoryMembership; if (existing.MembershipRingList.Count == 0) { // If the membership ring is empty, then we're the owner by default unless we're stopping. return !Running ? null : MyAddress; } // need to implement a binary search, but for now simply traverse the list of silos sorted by their hashes for (var index = existing.MembershipRingList.Count - 1; index >= 0; --index) { var item = existing.MembershipRingList[index]; if (IsSiloNextInTheRing(item, hash, excludeMySelf)) { siloAddress = item; break; } } if (siloAddress == null) { // If not found in the traversal, last silo will do (we are on a ring). // We checked above to make sure that the list isn't empty, so this should always be safe. siloAddress = existing.MembershipRingList[existing.MembershipRingList.Count - 1]; // Make sure it's not us... if (siloAddress.Equals(MyAddress) && excludeMySelf) { siloAddress = existing.MembershipRingList.Count > 1 ? existing.MembershipRingList[existing.MembershipRingList.Count - 2] : null; } } if (log.IsEnabled(LogLevel.Trace)) LogTraceCalculatedDirectoryPartitionOwner( MyAddress, siloAddress, grainId, hash, siloAddress?.GetConsistentHashCode()); return siloAddress; } public SiloAddress? CheckIfShouldForward(GrainId grainId, int hopCount, string operationDescription) { var owner = CalculateGrainDirectoryPartition(grainId); if (owner is null || owner.Equals(MyAddress)) { // Either we don't know about any other silos and we're stopping, or we are the owner. // Null indicates that the operation should be performed locally. // In the case that this host is terminating, any grain registered to this host must terminate. return null; } if (hopCount >= HOP_LIMIT) { // we are not forwarding because there were too many hops already throw new OrleansException($"Silo {MyAddress} is not owner of {grainId}, cannot forward {operationDescription} to owner {owner} because hop limit is reached"); } // forward to the silo that we think is the owner return owner; } public Task RegisterAsync(GrainAddress address, int hopCount) => RegisterAsync(address, previousAddress: null, hopCount: hopCount); public async Task RegisterAsync(GrainAddress address, GrainAddress? previousAddress, int hopCount) { if (hopCount > 0) { DirectoryInstruments.RegistrationsSingleActRemoteReceived.Add(1); } else { DirectoryInstruments.RegistrationsSingleActIssued.Add(1); } // see if the owner is somewhere else (returns null if we are owner) var forwardAddress = this.CheckIfShouldForward(address.GrainId, hopCount, "RegisterAsync"); // on all silos other than first, we insert a retry delay and recheck owner before forwarding if (hopCount > 0 && forwardAddress != null) { await Task.Delay(RETRY_DELAY); forwardAddress = this.CheckIfShouldForward(address.GrainId, hopCount, "RegisterAsync"); if (forwardAddress is not null) { int hash = unchecked((int)address.GrainId.GetUniformHashCode()); LogWarningRegisterAsyncNotOwner( address, hash, forwardAddress, hopCount); } } if (forwardAddress == null) { DirectoryInstruments.RegistrationsSingleActLocal.Add(1); var result = DirectoryPartition.AddSingleActivation(address, previousAddress); // update the cache so next local lookup will find this ActivationAddress in the cache and we will save full lookup. DirectoryCache.AddOrUpdate(result.Address, result.VersionTag); return result; } else { DirectoryInstruments.RegistrationsSingleActRemoteSent.Add(1); // otherwise, notify the owner AddressAndTag result = await GetDirectoryReference(forwardAddress).RegisterAsync(address, previousAddress, hopCount + 1); // Caching optimization: // cache the result of a successful RegisterSingleActivation call, only if it is not a duplicate activation. // this way next local lookup will find this ActivationAddress in the cache and we will save a full lookup! if (result.Address == null) return result; if (!address.Equals(result.Address) || !IsValidSilo(address.SiloAddress)) return result; // update the cache so next local lookup will find this ActivationAddress in the cache and we will save full lookup. DirectoryCache.AddOrUpdate(result.Address, result.VersionTag); return result; } } public Task UnregisterAfterNonexistingActivation(GrainAddress addr, SiloAddress origin) { LogTraceUnregisterAfterNonexistingActivation(addr, origin); if (origin == null || this.directoryMembership.MembershipCache.Contains(origin)) { // the request originated in this cluster, call unregister here return UnregisterAsync(addr, UnregistrationCause.NonexistentActivation, 0); } else { // the request originated in another cluster, call unregister there var remoteDirectory = GetDirectoryReference(origin); return remoteDirectory.UnregisterAsync(addr, UnregistrationCause.NonexistentActivation); } } public async Task UnregisterAsync(GrainAddress address, UnregistrationCause cause, int hopCount) { if (hopCount > 0) { DirectoryInstruments.UnregistrationsRemoteReceived.Add(1); } else { DirectoryInstruments.UnregistrationsIssued.Add(1); } if (hopCount == 0) InvalidateCacheEntry(address); // see if the owner is somewhere else (returns null if we are owner) var forwardAddress = this.CheckIfShouldForward(address.GrainId, hopCount, "UnregisterAsync"); // on all silos other than first, we insert a retry delay and recheck owner before forwarding if (hopCount > 0 && forwardAddress != null) { await Task.Delay(RETRY_DELAY); forwardAddress = this.CheckIfShouldForward(address.GrainId, hopCount, "UnregisterAsync"); LogWarningUnregisterAsyncNotOwner( address, forwardAddress, hopCount); } if (forwardAddress == null) { // we are the owner DirectoryInstruments.UnregistrationsLocal.Add(1); DirectoryPartition.RemoveActivation(address.GrainId, address.ActivationId, cause); } else { DirectoryInstruments.UnregistrationsRemoteSent.Add(1); // otherwise, notify the owner await GetDirectoryReference(forwardAddress).UnregisterAsync(address, cause, hopCount + 1); } } // helper method to avoid code duplication inside UnregisterManyAsync private void UnregisterOrPutInForwardList(List addresses, UnregistrationCause cause, int hopCount, ref Dictionary>? forward, string context) { foreach (var address in addresses) { // see if the owner is somewhere else (returns null if we are owner) var forwardAddress = this.CheckIfShouldForward(address.GrainId, hopCount, context); if (forwardAddress != null) { forward ??= new(); if (!forward.TryGetValue(forwardAddress, out var list)) forward[forwardAddress] = list = new(); list.Add(address); } else { // we are the owner DirectoryInstruments.UnregistrationsLocal.Add(1); DirectoryPartition.RemoveActivation(address.GrainId, address.ActivationId, cause); } } } public async Task UnregisterManyAsync(List addresses, UnregistrationCause cause, int hopCount) { if (hopCount > 0) { DirectoryInstruments.UnregistrationsManyRemoteReceived.Add(1); } else { DirectoryInstruments.UnregistrationsManyIssued.Add(1); } Dictionary>? forwardlist = null; UnregisterOrPutInForwardList(addresses, cause, hopCount, ref forwardlist, "UnregisterManyAsync"); // before forwarding to other silos, we insert a retry delay and re-check destination if (hopCount > 0 && forwardlist != null) { await Task.Delay(RETRY_DELAY); Dictionary>? forwardlist2 = null; UnregisterOrPutInForwardList(addresses, cause, hopCount, ref forwardlist2, "UnregisterManyAsync"); forwardlist = forwardlist2; if (forwardlist != null) { LogWarningUnregisterManyAsyncNotOwner( forwardlist.Count, hopCount); } } // forward the requests if (forwardlist != null) { var tasks = new List(); foreach (var kvp in forwardlist) { DirectoryInstruments.UnregistrationsManyRemoteSent.Add(1); tasks.Add(GetDirectoryReference(kvp.Key).UnregisterManyAsync(kvp.Value, cause, hopCount + 1)); } // wait for all the requests to finish await Task.WhenAll(tasks); } } public bool LocalLookup(GrainId grain, out AddressAndTag result) { DirectoryInstruments.LookupsLocalIssued.Add(1); var silo = CalculateGrainDirectoryPartition(grain); LogDebugLocalLookupAttempt( MyAddress, grain, silo, new(grain), new(silo)); //this will only happen if I'm the only silo in the cluster and I'm shutting down if (silo == null) { LogTraceLocalLookupMineNull(grain); result = default; return false; } // handle cache DirectoryInstruments.LookupsCacheIssued.Add(1); var address = GetLocalCacheData(grain); if (address != default) { result = new(address, 0); LogTraceLocalLookupCache(grain, result.Address); DirectoryInstruments.LookupsCacheSuccesses.Add(1); DirectoryInstruments.LookupsLocalSuccesses.Add(1); return true; } // check if we own the grain if (silo.Equals(MyAddress)) { DirectoryInstruments.LookupsLocalDirectoryIssued.Add(1); result = GetLocalDirectoryData(grain); if (result.Address == null) { // it can happen that we cannot find the grain in our partition if there were // some recent changes in the membership LogTraceLocalLookupMineNull(grain); return false; } LogTraceLocalLookupMine(grain, result.Address); DirectoryInstruments.LookupsLocalDirectorySuccesses.Add(1); DirectoryInstruments.LookupsLocalSuccesses.Add(1); return true; } LogTraceTryFullLookupElse(grain); result = default; return false; } public AddressAndTag GetLocalDirectoryData(GrainId grain) => DirectoryPartition.LookUpActivation(grain); public GrainAddress? GetLocalCacheData(GrainId grain) => DirectoryCache.LookUp(grain, out var cache) && IsValidSilo(cache.SiloAddress) ? cache : null; public async Task LookupAsync(GrainId grainId, int hopCount = 0) { if (hopCount > 0) { DirectoryInstruments.LookupsRemoteReceived.Add(1); } else { DirectoryInstruments.LookupsFullIssued.Add(1); } // see if the owner is somewhere else (returns null if we are owner) var forwardAddress = this.CheckIfShouldForward(grainId, hopCount, "LookUpAsync"); // on all silos other than first, we insert a retry delay and recheck owner before forwarding if (hopCount > 0 && forwardAddress != null) { await Task.Delay(RETRY_DELAY); forwardAddress = this.CheckIfShouldForward(grainId, hopCount, "LookUpAsync"); if (forwardAddress is not null) { int hash = unchecked((int)grainId.GetUniformHashCode()); LogWarningLookupAsyncNotOwner( grainId, hash, forwardAddress, hopCount); } } if (forwardAddress == null) { // we are the owner DirectoryInstruments.LookupsLocalDirectoryIssued.Add(1); var localResult = DirectoryPartition.LookUpActivation(grainId); if (localResult.Address == null) { // it can happen that we cannot find the grain in our partition if there were // some recent changes in the membership LogTraceFullLookupMineNone(grainId); return new(default, GrainInfo.NO_ETAG); } LogTraceFullLookupMine(grainId, localResult.Address); DirectoryInstruments.LookupsLocalDirectorySuccesses.Add(1); return localResult; } else { // Just a optimization. Why sending a message to someone we know is not valid. if (!IsValidSilo(forwardAddress)) { throw new OrleansException($"Current directory at {MyAddress} is not stable to perform the lookup for grainId {grainId} (it maps to {forwardAddress}, which is not a valid silo). Retry later."); } DirectoryInstruments.LookupsRemoteSent.Add(1); var result = await GetDirectoryReference(forwardAddress).LookupAsync(grainId, hopCount + 1); // update the cache if (result.Address is { } address && IsValidSilo(address.SiloAddress)) { DirectoryCache.AddOrUpdate(address, result.VersionTag); } LogTraceFullLookupRemote(grainId, result.Address); return result; } } public async Task DeleteGrainAsync(GrainId grainId, int hopCount) { // see if the owner is somewhere else (returns null if we are owner) var forwardAddress = this.CheckIfShouldForward(grainId, hopCount, "DeleteGrainAsync"); // on all silos other than first, we insert a retry delay and recheck owner before forwarding if (hopCount > 0 && forwardAddress != null) { await Task.Delay(RETRY_DELAY); forwardAddress = this.CheckIfShouldForward(grainId, hopCount, "DeleteGrainAsync"); LogWarningDeleteGrainAsyncNotOwner( grainId, forwardAddress, hopCount); } if (forwardAddress == null) { // we are the owner DirectoryPartition.RemoveGrain(grainId); } else { // otherwise, notify the owner DirectoryCache.Remove(grainId); await GetDirectoryReference(forwardAddress).DeleteGrainAsync(grainId, hopCount + 1); } } public void InvalidateCacheEntry(GrainId grainId) { DirectoryCache.Remove(grainId); } public void InvalidateCacheEntry(GrainAddress activationAddress) { DirectoryCache.Remove(activationAddress); } /// /// For testing purposes only. /// Returns the silo that this silo thinks is the primary owner of directory information for /// the provided grain ID. /// /// /// public SiloAddress? GetPrimaryForGrain(GrainId grain) => CalculateGrainDirectoryPartition(grain); private long RingDistanceToSuccessor() => FindSuccessor(MyAddress) is { } successor ? CalcRingDistance(MyAddress, successor) : 0; private static long CalcRingDistance(SiloAddress silo1, SiloAddress silo2) { const long ringSize = int.MaxValue * 2L; long hash1 = silo1.GetConsistentHashCode(); long hash2 = silo2.GetConsistentHashCode(); if (hash2 > hash1) return hash2 - hash1; if (hash2 < hash1) return ringSize - (hash1 - hash2); return 0; } internal IRemoteGrainDirectory GetDirectoryReference(SiloAddress silo) { return this.grainFactory.GetSystemTarget(Constants.DirectoryServiceType, silo); } private bool IsSiloNextInTheRing(SiloAddress siloAddr, int hash, bool excludeMySelf) { return siloAddr.GetConsistentHashCode() <= hash && (!excludeMySelf || !siloAddr.Equals(MyAddress)); } public bool IsSiloInCluster(SiloAddress silo) { return this.directoryMembership.MembershipCache.Contains(silo); } public void AddOrUpdateCacheEntry(GrainId grainId, SiloAddress siloAddress) => this.DirectoryCache.AddOrUpdate(new GrainAddress { GrainId = grainId, SiloAddress = siloAddress }, 0); public bool TryCachedLookup(GrainId grainId, [NotNullWhen(true)] out GrainAddress? address) => (address = GetLocalCacheData(grainId)) is not null; void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.RuntimeServices, (ct) => Task.Run(() => Start()), (ct) => Task.Run(() => StopAsync())); } private readonly struct SiloAddressLogValue(SiloAddress silo) { public override string ToString() => silo.ToStringWithHashCode(); } private readonly struct GrainHashLogValue(GrainId grain) { public override string ToString() => grain.GetUniformHashCode().ToString(); } private readonly struct SiloHashLogValue(SiloAddress? silo) { public override string ToString() => silo?.GetConsistentHashCode().ToString() ?? "null"; } [LoggerMessage( Level = LogLevel.Debug, Message = "Start" )] private partial void LogDebugStart(); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo {SiloAddress} added silo {OtherSiloAddress}" )] private partial void LogDebugSiloAddedSilo(SiloAddress siloAddress, SiloAddress otherSiloAddress); [LoggerMessage( EventId = (int)ErrorCode.Directory_SiloStatusChangeNotification_Exception, Level = LogLevel.Error, Message = "CatalogSiloStatusListener.SiloStatusChangeNotification has thrown an exception when notified about removed silo {Silo}." )] private partial void LogErrorCatalogSiloStatusChangeNotificationException(Exception exception, SiloAddressLogValue silo); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo {LocalSilo} removed silo {OtherSilo}" )] private partial void LogDebugSiloRemovedSilo(SiloAddress localSilo, SiloAddress otherSilo); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100201, Level = LogLevel.Warning, Message = "Got request to find predecessors of silo {SiloAddress}, which is not in the list of members" )] private partial void LogWarningFindPredecessorSiloNotInList(SiloAddress siloAddress); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100203, Level = LogLevel.Warning, Message = "Got request to find successors of silo {SiloAddress}, which is not in the list of members" )] private partial void LogWarningFindSuccessorSiloNotInList(SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Trace, Message = "Silo {SiloAddress} looked for a system target {GrainId}, returned {TargetSilo}" )] private partial void LogTraceSystemTargetLookup(SiloAddress siloAddress, GrainId grainId, SiloAddress targetSilo); [LoggerMessage( Level = LogLevel.Trace, Message = "Silo {SiloAddress} calculated directory partition owner silo {OwnerAddress} for grain {GrainId}: {GrainIdHash} --> {OwnerAddressHash}" )] private partial void LogTraceCalculatedDirectoryPartitionOwner(SiloAddress siloAddress, SiloAddress? ownerAddress, GrainId grainId, int grainIdHash, long? ownerAddressHash); [LoggerMessage( Level = LogLevel.Warning, Message = "RegisterAsync - It seems we are not the owner of activation {Address} (hash: {Hash:X}), trying to forward it to {ForwardAddress} (hopCount={HopCount})" )] private partial void LogWarningRegisterAsyncNotOwner(GrainAddress address, int hash, SiloAddress forwardAddress, int hopCount); [LoggerMessage( Level = LogLevel.Trace, Message = "UnregisterAfterNonexistingActivation addr={Address} origin={Origin}" )] private partial void LogTraceUnregisterAfterNonexistingActivation(GrainAddress address, SiloAddress origin); [LoggerMessage( Level = LogLevel.Warning, Message = "UnregisterAsync - It seems we are not the owner of activation {Address}, trying to forward it to {ForwardAddress} (hopCount={HopCount})" )] private partial void LogWarningUnregisterAsyncNotOwner(GrainAddress address, SiloAddress? forwardAddress, int hopCount); [LoggerMessage( Level = LogLevel.Warning, Message = "RegisterAsync - It seems we are not the owner of some activations, trying to forward it to {Count} silos (hopCount={HopCount})" )] private partial void LogWarningUnregisterManyAsyncNotOwner(int count, int hopCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo {SiloAddress} tries to lookup for {Grain}-->{PartitionOwner} ({GrainHashCode}-->{PartitionOwnerHashCode})" )] private partial void LogDebugLocalLookupAttempt(SiloAddress siloAddress, GrainId grain, SiloAddress? partitionOwner, GrainHashLogValue grainHashCode, SiloHashLogValue partitionOwnerHashCode); [LoggerMessage( Level = LogLevel.Trace, Message = "LocalLookup mine {GrainId}=null" )] private partial void LogTraceLocalLookupMineNull(GrainId grainId); [LoggerMessage( Level = LogLevel.Trace, Message = "LocalLookup cache {GrainId}={TargetAddress}" )] private partial void LogTraceLocalLookupCache(GrainId grainId, GrainAddress? targetAddress); [LoggerMessage( Level = LogLevel.Trace, Message = "LocalLookup mine {GrainId}={Address}" )] private partial void LogTraceLocalLookupMine(GrainId grainId, GrainAddress? address); [LoggerMessage( Level = LogLevel.Trace, Message = "TryFullLookup else {GrainId}=null" )] private partial void LogTraceTryFullLookupElse(GrainId grainId); [LoggerMessage( Level = LogLevel.Warning, Message = "LookupAsync - It seems we are not the owner of grain {GrainId} (hash: {Hash:X}), trying to forward it to {ForwardAddress} (hopCount={HopCount})" )] private partial void LogWarningLookupAsyncNotOwner(GrainId grainId, int hash, SiloAddress forwardAddress, int hopCount); [LoggerMessage( Level = LogLevel.Trace, Message = "FullLookup mine {GrainId}=none" )] private partial void LogTraceFullLookupMineNone(GrainId grainId); [LoggerMessage( Level = LogLevel.Trace, Message = "FullLookup mine {GrainId}={Address}" )] private partial void LogTraceFullLookupMine(GrainId grainId, GrainAddress? address); [LoggerMessage( Level = LogLevel.Trace, Message = "FullLookup remote {GrainId}={Address}" )] private partial void LogTraceFullLookupRemote(GrainId grainId, GrainAddress? address); [LoggerMessage( Level = LogLevel.Warning, Message = "DeleteGrainAsync - It seems we are not the owner of grain {GrainId}, trying to forward it to {ForwardAddress} (hopCount={HopCount})" )] private partial void LogWarningDeleteGrainAsyncNotOwner(GrainId grainId, SiloAddress? forwardAddress, int hopCount); private class DirectoryMembership { public DirectoryMembership(ImmutableList membershipRingList, ImmutableHashSet membershipCache) { this.MembershipRingList = membershipRingList; this.MembershipCache = membershipCache; } public static DirectoryMembership Default { get; } = new DirectoryMembership(ImmutableList.Empty, ImmutableHashSet.Empty); public ImmutableList MembershipRingList { get; } public ImmutableHashSet MembershipCache { get; } } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/LocalGrainDirectoryPartition.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; #nullable enable namespace Orleans.Runtime.GrainDirectory { [Serializable] internal sealed class GrainInfo { public const int NO_ETAG = -1; public GrainAddress? Activation { get; private set; } public DateTime TimeCreated { get; private set; } public int VersionTag { get; private set; } public bool OkToRemove(UnregistrationCause cause, TimeSpan lazyDeregistrationDelay) { switch (cause) { case UnregistrationCause.Force: return true; case UnregistrationCause.NonexistentActivation: { var delayparameter = lazyDeregistrationDelay; if (delayparameter <= TimeSpan.Zero) return false; // no lazy deregistration else return (TimeCreated <= DateTime.UtcNow - delayparameter); } default: throw new OrleansException("unhandled case"); } } public GrainAddress TryAddSingleActivation(GrainAddress address, GrainAddress? previousAddress) { // If there is an existing address which does not match the 'previousAddress' then we cannot add the new address. if (Activation is { } existing && (previousAddress is null || !previousAddress.Equals(existing))) { return existing; } else { Activation = address; TimeCreated = DateTime.UtcNow; VersionTag = Random.Shared.Next(); return address; } } public bool RemoveActivation(ActivationId act, UnregistrationCause cause, TimeSpan lazyDeregistrationDelay, out bool wasRemoved) { wasRemoved = false; if (Activation is { } existing && existing.ActivationId.Equals(act) && OkToRemove(cause, lazyDeregistrationDelay)) { wasRemoved = true; Activation = null; VersionTag = Random.Shared.Next(); } return wasRemoved; } public GrainAddress? Merge(GrainInfo other) { var otherActivation = other.Activation; if (otherActivation is not null && Activation is null) { Activation = other.Activation; TimeCreated = other.TimeCreated; VersionTag = Random.Shared.Next(); } else if (Activation is not null && otherActivation is not null) { // Grain is supposed to be in single activation mode, but we have two activations!! // Eventually we should somehow delegate handling this to the silo, but for now, we'll arbitrarily pick one value. if (Activation.ActivationId.Key.CompareTo(otherActivation.ActivationId.Key) < 0) { var activationToDrop = Activation; Activation = otherActivation; TimeCreated = other.TimeCreated; VersionTag = Random.Shared.Next(); return activationToDrop; } // Keep this activation and destroy the other. return otherActivation; } return null; } } internal sealed partial class LocalGrainDirectoryPartition { // Should we change this to SortedList<> or SortedDictionary so we can extract chunks better for shipping the full // partition to a follower, or should we leave it as a Dictionary to get O(1) lookups instead of O(log n), figuring we do // a lot more lookups and so can sort periodically? /// /// contains a map from grain to its list of activations along with the version (etag) counter for the list /// private Dictionary partitionData; private readonly object lockable; private readonly ILogger log; private readonly ISiloStatusOracle siloStatusOracle; private readonly IOptions grainDirectoryOptions; internal int Count { get { return partitionData.Count; } } public LocalGrainDirectoryPartition(ISiloStatusOracle siloStatusOracle, IOptions grainDirectoryOptions, ILoggerFactory loggerFactory) { partitionData = new Dictionary(); lockable = new object(); log = loggerFactory.CreateLogger(); this.siloStatusOracle = siloStatusOracle; this.grainDirectoryOptions = grainDirectoryOptions; } private bool IsValidSilo(SiloAddress? silo) => silo is not null && siloStatusOracle.IsFunctionalDirectory(silo); internal void Clear() { lock (lockable) { partitionData.Clear(); } } /// /// Returns all entries stored in the partition as an enumerable collection /// /// public List> GetItems() { lock (lockable) { return partitionData.ToList(); } } /// /// Adds a new activation to the directory partition /// /// The registered ActivationAddress and version associated with this directory mapping internal AddressAndTag AddSingleActivation(GrainAddress address, GrainAddress? previousAddress) { LogTraceAddingSingleActivation(address.SiloAddress, address.GrainId, address.ActivationId); if (!IsValidSilo(address.SiloAddress)) { var siloStatus = this.siloStatusOracle.GetApproximateSiloStatus(address.SiloAddress); throw new OrleansException($"Trying to register {address.GrainId} on invalid silo: {address.SiloAddress}. Known status: {siloStatus}"); } lock (lockable) { if (!partitionData.TryGetValue(address.GrainId, out var grainInfo)) { partitionData[address.GrainId] = grainInfo = new GrainInfo(); } else { var siloAddress = grainInfo.Activation?.SiloAddress; // If there is an existing entry pointing to an invalid silo then remove it if (siloAddress != null && !IsValidSilo(siloAddress)) { partitionData[address.GrainId] = grainInfo = new GrainInfo(); } } return new(grainInfo.TryAddSingleActivation(address, previousAddress), grainInfo.VersionTag); } } /// /// Removes an activation of the given grain from the partition /// /// the identity of the grain /// the id of the activation /// reason for removing the activation internal void RemoveActivation(GrainId grain, ActivationId activation, UnregistrationCause cause = UnregistrationCause.Force) { var wasRemoved = false; lock (lockable) { if (partitionData.TryGetValue(grain, out var value) && value.RemoveActivation(activation, cause, this.grainDirectoryOptions.Value.LazyDeregistrationDelay, out wasRemoved)) { // if the last activation for the grain was removed, we remove the entire grain info partitionData.Remove(grain); } } LogTraceRemovingActivation(grain, cause, wasRemoved); } /// /// Removes the grain (and, effectively, all its activations) from the directory /// /// internal void RemoveGrain(GrainId grain) { lock (lockable) { partitionData.Remove(grain); } LogTraceRemovingGrain(grain); } internal AddressAndTag LookUpActivation(GrainId grain) { AddressAndTag result; lock (lockable) { if (!partitionData.TryGetValue(grain, out var grainInfo) || grainInfo.Activation is null) { return default; } result = new(grainInfo.Activation, grainInfo.VersionTag); } if (!IsValidSilo(result.Address?.SiloAddress)) { result = new(null, result.VersionTag); } return result; } /// /// Returns the version number of the list of activations for the grain. /// If the grain is not found, -1 is returned. /// /// /// internal int GetGrainETag(GrainId grain) { lock (lockable) { return partitionData.TryGetValue(grain, out var info) ? info.VersionTag : GrainInfo.NO_ETAG; } } /// /// Merges one partition into another, assuming partitions are disjoint. /// This method is supposed to be used by handoff manager to update the partitions when the system view (set of live silos) changes. /// /// /// Activations which must be deactivated. internal Dictionary>? Merge(LocalGrainDirectoryPartition other) { Dictionary>? activationsToRemove = null; lock (lockable) { foreach (var pair in other.partitionData) { ref var grainInfo = ref CollectionsMarshal.GetValueRefOrAddDefault(partitionData, pair.Key, out _); if (grainInfo is { } existing) { LogDebugMergingDisjointPartitions(pair.Key); var activationToDrop = existing.Merge(pair.Value); if (activationToDrop == null) continue; (CollectionsMarshal.GetValueRefOrAddDefault(activationsToRemove ??= new(), activationToDrop.SiloAddress!, out _) ??= new()).Add(activationToDrop); } else { grainInfo = pair.Value; } } } return activationsToRemove; } /// /// Runs through all entries in the partition and moves/copies (depending on the given flag) the /// entries satisfying the given predicate into a new partition. /// This method is supposed to be used by handoff manager to update the partitions when the system view (set of live silos) changes. /// /// filter predicate (usually if the given grain is owned by particular silo) /// Entries satisfying the given predicate internal List Split(Predicate predicate) { var result = new List(); lock (lockable) { foreach (var pair in partitionData) { if (pair.Value.Activation is { } address && predicate(pair.Key)) { result.Add(address); } } } for (var i = result.Count - 1; i >= 0; i--) { if (!IsValidSilo(result[i].SiloAddress)) { result.RemoveAt(i); } } return result; } /// /// Sets the internal partition dictionary to the one given as input parameter. /// This method is supposed to be used by handoff manager to update the old partition with a new partition. /// /// new internal partition dictionary internal void Set(Dictionary newPartitionData) { partitionData = newPartitionData; } /// /// Updates partition with a new delta of changes. /// This method is supposed to be used by handoff manager to update the partition with a set of delta changes. /// /// dictionary holding a set of delta updates to this partition. /// If the value for a given key in the delta is valid, then existing entry in the partition is replaced. /// Otherwise, i.e., if the value is null, the corresponding entry is removed. /// internal void Update(Dictionary newPartitionDelta) { lock (lockable) { foreach (var kv in newPartitionDelta) { if (kv.Value != null) { partitionData[kv.Key] = kv.Value; } else { partitionData.Remove(kv.Key); } } } } public override string ToString() { var sb = new StringBuilder(); lock (lockable) { foreach (var grainEntry in partitionData) { if (grainEntry.Value.Activation is { } activation) { sb.Append(" ").Append(grainEntry.Key.ToString()).Append("[" + grainEntry.Value.VersionTag + "]"). Append(" => ").Append(activation.GrainId.ToString()). Append(" @ ").AppendLine(activation.ToString()); } } } return sb.ToString(); } [LoggerMessage( Level = LogLevel.Trace, Message = "Adding single activation for grain {SiloAddress} {GrainId} {ActivationId}" )] private partial void LogTraceAddingSingleActivation(SiloAddress? siloAddress, GrainId grainId, ActivationId activationId); [LoggerMessage( Level = LogLevel.Trace, Message = "Removing activation for grain {GrainId} cause={Cause} was_removed={WasRemoved}" )] private partial void LogTraceRemovingActivation(GrainId grainId, UnregistrationCause cause, bool wasRemoved); [LoggerMessage( Level = LogLevel.Trace, Message = "Removing grain {GrainId}" )] private partial void LogTraceRemovingGrain(GrainId grainId); [LoggerMessage( Level = LogLevel.Debug, Message = "While merging two disjoint partitions, same grain {GrainId} was found in both partitions" )] private partial void LogDebugMergingDisjointPartitions(GrainId grainId); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/LruGrainDirectoryCache.cs ================================================ using System; using System.Collections.Generic; using Orleans.Caching; namespace Orleans.Runtime.GrainDirectory; internal sealed class LruGrainDirectoryCache(int maxCacheSize) : ConcurrentLruCache(capacity: maxCacheSize), IGrainDirectoryCache { private static readonly Func<(GrainAddress Address, int Version), GrainAddress, bool> ActivationAddressesMatch = (value, state) => GrainAddress.MatchesGrainIdAndSilo(state, value.Address); public void AddOrUpdate(GrainAddress activationAddress, int version) => AddOrUpdate(activationAddress.GrainId, (activationAddress, version)); public bool Remove(GrainId key) => TryRemove(key); public bool Remove(GrainAddress grainAddress) => TryRemove(grainAddress.GrainId, ActivationAddressesMatch, grainAddress); public bool LookUp(GrainId key, out GrainAddress result, out int version) { if (TryGet(key, out var entry)) { version = entry.Version; result = entry.ActivationAddress; return true; } version = default; result = default; return false; } public IEnumerable<(GrainAddress ActivationAddress, int Version)> KeyValues { get { foreach (var entry in this) { yield return (entry.Value.ActivationAddress, entry.Value.Version); } } } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/RemoteGrainDirectory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.GrainDirectory; #nullable enable namespace Orleans.Runtime.GrainDirectory { internal sealed partial class RemoteGrainDirectory : SystemTarget, IRemoteGrainDirectory { private readonly LocalGrainDirectory router; private readonly LocalGrainDirectoryPartition partition; private readonly ILogger logger; internal RemoteGrainDirectory(LocalGrainDirectory localGrainDirectory, GrainType grainType, SystemTargetShared shared) : base(grainType, shared) { router = localGrainDirectory; partition = localGrainDirectory.DirectoryPartition; logger = shared.LoggerFactory.CreateLogger($"{typeof(RemoteGrainDirectory).FullName}.CacheValidator"); shared.ActivationDirectory.RecordNewTarget(this); } public Task RegisterAsync(GrainAddress address, GrainAddress? previousAddress, int hopCount) { DirectoryInstruments.RegistrationsSingleActRemoteReceived.Add(1); return router.RegisterAsync(address, previousAddress, hopCount); } public Task RegisterMany(List addresses) { if (addresses == null || addresses.Count == 0) { throw new ArgumentException("Addresses cannot be an empty list or null"); } // validate that this request arrived correctly //logger.Assert(ErrorCode.Runtime_Error_100140, silo.Matches(router.MyAddress), "destination address != my address"); LogRegisterMany(addresses.Count); return Task.WhenAll(addresses.Select(addr => router.RegisterAsync(addr, previousAddress: null, 1))); } public Task UnregisterAsync(GrainAddress address, UnregistrationCause cause, int hopCount) { return router.UnregisterAsync(address, cause, hopCount); } public Task UnregisterManyAsync(List addresses, UnregistrationCause cause, int hopCount) { return router.UnregisterManyAsync(addresses, cause, hopCount); } public Task DeleteGrainAsync(GrainId grainId, int hopCount) { return router.DeleteGrainAsync(grainId, hopCount); } public Task LookupAsync(GrainId grainId, int hopCount) { return router.LookupAsync(grainId, hopCount); } public Task> LookUpMany(List<(GrainId GrainId, int Version)> grainAndETagList) { DirectoryInstruments.ValidationsCacheReceived.Add(1); LogLookUpMany(grainAndETagList.Count); var result = new List(); foreach (var tuple in grainAndETagList) { int curGen = partition.GetGrainETag(tuple.GrainId); if (curGen == tuple.Version || curGen == GrainInfo.NO_ETAG) { // the grain entry either does not exist in the local partition (curGen = -1) or has not been updated result.Add(new(GrainAddress.GetAddress(null, tuple.GrainId, default), curGen)); } else { // the grain entry has been updated -- fetch and return its current version var lookupResult = partition.LookUpActivation(tuple.GrainId); // validate that the entry is still in the directory (i.e., it was not removed concurrently) if (lookupResult.Address != null) { result.Add(lookupResult); } else { result.Add(new(GrainAddress.GetAddress(null, tuple.GrainId, default), GrainInfo.NO_ETAG)); } } } return Task.FromResult(result); } public Task AcceptSplitPartition(List singleActivations) { router.HandoffManager.AcceptExistingRegistrations(singleActivations); return Task.CompletedTask; } public Task RegisterAsync(GrainAddress address, int hopCount = 0) => router.RegisterAsync(address, hopCount); [LoggerMessage( Level = LogLevel.Trace, Message = "RegisterMany Count={Count}" )] private partial void LogRegisterMany(int count); [LoggerMessage( Level = LogLevel.Trace, Message = "LookUpMany for {Count} entries" )] private partial void LogLookUpMany(int count); } } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/RingRange.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; #nullable enable namespace Orleans.Runtime.GrainDirectory; /// /// Represents a contiguous range of zero or more values on a ring. /// [GenerateSerializer, Immutable, Alias(nameof(RingRange))] internal readonly struct RingRange : IEquatable, ISpanFormattable, IComparable { // The exclusive starting point for the range. // Note that _start == _end == 1 is used as a special value to represent a full range. [Id(0)] private readonly uint _start; // The inclusive ending point for the range. // Note that _start == _end == 1 is used as a special value to represent a full range. [Id(1)] private readonly uint _end; public bool IsEmpty => _start == _end && _start == 0; public bool IsFull => _start == _end && _start != 0; // Whether the range includes uint.MaxValue. internal bool IsWrapped => _start >= _end && _start != 0; public static RingRange Full { get; } = new (1, 1); public static RingRange Empty { get; } = new (0, 0); public uint Start => IsFull ? 0 : _start; public uint End => IsFull ? 0 : _end; private RingRange(uint start, uint end) { _start = start == end && start > 1 ? 1 : start; _end = start == end && start > 1 ? 1 : end; } // For internal use only. internal static RingRange Create(uint start, uint end) => new (start, end); /// /// Creates a range representing a single point. /// /// The point which the range will include. /// A range including only . public static RingRange FromPoint(uint point) => new (unchecked(point - 1), point); /// /// Gets the size of the range. /// public uint Size { get { if (_start == _end) { // Empty if (_start == 0) return 0; // Full return uint.MaxValue; } // Normal if (_end > _start) return _end - _start; // Wrapped return uint.MaxValue - _start + _end; } } public int CompareTo(uint point) { if (Contains(point)) { return 0; } var start = Start; if (IsWrapped) { // Start > End (wrap-around case) if (point <= start) { // Range starts after N (range > N) return -1; } // n > _end // Range starts & ends before N (range < N) return 1; } if (point <= start) { // Range starts after N (range > N) return 1; } // n > _end // Range starts & ends before N (range < N) return -1; } /// /// Checks if n is element of (Start, End], while remembering that the ranges are on a ring /// /// true if n is in (Start, End], false otherwise internal bool Contains(GrainId grainId) => Contains(grainId.GetUniformHashCode()); /// /// checks if n is element of (Start, End], while remembering that the ranges are on a ring /// /// /// true if n is in (Start, End], false otherwise public bool Contains(uint point) { if (IsEmpty) { return false; } var num = point; if (Start < End) { return num > Start && num <= End; } // Start > End return num > Start || num <= End; } public float SizePercent => Size * (100.0f / uint.MaxValue); public bool Equals(RingRange other) => _start == other._start && _end == other._end; public override bool Equals(object? obj) => obj is RingRange other && Equals(other); public override int GetHashCode() => HashCode.Combine(_start, _end); public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { return IsEmpty ? destination.TryWrite($"(0, 0) 0.00%", out charsWritten) : IsFull ? destination.TryWrite($"(0, 0] (100.00%)", out charsWritten) : destination.TryWrite($"(0x{Start:X8}, 0x{End:X8}] ({SizePercent:0.00}%)", out charsWritten); } public bool Intersects(RingRange other) => !IsEmpty && !other.IsEmpty && (Equals(other) || Contains(other.End) || other.Contains(End)); internal RingRange Complement() { if (IsEmpty) { return Full; } if (IsFull) { return Empty; } return new RingRange(End, Start); } internal IEnumerable Intersections(RingRange other) { if (!Intersects(other)) { // No intersections. yield break; } if (IsFull) { // One intersection, the other range. yield return other; } else if (other.IsFull) { yield return this; } else if (IsWrapped ^ other.IsWrapped) { var wrapped = IsWrapped ? this : other; var normal = IsWrapped ? other : this; var (normalStart, normalEnd) = (normal.Start, normal.End); var (wrappedStart, wrappedEnd) = (wrapped.Start, wrapped.End); // There are possibly two intersections, between the normal and wrapped range. // low high // ...---NB====WE----WB====NE----... // Intersection at the low side. if (wrappedEnd > normalStart) { // ---NB====WE--- yield return new RingRange(normalStart, wrappedEnd); } // Intersection at the high side. if (wrappedStart < normalEnd) { // ---WB====NE--- yield return new RingRange(wrappedStart, normalEnd); } } else { yield return new RingRange(Math.Max(Start, other.Start), Math.Min(End, other.End)); } } // Gets the set difference: the sub-ranges which are in this range but are not in the 'other' range. internal IEnumerable Difference(RingRange other) { // Additions are the intersections between this range and the inverse of the previous range. foreach (var addition in Intersections(other.Complement())) { Debug.Assert(!addition.Intersects(other)); Debug.Assert(addition.Intersects(this)); yield return addition; } } public static bool operator ==(RingRange left, RingRange right) => left.Equals(right); public static bool operator !=(RingRange left, RingRange right) => !(left == right); } ================================================ FILE: src/Orleans.Runtime/GrainDirectory/RingRangeCollection.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Orleans.Runtime.Utilities; #nullable enable namespace Orleans.Runtime.GrainDirectory; // Read-only, sorted collection of non-overlapping ranges. [GenerateSerializer, Immutable, Alias(nameof(RingRangeCollection))] internal readonly struct RingRangeCollection : IEquatable, ISpanFormattable, IEnumerable { public RingRangeCollection(ImmutableArray ranges) { #if DEBUG Debug.Assert(!ranges.IsDefault); // Ranges must be in sorted order and must not overlap with each other. for (var i = 1; i < ranges.Length; i++) { var prev = ranges[i - 1]; var curr = ranges[i]; Debug.Assert(!curr.IsEmpty); Debug.Assert(!prev.Intersects(curr)); Debug.Assert(curr.Start >= prev.Start); } if (ranges.Length > 1) { Debug.Assert(!ranges[0].Intersects(ranges[^1])); } #endif Ranges = ranges; } public static RingRangeCollection Create(TCollection ranges) where TCollection : ICollection { ArgumentNullException.ThrowIfNull(ranges); var result = ImmutableArray.CreateBuilder(ranges.Count); foreach (var range in ranges) { if (range.IsEmpty) { continue; } result.AddRange(range); } result.Sort((l, r) => l.Start.CompareTo(r.Start)); return new(result.ToImmutable()); } public static RingRangeCollection Empty { get; } = new([]); [Id(0)] public ImmutableArray Ranges { get; } public bool IsDefault => Ranges.IsDefault; public bool IsEmpty => Ranges.Length == 0 || Ranges.All(r => r.IsEmpty); public bool IsFull => !IsEmpty && Ranges.Sum(r => r.Size) == uint.MaxValue; public uint Size => (uint)Ranges.Sum(static r => r.Size); public float SizePercent => Size * (100.0f / uint.MaxValue); public bool Contains(GrainId grainId) => Contains(grainId.GetUniformHashCode()); public bool Contains(uint value) { return SearchAlgorithms.RingRangeBinarySearch( Ranges.Length, Ranges, static (ranges, index) => ranges[index], value) >= 0; } public bool Intersects(RingRange other) { if (IsEmpty || other.IsEmpty) { return false; } if (Contains(other.End)) { return true; } foreach (var range in Ranges) { if (other.Contains(range.End)) { return true; } } return false; } public bool Intersects(RingRangeCollection other) { if (IsEmpty || other.IsEmpty) { return false; } foreach (var range in Ranges) { if (other.Contains(range.End)) { return true; } } foreach (var otherRange in other.Ranges) { if (Contains(otherRange.End)) { return true; } } return false; } public RingRangeCollection Difference(RingRangeCollection previous) { // Ranges in left must not overlap with each other. // Ranges in right must not overlap with each other. // Corresponding ranges in left and right have the same starting points. // The number of ranges in both 'Ranges' or 'previous.Ranges' is either zero or the configured number of ranges, // i.e., if both collections have more than zero ranges, the both have the same number of ranges. if (Ranges.Length == previous.Ranges.Length) { var result = ImmutableArray.CreateBuilder(Ranges.Length); for (var i = 0; i < Ranges.Length; i++) { var c = Ranges[i]; var p = previous.Ranges[i]; Debug.Assert(c.Start == p.Start); if (c.Size > p.Size) { result.Add(RingRange.Create(p.End, c.End)); } } // If the last range wrapped around but its extension does not wrap around, move it to the front. // This preserves sort order. if (result.Count > 1 && result[^1].Start < result[^2].Start) { var last = result[^1]; result.RemoveAt(result.Count - 1); result.Insert(0, last); } return new(result.ToImmutable()); } else { if (Ranges.Length > previous.Ranges.Length) { Debug.Assert(previous.Ranges.Length == 0); return this; } else { Debug.Assert(Ranges.Length == 0 ^ previous.Ranges.Length == 0); return Empty; } } } public bool Equals(RingRangeCollection other) { if (IsEmpty && other.IsEmpty) { return true; } if (IsEmpty ^ other.IsEmpty) { return false; } return Ranges.SequenceEqual(other.Ranges); } public static bool operator ==(RingRangeCollection left, RingRangeCollection right) => left.Equals(right); public static bool operator !=(RingRangeCollection left, RingRangeCollection right) => !(left == right); public override bool Equals(object? obj) => obj is RingRangeCollection range && Equals(range); public override int GetHashCode() { var result = new HashCode(); result.Add(Ranges.Length); if (!Ranges.IsDefaultOrEmpty) { foreach (var range in Ranges) { result.Add(range); } } return result.ToHashCode(); } public ImmutableArray.Enumerator GetEnumerator() => Ranges.GetEnumerator(); public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"({Ranges.Length} subranges), {SizePercent:0.00}%", out charsWritten); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Ranges).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Ranges).GetEnumerator(); } ================================================ FILE: src/Orleans.Runtime/GrainTypeManager/ClusterManifestSystemTarget.cs ================================================ using System.Threading.Tasks; using Orleans.Metadata; namespace Orleans.Runtime { internal sealed class ClusterManifestSystemTarget : SystemTarget, IClusterManifestSystemTarget, ISiloManifestSystemTarget, ILifecycleParticipant { private readonly GrainManifest _siloManifest; private readonly IClusterMembershipService _clusterMembershipService; private readonly IClusterManifestProvider _clusterManifestProvider; private readonly ClusterManifestUpdate _noUpdate = default; private MembershipVersion _cachedMembershipVersion; private ClusterManifestUpdate _cachedUpdate; public ClusterManifestSystemTarget( IClusterMembershipService clusterMembershipService, IClusterManifestProvider clusterManifestProvider, SystemTargetShared shared) : base(Constants.ManifestProviderType, shared) { _siloManifest = clusterManifestProvider.LocalGrainManifest; _clusterMembershipService = clusterMembershipService; _clusterManifestProvider = clusterManifestProvider; shared.ActivationDirectory.RecordNewTarget(this); } public ValueTask GetClusterManifest() => new(_clusterManifestProvider.Current); public ValueTask GetClusterManifestUpdate(MajorMinorVersion version) { var manifest = _clusterManifestProvider.Current; // Only return an updated manifest if it is newer than the provided version. if (manifest.Version <= version) { return new (_noUpdate); } // Maintain a cache of whether the current manifest contains all active servers so that it // does not need to be recomputed each time. var membershipSnapshot = _clusterMembershipService.CurrentSnapshot; if (_cachedUpdate is null || membershipSnapshot.Version > _cachedMembershipVersion || manifest.Version > _cachedUpdate.Version) { var includesAllActiveServers = true; foreach (var server in membershipSnapshot.Members) { if (server.Value.Status == SiloStatus.Active) { if (!manifest.Silos.ContainsKey(server.Key)) { includesAllActiveServers = false; } } } _cachedUpdate = new ClusterManifestUpdate(manifest.Version, manifest.Silos, includesAllActiveServers); _cachedMembershipVersion = membershipSnapshot.Version; } return new (_cachedUpdate); } public ValueTask GetSiloManifest() => new(_siloManifest); void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { // We don't participate in any lifecycle stages: activating this instance is all that is necessary. } } } ================================================ FILE: src/Orleans.Runtime/GrainTypeManager/ISiloManifestSystemTarget.cs ================================================ using System.Threading.Tasks; using Orleans.Metadata; namespace Orleans.Runtime { internal interface ISiloManifestSystemTarget : ISystemTarget { ValueTask GetSiloManifest(); } } ================================================ FILE: src/Orleans.Runtime/Hosting/ActivationRebalancerExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Runtime; using System.Diagnostics.CodeAnalysis; using Orleans.Configuration.Internal; using Orleans.Placement.Rebalancing; using Orleans.Runtime.Placement.Rebalancing; namespace Orleans.Hosting; #nullable enable /// /// Extensions for configuring activation rebalancing. /// public static class ActivationRebalancerExtensions { /// /// Enables activation rebalancing for the entire cluster. /// /// /// Activation rebalancing attempts to distribute activations around the cluster in such a /// way that it optimizes both activation count and memory usages across the silos of the cluster. /// You can read more on activation rebalancing here /// [Experimental("ORLEANSEXP002")] public static ISiloBuilder AddActivationRebalancer(this ISiloBuilder builder) => builder.AddActivationRebalancer(); /// . /// Custom backoff provider for determining next session after a failed attempt. [Experimental("ORLEANSEXP002")] public static ISiloBuilder AddActivationRebalancer(this ISiloBuilder builder) where TProvider : class, IFailedSessionBackoffProvider => builder.ConfigureServices(service => service.AddActivationRebalancer()); private static IServiceCollection AddActivationRebalancer(this IServiceCollection services) where TProvider : class, IFailedSessionBackoffProvider { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ActivationRebalancerMonitor>(); services.AddTransient(); services.AddSingleton(); services.AddFromExisting(); if (typeof(TProvider).IsAssignableTo(typeof(ILifecycleParticipant))) { services.AddFromExisting(typeof(ILifecycleParticipant), typeof(TProvider)); } return services; } } ================================================ FILE: src/Orleans.Runtime/Hosting/ActivationRepartitioningExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration.Internal; using Orleans.Runtime; using Orleans.Configuration; using Orleans.Runtime.Placement.Repartitioning; using System.Diagnostics.CodeAnalysis; using Orleans.Placement.Repartitioning; namespace Orleans.Hosting; #nullable enable public static class ActivationRepartitioningExtensions { /// /// Enables activation repartitioning for this silo. /// /// /// Activation repartitioning attempts to optimize grain call locality by collocating activations which communicate frequently, /// while keeping the number of activations on each silo approximately equal. /// [Experimental("ORLEANSEXP001")] public static ISiloBuilder AddActivationRepartitioner(this ISiloBuilder builder) => builder.AddActivationRepartitioner(); /// /// Enables activation repartitioning for this silo. /// /// /// Activation repartitioning attempts to optimize grain call locality by collocating activations which communicate frequently, /// while keeping the number of activations on each silo approximately equal. /// /// The type of the imbalance rule to use. [Experimental("ORLEANSEXP001")] public static ISiloBuilder AddActivationRepartitioner(this ISiloBuilder builder) where TRule : class, IImbalanceToleranceRule => builder .ConfigureServices(services => services.AddActivationRepartitioner()); private static IServiceCollection AddActivationRepartitioner(this IServiceCollection services) where TRule : class, IImbalanceToleranceRule { services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ActivationRepartitioner>(); services.AddTransient(); services.AddSingleton(); services.AddFromExisting(); if (typeof(TRule).IsAssignableTo(typeof(ILifecycleParticipant))) { services.AddFromExisting(typeof(ILifecycleParticipant), typeof(TRule)); } return services; } } ================================================ FILE: src/Orleans.Runtime/Hosting/CoreHostingExtensions.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.GrainDirectory; using Orleans.Runtime; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Hosting; using Orleans.Runtime.MembershipService; namespace Orleans.Hosting { /// /// Extensions for instances. /// public static class CoreHostingExtensions { private static readonly ServiceDescriptor DirectoryDescriptor = ServiceDescriptor.Singleton(); /// /// Add propagation through grain calls. /// Note: according to activity will be created only when any listener for activity exists and returns . /// /// The builder. /// The builder. public static ISiloBuilder AddActivityPropagation(this ISiloBuilder builder) { builder.Services.TryAddSingleton(DistributedContextPropagator.Current); return builder .AddOutgoingGrainCallFilter() .AddIncomingGrainCallFilter(); } /// /// Configures the silo to use development-only clustering and listen on localhost. /// /// The silo builder. /// The silo port. /// The gateway port. /// /// The endpoint of the primary silo, or to use this silo as the primary. /// /// The service id. /// The cluster id. /// The silo builder. public static ISiloBuilder UseLocalhostClustering( this ISiloBuilder builder, int siloPort = EndpointOptions.DEFAULT_SILO_PORT, int gatewayPort = EndpointOptions.DEFAULT_GATEWAY_PORT, IPEndPoint? primarySiloEndpoint = null, string serviceId = ClusterOptions.DevelopmentServiceId, string clusterId = ClusterOptions.DevelopmentClusterId) { builder.Configure(options => { options.AdvertisedIPAddress = IPAddress.Loopback; options.SiloPort = siloPort; options.GatewayPort = gatewayPort; }); builder.UseDevelopmentClustering(optionsBuilder => ConfigurePrimarySiloEndpoint(optionsBuilder, primarySiloEndpoint)); builder.ConfigureServices(services => { // If the caller did not override service id or cluster id, configure default values as a fallback. if (string.Equals(serviceId, ClusterOptions.DevelopmentServiceId) && string.Equals(clusterId, ClusterOptions.DevelopmentClusterId)) { services.PostConfigure(options => { if (string.IsNullOrWhiteSpace(options.ClusterId)) options.ClusterId = ClusterOptions.DevelopmentClusterId; if (string.IsNullOrWhiteSpace(options.ServiceId)) options.ServiceId = ClusterOptions.DevelopmentServiceId; }); } else { services.Configure(options => { options.ServiceId = serviceId; options.ClusterId = clusterId; }); } }); return builder; } /// /// Configures the silo to use development-only clustering. /// /// /// /// The endpoint of the primary silo, or to use this silo as the primary. /// /// The silo builder. public static ISiloBuilder UseDevelopmentClustering(this ISiloBuilder builder, IPEndPoint primarySiloEndpoint) { return builder.UseDevelopmentClustering(optionsBuilder => ConfigurePrimarySiloEndpoint(optionsBuilder, primarySiloEndpoint)); } /// /// Configures the silo to use development-only clustering. /// public static ISiloBuilder UseDevelopmentClustering( this ISiloBuilder builder, Action configureOptions) { return builder.UseDevelopmentClustering(options => options.Configure(configureOptions)); } /// /// Configures the silo to use development-only clustering. /// public static ISiloBuilder UseDevelopmentClustering( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices( services => { configureOptions?.Invoke(services.AddOptions()); services.ConfigureFormatter(); services .AddSingleton() .AddFromExisting(); services .AddSingleton() .AddFromExisting, MembershipTableSystemTarget>(); }); } private static void ConfigurePrimarySiloEndpoint(OptionsBuilder optionsBuilder, IPEndPoint? primarySiloEndpoint) { optionsBuilder.Configure((DevelopmentClusterMembershipOptions options, IOptions endpointOptions) => { if (primarySiloEndpoint is null) { primarySiloEndpoint = endpointOptions.Value.GetPublicSiloEndpoint(); } options.PrimarySiloEndpoint = primarySiloEndpoint; }); } /// /// Opts-in to the experimental distributed grain directory. /// /// The silo builder to register the directory implementation with. /// The name of the directory to register, or null to register the directory as the default. /// The provided silo builder. [Experimental("ORLEANSEXP003")] public static ISiloBuilder AddDistributedGrainDirectory(this ISiloBuilder siloBuilder, string? name = null) { var services = siloBuilder.Services; if (string.IsNullOrEmpty(name)) { name = GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY; } // Distributed Grain Directory services.TryAddSingleton(); if (!services.Contains(DirectoryDescriptor)) { services.Add(DirectoryDescriptor); services.AddGrainDirectory(name, (sp, name) => sp.GetRequiredService()); } return siloBuilder; } } } ================================================ FILE: src/Orleans.Runtime/Hosting/DefaultSiloServices.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.Configuration.Validators; using Orleans.Core; using Orleans.GrainReferences; using Orleans.Metadata; using Orleans.Networking.Shared; using Orleans.Placement.Repartitioning; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Configuration; using Orleans.Runtime.ConsistentRing; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.MembershipService; using Orleans.Runtime.Messaging; using Orleans.Runtime.Metadata; using Orleans.Runtime.Placement; using Orleans.Runtime.Placement.Filtering; using Orleans.Runtime.Providers; using Orleans.Runtime.Utilities; using Orleans.Runtime.Versions; using Orleans.Runtime.Versions.Compatibility; using Orleans.Runtime.Versions.Selector; using Orleans.Serialization; using Orleans.Serialization.Cloning; using Orleans.Serialization.Internal; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using Orleans.Statistics; using Orleans.Storage; using Orleans.Timers; using Orleans.Timers.Internal; using Orleans.Versions; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Hosting { internal static class DefaultSiloServices { private static readonly ServiceDescriptor ServiceDescriptor = new(typeof(ServicesAdded), new ServicesAdded()); internal static void AddDefaultServices(ISiloBuilder builder) { var services = builder.Services; if (services.Contains(ServiceDescriptor)) { return; } services.Add(ServiceDescriptor); // Common services services.AddLogging(); services.AddOptions(); services.AddMetrics(); services.TryAddSingleton(TimeProvider.System); services.TryAddSingleton(); services.TryAddSingleton(typeof(IOptionFormatter<>), typeof(DefaultOptionsFormatter<>)); services.TryAddSingleton(typeof(IOptionFormatterResolver<>), typeof(DefaultOptionsFormatterResolver<>)); services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); services.PostConfigure(options => options.SiloName ??= $"Silo_{Guid.NewGuid().ToString("N")[..5]}"); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddFromExisting(); services.AddSingleton(); services.AddFromExisting, SiloOptionsLogger>(); services.AddSingleton(); services.AddFromExisting, SiloControl>(); // Lifecycle services.AddSingleton>(); services.TryAddFromExisting>(); services.AddFromExisting, ServiceLifecycle>(); // Statistics services.AddSingleton(); #pragma warning disable 618 services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); #pragma warning restore 618 services.TryAddSingleton(); services.TryAddFromExisting(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddTransient(); services.AddKeyedTransient(typeof(ICancellationSourcesExtension), (sp, _) => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService().ConcreteGrainFactory); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddFromExisting(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ActivationCollector>(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ActivationWorkingSet>(); // Directory services.TryAddSingleton(); services.TryAddFromExisting(); services.AddFromExisting, LocalGrainDirectory>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(sp => DhtGrainLocator.FromLocalGrainDirectory(sp.GetService())); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting, CachedGrainLocator>(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddSingleton(FactoryUtility.Create); services.TryAddSingleton(sp => sp.GetRequiredService().Gateway as IConnectedClientCollection ?? new EmptyConnectedClientCollection()); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddFromExisting(); services.AddFromExisting, InsideRuntimeClient>(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddFromExisting, DeploymentLoadPublisher>(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, MembershipTableManager>(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, MembershipSystemTarget>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddFromExisting(); services.AddSingleton(); services.AddFromExisting, ClusterHealthMonitor>(); services.AddFromExisting(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, LocalSiloHealthMonitor>(); services.AddSingleton(); services.AddFromExisting, MembershipAgent>(); services.AddFromExisting(); services.AddSingleton(); services.AddFromExisting, MembershipTableCleanupAgent>(); services.AddFromExisting(); services.AddSingleton(); services.AddFromExisting, SiloStatusListenerManager>(); services.AddSingleton(); services.TryAddFromExisting(); services.AddFromExisting, ClusterMembershipService>(); services.TryAddSingleton(); services.AddFromExisting(); services.AddFromExisting, ClientDirectory>(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddSingleton(); services.TryAddSingleton(FactoryUtility.Create); // Placement services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Configure the default placement strategy. services.TryAddSingleton(); // Placement filters services.AddSingleton(); services.AddSingleton(); // Placement directors services.AddPlacementDirector(); services.AddPlacementDirector(); services.AddPlacementDirector(ServiceLifetime.Transient); services.AddPlacementDirector(); services.AddPlacementDirector(); services.AddPlacementDirector(); services.AddPlacementDirector(); services.AddPlacementDirector(); // Versioning services.TryAddSingleton(); services.TryAddSingleton(); // Version selector strategy if (!services.Any(x => x.ServiceType == typeof(IVersionStore))) { services.AddSingleton(); services.AddFromExisting(); } services.AddFromExisting, GrainVersionStore>(); services.AddKeyedSingleton(nameof(AllCompatibleVersions)); services.AddKeyedSingleton(nameof(LatestVersion)); services.AddKeyedSingleton(nameof(MinimumVersion)); // Versions selectors services.AddKeyedSingleton(typeof(MinimumVersion)); services.AddKeyedSingleton(typeof(LatestVersion)); services.AddKeyedSingleton(typeof(AllCompatibleVersions)); // Compatibility services.AddSingleton(); // Compatability strategy services.AddKeyedSingleton(nameof(AllVersionsCompatible)); services.AddKeyedSingleton(nameof(BackwardCompatible)); services.AddKeyedSingleton(nameof(StrictVersionCompatible)); // Compatability directors services.AddKeyedSingleton(typeof(BackwardCompatible)); services.AddKeyedSingleton(typeof(AllVersionsCompatible)); services.AddKeyedSingleton(typeof(StrictVersionCompatible)); services.TryAddSingleton>(sp => () => sp.GetRequiredService()); // Grain activation services.AddSingleton(); services.AddSingleton(); services.AddFromExisting, Catalog>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting, IncomingRequestMonitor>(); services.AddFromExisting(); services.AddSingleton(); // Scoped to a grain activation services.AddScoped(sp => RuntimeContext.Current ?? throw new InvalidOperationException("No current grain context available.")); services.TryAddSingleton( sp => { // TODO: make this not sux - jbragg var consistentRingOptions = sp.GetRequiredService>().Value; var siloDetails = sp.GetRequiredService(); var loggerFactory = sp.GetRequiredService(); var siloStatusOracle = sp.GetRequiredService(); if (consistentRingOptions.UseVirtualBucketsConsistentRing) { return new VirtualBucketsRingProvider(siloDetails.SiloAddress, loggerFactory, consistentRingOptions.NumVirtualBucketsConsistentRing, siloStatusOracle); } return new ConsistentRingProvider(siloDetails.SiloAddress, loggerFactory, siloStatusOracle); }); services.AddSingleton, DefaultGrainTypeOptionsProvider>(); services.AddSingleton, EndpointOptionsProvider>(); // Type metadata services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService().GrainTypeMap); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ClusterManifestProvider>(); services.AddSingleton(); services.AddFromExisting, ClusterManifestSystemTarget>(); //Add default option formatter if none is configured, for options which are required to be configured services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); services.ConfigureFormatter(); // This validator needs to construct the IMembershipOracle and the IMembershipTable // so move it in the end so other validator are called first services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient, SiloMessagingOptionsValidator>(); services.AddTransient>(static sp => sp.GetRequiredService>()); // Enable hosted client. services.TryAddSingleton(); services.AddFromExisting, HostedClient>(); services.TryAddSingleton(); services.TryAddFromExisting(); services.TryAddFromExisting(); // Enable collection specific Age limits services.AddOptions() .Configure>((options, grainTypeOptions) => { foreach (var grainClass in grainTypeOptions.Value.Classes) { var attr = grainClass.GetCustomAttribute(); if (attr != null) { var className = RuntimeTypeNameFormatter.Format(grainClass); options.ClassSpecificCollectionAge[className] = attr.AgeLimit; } } }); // Validate all CollectionAgeLimit values for the right configuration. services.AddTransient(); services.AddTransient(); services.TryAddSingleton(); // persistent state facet support services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(typeof(IAttributeToFactoryMapper), typeof(PersistentStateAttributeMapper)); services.TryAddSingleton(); // IAsyncEnumerable support services.AddScoped(); services.AddKeyedTransient( typeof(IAsyncEnumerableGrainExtension), (sp, _) => sp.GetRequiredService()); // Networking services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton, ConnectionManagerLifecycleAdapter>(); services.AddSingleton, SiloConnectionMaintainer>(); services.AddKeyedSingleton( SiloConnectionFactory.ServicesKey, (sp, key) => ActivatorUtilities.CreateInstance(sp)); services.AddKeyedSingleton( SiloConnectionListener.ServicesKey, (sp, key) => ActivatorUtilities.CreateInstance(sp)); services.AddKeyedSingleton( GatewayConnectionListener.ServicesKey, (sp, key) => ActivatorUtilities.CreateInstance(sp)); services.AddSerializer(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddTransient(); services.AddSingleton, ConfigureOrleansJsonSerializerOptions>(); services.AddSingleton(); services.TryAddTransient(sp => ActivatorUtilities.CreateInstance( sp, sp.GetRequiredService>().Value)); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); // Use Orleans server. services.AddSingleton, SiloConnectionListener>(); services.AddSingleton, GatewayConnectionListener>(); services.AddSingleton(); services.AddSingleton(); // Activation migration services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, ActivationMigrationManager>(); services.AddSingleton(); // Cancellation manager services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, GrainCallCancellationManager>(); ApplyConfiguration(builder); } private static void ApplyConfiguration(ISiloBuilder builder) { var services = builder.Services; var cfg = builder.Configuration.GetSection("Orleans"); var knownProviderTypes = GetRegisteredProviders(); if (cfg["Name"] is { Length: > 0 } name) { services.Configure(siloOptions => siloOptions.SiloName = name); } services.Configure(cfg); services.Configure(cfg.GetSection("Messaging")); if (cfg.GetSection("Endpoints") is { } ep && ep.Exists()) { services.Configure(o => o.Bind(ep)); } if (bool.TryParse(cfg["EnableDistributedTracing"], out var enableDistributedTracing) && enableDistributedTracing) { builder.AddActivityPropagation(); } ApplySubsection(builder, cfg, knownProviderTypes, "Clustering"); ApplySubsection(builder, cfg, knownProviderTypes, "Reminders"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "BroadcastChannel"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "Streaming"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "GrainStorage"); ApplyNamedSubsections(builder, cfg, knownProviderTypes, "GrainDirectory"); static void ConfigureProvider(ISiloBuilder builder, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string kind, string? name, IConfigurationSection configurationSection) { var providerType = configurationSection["ProviderType"] ?? "Default"; var provider = GetRequiredProvider(knownProviderTypes, kind, providerType); provider.Configure(builder, name, configurationSection); } static IProviderBuilder GetRequiredProvider(Dictionary<(string Kind, string Name), Type> knownProviderTypes, string kind, string name) { if (knownProviderTypes.TryGetValue((kind, name), out var type)) { var instance = Activator.CreateInstance(type); return instance as IProviderBuilder ?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder)}."); } var knownProvidersOfKind = knownProviderTypes .Where(kvp => string.Equals(kvp.Key.Kind, kind, StringComparison.OrdinalIgnoreCase)) .Select(kvp => kvp.Key.Name) .OrderBy(n => n) .ToList(); var knownProvidersMessage = knownProvidersOfKind.Count > 0 ? $" Known {kind} providers: {string.Join(", ", knownProvidersOfKind)}." : string.Empty; throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced by your application.{knownProvidersMessage}"); } static Dictionary<(string Kind, string Name), Type> GetRegisteredProviders() { var result = new Dictionary<(string, string), Type>(); foreach (var asm in ReferencedAssemblyProvider.GetRelevantAssemblies()) { foreach (var attr in asm.GetCustomAttributes()) { if (string.Equals(attr.Target, "Silo")) { result[(attr.Kind, attr.Name)] = attr.Type; } } } return result; } static void ApplySubsection(ISiloBuilder builder, IConfigurationSection cfg, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string sectionName) { if (cfg.GetSection(sectionName) is { } section && section.Exists()) { ConfigureProvider(builder, knownProviderTypes, sectionName, name: null, section); } } static void ApplyNamedSubsections(ISiloBuilder builder, IConfigurationSection cfg, Dictionary<(string Kind, string Name), Type> knownProviderTypes, string sectionName) { if (cfg.GetSection(sectionName) is { } section && section.Exists()) { foreach (var child in section.GetChildren()) { ConfigureProvider(builder, knownProviderTypes, sectionName, name: child.Key, child); } } } } private class AllowOrleansTypes : ITypeNameFilter { public bool? IsTypeNameAllowed(string typeName, string assemblyName) { if (assemblyName is { Length: > 0} && assemblyName.Contains("Orleans")) { return true; } return null; } } private class ServicesAdded { } } } ================================================ FILE: src/Orleans.Runtime/Hosting/DirectorySiloBuilderExtensions.cs ================================================ #nullable enable using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Orleans.GrainDirectory; using Orleans.Hosting; namespace Orleans.Runtime.Hosting { public static class DirectorySiloBuilderExtensions { /// /// Add a grain directory provider implementation to the silo. If the provider type implements /// it will automatically participate to the silo lifecycle. /// /// The concrete implementation type of the grain directory provider. /// The silo builder. /// The name of the grain directory to add. /// Factory to build the grain directory provider. /// The silo builder. public static ISiloBuilder AddGrainDirectory(this ISiloBuilder builder, string name, Func implementationFactory) where T : class, IGrainDirectory { builder.Services.AddGrainDirectory(name, implementationFactory); return builder; } /// /// Add a grain directory provider implementation to the silo. If the provider type implements /// it will automatically participate to the silo lifecycle. /// /// The concrete implementation type of the grain directory provider. /// The service collection. /// The name of the grain directory to add. /// Factory to build the grain directory provider. /// The service collection. public static IServiceCollection AddGrainDirectory(this IServiceCollection collection, string name, Func implementationFactory) where T : class, IGrainDirectory { // Register the grain directory name so that directories can be enumerated by name. collection.AddSingleton(sp => new NamedService(name)); // Register the grain directory implementation. collection.AddKeyedSingleton(name, (sp, key) => implementationFactory(sp, name)); collection.AddSingleton>(s => s.GetKeyedService(name) as ILifecycleParticipant ?? NoOpLifecycleParticipant.Instance); return collection; } internal static IEnumerable> GetGrainDirectories(this IServiceProvider serviceProvider) { return serviceProvider.GetServices>() ?? []; } private sealed class NoOpLifecycleParticipant : ILifecycleParticipant { public static readonly NoOpLifecycleParticipant Instance = new(); public void Participate(ISiloLifecycle observer) { } } } } ================================================ FILE: src/Orleans.Runtime/Hosting/EndpointOptions.cs ================================================ using System.Net; using Microsoft.Extensions.Configuration; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Configures the Silo endpoint options /// public class EndpointOptions { private IPAddress advertisedIPAddress; private int siloPort = DEFAULT_SILO_PORT; /// /// The IP address used for clustering. /// public IPAddress AdvertisedIPAddress { get => advertisedIPAddress; set { if (value is null) { throw new OrleansConfigurationException( $"No listening address specified. Use {nameof(Hosting.ISiloBuilder)}.{nameof(Hosting.EndpointOptionsExtensions.ConfigureEndpoints)}(...) " + $"to configure endpoints and ensure that {nameof(AdvertisedIPAddress)} is set."); } if (value == IPAddress.Any || value == IPAddress.IPv6Any || value == IPAddress.None || value == IPAddress.IPv6None) { throw new OrleansConfigurationException( $"Invalid value specified for {nameof(AdvertisedIPAddress)}. The value was {value}"); } advertisedIPAddress = value; } } /// /// Gets or sets the port this silo uses for silo-to-silo communication. /// public int SiloPort { get => siloPort; set { if (value == 0) { throw new OrleansConfigurationException( $"No listening port specified. Use {nameof(Hosting.ISiloBuilder)}.{nameof(Hosting.EndpointOptionsExtensions.ConfigureEndpoints)}(...) " + $"to configure endpoints and ensure that {nameof(SiloPort)} is set."); } siloPort = value; } } /// /// The default value for . /// public const int DEFAULT_SILO_PORT = 11111; /// /// Gets or sets the port this silo uses for silo-to-client (gateway) communication. Specify 0 to disable gateway functionality. /// public int GatewayPort { get; set; } = DEFAULT_GATEWAY_PORT; /// /// The default value for . /// public const int DEFAULT_GATEWAY_PORT = 30000; /// /// Gets or sets the endpoint used to listen for silo to silo communication. /// If not set will default to + /// public IPEndPoint SiloListeningEndpoint { get; set; } /// /// Gets or sets the endpoint used to listen for client to silo communication. /// If not set will default to + /// public IPEndPoint GatewayListeningEndpoint { get; set; } internal IPEndPoint GetPublicSiloEndpoint() => new(AdvertisedIPAddress, SiloPort); internal IPEndPoint GetPublicProxyEndpoint() { var gatewayPort = GatewayPort != 0 ? GatewayPort : GatewayListeningEndpoint?.Port ?? 0; return gatewayPort != 0 ? new(AdvertisedIPAddress, gatewayPort) : null; } internal IPEndPoint GetListeningSiloEndpoint() => SiloListeningEndpoint ?? GetPublicSiloEndpoint(); internal IPEndPoint GetListeningProxyEndpoint() => GatewayListeningEndpoint ?? GetPublicProxyEndpoint(); internal void Bind(IConfiguration cfg) { if (int.TryParse(cfg[nameof(GatewayPort)], out var gwPort)) { GatewayPort = gwPort; } if (int.TryParse(cfg[nameof(SiloPort)], out var siloPort)) { SiloPort = siloPort; } if (IPAddress.TryParse(cfg[nameof(AdvertisedIPAddress)], out var aip)) { AdvertisedIPAddress = aip; } if (IPEndPoint.TryParse(cfg[nameof(SiloListeningEndpoint)], out var sle)) { SiloListeningEndpoint = sle; } if (IPEndPoint.TryParse(cfg[nameof(GatewayListeningEndpoint)], out var gle)) { GatewayListeningEndpoint = gle; } } } } ================================================ FILE: src/Orleans.Runtime/Hosting/EndpointOptionsExtensions.cs ================================================ using System; using System.Net; using System.Net.Sockets; using Orleans.Configuration; using Orleans.Runtime.Configuration; namespace Orleans.Hosting { /// /// Extension methods for configuring . /// public static class EndpointOptionsExtensions { /// /// Configure endpoints for the silo. /// /// The host builder to configure. /// The IP address to be advertised in membership tables /// The port this silo uses for silo-to-silo communication. /// The port this silo uses for client-to-silo (gateway) communication. Specify 0 to disable gateway functionality. /// Set to true to listen on all IP addresses of the host instead of just the advertiseIP. /// public static ISiloBuilder ConfigureEndpoints( this ISiloBuilder builder, IPAddress advertisedIP, int siloPort, int gatewayPort, bool listenOnAnyHostAddress = false) { builder.Configure(options => { options.AdvertisedIPAddress = advertisedIP; options.GatewayPort = gatewayPort; options.SiloPort = siloPort; if (listenOnAnyHostAddress) { options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, siloPort); options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, gatewayPort); } }); return builder; } /// /// Configure endpoints for the silo. /// /// The host builder to configure. /// The host name the silo is running on. /// The port this silo uses for silo-to-silo communication. /// The port this silo uses for client-to-silo (gateway) communication. Specify 0 to disable gateway functionality. /// Address family to listen on. Default IPv4 address family. /// Set to true to listen on all IP addresses of the host instead of just the advertiseIP. /// public static ISiloBuilder ConfigureEndpoints( this ISiloBuilder builder, string hostname, int siloPort, int gatewayPort, AddressFamily addressFamily = AddressFamily.InterNetwork, bool listenOnAnyHostAddress = false) { var ip = ConfigUtilities.ResolveIPAddressOrDefault(hostname, null, addressFamily); if (ip == null) { throw new ArgumentException("Hostname '" + hostname + "' with subnet [] and family " + addressFamily + " is not a valid IP address or DNS name"); } return builder.ConfigureEndpoints(ip, siloPort, gatewayPort, listenOnAnyHostAddress); } /// /// Configure endpoints for the silo. /// /// The host builder to configure. /// The port this silo uses for silo-to-silo communication. /// The port this silo uses for client-to-silo (gateway) communication. Specify 0 to disable gateway functionality. /// Address family to listen on. Default IPv4 address family. /// Set to true to listen on all IP addresses of the host instead of just the advertiseIP. /// public static ISiloBuilder ConfigureEndpoints( this ISiloBuilder builder, int siloPort, int gatewayPort, AddressFamily addressFamily = AddressFamily.InterNetwork, bool listenOnAnyHostAddress = false) { return builder.ConfigureEndpoints(null, siloPort, gatewayPort, addressFamily, listenOnAnyHostAddress); } } } ================================================ FILE: src/Orleans.Runtime/Hosting/EndpointOptionsProvider.cs ================================================ using System; using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime.Configuration; namespace Orleans.Configuration { internal partial class EndpointOptionsProvider : IPostConfigureOptions { private readonly ILogger logger; public EndpointOptionsProvider(ILogger logger) { this.logger = logger; } public void PostConfigure(string name, EndpointOptions options) { if (options.AdvertisedIPAddress is null) { var advertisedIPAddress = IPAddress.Loopback; try { var resolvedIP = ConfigUtilities.ResolveIPAddressOrDefault(null, AddressFamily.InterNetwork); if (resolvedIP is null) { LogWarningUnableToFindSuitableCandidate(logger, nameof(EndpointOptions), nameof(options.AdvertisedIPAddress), nameof(IPAddress.Loopback), advertisedIPAddress); } else { advertisedIPAddress = resolvedIP; } } catch (Exception ex) { LogErrorUnableToFindSuitableCandidate(logger, ex, nameof(EndpointOptions), nameof(options.AdvertisedIPAddress), nameof(IPAddress.Loopback), advertisedIPAddress); } options.AdvertisedIPAddress = advertisedIPAddress; } } [LoggerMessage( Level = LogLevel.Warning, Message = "Unable to find a suitable candidate for {OptionName}.{PropertyName}. Falling back to {Fallback} ({AdvertisedIPAddress})" )] private static partial void LogWarningUnableToFindSuitableCandidate(ILogger logger, string optionName, string propertyName, string fallback, IPAddress advertisedIPAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Unable to find a suitable candidate for {OptionName}.{PropertyName}. Falling back to {Fallback} ({AdvertisedIPAddress})" )] private static partial void LogErrorUnableToFindSuitableCandidate(ILogger logger, Exception exception, string optionName, string propertyName, string fallback, IPAddress advertisedIPAddress); } } ================================================ FILE: src/Orleans.Runtime/Hosting/GrainCallFilterExtensions.cs ================================================ namespace Orleans.Hosting { /// /// Extension methods for configuring and implementations. /// public static class GrainCallFilterSiloBuilderExtensions { /// /// Adds an to the filter pipeline. /// /// The builder. /// The filter. /// The builder. public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder, IIncomingGrainCallFilter filter) { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The builder. /// The builder. public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder) where TImplementation : class, IIncomingGrainCallFilter { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter()); } /// /// Adds an to the filter pipeline via a delegate. /// /// The builder. /// The filter. /// The builder. public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder, IncomingGrainCallFilterDelegate filter) { return builder.ConfigureServices(services => services.AddIncomingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The builder. /// The filter. /// The builder. public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder, IOutgoingGrainCallFilter filter) { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter(filter)); } /// /// Adds an to the filter pipeline. /// /// The filter implementation type. /// The builder. /// The builder. public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder) where TImplementation : class, IOutgoingGrainCallFilter { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter()); } /// /// Adds an to the filter pipeline via a delegate. /// /// The builder. /// The filter. /// The builder. public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder, OutgoingGrainCallFilterDelegate filter) { return builder.ConfigureServices(services => services.AddOutgoingGrainCallFilter(filter)); } } } ================================================ FILE: src/Orleans.Runtime/Hosting/HostingGrainExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; namespace Orleans.Hosting { /// /// Methods for configuring s on a silo. /// public static class HostingGrainExtensions { /// /// Registers a grain extension implementation for the specified interface. /// /// The interface being registered. /// The implementation of . public static ISiloBuilder AddGrainExtension(this ISiloBuilder builder) where TExtensionInterface : class, IGrainExtension where TExtension : class, TExtensionInterface { return builder.ConfigureServices(services => services.AddKeyedTransient(typeof(TExtensionInterface))); } } } ================================================ FILE: src/Orleans.Runtime/Hosting/ISiloBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// Builder for configuring an Orleans server. /// public interface ISiloBuilder { /// /// The services shared by the silo and host. /// IServiceCollection Services { get; } /// /// Gets the configuration. /// IConfiguration Configuration { get; } } } ================================================ FILE: src/Orleans.Runtime/Hosting/NamedService.cs ================================================ using System; namespace Orleans.Runtime.Hosting { internal class NamedService(string name) { public string Name { get; } = name; } } ================================================ FILE: src/Orleans.Runtime/Hosting/OrleansSiloGenericHostExtensions.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Runtime; namespace Microsoft.Extensions.Hosting { /// /// Extension methods for . /// public static class OrleansSiloGenericHostExtensions { private static readonly Type MarkerType = typeof(OrleansBuilderMarker); /// /// Configures the host app builder to host an Orleans silo. /// /// The host app builder. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// public static IHostApplicationBuilder UseOrleans( this IHostApplicationBuilder hostAppBuilder) => hostAppBuilder.UseOrleans(_ => { }); /// /// Configures the host app builder to host an Orleans silo. /// /// The host app builder. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// public static HostApplicationBuilder UseOrleans( this HostApplicationBuilder hostAppBuilder) => hostAppBuilder.UseOrleans(_ => { }); /// /// Configures the host builder to host an Orleans silo. /// /// The host app builder. /// The delegate used to configure the silo. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// However, the effects of will be applied once for each call. /// public static IHostApplicationBuilder UseOrleans( this IHostApplicationBuilder hostAppBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostAppBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); configureDelegate(AddOrleansCore(hostAppBuilder.Services, hostAppBuilder.Configuration)); return hostAppBuilder; } /// /// Configures the host builder to host an Orleans silo. /// /// The host app builder. /// The delegate used to configure the silo. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// However, the effects of will be applied once for each call. /// public static HostApplicationBuilder UseOrleans( this HostApplicationBuilder hostAppBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostAppBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); configureDelegate(AddOrleansCore(hostAppBuilder.Services, hostAppBuilder.Configuration)); return hostAppBuilder; } /// /// Configures the host builder to host an Orleans silo. /// /// The host builder. /// The delegate used to configure the silo. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// However, the effects of will be applied once for each call. /// public static IHostBuilder UseOrleans( this IHostBuilder hostBuilder, Action configureDelegate) => hostBuilder.UseOrleans((_, siloBuilder) => configureDelegate(siloBuilder)); /// /// Configures the host builder to host an Orleans silo. /// /// The host builder. /// The delegate used to configure the silo. /// The host builder. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// However, the effects of will be applied once for each call. /// public static IHostBuilder UseOrleans( this IHostBuilder hostBuilder, Action configureDelegate) { ArgumentNullException.ThrowIfNull(hostBuilder); ArgumentNullException.ThrowIfNull(configureDelegate); if (hostBuilder.Properties.ContainsKey("HasOrleansClientBuilder")) { throw GetOrleansClientAddedException(); } hostBuilder.Properties["HasOrleansSiloBuilder"] = "true"; return hostBuilder.ConfigureServices((context, services) => configureDelegate(context, AddOrleansCore(services, context.Configuration))); } /// /// Configures the service collection to host an Orleans silo. /// /// The service collection. /// The delegate used to configure the silo. /// The service collection. /// /// Calling this method multiple times on the same instance will result in one silo being configured. /// However, the effects of will be applied once for each call. /// public static IServiceCollection AddOrleans( this IServiceCollection services, Action configureDelegate) { ArgumentNullException.ThrowIfNull(configureDelegate); var builder = AddOrleansCore(services, null); configureDelegate(builder); return services; } private static ISiloBuilder AddOrleansCore(IServiceCollection services, IConfiguration configuration) { ISiloBuilder builder = default; configuration ??= new ConfigurationBuilder().Build(); foreach (var descriptor in services.Where(d => d.ServiceType.Equals(MarkerType))) { var marker = (OrleansBuilderMarker)descriptor.ImplementationInstance; builder = marker.BuilderInstance switch { ISiloBuilder existingBuilder => existingBuilder, _ => throw GetOrleansClientAddedException() }; } if (builder is null) { builder = new SiloBuilder(services, configuration); services.AddSingleton(new OrleansBuilderMarker(builder)); } return builder; } private static OrleansConfigurationException GetOrleansClientAddedException() => new("Do not call both UseOrleansClient/AddOrleansClient with UseOrleans/AddOrleans. If you want a client and server in the same process, only UseOrleans/AddOrleans is necessary and the UseOrleansClient/AddOrleansClient call can be removed."); } } ================================================ FILE: src/Orleans.Runtime/Hosting/PlacementStrategyExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Runtime.Placement; namespace Orleans.Hosting { /// /// Extensions for configuring grain placement. /// public static class PlacementStrategyExtensions { /// /// Configures a as the placement director for placement strategy . /// /// The placement strategy. /// The placement director. /// The builder. /// The builder. public static ISiloBuilder AddPlacementDirector(this ISiloBuilder builder) where TStrategy : PlacementStrategy, new() where TDirector : class, IPlacementDirector { return builder.ConfigureServices(services => services.AddPlacementDirector()); } /// /// Adds a placement director. /// /// The placement strategy. /// The builder. /// The delegate used to create the placement director. /// The builder. public static ISiloBuilder AddPlacementDirector(this ISiloBuilder builder, Func createDirector) where TStrategy : PlacementStrategy, new() { return builder.ConfigureServices(services => services.AddPlacementDirector(createDirector)); } /// /// Configures a as the placement director for placement strategy . /// /// The placement strategy. /// The placement director. /// The service collection. /// The service collection. public static void AddPlacementDirector(this IServiceCollection services) where TStrategy : PlacementStrategy, new() where TDirector : class, IPlacementDirector => services.AddPlacementDirector(ServiceLifetime.Singleton); /// /// Configures a as the placement director for placement strategy . /// /// The placement strategy. /// The placement director. /// The service collection. /// The lifetime of the placement strategy. /// The service collection. public static void AddPlacementDirector(this IServiceCollection services, ServiceLifetime strategyLifetime) where TStrategy : PlacementStrategy, new() where TDirector : class, IPlacementDirector { services.Add(ServiceDescriptor.DescribeKeyed(typeof(PlacementStrategy), typeof(TStrategy).Name, typeof(TStrategy), strategyLifetime)); services.AddKeyedSingleton(typeof(TStrategy)); } /// /// Adds a placement director. /// /// The placement strategy. /// The service collection. /// The delegate used to create the placement director. /// The service collection. public static void AddPlacementDirector(this IServiceCollection services, Func createDirector) where TStrategy : PlacementStrategy, new() => services.AddPlacementDirector(createDirector, ServiceLifetime.Singleton); /// /// Adds a placement director. /// /// The placement strategy. /// The service collection. /// The delegate used to create the placement director. /// The lifetime of the placement strategy. /// The service collection. public static void AddPlacementDirector(this IServiceCollection services, Func createDirector, ServiceLifetime strategyLifetime) where TStrategy : PlacementStrategy, new() { services.Add(ServiceDescriptor.DescribeKeyed(typeof(PlacementStrategy), typeof(TStrategy).Name, typeof(TStrategy), strategyLifetime)); services.AddKeyedSingleton(typeof(TStrategy), (sp, type) => createDirector(sp)); } } } ================================================ FILE: src/Orleans.Runtime/Hosting/ProviderConfiguration/DevelopmentClusteringProvider.cs ================================================ using System.Net; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime.Hosting.ProviderConfiguration; [assembly: RegisterProvider("Development", "Clustering", "Silo", typeof(DevelopmentClusteringProvider))] namespace Orleans.Runtime.Hosting.ProviderConfiguration; internal sealed class DevelopmentClusteringProvider : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { IPEndPoint primarySiloEndPoint = null; if (configurationSection["PrimarySiloEndPoint"] is { Length: > 0 } primarySiloEndPointValue && !IPEndPoint.TryParse(primarySiloEndPointValue, out primarySiloEndPoint)) { throw new OrleansConfigurationException($"Unable to parse configuration value at path {configurationSection.Path}:PrimarySiloEndPoint as an IPEndPoint. Value: '{primarySiloEndPointValue}'."); } builder.UseDevelopmentClustering(primarySiloEndPoint); } } ================================================ FILE: src/Orleans.Runtime/Hosting/SiloBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// Builder for configuring an Orleans server. /// internal class SiloBuilder : ISiloBuilder { public SiloBuilder(IServiceCollection services, IConfiguration configuration) { Services = services; Configuration = configuration; DefaultSiloServices.AddDefaultServices(this); } public IServiceCollection Services { get; } public IConfiguration Configuration { get; } } } ================================================ FILE: src/Orleans.Runtime/Hosting/SiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Orleans.Hosting { /// /// Extensions for instances. /// public static class SiloBuilderExtensions { /// /// Adds services to the container. This can be called multiple times and the results will be additive. /// /// The to configure. /// /// The same instance of the for chaining. public static ISiloBuilder ConfigureServices(this ISiloBuilder builder, Action configureDelegate) { if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); configureDelegate(builder.Services); return builder; } /// /// Registers an action used to configure a particular type of options. /// /// The options type to be configured. /// The silo builder. /// The action used to configure the options. /// The silo builder. public static ISiloBuilder Configure(this ISiloBuilder builder, Action configureOptions) where TOptions : class { return builder.ConfigureServices(services => services.Configure(configureOptions)); } /// /// Registers a configuration instance which will bind against. /// /// The options type to be configured. /// The silo builder. /// The configuration. /// The silo builder. public static ISiloBuilder Configure(this ISiloBuilder builder, IConfiguration configuration) where TOptions : class { return builder.ConfigureServices(services => services.AddOptions().Bind(configuration)); } /// /// Adds a delegate for configuring the provided . This may be called multiple times. /// /// The to configure. /// The delegate that configures the . /// The same instance of the for chaining. public static ISiloBuilder ConfigureLogging(this ISiloBuilder builder, Action configureLogging) { return builder.ConfigureServices(collection => collection.AddLogging(configureLogging)); } } } ================================================ FILE: src/Orleans.Runtime/Hosting/SiloBuilderStartupExtensions.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; namespace Orleans.Hosting { /// /// The silo builder startup extensions. /// public static class SiloBuilderStartupExtensions { /// /// Adds a startup task to be executed when the silo has started. /// /// /// The builder. /// /// /// The stage to execute the startup task, see values in . /// /// /// The startup task type. /// /// /// The provided . /// public static ISiloBuilder AddStartupTask( this ISiloBuilder builder, int stage = ServiceLifecycleStage.Active) where TStartup : class, IStartupTask { return builder.AddStartupTask((sp, ct) => ActivatorUtilities.GetServiceOrCreateInstance(sp).Execute(ct), stage); } /// /// Adds a startup task to be executed when the silo has started. /// /// /// The builder. /// /// /// The startup task. /// /// /// The stage to execute the startup task, see values in . /// /// /// The provided . /// public static ISiloBuilder AddStartupTask( this ISiloBuilder builder, IStartupTask startupTask, int stage = ServiceLifecycleStage.Active) { return builder.AddStartupTask((sp, ct) => startupTask.Execute(ct), stage); } /// /// Adds a startup task to be executed when the silo has started. /// /// /// The builder. /// /// /// The startup task. /// /// /// The stage to execute the startup task, see values in . /// /// /// The provided . /// public static ISiloBuilder AddStartupTask( this ISiloBuilder builder, Func startupTask, int stage = ServiceLifecycleStage.Active) { builder.ConfigureServices(services => services.AddTransient>(sp => new StartupTask( sp, startupTask, stage))); return builder; } /// private class StartupTask : ILifecycleParticipant { private readonly IServiceProvider serviceProvider; private readonly Func startupTask; private readonly int stage; public StartupTask( IServiceProvider serviceProvider, Func startupTask, int stage) { this.serviceProvider = serviceProvider; this.startupTask = startupTask; this.stage = stage; } /// public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( this.stage, cancellation => this.startupTask(this.serviceProvider, cancellation)); } } } } ================================================ FILE: src/Orleans.Runtime/Hosting/SiloHostedService.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.Hosting { internal partial class SiloHostedService : IHostedService { private readonly ILogger logger; private readonly Silo silo; public SiloHostedService( Silo silo, IEnumerable configurationValidators, ILogger logger) { ValidateSystemConfiguration(configurationValidators); this.silo = silo; this.logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { LogInformationStartingSilo(logger); await this.silo.StartAsync(cancellationToken).ConfigureAwait(false); LogInformationSiloStarted(logger); } public async Task StopAsync(CancellationToken cancellationToken) { LogInformationStoppingSilo(logger); await this.silo.StopAsync(cancellationToken).ConfigureAwait(false); LogInformationSiloStopped(logger); } private static void ValidateSystemConfiguration(IEnumerable configurationValidators) { foreach (var validator in configurationValidators) { validator.ValidateConfiguration(); } } [LoggerMessage( Level = LogLevel.Information, Message = "Starting Orleans Silo." )] private static partial void LogInformationStartingSilo(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Orleans Silo started." )] private static partial void LogInformationSiloStarted(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Stopping Orleans Silo" )] private static partial void LogInformationStoppingSilo(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "Orleans Silo stopped." )] private static partial void LogInformationSiloStopped(ILogger logger); } } ================================================ FILE: src/Orleans.Runtime/Hosting/StorageProviderHostExtensions.cs ================================================ using System; using Orleans.Storage; using Microsoft.Extensions.DependencyInjection; using Orleans.Providers; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Orleans.Runtime.Hosting { public static class StorageProviderExtensions { /// /// Add a grain storage provider implementation to the silo. If the provider type implements /// it will automatically participate to the silo lifecycle. /// /// The concrete implementation type of the grain storage provider. /// The service collection. /// The name of the storage to add. /// Factory to build the storage provider. /// The service provider. public static IServiceCollection AddGrainStorage(this IServiceCollection collection, string name, Func implementationFactory) where T : IGrainStorage { collection.AddKeyedSingleton(name, (sp, key) => implementationFactory(sp, key as string)); // Check if it is the default implementation if (string.Equals(name, ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, StringComparison.Ordinal)) { collection.TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); } // Check if the grain storage implements ILifecycleParticipant if (typeof(ILifecycleParticipant).IsAssignableFrom(typeof(T))) { collection.AddSingleton(s => (ILifecycleParticipant)s.GetRequiredKeyedService(name)); } return collection; } } } ================================================ FILE: src/Orleans.Runtime/Lifecycle/ISiloLifecycle.cs ================================================ namespace Orleans.Runtime { /// /// The observable silo lifecycle. /// /// /// This type is usually used as the generic parameter in as /// a means of participating in the lifecycle stages of a silo. /// /// public interface ISiloLifecycle : ILifecycleObservable { /// /// The highest lifecycle stage which has completed starting. /// int HighestCompletedStage { get; } /// /// The lowest lifecycle stage which has completed stopping. /// int LowestStoppedStage { get; } } } ================================================ FILE: src/Orleans.Runtime/Lifecycle/ISiloLifecycleSubject.cs ================================================  namespace Orleans.Runtime { /// /// Observable silo lifecycle and observer. /// public interface ISiloLifecycleSubject : ISiloLifecycle, ILifecycleObserver { } } ================================================ FILE: src/Orleans.Runtime/Lifecycle/IStartupTask.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Defines an action to be taken after silo startup. /// public interface IStartupTask { /// /// Called after the silo has started. /// /// The cancellation token which is canceled when the method must abort. /// A representing the work performed. Task Execute(CancellationToken cancellationToken); } } ================================================ FILE: src/Orleans.Runtime/Lifecycle/SiloLifecycleSubject.cs ================================================ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Decorator over lifecycle subject for silo. Adds some logging and monitoring /// public partial class SiloLifecycleSubject : LifecycleSubject, ISiloLifecycleSubject { private static readonly ImmutableDictionary StageNames = GetStageNames(typeof(ServiceLifecycleStage)); private readonly List observers; private int highestCompletedStage; private int lowestStoppedStage; /// public int HighestCompletedStage => this.highestCompletedStage; /// public int LowestStoppedStage => this.lowestStoppedStage; /// /// Initializes a new instance of the class. /// /// The logger. public SiloLifecycleSubject(ILogger logger) : base(logger) { this.observers = new List(); this.highestCompletedStage = int.MinValue; this.lowestStoppedStage = int.MaxValue; } /// public override Task OnStart(CancellationToken cancellationToken = default) { foreach (var stage in this.observers.GroupBy(o => o.Stage).OrderBy(s => s.Key)) { LogDebugLifecycleStagesReport(stage.Key, string.Join(", ", stage.Select(o => o.Name))); } return base.OnStart(cancellationToken); } /// protected override void OnStartStageCompleted(int stage) { Interlocked.Exchange(ref this.highestCompletedStage, stage); base.OnStartStageCompleted(stage); } /// protected override void OnStopStageCompleted(int stage) { Interlocked.Exchange(ref this.lowestStoppedStage, stage); base.OnStopStageCompleted(stage); } /// protected override string GetStageName(int stage) { if (StageNames.TryGetValue(stage, out var result)) return result; return base.GetStageName(stage); } /// protected override void PerfMeasureOnStop(int stage, TimeSpan elapsed) { LogDebugStoppingLifecycleStage(this.GetStageName(stage), elapsed); } /// protected override void PerfMeasureOnStart(int stage, TimeSpan elapsed) { LogDebugStartingLifecycleStage(this.GetStageName(stage), elapsed); } /// public override IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer) { var monitoredObserver = new MonitoredObserver(observerName, stage, this.GetStageName(stage), observer, this.Logger); this.observers.Add(monitoredObserver); return base.Subscribe(observerName, stage, monitoredObserver); } private partial class MonitoredObserver : ILifecycleObserver { private readonly ILifecycleObserver observer; private readonly ILogger logger; public MonitoredObserver(string name, int stage, string stageName, ILifecycleObserver observer, ILogger logger) { this.Name = name; this.Stage = stage; this.StageName = stageName; this.observer = observer; this.logger = logger; } public string Name { get; } public int Stage { get; } public string StageName { get; } public async Task OnStart(CancellationToken ct) { try { var stopwatch = ValueStopwatch.StartNew(); await this.observer.OnStart(ct); stopwatch.Stop(); LogDebugObserverStarted(this.Name, this.StageName, stopwatch.Elapsed); } catch (Exception exception) { LogErrorObserverStartFailure(exception, this.Name, this.StageName); throw; } } public async Task OnStop(CancellationToken cancellationToken = default) { var stopwatch = ValueStopwatch.StartNew(); try { LogDebugObserverStopping(this.Name, this.StageName); await this.observer.OnStop(cancellationToken); stopwatch.Stop(); if (stopwatch.Elapsed > TimeSpan.FromSeconds(1)) { LogObserverStopped(LogLevel.Warning, this.Name, this.StageName, stopwatch.Elapsed); } else { LogObserverStopped(LogLevel.Debug, this.Name, this.StageName, stopwatch.Elapsed); } } catch (Exception exception) { LogErrorObserverStopFailure(exception, this.Name, this.StageName, stopwatch.Elapsed); throw; } } [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Debug, Message = "'{Name}' started in stage '{Stage}' in '{Elapsed}'." )] private partial void LogDebugObserverStarted(string name, string stage, TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.LifecycleStartFailure, Level = LogLevel.Error, Message = "'{Name}' failed to start due to errors at stage '{Stage}'." )] private partial void LogErrorObserverStartFailure(Exception exception, string name, string stage); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Debug, Message = "'{Name}' stopping in stage '{Stage}'." )] private partial void LogDebugObserverStopping(string name, string stage); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Message = "'{Name}' stopped in stage '{Stage}' in '{Elapsed}'." )] private partial void LogObserverStopped(LogLevel logLevel, string name, string stage, TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.LifecycleStartFailure, Level = LogLevel.Error, Message = "'{Name}' failed to stop due to errors at stage '{Stage}' after '{Elapsed}'." )] private partial void LogErrorObserverStopFailure(Exception exception, string name, string stage, TimeSpan elapsed); } [LoggerMessage( EventId = (int)ErrorCode.LifecycleStagesReport, Level = LogLevel.Debug, Message = "Stage {Stage}: {Observers}" )] private partial void LogDebugLifecycleStagesReport(int stage, string observers); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Debug, Message = "Stopping lifecycle stage '{Stage}' took '{Elapsed}'." )] private partial void LogDebugStoppingLifecycleStage(string stage, TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.SiloStartPerfMeasure, Level = LogLevel.Debug, Message = "Starting lifecycle stage '{Stage}' took '{Elapsed}'" )] private partial void LogDebugStartingLifecycleStage(string stage, TimeSpan elapsed); } } ================================================ FILE: src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Metadata; using Orleans.Runtime.Utilities; namespace Orleans.Runtime.Metadata { internal partial class ClusterManifestProvider : IClusterManifestProvider, IAsyncDisposable, IDisposable, ILifecycleParticipant { private readonly SiloAddress _localSiloAddress; private readonly ILogger _logger; private readonly IServiceProvider _services; private readonly IClusterMembershipService _clusterMembershipService; private readonly IFatalErrorHandler _fatalErrorHandler; private readonly CancellationTokenSource _shutdownCts = new(); private readonly AsyncEnumerable _updates; private ClusterManifest _current; private IInternalGrainFactory? _grainFactory; private Task? _runTask; public ClusterManifestProvider( ILocalSiloDetails localSiloDetails, SiloManifestProvider siloManifestProvider, ClusterMembershipService clusterMembershipService, IFatalErrorHandler fatalErrorHandler, ILogger logger, IServiceProvider services) { _localSiloAddress = localSiloDetails.SiloAddress; _logger = logger; _services = services; _clusterMembershipService = clusterMembershipService; _fatalErrorHandler = fatalErrorHandler; LocalGrainManifest = siloManifestProvider.SiloManifest; _current = new ClusterManifest( MajorMinorVersion.Zero, ImmutableDictionary.CreateRange([new KeyValuePair(localSiloDetails.SiloAddress, LocalGrainManifest)])); _updates = new AsyncEnumerable( initialValue: _current, updateValidator: (previous, proposed) => proposed.Version > previous.Version, onPublished: update => Interlocked.Exchange(ref _current, update)); } public ClusterManifest Current => _current; public IAsyncEnumerable Updates => _updates; public GrainManifest LocalGrainManifest { get; } private async Task ProcessMembershipUpdates() { try { LogDebugStartingToProcessMembershipUpdates(); var cancellation = _shutdownCts.Token; await foreach (var _ in _clusterMembershipService.MembershipUpdates.WithCancellation(cancellation)) { while (true) { var membershipSnapshot = _clusterMembershipService.CurrentSnapshot; var success = await UpdateManifest(membershipSnapshot); if (success || cancellation.IsCancellationRequested) { break; } await Task.Delay(TimeSpan.FromSeconds(5), cancellation); } } } catch (OperationCanceledException) when (_shutdownCts.IsCancellationRequested) { // Ignore during shutdown. } catch (Exception exception) when (_fatalErrorHandler.IsUnexpected(exception)) { _fatalErrorHandler.OnFatalException(this, nameof(ProcessMembershipUpdates), exception); } finally { LogDebugStoppedProcessingMembershipUpdates(); } } private async Task UpdateManifest(ClusterMembershipSnapshot clusterMembership) { var existingManifest = _current; var builder = existingManifest.Silos.ToBuilder(); var modified = false; // First, remove defunct entries. foreach (var entry in existingManifest.Silos) { var address = entry.Key; var status = clusterMembership.GetSiloStatus(address); if (address.Equals(_localSiloAddress)) { // The local silo is always present in the manifest. continue; } if (status == SiloStatus.None || status == SiloStatus.Dead) { builder.Remove(address); modified = true; } } // Next, fill missing entries. var tasks = new List>(); foreach (var entry in clusterMembership.Members) { var member = entry.Value; if (member.SiloAddress.Equals(_localSiloAddress)) { // The local silo is always present in the manifest. continue; } if (existingManifest.Silos.ContainsKey(member.SiloAddress)) { // Manifest has already been retrieved for the cluster member. continue; } if (member.Status != SiloStatus.Active) { // If the member is not yet active, it may not be ready to process requests. continue; } tasks.Add(GetManifest(member.SiloAddress)); async Task<(SiloAddress, GrainManifest?, Exception?)> GetManifest(SiloAddress siloAddress) { try { // Get the manifest from the remote silo. var remoteManifestProvider = _grainFactory!.GetSystemTarget(Constants.ManifestProviderType, member.SiloAddress); var manifest = await remoteManifestProvider.GetSiloManifest().AsTask().WaitAsync(_shutdownCts.Token); return (siloAddress, manifest, null); } catch (Exception exception) { return (siloAddress, null, exception); } } } var fetchSuccess = true; await Task.WhenAll(tasks); foreach (var task in tasks) { var result = await task; if (result.Exception is Exception exception) { fetchSuccess = false; if (exception is not OperationCanceledException) { LogWarningErrorRetrievingSiloManifest(exception, result.Key); } } else { modified = true; if (result.Value is not null) { builder[result.Key] = result.Value; } } } // Regardless of success or failure, update the manifest if it has been modified. var version = new MajorMinorVersion(clusterMembership.Version.Value, existingManifest.Version.Minor + 1); if (modified) { return _updates.TryPublish(new ClusterManifest(version, builder.ToImmutable())) && fetchSuccess; } return fetchSuccess; } [MemberNotNull(nameof(_runTask))] private Task StartAsync(CancellationToken cancellationToken) { Debug.Assert(_grainFactory is not null); _runTask = Task.Run(ProcessMembershipUpdates); return Task.CompletedTask; } [MemberNotNull(nameof(_grainFactory))] private Task Initialize(CancellationToken cancellationToken) { _grainFactory = _services.GetRequiredService(); return Task.CompletedTask; } private async Task StopAsync(CancellationToken cancellationToken) { _shutdownCts.Cancel(); if (_runTask is Task task) { await task.WaitAsync(cancellationToken).SuppressThrowing(); } } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe( nameof(ClusterManifestProvider), ServiceLifecycleStage.RuntimeServices, Initialize, _ => Task.CompletedTask); lifecycle.Subscribe( nameof(ClusterManifestProvider), ServiceLifecycleStage.RuntimeGrainServices, StartAsync, StopAsync); } public async ValueTask DisposeAsync() { if (_shutdownCts.IsCancellationRequested) { return; } await StopAsync(CancellationToken.None); } public void Dispose() { DisposeAsync().AsTask().GetAwaiter().GetResult(); } [LoggerMessage( Level = LogLevel.Warning, Message = "Error retrieving silo manifest for silo {SiloAddress}" )] private partial void LogWarningErrorRetrievingSiloManifest(Exception exception, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting to process membership updates" )] private partial void LogDebugStartingToProcessMembershipUpdates(); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopped processing membership updates" )] private partial void LogDebugStoppedProcessingMembershipUpdates(); } } ================================================ FILE: src/Orleans.Runtime/Manifest/GrainClassMap.cs ================================================ using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Metadata { /// /// Mapping between and implementing . /// public class GrainClassMap { private readonly TypeConverter _typeConverter; private readonly ImmutableDictionary _types; /// /// Initializes a new instance of the class. /// /// The type converter. /// The grain classes. public GrainClassMap(TypeConverter typeConverter, ImmutableDictionary classes) { _typeConverter = typeConverter; _types = classes; } /// /// Returns the grain class type corresponding to the provided grain type. /// /// Type of the grain. /// The grain class. /// if a corresponding grain class was found, otherwise. public bool TryGetGrainClass(GrainType grainType, [NotNullWhen(true)] out Type grainClass) { GrainType lookupType; Type[] args; if (GenericGrainType.TryParse(grainType, out var genericId)) { lookupType = genericId.GetUnconstructedGrainType().GrainType; args = genericId.GetArguments(_typeConverter); } else { lookupType = grainType; args = default; } if (!_types.TryGetValue(lookupType, out grainClass)) { return false; } if (args is not null) { grainClass = grainClass.MakeGenericType(args); } return true; } } } ================================================ FILE: src/Orleans.Runtime/Manifest/SiloManifestProvider.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization.TypeSystem; namespace Orleans.Metadata { /// /// Creates a for this silo. /// internal class SiloManifestProvider { public SiloManifestProvider( IEnumerable grainPropertiesProviders, IEnumerable grainInterfacePropertiesProviders, IOptions grainTypeOptions, GrainTypeResolver typeProvider, GrainInterfaceTypeResolver interfaceIdProvider, TypeConverter typeConverter) { var (grainProperties, grainTypes) = CreateGrainManifest(grainPropertiesProviders, grainTypeOptions, typeProvider); var interfaces = CreateInterfaceManifest(grainInterfacePropertiesProviders, grainTypeOptions, interfaceIdProvider); this.SiloManifest = new GrainManifest(grainProperties, interfaces); this.GrainTypeMap = new GrainClassMap(typeConverter, grainTypes); } public GrainManifest SiloManifest { get; } public GrainClassMap GrainTypeMap { get; } private static ImmutableDictionary CreateInterfaceManifest( IEnumerable propertyProviders, IOptions grainTypeOptions, GrainInterfaceTypeResolver grainInterfaceIdProvider) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var grainInterface in grainTypeOptions.Value.Interfaces) { var interfaceId = grainInterfaceIdProvider.GetGrainInterfaceType(grainInterface); var properties = new Dictionary(); foreach (var provider in propertyProviders) { provider.Populate(grainInterface, interfaceId, properties); } var result = new GrainInterfaceProperties(properties.ToImmutableDictionary()); if (builder.TryGetValue(interfaceId, out var graintInterfaceProperty)) { throw new InvalidOperationException($"An entry with the key {interfaceId} is already present." + $"\nExisting: {graintInterfaceProperty.ToDetailedString()}\nTrying to add: {result.ToDetailedString()}" + "\nConsider using the [GrainInterfaceType(\"name\")] attribute to give these interfaces unique names."); } builder.Add(interfaceId, result); } return builder.ToImmutable(); } private static (ImmutableDictionary, ImmutableDictionary) CreateGrainManifest( IEnumerable grainMetadataProviders, IOptions grainTypeOptions, GrainTypeResolver grainTypeProvider) { var propertiesMap = ImmutableDictionary.CreateBuilder(); var typeMap = ImmutableDictionary.CreateBuilder(); foreach (var grainClass in grainTypeOptions.Value.Classes) { var grainType = grainTypeProvider.GetGrainType(grainClass); var properties = new Dictionary(); foreach (var provider in grainMetadataProviders) { provider.Populate(grainClass, grainType, properties); } var result = new GrainProperties(properties.ToImmutableDictionary()); if (propertiesMap.TryGetValue(grainType, out var grainProperty)) { throw new InvalidOperationException($"An entry with the key {grainType} is already present." + $"\nExisting: {grainProperty.ToDetailedString()}\nTrying to add: {result.ToDetailedString()}" + "\nConsider using the [GrainType(\"name\")] attribute to give these classes unique names."); } propertiesMap.Add(grainType, result); typeMap.Add(grainType, grainClass); } return (propertiesMap.ToImmutable(), typeMap.ToImmutable()); } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ClusterHealthMonitor.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using static Orleans.Runtime.MembershipService.SiloHealthMonitor; namespace Orleans.Runtime.MembershipService { /// /// Responsible for ensuring that this silo monitors other silos in the cluster. /// internal partial class ClusterHealthMonitor : IHealthCheckParticipant, ILifecycleParticipant, ClusterHealthMonitor.ITestAccessor, IDisposable, IAsyncDisposable { private readonly CancellationTokenSource shutdownCancellation = new CancellationTokenSource(); private readonly ILocalSiloDetails localSiloDetails; private readonly IServiceProvider serviceProvider; private readonly MembershipTableManager membershipService; private readonly ILogger log; private readonly IFatalErrorHandler fatalErrorHandler; private readonly IOptionsMonitor clusterMembershipOptions; private ImmutableDictionary monitoredSilos = ImmutableDictionary.Empty; private MembershipVersion observedMembershipVersion; private Func createMonitor; private Func onProbeResult; /// /// Exposes private members of for test purposes. /// internal interface ITestAccessor { ImmutableDictionary MonitoredSilos { get; set; } Func CreateMonitor { get; set; } MembershipVersion ObservedVersion { get; } Func OnProbeResult { get; set; } } public ClusterHealthMonitor( ILocalSiloDetails localSiloDetails, MembershipTableManager membershipService, ILogger log, IOptionsMonitor clusterMembershipOptions, IFatalErrorHandler fatalErrorHandler, IServiceProvider serviceProvider) { this.localSiloDetails = localSiloDetails; this.serviceProvider = serviceProvider; this.membershipService = membershipService; this.log = log; this.fatalErrorHandler = fatalErrorHandler; this.clusterMembershipOptions = clusterMembershipOptions; this.onProbeResult = this.OnProbeResultInternal; Func onProbeResultFunc = (siloHealthMonitor, probeResult) => this.onProbeResult(siloHealthMonitor, probeResult); this.createMonitor = silo => ActivatorUtilities.CreateInstance(serviceProvider, silo, onProbeResultFunc); } ImmutableDictionary ITestAccessor.MonitoredSilos { get => this.monitoredSilos; set => this.monitoredSilos = value; } Func ITestAccessor.CreateMonitor { get => this.createMonitor; set => this.createMonitor = value; } MembershipVersion ITestAccessor.ObservedVersion => this.observedMembershipVersion; Func ITestAccessor.OnProbeResult { get => this.onProbeResult; set => this.onProbeResult = value; } /// /// Gets the collection of monitored silos. /// public ImmutableDictionary SiloMonitors => this.monitoredSilos; private async Task ProcessMembershipUpdates() { try { LogDebugStartingToProcessMembershipUpdates(log); await foreach (var tableSnapshot in this.membershipService.MembershipTableUpdates.WithCancellation(this.shutdownCancellation.Token)) { var utcNow = DateTime.UtcNow; var newMonitoredSilos = this.UpdateMonitoredSilos(tableSnapshot, this.monitoredSilos, utcNow); if (this.clusterMembershipOptions.CurrentValue.EvictWhenMaxJoinAttemptTimeExceeded) { await this.EvictStaleStateSilos(tableSnapshot, utcNow); } foreach (var pair in this.monitoredSilos) { if (!newMonitoredSilos.ContainsKey(pair.Key)) { using var cancellation = new CancellationTokenSource(this.clusterMembershipOptions.CurrentValue.ProbeTimeout); await pair.Value.StopAsync(cancellation.Token); } } this.monitoredSilos = newMonitoredSilos; this.observedMembershipVersion = tableSnapshot.Version; } } catch (OperationCanceledException) when (shutdownCancellation.IsCancellationRequested) { // Ignore and continue shutting down. } catch (Exception exception) when (this.fatalErrorHandler.IsUnexpected(exception)) { this.fatalErrorHandler.OnFatalException(this, nameof(ProcessMembershipUpdates), exception); } finally { LogDebugStoppedProcessingMembershipUpdates(log); } } private async Task EvictStaleStateSilos( MembershipTableSnapshot membership, DateTime utcNow) { foreach (var member in membership.Entries) { if (IsCreatedOrJoining(member.Value.Status) && HasExceededMaxJoinTime( startTime: member.Value.StartTime, now: utcNow, maxJoinTime: this.clusterMembershipOptions.CurrentValue.MaxJoinAttemptTime)) { try { LogDebugStaleSiloFound(log); await this.membershipService.TryToSuspectOrKill(member.Key); } catch(Exception exception) { LogErrorTryToSuspectOrKillFailed(log, exception, member.Value.SiloAddress, member.Value.Status); } } } static bool IsCreatedOrJoining(SiloStatus status) { return status == SiloStatus.Created || status == SiloStatus.Joining; } static bool HasExceededMaxJoinTime(DateTime startTime, DateTime now, TimeSpan maxJoinTime) { return now > startTime.Add(maxJoinTime); } } [Pure] private ImmutableDictionary UpdateMonitoredSilos( MembershipTableSnapshot membership, ImmutableDictionary monitoredSilos, DateTime now) { // If I am still not fully functional, I should not be probing others. if (!membership.Entries.TryGetValue(this.localSiloDetails.SiloAddress, out var self) || !IsFunctionalForMembership(self.Status)) { return ImmutableDictionary.Empty; } var options = clusterMembershipOptions.CurrentValue; var numProbedSilos = options.NumProbedSilos; // Go over every node excluding this one. // Find up to NumProbedSilos silos after me, which are not suspected by anyone and add them to the probedSilos, // In addition, every suspected silo you encounter on the way, add it to the probedSilos. var silosToWatch = new List(); var additionalSilos = new List(); var tmpList = new List<(SiloAddress SiloAddress, int HashCode)>(); foreach (var (candidate, entry) in membership.Entries) { // Watch shutting-down silos as well, so we can properly ensure they become dead. if (!IsFunctionalForMembership(entry.Status)) { continue; } tmpList.Add((candidate, 0)); // Ignore the local silo. if (candidate.IsSameLogicalSilo(this.localSiloDetails.SiloAddress)) { continue; } // Monitor all suspected and stale silos. if (entry.GetFreshVotes(now, options.DeathVoteExpirationTimeout).Count > 0 || entry.HasMissedIAmAlives(options, now)) { additionalSilos.Add(candidate); } } // Each silo monitors up to NumProbedSilos other silos. // Monitoring is determined using multiple hash rings, each generated with a different seeded hash function. // For each hash ring: // 1. The hash values of all silos are updated based on the ring's seed and sorted to determine their positions. // 2. The local silo finds its position in the sorted list and iterates over subsequent silos, wrapping around at the ends. // 3. The first silo not already being monitored is selected and added to the monitoring set. // // This approach probabilistically constructs an Expander Graph (https://en.wikipedia.org/wiki/Expander_graph). // Expander graphs improve fault detection time when there are multiple concurrent failures by minimizing overlap // in monitoring sets between any two silos and reducing dependency chains (e.g., avoiding cases where one failed // silo must be evicted before another failed silo can be detected). // The idea to use an expander graph is taken from "Stable and Consistent Membership at Scale with Rapid" by Lalith Suresh et al: // https://www.usenix.org/conference/atc18/presentation/suresh for (var ringNum = 0; ringNum < numProbedSilos; ++ringNum) { // Update hash values with the current ring number. for (var i = 0; i < tmpList.Count; i++) { var siloAddress = tmpList[i].SiloAddress; tmpList[i] = (siloAddress, siloAddress.GetConsistentHashCode(ringNum)); } // Sort the candidates based on their updated hash values. tmpList.Sort((x, y) => x.HashCode.CompareTo(y.HashCode)); var myIndex = tmpList.FindIndex(el => el.SiloAddress.Equals(self.SiloAddress)); if (myIndex < 0) { LogErrorSiloNotInLocalList(log, self.SiloAddress, self.Status); throw new OrleansMissingMembershipEntryException( $"This silo {self.SiloAddress} status {self.Status} is not in its own local silo list! This is a bug!"); } // Starting at the local silo's index, find the first non-monitored silo and add it to the list. for (var i = 0; i < tmpList.Count - 1; i++) { var candidate = tmpList[(myIndex + i + 1) % tmpList.Count].SiloAddress; if (!silosToWatch.Contains(candidate)) { Debug.Assert(!candidate.IsSameLogicalSilo(this.localSiloDetails.SiloAddress)); silosToWatch.Add(candidate); break; } } } // Take new watched silos, but leave the probe counters for the old ones. var newProbedSilos = ImmutableDictionary.CreateBuilder(); foreach (var silo in silosToWatch.Union(additionalSilos)) { SiloHealthMonitor monitor; if (!monitoredSilos.TryGetValue(silo, out monitor)) { monitor = this.createMonitor(silo); monitor.Start(); } newProbedSilos[silo] = monitor; } var result = newProbedSilos.ToImmutable(); if (!AreTheSame(monitoredSilos, result)) { LogInformationWillWatchActivelyPing(log, newProbedSilos.Count, new(newProbedSilos.Keys)); } return result; static bool AreTheSame(ImmutableDictionary first, ImmutableDictionary second) => first.Count == second.Count && first.Count == first.Keys.Intersect(second.Keys).Count(); static bool IsFunctionalForMembership(SiloStatus status) => status is SiloStatus.Active or SiloStatus.ShuttingDown or SiloStatus.Stopping; } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { var tasks = new List(); lifecycle.Subscribe(nameof(ClusterHealthMonitor), ServiceLifecycleStage.Active, OnActiveStart, OnActiveStop); Task OnActiveStart(CancellationToken ct) { tasks.Add(Task.Run(() => this.ProcessMembershipUpdates())); return Task.CompletedTask; } async Task OnActiveStop(CancellationToken ct) { this.shutdownCancellation.Cancel(throwOnFirstException: false); foreach (var monitor in this.monitoredSilos.Values) { tasks.Add(monitor.StopAsync(ct)); } this.monitoredSilos = ImmutableDictionary.Empty; // Allow some minimum time for graceful shutdown. var shutdownGracePeriod = Task.WhenAll(Task.Delay(ClusterMembershipOptions.ClusteringShutdownGracePeriod), ct.WhenCancelled()); await Task.WhenAny(shutdownGracePeriod, Task.WhenAll(tasks)); } } /// /// Performs the default action when a new probe result is created. /// private async Task OnProbeResultInternal(SiloHealthMonitor monitor, ProbeResult probeResult) { // Do not act on probe results if shutdown is in progress. if (this.shutdownCancellation.IsCancellationRequested) { return; } if (probeResult.IsDirectProbe) { if (probeResult.Status == ProbeResultStatus.Failed && probeResult.FailedProbeCount >= this.clusterMembershipOptions.CurrentValue.NumMissedProbesLimit) { await this.membershipService.TryToSuspectOrKill(monitor.TargetSiloAddress).ConfigureAwait(false); } } else if (probeResult.Status == ProbeResultStatus.Failed) { await this.membershipService.TryToSuspectOrKill(monitor.TargetSiloAddress, probeResult.Intermediary).ConfigureAwait(false); } } bool IHealthCheckable.CheckHealth(DateTime lastCheckTime, out string reason) { var ok = true; reason = default; foreach (var monitor in this.monitoredSilos.Values) { ok &= monitor.CheckHealth(lastCheckTime, out var monitorReason); if (!string.IsNullOrWhiteSpace(monitorReason)) { var siloReason = $"Monitor for {monitor.TargetSiloAddress} is degraded with: {monitorReason}."; if (string.IsNullOrWhiteSpace(reason)) { reason = siloReason; } else { reason = reason + " " + siloReason; } } } return ok; } public void Dispose() { try { shutdownCancellation.Cancel(); } catch (Exception exception) { LogErrorCancellingShutdownToken(log, exception); } foreach (var monitor in monitoredSilos.Values) { try { monitor.Dispose(); } catch (Exception exception) { LogErrorDisposingMonitorForSilo(log, exception, monitor.TargetSiloAddress); } } } public async ValueTask DisposeAsync() { try { shutdownCancellation.Cancel(); } catch (Exception exception) { LogErrorCancellingShutdownToken(log, exception); } var tasks = new List(); foreach (var monitor in monitoredSilos.Values) { try { tasks.Add(monitor.DisposeAsync().AsTask()); } catch (Exception exception) { LogErrorDisposingMonitorForSilo(log, exception, monitor.TargetSiloAddress); } } await Task.WhenAll(tasks).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } // --- Logging methods --- [LoggerMessage( Level = LogLevel.Debug, Message = "Starting to process membership updates" )] private static partial void LogDebugStartingToProcessMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopped processing membership updates" )] private static partial void LogDebugStoppedProcessingMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Stale silo with a joining or created state found, calling 'TryToSuspectOrKill'" )] private static partial void LogDebugStaleSiloFound(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Silo {SuspectAddress} has had the status '{SiloStatus}' for longer than 'MaxJoinAttemptTime' but a call to 'TryToSuspectOrKill' has failed" )] private static partial void LogErrorTryToSuspectOrKillFailed(ILogger logger, Exception exception, SiloAddress suspectAddress, SiloStatus siloStatus); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100305, Level = LogLevel.Error, Message = "This silo {SiloAddress} status {Status} is not in its own local silo list! This is a bug!" )] private static partial void LogErrorSiloNotInLocalList(ILogger logger, SiloAddress siloAddress, SiloStatus status); private readonly struct ProbedSilosLogRecord(IEnumerable probedSilos) { public override string ToString() => Utils.EnumerableToString(probedSilos); } [LoggerMessage( EventId = (int)ErrorCode.MembershipWatchList, Level = LogLevel.Information, Message = "Will watch (actively ping) {ProbedSiloCount} silos: {ProbedSilos}" )] private static partial void LogInformationWillWatchActivelyPing(ILogger logger, int probedSiloCount, ProbedSilosLogRecord probedSilos); [LoggerMessage( Level = LogLevel.Error, Message = "Error cancelling shutdown token." )] private static partial void LogErrorCancellingShutdownToken(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error disposing monitor for {SiloAddress}." )] private static partial void LogErrorDisposingMonitorForSilo(ILogger logger, Exception exception, SiloAddress siloAddress); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ClusterMember.cs ================================================ using System; namespace Orleans.Runtime { /// /// Represents a cluster member. /// [Serializable, GenerateSerializer, Immutable] public sealed class ClusterMember : IEquatable { /// /// Initializes a new instance of the class. /// /// /// The silo address. /// /// /// The silo status. /// /// /// The silo name. /// public ClusterMember(SiloAddress siloAddress, SiloStatus status, string name) { this.SiloAddress = siloAddress ?? throw new ArgumentNullException(nameof(siloAddress)); this.Status = status; this.Name = name; } /// /// Gets the silo address. /// /// The silo address. [Id(0)] public SiloAddress SiloAddress { get; } /// /// Gets the silo status. /// /// The silo status. [Id(1)] public SiloStatus Status { get; } /// /// Gets the silo name. /// /// The silo name. [Id(2)] public string Name { get; } /// public override bool Equals(object obj) => this.Equals(obj as ClusterMember); /// public bool Equals(ClusterMember other) => other != null && this.SiloAddress.Equals(other.SiloAddress) && this.Status == other.Status && string.Equals(this.Name, other.Name, StringComparison.Ordinal); /// public override int GetHashCode() => this.SiloAddress.GetConsistentHashCode(); /// public override string ToString() => $"{this.SiloAddress}/{this.Name}/{this.Status}"; } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ClusterMembershipService.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.MembershipService; using Orleans.Runtime.Utilities; namespace Orleans.Runtime { internal partial class ClusterMembershipService : IClusterMembershipService, ILifecycleParticipant, IDisposable { private readonly AsyncEnumerable updates; private readonly MembershipTableManager membershipTableManager; private readonly ILogger log; private readonly IFatalErrorHandler fatalErrorHandler; private ClusterMembershipSnapshot snapshot; public ClusterMembershipService( MembershipTableManager membershipTableManager, ILogger log, IFatalErrorHandler fatalErrorHandler) { this.snapshot = membershipTableManager.MembershipTableSnapshot.CreateClusterMembershipSnapshot(); this.updates = new AsyncEnumerable( initialValue: this.snapshot, updateValidator: (previous, proposed) => proposed.Version > previous.Version, onPublished: update => Interlocked.Exchange(ref this.snapshot, update)); this.membershipTableManager = membershipTableManager; this.log = log; this.fatalErrorHandler = fatalErrorHandler; } public ClusterMembershipSnapshot CurrentSnapshot { get { var tableSnapshot = this.membershipTableManager.MembershipTableSnapshot; if (this.snapshot.Version == tableSnapshot.Version) { return this.snapshot; } this.updates.TryPublish(tableSnapshot.CreateClusterMembershipSnapshot()); return this.snapshot; } } public IAsyncEnumerable MembershipUpdates => this.updates; public ValueTask Refresh(MembershipVersion targetVersion) => Refresh(targetVersion, CancellationToken.None); public ValueTask Refresh(MembershipVersion targetVersion, CancellationToken cancellationToken) { if (targetVersion != default && targetVersion != MembershipVersion.MinValue && this.snapshot.Version >= targetVersion) return default; return RefreshAsync(targetVersion, cancellationToken); async ValueTask RefreshAsync(MembershipVersion v, CancellationToken cancellationToken) { var didRefresh = false; do { cancellationToken.ThrowIfCancellationRequested(); if (!didRefresh || this.membershipTableManager.MembershipTableSnapshot.Version < v) { await this.membershipTableManager.Refresh(); didRefresh = true; } await Task.Delay(TimeSpan.FromMilliseconds(10), cancellationToken); } while (this.snapshot.Version < v || this.snapshot.Version < this.membershipTableManager.MembershipTableSnapshot.Version); } } public async Task TryKill(SiloAddress siloAddress) => await this.membershipTableManager.TryKill(siloAddress); private async Task ProcessMembershipUpdates(CancellationToken ct) { try { LogDebugStartingToProcessMembershipUpdates(log); await foreach (var tableSnapshot in this.membershipTableManager.MembershipTableUpdates.WithCancellation(ct)) { this.updates.TryPublish(tableSnapshot.CreateClusterMembershipSnapshot()); } } catch (OperationCanceledException) when (ct.IsCancellationRequested) { // Ignore and continue shutting down. } catch (Exception exception) when (this.fatalErrorHandler.IsUnexpected(exception)) { LogErrorProcessingMembershipUpdates(log, exception); this.fatalErrorHandler.OnFatalException(this, nameof(ProcessMembershipUpdates), exception); } finally { LogDebugStoppingMembershipUpdateProcessor(log); } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { var tasks = new List(1); var cancellation = new CancellationTokenSource(); Task OnRuntimeInitializeStart(CancellationToken _) { tasks.Add(Task.Run(() => this.ProcessMembershipUpdates(cancellation.Token))); return Task.CompletedTask; } async Task OnRuntimeInitializeStop(CancellationToken ct) { cancellation.Cancel(throwOnFirstException: false); var shutdownGracePeriod = Task.WhenAll(Task.Delay(ClusterMembershipOptions.ClusteringShutdownGracePeriod), ct.WhenCancelled()); await Task.WhenAny(shutdownGracePeriod, Task.WhenAll(tasks)); } lifecycle.Subscribe( nameof(ClusterMembershipService), ServiceLifecycleStage.RuntimeInitialize, OnRuntimeInitializeStart, OnRuntimeInitializeStop); } void IDisposable.Dispose() { this.updates.Dispose(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting to process membership updates" )] private static partial void LogDebugStartingToProcessMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing membership updates" )] private static partial void LogErrorProcessingMembershipUpdates(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping membership update processor" )] private static partial void LogDebugStoppingMembershipUpdateProcessor(ILogger logger); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ClusterMembershipSnapshot.cs ================================================ using System; using System.Collections.Immutable; using System.Text; namespace Orleans.Runtime { /// /// Represents a snapshot of cluster membership. /// [Serializable, GenerateSerializer, Immutable] public sealed class ClusterMembershipSnapshot { /// /// Initializes a new instance of the class. /// /// The cluster members. /// The cluster membership version. public ClusterMembershipSnapshot(ImmutableDictionary members, MembershipVersion version) { this.Members = members; this.Version = version; } internal static ClusterMembershipSnapshot Default => new(ImmutableDictionary.Empty, MembershipVersion.MinValue); /// /// Gets the cluster members. /// /// The cluster members. [Id(0)] public ImmutableDictionary Members { get; } /// /// Gets the cluster membership version. /// /// The cluster membership version. [Id(1)] public MembershipVersion Version { get; } /// /// Gets status of the specified silo. /// /// The silo. /// The status of the specified silo. public SiloStatus GetSiloStatus(SiloAddress silo) { var status = this.Members.TryGetValue(silo, out var entry) ? entry.Status : SiloStatus.None; if (status == SiloStatus.None) { foreach (var member in this.Members) { if (member.Key.IsSuccessorOf(silo)) { status = SiloStatus.Dead; break; } } } return status; } /// /// Returns a which represents this instance. /// /// A which represents this instance. public ClusterMembershipUpdate AsUpdate() => new ClusterMembershipUpdate(this, this.Members.Values.ToImmutableArray()); /// /// Returns a which represents the change in cluster membership from the provided snapshot to this instance. /// /// A which represents the change in cluster membership from the provided snapshot to this instance. public ClusterMembershipUpdate CreateUpdate(ClusterMembershipSnapshot previous) { if (previous is null) throw new ArgumentNullException(nameof(previous)); if (this.Version < previous.Version) { throw new ArgumentException($"Argument must have a previous version to the current instance. Expected <= {this.Version}, encountered {previous.Version}", nameof(previous)); } if (this.Version == previous.Version) { return new ClusterMembershipUpdate(this, ImmutableArray.Empty); } var changes = ImmutableHashSet.CreateBuilder(); foreach (var entry in this.Members) { // Include any entry which is new or has changed state. if (!previous.Members.TryGetValue(entry.Key, out var previousEntry) || previousEntry.Status != entry.Value.Status) { changes.Add(entry.Value); } } // Handle entries which were removed entirely. foreach (var entry in previous.Members) { if (!this.Members.TryGetValue(entry.Key, out _)) { changes.Add(new ClusterMember(entry.Key, SiloStatus.Dead, entry.Value.Name)); } } return new ClusterMembershipUpdate(this, changes.ToImmutableArray()); } /// public override string ToString() { var sb = new StringBuilder(); sb.Append($"ClusterMembershipSnapshot {{ Version = {this.Version}, Members.Count = {this.Members.Count}, Members = ["); var first = true; foreach (var member in this.Members) { if (first) { first = false; } else { sb.Append(", "); } sb.Append(member.Value); } sb.Append("] }}"); return sb.ToString(); } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ClusterMembershipUpdate.cs ================================================ using System; using System.Collections.Immutable; namespace Orleans.Runtime { /// /// Represents a cluster membership snapshot and changes from a previous snapshot. /// [Serializable, GenerateSerializer, Immutable] public sealed class ClusterMembershipUpdate { /// /// Initializes a new instance of the class. /// /// The snapshot. /// The changes. public ClusterMembershipUpdate(ClusterMembershipSnapshot snapshot, ImmutableArray changes) { this.Snapshot = snapshot; this.Changes = changes; } /// /// Gets a value indicating whether this instance has changes. /// /// if this instance has changes; otherwise, . public bool HasChanges => !this.Changes.IsDefaultOrEmpty; /// /// Gets the changes. /// /// The changes. [Id(0)] public ImmutableArray Changes { get; } /// /// Gets the snapshot. /// /// The snapshot. [Id(1)] public ClusterMembershipSnapshot Snapshot { get; } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/DevelopmentClusterMembershipOptions.cs ================================================ using System.Net; namespace Orleans.Configuration { /// Configures development clustering options public class DevelopmentClusterMembershipOptions { /// /// Gets or sets the seed node to find the membership system grain. /// public IPEndPoint PrimarySiloEndpoint { get; set; } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/DevelopmentClusterMembershipOptionsValidator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Runtime.MembershipService; namespace Orleans.Configuration { internal class DevelopmentClusterMembershipOptionsValidator : IConfigurationValidator { private readonly DevelopmentClusterMembershipOptions options; private readonly IMembershipTable membershipTable; public DevelopmentClusterMembershipOptionsValidator(IOptions options, IServiceProvider serviceProvider) { this.options = options.Value; this.membershipTable = serviceProvider.GetService(); } public void ValidateConfiguration() { if (this.membershipTable is SystemTargetBasedMembershipTable && this.options.PrimarySiloEndpoint is null) { throw new OrleansConfigurationException("Development clustering is enabled but no value is specified "); } } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/IClusterMembershipService.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime { /// /// Functionality for querying and interacting with cluster membership. /// public interface IClusterMembershipService { /// /// Gets the most recent cluster membership snapshot. /// /// The current snapshot. ClusterMembershipSnapshot CurrentSnapshot { get; } /// /// Gets an enumerable collection of membership updates. /// /// The membership updates. IAsyncEnumerable MembershipUpdates { get; } /// /// Refreshes cluster membership if it is not at or above the specified minimum version. /// /// The minimum version. /// The cancellation token. /// A representing the work performed. ValueTask Refresh(MembershipVersion minimumVersion = default, CancellationToken cancellationToken = default); /// /// Unilaterally declares the specified silo defunct. /// /// The silo address which is being declared defunct. /// if the silo has been evicted, otherwise. Task TryKill(SiloAddress siloAddress); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/IMembershipGossiper.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Runtime.MembershipService { internal interface IMembershipGossiper { Task GossipToRemoteSilos( List gossipPartners, MembershipTableSnapshot snapshot, SiloAddress updatedSilo, SiloStatus updatedStatus); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/IRemoteSiloProber.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace Orleans.Runtime.MembershipService { /// /// Responsible for probing remote silos for responsiveness. /// internal interface IRemoteSiloProber { /// /// Probes the specified for responsiveness. /// /// The silo to probe. /// The probe identifier for diagnostic purposes. /// The cancellation token. /// /// A which completes when the probe returns successfully and faults when the probe fails. /// Task Probe(SiloAddress silo, int probeNumber, CancellationToken cancellationToken = default); /// /// Probes the specified indirectly, via . /// /// The silo which will perform a direct probe. /// The silo which will be probed. /// The timeout which the should apply to the probe. /// The probe number for diagnostic purposes. /// The cancellation token. Task ProbeIndirectly(SiloAddress intermediary, SiloAddress target, TimeSpan probeTimeout, int probeNumber, CancellationToken cancellationToken = default); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ISiloStatusListener.cs ================================================ namespace Orleans.Runtime { /// /// Interface for types which listen to silo status change notifications. /// /// /// To be implemented by different in-silo runtime components that are interested in silo status notifications from ISiloStatusOracle. /// public interface ISiloStatusListener { /// /// Receive notifications about silo status events. /// /// A silo to update about. /// The status of a silo. void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/ISiloStatusOracle.cs ================================================ using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Runtime { /// /// Authoritative local, per-silo source for information about the status of other silos. /// public interface ISiloStatusOracle { /// /// Gets the current status of this silo. /// SiloStatus CurrentStatus { get; } /// /// Gets the name of this silo. /// string SiloName { get; } /// /// Gets the address of this silo. /// SiloAddress SiloAddress { get; } /// /// Gets the currently active silos. /// ImmutableArray GetActiveSilos(); /// /// Gets the status of a given silo. /// This method returns an approximate view on the status of a given silo. /// In particular, this oracle may think the given silo is alive, while it may already have failed. /// If this oracle thinks the given silo is dead, it has been authoritatively told so by ISiloDirectory. /// /// A silo whose status we are interested in. /// The status of a given silo. SiloStatus GetApproximateSiloStatus(SiloAddress siloAddress); /// /// Gets the statuses of all silo. /// This method returns an approximate view on the statuses of all silo. /// /// Include only silo who are currently considered to be active. If false, include all. /// A list of silo statuses. Dictionary GetApproximateSiloStatuses(bool onlyActive = false); /// /// Gets the name of a silo. /// Silo name is assumed to be static and does not change across restarts of the same silo. /// /// A silo whose name we are interested in. /// A silo name. /// TTrue if could return the requested name, false otherwise. bool TryGetSiloName(SiloAddress siloAddress, out string siloName); /// /// Gets a value indicating whether the current silo is valid for creating new activations on or for directory lookups. /// /// The silo so ask about. bool IsFunctionalDirectory(SiloAddress siloAddress); /// /// Gets a value indicating whether the current silo is dead. /// /// The silo so ask about. bool IsDeadSilo(SiloAddress silo); /// /// Subscribe to status events about all silos. /// /// An observer async interface to receive silo status notifications. /// A value indicating whether subscription succeeded or not. bool SubscribeToSiloStatusEvents(ISiloStatusListener observer); /// /// UnSubscribe from status events about all silos. /// /// A value indicating whether subscription succeeded or not. bool UnSubscribeFromSiloStatusEvents(ISiloStatusListener observer); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/InMemoryMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Orleans.Serialization; namespace Orleans.Runtime.MembershipService { internal class InMemoryMembershipTable { private readonly Dictionary> siloTable; private TableVersion tableVersion; private long lastETagCounter; [NonSerialized] private readonly DeepCopier deepCopier; public InMemoryMembershipTable(DeepCopier deepCopier) { this.deepCopier = deepCopier; siloTable = new Dictionary>(); lastETagCounter = 0; tableVersion = new TableVersion(0, NewETag()); } public MembershipTableData Read(SiloAddress key) { return siloTable.TryGetValue(key, out var data) ? new MembershipTableData(this.deepCopier.Copy(data), tableVersion) : new MembershipTableData(tableVersion); } public MembershipTableData ReadAll() { return new MembershipTableData(siloTable.Values.Select(tuple => new Tuple(this.deepCopier.Copy(tuple.Item1), tuple.Item2)).ToList(), tableVersion); } public TableVersion ReadTableVersion() { return tableVersion; } public bool Insert(MembershipEntry entry, TableVersion version) { Tuple data; siloTable.TryGetValue(entry.SiloAddress, out data); if (data != null) return false; if (!tableVersion.VersionEtag.Equals(version.VersionEtag)) return false; siloTable[entry.SiloAddress] = new Tuple( entry, lastETagCounter++.ToString(CultureInfo.InvariantCulture)); tableVersion = new TableVersion(version.Version, NewETag()); return true; } public bool Update(MembershipEntry entry, string etag, TableVersion version) { Tuple data; siloTable.TryGetValue(entry.SiloAddress, out data); if (data == null) return false; if (!data.Item2.Equals(etag) || !tableVersion.VersionEtag.Equals(version.VersionEtag)) return false; siloTable[entry.SiloAddress] = new Tuple( entry, lastETagCounter++.ToString(CultureInfo.InvariantCulture)); tableVersion = new TableVersion(version.Version, NewETag()); return true; } public void UpdateIAmAlive(MembershipEntry entry) { Tuple data; siloTable.TryGetValue(entry.SiloAddress, out data); if (data == null) return; data.Item1.IAmAliveTime = entry.IAmAliveTime; siloTable[entry.SiloAddress] = new Tuple(data.Item1, NewETag()); } public override string ToString() => $"Table = {ReadAll()}, ETagCounter={lastETagCounter}"; private string NewETag() { return lastETagCounter++.ToString(CultureInfo.InvariantCulture); } public void CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { var removedEnties = new List(); foreach (var (key, (value, etag)) in siloTable) { if (value.Status == SiloStatus.Dead && new DateTime(Math.Max(value.IAmAliveTime.Ticks, value.StartTime.Ticks), DateTimeKind.Utc) < beforeDate) { removedEnties.Add(key); } } foreach (var removedEntry in removedEnties) { siloTable.Remove(removedEntry); } } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/LocalSiloHealthMonitor.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Messaging; namespace Orleans.Runtime.MembershipService { internal interface ILocalSiloHealthMonitor { /// /// Returns the local health degradation score, which is a value between 0 (healthy) and (unhealthy). /// /// The time which the check is taking place. /// The local health degradation score, which is a value between 0 (healthy) and (unhealthy). int GetLocalHealthDegradationScore(DateTime checkTime); /// /// The most recent list of detected health issues. /// ImmutableArray Complaints { get; } } /// /// Monitors the health of the local node using a combination of heuristics to create a health degradation score which /// is exposed as a boolean value: whether or not the local node's health is degraded. /// /// /// The primary goal of this functionality is to passify degraded nodes so that they do not evict healthy nodes. /// This functionality is inspired by the Lifeguard paper (https://arxiv.org/abs/1707.00788), which is a set of extensions /// to the SWIM membership algorithm (https://research.cs.cornell.edu/projects/Quicksilver/public_pdfs/SWIM.pdf). Orleans /// uses a strong consistency membership algorithm, and not all of the Lifeguard extensions to SWIM apply to Orleans' /// membership algorithm (refutation, for example). /// The monitor implements the following heuristics: /// /// Check that this silos is marked as active in membership. /// Check that no other silo suspects this silo. /// Check for recently received successful ping responses. /// Check for recently received ping requests. /// Check that the .NET Thread Pool is able to process work items within one second. /// Check that local async timers have been firing on-time (within 3 seconds of their due time). /// /// internal partial class LocalSiloHealthMonitor : ILifecycleParticipant, ILifecycleObserver, ILocalSiloHealthMonitor { private const int MaxScore = 8; private readonly List _healthCheckParticipants; private readonly MembershipTableManager _membershipTableManager; private readonly ClusterHealthMonitor _clusterHealthMonitor; private readonly ILocalSiloDetails _localSiloDetails; private readonly ILogger _lo; private readonly ClusterMembershipOptions _clusterMembershipOptions; private readonly IAsyncTimer _degradationCheckTimer; private readonly ThreadPoolMonitor _threadPoolMonitor; private readonly ProbeRequestMonitor _probeRequestMonitor; private ValueStopwatch _clusteredDuration; private Task? _runTask; private bool _isActive; private DateTime _lastHealthCheckTime; public LocalSiloHealthMonitor( IEnumerable healthCheckParticipants, MembershipTableManager membershipTableManager, ConnectionManager connectionManager, ClusterHealthMonitor clusterHealthMonitor, ILocalSiloDetails localSiloDetails, ILogger log, IOptions clusterMembershipOptions, IAsyncTimerFactory timerFactory, ILoggerFactory loggerFactory, ProbeRequestMonitor probeRequestMonitor) { _healthCheckParticipants = healthCheckParticipants.ToList(); _membershipTableManager = membershipTableManager; _clusterHealthMonitor = clusterHealthMonitor; _localSiloDetails = localSiloDetails; _lo = log; _probeRequestMonitor = probeRequestMonitor; _clusterMembershipOptions = clusterMembershipOptions.Value; _degradationCheckTimer = timerFactory.Create( _clusterMembershipOptions.LocalHealthDegradationMonitoringPeriod, nameof(LocalSiloHealthMonitor)); _threadPoolMonitor = new ThreadPoolMonitor(loggerFactory.CreateLogger()); } /// public ImmutableArray Complaints { get; private set; } = ImmutableArray.Empty; /// public int GetLocalHealthDegradationScore(DateTime checkTime) => GetLocalHealthDegradationScore(checkTime, null); /// /// Returns the local health degradation score, which is a value between 0 (healthy) and (unhealthy). /// /// The time which the check is taking place. /// If not null, will be populated with the current set of detected health issues. /// The local health degradation score, which is a value between 0 (healthy) and (unhealthy). public int GetLocalHealthDegradationScore(DateTime checkTime, List? complaints) { var score = 0; score += CheckSuspectingNodes(checkTime, complaints); score += CheckLocalHealthCheckParticipants(checkTime, complaints); score += CheckThreadPoolQueueDelay(checkTime, complaints); if (_isActive) { var membershipSnapshot = _membershipTableManager.MembershipTableSnapshot; if (membershipSnapshot.ActiveNodeCount <= 1) { _clusteredDuration.Reset(); } else if (!_clusteredDuration.IsRunning) { _clusteredDuration.Restart(); } // Only consider certain checks if the silo has been a member of a multi-silo cluster for a certain period. var recencyWindow = _clusterMembershipOptions.ProbeTimeout.Multiply(_clusterMembershipOptions.NumMissedProbesLimit); if (_clusteredDuration.Elapsed > recencyWindow) { score += CheckReceivedProbeResponses(checkTime, complaints); score += CheckReceivedProbeRequests(checkTime, complaints); } } // Clamp the score between 0 and the maximum allowed score. score = Math.Max(0, Math.Min(MaxScore, score)); return score; } private int CheckThreadPoolQueueDelay(DateTime checkTime, List? complaints) { var threadPoolDelaySeconds = _threadPoolMonitor.MeasureQueueDelay().TotalSeconds; if ((int)threadPoolDelaySeconds >= 1) { // Log as an error if the delay is massive. var logLevel = (int)threadPoolDelaySeconds >= 10 ? LogLevel.Error : LogLevel.Warning; LogThreadPoolDelay(logLevel, threadPoolDelaySeconds); complaints?.Add( $".NET Thread Pool is exhibiting delays of {threadPoolDelaySeconds}s. This can indicate .NET Thread Pool starvation, very long .NET GC pauses, or other runtime or machine pauses."); } // Each second of delay contributes to the score. return (int)threadPoolDelaySeconds; } private int CheckSuspectingNodes(DateTime now, List? complaints) { var score = 0; var membershipSnapshot = _membershipTableManager.MembershipTableSnapshot; if (membershipSnapshot.Entries.TryGetValue(_localSiloDetails.SiloAddress, out var membershipEntry)) { if (membershipEntry.Status != SiloStatus.Active) { LogSiloNotActive(membershipEntry.Status); complaints?.Add($"This silo is not active (Status: {membershipEntry.Status}) and is therefore not healthy."); score = MaxScore; } // Check if there are valid votes against this node. var expiration = _clusterMembershipOptions.DeathVoteExpirationTimeout; var freshVotes = membershipEntry.GetFreshVotes(now, expiration); foreach (var vote in freshVotes) { if (membershipSnapshot.GetSiloStatus(vote.Item1) == SiloStatus.Active) { LogSiloSuspected(vote.Item1, vote.Item2); complaints?.Add($"Silo {vote.Item1} recently suspected this silo is dead at {vote.Item2}."); ++score; } } } else { LogMembershipEntryNotFound(); complaints?.Add("Could not find a membership entry for this silo"); score = MaxScore; } return score; } private int CheckReceivedProbeRequests(DateTime now, List? complaints) { // Have we received ping REQUESTS from other nodes? var score = 0; var membershipSnapshot = _membershipTableManager.MembershipTableSnapshot; // Only consider recency of the last received probe request if there is more than one other node. // Otherwise, it may fail to vote another node dead in a one or two node cluster. if (membershipSnapshot.ActiveNodeCount > 2) { var sinceLastProbeRequest = _probeRequestMonitor.ElapsedSinceLastProbeRequest; var recencyWindow = _clusterMembershipOptions.ProbeTimeout.Multiply(_clusterMembershipOptions.NumMissedProbesLimit); if (!sinceLastProbeRequest.HasValue) { LogNoProbeRequests(); complaints?.Add("This silo has not received any probe requests"); ++score; } else if (sinceLastProbeRequest.Value > recencyWindow) { // This node has not received a successful ping response since the window began. var lastRequestTime = now - sinceLastProbeRequest.Value; LogNoRecentProbeRequest(lastRequestTime); complaints?.Add($"This silo has not received a probe request since {lastRequestTime}"); ++score; } } return score; } private int CheckReceivedProbeResponses(DateTime now, List? complaints) { // Determine how recently the latest successful ping response was received. var siloMonitors = _clusterHealthMonitor.SiloMonitors; var elapsedSinceLastResponse = default(TimeSpan?); foreach (var monitor in siloMonitors.Values) { var current = monitor.ElapsedSinceLastResponse; if (current.HasValue && (!elapsedSinceLastResponse.HasValue || current.Value < elapsedSinceLastResponse.Value)) { elapsedSinceLastResponse = current.Value; } } // Only consider recency of the last successful ping if this node is monitoring more than one other node. // Otherwise, it may fail to vote another node dead in a one or two node cluster. int score = 0; if (siloMonitors.Count > 1) { var recencyWindow = _clusterMembershipOptions.ProbeTimeout.Multiply(_clusterMembershipOptions.NumMissedProbesLimit); if (!elapsedSinceLastResponse.HasValue) { LogNoProbeResponses(); complaints?.Add("This silo has not received any successful probe responses"); ++score; } else if (elapsedSinceLastResponse.Value > recencyWindow) { // This node has not received a successful ping response since the window began. LogNoRecentProbeResponse(elapsedSinceLastResponse.Value); complaints?.Add($"This silo has not received a successful probe response since {elapsedSinceLastResponse.Value}"); ++score; } } return score; } private int CheckLocalHealthCheckParticipants(DateTime now, List? complaints) { // Check for execution delays and other local health warning signs. var score = 0; foreach (var participant in _healthCheckParticipants) { try { if (!participant.CheckHealth(_lastHealthCheckTime, out var reason)) { LogHealthCheckParticipantUnhealthy(participant.GetType(), reason); complaints?.Add($"Health check participant {participant.GetType()} is reporting that it is unhealthy with complaint: {reason}"); ++score; } } catch (Exception exception) { LogHealthCheckParticipantError(exception, participant.GetType()); complaints?.Add($"Error checking health for participant {participant.GetType()}: {LogFormatter.PrintException(exception)}"); ++score; } } _lastHealthCheckTime = now; return score; } private async Task Run() { while (await _degradationCheckTimer.NextTick()) { try { var complaints = new List(); var now = DateTime.UtcNow; var score = GetLocalHealthDegradationScore(now, complaints); if (score > 0) { var complaintsString = string.Join("\n", complaints); LogSelfMonitoringDegraded(score, MaxScore, complaintsString); } this.Complaints = ImmutableArray.CreateRange(complaints); } catch (Exception exception) { LogErrorMonitoringLocalSiloHealth(exception); } } } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.Active, this); } public Task OnStart(CancellationToken ct) { _runTask = Task.Run(this.Run); _isActive = true; return Task.CompletedTask; } public async Task OnStop(CancellationToken ct) { _degradationCheckTimer.Dispose(); _isActive = false; if (_runTask is Task task) { await task.WaitAsync(ct).SuppressThrowing(); } } /// /// Measures queue delay on the .NET . /// private class ThreadPoolMonitor { private static readonly WaitCallback Callback = state => ((ThreadPoolMonitor)state!).Execute(); #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly ILogger _log; private bool _scheduled; private TimeSpan _lastQueueDelay; private ValueStopwatch _queueDelay; public ThreadPoolMonitor(ILogger log) { _log = log; } public TimeSpan MeasureQueueDelay() { bool shouldSchedule; TimeSpan delay; lock (_lockObj) { var currentQueueDelay = _queueDelay.Elapsed; delay = currentQueueDelay > _lastQueueDelay ? currentQueueDelay : _lastQueueDelay; if (!_scheduled) { _scheduled = true; shouldSchedule = true; _queueDelay.Restart(); } else { shouldSchedule = false; } } if (shouldSchedule) { _ = ThreadPool.UnsafeQueueUserWorkItem(Callback, this); } return delay; } private void Execute() { try { lock (_lockObj) { _scheduled = false; _queueDelay.Stop(); _lastQueueDelay = _queueDelay.Elapsed; } } catch (Exception exception) { _log.LogError(exception, "Exception monitoring .NET thread pool delay"); } } } [LoggerMessage( Message = ".NET Thread Pool is exhibiting delays of {ThreadPoolQueueDelaySeconds}s. This can indicate .NET Thread Pool starvation, very long .NET GC pauses, or other runtime or machine pauses." )] private partial void LogThreadPoolDelay(LogLevel logLevel, double threadPoolQueueDelaySeconds); [LoggerMessage( Level = LogLevel.Warning, Message = "This silo is not active (Status: {Status}) and is therefore not healthy." )] private partial void LogSiloNotActive(SiloStatus status); [LoggerMessage( Level = LogLevel.Warning, Message = "Silo {Silo} recently suspected this silo is dead at {SuspectingTime}." )] private partial void LogSiloSuspected(SiloAddress silo, DateTime suspectingTime); [LoggerMessage( Level = LogLevel.Error, Message = "Could not find a membership entry for this silo" )] private partial void LogMembershipEntryNotFound(); [LoggerMessage( Level = LogLevel.Warning, Message = "This silo has not received any probe requests" )] private partial void LogNoProbeRequests(); [LoggerMessage( Level = LogLevel.Warning, Message = "This silo has not received a probe request since {LastProbeRequest}" )] private partial void LogNoRecentProbeRequest(DateTime lastProbeRequest); [LoggerMessage( Level = LogLevel.Warning, Message = "This silo has not received any successful probe responses" )] private partial void LogNoProbeResponses(); [LoggerMessage( Level = LogLevel.Warning, Message = "This silo has not received a successful probe response since {LastSuccessfulResponse}" )] private partial void LogNoRecentProbeResponse(TimeSpan lastSuccessfulResponse); [LoggerMessage( Level = LogLevel.Warning, Message = "Health check participant {Participant} is reporting that it is unhealthy with complaint: {Reason}" )] private partial void LogHealthCheckParticipantUnhealthy(Type participant, string reason); [LoggerMessage( Level = LogLevel.Error, Message = "Error checking health for {Participant}" )] private partial void LogHealthCheckParticipantError(Exception exception, Type participant); [LoggerMessage( Level = LogLevel.Warning, Message = "Self-monitoring determined that local health is degraded. Degradation score is {Score}/{MaxScore} (lower is better). Complaints: {Complaints}" )] private partial void LogSelfMonitoringDegraded(int score, int maxScore, string complaints); [LoggerMessage( Level = LogLevel.Error, Message = "Error while monitoring local silo health" )] private partial void LogErrorMonitoringLocalSiloHealth(Exception exception); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipAgent.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Orleans.Configuration; using System.Threading.Tasks; using System.Threading; using Microsoft.Extensions.Options; using System.Linq; using Orleans.Internal; namespace Orleans.Runtime.MembershipService { /// /// Responsible for updating membership table with details about the local silo. /// internal partial class MembershipAgent : IHealthCheckParticipant, ILifecycleParticipant, IDisposable, MembershipAgent.ITestAccessor { private static readonly TimeSpan EXP_BACKOFF_CONTENTION_MIN = TimeSpan.FromMilliseconds(200); private static readonly TimeSpan EXP_BACKOFF_CONTENTION_MAX = TimeSpan.FromMinutes(2); private static readonly TimeSpan EXP_BACKOFF_STEP = TimeSpan.FromMilliseconds(1000); private readonly CancellationTokenSource cancellation = new CancellationTokenSource(); private readonly MembershipTableManager tableManager; private readonly ILocalSiloDetails localSilo; private readonly IFatalErrorHandler fatalErrorHandler; private readonly ClusterMembershipOptions clusterMembershipOptions; private readonly ILogger log; private readonly IRemoteSiloProber siloProber; private readonly IAsyncTimer iAmAliveTimer; private Func getUtcDateTime = () => DateTime.UtcNow; public MembershipAgent( MembershipTableManager tableManager, ILocalSiloDetails localSilo, IFatalErrorHandler fatalErrorHandler, IOptions options, ILogger log, IAsyncTimerFactory timerFactory, IRemoteSiloProber siloProber) { this.tableManager = tableManager; this.localSilo = localSilo; this.fatalErrorHandler = fatalErrorHandler; this.clusterMembershipOptions = options.Value; this.log = log; this.siloProber = siloProber; this.iAmAliveTimer = timerFactory.Create( this.clusterMembershipOptions.IAmAliveTablePublishTimeout, nameof(UpdateIAmAlive)); } internal interface ITestAccessor { Action OnUpdateIAmAlive { get; set; } Func GetDateTime { get; set; } } Action ITestAccessor.OnUpdateIAmAlive { get; set; } Func ITestAccessor.GetDateTime { get => this.getUtcDateTime; set => this.getUtcDateTime = value ?? throw new ArgumentNullException(nameof(value)); } private async Task UpdateIAmAlive() { LogDebugStartingPeriodicMembershipLivenessTimestampUpdates(); try { // jitter for initial TimeSpan? overrideDelayPeriod = RandomTimeSpan.Next(this.clusterMembershipOptions.IAmAliveTablePublishTimeout); var exponentialBackoff = new ExponentialBackoff(EXP_BACKOFF_CONTENTION_MIN, EXP_BACKOFF_CONTENTION_MAX, EXP_BACKOFF_STEP); var runningFailures = 0; while (await this.iAmAliveTimer.NextTick(overrideDelayPeriod) && !this.tableManager.CurrentStatus.IsTerminating()) { try { var stopwatch = ValueStopwatch.StartNew(); ((ITestAccessor)this).OnUpdateIAmAlive?.Invoke(); await this.tableManager.UpdateIAmAlive(); LogTraceUpdatingIAmAliveTook(stopwatch.Elapsed); overrideDelayPeriod = default; runningFailures = 0; } catch (Exception exception) { runningFailures += 1; LogWarningFailedToUpdateTableEntryForThisSilo(exception); // Retry quickly and then exponentially back off overrideDelayPeriod = exponentialBackoff.Next(runningFailures); } } } catch (Exception exception) when (this.fatalErrorHandler.IsUnexpected(exception)) { LogErrorErrorUpdatingLivenessTimestamp(exception); this.fatalErrorHandler.OnFatalException(this, nameof(UpdateIAmAlive), exception); } finally { LogDebugStoppingPeriodicMembershipLivenessTimestampUpdates(); } } private async Task BecomeActive() { LogInformationBecomeActive(); await this.ValidateInitialConnectivity(); try { await this.UpdateStatus(SiloStatus.Active); LogInformationFinishedBecomeActive(); } catch (Exception exception) { LogInformationBecomeActiveFailed(exception); throw; } } private async Task ValidateInitialConnectivity() { // Continue attempting to validate connectivity until some reasonable timeout. var maxAttemptTime = this.clusterMembershipOptions.MaxJoinAttemptTime; var attemptNumber = 1; var now = this.getUtcDateTime(); var attemptUntil = now + maxAttemptTime; var canContinue = true; while (true) { try { var activeSilos = new List(); foreach (var item in this.tableManager.MembershipTableSnapshot.Entries) { var entry = item.Value; if (entry.Status != SiloStatus.Active) continue; if (entry.SiloAddress.IsSameLogicalSilo(this.localSilo.SiloAddress)) continue; if (entry.HasMissedIAmAlives(this.clusterMembershipOptions, now) != default) continue; activeSilos.Add(entry.SiloAddress); } var failedSilos = await CheckClusterConnectivity(activeSilos.ToArray()); var successfulSilos = activeSilos.Where(s => !failedSilos.Contains(s)).ToList(); // If there were no failures, terminate the loop and return without error. if (failedSilos.Count == 0) break; LogErrorFailedToGetPingResponses(failedSilos.Count, activeSilos.Count, new(successfulSilos), new(failedSilos), attemptUntil, attemptNumber); if (now + TimeSpan.FromSeconds(5) > attemptUntil) { canContinue = false; var msg = $"Failed to get ping responses from {failedSilos.Count} of {activeSilos.Count} active silos. " + "Newly joining silos validate connectivity with all active silos that have recently updated their 'I Am Alive' value before joining the cluster. " + $"Successfully contacted: {Utils.EnumerableToString(successfulSilos)}. Failed to get response from: {Utils.EnumerableToString(failedSilos)}"; throw new OrleansClusterConnectivityCheckFailedException(msg); } // Refresh membership after some delay and retry. await Task.Delay(TimeSpan.FromSeconds(5)); await this.tableManager.Refresh(); } catch (Exception exception) when (canContinue) { LogErrorFailedToValidateInitialClusterConnectivity(exception); await Task.Delay(TimeSpan.FromSeconds(1)); } ++attemptNumber; now = this.getUtcDateTime(); } async Task> CheckClusterConnectivity(SiloAddress[] members) { if (members.Length == 0) return new List(); var tasks = new List>(members.Length); LogInformationAboutToSendPings(members.Length, new EnumerableToStringLogValue(members)); var timeout = this.clusterMembershipOptions.ProbeTimeout; foreach (var silo in members) { tasks.Add(ProbeSilo(this.siloProber, silo, timeout, this.log)); } try { await Task.WhenAll(tasks); } catch { // Ignore exceptions for now. } var failed = new List(); for (var i = 0; i < tasks.Count; i++) { if (tasks[i].Status != TaskStatus.RanToCompletion || !tasks[i].GetAwaiter().GetResult()) { failed.Add(members[i]); } } return failed; } static async Task ProbeSilo(IRemoteSiloProber siloProber, SiloAddress silo, TimeSpan timeout, ILogger log) { Exception exception; try { await siloProber.Probe(silo, 0).WaitAsync(timeout); return true; } catch (Exception ex) { exception = ex; } LogWarningDidNotReceiveProbeResponse(log, exception, silo, timeout); return false; } } private async Task BecomeJoining() { LogInformationJoining(); try { await this.UpdateStatus(SiloStatus.Joining); } catch (Exception exc) { LogErrorErrorUpdatingStatusToJoining(exc); throw; } } private async Task BecomeShuttingDown() { LogDebugShutdown(); try { await this.UpdateStatus(SiloStatus.ShuttingDown); } catch (Exception exc) { LogErrorErrorUpdatingStatusToShuttingDown(exc); throw; } } private async Task BecomeStopping() { LogDebugStop(); try { await this.UpdateStatus(SiloStatus.Stopping); } catch (Exception exc) { LogErrorErrorUpdatingStatusToStopping(exc); throw; } } private async Task BecomeDead() { LogDebugUpdatingStatusToDead(); try { await this.UpdateStatus(SiloStatus.Dead); } catch (Exception exception) { LogErrorFailureUpdatingStatusToDead(exception); throw; } } private async Task UpdateStatus(SiloStatus status) { await this.tableManager.UpdateStatus(status); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { { Task OnRuntimeInitializeStart(CancellationToken ct) => Task.CompletedTask; async Task OnRuntimeInitializeStop(CancellationToken ct) { this.iAmAliveTimer.Dispose(); this.cancellation.Cancel(); await Task.WhenAny( Task.Run(() => this.BecomeDead()), Task.Delay(TimeSpan.FromMinutes(1))); } lifecycle.Subscribe( nameof(MembershipAgent), ServiceLifecycleStage.RuntimeInitialize + 1, // Gossip before the outbound queue gets closed OnRuntimeInitializeStart, OnRuntimeInitializeStop); } { async Task AfterRuntimeGrainServicesStart(CancellationToken ct) { await Task.Run(() => this.BecomeJoining()); } Task AfterRuntimeGrainServicesStop(CancellationToken ct) => Task.CompletedTask; lifecycle.Subscribe( nameof(MembershipAgent), ServiceLifecycleStage.AfterRuntimeGrainServices, AfterRuntimeGrainServicesStart, AfterRuntimeGrainServicesStop); } { var tasks = new List(); async Task OnBecomeActiveStart(CancellationToken ct) { await Task.Run(() => this.BecomeActive()); tasks.Add(Task.Run(() => this.UpdateIAmAlive())); } async Task OnBecomeActiveStop(CancellationToken ct) { this.iAmAliveTimer.Dispose(); this.cancellation.Cancel(throwOnFirstException: false); var cancellationTask = ct.WhenCancelled(); if (ct.IsCancellationRequested) { await Task.Run(() => this.BecomeStopping()); } else { // Allow some minimum time for graceful shutdown. var gracePeriod = Task.WhenAll(Task.Delay(ClusterMembershipOptions.ClusteringShutdownGracePeriod), cancellationTask); var task = await Task.WhenAny(gracePeriod, this.BecomeShuttingDown()); if (ReferenceEquals(task, gracePeriod)) { this.log.LogWarning("Graceful shutdown aborted: starting ungraceful shutdown"); await Task.Run(() => this.BecomeStopping()); } else { await Task.WhenAny(gracePeriod, Task.WhenAll(tasks)); } } } lifecycle.Subscribe( nameof(MembershipAgent), ServiceLifecycleStage.BecomeActive, OnBecomeActiveStart, OnBecomeActiveStop); } } public void Dispose() { this.iAmAliveTimer.Dispose(); } bool IHealthCheckable.CheckHealth(DateTime lastCheckTime, out string reason) => this.iAmAliveTimer.CheckHealth(lastCheckTime, out reason); private readonly struct EnumerableToStringLogValue(IEnumerable enumerable) { public override string ToString() => Utils.EnumerableToString(enumerable); } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting periodic membership liveness timestamp updates" )] private partial void LogDebugStartingPeriodicMembershipLivenessTimestampUpdates(); [LoggerMessage( Level = LogLevel.Trace, Message = "Updating IAmAlive took {Elapsed}" )] private partial void LogTraceUpdatingIAmAliveTook(TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.MembershipUpdateIAmAliveFailure, Level = LogLevel.Warning, Message = "Failed to update table entry for this silo, will retry shortly" )] private partial void LogWarningFailedToUpdateTableEntryForThisSilo(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping periodic membership liveness timestamp updates" )] private partial void LogDebugStoppingPeriodicMembershipLivenessTimestampUpdates(); [LoggerMessage( Level = LogLevel.Error, Message = "Error updating liveness timestamp" )] private partial void LogErrorErrorUpdatingLivenessTimestamp(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipBecomeActive, Level = LogLevel.Information, Message = "-BecomeActive" )] private partial void LogInformationBecomeActive(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFinishBecomeActive, Level = LogLevel.Information, Message = "-Finished BecomeActive." )] private partial void LogInformationFinishedBecomeActive(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToBecomeActive, Level = LogLevel.Information, Message = "BecomeActive failed" )] private partial void LogInformationBecomeActiveFailed(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipJoiningPreconditionFailure, Level = LogLevel.Error, Message = "Failed to get ping responses from {FailedCount} of {ActiveCount} active silos. " + "Newly joining silos validate connectivity with all active silos that have recently updated their 'I Am Alive' value before joining the cluster. " + "Successfully contacted: {SuccessfulSilos}. Silos which did not respond successfully are: {FailedSilos}. " + "Will continue attempting to validate connectivity until {Timeout}. Attempt #{Attempt}" )] private partial void LogErrorFailedToGetPingResponses(int failedCount, int activeCount, EnumerableToStringLogValue successfulSilos, EnumerableToStringLogValue failedSilos, DateTime timeout, int attempt); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to validate initial cluster connectivity" )] private partial void LogErrorFailedToValidateInitialClusterConnectivity(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipSendingPreJoinPing, Level = LogLevel.Information, Message = "About to send pings to {Count} nodes in order to validate communication in the Joining state. Pinged nodes = {Nodes}" )] private partial void LogInformationAboutToSendPings(int count, EnumerableToStringLogValue nodes); [LoggerMessage( Level = LogLevel.Warning, Message = "Did not receive a probe response from silo {SiloAddress} in timeout {Timeout}" )] private static partial void LogWarningDidNotReceiveProbeResponse(ILogger logger, Exception exception, SiloAddress siloAddress, TimeSpan timeout); [LoggerMessage( EventId = (int)ErrorCode.MembershipJoining, Level = LogLevel.Information, Message = "Joining" )] private partial void LogInformationJoining(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToJoin, Level = LogLevel.Error, Message = "Error updating status to Joining" )] private partial void LogErrorErrorUpdatingStatusToJoining(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipShutDown, Level = LogLevel.Debug, Message = "-Shutdown" )] private partial void LogDebugShutdown(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToShutdown, Level = LogLevel.Error, Message = "Error updating status to ShuttingDown" )] private partial void LogErrorErrorUpdatingStatusToShuttingDown(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipStop, Level = LogLevel.Debug, Message = "-Stop" )] private partial void LogDebugStop(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToStop, Level = LogLevel.Error, Message = "Error updating status to Stopping" )] private partial void LogErrorErrorUpdatingStatusToStopping(Exception exception); [LoggerMessage( EventId = (int)ErrorCode.MembershipKillMyself, Level = LogLevel.Debug, Message = "Updating status to Dead" )] private partial void LogDebugUpdatingStatusToDead(); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToKillMyself, Level = LogLevel.Error, Message = "Failure updating status to Dead" )] private partial void LogErrorFailureUpdatingStatusToDead(Exception exception); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipGossiper.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.MembershipService; internal partial class MembershipGossiper(IServiceProvider serviceProvider, ILogger logger) : IMembershipGossiper { private MembershipSystemTarget? _membershipSystemTarget; public Task GossipToRemoteSilos( List gossipPartners, MembershipTableSnapshot snapshot, SiloAddress updatedSilo, SiloStatus updatedStatus) { if (gossipPartners.Count == 0) return Task.CompletedTask; LogDebugGossipingStatusToPartners(logger, updatedSilo, updatedStatus, gossipPartners.Count); var systemTarget = _membershipSystemTarget ??= serviceProvider.GetRequiredService(); return systemTarget.GossipToRemoteSilos(gossipPartners, snapshot, updatedSilo, updatedStatus); } [LoggerMessage( Level = LogLevel.Debug, Message = "Gossiping {Silo} status {Status} to {NumPartners} partners" )] private static partial void LogDebugGossipingStatusToPartners(ILogger logger, SiloAddress silo, SiloStatus status, int numPartners); } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipSystemTarget.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime.Scheduler; namespace Orleans.Runtime.MembershipService { internal sealed partial class MembershipSystemTarget : SystemTarget, IMembershipService, ILifecycleParticipant { private readonly MembershipTableManager membershipTableManager; private readonly ILogger log; private readonly IInternalGrainFactory grainFactory; public MembershipSystemTarget( MembershipTableManager membershipTableManager, ILogger log, IInternalGrainFactory grainFactory, SystemTargetShared shared) : base(Constants.MembershipServiceType, shared) { this.membershipTableManager = membershipTableManager; this.log = log; this.grainFactory = grainFactory; shared.ActivationDirectory.RecordNewTarget(this); } public Task Ping(int pingNumber) => Task.CompletedTask; public async Task MembershipChangeNotification(MembershipTableSnapshot snapshot) { if (snapshot.Version != MembershipVersion.MinValue) { await this.membershipTableManager.RefreshFromSnapshot(snapshot); } else { LogTraceReceivedGossipMembershipChangeNotificationWithMinValue(this.log); await ReadTable(); } } /// /// Send a ping to a remote silo. This is intended to be called from a /// in order to initiate the call from the 's context /// /// The remote silo to ping. /// The probe number, for diagnostic purposes. /// The result of pinging the remote silo. public Task ProbeRemoteSilo(SiloAddress remoteSilo, int probeNumber) => this.RunOrQueueTask(() => ProbeInternal(remoteSilo, probeNumber)); /// /// Send a ping to a remote silo via an intermediary silo. This is intended to be called from a /// in order to initiate the call from the 's context /// /// The intermediary which will directly probe the target. /// The target which will be probed. /// The timeout for the eventual direct probe request. /// The probe number, for diagnostic purposes. /// The result of pinging the remote silo. public Task ProbeRemoteSiloIndirectly(SiloAddress intermediary, SiloAddress target, TimeSpan probeTimeout, int probeNumber) { Task ProbeIndirectly() { var remoteOracle = this.grainFactory.GetSystemTarget(Constants.MembershipServiceType, intermediary); return remoteOracle.ProbeIndirectly(target, probeTimeout, probeNumber); } var workItem = new AsyncClosureWorkItem(ProbeIndirectly, this); WorkItemGroup.QueueWorkItem(workItem); return workItem.Task; } public async Task ProbeIndirectly(SiloAddress target, TimeSpan probeTimeout, int probeNumber) { IndirectProbeResponse result; var healthScore = this.ActivationServices.GetRequiredService().GetLocalHealthDegradationScore(DateTime.UtcNow); var probeResponseTimer = ValueStopwatch.StartNew(); try { var probeTask = this.ProbeInternal(target, probeNumber); try { await probeTask.WaitAsync(probeTimeout); } catch (TimeoutException exception) { LogWarningRequestedProbeTimeoutExceeded(this.log, exception, probeTimeout); throw; } result = new IndirectProbeResponse { Succeeded = true, IntermediaryHealthScore = healthScore, ProbeResponseTime = probeResponseTimer.Elapsed, }; } catch (Exception exception) { result = new IndirectProbeResponse { Succeeded = false, IntermediaryHealthScore = healthScore, FailureMessage = $"Encountered exception {LogFormatter.PrintException(exception)}", ProbeResponseTime = probeResponseTimer.Elapsed, }; } return result; } public Task GossipToRemoteSilos( List gossipPartners, MembershipTableSnapshot snapshot, SiloAddress updatedSilo, SiloStatus updatedStatus) { async Task Gossip() { var tasks = new List(gossipPartners.Count); foreach (var silo in gossipPartners) { tasks.Add(this.GossipToRemoteSilo(silo, snapshot, updatedSilo, updatedStatus)); } await Task.WhenAll(tasks); } return this.RunOrQueueTask(Gossip); } private async Task GossipToRemoteSilo( SiloAddress silo, MembershipTableSnapshot snapshot, SiloAddress updatedSilo, SiloStatus updatedStatus) { LogTraceSendingStatusUpdateGossipNotification(this.log, updatedSilo, updatedStatus, silo); try { var remoteOracle = this.grainFactory.GetSystemTarget(Constants.MembershipServiceType, silo); await remoteOracle.MembershipChangeNotification(snapshot); } catch (Exception exception) { LogWarningErrorSendingGossipNotificationToRemoteSilo(this.log, exception, silo); } } private async Task ReadTable() { try { await this.membershipTableManager.Refresh(); } catch (Exception exception) { LogErrorErrorRefreshingMembershipTable(this.log, exception); } } private Task ProbeInternal(SiloAddress remoteSilo, int probeNumber) { Task task; try { RequestContext.Set(RequestContext.PING_APPLICATION_HEADER, true); var remoteOracle = this.grainFactory.GetSystemTarget(Constants.MembershipServiceType, remoteSilo); task = remoteOracle.Ping(probeNumber); // Update stats counter. Only count Pings that were successfully sent, but not necessarily replied to. MessagingInstruments.OnPingSend(remoteSilo); } finally { RequestContext.Remove(RequestContext.PING_APPLICATION_HEADER); } return task; } void ILifecycleParticipant.Participate(ISiloLifecycle observer) { // No-op, just ensure this instance is created at start-up. } [LoggerMessage( Level = LogLevel.Trace, Message = "-Received GOSSIP MembershipChangeNotification with MembershipVersion.MinValue. Going to read the table" )] private static partial void LogTraceReceivedGossipMembershipChangeNotificationWithMinValue(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, Message = "Requested probe timeout {ProbeTimeout} exceeded" )] private static partial void LogWarningRequestedProbeTimeoutExceeded(ILogger logger, Exception exception, TimeSpan probeTimeout); [LoggerMessage( Level = LogLevel.Trace, Message = "-Sending status update GOSSIP notification about silo {UpdatedSilo}, status {UpdatedStatus}, to silo {RemoteSilo}" )] private static partial void LogTraceSendingStatusUpdateGossipNotification(ILogger logger, SiloAddress updatedSilo, SiloStatus updatedStatus, SiloAddress remoteSilo); [LoggerMessage( EventId = (int)ErrorCode.MembershipGossipSendFailure, Level = LogLevel.Warning, Message = "Error sending gossip notification to remote silo '{Silo}'." )] private static partial void LogWarningErrorSendingGossipNotificationToRemoteSilo(ILogger logger, Exception exception, SiloAddress silo); [LoggerMessage( EventId = (int)ErrorCode.MembershipGossipProcessingFailure, Level = LogLevel.Error, Message = "Error refreshing membership table." )] private static partial void LogErrorErrorRefreshingMembershipTable(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipTableCleanupAgent.cs ================================================ #nullable enable using System; using Orleans.Configuration; using System.Threading.Tasks; using System.Threading; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Orleans.Internal; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Orleans.Runtime.MembershipService { /// /// Responsible for cleaning up dead membership table entries. /// internal partial class MembershipTableCleanupAgent : IHealthCheckParticipant, ILifecycleParticipant, IDisposable { private readonly ClusterMembershipOptions _clusterMembershipOptions; private readonly IMembershipTable _membershipTableProvider; private readonly ILogger _logger; private readonly IAsyncTimer? _cleanupDefunctSilosTimer; public MembershipTableCleanupAgent( IOptions clusterMembershipOptions, IMembershipTable membershipTableProvider, ILogger log, IAsyncTimerFactory timerFactory) { _clusterMembershipOptions = clusterMembershipOptions.Value; _membershipTableProvider = membershipTableProvider; _logger = log; if (_clusterMembershipOptions.DefunctSiloCleanupPeriod.HasValue) { _cleanupDefunctSilosTimer = timerFactory.Create( _clusterMembershipOptions.DefunctSiloCleanupPeriod.Value, nameof(CleanupDefunctSilos)); } } public void Dispose() { _cleanupDefunctSilosTimer?.Dispose(); } private async Task CleanupDefunctSilos() { if (!_clusterMembershipOptions.DefunctSiloCleanupPeriod.HasValue) { LogDebugMembershipTableCleanupDisabled(_logger); return; } Debug.Assert(_cleanupDefunctSilosTimer is not null); LogDebugStartingMembershipTableCleanupAgent(_logger); try { var period = _clusterMembershipOptions.DefunctSiloCleanupPeriod.Value; // The first cleanup should be scheduled for shortly after silo startup. var delay = RandomTimeSpan.Next(TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(10)); while (await _cleanupDefunctSilosTimer.NextTick(delay)) { // Select a random time within the next window. // The purpose of this is to add jitter to a process which could be affected by contention with other silos. delay = RandomTimeSpan.Next(period, period + TimeSpan.FromMinutes(5)); try { var dateLimit = DateTime.UtcNow - _clusterMembershipOptions.DefunctSiloExpiration; await _membershipTableProvider.CleanupDefunctSiloEntries(dateLimit); } catch (Exception exception) when (exception is NotImplementedException or MissingMethodException) { _cleanupDefunctSilosTimer.Dispose(); LogWarningCleanupDefunctSiloEntriesNotSupported(_logger); return; } catch (Exception exception) { LogErrorFailedToCleanUpDefunctMembershipTableEntries(_logger, exception); } } } finally { LogDebugStoppedMembershipTableCleanupAgent(_logger); } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { Task? task = null; lifecycle.Subscribe(nameof(MembershipTableCleanupAgent), ServiceLifecycleStage.Active, OnStart, OnStop); Task OnStart(CancellationToken ct) { task = Task.Run(CleanupDefunctSilos); return Task.CompletedTask; } async Task OnStop(CancellationToken ct) { _cleanupDefunctSilosTimer?.Dispose(); if (task is { }) { await task.WaitAsync(ct).SuppressThrowing(); } } } bool IHealthCheckable.CheckHealth(DateTime lastCheckTime, [NotNullWhen(false)] out string? reason) { if (_cleanupDefunctSilosTimer is IAsyncTimer timer) { return timer.CheckHealth(lastCheckTime, out reason); } reason = default; return true; } [LoggerMessage( Level = LogLevel.Debug, Message = "Membership table cleanup is disabled due to ClusterMembershipOptions.DefunctSiloCleanupPeriod not being specified" )] private static partial void LogDebugMembershipTableCleanupDisabled(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting membership table cleanup agent" )] private static partial void LogDebugStartingMembershipTableCleanupAgent(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, Message = "IMembershipTable.CleanupDefunctSiloEntries operation is not supported by the current implementation of IMembershipTable. Disabling the timer now." )] private static partial void LogWarningCleanupDefunctSiloEntriesNotSupported(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to clean up defunct membership table entries" )] private static partial void LogErrorFailedToCleanUpDefunctMembershipTableEntries(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopped membership table cleanup agent" )] private static partial void LogDebugStoppedMembershipTableCleanupAgent(ILogger logger); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipTableEntryExtensions.cs ================================================ using System; using Orleans.Configuration; namespace Orleans.Runtime.MembershipService; internal static class MembershipTableEntryExtensions { public static bool HasMissedIAmAlives(this MembershipEntry entry, ClusterMembershipOptions options, DateTime time) => time - entry.EffectiveIAmAliveTime > options.AllowedIAmAliveMissPeriod; } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipTableManager.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Utilities; using Orleans.Serialization.TypeSystem; namespace Orleans.Runtime.MembershipService { internal partial class MembershipTableManager : IHealthCheckParticipant, ILifecycleParticipant, IDisposable { private const int NUM_CONDITIONAL_WRITE_CONTENTION_ATTEMPTS = -1; // unlimited private const int NUM_CONDITIONAL_WRITE_ERROR_ATTEMPTS = -1; private static readonly TimeSpan EXP_BACKOFF_ERROR_MIN = TimeSpan.FromMilliseconds(1000); private static readonly TimeSpan EXP_BACKOFF_CONTENTION_MIN = TimeSpan.FromMilliseconds(100); private static readonly TimeSpan EXP_BACKOFF_ERROR_MAX = TimeSpan.FromMinutes(1); private static readonly TimeSpan EXP_BACKOFF_CONTENTION_MAX = TimeSpan.FromMinutes(1); private static readonly TimeSpan EXP_BACKOFF_STEP = TimeSpan.FromMilliseconds(1000); private static readonly TimeSpan GossipTimeout = TimeSpan.FromMilliseconds(3000); private static readonly string RoleName = CachedTypeResolver.GetName(Assembly.GetEntryAssembly() ?? typeof(MembershipTableManager).Assembly); private readonly IFatalErrorHandler fatalErrorHandler; private readonly IMembershipGossiper gossiper; private readonly ILocalSiloDetails localSiloDetails; private readonly IMembershipTable membershipTableProvider; private readonly ILogger log; private readonly ISiloLifecycle siloLifecycle; private readonly ClusterMembershipOptions clusterMembershipOptions; private readonly DateTime siloStartTime = DateTime.UtcNow; private readonly SiloAddress myAddress; private readonly AsyncEnumerable updates; private readonly IAsyncTimer membershipUpdateTimer; private readonly CancellationTokenSource _shutdownCts = new(); private readonly Task _suspectOrKillsListTask; private readonly Channel _trySuspectOrKillChannel = Channel.CreateBounded(new BoundedChannelOptions(100) { FullMode = BoundedChannelFullMode.DropOldest }); // For testing. internal AutoResetEvent TestingSuspectOrKillIdle = new(false); private MembershipTableSnapshot snapshot; public MembershipTableManager( ILocalSiloDetails localSiloDetails, IOptions clusterMembershipOptions, IMembershipTable membershipTable, IFatalErrorHandler fatalErrorHandler, IMembershipGossiper gossiper, ILogger log, IAsyncTimerFactory timerFactory, ISiloLifecycle siloLifecycle) { this.localSiloDetails = localSiloDetails; this.membershipTableProvider = membershipTable; this.fatalErrorHandler = fatalErrorHandler; this.gossiper = gossiper; this.clusterMembershipOptions = clusterMembershipOptions.Value; this.myAddress = this.localSiloDetails.SiloAddress; this.log = log; this.siloLifecycle = siloLifecycle; var initialEntries = ImmutableDictionary.Empty.SetItem(this.myAddress, this.CreateLocalSiloEntry(this.CurrentStatus)); this.snapshot = new MembershipTableSnapshot( MembershipVersion.MinValue, initialEntries); this.updates = new AsyncEnumerable( initialValue: this.snapshot, updateValidator: (previous, proposed) => proposed.IsSuccessorTo(previous), onPublished: update => Interlocked.Exchange(ref this.snapshot, update)); this.membershipUpdateTimer = timerFactory.Create( this.clusterMembershipOptions.TableRefreshTimeout, nameof(PeriodicallyRefreshMembershipTable)); _suspectOrKillsListTask = Task.Run(ProcessSuspectOrKillLists); } internal Func GetDateTimeUtcNow { get; set; } = () => DateTime.UtcNow; public MembershipTableSnapshot MembershipTableSnapshot => this.snapshot; public IAsyncEnumerable MembershipTableUpdates => this.updates; public SiloStatus CurrentStatus { get; private set; } = SiloStatus.Created; private bool IsStopping => this.siloLifecycle.LowestStoppedStage <= ServiceLifecycleStage.Active; private Task pendingRefresh; public async Task Refresh() { var pending = this.pendingRefresh; if (pending == null || pending.IsCompleted) { pending = this.pendingRefresh = this.RefreshInternal(requireCleanup: false); } await pending; } public async Task RefreshFromSnapshot(MembershipTableSnapshot snapshot) { if (snapshot.Version == MembershipVersion.MinValue) throw new ArgumentException("Cannot call RefreshFromSnapshot with Version == MembershipVersion.MinValue"); // Check if a refresh is underway var pending = this.pendingRefresh; if (pending != null && !pending.IsCompleted) { await pending; } LogInformationReceivedClusterMembershipSnapshot(this.log, snapshot); if (snapshot.Entries.TryGetValue(this.myAddress, out var localSiloEntry)) { if (localSiloEntry.Status == SiloStatus.Dead && this.CurrentStatus != SiloStatus.Dead) { LogWarningFoundMyselfDeadInRefreshFromSnapshot(this.log, localSiloEntry.ToFullString()); this.KillMyselfLocally($"I should be Dead according to membership table (in RefreshFromSnapshot). Local entry: {(localSiloEntry.ToFullString())}."); } } this.updates.TryPublish(MembershipTableSnapshot.Update, snapshot); } private async Task RefreshInternal(bool requireCleanup) { var table = await this.membershipTableProvider.ReadAll(); this.ProcessTableUpdate(table, "Refresh"); bool success; try { success = await this.CleanupMyTableEntries(table); } catch (Exception exception) when (!requireCleanup) { success = false; LogWarningExceptionWhileCleaningUpTableEntries(this.log, exception); } // If cleanup was not required then the cleanup result is ignored. return !requireCleanup || success; } private async Task Start() { try { LogInformationMembershipStarting(this.log, this.localSiloDetails.DnsHostName, this.myAddress, LogFormatter.PrintDate(this.siloStartTime)); // Init the membership table. await this.membershipTableProvider.InitializeMembershipTable(true); // Perform an initial table read var refreshed = await AsyncExecutorWithRetries.ExecuteWithRetries( function: _ => this.RefreshInternal(requireCleanup: true), maxNumSuccessTries: NUM_CONDITIONAL_WRITE_CONTENTION_ATTEMPTS, maxNumErrorTries: NUM_CONDITIONAL_WRITE_ERROR_ATTEMPTS, retryValueFilter: (value, i) => !value, retryExceptionFilter: (exc, i) => true, maxExecutionTime: this.clusterMembershipOptions.MaxJoinAttemptTime, onSuccessBackOff: new ExponentialBackoff(EXP_BACKOFF_CONTENTION_MIN, EXP_BACKOFF_CONTENTION_MAX, EXP_BACKOFF_STEP), onErrorBackOff: new ExponentialBackoff(EXP_BACKOFF_ERROR_MIN, EXP_BACKOFF_ERROR_MAX, EXP_BACKOFF_STEP)); if (!refreshed) { throw new OrleansException("Failed to perform initial membership refresh and cleanup."); } // read the table and look for my node migration occurrences DetectNodeMigration(this.snapshot, this.localSiloDetails.DnsHostName); } catch (Exception exception) { LogErrorMembershipFailedToStart(this.log, exception); throw; } } public async Task UpdateIAmAlive() { var entry = new MembershipEntry { SiloAddress = myAddress, IAmAliveTime = GetDateTimeUtcNow() }; await this.membershipTableProvider.UpdateIAmAlive(entry); } private void DetectNodeMigration(MembershipTableSnapshot snapshot, string myHostname) { string mySiloName = this.localSiloDetails.Name; MembershipEntry mostRecentPreviousEntry = null; // look for silo instances that are same as me, find most recent with Generation before me. foreach (var entry in snapshot.Entries.Select(entry => entry.Value).Where(data => mySiloName.Equals(data.SiloName))) { bool iAmLater = myAddress.Generation.CompareTo(entry.SiloAddress.Generation) > 0; // more recent if (iAmLater && (mostRecentPreviousEntry == null || entry.SiloAddress.Generation.CompareTo(mostRecentPreviousEntry.SiloAddress.Generation) > 0)) mostRecentPreviousEntry = entry; } if (mostRecentPreviousEntry != null) { bool physicalHostChanged = !myHostname.Equals(mostRecentPreviousEntry.HostName) || !myAddress.Endpoint.Equals(mostRecentPreviousEntry.SiloAddress.Endpoint); if (physicalHostChanged) { LogWarningNodeMigrated(this.log, mySiloName, myHostname, myAddress, mostRecentPreviousEntry.HostName, mostRecentPreviousEntry.SiloAddress); } else { LogWarningNodeRestarted(this.log, mySiloName, myHostname, myAddress, mostRecentPreviousEntry.SiloAddress); } } } private async Task PeriodicallyRefreshMembershipTable() { LogDebugStartingPeriodicMembershipTableRefreshes(this.log); try { // jitter for initial TimeSpan? overrideDelayPeriod = RandomTimeSpan.Next(this.clusterMembershipOptions.TableRefreshTimeout); var exponentialBackoff = new ExponentialBackoff(EXP_BACKOFF_CONTENTION_MIN, EXP_BACKOFF_CONTENTION_MAX, EXP_BACKOFF_STEP); var runningFailures = 0; while (await this.membershipUpdateTimer.NextTick(overrideDelayPeriod)) { try { var stopwatch = ValueStopwatch.StartNew(); await this.Refresh(); LogTraceRefreshingMembershipTableTook(this.log, stopwatch.Elapsed); // reset to allow normal refresh period after success overrideDelayPeriod = default; runningFailures = 0; } catch (Exception exception) { runningFailures += 1; LogWarningFailedToRefreshMembershipTable(this.log, exception, runningFailures); // Retry quickly and then exponentially back off overrideDelayPeriod = exponentialBackoff.Next(runningFailures); } } } catch (Exception exception) when (this.fatalErrorHandler.IsUnexpected(exception)) { LogWarningErrorRefreshingMembershipTable(this.log, exception); this.fatalErrorHandler.OnFatalException(this, nameof(PeriodicallyRefreshMembershipTable), exception); } finally { LogDebugStoppingPeriodicMembershipTableRefreshes(this.log); } } private static Task MembershipExecuteWithRetries( Func> taskFunction, TimeSpan timeout) { return MembershipExecuteWithRetries(taskFunction, timeout, (result, i) => result == false); } private static Task MembershipExecuteWithRetries( Func> taskFunction, TimeSpan timeout, Func retryValueFilter) { return AsyncExecutorWithRetries.ExecuteWithRetries( taskFunction, NUM_CONDITIONAL_WRITE_CONTENTION_ATTEMPTS, NUM_CONDITIONAL_WRITE_ERROR_ATTEMPTS, retryValueFilter, // if failed to Update on contention - retry (exc, i) => true, // Retry on errors. timeout, new ExponentialBackoff(EXP_BACKOFF_CONTENTION_MIN, EXP_BACKOFF_CONTENTION_MAX, EXP_BACKOFF_STEP), // how long to wait between successful retries new ExponentialBackoff(EXP_BACKOFF_ERROR_MIN, EXP_BACKOFF_ERROR_MAX, EXP_BACKOFF_STEP) // how long to wait between error retries ); } public async Task UpdateStatus(SiloStatus status) { bool wasThrownLocally = false; int numCalls = 0; try { async Task UpdateMyStatusTask(int counter) { numCalls++; LogDebugGoingToTryToUpdateMyStatusGlobalOnce(this.log, counter); return await TryUpdateMyStatusGlobalOnce(status); // function to retry } if (status.IsTerminating() && this.membershipTableProvider is SystemTargetBasedMembershipTable) { // SystemTarget-based membership may not be accessible at this stage, so allow for one quick attempt to update // the status before continuing regardless of the outcome. var updateTask = UpdateMyStatusTask(0); updateTask.Ignore(); await Task.WhenAny(Task.Delay(TimeSpan.FromMilliseconds(500)), updateTask); var gossipTask = this.GossipToOthers(this.myAddress, status); gossipTask.Ignore(); await Task.WhenAny(Task.Delay(TimeSpan.FromMilliseconds(500)), gossipTask); this.CurrentStatus = status; return; } bool ok = await MembershipExecuteWithRetries(UpdateMyStatusTask, this.clusterMembershipOptions.MaxJoinAttemptTime); if (ok) { LogDebugSuccessfullyUpdatedMyStatus(this.log, myAddress, status); var gossipTask = this.GossipToOthers(this.myAddress, status); gossipTask.Ignore(); using var cancellation = new CancellationTokenSource(); var timeoutTask = Task.Delay(GossipTimeout, cancellation.Token); var task = await Task.WhenAny(gossipTask, timeoutTask); if (ReferenceEquals(task, timeoutTask)) { if (status.IsTerminating()) { LogWarningTimedOutWhileGossipingStatus(this.log, GossipTimeout); } else { LogDebugTimedOutWhileGossipingStatus(this.log, GossipTimeout); } } else { cancellation.Cancel(); } } else { wasThrownLocally = true; LogInformationFailedToUpdateMyStatusDueToWriteContention(this.log, myAddress, status, numCalls); throw new OrleansException($"Silo {myAddress} failed to update its status to {status} in the membership table due to write contention on the table after {numCalls} attempts."); } } catch (Exception exc) when (!wasThrownLocally) { LogWarningFailedToUpdateMyStatusDueToFailures(this.log, exc, myAddress, status, numCalls); throw new OrleansException($"Silo {myAddress} failed to update its status to {status} in the table due to failures (socket failures or table read/write failures) after {numCalls} attempts", exc); } } // read the table // find all currently active nodes and test pings to all of them // try to ping all // if all pings succeeded // try to change my status to Active and in the same write transaction update Membership version row, conditioned on both etags // if failed (on ping or on write exception or on etag) - retry the whole AttemptToJoinActiveNodes private async Task TryUpdateMyStatusGlobalOnce(SiloStatus newStatus) { var table = await membershipTableProvider.ReadAll(); LogDebugTryUpdateMyStatusGlobalOnce(this.log, newStatus.Equals(SiloStatus.Active) ? "All" : " my entry from", table.ToString()); LogMissedIAmAlives(table); var (myEntry, myEtag) = this.GetOrCreateLocalSiloEntry(table, newStatus); if (myEntry.Status == SiloStatus.Dead && myEntry.Status != newStatus) { LogWarningFoundMyselfDead1(this.log, myEntry.ToFullString()); this.KillMyselfLocally($"I should be Dead according to membership table (in TryUpdateMyStatusGlobalOnce): Entry = {(myEntry.ToFullString())}."); return true; } var now = GetDateTimeUtcNow(); if (newStatus == SiloStatus.Dead) myEntry.AddSuspector(myAddress, now); // add the killer (myself) to the suspect list, for easier diagnostics later on. myEntry.Status = newStatus; myEntry.IAmAliveTime = now; bool ok; TableVersion next = table.Version.Next(); if (myEtag != null) // no previous etag for my entry -> its the first write to this entry, so insert instead of update. { ok = await membershipTableProvider.UpdateRow(myEntry, myEtag, next); } else { ok = await membershipTableProvider.InsertRow(myEntry, next); } if (ok) { this.CurrentStatus = newStatus; var entries = table.Members.ToDictionary(e => e.Item1.SiloAddress, e => e); entries[myEntry.SiloAddress] = Tuple.Create(myEntry, myEtag); var updatedTable = new MembershipTableData(entries.Values.ToList(), next); this.ProcessTableUpdate(updatedTable, nameof(TryUpdateMyStatusGlobalOnce)); } return ok; } private (MembershipEntry Entry, string ETag) GetOrCreateLocalSiloEntry(MembershipTableData table, SiloStatus currentStatus) { if (table.TryGet(myAddress) is { } myTuple) { return (myTuple.Item1.Copy(), myTuple.Item2); } var result = CreateLocalSiloEntry(currentStatus); return (result, null); } private MembershipEntry CreateLocalSiloEntry(SiloStatus currentStatus) { return new MembershipEntry { SiloAddress = this.localSiloDetails.SiloAddress, HostName = this.localSiloDetails.DnsHostName, SiloName = this.localSiloDetails.Name, Status = currentStatus, ProxyPort = this.localSiloDetails.GatewayAddress?.Endpoint?.Port ?? 0, RoleName = RoleName, SuspectTimes = new List>(), StartTime = this.siloStartTime, IAmAliveTime = GetDateTimeUtcNow() }; } private void ProcessTableUpdate(MembershipTableData table, string caller) { if (table is null) throw new ArgumentNullException(nameof(table)); LogDebugProcessTableUpdate(this.log, caller, table); if (this.updates.TryPublish(MembershipTableSnapshot.Update, table)) { this.LogMissedIAmAlives(table); LogDebugProcessTableUpdateWithTable(this.log, caller, new(table)); } } private void LogMissedIAmAlives(MembershipTableData table) { foreach (var pair in table.Members) { var entry = pair.Item1; if (entry.SiloAddress.Equals(myAddress)) continue; if (entry.Status != SiloStatus.Active) continue; var now = GetDateTimeUtcNow(); if (entry.HasMissedIAmAlives(this.clusterMembershipOptions, now)) { var missedSince = entry.EffectiveIAmAliveTime; LogWarningMissedIAmAliveTableUpdate(this.log, entry.SiloAddress, missedSince, now, now - missedSince, clusterMembershipOptions.AllowedIAmAliveMissPeriod); } } } private async Task CleanupMyTableEntries(MembershipTableData table) { if (this.IsStopping) return true; var silosToDeclareDead = new List>(); foreach (var tuple in table.Members.Where( tuple => tuple.Item1.SiloAddress.Endpoint.Equals(myAddress.Endpoint))) { var entry = tuple.Item1; var siloAddress = entry.SiloAddress; if (siloAddress.Generation.Equals(myAddress.Generation)) { if (entry.Status == SiloStatus.Dead) { LogWarningFoundMyselfDead2(this.log, entry.ToFullString()); KillMyselfLocally($"I should be Dead according to membership table (in CleanupTableEntries): entry = {(entry.ToFullString())}."); } continue; } if (entry.Status == SiloStatus.Dead) { LogTraceSkippingOldDeadEntry(this.log, entry.ToFullString()); continue; } LogDebugTemporalAnomalyDetected(this.log, myAddress, siloAddress); // Temporal paradox - There is an older clone of this silo in the membership table if (siloAddress.Generation < myAddress.Generation) { LogWarningDetectedOlder(this.log, myAddress, siloAddress, entry.ToString()); // Declare older clone of me as Dead. silosToDeclareDead.Add(tuple); //return DeclareDead(entry, eTag, tableVersion); } else if (siloAddress.Generation > myAddress.Generation) { // I am the older clone - Newer version of me should survive - I need to kill myself LogWarningDetectedNewer(this.log, myAddress, siloAddress, entry.ToString()); await this.UpdateStatus(SiloStatus.Dead); KillMyselfLocally($"Detected newer version of myself - I am the older clone so I will stop -- Current Me={myAddress} Newer Me={siloAddress}, Current entry={entry}"); return true; // No point continuing! } } if (silosToDeclareDead.Count == 0) return true; LogDebugCleanupTableEntriesAboutToDeclareDead(this.log, silosToDeclareDead.Count, Utils.EnumerableToString(silosToDeclareDead.Select(tuple => tuple.Item1))); foreach (var siloData in silosToDeclareDead) { await _trySuspectOrKillChannel.Writer.WriteAsync( SuspectOrKillRequest.CreateKillRequest(siloData.Item1.SiloAddress)); } return true; } private void KillMyselfLocally(string reason) { LogErrorKillMyselfLocally(this.log, reason); this.CurrentStatus = SiloStatus.Dead; this.fatalErrorHandler.OnFatalException(this, $"I have been told I am dead, so this silo will stop! Reason: {reason}", null); } private async Task GossipToOthers(SiloAddress updatedSilo, SiloStatus updatedStatus) { if (!this.clusterMembershipOptions.UseLivenessGossip) return; var now = GetDateTimeUtcNow(); var gossipPartners = new List(); foreach (var item in this.MembershipTableSnapshot.Entries) { var entry = item.Value; if (entry.SiloAddress.IsSameLogicalSilo(this.myAddress)) continue; if (!IsFunctionalForMembership(entry.Status)) continue; if (entry.HasMissedIAmAlives(this.clusterMembershipOptions, now)) continue; gossipPartners.Add(entry.SiloAddress); bool IsFunctionalForMembership(SiloStatus status) { return status == SiloStatus.Active || status == SiloStatus.ShuttingDown || status == SiloStatus.Stopping; } } try { await this.gossiper.GossipToRemoteSilos(gossipPartners, MembershipTableSnapshot, updatedSilo, updatedStatus); } catch (Exception exception) { LogWarningErrorWhileGossipingStatus(this.log, exception); } } private class SuspectOrKillRequest { public SiloAddress SiloAddress { get; set; } public SiloAddress OtherSilo { get; set; } public RequestType Type { get; set; } public enum RequestType { Unknown = 0, SuspectOrKill, Kill } public static SuspectOrKillRequest CreateKillRequest(SiloAddress silo) { return new SuspectOrKillRequest { SiloAddress = silo, OtherSilo = null, Type = RequestType.Kill }; } public static SuspectOrKillRequest CreateSuspectOrKillRequest(SiloAddress silo, SiloAddress otherSilo) { return new SuspectOrKillRequest { SiloAddress = silo, OtherSilo = otherSilo, Type = RequestType.SuspectOrKill }; } } public async Task TryKill(SiloAddress silo) { await _trySuspectOrKillChannel.Writer.WriteAsync(SuspectOrKillRequest.CreateKillRequest(silo)); return true; } public async Task ProcessSuspectOrKillLists() { var backoff = new ExponentialBackoff(EXP_BACKOFF_ERROR_MIN, EXP_BACKOFF_ERROR_MAX, EXP_BACKOFF_STEP); var runningFailureCount = 0; var reader = _trySuspectOrKillChannel.Reader; while (await reader.WaitToReadAsync(_shutdownCts.Token)) { while (reader.TryRead(out var request)) { await Task.Delay(backoff.Next(runningFailureCount), _shutdownCts.Token); try { switch (request.Type) { case SuspectOrKillRequest.RequestType.Kill: await InnerTryKill(request.SiloAddress, _shutdownCts.Token); break; case SuspectOrKillRequest.RequestType.SuspectOrKill: await InnerTryToSuspectOrKill(request.SiloAddress, request.OtherSilo, _shutdownCts.Token); break; } runningFailureCount = 0; } catch (Exception ex) { runningFailureCount += 1; LogErrorProcessingSuspectOrKillLists(this.log, ex, runningFailureCount); await _trySuspectOrKillChannel.Writer.WriteAsync(request, _shutdownCts.Token); } if (!reader.TryPeek(out _)) { TestingSuspectOrKillIdle.Set(); } } } } private async Task InnerTryKill(SiloAddress silo, CancellationToken cancellationToken) { var table = await membershipTableProvider.ReadAll().WaitAsync(cancellationToken); LogDebugTryKillReadMembershipTable(this.log, table.ToString()); if (this.IsStopping) { LogInformationIgnoringCallToTryKill(this.log, silo); return true; } var (localSiloEntry, _) = this.GetOrCreateLocalSiloEntry(table, this.CurrentStatus); if (localSiloEntry.Status == SiloStatus.Dead) { var msg = string.Format("I should be Dead according to membership table (in TryKill): entry = {0}.", localSiloEntry.ToFullString()); LogWarningFoundMyselfDead3(this.log, msg); KillMyselfLocally(msg); return true; } if (table.TryGet(silo) is not { } tuple) { var str = $"Could not find silo entry for silo {silo} in the table."; LogErrorCouldNotFindSiloEntry(this.log, str); throw new KeyNotFoundException(str); } var entry = tuple.Item1.Copy(); string eTag = tuple.Item2; // Check if the table already knows that this silo is dead if (entry.Status == SiloStatus.Dead) { this.ProcessTableUpdate(table, "TryKill"); return true; } LogInformationMarkingSiloAsDead(this.log, entry.SiloAddress); return await DeclareDead(entry, eTag, table.Version, GetDateTimeUtcNow()).WaitAsync(cancellationToken); } public async Task TryToSuspectOrKill(SiloAddress silo, SiloAddress indirectProbingSilo = null) { await _trySuspectOrKillChannel.Writer.WriteAsync(SuspectOrKillRequest.CreateSuspectOrKillRequest(silo, indirectProbingSilo)); return true; } private async Task InnerTryToSuspectOrKill(SiloAddress silo, SiloAddress indirectProbingSilo, CancellationToken cancellationToken) { var table = await membershipTableProvider.ReadAll().WaitAsync(cancellationToken); var now = GetDateTimeUtcNow(); LogDebugTryToSuspectOrKillReadMembershipTable(this.log, table.ToString()); if (this.IsStopping) { LogInformationIgnoringCallToTrySuspectOrKill(this.log, silo); return true; } var (localSiloEntry, _) = this.GetOrCreateLocalSiloEntry(table, this.CurrentStatus); if (localSiloEntry.Status == SiloStatus.Dead) { var localSiloEntryDetails = localSiloEntry.ToFullString(); LogWarningFoundMyselfDead3(this.log, $"I should be Dead according to membership table (in TryToSuspectOrKill): entry = {localSiloEntryDetails}."); KillMyselfLocally($"I should be Dead according to membership table (in TryToSuspectOrKill): entry = {localSiloEntryDetails}."); return true; } if (table.TryGet(silo) is not { } tuple) { LogErrorCouldNotFindSiloEntry(this.log, $"Could not find silo entry for silo {silo} in the table."); return false; } var entry = tuple.Item1.Copy(); string eTag = tuple.Item2; LogDebugTryToSuspectOrKillCurrentStatus(this.log, entry.SiloAddress, entry.Status, entry.ToString()); // Check if the table already knows that this silo is dead if (entry.Status == SiloStatus.Dead) { this.ProcessTableUpdate(table, "TrySuspectOrKill"); return true; } // Get all valid (non-expired) votes var freshVotes = entry.GetFreshVotes(now, this.clusterMembershipOptions.DeathVoteExpirationTimeout); LogTraceCurrentNumberOfFreshVoters(this.log, silo, freshVotes.Count.ToString()); if (freshVotes.Count >= this.clusterMembershipOptions.NumVotesForDeathDeclaration) { LogErrorSiloIsSuspectedButNotMarkedAsDead(this.log, entry.SiloAddress, freshVotes.Count.ToString(), this.clusterMembershipOptions.NumVotesForDeathDeclaration.ToString()); KillMyselfLocally("Found a bug! Will stop."); return false; } // Try to add our vote to the list and tally the fresh votes again. var prevList = entry.SuspectTimes?.ToList() ?? new List>(); entry.AddOrUpdateSuspector(myAddress, now, clusterMembershipOptions.NumVotesForDeathDeclaration); // Include the indirect probe silo's vote as well, if it exists. if (indirectProbingSilo is not null) { entry.AddOrUpdateSuspector(indirectProbingSilo, now, clusterMembershipOptions.NumVotesForDeathDeclaration); } freshVotes = entry.GetFreshVotes(now, this.clusterMembershipOptions.DeathVoteExpirationTimeout); // Determine if there are enough votes to evict the silo. // Handle the corner case when the number of active silos is very small (then my only vote is enough) int activeNonStaleSilos = table.Members.Count(kv => kv.Item1.Status == SiloStatus.Active && !kv.Item1.HasMissedIAmAlives(clusterMembershipOptions, now)); var numVotesRequiredToEvict = Math.Min(clusterMembershipOptions.NumVotesForDeathDeclaration, (activeNonStaleSilos + 1) / 2); if (freshVotes.Count >= numVotesRequiredToEvict) { LogInformationEvictingSilo(this.log, entry.SiloAddress, freshVotes.Count, this.clusterMembershipOptions.NumVotesForDeathDeclaration, activeNonStaleSilos, PrintSuspectList(entry.SuspectTimes)); return await DeclareDead(entry, eTag, table.Version, now).WaitAsync(cancellationToken); } LogInformationVotingToEvictSilo(this.log, entry.SiloAddress, PrintSuspectList(prevList), PrintSuspectList(entry.SuspectTimes), eTag, PrintSuspectList(freshVotes)); // If we fail to update here we will retry later. var ok = await membershipTableProvider.UpdateRow(entry, eTag, table.Version.Next()).WaitAsync(cancellationToken); if (ok) { table = await membershipTableProvider.ReadAll().WaitAsync(cancellationToken); this.ProcessTableUpdate(table, "TrySuspectOrKill"); // Gossip using the local silo status, since this is just informational to propagate the suspicion vote. GossipToOthers(localSiloEntry.SiloAddress, localSiloEntry.Status).Ignore(); } return ok; string PrintSuspectList(IEnumerable> list) { return Utils.EnumerableToString(list, t => $"<{t.Item1}, {LogFormatter.PrintDate(t.Item2)}>"); } } private async Task DeclareDead(MembershipEntry entry, string etag, TableVersion tableVersion, DateTime time) { if (this.clusterMembershipOptions.LivenessEnabled) { entry = entry.Copy(); // Add the killer (myself) to the suspect list, for easier diagnosis later on. entry.AddSuspector(myAddress, time); LogDebugGoingToDeclareDead(this.log, entry.SiloAddress, entry.ToString()); entry.Status = SiloStatus.Dead; bool ok = await membershipTableProvider.UpdateRow(entry, etag, tableVersion.Next()); if (ok) { LogDebugSuccessfullyUpdatedStatusToDead(this.log, entry.SiloAddress); var table = await membershipTableProvider.ReadAll(); this.ProcessTableUpdate(table, "DeclareDead"); GossipToOthers(entry.SiloAddress, entry.Status).Ignore(); return true; } LogInformationFailedToUpdateStatusToDead(this.log, entry.SiloAddress); return false; } LogInformationLivenessDisabled(this.log, entry.SiloAddress); return true; } bool IHealthCheckable.CheckHealth(DateTime lastCheckTime, out string reason) => this.membershipUpdateTimer.CheckHealth(lastCheckTime, out reason); void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { var tasks = new List(1); lifecycle.Subscribe( nameof(MembershipTableManager), ServiceLifecycleStage.RuntimeGrainServices, OnRuntimeGrainServicesStart, OnRuntimeGrainServicesStop); async Task OnRuntimeGrainServicesStart(CancellationToken ct) { await Task.Run(() => this.Start()); tasks.Add(Task.Run(() => this.PeriodicallyRefreshMembershipTable())); } async Task OnRuntimeGrainServicesStop(CancellationToken ct) { tasks.Add(_suspectOrKillsListTask); _trySuspectOrKillChannel.Writer.TryComplete(); this.membershipUpdateTimer.Dispose(); _shutdownCts.Cancel(); // Allow some minimum time for graceful shutdown. var gracePeriod = Task.WhenAll(Task.Delay(ClusterMembershipOptions.ClusteringShutdownGracePeriod), ct.WhenCancelled()); await Task.WhenAny(gracePeriod, Task.WhenAll(tasks)).SuppressThrowing(); } } public void Dispose() { this.updates.Dispose(); this.membershipUpdateTimer.Dispose(); _shutdownCts.Dispose(); } private readonly struct WithoutDuplicateDeadsLogValue(MembershipTableData table) { public override string ToString() => table.WithoutDuplicateDeads().ToString(); } [LoggerMessage( Level = LogLevel.Information, Message = "Received cluster membership snapshot via gossip: {Snapshot}" )] private static partial void LogInformationReceivedClusterMembershipSnapshot(ILogger logger, MembershipTableSnapshot snapshot); [LoggerMessage( EventId = (int)ErrorCode.MembershipStarting, Level = LogLevel.Information, Message = "MembershipOracle starting on host {HostName} with SiloAddress {SiloAddress} at {StartTime}" )] private static partial void LogInformationMembershipStarting(ILogger logger, string hostName, SiloAddress siloAddress, string startTime); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead1, Level = LogLevel.Warning, Message = "I should be Dead according to membership table (in RefreshFromSnapshot). Local entry: {Entry}." )] private static partial void LogWarningFoundMyselfDeadInRefreshFromSnapshot(ILogger logger, string entry); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception while trying to clean up my table entries" )] private static partial void LogWarningExceptionWhileCleaningUpTableEntries(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Refreshing membership table took {Elapsed}" )] private static partial void LogTraceRefreshingMembershipTableTook(ILogger logger, TimeSpan elapsed); [LoggerMessage( EventId = (int)ErrorCode.MembershipUpdateIAmAliveFailure, Level = LogLevel.Warning, Message = "Failed to refresh membership table, will retry shortly. Retry attempt {retries}" )] private static partial void LogWarningFailedToRefreshMembershipTable(ILogger logger, Exception exception, int retries); [LoggerMessage( Level = LogLevel.Warning, Message = "Error refreshing membership table" )] private static partial void LogWarningErrorRefreshingMembershipTable(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping periodic membership table refreshes" )] private static partial void LogDebugStoppingPeriodicMembershipTableRefreshes(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting periodic membership table refreshes" )] private static partial void LogDebugStartingPeriodicMembershipTableRefreshes(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "{Caller} membership table {Table}" )] private static partial void LogDebugProcessTableUpdate(ILogger logger, string caller, MembershipTableData table); [LoggerMessage( EventId = (int)ErrorCode.MembershipReadAll_2, Level = LogLevel.Debug, Message = "{Caller} membership table: {Table}" )] private static partial void LogDebugProcessTableUpdateWithTable(ILogger logger, string caller, WithoutDuplicateDeadsLogValue table); [LoggerMessage( EventId = (int)ErrorCode.MembershipMissedIAmAliveTableUpdate, Level = LogLevel.Warning, Message = "Noticed that silo {SiloAddress} has not updated it's IAmAliveTime table column recently." + " Last update was at {LastUpdateTime}, now is {CurrentTime}, no update for {SinceUpdate}, which is more than {AllowedIAmAliveMissPeriod}." )] private static partial void LogWarningMissedIAmAliveTableUpdate(ILogger logger, SiloAddress siloAddress, DateTime lastUpdateTime, DateTime currentTime, TimeSpan sinceUpdate, TimeSpan allowedIAmAliveMissPeriod); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead2, Level = LogLevel.Warning, Message = "I should be Dead according to membership table (in CleanupTableEntries): entry = {Entry}." )] private static partial void LogWarningFoundMyselfDead2(ILogger logger, string entry); [LoggerMessage( Level = LogLevel.Trace, Message = "Skipping my previous old Dead entry in membership table: {Entry}" )] private static partial void LogTraceSkippingOldDeadEntry(ILogger logger, string entry); [LoggerMessage( Level = LogLevel.Debug, Message = "Temporal anomaly detected in membership table -- Me={SiloAddress} Other me={OtherSiloAddress}" )] private static partial void LogDebugTemporalAnomalyDetected(ILogger logger, SiloAddress siloAddress, SiloAddress otherSiloAddress); [LoggerMessage( EventId = (int)ErrorCode.MembershipDetectedOlder, Level = LogLevel.Warning, Message = "Detected older version of myself - Marking other older clone as Dead -- Current Me={LocalSiloAddress} Older Me={OlderSiloAddress}, Old entry={Entry}" )] private static partial void LogWarningDetectedOlder(ILogger logger, SiloAddress localSiloAddress, SiloAddress olderSiloAddress, string entry); [LoggerMessage( EventId = (int)ErrorCode.MembershipDetectedNewer, Level = LogLevel.Warning, Message = "Detected newer version of myself - I am the older clone so I will stop -- Current Me={LocalSiloAddress} Newer Me={NewerSiloAddress}, Current entry={Entry}" )] private static partial void LogWarningDetectedNewer(ILogger logger, SiloAddress localSiloAddress, SiloAddress newerSiloAddress, string entry); [LoggerMessage( Level = LogLevel.Debug, Message = "CleanupTableEntries: About to DeclareDead {Count} outdated silos in the table: {Silos}" )] private static partial void LogDebugCleanupTableEntriesAboutToDeclareDead(ILogger logger, int count, string silos); [LoggerMessage( EventId = (int)ErrorCode.MembershipKillMyselfLocally, Level = LogLevel.Error, Message = "I have been told I am dead, so this silo will stop! Reason: {Reason}" )] private static partial void LogErrorKillMyselfLocally(ILogger logger, string reason); [LoggerMessage( Level = LogLevel.Warning, Message = "Error while gossiping status to other silos" )] private static partial void LogWarningErrorWhileGossipingStatus(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Going to try to TryUpdateMyStatusGlobalOnce #{Attempt}" )] private static partial void LogDebugGoingToTryToUpdateMyStatusGlobalOnce(ILogger logger, int attempt); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo {SiloAddress} Successfully updated my Status in the membership table to {Status}" )] private static partial void LogDebugSuccessfullyUpdatedMyStatus(ILogger logger, SiloAddress siloAddress, SiloStatus status); [LoggerMessage( Level = LogLevel.Warning, Message = "Timed out while gossiping status to other silos after {Timeout}" )] private static partial void LogWarningTimedOutWhileGossipingStatus(ILogger logger, TimeSpan timeout); [LoggerMessage( Level = LogLevel.Debug, Message = "Timed out while gossiping status to other silos after {Timeout}" )] private static partial void LogDebugTimedOutWhileGossipingStatus(ILogger logger, TimeSpan timeout); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToWriteConditional, Level = LogLevel.Information, Message = "Silo {MyAddress} failed to update its status to {Status} in the membership table due to write contention on the table after {NumCalls} attempts." )] private static partial void LogInformationFailedToUpdateMyStatusDueToWriteContention(ILogger logger, SiloAddress myAddress, SiloStatus status, int numCalls); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToWrite, Level = LogLevel.Warning, Message = "Silo {MyAddress} failed to update its status to {Status} in the table due to failures (socket failures or table read/write failures) after {NumCalls} attempts" )] private static partial void LogWarningFailedToUpdateMyStatusDueToFailures(ILogger logger, Exception exception, SiloAddress myAddress, SiloStatus status, int numCalls); [LoggerMessage( Level = LogLevel.Debug, Message = "TryUpdateMyStatusGlobalOnce: Read{Selection} Membership table {Table}" )] private static partial void LogDebugTryUpdateMyStatusGlobalOnce(ILogger logger, string selection, string table); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead1, Level = LogLevel.Warning, Message = "I should be Dead according to membership table (in TryUpdateMyStatusGlobalOnce): Entry = {Entry}." )] private static partial void LogWarningFoundMyselfDead1(ILogger logger, string entry); [LoggerMessage( Level = LogLevel.Debug, Message = "TryKill: Read Membership table {Table}" )] private static partial void LogDebugTryKillReadMembershipTable(ILogger logger, string table); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead3, Level = LogLevel.Information, Message = "Ignoring call to TryKill for silo {Silo} since the local silo is stopping" )] private static partial void LogInformationIgnoringCallToTryKill(ILogger logger, SiloAddress silo); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead3, Level = LogLevel.Warning, Message = "{Message}" )] private static partial void LogWarningFoundMyselfDead3(ILogger logger, string message); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToReadSilo, Level = LogLevel.Error, Message = "{Message}" )] private static partial void LogErrorCouldNotFindSiloEntry(ILogger logger, string message); [LoggerMessage( EventId = (int)ErrorCode.MembershipMarkingAsDead, Level = LogLevel.Information, Message = "Going to mark silo {SiloAddress} dead as a result of a call to TryKill" )] private static partial void LogInformationMarkingSiloAsDead(ILogger logger, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "TryToSuspectOrKill: Read Membership table {Table}" )] private static partial void LogDebugTryToSuspectOrKillReadMembershipTable(ILogger logger, string table); [LoggerMessage( EventId = (int)ErrorCode.MembershipFoundMyselfDead3, Level = LogLevel.Information, Message = "Ignoring call to TrySuspectOrKill for silo {Silo} since the local silo is dead" )] private static partial void LogInformationIgnoringCallToTrySuspectOrKill(ILogger logger, SiloAddress silo); [LoggerMessage( Level = LogLevel.Debug, Message = "TryToSuspectOrKill {SiloAddress}: The current status of {SiloAddress} in the table is {Status}, its entry is {Entry}" )] private static partial void LogDebugTryToSuspectOrKillCurrentStatus(ILogger logger, SiloAddress siloAddress, SiloStatus status, string entry); [LoggerMessage( Level = LogLevel.Trace, Message = "Current number of fresh voters for '{SiloAddress}' is '{FreshVotes}'." )] private static partial void LogTraceCurrentNumberOfFreshVoters(ILogger logger, SiloAddress siloAddress, string freshVotes); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100053, Level = LogLevel.Error, Message = "Silo '{SiloAddress}' is suspected by '{SuspecterCount}' which is greater than or equal to '{NumVotesForDeathDeclaration}', but is not marked as dead. This is a bug!" )] private static partial void LogErrorSiloIsSuspectedButNotMarkedAsDead(ILogger logger, SiloAddress siloAddress, string suspecterCount, string numVotesForDeathDeclaration); [LoggerMessage( EventId = (int)ErrorCode.MembershipMarkingAsDead, Level = LogLevel.Information, Message = "Evicting '{SiloAddress}'. Fresh vote count: '{FreshVotes}', votes required to evict: '{NumVotesRequiredToEvict}', non-stale silo count: '{NonStaleSiloCount}', suspecters: '{SuspectingSilos}'" )] private static partial void LogInformationEvictingSilo(ILogger logger, SiloAddress siloAddress, int freshVotes, int numVotesRequiredToEvict, int nonStaleSiloCount, string suspectingSilos); [LoggerMessage( EventId = (int)ErrorCode.MembershipVotingForKill, Level = LogLevel.Information, Message = "Voting to evict '{SiloAddress}'. Previous suspect list is '{PreviousSuspecters}', trying to update to '{Suspecters}', ETag: '{ETag}', Fresh vote count: '{FreshVotes}'" )] private static partial void LogInformationVotingToEvictSilo(ILogger logger, SiloAddress siloAddress, string previousSuspecters, string suspecters, string eTag, string freshVotes); [LoggerMessage( Level = LogLevel.Debug, Message = "Going to DeclareDead silo {SiloAddress} in the table. About to write entry {Entry}." )] private static partial void LogDebugGoingToDeclareDead(ILogger logger, SiloAddress siloAddress, string entry); [LoggerMessage( Level = LogLevel.Debug, Message = "Successfully updated {SiloAddress} status to Dead in the membership table." )] private static partial void LogDebugSuccessfullyUpdatedStatusToDead(ILogger logger, SiloAddress siloAddress); [LoggerMessage( EventId = (int)ErrorCode.MembershipMarkDeadWriteFailed, Level = LogLevel.Information, Message = "Failed to update {SiloAddress} status to Dead in the membership table, due to write conflicts. Will retry." )] private static partial void LogInformationFailedToUpdateStatusToDead(ILogger logger, SiloAddress siloAddress); [LoggerMessage( EventId = (int)ErrorCode.MembershipCantWriteLivenessDisabled, Level = LogLevel.Information, Message = "Want to mark silo {SiloAddress} as DEAD, but will ignore because Liveness is Disabled." )] private static partial void LogInformationLivenessDisabled(ILogger logger, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Error, Message = "Error while processing suspect or kill lists. '{FailureCount}' consecutive failures." )] private static partial void LogErrorProcessingSuspectOrKillLists(ILogger logger, Exception exception, int failureCount); [LoggerMessage( EventId = (int)ErrorCode.MembershipNodeMigrated, Level = LogLevel.Warning, Message = "Silo {SiloName} migrated to host {HostName} silo address {SiloAddress} from host {PreviousHostName} silo address {PreviousSiloAddress}." )] private static partial void LogWarningNodeMigrated(ILogger logger, string siloName, string hostName, SiloAddress siloAddress, string previousHostName, SiloAddress previousSiloAddress); [LoggerMessage( EventId = (int)ErrorCode.MembershipNodeRestarted, Level = LogLevel.Warning, Message = "Silo {SiloName} restarted on same host {HostName} with silo address = {SiloAddress} Previous silo address = {PreviousSiloAddress}" )] private static partial void LogWarningNodeRestarted(ILogger logger, string siloName, string hostName, SiloAddress siloAddress, SiloAddress previousSiloAddress); [LoggerMessage( EventId = (int)ErrorCode.MembershipFailedToStart, Level = LogLevel.Error, Message = "Membership failed to start" )] private static partial void LogErrorMembershipFailedToStart(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/MembershipTableSnapshotExtensions.cs ================================================ using System.Collections.Immutable; namespace Orleans.Runtime.MembershipService { internal static class MembershipTableSnapshotExtensions { internal static ClusterMembershipSnapshot CreateClusterMembershipSnapshot(this MembershipTableSnapshot membership) { var memberBuilder = ImmutableDictionary.CreateBuilder(); foreach (var member in membership.Entries) { var entry = member.Value; memberBuilder[entry.SiloAddress] = new ClusterMember(entry.SiloAddress, entry.Status, entry.SiloName); } return new ClusterMembershipSnapshot(memberBuilder.ToImmutable(), membership.Version); } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/OrleansClusterConnectivityCheckFailedException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime.MembershipService { /// /// Exception used to indicate that a cluster connectivity check failed. /// /// [Serializable] [GenerateSerializer] public sealed class OrleansClusterConnectivityCheckFailedException : OrleansException { /// /// Initializes a new instance of the class. /// public OrleansClusterConnectivityCheckFailedException() : base("Failed to verify connectivity with active cluster nodes.") { } /// /// Initializes a new instance of the class. /// /// The message. public OrleansClusterConnectivityCheckFailedException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public OrleansClusterConnectivityCheckFailedException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private OrleansClusterConnectivityCheckFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/OrleansMissingMembershipEntryException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Runtime.MembershipService { /// /// Exception used to indicate that a cluster membership entry which was expected to be present. /// /// [Serializable] [GenerateSerializer] public sealed class OrleansMissingMembershipEntryException : OrleansException { /// /// Initializes a new instance of the class. /// public OrleansMissingMembershipEntryException() : base("Membership table does not contain information an entry for this silo.") { } /// /// Initializes a new instance of the class. /// /// The message. public OrleansMissingMembershipEntryException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public OrleansMissingMembershipEntryException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private OrleansMissingMembershipEntryException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Runtime/MembershipService/RemoteSiloProber.cs ================================================ #nullable enable using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime.MembershipService; /// internal class RemoteSiloProber(IServiceProvider serviceProvider) : IRemoteSiloProber { /// public async Task Probe(SiloAddress remoteSilo, int probeNumber, CancellationToken cancellationToken) { var systemTarget = serviceProvider.GetRequiredService(); await systemTarget.ProbeRemoteSilo(remoteSilo, probeNumber).WaitAsync(cancellationToken); } /// public async Task ProbeIndirectly(SiloAddress intermediary, SiloAddress target, TimeSpan probeTimeout, int probeNumber, CancellationToken cancellationToken) { var systemTarget = serviceProvider.GetRequiredService(); return await systemTarget.ProbeRemoteSiloIndirectly(intermediary, target, probeTimeout, probeNumber).WaitAsync(cancellationToken); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloHealthMonitor.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Internal; using static Orleans.Runtime.MembershipService.SiloHealthMonitor; namespace Orleans.Runtime.MembershipService { /// /// Responsible for monitoring an individual remote silo. /// internal partial class SiloHealthMonitor : ITestAccessor, IHealthCheckable, IDisposable, IAsyncDisposable { private readonly ILogger _log; private readonly IOptionsMonitor _clusterMembershipOptions; private readonly IRemoteSiloProber _prober; private readonly ILocalSiloHealthMonitor _localSiloHealthMonitor; private readonly MembershipTableManager _membershipService; private readonly ILocalSiloDetails _localSiloDetails; private readonly CancellationTokenSource _stoppingCancellation = new(); #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly IAsyncTimer _pingTimer; private ValueStopwatch _elapsedSinceLastSuccessfulResponse; private readonly Func _onProbeResult; private Task? _runTask; /// /// The id of the next probe. /// private int _nextProbeId; /// /// The number of failed probes since the last successful probe. /// private int _failedProbes; /// /// The time since the last ping response was received from either the node being monitored or an intermediary. /// public TimeSpan? ElapsedSinceLastResponse => _elapsedSinceLastSuccessfulResponse.IsRunning ? (TimeSpan?)_elapsedSinceLastSuccessfulResponse.Elapsed : null; /// /// The duration of time measured from just prior to sending the last probe which received a response until just after receiving and processing the response. /// public TimeSpan LastRoundTripTime { get; private set; } public SiloHealthMonitor( SiloAddress siloAddress, Func onProbeResult, IOptionsMonitor clusterMembershipOptions, ILoggerFactory loggerFactory, IRemoteSiloProber remoteSiloProber, IAsyncTimerFactory asyncTimerFactory, ILocalSiloHealthMonitor localSiloHealthMonitor, MembershipTableManager membershipService, ILocalSiloDetails localSiloDetails) { TargetSiloAddress = siloAddress; _clusterMembershipOptions = clusterMembershipOptions; _prober = remoteSiloProber; _localSiloHealthMonitor = localSiloHealthMonitor; _membershipService = membershipService; _localSiloDetails = localSiloDetails; _log = loggerFactory.CreateLogger(); _pingTimer = asyncTimerFactory.Create( _clusterMembershipOptions.CurrentValue.ProbeTimeout, nameof(SiloHealthMonitor)); _onProbeResult = onProbeResult; _elapsedSinceLastSuccessfulResponse = ValueStopwatch.StartNew(); } internal interface ITestAccessor { int MissedProbes { get; } } /// /// The silo which this instance is responsible for. /// public SiloAddress TargetSiloAddress { get; } /// /// Whether or not this monitor is canceled. /// public bool IsCanceled => _stoppingCancellation.IsCancellationRequested; int ITestAccessor.MissedProbes => _failedProbes; /// /// Start the monitor. /// public void Start() { using var suppressExecutionContext = new ExecutionContextSuppressor(); lock (_lockObj) { if (_stoppingCancellation.IsCancellationRequested) { throw new InvalidOperationException("This instance has already been stopped and cannot be started again"); } if (_runTask is not null) { throw new InvalidOperationException("This instance has already been started"); } _runTask = Task.Run(Run); } } /// /// Stop the monitor. /// public async Task StopAsync(CancellationToken cancellationToken) { Dispose(); if (_runTask is Task task) { await task.WaitAsync(cancellationToken).SuppressThrowing(); } } public void Dispose() { lock (_lockObj) { if (_stoppingCancellation.IsCancellationRequested) { return; } _stoppingCancellation.Cancel(); _pingTimer.Dispose(); } } public async ValueTask DisposeAsync() { Dispose(); if (_runTask is Task task) { await task.SuppressThrowing(); } } private async Task Run() { MembershipTableSnapshot? activeMembersSnapshot = default; SiloAddress[]? otherNodes = default; var options = _clusterMembershipOptions.CurrentValue; TimeSpan? overrideDelay = RandomTimeSpan.Next(options.ProbeTimeout); while (await _pingTimer.NextTick(overrideDelay)) { ProbeResult probeResult; overrideDelay = default; var now = DateTime.UtcNow; try { // Discover the other active nodes in the cluster, if there are any. var membershipSnapshot = _membershipService.MembershipTableSnapshot; if (otherNodes is null || !ReferenceEquals(activeMembersSnapshot, membershipSnapshot)) { activeMembersSnapshot = membershipSnapshot; otherNodes = membershipSnapshot.Entries.Values .Where(v => v.Status == SiloStatus.Active && !v.SiloAddress.Equals(TargetSiloAddress) && !v.SiloAddress.Equals(_localSiloDetails.SiloAddress) && !v.HasMissedIAmAlives(options, now)) .Select(s => s.SiloAddress) .ToArray(); } var isDirectProbe = !options.EnableIndirectProbes || _failedProbes < options.NumMissedProbesLimit - 1 || otherNodes.Length == 0; var timeout = GetTimeout(isDirectProbe); using var cancellation = new CancellationTokenSource(timeout); if (isDirectProbe) { // Probe the silo directly. probeResult = await this.ProbeDirectly(cancellation.Token).ConfigureAwait(false); } else { // Pick a random other node and probe the target indirectly, using the selected node as an intermediary. var intermediary = otherNodes[Random.Shared.Next(otherNodes.Length)]; // Select a timeout which will allow the intermediary node to attempt to probe the target node and still respond to this node // if the remote node does not respond in time. // Attempt to account for local health degradation by extending the timeout period. probeResult = await this.ProbeIndirectly(intermediary, timeout, cancellation.Token).ConfigureAwait(false); // If the intermediary is not entirely healthy, remove it from consideration and continue to probe. // Note that all recused silos will be included in the consideration set the next time cluster membership changes. if (probeResult.Status != ProbeResultStatus.Succeeded && probeResult.IntermediaryHealthDegradationScore > 0) { LogInformationRecusingUnhealthyIntermediary(_log, intermediary); otherNodes = [.. otherNodes.Where(node => !node.Equals(intermediary))]; overrideDelay = TimeSpan.FromMilliseconds(250); } } if (!_stoppingCancellation.IsCancellationRequested) { await _onProbeResult(this, probeResult).ConfigureAwait(false); } } catch (Exception exception) { LogErrorExceptionMonitoringSilo(_log, exception, TargetSiloAddress); } } TimeSpan GetTimeout(bool isDirectProbe) { var additionalTimeout = 0; if (options.ExtendProbeTimeoutDuringDegradation) { // Attempt to account for local health degradation by extending the timeout period. var localDegradationScore = _localSiloHealthMonitor.GetLocalHealthDegradationScore(DateTime.UtcNow); additionalTimeout += localDegradationScore; } if (!isDirectProbe) { // Indirect probes need extra time to account for the additional hop. additionalTimeout += 1; } // When the debugger is attached, extend probe times so that silos are not terminated // due to debugging pauses. if (Debugger.IsAttached) { additionalTimeout += 25; } return options.ProbeTimeout.Multiply(1 + additionalTimeout); } } /// /// Probes the remote silo. /// /// A token to cancel and fail the probe attempt. /// The number of failed probes since the last successful probe. private async Task ProbeDirectly(CancellationToken cancellation) { var id = ++_nextProbeId; LogTraceGoingToSendPing(_log, id, TargetSiloAddress); var roundTripTimer = ValueStopwatch.StartNew(); ProbeResult probeResult; Exception? failureException; try { await _prober.Probe(TargetSiloAddress, id, cancellation).WaitAsync(cancellation); failureException = null; } catch (OperationCanceledException exception) { failureException = new OperationCanceledException($"The ping attempt was cancelled after {roundTripTimer.Elapsed}. Ping #{id}", exception); } catch (Exception exception) { failureException = exception; } finally { roundTripTimer.Stop(); } if (failureException is null) { MessagingInstruments.OnPingReplyReceived(TargetSiloAddress); LogTraceGotSuccessfulPingResponse(_log, id, TargetSiloAddress, roundTripTimer.Elapsed); _failedProbes = 0; _elapsedSinceLastSuccessfulResponse.Restart(); LastRoundTripTime = roundTripTimer.Elapsed; probeResult = ProbeResult.CreateDirect(0, ProbeResultStatus.Succeeded); } else { MessagingInstruments.OnPingReplyMissed(TargetSiloAddress); var failedProbes = ++_failedProbes; LogWarningDidNotGetResponseForProbe(_log, failureException, id, TargetSiloAddress, roundTripTimer.Elapsed, failedProbes); probeResult = ProbeResult.CreateDirect(failedProbes, ProbeResultStatus.Failed); } return probeResult; } /// /// Probes the remote node via an intermediary silo. /// /// The node to probe the target with. /// The amount of time which the intermediary should allow for the target to respond. /// A token to cancel and fail the probe attempt. /// The number of failed probes since the last successful probe. private async Task ProbeIndirectly(SiloAddress intermediary, TimeSpan directProbeTimeout, CancellationToken cancellation) { var id = ++_nextProbeId; LogTraceGoingToSendIndirectPing(_log, id, TargetSiloAddress, intermediary); var roundTripTimer = ValueStopwatch.StartNew(); ProbeResult probeResult; try { using var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellation, _stoppingCancellation.Token); var indirectResult = await _prober.ProbeIndirectly(intermediary, TargetSiloAddress, directProbeTimeout, id, cancellationSource.Token).WaitAsync(cancellationSource.Token); roundTripTimer.Stop(); var roundTripTime = roundTripTimer.Elapsed - indirectResult.ProbeResponseTime; // Record timing regardless of the result. _elapsedSinceLastSuccessfulResponse.Restart(); LastRoundTripTime = roundTripTime; if (indirectResult.Succeeded) { LogInformationIndirectProbeSucceeded(_log, id, TargetSiloAddress, intermediary, roundTripTimer.Elapsed, indirectResult.ProbeResponseTime); MessagingInstruments.OnPingReplyReceived(TargetSiloAddress); _failedProbes = 0; probeResult = ProbeResult.CreateIndirect(0, ProbeResultStatus.Succeeded, indirectResult, intermediary); } else { MessagingInstruments.OnPingReplyMissed(TargetSiloAddress); if (indirectResult.IntermediaryHealthScore > 0) { LogInformationIgnoringFailureResultForPing(_log, id, TargetSiloAddress, indirectResult.IntermediaryHealthScore); probeResult = ProbeResult.CreateIndirect(_failedProbes, ProbeResultStatus.Unknown, indirectResult, intermediary); } else { LogWarningIndirectProbeFailed(_log, id, TargetSiloAddress, intermediary, roundTripTimer.Elapsed, indirectResult.ProbeResponseTime, indirectResult.FailureMessage, indirectResult.IntermediaryHealthScore); var missed = ++_failedProbes; probeResult = ProbeResult.CreateIndirect(missed, ProbeResultStatus.Failed, indirectResult, intermediary); } } } catch (Exception exception) { MessagingInstruments.OnPingReplyMissed(TargetSiloAddress); LogWarningIndirectProbeRequestFailed(_log, exception); probeResult = ProbeResult.CreateIndirect(_failedProbes, ProbeResultStatus.Unknown, default, intermediary); } return probeResult; } /// public bool CheckHealth(DateTime lastCheckTime, out string reason) => _pingTimer.CheckHealth(lastCheckTime, out reason); /// /// Represents the result of probing a silo. /// [StructLayout(LayoutKind.Auto)] public readonly struct ProbeResult { private ProbeResult(int failedProbeCount, ProbeResultStatus status, bool isDirectProbe, int intermediaryHealthDegradationScore, SiloAddress? intermediary) { FailedProbeCount = failedProbeCount; Status = status; IsDirectProbe = isDirectProbe; IntermediaryHealthDegradationScore = intermediaryHealthDegradationScore; Intermediary = intermediary; } public static ProbeResult CreateDirect(int failedProbeCount, ProbeResultStatus status) => new(failedProbeCount, status, isDirectProbe: true, 0, null); public static ProbeResult CreateIndirect(int failedProbeCount, ProbeResultStatus status, IndirectProbeResponse indirectProbeResponse, SiloAddress? intermediary) => new(failedProbeCount, status, isDirectProbe: false, indirectProbeResponse.IntermediaryHealthScore, intermediary); public int FailedProbeCount { get; } public ProbeResultStatus Status { get; } public bool IsDirectProbe { get; } public int IntermediaryHealthDegradationScore { get; } public SiloAddress? Intermediary { get; } } public enum ProbeResultStatus { Unknown, Failed, Succeeded } [LoggerMessage( Level = LogLevel.Trace, Message = "Going to send Ping #{Id} to probe silo {Silo}" )] private static partial void LogTraceGoingToSendPing(ILogger logger, int id, SiloAddress silo); [LoggerMessage( Level = LogLevel.Trace, Message = "Got successful ping response for ping #{Id} from {Silo} with round trip time of {RoundTripTime}" )] private static partial void LogTraceGotSuccessfulPingResponse(ILogger logger, int id, SiloAddress silo, TimeSpan roundTripTime); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.MembershipMissedPing, Message = "Did not get response for probe #{Id} to silo {Silo} after {Elapsed}. Current number of consecutive failed probes is {FailedProbeCount}" )] private static partial void LogWarningDidNotGetResponseForProbe(ILogger logger, Exception exception, int id, SiloAddress silo, TimeSpan elapsed, int failedProbeCount); [LoggerMessage( Level = LogLevel.Trace, Message = "Going to send indirect ping #{Id} to probe silo {Silo} via {Intermediary}" )] private static partial void LogTraceGoingToSendIndirectPing(ILogger logger, int id, SiloAddress silo, SiloAddress intermediary); [LoggerMessage( Level = LogLevel.Information, Message = "Indirect probe request #{Id} to silo {SiloAddress} via silo {IntermediarySiloAddress} succeeded after {RoundTripTime} with a direct probe response time of {ProbeResponseTime}." )] private static partial void LogInformationIndirectProbeSucceeded(ILogger logger, int id, SiloAddress siloAddress, SiloAddress intermediarySiloAddress, TimeSpan roundTripTime, TimeSpan probeResponseTime); [LoggerMessage( Level = LogLevel.Information, Message = "Ignoring failure result for ping #{Id} from {Silo} since the intermediary used to probe the silo is not healthy. Intermediary health degradation score: {IntermediaryHealthScore}." )] private static partial void LogInformationIgnoringFailureResultForPing(ILogger logger, int id, SiloAddress silo, int intermediaryHealthScore); [LoggerMessage( Level = LogLevel.Warning, Message = "Indirect probe request #{Id} to silo {SiloAddress} via silo {IntermediarySiloAddress} failed after {RoundTripTime} with a direct probe response time of {ProbeResponseTime}. Failure message: {FailureMessage}. Intermediary health score: {IntermediaryHealthScore}." )] private static partial void LogWarningIndirectProbeFailed(ILogger logger, int id, SiloAddress siloAddress, SiloAddress intermediarySiloAddress, TimeSpan roundTripTime, TimeSpan probeResponseTime, string failureMessage, int intermediaryHealthScore); [LoggerMessage( Level = LogLevel.Warning, Message = "Indirect probe request failed." )] private static partial void LogWarningIndirectProbeRequestFailed(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "Recusing unhealthy intermediary '{Intermediary}' and trying again with remaining nodes" )] private static partial void LogInformationRecusingUnhealthyIntermediary(ILogger logger, SiloAddress intermediary); [LoggerMessage( Level = LogLevel.Error, Message = "Exception monitoring silo {SiloAddress}" )] private static partial void LogErrorExceptionMonitoringSilo(ILogger logger, Exception exception, SiloAddress siloAddress); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/ISiloMetadataCache.cs ================================================ #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; public interface ISiloMetadataCache { SiloMetadata GetSiloMetadata(SiloAddress siloAddress); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/ISiloMetadataClient.cs ================================================ using System.Threading.Tasks; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; internal interface ISiloMetadataClient { Task GetSiloMetadata(SiloAddress siloAddress); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/ISiloMetadataSystemTarget.cs ================================================ using System.Threading.Tasks; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; [Alias("Orleans.Runtime.MembershipService.SiloMetadata.ISiloMetadataSystemTarget")] internal interface ISiloMetadataSystemTarget : ISystemTarget { [Alias("GetSiloMetadata")] Task GetSiloMetadata(); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/SiloMetadaCache.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; internal partial class SiloMetadataCache( ISiloMetadataClient siloMetadataClient, MembershipTableManager membershipTableManager, IOptions clusterMembershipOptions, ILogger logger) : ISiloMetadataCache, ILifecycleParticipant, IDisposable { private readonly ConcurrentDictionary _metadata = new(); private readonly Dictionary _negativeCache = new(); private readonly CancellationTokenSource _cts = new(); private TimeSpan negativeCachePeriod; void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { Task? task = null; Task OnStart(CancellationToken _) { // This gives time for the cluster to be voted Dead and for membership updates to propagate that out negativeCachePeriod = clusterMembershipOptions.Value.ProbeTimeout * clusterMembershipOptions.Value.NumMissedProbesLimit + (2 * clusterMembershipOptions.Value.TableRefreshTimeout); task = Task.Run(() => this.ProcessMembershipUpdates(_cts.Token)); return Task.CompletedTask; } async Task OnStop(CancellationToken ct) { await _cts.CancelAsync().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); if (task is not null) { await task.WaitAsync(ct).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } } lifecycle.Subscribe( nameof(ClusterMembershipService), ServiceLifecycleStage.RuntimeServices, OnStart, OnStop); } private async Task ProcessMembershipUpdates(CancellationToken ct) { try { LogDebugStartProcessingMembershipUpdates(logger); await foreach (var update in membershipTableManager.MembershipTableUpdates.WithCancellation(ct)) { // Add entries for members that aren't already in the cache var now = DateTime.UtcNow; var recentlyActiveSilos = update.Entries .Where(e => e.Value.Status is SiloStatus.Active or SiloStatus.Joining) .Where(e => !e.Value.HasMissedIAmAlives(clusterMembershipOptions.Value, now)); foreach (var membershipEntry in recentlyActiveSilos) { if (!_metadata.ContainsKey(membershipEntry.Key)) { if (_negativeCache.TryGetValue(membershipEntry.Key, out var expiration) && expiration > now) { continue; } try { var metadata = await siloMetadataClient.GetSiloMetadata(membershipEntry.Key).WaitAsync(ct); _metadata.TryAdd(membershipEntry.Key, metadata); _negativeCache.Remove(membershipEntry.Key, out _); } catch(Exception exception) { _negativeCache.TryAdd(membershipEntry.Key, now + negativeCachePeriod); LogErrorFetchingSiloMetadata(logger, exception, membershipEntry.Key); } } } // Remove entries for members that are now dead var deadSilos = update.Entries .Where(e => e.Value.Status == SiloStatus.Dead); foreach (var membershipEntry in deadSilos) { _metadata.TryRemove(membershipEntry.Key, out _); _negativeCache.Remove(membershipEntry.Key, out _); } // Remove entries for members that are no longer in the table foreach (var silo in _metadata.Keys.ToList()) { if (!update.Entries.ContainsKey(silo)) { _metadata.TryRemove(silo, out _); _negativeCache.Remove(silo, out _); } } } } catch (OperationCanceledException) when (ct.IsCancellationRequested) { // Ignore and continue shutting down. } catch (Exception exception) { LogErrorProcessingMembershipUpdates(logger, exception); } finally { LogDebugStoppingMembershipProcessor(logger); } } public SiloMetadata GetSiloMetadata(SiloAddress siloAddress) => _metadata.GetValueOrDefault(siloAddress) ?? SiloMetadata.Empty; public void SetMetadata(SiloAddress siloAddress, SiloMetadata metadata) => _metadata.TryAdd(siloAddress, metadata); public void Dispose() => _cts.Cancel(); [LoggerMessage( Level = LogLevel.Debug, Message = "Starting to process membership updates.")] private static partial void LogDebugStartProcessingMembershipUpdates(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Error fetching metadata for silo {Silo}")] private static partial void LogErrorFetchingSiloMetadata(ILogger logger, Exception exception, SiloAddress silo); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing membership updates")] private static partial void LogErrorProcessingMembershipUpdates(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping membership update processor")] private static partial void LogDebugStoppingMembershipProcessor(ILogger logger); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/SiloMetadata.cs ================================================ using System.Collections.Generic; using System.Collections.Immutable; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; [GenerateSerializer] [Alias("Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata")] public record SiloMetadata { public static SiloMetadata Empty { get; } = new SiloMetadata(); [Id(0)] public ImmutableDictionary Metadata { get; private set; } = ImmutableDictionary.Empty; internal void AddMetadata(IEnumerable> metadata) => Metadata = Metadata.SetItems(metadata); internal void AddMetadata(string key, string value) => Metadata = Metadata.SetItem(key, value); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/SiloMetadataClient.cs ================================================ using System.Threading.Tasks; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; internal sealed class SiloMetadataClient(IInternalGrainFactory grainFactory) : ISiloMetadataClient { public async Task GetSiloMetadata(SiloAddress siloAddress) { var metadataSystemTarget = grainFactory.GetSystemTarget(Constants.SiloMetadataType, siloAddress); var metadata = await metadataSystemTarget.GetSiloMetadata(); return metadata; } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/SiloMetadataHostingExtensions.cs ================================================ using System.Collections.Generic; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration.Internal; using Orleans.Hosting; using Orleans.Placement; using Orleans.Runtime.Placement.Filtering; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; public static class SiloMetadataHostingExtensions { /// /// Configure silo metadata from the builder configuration. /// /// Silo builder /// /// Get the ORLEANS__METADATA section from config /// Key/value pairs in configuration as a will look like this as environment variables: /// ORLEANS__METADATA__key1=value1 /// /// public static ISiloBuilder UseSiloMetadata(this ISiloBuilder builder) => builder.UseSiloMetadata(builder.Configuration); /// /// Configure silo metadata from configuration. /// /// Silo builder /// Configuration to pull from /// /// Get the ORLEANS__METADATA section from config /// Key/value pairs in configuration as a will look like this as environment variables: /// ORLEANS__METADATA__key1=value1 /// /// public static ISiloBuilder UseSiloMetadata(this ISiloBuilder builder, IConfiguration configuration) { var metadataConfigSection = configuration.GetSection("Orleans").GetSection("Metadata"); return builder.UseSiloMetadata(metadataConfigSection); } /// /// Configure silo metadata from configuration section. /// /// Silo builder /// Configuration section to pull from /// /// Get the ORLEANS__METADATA section from config section /// Key/value pairs in configuration as a will look like this as environment variables: /// ORLEANS__METADATA__key1=value1 /// /// public static ISiloBuilder UseSiloMetadata(this ISiloBuilder builder, IConfigurationSection configurationSection) { var dictionary = configurationSection.Get>(); return builder.UseSiloMetadata(dictionary ?? []); } /// /// Configure silo metadata from configuration section. /// /// Silo builder /// Metadata to add /// public static ISiloBuilder UseSiloMetadata(this ISiloBuilder builder, Dictionary metadata) { builder.ConfigureServices(services => { services .AddOptionsWithValidateOnStart() .Configure(m => m.AddMetadata(metadata)); services.AddSingleton(); services.AddFromExisting, SiloMetadataSystemTarget>(); services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting, SiloMetadataCache>(); services.AddSingleton(); // Placement filters services.AddPlacementFilter(ServiceLifetime.Transient); services.AddPlacementFilter(ServiceLifetime.Transient); }); return builder; } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloMetadata/SiloMetadataSystemTarget.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; #nullable enable namespace Orleans.Runtime.MembershipService.SiloMetadata; internal sealed class SiloMetadataSystemTarget : SystemTarget, ISiloMetadataSystemTarget, ILifecycleParticipant { private readonly SiloMetadata _siloMetadata; public SiloMetadataSystemTarget( IOptions siloMetadata, SystemTargetShared shared) : base(Constants.SiloMetadataType, shared) { _siloMetadata = siloMetadata.Value; shared.ActivationDirectory.RecordNewTarget(this); } public Task GetSiloMetadata() => Task.FromResult(_siloMetadata); void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { // We don't participate in any lifecycle stages: activating this instance is all that is necessary. } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloStatusListenerManager.cs ================================================ #nullable enable using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using System.Threading.Tasks; using System.Threading; using System.Collections.Immutable; using Orleans.Internal; namespace Orleans.Runtime.MembershipService; /// /// Manages instances. /// internal partial class SiloStatusListenerManager : ILifecycleParticipant { #if NET9_0_OR_GREATER private readonly Lock _listenersLock = new(); #else private readonly object _listenersLock = new(); #endif private readonly CancellationTokenSource _cancellation = new(); private readonly MembershipTableManager _membershipTableManager; private readonly ILogger _logger; private readonly IFatalErrorHandler _fatalErrorHandler; private ImmutableList> _listeners = []; public SiloStatusListenerManager( MembershipTableManager membershipTableManager, ILogger log, IFatalErrorHandler fatalErrorHandler) { _membershipTableManager = membershipTableManager; _logger = log; _fatalErrorHandler = fatalErrorHandler; } public bool Subscribe(ISiloStatusListener listener) { lock (_listenersLock) { foreach (var reference in _listeners) { if (!reference.TryGetTarget(out var existing)) { continue; } if (ReferenceEquals(existing, listener)) return false; } _listeners = _listeners.Add(new WeakReference(listener)); return true; } } public bool Unsubscribe(ISiloStatusListener listener) { lock (_listenersLock) { for (var i = 0; i < _listeners.Count; i++) { if (!_listeners[i].TryGetTarget(out var existing)) { continue; } if (ReferenceEquals(existing, listener)) { _listeners = _listeners.RemoveAt(i); return true; } } return false; } } private async Task ProcessMembershipUpdates() { ClusterMembershipSnapshot? previous = default; try { LogDebugStartingToProcessMembershipUpdates(); await foreach (var tableSnapshot in _membershipTableManager.MembershipTableUpdates.WithCancellation(_cancellation.Token)) { var snapshot = tableSnapshot.CreateClusterMembershipSnapshot(); var update = (previous is null || snapshot.Version == MembershipVersion.MinValue) ? snapshot.AsUpdate() : snapshot.CreateUpdate(previous); NotifyObservers(update); previous = snapshot; } } catch (OperationCanceledException) when (_cancellation.IsCancellationRequested) { // Ignore and continue shutting down. } catch (Exception exception) when (_fatalErrorHandler.IsUnexpected(exception)) { LogErrorProcessingMembershipUpdates(exception); _fatalErrorHandler.OnFatalException(this, nameof(ProcessMembershipUpdates), exception); } finally { LogDebugStoppingMembershipUpdateProcessor(); } } private void NotifyObservers(ClusterMembershipUpdate update) { if (!update.HasChanges) return; List>? toRemove = null; var subscribers = _listeners; foreach (var change in update.Changes) { for (var i = 0; i < subscribers.Count; ++i) { if (!subscribers[i].TryGetTarget(out var listener)) { if (toRemove is null) toRemove = new List>(); toRemove.Add(subscribers[i]); continue; } try { listener.SiloStatusChangeNotification(change.SiloAddress, change.Status); } catch (Exception exception) { LogErrorCallingSiloStatusChangeNotification(exception, listener); } } } if (toRemove != null) { lock (_listenersLock) { var builder = _listeners.ToBuilder(); foreach (var entry in toRemove) builder.Remove(entry); _listeners = builder.ToImmutable(); } } } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { Task? task = null; lifecycle.Subscribe(nameof(SiloStatusListenerManager), ServiceLifecycleStage.AfterRuntimeGrainServices, OnStart, _ => Task.CompletedTask); lifecycle.Subscribe(nameof(SiloStatusListenerManager), ServiceLifecycleStage.RuntimeInitialize, _ => Task.CompletedTask, OnStop); Task OnStart(CancellationToken ct) { task = Task.Run(ProcessMembershipUpdates); return Task.CompletedTask; } async Task OnStop(CancellationToken ct) { _cancellation.Cancel(throwOnFirstException: false); if (task is not null) { await task.WaitAsync(ct).SuppressThrowing(); } } } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting to process membership updates." )] private partial void LogDebugStartingToProcessMembershipUpdates(); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing membership updates." )] private partial void LogErrorProcessingMembershipUpdates(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Stopping membership update processor." )] private partial void LogDebugStoppingMembershipUpdateProcessor(); [LoggerMessage( Level = LogLevel.Error, Message = "Exception while calling " + nameof(ISiloStatusListener.SiloStatusChangeNotification) + " on listener '{Listener}'." )] private partial void LogErrorCallingSiloStatusChangeNotification(Exception exception, ISiloStatusListener listener); } ================================================ FILE: src/Orleans.Runtime/MembershipService/SiloStatusOracle.cs ================================================ using System.Collections.Generic; using System.Threading; using Microsoft.Extensions.Logging; using System.Collections.Immutable; namespace Orleans.Runtime.MembershipService { internal partial class SiloStatusOracle : ISiloStatusOracle { private readonly ILocalSiloDetails localSiloDetails; private readonly MembershipTableManager membershipTableManager; private readonly SiloStatusListenerManager listenerManager; private readonly ILogger log; #if NET9_0_OR_GREATER private readonly Lock cacheUpdateLock = new(); #else private readonly object cacheUpdateLock = new(); #endif private MembershipTableSnapshot cachedSnapshot; private Dictionary siloStatusCache = new Dictionary(); private Dictionary siloStatusCacheOnlyActive = new Dictionary(); private ImmutableArray _activeSilos = []; public SiloStatusOracle( ILocalSiloDetails localSiloDetails, MembershipTableManager membershipTableManager, ILogger logger, SiloStatusListenerManager listenerManager) { this.localSiloDetails = localSiloDetails; this.membershipTableManager = membershipTableManager; this.listenerManager = listenerManager; this.log = logger; } public SiloStatus CurrentStatus => this.membershipTableManager.CurrentStatus; public string SiloName => this.localSiloDetails.Name; public SiloAddress SiloAddress => this.localSiloDetails.SiloAddress; public SiloStatus GetApproximateSiloStatus(SiloAddress silo) { var status = this.membershipTableManager.MembershipTableSnapshot.GetSiloStatus(silo); if (status == SiloStatus.None) { if (this.CurrentStatus == SiloStatus.Active) { LogSiloAddressNotRegistered(this.log, silo); } } return status; } public ImmutableArray GetActiveSilos() { EnsureFreshCache(); return _activeSilos; } public Dictionary GetApproximateSiloStatuses(bool onlyActive = false) { EnsureFreshCache(); return onlyActive ? this.siloStatusCacheOnlyActive : this.siloStatusCache; } private void EnsureFreshCache() { var currentMembership = this.membershipTableManager.MembershipTableSnapshot; if (ReferenceEquals(this.cachedSnapshot, currentMembership)) { return; } lock (this.cacheUpdateLock) { currentMembership = this.membershipTableManager.MembershipTableSnapshot; if (ReferenceEquals(this.cachedSnapshot, currentMembership)) { return; } var newSiloStatusCache = new Dictionary(); var newSiloStatusCacheOnlyActive = new Dictionary(); var newActiveSilos = ImmutableArray.CreateBuilder(); foreach (var entry in currentMembership.Entries) { var silo = entry.Key; var status = entry.Value.Status; newSiloStatusCache[silo] = status; if (status == SiloStatus.Active) { newSiloStatusCacheOnlyActive[silo] = status; newActiveSilos.Add(silo); } } Interlocked.Exchange(ref this.cachedSnapshot, currentMembership); this.siloStatusCache = newSiloStatusCache; this.siloStatusCacheOnlyActive = newSiloStatusCacheOnlyActive; _activeSilos = newActiveSilos.ToImmutable(); } } public bool IsDeadSilo(SiloAddress silo) { if (silo.Equals(this.SiloAddress)) return false; var status = this.GetApproximateSiloStatus(silo); return status == SiloStatus.Dead; } public bool IsFunctionalDirectory(SiloAddress silo) { if (silo.Equals(this.SiloAddress)) return true; var status = this.GetApproximateSiloStatus(silo); return !status.IsTerminating(); } public bool TryGetSiloName(SiloAddress siloAddress, out string siloName) { var snapshot = this.membershipTableManager.MembershipTableSnapshot.Entries; if (snapshot.TryGetValue(siloAddress, out var entry)) { siloName = entry.SiloName; return true; } siloName = default; return false; } public bool SubscribeToSiloStatusEvents(ISiloStatusListener listener) => this.listenerManager.Subscribe(listener); public bool UnSubscribeFromSiloStatusEvents(ISiloStatusListener listener) => this.listenerManager.Unsubscribe(listener); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.Runtime_Error_100209, Message = "The given SiloAddress {SiloAddress} is not registered in this MembershipOracle." )] private static partial void LogSiloAddressNotRegistered(ILogger logger, SiloAddress siloAddress); } } ================================================ FILE: src/Orleans.Runtime/MembershipService/SystemTargetBasedMembershipTable.cs ================================================ using System; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Concurrency; using Orleans.Configuration; using Orleans.Serialization; namespace Orleans.Runtime.MembershipService { internal partial class SystemTargetBasedMembershipTable : IMembershipTable { private readonly IServiceProvider serviceProvider; private readonly ILogger logger; private IMembershipTableSystemTarget grain; public SystemTargetBasedMembershipTable(IServiceProvider serviceProvider, ILogger logger) { this.serviceProvider = serviceProvider; this.logger = logger; } public async Task InitializeMembershipTable(bool tryInitTableVersion) { this.grain = await GetMembershipTable(); } private async Task GetMembershipTable() { var options = this.serviceProvider.GetRequiredService>().Value; if (options.PrimarySiloEndpoint == null) { throw new OrleansConfigurationException( $"{nameof(DevelopmentClusterMembershipOptions)}.{nameof(options.PrimarySiloEndpoint)} must be set when using development clustering."); } var siloDetails = this.serviceProvider.GetService(); bool isPrimarySilo = siloDetails.SiloAddress.Endpoint.Equals(options.PrimarySiloEndpoint); var grainFactory = this.serviceProvider.GetRequiredService(); var result = grainFactory.GetSystemTarget(Constants.SystemMembershipTableType, SiloAddress.New(options.PrimarySiloEndpoint, 0)); if (isPrimarySilo) { await this.WaitForTableGrainToInit(result); } return result; } // Only used with MembershipTableGrain to wait for primary to start. private async Task WaitForTableGrainToInit(IMembershipTableSystemTarget membershipTableSystemTarget) { var timespan = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(5); // This is a quick temporary solution to enable primary node to start fully before secondaries. // Secondary silos waits untill GrainBasedMembershipTable is created. for (int i = 0; i < 100; i++) { try { await membershipTableSystemTarget.ReadAll().WaitAsync(timespan); LogInformationConnectedToMembershipTableProvider(logger); return; } catch (Exception exc) { var type = exc.GetBaseException().GetType(); if (type == typeof(TimeoutException) || type == typeof(OrleansException)) { LogInformationWaitingForMembershipTableProvider(logger, timespan); } else { LogInformationMembershipTableProviderFailedToInitialize(logger); throw; } } await Task.Delay(timespan); } } public Task DeleteMembershipTableEntries(string clusterId) => this.grain.DeleteMembershipTableEntries(clusterId); public Task ReadRow(SiloAddress key) => this.grain.ReadRow(key); public Task ReadAll() => this.grain.ReadAll(); public Task InsertRow(MembershipEntry entry, TableVersion tableVersion) => this.grain.InsertRow(entry, tableVersion); public Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) => this.grain.UpdateRow(entry, etag, tableVersion); public Task UpdateIAmAlive(MembershipEntry entry) => this.grain.UpdateIAmAlive(entry); public Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) => this.grain.CleanupDefunctSiloEntries(beforeDate); [LoggerMessage( EventId = (int)ErrorCode.MembershipFactory1, Level = LogLevel.Information, Message = "Creating in-memory membership table" )] private static partial void LogInformationCreatingInMemoryMembershipTable(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.MembershipTableGrainInit2, Level = LogLevel.Information, Message = "Connected to membership table provider." )] private static partial void LogInformationConnectedToMembershipTableProvider(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.MembershipTableGrainInit3, Level = LogLevel.Information, Message = "Waiting for membership table provider to initialize. Going to sleep for {Duration} and re-try to reconnect." )] private static partial void LogInformationWaitingForMembershipTableProvider(ILogger logger, TimeSpan duration); [LoggerMessage( EventId = (int)ErrorCode.MembershipTableGrainInit4, Level = LogLevel.Information, Message = "Membership table provider failed to initialize. Giving up." )] private static partial void LogInformationMembershipTableProviderFailedToInitialize(ILogger logger); } [Reentrant] internal sealed partial class MembershipTableSystemTarget : SystemTarget, IMembershipTableSystemTarget, ILifecycleParticipant { private InMemoryMembershipTable table; private readonly ILogger logger; public MembershipTableSystemTarget( ILogger logger, DeepCopier deepCopier, SystemTargetShared shared) : base(CreateId(shared.SiloAddress), shared) { this.logger = logger; table = new InMemoryMembershipTable(deepCopier); LogInformationGrainBasedMembershipTableActivated(logger); shared.ActivationDirectory.RecordNewTarget(this); } private static SystemTargetGrainId CreateId(SiloAddress siloAddress) { return SystemTargetGrainId.Create(Constants.SystemMembershipTableType, SiloAddress.New(siloAddress.Endpoint, 0)); } public Task InitializeMembershipTable(bool tryInitTableVersion) { LogInformationInitializeMembershipTable(logger, tryInitTableVersion); return Task.CompletedTask; } public Task DeleteMembershipTableEntries(string clusterId) { LogInformationDeleteMembershipTableEntries(logger, clusterId); table = null; return Task.CompletedTask; } public Task ReadRow(SiloAddress key) { return Task.FromResult(table.Read(key)); } public Task ReadAll() { var t = table.ReadAll(); return Task.FromResult(t); } public Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { LogDebugInsertRow(logger, entry, tableVersion); bool result = table.Insert(entry, tableVersion); if (result == false) LogInformationInsertRowFailed(logger, entry, tableVersion, table.ReadAll()); return Task.FromResult(result); } public Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { LogDebugUpdateRow(logger, entry, etag, tableVersion); bool result = table.Update(entry, etag, tableVersion); if (result == false) LogInformationUpdateRowFailed(logger, entry, etag, tableVersion, table.ReadAll()); return Task.FromResult(result); } public Task UpdateIAmAlive(MembershipEntry entry) { LogDebugUpdateIAmAlive(logger, entry); table.UpdateIAmAlive(entry); return Task.CompletedTask; } public Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { table.CleanupDefunctSiloEntries(beforeDate); return Task.CompletedTask; } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { // Do nothing, just ensure that this instance is created so that it can register itself in the catalog. } [LoggerMessage( EventId = (int)ErrorCode.MembershipGrainBasedTable1, Level = LogLevel.Information, Message = "GrainBasedMembershipTable Activated." )] private static partial void LogInformationGrainBasedMembershipTableActivated(ILogger logger); [LoggerMessage( Level = LogLevel.Information, Message = "InitializeMembershipTable {TryInitTableVersion}." )] private static partial void LogInformationInitializeMembershipTable(ILogger logger, bool tryInitTableVersion); [LoggerMessage( Level = LogLevel.Information, Message = "DeleteMembershipTableEntries {ClusterId}" )] private static partial void LogInformationDeleteMembershipTableEntries(ILogger logger, string clusterId); [LoggerMessage( Level = LogLevel.Debug, Message = "InsertRow entry = {Entry}, table version = {Version}" )] private static partial void LogDebugInsertRow(ILogger logger, MembershipEntry entry, TableVersion version); [LoggerMessage( EventId = (int)ErrorCode.MembershipGrainBasedTable2, Level = LogLevel.Information, Message = "Insert of {Entry} and table version {Version} failed. Table now is {Table}" )] private static partial void LogInformationInsertRowFailed(ILogger logger, MembershipEntry entry, TableVersion version, MembershipTableData table); [LoggerMessage( Level = LogLevel.Debug, Message = "UpdateRow entry = {Entry}, etag = {ETag}, table version = {Version}" )] private static partial void LogDebugUpdateRow(ILogger logger, MembershipEntry entry, string etag, TableVersion version); [LoggerMessage( EventId = (int)ErrorCode.MembershipGrainBasedTable3, Level = LogLevel.Information, Message = "Update of {Entry}, eTag {ETag}, table version {Version} failed. Table now is {Table}" )] private static partial void LogInformationUpdateRowFailed(ILogger logger, MembershipEntry entry, string etag, TableVersion version, MembershipTableData table); [LoggerMessage( Level = LogLevel.Debug, Message = "UpdateIAmAlive entry = {Entry}" )] private static partial void LogDebugUpdateIAmAlive(ILogger logger, MembershipEntry entry); } } ================================================ FILE: src/Orleans.Runtime/Messaging/Gateway.cs ================================================ using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.ClientObservers; using Orleans.Configuration; using Orleans.Runtime.Internal; namespace Orleans.Runtime.Messaging { internal sealed partial class Gateway : IConnectedClientCollection { // clients is the main authorative collection of all connected clients. // Any client currently in the system appears in this collection. // In addition, we use clientConnections collection for fast retrival of ClientState. // Anything that appears in those 2 collections should also appear in the main clients collection. private readonly ConcurrentDictionary clients = new(); private readonly Dictionary clientConnections = new(); private readonly SiloAddress gatewayAddress; private readonly IAsyncTimer gatewayMaintenanceTimer; private readonly Task gatewayMaintenanceTask; private readonly ClientsReplyRoutingCache clientsReplyRoutingCache; private readonly MessageCenter messageCenter; private readonly ILogger logger; private readonly ILoggerFactory loggerFactory; private readonly SiloMessagingOptions messagingOptions; private long clientsCollectionVersion = 0; private readonly TimeSpan clientDropTimeout; public Gateway( MessageCenter messageCenter, ILocalSiloDetails siloDetails, ILoggerFactory loggerFactory, IOptions options, IAsyncTimerFactory timerFactory) { this.messageCenter = messageCenter; this.messagingOptions = options.Value; this.loggerFactory = loggerFactory; this.logger = this.loggerFactory.CreateLogger(); this.clientDropTimeout = messagingOptions.ClientDropTimeout; clientsReplyRoutingCache = new ClientsReplyRoutingCache(messagingOptions.ResponseTimeout); this.gatewayAddress = siloDetails.GatewayAddress; this.gatewayMaintenanceTimer = timerFactory.Create(messagingOptions.ClientDropTimeout, nameof(PerformGatewayMaintenance)); this.gatewayMaintenanceTask = Task.Run(PerformGatewayMaintenance); } public static GrainAddress GetClientActivationAddress(GrainId clientId, SiloAddress siloAddress) { // Need to pick a unique deterministic ActivationId for this client. // We store it in the grain directory and there for every GrainId we use ActivationId as a key // so every GW needs to behave as a different "activation" with a different ActivationId (its not enough that they have different SiloAddress) Span bytes = stackalloc byte[16]; BinaryPrimitives.WriteUInt32LittleEndian(bytes, clientId.Type.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[4..], clientId.Key.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[8..], (uint)siloAddress.GetConsistentHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[12..], (uint)siloAddress.Generation); var activationId = new ActivationId(new Guid(bytes)); return GrainAddress.GetAddress(siloAddress, clientId, activationId); } private async Task PerformGatewayMaintenance() { while (await gatewayMaintenanceTimer.NextTick()) { try { DropDisconnectedClients(); DropExpiredRoutingCachedEntries(); } catch (Exception exception) { LogErrorGatewayMaintenanceError(logger, exception); } } } internal async Task SendStopSendMessages(IInternalGrainFactory grainFactory, CancellationToken cancellationToken = default) { lock (clients) { foreach (var client in clients) { if (client.Value.IsConnected) { var observer = ClientGatewayObserver.GetObserver(grainFactory, client.Key); observer.StopSendingToGateway(this.gatewayAddress); } } } await Task.Delay(this.messagingOptions.ClientGatewayShutdownNotificationTimeout, cancellationToken); } internal async Task StopAsync() { gatewayMaintenanceTimer.Dispose(); await gatewayMaintenanceTask.ConfigureAwait(false); } long IConnectedClientCollection.Version => Interlocked.Read(ref clientsCollectionVersion); List IConnectedClientCollection.GetConnectedClientIds() { var result = new List(); foreach (var pair in clients) { result.Add(pair.Key.GrainId); } return result; } internal void RecordOpenedConnection(GatewayInboundConnection connection, ClientGrainId clientId) { LogInformationGatewayClientOpenedSocket(logger, connection.RemoteEndPoint, clientId); lock (clients) { if (clients.TryGetValue(clientId, out var clientState)) { var oldSocket = clientState.Connection; if (oldSocket != null) { // The old socket will be closed by itself later. clientConnections.Remove(oldSocket); } } else { clientState = new ClientState(this, clientId); clients[clientId] = clientState; MessagingInstruments.ConnectedClient.Add(1); } clientState.RecordConnection(connection); clientConnections[connection] = clientState; clientsCollectionVersion++; } } internal void RecordClosedConnection(GatewayInboundConnection connection) { if (connection == null) return; ClientState clientState; lock (clients) { if (!clientConnections.Remove(connection, out clientState)) return; clientState.RecordDisconnection(); clientsCollectionVersion++; } LogInformationGatewayClientClosedSocket(logger, connection.RemoteEndPoint?.ToString() ?? "null", clientState.Id); } internal SiloAddress TryToReroute(Message msg) { // ** Special routing rule for system target here ** // When a client make a request/response to/from a SystemTarget, the TargetSilo can be set to either // - the GatewayAddress of the target silo (for example, when the client want get the cluster typemap) // - the "internal" Silo-to-Silo address, if the client want to send a message to a specific SystemTarget // activation that is on a silo on which there is no gateway available (or if the client is not // connected to that gateway) // So, if the TargetGrain is a SystemTarget we always trust the value from Message.TargetSilo and forward // it to this address... // EXCEPT if the value is equal to the current GatewayAddress: in this case we will return // null and the local dispatcher will forward the Message to a local SystemTarget activation if (msg.TargetGrain.IsSystemTarget() && !IsTargetingLocalGateway(msg.TargetSilo)) { return msg.TargetSilo; } // for responses from ClientAddressableObject to ClientGrain try to use clientsReplyRoutingCache for sending replies directly back. if (!msg.SendingGrain.IsClient() || !msg.TargetGrain.IsClient()) { return null; } if (msg.Direction != Message.Directions.Response) { return null; } if (clientsReplyRoutingCache.TryFindClientRoute(msg.TargetGrain, out var gateway)) { return gateway; } return null; } internal void DropExpiredRoutingCachedEntries() { lock (clients) { clientsReplyRoutingCache.DropExpiredEntries(); } } private bool IsTargetingLocalGateway(SiloAddress siloAddress) { // Special case if the address used by the client was loopback return this.gatewayAddress.Matches(siloAddress) || (IPAddress.IsLoopback(siloAddress.Endpoint.Address) && siloAddress.Endpoint.Port == this.gatewayAddress.Endpoint.Port && siloAddress.Generation == this.gatewayAddress.Generation); } // There is NO need to acquire individual ClientState lock, since we only close an older socket. internal void DropDisconnectedClients() { foreach (var kv in clients) { if (kv.Value.ReadyToDrop()) { lock (clients) { if (clients.TryGetValue(kv.Key, out var client) && client.ReadyToDrop()) { LogInformationGatewayDroppingClient(logger, kv.Key, client.DisconnectedSince); if (clients.TryRemove(kv.Key, out _)) { // Reject all pending messages from the client. client.Drop(); } clientsCollectionVersion++; MessagingInstruments.ConnectedClient.Add(-1); } } } } } /// /// See if this message is intended for a grain we're proxying, and queue it for delivery if so. /// /// /// true if the message should be delivered to a proxied grain, false if not. internal bool TryDeliverToProxy(Message msg) { // See if it's a grain we're proxying. var targetGrain = msg.TargetGrain; if (!ClientGrainId.TryParse(targetGrain, out var clientId)) { return false; } if (!clients.TryGetValue(clientId, out var client)) { return false; } // when this Gateway receives a message from client X to client addressable object Y // it needs to record the original Gateway address through which this message came from (the address of the Gateway that X is connected to) // it will use this Gateway to re-route the REPLY from Y back to X. if (msg.SendingGrain.IsClient()) { clientsReplyRoutingCache.RecordClientRoute(msg.SendingGrain, msg.SendingSilo); } msg.TargetSilo = null; msg.SendingSilo ??= gatewayAddress; client.Send(msg); return true; } private class ClientState { private readonly Gateway _gateway; private readonly Task _messageLoop; private readonly ConcurrentQueue _pendingToSend = new(); private readonly SingleWaiterAutoResetEvent _signal = new() { RunContinuationsAsynchronously = true }; private GatewayInboundConnection _connection; private int _dropped; private CoarseStopwatch _disconnectedSince; internal ClientState(Gateway gateway, ClientGrainId id) { // Ensure that the client does not capture any AsyncLocal state, etc using var suppressExecutionContext = new ExecutionContextSuppressor(); _gateway = gateway; Id = id; _disconnectedSince.Restart(); _messageLoop = Task.Run(RunMessageLoop); } public bool IsConnected => Connection != null; private bool IsDropped => Volatile.Read(ref _dropped) == 1; public GatewayInboundConnection Connection => _connection; public TimeSpan DisconnectedSince => _disconnectedSince.Elapsed; public ClientGrainId Id { get; } public void RecordDisconnection() { var connection = Interlocked.Exchange(ref _connection, null); if (connection is null) { return; } _disconnectedSince.Restart(); _signal.Signal(); } public void RecordConnection(GatewayInboundConnection connection) { var existing = Interlocked.Exchange(ref _connection, connection); if (existing is not null) { LogWarningGatewayClientReceivedNewConnectionBeforePreviousConnectionRemoved(_gateway.logger, Id, connection, existing); } _disconnectedSince.Reset(); _signal.Signal(); } public bool ReadyToDrop() { if (IsConnected) return false; if (_disconnectedSince.Elapsed < _gateway.clientDropTimeout) { return false; } return true; } public void Drop() { Interlocked.Exchange(ref _dropped, 1); RejectDroppedClientMessages(); _signal.Signal(); } public void Send(Message msg) { _pendingToSend.Enqueue(msg); _signal.Signal(); LogTraceQueuedMessage(_gateway.logger, msg, msg.TargetGrain); } private async Task RunMessageLoop() { while (true) { try { await _signal.WaitAsync(); if (IsDropped) { RejectDroppedClientMessages(); continue; } var connection = Volatile.Read(ref _connection); if (connection is null) { continue; } // Send all pending messages. while (_pendingToSend.TryDequeue(out var message)) { if (TrySend(connection, message)) { LogTraceSentQueuedMessage(_gateway.logger, message, Id); } else { // Re-enqueue the message. It's ok that it is at the end of the queue: message ordering is not guaranteed. _pendingToSend.Enqueue(message); return; } } } catch (Exception exception) { LogWarningGatewayClientMessageLoopException(_gateway.logger, exception, Id); } } } private void RejectDroppedClientMessages() { ClientNotAvailableException exception = null; while (_pendingToSend.TryDequeue(out var message)) { exception ??= new ClientNotAvailableException(Id.GrainId); _gateway.messageCenter.RejectMessage(message, Message.RejectionTypes.Transient, exc: exception, rejectInfo: "Client dropped"); } } private bool TrySend(GatewayInboundConnection connection, Message message) { if (connection is null) { return false; } try { connection.Send(message); GatewayInstruments.GatewaySent.Add(1); return true; } catch (Exception exception) { _gateway.RecordClosedConnection(connection); connection.CloseAsync(new ConnectionAbortedException("Exception posting a message to sender. See InnerException for details.", exception)).Ignore(); return false; } } } // this cache is used to record the addresses of Gateways from which clients connected to. // it is used to route replies to clients from client addressable objects // without this cache this Gateway will not know how to route the reply back to the client // (since clients are not registered in the directory and this Gateway may not be proxying for the client for whom the reply is destined). private class ClientsReplyRoutingCache { // for every client: the Gateway to use to route replies back to it plus the last time that client connected via this Gateway. private readonly ConcurrentDictionary> clientRoutes = new(); private readonly TimeSpan TIME_BEFORE_ROUTE_CACHED_ENTRY_EXPIRES; internal ClientsReplyRoutingCache(TimeSpan responseTimeout) { TIME_BEFORE_ROUTE_CACHED_ENTRY_EXPIRES = responseTimeout.Multiply(5); } internal void RecordClientRoute(GrainId client, SiloAddress gateway) { clientRoutes[client] = new(gateway, DateTime.UtcNow); } internal bool TryFindClientRoute(GrainId client, out SiloAddress gateway) { if (clientRoutes.TryGetValue(client, out var tuple)) { gateway = tuple.Item1; return true; } gateway = null; return false; } internal void DropExpiredEntries() { var expiredTime = DateTime.UtcNow - TIME_BEFORE_ROUTE_CACHED_ENTRY_EXPIRES; foreach (var client in clientRoutes) { if (client.Value.Item2 < expiredTime) { clientRoutes.TryRemove(client.Key, out _); } } } } [LoggerMessage( Level = LogLevel.Error, Message = "Error performing gateway maintenance" )] private static partial void LogErrorGatewayMaintenanceError(ILogger logger, Exception exception); [LoggerMessage( EventId = (int)ErrorCode.GatewayClientOpenedSocket, Level = LogLevel.Information, Message = "Recorded opened connection from endpoint {EndPoint}, client ID {ClientId}." )] private static partial void LogInformationGatewayClientOpenedSocket(ILogger logger, EndPoint endPoint, ClientGrainId clientId); [LoggerMessage( EventId = (int)ErrorCode.GatewayClientClosedSocket, Level = LogLevel.Information, Message = "Recorded closed socket from endpoint {Endpoint}, client ID {ClientId}." )] private static partial void LogInformationGatewayClientClosedSocket(ILogger logger, string endpoint, ClientGrainId clientId); [LoggerMessage( EventId = (int)ErrorCode.GatewayDroppingClient, Level = LogLevel.Information, Message = "Dropping client {ClientId}, {IdleDuration} after disconnect with no reconnect" )] private static partial void LogInformationGatewayDroppingClient(ILogger logger, ClientGrainId clientId, TimeSpan idleDuration); [LoggerMessage( Level = LogLevel.Warning, Message = "Client {ClientId} received new connection ({NewConnection}) before the previous connection ({PreviousConnection}) had been removed" )] private static partial void LogWarningGatewayClientReceivedNewConnectionBeforePreviousConnectionRemoved(ILogger logger, ClientGrainId clientId, GatewayInboundConnection newConnection, GatewayInboundConnection previousConnection); [LoggerMessage( Level = LogLevel.Trace, Message = "Queued message {Message} for client {TargetGrain}" )] private static partial void LogTraceQueuedMessage(ILogger logger, object message, GrainId targetGrain); [LoggerMessage( Level = LogLevel.Trace, Message = "Sent queued message {Message} to client {ClientId}" )] private static partial void LogTraceSentQueuedMessage(ILogger logger, object message, ClientGrainId clientId); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception in message loop for client {ClientId}" )] private static partial void LogWarningGatewayClientMessageLoopException(ILogger logger, Exception exception, ClientGrainId clientId); } } ================================================ FILE: src/Orleans.Runtime/Messaging/IConnectedClientCollection.cs ================================================ using System.Collections.Generic; namespace Orleans.Runtime { /// /// Represents the collection of clients which are currently connected to this gateway. /// internal interface IConnectedClientCollection { /// /// The monotonically increasing version of the collection, which can be used for change notification. /// long Version { get; } /// /// Gets the collection of ids for the connected clients. /// List GetConnectedClientIds(); } internal sealed class EmptyConnectedClientCollection : IConnectedClientCollection { public long Version => 0; public List GetConnectedClientIds() => new List(0); } } ================================================ FILE: src/Orleans.Runtime/Messaging/MessageCenter.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Placement.Repartitioning; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Placement; using Orleans.Serialization.Invocation; namespace Orleans.Runtime.Messaging { internal partial class MessageCenter : IMessageCenter, IAsyncDisposable { private readonly ISiloStatusOracle siloStatusOracle; private readonly MessageFactory messageFactory; private readonly ConnectionManager connectionManager; private readonly RuntimeMessagingTrace messagingTrace; private readonly SiloAddress _siloAddress; private readonly SiloMessagingOptions messagingOptions; private readonly PlacementService placementService; private readonly GrainLocator _grainLocator; private readonly Action? _messageObserver; private readonly ILogger log; private readonly Catalog catalog; private bool stopped; private HostedClient? hostedClient; private Action? sniffIncomingMessageHandler; public MessageCenter( ILocalSiloDetails siloDetails, MessageFactory messageFactory, Catalog catalog, Factory gatewayFactory, ILogger logger, ISiloStatusOracle siloStatusOracle, ConnectionManager senderManager, RuntimeMessagingTrace messagingTrace, IOptions messagingOptions, PlacementService placementService, GrainLocator grainLocator, IMessageStatisticsSink messageStatisticsSink) { this.catalog = catalog; this.messagingOptions = messagingOptions.Value; this.siloStatusOracle = siloStatusOracle; this.connectionManager = senderManager; this.messagingTrace = messagingTrace; this.placementService = placementService; _grainLocator = grainLocator; _messageObserver = messageStatisticsSink.GetMessageObserver(); this.log = logger; this.messageFactory = messageFactory; this._siloAddress = siloDetails.SiloAddress; if (siloDetails.GatewayAddress != null) { Gateway = gatewayFactory(this); } } public Gateway? Gateway { get; } internal bool IsBlockingApplicationMessages { get; private set; } public void SetHostedClient(HostedClient? client) => this.hostedClient = client; public bool TryDeliverToProxy(Message msg) { if (!msg.TargetGrain.IsClient()) return false; if (this.Gateway is Gateway gateway && gateway.TryDeliverToProxy(msg) || this.hostedClient is HostedClient client && client.TryDispatchToClient(msg)) { _messageObserver?.Invoke(msg); return true; } return false; } public async Task StopAsync() { BlockApplicationMessages(); await StopAcceptingClientMessages(); stopped = true; } /// /// Indicates that application messages should be blocked from being sent or received. /// This method is used by the "fast stop" process. /// /// Specifically, all outbound application messages are dropped, except for rejections and messages to the membership table grain. /// Inbound application requests are rejected, and other inbound application messages are dropped. /// /// public void BlockApplicationMessages() { LogDebugBlockApplicationMessages(log); IsBlockingApplicationMessages = true; } public async Task StopAcceptingClientMessages() { LogDebugStopClientMessages(log); try { if (Gateway is { } gateway) { await gateway.StopAsync(); } } catch (Exception exc) { LogErrorStopFailed(log, exc); } } public Action? SniffIncomingMessage { set { if (this.sniffIncomingMessageHandler != null) { throw new InvalidOperationException("MessageCenter.SniffIncomingMessage already set"); } this.sniffIncomingMessageHandler = value; } get => this.sniffIncomingMessageHandler; } public void SendMessage(Message msg) { Debug.Assert(!msg.IsLocalOnly); // Note that if we identify or add other grains that are required for proper stopping, we will need to treat them as we do the membership table grain here. if (IsBlockingApplicationMessages && !msg.IsSystemMessage && msg.Result is not Message.ResponseTypes.Rejection && !Constants.SystemMembershipTableType.Equals(msg.TargetGrain)) { // Drop the message on the floor if it's an application message that isn't a rejection this.messagingTrace.OnDropBlockedApplicationMessage(msg); } else { msg.SendingSilo ??= _siloAddress; if (stopped) { LogInformationMessageQueuedAfterStop(log, msg); SendRejection(msg, Message.RejectionTypes.Unrecoverable, "Message was queued for sending after outbound queue was stopped"); return; } // Don't process messages that have already timed out if (msg.IsExpired) { this.messagingTrace.OnDropExpiredMessage(msg, MessagingInstruments.Phase.Send); return; } // First check to see if it's really destined for a proxied client, instead of a local grain. if (TryDeliverToProxy(msg)) { // Message was successfully delivered to the proxy. return; } if (msg.TargetSilo is not { } targetSilo) { LogErrorMessageNoTargetSilo(log, msg, new()); SendRejection(msg, Message.RejectionTypes.Unrecoverable, "Message to be sent does not have a target silo."); return; } messagingTrace.OnSendMessage(msg); if (targetSilo.Matches(_siloAddress)) { LogTraceMessageLoopedBack(log, msg); MessagingInstruments.LocalMessagesSentCounterAggregator.Add(1); this.ReceiveMessage(msg); } else { if (this.connectionManager.TryGetConnection(targetSilo, out var existingConnection)) { existingConnection.Send(msg); return; } else if (this.siloStatusOracle.IsDeadSilo(targetSilo)) { // Do not try to establish if (msg.Direction is Message.Directions.Request or Message.Directions.OneWay) { this.messagingTrace.OnRejectSendMessageToDeadSilo(_siloAddress, msg); this.SendRejection(msg, Message.RejectionTypes.Transient, "Target silo is known to be dead", new SiloUnavailableException()); } return; } else { var connectionTask = this.connectionManager.GetConnection(targetSilo); if (connectionTask.IsCompletedSuccessfully) { var sender = connectionTask.Result; sender.Send(msg); } else { _ = SendAsync(this, connectionTask, msg); static async Task SendAsync(MessageCenter messageCenter, ValueTask connectionTask, Message msg) { try { var sender = await connectionTask; sender.Send(msg); } catch (Exception exception) { messageCenter.SendRejection(msg, Message.RejectionTypes.Transient, $"Exception while sending message: {exception}"); } } } } } } } public void DispatchLocalMessage(Message message) => ReceiveMessage(message); public void RejectMessage( Message message, Message.RejectionTypes rejectionType, Exception? exc, string? rejectInfo = null) { if (message.Direction == Message.Directions.Request || (message.Direction == Message.Directions.OneWay && message.HasCacheInvalidationHeader)) { this.messagingTrace.OnDispatcherRejectMessage(message, rejectionType, rejectInfo, exc); var str = $"{rejectInfo} {exc}"; var rejection = this.messageFactory.CreateRejectionResponse(message, rejectionType, str, exc); SendMessage(rejection); } else { this.messagingTrace.OnDispatcherDiscardedRejection(message, rejectionType, rejectInfo, exc); } } internal void ProcessRequestsToInvalidActivation( List messages, GrainAddress? oldAddress, SiloAddress? forwardingAddress, string? failedOperation = null, Exception? exc = null, bool rejectMessages = false) { if (rejectMessages) { GrainAddress? validAddress = forwardingAddress switch { null => null, _ => new() { GrainId = oldAddress?.GrainId ?? default, SiloAddress = forwardingAddress, } }; foreach (var message in messages) { Debug.Assert(!message.IsLocalOnly); if (oldAddress != null) { message.AddToCacheInvalidationHeader(oldAddress, validAddress: validAddress); } RejectMessage(message, Message.RejectionTypes.Transient, exc, failedOperation); } } else { this.messagingTrace.OnDispatcherForwardingMultiple(messages.Count, oldAddress, forwardingAddress, failedOperation, exc); GrainAddress? destination = forwardingAddress switch { null => null, _ => new() { GrainId = oldAddress?.GrainId ?? default, SiloAddress = forwardingAddress, } }; foreach (var message in messages) { TryForwardRequest(message, oldAddress, destination, failedOperation, exc); } } } private void ProcessRequestToInvalidActivation( Message message, GrainAddress? oldAddress, SiloAddress? forwardingAddress, string failedOperation, Exception? exc = null, bool rejectMessages = false) { Debug.Assert(!message.IsLocalOnly); // Just use this opportunity to invalidate local Cache Entry as well. if (oldAddress != null) { _grainLocator.InvalidateCache(oldAddress); } // IMPORTANT: do not do anything on activation context anymore, since this activation is invalid already. if (rejectMessages) { this.RejectMessage(message, Message.RejectionTypes.Transient, exc, failedOperation); } else { GrainAddress? destination = forwardingAddress switch { null => null, _ => new() { GrainId = oldAddress?.GrainId ?? default, SiloAddress = forwardingAddress, } }; this.TryForwardRequest(message, oldAddress, destination, failedOperation, exc); } } private void TryForwardRequest(Message message, GrainAddress? oldAddress, GrainAddress? destination, string? failedOperation = null, Exception? exc = null) { Debug.Assert(!message.IsLocalOnly); bool forwardingSucceeded = false; var forwardingAddress = destination?.SiloAddress; try { this.messagingTrace.OnDispatcherForwarding(message, oldAddress, forwardingAddress, failedOperation, exc); if (oldAddress != null) { message.AddToCacheInvalidationHeader(oldAddress, validAddress: destination); } LogDebugForwarding(log, exc, message, forwardingAddress, failedOperation); forwardingSucceeded = this.TryForwardMessage(message, forwardingAddress); } catch (Exception exc2) { forwardingSucceeded = false; exc = exc2; } finally { var sentRejection = false; // If the message was a one-way message, send a cache invalidation response even if the message was successfully forwarded. if (message.Direction == Message.Directions.OneWay) { this.RejectMessage( message, Message.RejectionTypes.CacheInvalidation, exc, "OneWay message sent to invalid activation"); sentRejection = true; } if (!forwardingSucceeded) { this.messagingTrace.OnDispatcherForwardingFailed(message, oldAddress, forwardingAddress, failedOperation, exc); if (!sentRejection) { var str = $"Forwarding failed: tried to forward message {message} for {message.ForwardCount} times after \"{failedOperation}\" to invalid activation. Rejecting now."; RejectMessage(message, Message.RejectionTypes.Transient, exc, str); } } } } /// /// Reroute a message coming in through a gateway /// /// internal void RerouteMessage(Message message) { ResendMessageImpl(message); } private bool TryForwardMessage(Message message, SiloAddress? forwardingAddress) { if (!MayForward(message, this.messagingOptions)) return false; message.ForwardCount = message.ForwardCount + 1; MessagingProcessingInstruments.OnDispatcherMessageForwared(message); ResendMessageImpl(message, forwardingAddress); return true; } private void ResendMessageImpl(Message message, SiloAddress? forwardingAddress = null) { LogDebugResend(log, message); if (message.TargetGrain.IsSystemTarget()) { message.IsSystemMessage = true; SendMessage(message); } else if (forwardingAddress != null) { message.TargetSilo = forwardingAddress; SendMessage(message); } else { message.TargetSilo = null; _ = AddressAndSendMessage(message); } } // Forwarding is used by the receiver, usually when it cannot process the message and forwards it to another silo to perform the processing // (got here due to duplicate activation, outdated cache, silo is shutting down/overloaded, ...). private static bool MayForward(Message message, SiloMessagingOptions messagingOptions) { return message.ForwardCount < messagingOptions.MaxForwardCount; } /// /// Send an outgoing message, may complete synchronously /// - may buffer for transaction completion / commit if it ends a transaction /// - choose target placement address, maintaining send order /// - add ordering info and maintain send order /// /// internal Task AddressAndSendMessage(Message message) { try { var messageAddressingTask = placementService.AddressMessage(message); if (messageAddressingTask.Status != TaskStatus.RanToCompletion) { return SendMessageAsync(messageAddressingTask, message); } SendMessage(message); } catch (Exception ex) { OnAddressingFailure(message, ex); } return Task.CompletedTask; async Task SendMessageAsync(Task addressMessageTask, Message m) { try { await addressMessageTask; } catch (Exception ex) { OnAddressingFailure(m, ex); return; } SendMessage(m); } void OnAddressingFailure(Message m, Exception ex) { this.messagingTrace.OnDispatcherSelectTargetFailed(m, ex); RejectMessage(m, Message.RejectionTypes.Unrecoverable, ex); } } internal void SendResponse(Message request, Response response) { // create the response var message = this.messageFactory.CreateResponseMessage(request); message.BodyObject = response; if (message.TargetGrain.IsSystemTarget()) { message.IsSystemMessage = true; } SendMessage(message); } public void ReceiveMessage(Message msg) { Debug.Assert(!msg.IsLocalOnly); try { this.messagingTrace.OnIncomingMessageAgentReceiveMessage(msg); if (TryDeliverToProxy(msg)) { return; } else if (msg.Direction == Message.Directions.Response) { this.catalog.RuntimeClient.ReceiveResponse(msg); } else { var targetActivation = catalog.GetOrCreateActivation( msg.TargetGrain, msg.RequestContextData, rehydrationContext: null); if (targetActivation is null) { ProcessMessageToNonExistentActivation(msg); return; } targetActivation.ReceiveMessage(msg); _messageObserver?.Invoke(msg); } } catch (Exception ex) { HandleReceiveFailure(msg, ex); } void HandleReceiveFailure(Message msg, Exception ex) { MessagingProcessingInstruments.OnDispatcherMessageProcessedError(msg); LogErrorCreatingActivation(log, ex, msg.TargetGrain, msg.InterfaceType, msg); this.RejectMessage(msg, Message.RejectionTypes.Transient, ex); } } private void ProcessMessageToNonExistentActivation(Message msg) { var target = msg.TargetGrain; if (target.IsSystemTarget()) { MessagingInstruments.OnRejectedMessage(msg); LogWarningUnknownSystemTarget(log, msg, msg.TargetGrain); // Send a rejection only on a request if (msg.Direction == Message.Directions.Request) { var response = this.messageFactory.CreateRejectionResponse( msg, Message.RejectionTypes.Unrecoverable, $"SystemTarget {msg.TargetGrain} not active on this silo. Msg={msg}"); SendMessage(response); } } else { // Activation does not exists and is not a new placement. LogDebugUnableToCreateActivation(log, msg); var partialAddress = new GrainAddress { SiloAddress = msg.TargetSilo, GrainId = msg.TargetGrain }; ProcessRequestToInvalidActivation(msg, partialAddress, null, "Unable to create local activation"); } } internal void SendRejection(Message msg, Message.RejectionTypes rejectionType, string reason, Exception? exception = null) { MessagingInstruments.OnRejectedMessage(msg); if (msg.Direction is Message.Directions.Response && msg.Result is Message.ResponseTypes.Rejection) { // Do not send reject a rejection locally, it will create a stack overflow MessagingInstruments.OnDroppedSentMessage(msg); LogDebugDroppingRejection(log, msg); } else { if (string.IsNullOrEmpty(reason)) reason = $"Rejection from silo {this._siloAddress} - Unknown reason."; var error = this.messageFactory.CreateRejectionResponse(msg, rejectionType, reason, exception); // rejection msgs are always originated in the local silo, they are never remote. this.ReceiveMessage(error); } } public async ValueTask DisposeAsync() { await StopAsync(); } private readonly struct StackTraceLogValue() { public override string ToString() => Utils.GetStackTrace(1); } [LoggerMessage( Level = LogLevel.Debug, Message = "BlockApplicationMessages" )] private static partial void LogDebugBlockApplicationMessages(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "StopClientMessages" )] private static partial void LogDebugStopClientMessages(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100109, Level = LogLevel.Error, Message = "Stop failed" )] private static partial void LogErrorStopFailed(ILogger logger, Exception exc); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100115, Level = LogLevel.Information, Message = "Message was queued for sending after outbound queue was stopped: {Message}" )] private static partial void LogInformationMessageQueuedAfterStop(ILogger logger, Message message); [LoggerMessage( EventId = (int)ErrorCode.Runtime_Error_100113, Level = LogLevel.Error, Message = "Message does not have a target silo: '{Message}'. Call stack: {StackTrace}" )] private static partial void LogErrorMessageNoTargetSilo(ILogger logger, Message message, StackTraceLogValue stackTrace); [LoggerMessage( Level = LogLevel.Trace, Message = "Message has been looped back to this silo: {Message}" )] private static partial void LogTraceMessageLoopedBack(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, Message = "Forwarding {Message} to '{ForwardingAddress}' after '{FailedOperation}'" )] private static partial void LogDebugForwarding(ILogger logger, Exception? exc, Message message, SiloAddress? forwardingAddress, string? failedOperation); [LoggerMessage( Level = LogLevel.Debug, Message = "Resend {Message}" )] private static partial void LogDebugResend(ILogger logger, Message message); [LoggerMessage( EventId = (int)ErrorCode.Dispatcher_ErrorCreatingActivation, Level = LogLevel.Error, Message = "Error creating activation for grain {TargetGrain} (interface: {InterfaceType}). Message {Message}" )] private static partial void LogErrorCreatingActivation(ILogger logger, Exception ex, GrainId targetGrain, GrainInterfaceType interfaceType, Message message); [LoggerMessage( EventId = (int)ErrorCode.MessagingMessageFromUnknownActivation, Level = LogLevel.Warning, Message = "Received a message {Message} for an unknown SystemTarget: {Target}" )] private static partial void LogWarningUnknownSystemTarget(ILogger logger, Message message, GrainId target); [LoggerMessage( EventId = (int)ErrorCode.Dispatcher_Intermediate_GetOrCreateActivation, Level = LogLevel.Debug, Message = "Unable to create local activation for message {Message}." )] private static partial void LogDebugUnableToCreateActivation(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, Message = "Dropping rejection {Message}" )] private static partial void LogDebugDroppingRejection(ILogger logger, Message message); } } ================================================ FILE: src/Orleans.Runtime/Messaging/OverloadDetector.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Core.Messaging; using Orleans.Statistics; namespace Orleans.Runtime.Messaging { /// /// Determines whether or not the process is overloaded. /// public interface IOverloadDetector { /// /// Returns if this process is overloaded, otherwise. /// bool IsOverloaded { get; } } /// /// Determines whether or not the process is overloaded. /// internal class OverloadDetector : IOverloadDetector { private const int RefreshIntervalMilliseconds = 1_000; private readonly IEnvironmentStatisticsProvider _environmentStatisticsProvider; private readonly LoadSheddingOptions _options; private CoarseStopwatch _refreshStopwatch; private bool? _isOverloaded; public OverloadDetector(IEnvironmentStatisticsProvider environmentStatisticsProvider, IOptions loadSheddingOptions) { _environmentStatisticsProvider = environmentStatisticsProvider; _options = loadSheddingOptions.Value; Enabled = _options.LoadSheddingEnabled; _refreshStopwatch = CoarseStopwatch.StartNew(); } /// /// Gets or sets a value indicating whether overload detection is enabled. /// public bool Enabled { get; set; } /// /// Returns if this process is overloaded, otherwise. /// public bool IsOverloaded { get { if (!Enabled) { return false; } if (!_isOverloaded.HasValue || _refreshStopwatch.ElapsedMilliseconds >= RefreshIntervalMilliseconds) { var stats = _environmentStatisticsProvider.GetEnvironmentStatistics(); _isOverloaded = OverloadDetectionLogic.IsOverloaded(ref stats, _options); _refreshStopwatch.Restart(); } return _isOverloaded.Value; } } // Exposed only for testing purposes internal void ForceRefresh() { _isOverloaded = null; } } } ================================================ FILE: src/Orleans.Runtime/Messaging/RuntimeMessagingTrace.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal sealed class RuntimeMessagingTrace : MessagingTrace { public const string DispatcherReceiveInvalidActivationEventName = Category + ".Dispatcher.InvalidActivation"; public const string DispatcherDetectedDeadlockEventName = Category + ".Dispatcher.DetectedDeadlock"; public const string DispatcherDiscardedRejectionEventName = Category + ".Dispatcher.DiscardedRejection"; public const string DispatcherRejectedMessageEventName = Category + ".Dispatcher.Rejected"; public const string DispatcherForwardingEventName = Category + ".Dispatcher.Forwarding"; public const string DispatcherForwardingMultipleEventName = Category + ".Dispatcher.ForwardingMultiple"; public const string DispatcherForwardingFailedEventName = Category + ".Dispatcher.ForwardingFailed"; public const string DispatcherSelectTargetFailedEventName = Category + ".Dispatcher.SelectTargetFailed"; private static readonly Action LogDispatcherReceiveInvalidActivation = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Dispatcher_Receive_InvalidActivation, DispatcherReceiveInvalidActivationEventName), "Invalid activation in state {State} for message {Message}"); private static readonly Action LogDispatcherDetectedDeadlock = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Dispatcher_DetectedDeadlock, DispatcherDetectedDeadlockEventName), "Detected application deadlock on message {Message} and activation {Activation}"); private static readonly Action LogDispatcherDiscardedRejection = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Messaging_Dispatcher_DiscardRejection, DispatcherDiscardedRejectionEventName), "Discarding rejection of message {Message} with reason '{Reason}' ({RejectionType})"); private static readonly Action LogDispatcherRejectedMessage = LoggerMessage.Define( LogLevel.Debug, new EventId((int)ErrorCode.Messaging_Dispatcher_Rejected, DispatcherRejectedMessageEventName), "Rejected message {Message} with reason '{Reason}' ({RejectionType})"); private static readonly Action LogDispatcherForwarding = LoggerMessage.Define( LogLevel.Debug, new EventId((int)ErrorCode.Messaging_Dispatcher_TryForward, DispatcherForwardingEventName), "Trying to forward {Message} from {OldAddress} to {ForwardingAddress} after {FailedOperation}. Attempt {ForwardCount}"); private static readonly Action LogDispatcherForwardingFailed = LoggerMessage.Define( LogLevel.Warning, new EventId((int)ErrorCode.Messaging_Dispatcher_TryForwardFailed, DispatcherForwardingFailedEventName), "Failed to forward message {Message} from {OldAddress} to {ForwardingAddress} after {FailedOperation}. Attempt {ForwardCount}"); private static readonly Action LogDispatcherForwardingMultiple = LoggerMessage.Define( LogLevel.Debug, new EventId((int)ErrorCode.Messaging_Dispatcher_ForwardingRequests, DispatcherForwardingMultipleEventName), "Forwarding {MessageCount} requests destined for address {OldAddress} to address {ForwardingAddress} after {FailedOperation}"); private static readonly Action LogDispatcherSelectTargetFailed = LoggerMessage.Define( LogLevel.Error, new EventId((int)ErrorCode.Dispatcher_SelectTarget_Failed, DispatcherSelectTargetFailedEventName), "Failed to address message {Message}"); public RuntimeMessagingTrace(ILoggerFactory loggerFactory) : base(loggerFactory) { } internal void OnDispatcherReceiveInvalidActivation(Message message, ActivationState activationState) { if (this.IsEnabled(DispatcherReceiveInvalidActivationEventName)) { this.Write(DispatcherReceiveInvalidActivationEventName, new { Message = message, ActivationState = activationState }); } MessagingProcessingInstruments.OnDispatcherMessageProcessedError(message); LogDispatcherReceiveInvalidActivation(this, activationState, message, null); } internal void OnDispatcherDiscardedRejection(Message message, Message.RejectionTypes rejectionType, string reason, Exception exception) { if (this.IsEnabled(DispatcherDiscardedRejectionEventName)) { this.Write(DispatcherDiscardedRejectionEventName, new { Message = message, RejectionType = rejectionType, Reason = reason, Exception = exception }); } LogDispatcherDiscardedRejection(this, message, reason, rejectionType, exception); } internal void OnDispatcherRejectMessage(Message message, Message.RejectionTypes rejectionType, string reason, Exception exception) { if (this.IsEnabled(DispatcherRejectedMessageEventName)) { this.Write(DispatcherRejectedMessageEventName, new { Message = message, RejectionType = rejectionType, Reason = reason, Exception = exception }); } MessagingInstruments.OnRejectedMessage(message); if (this.IsEnabled(LogLevel.Debug)) { LogDispatcherRejectedMessage(this, message, reason, rejectionType, exception); } } internal void OnDispatcherForwarding(Message message, GrainAddress oldAddress, SiloAddress forwardingAddress, string failedOperation, Exception exception) { if (this.IsEnabled(DispatcherForwardingEventName)) { this.Write(DispatcherForwardingEventName, new { Message = message, OldAddress = oldAddress, ForwardingAddress = forwardingAddress, FailedOperation = failedOperation, Exception = exception }); } if (this.IsEnabled(LogLevel.Debug)) { LogDispatcherForwarding(this, message, oldAddress, forwardingAddress, failedOperation, message.ForwardCount, exception); } MessagingProcessingInstruments.OnDispatcherMessageForwared(message); } internal void OnDispatcherForwardingFailed(Message message, GrainAddress oldAddress, SiloAddress forwardingAddress, string failedOperation, Exception exception) { if (this.IsEnabled(DispatcherForwardingFailedEventName)) { this.Write(DispatcherForwardingFailedEventName, new { Message = message, OldAddress = oldAddress, ForwardingAddress = forwardingAddress, FailedOperation = failedOperation, Exception = exception }); } LogDispatcherForwardingFailed(this, message, oldAddress, forwardingAddress, failedOperation, message.ForwardCount, exception); } internal void OnDispatcherForwardingMultiple(int messageCount, GrainAddress oldAddress, SiloAddress forwardingAddress, string failedOperation, Exception exception) { if (this.IsEnabled(DispatcherForwardingMultipleEventName)) { this.Write(DispatcherForwardingMultipleEventName, new { MessageCount = messageCount, OldAddress = oldAddress, ForwardingAddress = forwardingAddress, FailedOperation = failedOperation, Exception = exception }); } if (this.IsEnabled(LogLevel.Debug)) { LogDispatcherForwardingMultiple(this, messageCount, oldAddress, forwardingAddress, failedOperation, exception); } } internal void OnDispatcherSelectTargetFailed(Message message, Exception exception) { if (this.IsEnabled(DispatcherSelectTargetFailedEventName)) { this.Write(DispatcherSelectTargetFailedEventName, new { Message = message, Exception = exception }); } if (ShouldLogError(exception)) { LogDispatcherSelectTargetFailed(this, message, exception); } MessagingProcessingInstruments.OnDispatcherMessageProcessedError(message); static bool ShouldLogError(Exception ex) { return !(ex.GetBaseException() is KeyNotFoundException) && !(ex.GetBaseException() is ClientNotAvailableException); } } } } ================================================ FILE: src/Orleans.Runtime/Networking/ConnectionListener.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; namespace Orleans.Runtime.Messaging { internal abstract partial class ConnectionListener { private readonly IConnectionListenerFactory listenerFactory; private readonly ConnectionManager connectionManager; protected readonly ConcurrentDictionary connections = new(ReferenceEqualsComparer.Default); private readonly ConnectionCommon connectionShared; private Task acceptLoopTask; private IConnectionListener listener; private ConnectionDelegate connectionDelegate; protected ConnectionListener( IConnectionListenerFactory listenerFactory, IOptions connectionOptions, ConnectionManager connectionManager, ConnectionCommon connectionShared) { this.listenerFactory = listenerFactory; this.connectionManager = connectionManager; this.ConnectionOptions = connectionOptions.Value; this.connectionShared = connectionShared; } public abstract EndPoint Endpoint { get; } protected IServiceProvider ServiceProvider => this.connectionShared.ServiceProvider; protected NetworkingTrace NetworkingTrace => this.connectionShared.NetworkingTrace; protected ConnectionOptions ConnectionOptions { get; } protected abstract Connection CreateConnection(ConnectionContext context); protected ConnectionDelegate ConnectionDelegate { get { if (this.connectionDelegate != null) return this.connectionDelegate; lock (this) { if (this.connectionDelegate != null) return this.connectionDelegate; // Configure the connection builder using the user-defined options. var connectionBuilder = new ConnectionBuilder(this.ServiceProvider); connectionBuilder.Use(next => { return context => { context.Features.Set(new UnderlyingConnectionTransportFeature { Transport = context.Transport }); return next(context); }; }); this.ConfigureConnectionBuilder(connectionBuilder); Connection.ConfigureBuilder(connectionBuilder); return this.connectionDelegate = connectionBuilder.Build(); } } } protected virtual void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { } protected async Task BindAsync() { this.listener = await this.listenerFactory.BindAsync(this.Endpoint); } protected void Start() { if (this.listener is null) throw new InvalidOperationException("Listener is not bound"); acceptLoopTask = RunAcceptLoop(); } private async Task RunAcceptLoop() { await Task.Yield(); try { while (true) { var context = await this.listener.AcceptAsync(); if (context == null) break; var connection = this.CreateConnection(context); this.StartConnection(connection); } } catch (Exception exception) { LogCriticalExceptionInAcceptAsync(this.NetworkingTrace, exception); } } protected async Task StopAsync(CancellationToken cancellationToken) { try { await listener.UnbindAsync(cancellationToken); if (acceptLoopTask is not null) { await acceptLoopTask; } var closeTasks = new List(); foreach (var kv in connections) { closeTasks.Add(kv.Key.CloseAsync(exception: null)); } if (closeTasks.Count > 0) { await Task.WhenAll(closeTasks).WaitAsync(cancellationToken).SuppressThrowing(); } await this.connectionManager.Closed; await this.listener.DisposeAsync(); } catch (Exception exception) { LogWarningExceptionDuringShutdown(this.NetworkingTrace, exception); } } private void StartConnection(Connection connection) { connections.TryAdd(connection, null); ThreadPool.UnsafeQueueUserWorkItem(state => { var (t, connection) = ((ConnectionListener, Connection))state; t.RunConnectionAsync(connection).Ignore(); }, (this, connection)); } private async Task RunConnectionAsync(Connection connection) { using (this.BeginConnectionScope(connection)) { try { await connection.Run(); LogInformationConnectionTerminated(this.NetworkingTrace, connection); } catch (Exception exception) { LogInformationConnectionTerminatedWithException(this.NetworkingTrace, exception, connection); } finally { this.connections.TryRemove(connection, out _); } } } private IDisposable BeginConnectionScope(Connection connection) { if (this.NetworkingTrace.IsEnabled(LogLevel.Critical)) { return this.NetworkingTrace.BeginScope(new ConnectionLogScope(connection)); } return null; } [LoggerMessage( Level = LogLevel.Critical, Message = "Exception in AcceptAsync" )] private static partial void LogCriticalExceptionInAcceptAsync(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception during shutdown" )] private static partial void LogWarningExceptionDuringShutdown(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Information, Message = "Connection {Connection} terminated" )] private static partial void LogInformationConnectionTerminated(ILogger logger, Connection connection); [LoggerMessage( Level = LogLevel.Information, Message = "Connection {Connection} terminated with an exception" )] private static partial void LogInformationConnectionTerminatedWithException(ILogger logger, Exception exception, Connection connection); } } ================================================ FILE: src/Orleans.Runtime/Networking/GatewayConnectionListener.cs ================================================ using System; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Messaging { internal sealed class GatewayConnectionListener : ConnectionListener, ILifecycleParticipant, ILifecycleObserver { internal static readonly object ServicesKey = new object(); private readonly ILocalSiloDetails localSiloDetails; private readonly MessageCenter messageCenter; private readonly ConnectionCommon connectionShared; private readonly ConnectionPreambleHelper connectionPreambleHelper; private readonly ILogger logger; private readonly EndpointOptions endpointOptions; private readonly SiloConnectionOptions siloConnectionOptions; private readonly OverloadDetector overloadDetector; private readonly Gateway gateway; public GatewayConnectionListener( IServiceProvider serviceProvider, IOptions connectionOptions, IOptions siloConnectionOptions, OverloadDetector overloadDetector, ILocalSiloDetails localSiloDetails, IOptions endpointOptions, MessageCenter messageCenter, ConnectionManager connectionManager, ConnectionCommon connectionShared, ConnectionPreambleHelper connectionPreambleHelper, ILogger logger) : base(serviceProvider.GetRequiredKeyedService(ServicesKey), connectionOptions, connectionManager, connectionShared) { this.siloConnectionOptions = siloConnectionOptions.Value; this.overloadDetector = overloadDetector; this.gateway = messageCenter.Gateway; this.localSiloDetails = localSiloDetails; this.messageCenter = messageCenter; this.connectionShared = connectionShared; this.connectionPreambleHelper = connectionPreambleHelper; this.logger = logger; this.endpointOptions = endpointOptions.Value; } public override EndPoint Endpoint => this.endpointOptions.GetListeningProxyEndpoint(); protected override Connection CreateConnection(ConnectionContext context) { return new GatewayInboundConnection( context, this.ConnectionDelegate, this.gateway, this.overloadDetector, this.localSiloDetails, this.ConnectionOptions, this.messageCenter, this.connectionShared, this.connectionPreambleHelper); } protected override void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { var configureDelegate = (SiloConnectionOptions.ISiloConnectionBuilderOptions)this.siloConnectionOptions; configureDelegate.ConfigureGatewayInboundBuilder(connectionBuilder); base.ConfigureConnectionBuilder(connectionBuilder); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { if (this.Endpoint is null) return; lifecycle.Subscribe(nameof(GatewayConnectionListener), ServiceLifecycleStage.RuntimeInitialize - 1, this); lifecycle.Subscribe(nameof(GatewayConnectionListener), ServiceLifecycleStage.Active, _ => Task.Run(Start)); } Task ILifecycleObserver.OnStart(CancellationToken ct) => Task.Run(BindAsync); Task ILifecycleObserver.OnStop(CancellationToken ct) => Task.Run(() => StopAsync(ct)); } } ================================================ FILE: src/Orleans.Runtime/Networking/GatewayInboundConnection.cs ================================================ using System; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Messaging; namespace Orleans.Runtime.Messaging { internal sealed partial class GatewayInboundConnection : Connection { private readonly MessageCenter messageCenter; private readonly ConnectionPreambleHelper connectionPreambleHelper; private readonly ConnectionOptions connectionOptions; private readonly Gateway gateway; private readonly OverloadDetector overloadDetector; private readonly SiloAddress myAddress; private readonly string myClusterId; public GatewayInboundConnection( ConnectionContext connection, ConnectionDelegate middleware, Gateway gateway, OverloadDetector overloadDetector, ILocalSiloDetails siloDetails, ConnectionOptions connectionOptions, MessageCenter messageCenter, ConnectionCommon connectionShared, ConnectionPreambleHelper connectionPreambleHelper) : base(connection, middleware, connectionShared) { this.connectionOptions = connectionOptions; this.gateway = gateway; this.overloadDetector = overloadDetector; this.messageCenter = messageCenter; this.connectionPreambleHelper = connectionPreambleHelper; this.myAddress = siloDetails.SiloAddress; this.myClusterId = siloDetails.ClusterId; } protected override ConnectionDirection ConnectionDirection => ConnectionDirection.GatewayToClient; protected override IMessageCenter MessageCenter => this.messageCenter; protected override void RecordMessageReceive(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageReceive(msg, numTotalBytes, headerBytes, ConnectionDirection); GatewayInstruments.GatewayReceived.Add(1); } protected override void RecordMessageSend(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageSend(msg, numTotalBytes, headerBytes, ConnectionDirection); GatewayInstruments.GatewaySent.Add(1); } protected override void OnReceivedMessage(Message msg) { // Don't process messages that have already timed out if (msg.IsExpired) { this.MessagingTrace.OnDropExpiredMessage(msg, MessagingInstruments.Phase.Receive); return; } // Are we overloaded? if (this.overloadDetector.IsOverloaded) { MessagingInstruments.OnRejectedMessage(msg); Message rejection = this.MessageFactory.CreateRejectionResponse(msg, Message.RejectionTypes.GatewayTooBusy, "Shedding load"); this.messageCenter.TryDeliverToProxy(rejection); LogRejectingRequestDueToOverloading(this.Log, msg); GatewayInstruments.GatewayLoadShedding.Add(1); return; } SiloAddress targetAddress = this.gateway.TryToReroute(msg); msg.SendingSilo = this.myAddress; if (targetAddress is null) { // reroute via Dispatcher msg.TargetSilo = null; if (SystemTargetGrainId.TryParse(msg.TargetGrain, out var systemTargetId)) { msg.TargetSilo = this.myAddress; msg.TargetGrain = systemTargetId.WithSiloAddress(this.myAddress).GrainId; } MessagingInstruments.OnMessageReRoute(msg); this.messageCenter.RerouteMessage(msg); } else { // send directly msg.TargetSilo = targetAddress; if (SystemTargetGrainId.TryParse(msg.TargetGrain, out var systemTargetId)) { msg.TargetGrain = systemTargetId.WithSiloAddress(targetAddress).GrainId; } this.messageCenter.SendMessage(msg); } } protected override async Task RunInternal() { var preamble = await connectionPreambleHelper.Read(this.Context); await connectionPreambleHelper.Write( this.Context, new ConnectionPreamble { NodeIdentity = Constants.SiloDirectConnectionId, NetworkProtocolVersion = this.connectionOptions.ProtocolVersion, SiloAddress = this.myAddress, ClusterId = this.myClusterId }); if (!ClientGrainId.TryParse(preamble.NodeIdentity, out var clientId)) { throw new InvalidOperationException($"Unexpected connection id {preamble.NodeIdentity} on proxy endpoint from {preamble.SiloAddress?.ToString() ?? "unknown silo"}"); } if (preamble.ClusterId != this.myClusterId) { throw new InvalidOperationException($@"Unexpected cluster id ""{preamble.ClusterId}"", expected ""{this.myClusterId}"""); } try { this.gateway.RecordOpenedConnection(this, clientId); await base.RunInternal(); } finally { this.gateway.RecordClosedConnection(this); } } protected override bool PrepareMessageForSend(Message msg) { // Don't send messages that have already timed out if (msg.IsExpired) { this.MessagingTrace.OnDropExpiredMessage(msg, MessagingInstruments.Phase.Send); return false; } // Fill in the outbound message with our silo address, if it's not already set msg.SendingSilo ??= this.myAddress; return true; } public void FailMessage(Message msg, string reason) { MessagingInstruments.OnFailedSentMessage(msg); if (msg.Direction == Message.Directions.Request) { LogSiloRejectingMessage(this.Log, this.myAddress, msg, reason); // Done retrying, send back an error instead this.messageCenter.SendRejection( msg, Message.RejectionTypes.Transient, $"Silo {this.myAddress} is rejecting message: {msg}. Reason = {reason}", new SiloUnavailableException()); } else { LogSiloDroppingMessage(this.Log, this.myAddress, msg, reason); MessagingInstruments.OnDroppedSentMessage(msg); } } protected override void RetryMessage(Message msg, Exception ex = null) { if (msg == null) return; if (msg.RetryCount < MessagingOptions.DEFAULT_MAX_MESSAGE_SEND_RETRIES) { msg.RetryCount++; this.messageCenter.SendMessage(msg); } else { var reason = new StringBuilder("Retry count exceeded. "); if (ex != null) { reason.Append("Original exception is: ").Append(ex.ToString()); } reason.Append("Msg is: ").Append(msg); FailMessage(msg, reason.ToString()); } } protected override void OnSendMessageFailure(Message message, string error) { this.FailMessage(message, error); } [LoggerMessage( Level = LogLevel.Debug, Message = "Rejecting a request due to overloading: {Message}" )] private static partial void LogRejectingRequestDueToOverloading(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.MessagingSendingRejection, Message = "Silo {SiloAddress} is rejecting message: {Message}. Reason = {Reason}" )] private static partial void LogSiloRejectingMessage(ILogger logger, SiloAddress siloAddress, Message message, string reason); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.Messaging_OutgoingMS_DroppingMessage, Message = "Silo {SiloAddress} is dropping message: {Message}. Reason = {Reason}" )] private static partial void LogSiloDroppingMessage(ILogger logger, SiloAddress siloAddress, Message message, string reason); } } ================================================ FILE: src/Orleans.Runtime/Networking/ProbeRequestMonitor.cs ================================================ using System; using System.Threading; namespace Orleans.Runtime.Messaging { /// /// Monitors incoming cluster health probe requests /// internal sealed class ProbeRequestMonitor { #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif private ValueStopwatch _probeRequestStopwatch; /// /// Called when this silo receives a health probe request. /// public void OnReceivedProbeRequest() { lock (_lock) { _probeRequestStopwatch.Restart(); } } /// /// The duration which has elapsed since the most recently received health probe request. /// public TimeSpan? ElapsedSinceLastProbeRequest => _probeRequestStopwatch.IsRunning ? (Nullable)_probeRequestStopwatch.Elapsed : null; } } ================================================ FILE: src/Orleans.Runtime/Networking/SiloConnection.cs ================================================ #nullable enable using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Messaging; using Orleans.Serialization.Invocation; namespace Orleans.Runtime.Messaging { internal sealed partial class SiloConnection : Connection { private static readonly Response PingResponse = Response.Completed; private readonly MessageCenter messageCenter; private readonly ConnectionManager connectionManager; private readonly ConnectionOptions connectionOptions; private readonly ProbeRequestMonitor probeMonitor; private readonly ConnectionPreambleHelper connectionPreambleHelper; public SiloConnection( SiloAddress remoteSiloAddress, ConnectionContext connection, ConnectionDelegate middleware, MessageCenter messageCenter, ILocalSiloDetails localSiloDetails, ConnectionManager connectionManager, ConnectionOptions connectionOptions, ConnectionCommon connectionShared, ProbeRequestMonitor probeMonitor, ConnectionPreambleHelper connectionPreambleHelper) : base(connection, middleware, connectionShared) { this.messageCenter = messageCenter; this.connectionManager = connectionManager; this.connectionOptions = connectionOptions; this.probeMonitor = probeMonitor; this.connectionPreambleHelper = connectionPreambleHelper; this.LocalSiloAddress = localSiloDetails.SiloAddress; this.LocalClusterId = localSiloDetails.ClusterId; this.RemoteSiloAddress = remoteSiloAddress; } public SiloAddress RemoteSiloAddress { get; private set; } public SiloAddress LocalSiloAddress { get; } public string LocalClusterId { get; } protected override ConnectionDirection ConnectionDirection => ConnectionDirection.SiloToSilo; protected override IMessageCenter MessageCenter => this.messageCenter; protected override void RecordMessageReceive(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageReceive(msg, numTotalBytes, headerBytes, ConnectionDirection, RemoteSiloAddress); } protected override void RecordMessageSend(Message msg, int numTotalBytes, int headerBytes) { MessagingInstruments.OnMessageSend(msg, numTotalBytes, headerBytes, ConnectionDirection, RemoteSiloAddress); } protected override void OnReceivedMessage(Message msg) { // See it's a Ping message, and if so, short-circuit it if (msg.IsPing()) { this.HandlePingMessage(msg); return; } // sniff message headers for directory cache management this.messageCenter.SniffIncomingMessage?.Invoke(msg); // Don't process messages that have already timed out if (msg.IsExpired) { this.MessagingTrace.OnDropExpiredMessage(msg, MessagingInstruments.Phase.Receive); return; } // If we've stopped application message processing, then filter those out now // Note that if we identify or add other grains that are required for proper stopping, we will need to treat them as we do the membership table grain here. if (messageCenter.IsBlockingApplicationMessages && !msg.IsSystemMessage) { // We reject new requests, and drop all other messages if (msg.Direction != Message.Directions.Request) { this.MessagingTrace.OnDropBlockedApplicationMessage(msg); return; } MessagingInstruments.OnRejectedMessage(msg); var rejection = this.MessageFactory.CreateRejectionResponse(msg, Message.RejectionTypes.Unrecoverable, "Silo stopping", new SiloUnavailableException()); this.Send(rejection); return; } // Make sure the message is for us. Note that some control messages may have no target // information, so a null target silo is OK. if (msg.TargetSilo == null || msg.TargetSilo.Matches(this.LocalSiloAddress)) { messageCenter.ReceiveMessage(msg); return; } if (!msg.TargetSilo.Endpoint.Equals(this.LocalSiloAddress.Endpoint)) { // If the message is for some other silo altogether, then we need to forward it. LogTraceForwardingMessage(this.Log, msg.Id, msg.SendingSilo!, msg.TargetSilo); messageCenter.SendMessage(msg); return; } // If the message was for this endpoint but an older epoch, then reject the message // (if it was a request), or drop it on the floor if it was a response or one-way. if (msg.Direction == Message.Directions.Request) { MessagingInstruments.OnRejectedMessage(msg); var rejection = this.MessageFactory.CreateRejectionResponse( msg, Message.RejectionTypes.Transient, $"The target silo is no longer active: target was {msg.TargetSilo}, but this silo is {LocalSiloAddress}. The rejected message is {msg}."); // Invalidate the remote caller's activation cache entry. if (msg.TargetSilo != null) { rejection.AddToCacheInvalidationHeader(new GrainAddress { GrainId = msg.TargetGrain, SiloAddress = msg.TargetSilo }, validAddress: null); } this.Send(rejection); LogDebugRejectingObsoleteRequest(this.Log, msg.TargetSilo?.ToString() ?? "null", this.LocalSiloAddress.ToString(), msg); } } private void HandlePingMessage(Message msg) { MessagingInstruments.OnPingReceive(msg.SendingSilo); var objectId = RuntimeHelpers.GetHashCode(msg); LogTraceRespondingToPing(this.Log, msg.SendingSilo!, objectId, msg); if (!this.LocalSiloAddress.Equals(msg.TargetSilo)) { // Got ping that is not destined to me. For example, got a ping to my older incarnation. MessagingInstruments.OnRejectedMessage(msg); Message rejection = this.MessageFactory.CreateRejectionResponse(msg, Message.RejectionTypes.Unrecoverable, $"The target silo is no longer active: target was {msg.TargetSilo}, but this silo is {LocalSiloAddress}. The rejected ping message is {msg}."); this.Send(rejection); } else { this.probeMonitor.OnReceivedProbeRequest(); var response = this.MessageFactory.CreateResponseMessage(msg); response.BodyObject = PingResponse; this.Send(response); } } protected override void OnSendMessageFailure(Message message, string error) { if (message.IsPing()) { LogWarningFailedToSendPingMessage(this.Log, message); } this.FailMessage(message, error); } protected override async Task RunInternal() { Exception? error = default; try { await Task.WhenAll(ReadPreamble(), WritePreamble()); await base.RunInternal(); } catch (Exception exception) when ((error = exception) is null) { Debug.Fail("Execution should not be able to reach this point."); } finally { if (this.RemoteSiloAddress is not null) { this.connectionManager.OnConnectionTerminated(this.RemoteSiloAddress, this, error); } } async Task WritePreamble() { await connectionPreambleHelper.Write( this.Context, new ConnectionPreamble { NodeIdentity = Constants.SiloDirectConnectionId, NetworkProtocolVersion = this.connectionOptions.ProtocolVersion, SiloAddress = this.LocalSiloAddress, ClusterId = this.LocalClusterId }); } async Task ReadPreamble() { var preamble = await connectionPreambleHelper.Read(this.Context); if (!preamble.NodeIdentity.Equals(Constants.SiloDirectConnectionId)) { throw new InvalidOperationException("Unexpected client connection on silo endpoint."); } if (preamble.ClusterId != LocalClusterId) { throw new InvalidOperationException($@"Unexpected cluster id ""{preamble.ClusterId}"", expected ""{LocalClusterId}"""); } if (preamble.SiloAddress is not null) { this.RemoteSiloAddress = preamble.SiloAddress; this.connectionManager.OnConnected(preamble.SiloAddress, this); } } } protected override bool PrepareMessageForSend(Message msg) { // Don't send messages that have already timed out if (msg.IsExpired) { this.MessagingTrace.OnDropExpiredMessage(msg, MessagingInstruments.Phase.Send); if (msg.IsPing()) { LogWarningDroppingExpiredPingMessage(this.Log, msg); } return false; } // Fill in the outbound message with our silo address, if it's not already set msg.SendingSilo ??= this.LocalSiloAddress; if (msg.IsPing()) { LogDebugSendingPingMessage(this.Log, msg); } if (this.RemoteSiloAddress is not null && msg.TargetSilo is not null && !this.RemoteSiloAddress.Matches(msg.TargetSilo)) { LogWarningAttemptingToSendMessageToWrongConnection(this.Log, msg.TargetSilo, this.RemoteSiloAddress, msg); } return true; } public void FailMessage(Message msg, string reason) { if (msg.IsPing()) { LogWarningFailedPingMessage(this.Log, msg); } MessagingInstruments.OnFailedSentMessage(msg); if (msg.Direction == Message.Directions.Request) { LogDebugSiloRejectingMessage(this.Log, this.LocalSiloAddress, msg, reason); // Done retrying, send back an error instead this.messageCenter.SendRejection( msg, Message.RejectionTypes.Transient, $"Silo {this.LocalSiloAddress} is rejecting message: {msg}. Reason = {reason}", new SiloUnavailableException()); } else { this.MessagingTrace.OnSiloDropSendingMessage(this.LocalSiloAddress, msg, reason); } } protected override void RetryMessage(Message msg, Exception? ex = null) { if (msg.IsPing()) { LogWarningRetryingPingMessage(this.Log, msg); } if (msg.RetryCount < MessagingOptions.DEFAULT_MAX_MESSAGE_SEND_RETRIES) { ++msg.RetryCount; this.messageCenter.SendMessage(msg); } else { var reason = new StringBuilder("Retry count exceeded. "); if (ex != null) { reason.Append("Original exception is: ").Append(ex.ToString()); } reason.Append("Msg is: ").Append(msg); FailMessage(msg, reason.ToString()); } } [LoggerMessage( Level = LogLevel.Trace, Message = "Forwarding message {MessageId} from {SendingSilo} to silo {TargetSilo}" )] private static partial void LogTraceForwardingMessage(ILogger logger, CorrelationId messageId, SiloAddress sendingSilo, SiloAddress targetSilo); [LoggerMessage( Level = LogLevel.Debug, Message = "Rejecting an obsolete request; target was {TargetSilo}, but this silo is {SiloAddress}. The rejected message is {Message}." )] private static partial void LogDebugRejectingObsoleteRequest(ILogger logger, string targetSilo, string siloAddress, Message message); [LoggerMessage( Level = LogLevel.Trace, Message = "Responding to Ping from {Silo} with object id {ObjectId}. Message {Message}" )] private static partial void LogTraceRespondingToPing(ILogger logger, SiloAddress silo, int objectId, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to send ping message {Message}" )] private static partial void LogWarningFailedToSendPingMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Dropping expired ping message {Message}" )] private static partial void LogWarningDroppingExpiredPingMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Debug, Message = "Sending ping message {Message}" )] private static partial void LogDebugSendingPingMessage(ILogger logger, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Attempting to send message addressed to {TargetSilo} to connection with {RemoteSiloAddress}. Message {Message}" )] private static partial void LogWarningAttemptingToSendMessageToWrongConnection(ILogger logger, SiloAddress targetSilo, SiloAddress remoteSiloAddress, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed ping message {Message}" )] private static partial void LogWarningFailedPingMessage(ILogger logger, Message message); [LoggerMessage( EventId = (int)ErrorCode.MessagingSendingRejection, Level = LogLevel.Debug, Message = "Silo {SiloAddress} is rejecting message: {Message}. Reason = {Reason}" )] private static partial void LogDebugSiloRejectingMessage(ILogger logger, SiloAddress siloAddress, Message message, string reason); [LoggerMessage( Level = LogLevel.Warning, Message = "Retrying ping message {Message}" )] private static partial void LogWarningRetryingPingMessage(ILogger logger, Message message); } } ================================================ FILE: src/Orleans.Runtime/Networking/SiloConnectionFactory.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Messaging { internal sealed class SiloConnectionFactory : ConnectionFactory { internal static readonly object ServicesKey = new object(); private readonly ILocalSiloDetails localSiloDetails; private readonly ConnectionCommon connectionShared; private readonly ProbeRequestMonitor probeRequestMonitor; private readonly ConnectionPreambleHelper connectionPreambleHelper; private readonly IServiceProvider serviceProvider; private readonly SiloConnectionOptions siloConnectionOptions; #if NET9_0_OR_GREATER private readonly Lock initializationLock = new(); #else private readonly object initializationLock = new(); #endif private bool isInitialized; private ConnectionManager connectionManager; private MessageCenter messageCenter; private ISiloStatusOracle siloStatusOracle; public SiloConnectionFactory( IServiceProvider serviceProvider, IOptions connectionOptions, IOptions siloConnectionOptions, ILocalSiloDetails localSiloDetails, ConnectionCommon connectionShared, ProbeRequestMonitor probeRequestMonitor, ConnectionPreambleHelper connectionPreambleHelper) : base(serviceProvider.GetRequiredKeyedService(ServicesKey), serviceProvider, connectionOptions) { this.serviceProvider = serviceProvider; this.siloConnectionOptions = siloConnectionOptions.Value; this.localSiloDetails = localSiloDetails; this.connectionShared = connectionShared; this.probeRequestMonitor = probeRequestMonitor; this.connectionPreambleHelper = connectionPreambleHelper; } public override ValueTask ConnectAsync(SiloAddress address, CancellationToken cancellationToken) { EnsureInitialized(); if (this.siloStatusOracle.IsDeadSilo(address)) { throw new ConnectionAbortedException($"Denying connection to known-dead silo {address}"); } return base.ConnectAsync(address, cancellationToken); } protected override Connection CreateConnection(SiloAddress address, ConnectionContext context) { EnsureInitialized(); return new SiloConnection( address, context, this.ConnectionDelegate, this.messageCenter, this.localSiloDetails, this.connectionManager, this.ConnectionOptions, this.connectionShared, this.probeRequestMonitor, this.connectionPreambleHelper); } protected override void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { var configureDelegate = (SiloConnectionOptions.ISiloConnectionBuilderOptions)this.siloConnectionOptions; configureDelegate.ConfigureSiloOutboundBuilder(connectionBuilder); base.ConfigureConnectionBuilder(connectionBuilder); } private void EnsureInitialized() { if (!isInitialized) { lock (this.initializationLock) { if (!isInitialized) { this.messageCenter = this.serviceProvider.GetRequiredService(); this.connectionManager = this.serviceProvider.GetRequiredService(); this.siloStatusOracle = this.serviceProvider.GetRequiredService(); this.isInitialized = true; } } } } } } ================================================ FILE: src/Orleans.Runtime/Networking/SiloConnectionListener.cs ================================================ using System; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Messaging { internal sealed class SiloConnectionListener : ConnectionListener, ILifecycleParticipant, ILifecycleObserver { internal static readonly object ServicesKey = new object(); private readonly ILocalSiloDetails localSiloDetails; private readonly SiloConnectionOptions siloConnectionOptions; private readonly MessageCenter messageCenter; private readonly EndpointOptions endpointOptions; private readonly ConnectionManager connectionManager; private readonly ConnectionCommon connectionShared; private readonly ProbeRequestMonitor probeRequestMonitor; private readonly ConnectionPreambleHelper connectionPreambleHelper; public SiloConnectionListener( IServiceProvider serviceProvider, IOptions connectionOptions, IOptions siloConnectionOptions, MessageCenter messageCenter, IOptions endpointOptions, ILocalSiloDetails localSiloDetails, ConnectionManager connectionManager, ConnectionCommon connectionShared, ProbeRequestMonitor probeRequestMonitor, ConnectionPreambleHelper connectionPreambleHelper) : base(serviceProvider.GetRequiredKeyedService(ServicesKey), connectionOptions, connectionManager, connectionShared) { this.siloConnectionOptions = siloConnectionOptions.Value; this.messageCenter = messageCenter; this.localSiloDetails = localSiloDetails; this.connectionManager = connectionManager; this.connectionShared = connectionShared; this.probeRequestMonitor = probeRequestMonitor; this.connectionPreambleHelper = connectionPreambleHelper; this.endpointOptions = endpointOptions.Value; } public override EndPoint Endpoint => this.endpointOptions.GetListeningSiloEndpoint(); protected override Connection CreateConnection(ConnectionContext context) { return new SiloConnection( default, context, this.ConnectionDelegate, this.messageCenter, this.localSiloDetails, this.connectionManager, this.ConnectionOptions, this.connectionShared, this.probeRequestMonitor, this.connectionPreambleHelper); } protected override void ConfigureConnectionBuilder(IConnectionBuilder connectionBuilder) { var configureDelegate = (SiloConnectionOptions.ISiloConnectionBuilderOptions)this.siloConnectionOptions; configureDelegate.ConfigureSiloInboundBuilder(connectionBuilder); base.ConfigureConnectionBuilder(connectionBuilder); } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { if (this.Endpoint is null) return; lifecycle.Subscribe(nameof(SiloConnectionListener), ServiceLifecycleStage.RuntimeInitialize - 1, this); } Task ILifecycleObserver.OnStart(CancellationToken ct) => Task.Run(async () => { await BindAsync(); // Start accepting connections immediately. Start(); }); Task ILifecycleObserver.OnStop(CancellationToken ct) => Task.Run(() => StopAsync(ct)); } } ================================================ FILE: src/Orleans.Runtime/Networking/SiloConnectionMaintainer.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.Messaging { internal partial class SiloConnectionMaintainer : ILifecycleParticipant, ISiloStatusListener, ILifecycleObserver { private readonly ConnectionManager connectionManager; private readonly ISiloStatusOracle siloStatusOracle; private readonly ILogger log; public SiloConnectionMaintainer( ConnectionManager connectionManager, ISiloStatusOracle siloStatusOracle, ILogger log) { this.connectionManager = connectionManager; this.siloStatusOracle = siloStatusOracle; this.log = log; } public Task OnStart(CancellationToken ct) { this.siloStatusOracle.SubscribeToSiloStatusEvents(this); return Task.CompletedTask; } public Task OnStop(CancellationToken ct) { this.siloStatusOracle.UnSubscribeFromSiloStatusEvents(this); return Task.CompletedTask; } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(nameof(SiloConnectionMaintainer), ServiceLifecycleStage.RuntimeInitialize, this); } public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { if (status == SiloStatus.Dead && updatedSilo != siloStatusOracle.SiloAddress) { _ = Task.Run(() => this.CloseConnectionAsync(updatedSilo)); } } private async Task CloseConnectionAsync(SiloAddress silo) { try { // Allow a short grace period to complete sending pending messages (eg, gossip responses) await Task.Delay(TimeSpan.FromSeconds(10)); await this.connectionManager.CloseAsync(silo); } catch (Exception exception) { LogExceptionWhileClosingConnections(this.log, silo, exception); } } [LoggerMessage( Level = LogLevel.Information, EventId = 1001, Message = "Exception while closing connections to defunct silo {SiloAddress}" )] private static partial void LogExceptionWhileClosingConnections(ILogger logger, SiloAddress siloAddress, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Orleans.Runtime.csproj ================================================ Microsoft.Orleans.Runtime Microsoft Orleans Runtime Core runtime library of Microsoft Orleans that hosts and executes grains within a silo. $(DefaultTargetFrameworks) true README.md ================================================ FILE: src/Orleans.Runtime/Placement/ActivationCountPlacementDirector.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Placement { internal class ActivationCountPlacementDirector : RandomPlacementDirector, ISiloStatisticsChangeListener, IPlacementDirector { private class CachedLocalStat { private int _activationCount; internal CachedLocalStat(SiloRuntimeStatistics siloStats) => SiloStats = siloStats; public SiloRuntimeStatistics SiloStats { get; } public int ActivationCount => _activationCount; public void IncrementActivationCount(int delta) => Interlocked.Add(ref _activationCount, delta); } // Track created activations on this silo between statistic intervals. private readonly ConcurrentDictionary _localCache = new(); private readonly SiloAddress _localAddress; private readonly int _chooseHowMany; public ActivationCountPlacementDirector( ILocalSiloDetails localSiloDetails, DeploymentLoadPublisher deploymentLoadPublisher, IOptions options) { _localAddress = localSiloDetails.SiloAddress; _chooseHowMany = options.Value.ChooseOutOf; if (_chooseHowMany <= 0) throw new ArgumentException($"{nameof(ActivationCountBasedPlacementOptions)}.{nameof(ActivationCountBasedPlacementOptions.ChooseOutOf)} is {_chooseHowMany}. It must be greater than zero."); deploymentLoadPublisher?.SubscribeToStatisticsChangeEvents(this); } private SiloAddress SelectSiloPowerOfK(SiloAddress[] silos) { if (silos.Length == 0) { throw new SiloUnavailableException("Unable to select a candidate because there are no compatible silos."); } // Exclude overloaded and non-compatible silos var relevantSilos = new List>(); var totalSilos = _localCache.Count; var compatibleSilosWithoutStats = 0; SiloAddress sampledCompatibleSiloWithoutStats = default; foreach (var silo in silos) { if (!_localCache.TryGetValue(silo, out var localSiloStat)) { compatibleSilosWithoutStats++; if (compatibleSilosWithoutStats == 1 || Random.Shared.Next(compatibleSilosWithoutStats) == 0) { sampledCompatibleSiloWithoutStats = silo; } continue; } if (localSiloStat.SiloStats.IsOverloaded) continue; relevantSilos.Add(new(silo, localSiloStat)); } if (relevantSilos.Count > 0) { int chooseFrom = Math.Min(relevantSilos.Count, _chooseHowMany); var chooseFromThoseSilos = new List>(chooseFrom); while (chooseFromThoseSilos.Count < chooseFrom) { int index = Random.Shared.Next(relevantSilos.Count); var pickedSilo = relevantSilos[index]; relevantSilos.RemoveAt(index); chooseFromThoseSilos.Add(pickedSilo); } KeyValuePair minLoadedSilo = default; var minLoad = int.MaxValue; foreach (var s in chooseFromThoseSilos) { var load = s.Value.ActivationCount + s.Value.SiloStats.RecentlyUsedActivationCount; if (load < minLoad) { minLoadedSilo = s; minLoad = load; } } // Increment placement by number of silos instead of by one. // This is our trick to get more balanced placement, accounting to the probable // case when multiple silos place on the same silo at the same time, before stats are refreshed. minLoadedSilo.Value.IncrementActivationCount(totalSilos); return minLoadedSilo.Key; } // Some compatible silos might not have published statistics yet. if (compatibleSilosWithoutStats > 0) { return sampledCompatibleSiloWithoutStats; } // All compatible silos have published stats and are overloaded. var allSiloStats = _localCache.ToList(); throw new SiloUnavailableException( $"Unable to select a candidate from {silos.Length} compatible silos (all are overloaded). All silo stats: {Utils.EnumerableToString(allSiloStats, kvp => $"SiloAddress = {kvp.Key} -> IsOverloaded = {kvp.Value.SiloStats.IsOverloaded}, ActivationCount = {kvp.Value.ActivationCount}, RecentlyUsedActivationCount = {kvp.Value.SiloStats.RecentlyUsedActivationCount}")}"); } public override Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) => Task.FromResult(OnAddActivationInternal(target, context)); private SiloAddress OnAddActivationInternal(PlacementTarget target, IPlacementContext context) { var compatibleSilos = context.GetCompatibleSilos(target); // If a valid placement hint was specified, use it. if (IPlacementDirector.GetPlacementHint(target.RequestContextData, compatibleSilos) is { } placementHint) { return placementHint; } // If the cache was not populated, place locally only if this silo is compatible. if (_localCache.IsEmpty) { if (compatibleSilos.Contains(_localAddress)) { return _localAddress; } return SelectSiloPowerOfK(compatibleSilos); } return SelectSiloPowerOfK(compatibleSilos); } public void SiloStatisticsChangeNotification(SiloAddress updatedSilo, SiloRuntimeStatistics newSiloStats) { // just create a new empty CachedLocalStat and throw the old one. _localCache[updatedSilo] = new(newSiloStats); } public void RemoveSilo(SiloAddress removedSilo) { _localCache.TryRemove(removedSilo, out _); } } } ================================================ FILE: src/Orleans.Runtime/Placement/ClientObserverPlacementStrategyResolver.cs ================================================ using Orleans.Metadata; namespace Orleans.Runtime.Placement { internal class ClientObserverPlacementStrategyResolver : IPlacementStrategyResolver { private readonly ClientObserversPlacement _strategy = new ClientObserversPlacement(); public bool TryResolvePlacementStrategy(GrainType grainType, GrainProperties properties, out PlacementStrategy result) { if (grainType.IsClient()) { result = _strategy; return true; } result = default; return false; } } } ================================================ FILE: src/Orleans.Runtime/Placement/ClientObserversPlacementDirector.cs ================================================ using System.Threading.Tasks; namespace Orleans.Runtime.Placement { /// /// ClientObserversPlacementDirector is used to prevent placement of client observer activations. /// internal class ClientObserversPlacementDirector : IPlacementDirector { public Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) => throw new ClientNotAvailableException(target.GrainIdentity); } } ================================================ FILE: src/Orleans.Runtime/Placement/DeploymentLoadPublisher.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Scheduler; using Orleans.Statistics; namespace Orleans.Runtime { /// /// This class collects runtime statistics for all silos in the current deployment for use by placement. /// internal sealed partial class DeploymentLoadPublisher : SystemTarget, IDeploymentLoadPublisher, ISiloStatusListener, ILifecycleParticipant { private readonly ILocalSiloDetails _siloDetails; private readonly ISiloStatusOracle _siloStatusOracle; private readonly IInternalGrainFactory _grainFactory; private readonly ActivationDirectory _activationDirectory; private readonly IActivationWorkingSet _activationWorkingSet; private readonly IEnvironmentStatisticsProvider _environmentStatisticsProvider; private readonly IOptions _loadSheddingOptions; private readonly ConcurrentDictionary _periodicStats; private readonly TimeSpan _statisticsRefreshTime; private readonly List _siloStatisticsChangeListeners; private readonly ILogger _logger; private long _lastUpdateDateTimeTicks; private IDisposable _publishTimer; public ConcurrentDictionary PeriodicStatistics => _periodicStats; public SiloRuntimeStatistics LocalRuntimeStatistics { get; private set; } public DeploymentLoadPublisher( ILocalSiloDetails siloDetails, ISiloStatusOracle siloStatusOracle, IOptions options, IInternalGrainFactory grainFactory, ILoggerFactory loggerFactory, ActivationDirectory activationDirectory, IActivationWorkingSet activationWorkingSet, IEnvironmentStatisticsProvider environmentStatisticsProvider, IOptions loadSheddingOptions, SystemTargetShared shared) : base(Constants.DeploymentLoadPublisherSystemTargetType, shared) { _logger = loggerFactory.CreateLogger(); _siloDetails = siloDetails; _siloStatusOracle = siloStatusOracle; _grainFactory = grainFactory; _activationDirectory = activationDirectory; _activationWorkingSet = activationWorkingSet; _environmentStatisticsProvider = environmentStatisticsProvider; _loadSheddingOptions = loadSheddingOptions; _statisticsRefreshTime = options.Value.DeploymentLoadPublisherRefreshTime; _periodicStats = new ConcurrentDictionary(); _siloStatisticsChangeListeners = new List(); siloStatusOracle.SubscribeToSiloStatusEvents(this); shared.ActivationDirectory.RecordNewTarget(this); } private async Task StartAsync(CancellationToken cancellationToken) { LogDebugStartingDeploymentLoadPublisher(_logger); if (_statisticsRefreshTime > TimeSpan.Zero) { // Randomize PublishStatistics timer, // but also upon start publish my stats to everyone and take everyone's stats for me to start with something. var randomTimerOffset = RandomTimeSpan.Next(_statisticsRefreshTime); _publishTimer = RegisterTimer( static state => ((DeploymentLoadPublisher)state).PublishStatistics(), this, randomTimerOffset, _statisticsRefreshTime); } await RefreshClusterStatistics(); await PublishStatistics(); LogDebugStartedDeploymentLoadPublisher(_logger); } private async Task PublishStatistics() { try { LogTracePublishStatistics(_logger); // Ensure that our timestamp is monotonically increasing. var ticks = _lastUpdateDateTimeTicks = Math.Max(_lastUpdateDateTimeTicks + 1, DateTime.UtcNow.Ticks); var myStats = new SiloRuntimeStatistics( _activationDirectory.Count, _activationWorkingSet.Count, _environmentStatisticsProvider, _loadSheddingOptions, new DateTime(ticks, DateTimeKind.Utc)); // Update statistics locally. LocalRuntimeStatistics = myStats; UpdateRuntimeStatisticsInternal(_siloDetails.SiloAddress, myStats); // Inform other cluster members about our refreshed statistics. var members = _siloStatusOracle.GetApproximateSiloStatuses(true).Keys; var tasks = new List(members.Count); foreach (var siloAddress in members) { // No need to make a grain call to ourselves. if (siloAddress == _siloDetails.SiloAddress) { continue; } try { var deploymentLoadPublisher = _grainFactory.GetSystemTarget(Constants.DeploymentLoadPublisherSystemTargetType, siloAddress); tasks.Add(deploymentLoadPublisher.UpdateRuntimeStatistics(_siloDetails.SiloAddress, myStats)); } catch (Exception exception) { LogWarningRuntimeStatisticsUpdateFailure1(_logger, exception); } } await Task.WhenAll(tasks); } catch (Exception exc) { LogWarningRuntimeStatisticsUpdateFailure2(_logger, exc); } } public Task UpdateRuntimeStatistics(SiloAddress siloAddress, SiloRuntimeStatistics siloStats) { UpdateRuntimeStatisticsInternal(siloAddress, siloStats); return Task.CompletedTask; } private void UpdateRuntimeStatisticsInternal(SiloAddress siloAddress, SiloRuntimeStatistics siloStats) { LogTraceUpdateRuntimeStatistics(_logger, siloAddress); if (_siloStatusOracle.GetApproximateSiloStatus(siloAddress) != SiloStatus.Active) { return; } // Take only if newer. if (_periodicStats.TryGetValue(siloAddress, out var old) && old.DateTime > siloStats.DateTime) { return; } _periodicStats[siloAddress] = siloStats; NotifyAllStatisticsChangeEventsSubscribers(siloAddress, siloStats); } internal async Task RefreshClusterStatistics() { LogTraceRefreshStatistics(_logger); await this.RunOrQueueTask(() => { var members = _siloStatusOracle.GetApproximateSiloStatuses(true).Keys; var tasks = new List(members.Count); foreach (var siloAddress in members) { tasks.Add(RefreshSiloStatistics(siloAddress)); } return Task.WhenAll(tasks); }); } private async Task RefreshSiloStatistics(SiloAddress silo) { try { var statistics = await _grainFactory.GetSystemTarget(Constants.SiloControlType, silo).GetRuntimeStatistics(); UpdateRuntimeStatisticsInternal(silo, statistics); } catch (Exception exception) { LogWarningRuntimeStatisticsUpdateFailure3(_logger, exception, silo); } } public bool SubscribeToStatisticsChangeEvents(ISiloStatisticsChangeListener observer) { lock (_siloStatisticsChangeListeners) { if (_siloStatisticsChangeListeners.Contains(observer)) return false; _siloStatisticsChangeListeners.Add(observer); return true; } } public bool UnsubscribeStatisticsChangeEvents(ISiloStatisticsChangeListener observer) { lock (_siloStatisticsChangeListeners) { return _siloStatisticsChangeListeners.Remove(observer); } } private void NotifyAllStatisticsChangeEventsSubscribers(SiloAddress silo, SiloRuntimeStatistics stats) { lock (_siloStatisticsChangeListeners) { foreach (var subscriber in _siloStatisticsChangeListeners) { if (stats == null) { subscriber.RemoveSilo(silo); } else { subscriber.SiloStatisticsChangeNotification(silo, stats); } } } } public void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { WorkItemGroup.QueueAction(() => { Utils.SafeExecute(() => OnSiloStatusChange(updatedSilo, status), _logger); }); } private void OnSiloStatusChange(SiloAddress updatedSilo, SiloStatus status) { if (!status.IsTerminating()) return; _periodicStats.TryRemove(updatedSilo, out _); NotifyAllStatisticsChangeEventsSubscribers(updatedSilo, null); } void ILifecycleParticipant.Participate(ISiloLifecycle observer) { observer.Subscribe( nameof(DeploymentLoadPublisher), ServiceLifecycleStage.RuntimeGrainServices, StartAsync, ct => { _publishTimer.Dispose(); return Task.CompletedTask; }); } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting DeploymentLoadPublisher" )] private static partial void LogDebugStartingDeploymentLoadPublisher(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Started DeploymentLoadPublisher" )] private static partial void LogDebugStartedDeploymentLoadPublisher(ILogger logger); [LoggerMessage( Level = LogLevel.Trace, Message = "PublishStatistics" )] private static partial void LogTracePublishStatistics(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.Placement_RuntimeStatisticsUpdateFailure_1, Level = LogLevel.Warning, Message = "An unexpected exception was thrown by PublishStatistics.UpdateRuntimeStatistics(). Ignored" )] private static partial void LogWarningRuntimeStatisticsUpdateFailure1(ILogger logger, Exception exception); [LoggerMessage( EventId = (int)ErrorCode.Placement_RuntimeStatisticsUpdateFailure_2, Level = LogLevel.Warning, Message = "An exception was thrown by PublishStatistics.UpdateRuntimeStatistics(). Ignoring" )] private static partial void LogWarningRuntimeStatisticsUpdateFailure2(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "UpdateRuntimeStatistics from {Server}" )] private static partial void LogTraceUpdateRuntimeStatistics(ILogger logger, SiloAddress server); [LoggerMessage( Level = LogLevel.Trace, Message = "RefreshStatistics" )] private static partial void LogTraceRefreshStatistics(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.Placement_RuntimeStatisticsUpdateFailure_3, Level = LogLevel.Warning, Message = "An unexpected exception was thrown from RefreshStatistics by ISiloControl.GetRuntimeStatistics({SiloAddress}). Will keep using stale statistics." )] private static partial void LogWarningRuntimeStatisticsUpdateFailure3(ILogger logger, Exception exception, SiloAddress siloAddress); } } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/PlacementFilterDirectorResolver.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; /// /// Responsible for resolving an for a . /// public sealed class PlacementFilterDirectorResolver(IServiceProvider services) { public IPlacementFilterDirector GetFilterDirector(PlacementFilterStrategy placementFilterStrategy) => services.GetRequiredKeyedService(placementFilterStrategy.GetType()); } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/PlacementFilterStrategyResolver.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.Metadata; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; /// /// Responsible for resolving an for a . /// public sealed class PlacementFilterStrategyResolver { private readonly ConcurrentDictionary _resolvedFilters = new(); private readonly Func _getFiltersInternal; private readonly GrainPropertiesResolver _grainPropertiesResolver; private readonly IServiceProvider _services; /// /// Create a instance. /// public PlacementFilterStrategyResolver( IServiceProvider services, GrainPropertiesResolver grainPropertiesResolver) { _services = services; _getFiltersInternal = GetPlacementFilterStrategyInternal; _grainPropertiesResolver = grainPropertiesResolver; } /// /// Gets the placement filter strategy associated with the provided grain type. /// public PlacementFilterStrategy[] GetPlacementFilterStrategies(GrainType grainType) => _resolvedFilters.GetOrAdd(grainType, _getFiltersInternal); private PlacementFilterStrategy[] GetPlacementFilterStrategyInternal(GrainType grainType) { _grainPropertiesResolver.TryGetGrainProperties(grainType, out var properties); if (properties is not null && properties.Properties.TryGetValue(WellKnownGrainTypeProperties.PlacementFilter, out var placementFilterIds) && !string.IsNullOrWhiteSpace(placementFilterIds)) { var filterList = new List(); foreach (var filterId in placementFilterIds.Split(",")) { var filter = _services.GetKeyedService(filterId); if (filter is not null) { filter.Initialize(properties); filterList.Add(filter); } else { throw new KeyNotFoundException($"Could not resolve placement filter strategy {filterId} for grain type {grainType}. Ensure that dependencies for that filter have been configured in the Container. This is often through a .Use* extension method provided by the implementation."); } } var orderedFilters = filterList.OrderBy(f => f.Order).ToArray(); // check that the order is unique if (orderedFilters.Select(f => f.Order).Distinct().Count() != orderedFilters.Length) { throw new InvalidOperationException($"Placement filters for grain type {grainType} have duplicate order values. Order values must be specified if more than one filter is applied and must be unique."); } return orderedFilters; } return []; } } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/PreferredMatchSiloMetadataPlacementFilterAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; /// /// Attribute to specify the preferred match silo metadata placement filter that preferentially filters down to silos where the metadata matches the local (calling) silo metadata. /// /// Ordered set of metadata keys to try to match. The earlier entries are considered less important and will be dropped to find a less-specific match if sufficient more-specific matches do not have enough results. /// The minimum desired candidates to filter. This is to balance meeting the metadata preferences with not overloading a single or small set of silos with activations. Set this to 1 if you only want the best matches, even if there's only one silo that is currently the best match. /// Example: If keys ["first","second"] are specified, then it will attempt to return only silos where both keys match the local silo's metadata values. If there are not sufficient silos matching both, then it will also include silos matching only the second key. Finally, if there are still fewer than minCandidates results then it will include all silos. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [Experimental("ORLEANSEXP004")] public class PreferredMatchSiloMetadataPlacementFilterAttribute(string[] orderedMetadataKeys, int minCandidates = 2, int order = 0) : PlacementFilterAttribute(new PreferredMatchSiloMetadataPlacementFilterStrategy(orderedMetadataKeys, minCandidates, order)); ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/PreferredMatchSiloMetadataPlacementFilterDirector.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Placement; using Orleans.Runtime.MembershipService.SiloMetadata; #nullable enable namespace Orleans.Runtime.Placement.Filtering; internal class PreferredMatchSiloMetadataPlacementFilterDirector( ILocalSiloDetails localSiloDetails, ISiloMetadataCache siloMetadataCache) : IPlacementFilterDirector { public IEnumerable Filter(PlacementFilterStrategy filterStrategy, PlacementTarget target, IEnumerable silos) { var preferredMatchSiloMetadataPlacementFilterStrategy = filterStrategy as PreferredMatchSiloMetadataPlacementFilterStrategy; var minCandidates = preferredMatchSiloMetadataPlacementFilterStrategy?.MinCandidates ?? 1; var orderedMetadataKeys = preferredMatchSiloMetadataPlacementFilterStrategy?.OrderedMetadataKeys ?? []; var localSiloMetadata = siloMetadataCache.GetSiloMetadata(localSiloDetails.SiloAddress).Metadata; if (localSiloMetadata.Count == 0) { return silos; } var siloList = silos.ToList(); if (siloList.Count <= minCandidates) { return siloList; } // return the list of silos that match the most metadata keys. The first key in the list is the least important. // This means that the last key in the list is the most important. // If no silos match any metadata keys, return the original list of silos. var maxScore = 0; var siloScores = new int[siloList.Count]; var scoreCounts = new int[orderedMetadataKeys.Length+1]; for (var i = 0; i < siloList.Count; i++) { var siloMetadata = siloMetadataCache.GetSiloMetadata(siloList[i]).Metadata; var siloScore = 0; for (var j = orderedMetadataKeys.Length - 1; j >= 0; --j) { if (siloMetadata.TryGetValue(orderedMetadataKeys[j], out var siloMetadataValue) && localSiloMetadata.TryGetValue(orderedMetadataKeys[j], out var localSiloMetadataValue) && siloMetadataValue == localSiloMetadataValue) { siloScore = ++siloScores[i]; maxScore = Math.Max(maxScore, siloScore); } else { break; } } scoreCounts[siloScore]++; } if (maxScore == 0) { return siloList; } var candidateCount = 0; var scoreCutOff = orderedMetadataKeys.Length; for (var i = scoreCounts.Length-1; i >= 0; i--) { candidateCount += scoreCounts[i]; if (candidateCount >= minCandidates) { scoreCutOff = i; break; } } return siloList.Where((_, i) => siloScores[i] >= scoreCutOff); } } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/PreferredMatchSiloMetadataPlacementFilterStrategy.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using Orleans.Metadata; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; public class PreferredMatchSiloMetadataPlacementFilterStrategy(string[] orderedMetadataKeys, int minCandidates, int order) : PlacementFilterStrategy(order) { public string[] OrderedMetadataKeys { get; set; } = orderedMetadataKeys; public int MinCandidates { get; set; } = minCandidates; public PreferredMatchSiloMetadataPlacementFilterStrategy() : this([], 2, 0) { } public override void AdditionalInitialize(GrainProperties properties) { var placementFilterGrainProperty = GetPlacementFilterGrainProperty("ordered-metadata-keys", properties); if (placementFilterGrainProperty is null) { throw new ArgumentException("Invalid ordered-metadata-keys property value."); } OrderedMetadataKeys = placementFilterGrainProperty.Split(","); var minCandidatesProperty = GetPlacementFilterGrainProperty("min-candidates", properties); if (!int.TryParse(minCandidatesProperty, out var parsedMinCandidates)) { throw new ArgumentException("Invalid min-candidates property value."); } MinCandidates = parsedMinCandidates; } protected override IEnumerable> GetAdditionalGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, IReadOnlyDictionary existingProperties) { yield return new KeyValuePair("ordered-metadata-keys", string.Join(",", OrderedMetadataKeys)); yield return new KeyValuePair("min-candidates", MinCandidates.ToString(CultureInfo.InvariantCulture)); } } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/RequiredMatchSiloMetadataPlacementFilterAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; /// /// Attribute to specify that a silo must have a specific metadata key-value pair matching the local (calling) silo to be considered for placement. /// /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [Experimental("ORLEANSEXP004")] public class RequiredMatchSiloMetadataPlacementFilterAttribute(string[] metadataKeys, int order = 0) : PlacementFilterAttribute(new RequiredMatchSiloMetadataPlacementFilterStrategy(metadataKeys, order)); ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/RequiredMatchSiloMetadataPlacementFilterDirector.cs ================================================ using System.Collections.Generic; using System.Linq; using Orleans.Placement; using Orleans.Runtime.MembershipService.SiloMetadata; #nullable enable namespace Orleans.Runtime.Placement.Filtering; internal class RequiredMatchSiloMetadataPlacementFilterDirector(ILocalSiloDetails localSiloDetails, ISiloMetadataCache siloMetadataCache) : IPlacementFilterDirector { public IEnumerable Filter(PlacementFilterStrategy filterStrategy, PlacementTarget target, IEnumerable silos) { var metadataKeys = (filterStrategy as RequiredMatchSiloMetadataPlacementFilterStrategy)?.MetadataKeys ?? []; // yield return all silos if no silos match any metadata keys if (metadataKeys.Length == 0) { return silos; } var localMetadata = siloMetadataCache.GetSiloMetadata(localSiloDetails.SiloAddress); var localRequiredMetadata = GetMetadata(localMetadata, metadataKeys); return silos.Where(silo => { var remoteMetadata = siloMetadataCache.GetSiloMetadata(silo); return DoesMetadataMatch(localRequiredMetadata, remoteMetadata, metadataKeys); }); } private static bool DoesMetadataMatch(string?[] localMetadata, SiloMetadata siloMetadata, string[] metadataKeys) { for (var i = 0; i < metadataKeys.Length; i++) { if (localMetadata[i] != siloMetadata.Metadata.GetValueOrDefault(metadataKeys[i])) { return false; } } return true; } private static string?[] GetMetadata(SiloMetadata siloMetadata, string[] metadataKeys) { var result = new string?[metadataKeys.Length]; for (var i = 0; i < metadataKeys.Length; i++) { result[i] = siloMetadata.Metadata.GetValueOrDefault(metadataKeys[i]); } return result; } } ================================================ FILE: src/Orleans.Runtime/Placement/Filtering/RequiredMatchSiloMetadataPlacementFilterStrategy.cs ================================================ using System; using System.Collections.Generic; using Orleans.Metadata; using Orleans.Placement; #nullable enable namespace Orleans.Runtime.Placement.Filtering; public class RequiredMatchSiloMetadataPlacementFilterStrategy(string[] metadataKeys, int order) : PlacementFilterStrategy(order) { public string[] MetadataKeys { get; private set; } = metadataKeys; public RequiredMatchSiloMetadataPlacementFilterStrategy() : this([], 0) { } public override void AdditionalInitialize(GrainProperties properties) { var placementFilterGrainProperty = GetPlacementFilterGrainProperty("metadata-keys", properties); if (placementFilterGrainProperty is null) { throw new ArgumentException("Invalid metadata-keys property value."); } MetadataKeys = placementFilterGrainProperty.Split(","); } protected override IEnumerable> GetAdditionalGrainProperties(IServiceProvider services, Type grainClass, GrainType grainType, IReadOnlyDictionary existingProperties) { yield return new KeyValuePair("metadata-keys", String.Join(",", MetadataKeys)); } } ================================================ FILE: src/Orleans.Runtime/Placement/GrainMigratabilityChecker.cs ================================================ using Orleans.Metadata; using Orleans.Placement; using System; using System.Collections.Concurrent; using System.Collections.Frozen; using System.Runtime.CompilerServices; #nullable enable namespace Orleans.Runtime.Placement; internal sealed class GrainMigratabilityChecker( PlacementStrategyResolver strategyResolver, IClusterManifestProvider clusterManifestProvider, TimeProvider timeProvider) { // We override equality and hashcode as this type is used as the dictionary key, // and record structs use default equality comparer, which for an enum is not that great for performance. private readonly record struct StatusKey(GrainType Type, ImmovableKind Kind) : IEquatable { public bool Equals(StatusKey other) => Type == other.Type && Kind == other.Kind; public override int GetHashCode() => HashCode.Combine(Type.GetUniformHashCode(), Kind); } private readonly GrainManifest _localManifest = clusterManifestProvider.LocalGrainManifest; private readonly PlacementStrategyResolver _strategyResolver = strategyResolver; private readonly TimeProvider _timeProvider = timeProvider; private readonly ConcurrentDictionary _migratableStatuses = new(); private FrozenDictionary? _migratableStatusesCache; private long _lastRegeneratedCacheTimestamp = timeProvider.GetTimestamp(); public bool IsMigratable(GrainType grainType, ImmovableKind expectedKind) { var statusKey = new StatusKey(grainType, expectedKind); if (_migratableStatusesCache is { } cache && cache.TryGetValue(statusKey, out var isMigratable)) { return isMigratable; } return IsMigratableRare(grainType, statusKey); bool IsMigratableRare(GrainType grainType, StatusKey statusKey) { // _migratableStatuses holds statuses for each grain type if its migratable type or not, so we can make fast lookups. // since we don't anticipate a huge number of grain *types*, i think its just fine to have this in place as fast-check. if (!_migratableStatuses.TryGetValue(statusKey, out var isMigratable)) { isMigratable = !(grainType.IsClient() || grainType.IsSystemTarget() || grainType.IsGrainService() || IsStatelessWorker(grainType) || IsImmovable(grainType)); _migratableStatuses.TryAdd(statusKey, isMigratable); } // Regenerate the cache periodically. var currentTimestamp = _timeProvider.GetTimestamp(); if (_timeProvider.GetElapsedTime(_lastRegeneratedCacheTimestamp, currentTimestamp) > TimeSpan.FromSeconds(5)) { _migratableStatusesCache = _migratableStatuses.ToFrozenDictionary(); _lastRegeneratedCacheTimestamp = currentTimestamp; } return isMigratable; } [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsStatelessWorker(GrainType grainType) => _strategyResolver.GetPlacementStrategy(grainType).GetType() == typeof(StatelessWorkerPlacement); [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsImmovable(GrainType grainType) { if (_localManifest.Grains.TryGetValue(grainType, out var props)) { // If there is no 'Immovable' property, it is not immovable. if (!props.Properties.TryGetValue(WellKnownGrainTypeProperties.Immovable, out var value)) { return false; } // If the value fails to parse, assume it's immovable. if (!byte.TryParse(value, out var actualKindValue)) { return true; } // It is immovable, but does the kind match with the parameter. return ((ImmovableKind)actualKindValue & expectedKind) == expectedKind; } // Assume unknown grains are immovable. return true; } } } ================================================ FILE: src/Orleans.Runtime/Placement/HashBasedPlacementDirector.cs ================================================ using System.Linq; using System.Threading.Tasks; namespace Orleans.Runtime.Placement { internal class HashBasedPlacementDirector : IPlacementDirector { public virtual Task OnAddActivation( PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { var compatibleSilos = context.GetCompatibleSilos(target); // If a valid placement hint was specified, use it. if (IPlacementDirector.GetPlacementHint(target.RequestContextData, compatibleSilos) is { } placementHint) { return Task.FromResult(placementHint); } var sortedSilos = compatibleSilos.OrderBy(s => s).ToArray(); // need to sort the list, so that the outcome is deterministic int hash = (int) (target.GrainIdentity.GetUniformHashCode() & 0x7fffffff); // reset highest order bit to avoid negative ints return Task.FromResult(sortedSilos[hash % sortedSilos.Length]); } } } ================================================ FILE: src/Orleans.Runtime/Placement/IPlacementStrategyResolver.cs ================================================ using Orleans.Metadata; namespace Orleans.Runtime.Placement { /// /// Associates a with a . /// public interface IPlacementStrategyResolver { /// /// Gets the placement strategy associated with the provided grain type. /// bool TryResolvePlacementStrategy(GrainType grainType, GrainProperties properties, out PlacementStrategy result); } } ================================================ FILE: src/Orleans.Runtime/Placement/ISiloStatisticsChangeListener.cs ================================================ namespace Orleans.Runtime { internal interface ISiloStatisticsChangeListener { /// /// Receive notification when new statistics data arrives. /// /// Updated silo. /// New Silo statistics. void SiloStatisticsChangeNotification(SiloAddress updatedSilo, SiloRuntimeStatistics newStats); void RemoveSilo(SiloAddress removedSilo); } } ================================================ FILE: src/Orleans.Runtime/Placement/PlacementDirectorResolver.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime.Placement { /// /// Responsible for resolving an for a . /// public sealed class PlacementDirectorResolver { private readonly IServiceProvider _services; public PlacementDirectorResolver(IServiceProvider services) { _services = services; } public IPlacementDirector GetPlacementDirector(PlacementStrategy placementStrategy) => _services.GetRequiredKeyedService(placementStrategy.GetType()); } } ================================================ FILE: src/Orleans.Runtime/Placement/PlacementService.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Diagnostics; using Orleans.Placement; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Internal; using Orleans.Runtime.Placement.Filtering; using Orleans.Runtime.Versions; namespace Orleans.Runtime.Placement { /// /// Central point for placement decisions. /// internal partial class PlacementService : IPlacementContext { private const int PlacementWorkerCount = 16; private readonly PlacementStrategyResolver _strategyResolver; private readonly PlacementDirectorResolver _directorResolver; private readonly ILogger _logger; private readonly GrainLocator _grainLocator; private readonly GrainVersionManifest _grainInterfaceVersions; private readonly CachedVersionSelectorManager _versionSelectorManager; private readonly ISiloStatusOracle _siloStatusOracle; private readonly bool _assumeHomogeneousSilosForTesting; private readonly PlacementWorker[] _workers; private readonly PlacementFilterStrategyResolver _filterStrategyResolver; private readonly PlacementFilterDirectorResolver _placementFilterDirectoryResolver; /// /// Create a instance. /// public PlacementService( IOptionsMonitor siloMessagingOptions, ILocalSiloDetails localSiloDetails, ISiloStatusOracle siloStatusOracle, ILogger logger, GrainLocator grainLocator, GrainVersionManifest grainInterfaceVersions, CachedVersionSelectorManager versionSelectorManager, PlacementDirectorResolver directorResolver, PlacementStrategyResolver strategyResolver, PlacementFilterStrategyResolver filterStrategyResolver, PlacementFilterDirectorResolver placementFilterDirectoryResolver) { LocalSilo = localSiloDetails.SiloAddress; _strategyResolver = strategyResolver; _directorResolver = directorResolver; _filterStrategyResolver = filterStrategyResolver; _placementFilterDirectoryResolver = placementFilterDirectoryResolver; _logger = logger; _grainLocator = grainLocator; _grainInterfaceVersions = grainInterfaceVersions; _versionSelectorManager = versionSelectorManager; _siloStatusOracle = siloStatusOracle; _assumeHomogeneousSilosForTesting = siloMessagingOptions.CurrentValue.AssumeHomogenousSilosForTesting; _workers = new PlacementWorker[PlacementWorkerCount]; for (var i = 0; i < PlacementWorkerCount; i++) { _workers[i] = new(this); } } public SiloAddress LocalSilo { get; } public SiloStatus LocalSiloStatus => _siloStatusOracle.CurrentStatus; /// /// Gets or places an activation. /// public Task AddressMessage(Message message) { if (message.IsTargetFullyAddressed) return Task.CompletedTask; if (message.TargetGrain.IsDefault) ThrowMissingAddress(); var grainId = message.TargetGrain; if (_grainLocator.TryLookupInCache(grainId, out var result) && CachedAddressIsValid(message, result)) { LogDebugFoundAddress(result, grainId, message); SetMessageTargetPlacement(message, result.SiloAddress); return Task.CompletedTask; } LogDebugLookingUpAddress(grainId, message); var worker = _workers[grainId.GetUniformHashCode() % PlacementWorkerCount]; return worker.AddressMessage(message); static void ThrowMissingAddress() => throw new InvalidOperationException("Cannot address a message without a target"); } private void SetMessageTargetPlacement(Message message, SiloAddress targetSilo) { message.TargetSilo = targetSilo; LogTraceAddressMessageSelectTarget(message); } public SiloAddress[] GetCompatibleSilos(PlacementTarget target) { // For test only: if we have silos that are not yet in the Cluster TypeMap, we assume that they are compatible // with the current silo if (_assumeHomogeneousSilosForTesting) { return AllActiveSilos; } var grainType = target.GrainIdentity.Type; var silos = target.InterfaceVersion > 0 ? _versionSelectorManager.GetSuitableSilos(grainType, target.InterfaceType, target.InterfaceVersion).SuitableSilos : _grainInterfaceVersions.GetSupportedSilos(grainType).Result; var compatibleSilos = silos.Intersect(AllActiveSilos).ToArray(); var filters = _filterStrategyResolver.GetPlacementFilterStrategies(grainType); if (filters.Length > 0) { // Capture the parent activity context now so each filter span is parented to the // current activity (e.g. PlaceGrain) rather than to sibling filter spans that may // be active during deferred enumeration. var parentActivityContext = Activity.Current?.Context; IEnumerable filteredSilos = compatibleSilos; foreach (var placementFilter in filters) { var director = _placementFilterDirectoryResolver.GetFilterDirector(placementFilter); filteredSilos = InstrumentFilteredSilos( director.Filter(placementFilter, target, filteredSilos), placementFilter, grainType, parentActivityContext); } compatibleSilos = filteredSilos.ToArray(); } if (compatibleSilos.Length == 0) { var allWithType = _grainInterfaceVersions.GetSupportedSilos(grainType).Result; var versions = _grainInterfaceVersions.GetSupportedSilos(target.InterfaceType, target.InterfaceVersion).Result; var allWithTypeString = string.Join(", ", allWithType.Select(s => s.ToString())) is string withGrain && !string.IsNullOrWhiteSpace(withGrain) ? withGrain : "none"; var allWithInterfaceString = string.Join(", ", versions.Select(s => s.ToString())) is string withIface && !string.IsNullOrWhiteSpace(withIface) ? withIface : "none"; throw new OrleansException( $"No active nodes are compatible with grain {grainType} and interface {target.InterfaceType} version {target.InterfaceVersion}. " + $"Known nodes with grain type: {allWithTypeString}. " + $"All known nodes compatible with interface version: {allWithInterfaceString}"); } return compatibleSilos; } public SiloAddress[] AllActiveSilos { get { var result = _siloStatusOracle.GetApproximateSiloStatuses(true).Keys.ToArray(); if (result.Length > 0) return result; LogWarningAllActiveSilos(); return new SiloAddress[] { LocalSilo }; } } public IReadOnlyDictionary GetCompatibleSilosWithVersions(PlacementTarget target) { if (target.InterfaceVersion == 0) { throw new ArgumentException("Interface version not provided", nameof(target)); } var grainType = target.GrainIdentity.Type; var silos = _versionSelectorManager .GetSuitableSilos(grainType, target.InterfaceType, target.InterfaceVersion) .SuitableSilosByVersion; return silos; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CachedAddressIsValid(Message message, GrainAddress cachedAddress) { // Verify that the result from the cache has not been invalidated by the message being addressed. if (message.CacheInvalidationHeader is { } cacheUpdates) { lock (cacheUpdates) { return CachedAddressIsValidCore(message, cachedAddress, cacheUpdates); } } return true; [MethodImpl(MethodImplOptions.NoInlining)] bool CachedAddressIsValidCore(Message message, GrainAddress cachedAddress, List cacheUpdates) { var resultIsValid = true; LogDebugInvalidatingCachedEntries(cacheUpdates.Count, message); foreach (var update in cacheUpdates) { // Invalidate/update cache entries while we are examining them. var invalidAddress = update.InvalidGrainAddress; var validAddress = update.ValidGrainAddress; _grainLocator.UpdateCache(update); if (cachedAddress.Matches(validAddress)) { resultIsValid = true; } else if (cachedAddress.Matches(invalidAddress)) { resultIsValid = false; } } return resultIsValid; } } /// /// Places a grain without considering the grain's existing location, if any. /// /// The grain id of the grain being placed. /// The request context, which will be available to the placement strategy. /// The placement strategy to use. /// A location for the new activation. public async Task PlaceGrainAsync(GrainId grainId, Dictionary requestContextData, PlacementStrategy placementStrategy) { using var _ = TryRestoreActivityContext(requestContextData, ActivityNames.PlaceGrain); var target = new PlacementTarget(grainId, requestContextData, default, 0); var director = _directorResolver.GetPlacementDirector(placementStrategy); return await director.OnAddActivation(placementStrategy, target, this); } private class PlacementWorker { private readonly Dictionary _inProgress = new(); private readonly SingleWaiterAutoResetEvent _workSignal = new() { RunContinuationsAsynchronously = true }; private readonly ILogger _logger; #pragma warning disable IDE0052 // Remove unread private members. Justification: retained for debugging purposes private readonly Task _processLoopTask; #pragma warning restore IDE0052 // Remove unread private members #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly PlacementService _placementService; private List<(Message Message, TaskCompletionSource Completion)> _messages = new(); public PlacementWorker(PlacementService placementService) { _logger = placementService._logger; _placementService = placementService; using var _ = new ExecutionContextSuppressor(); _processLoopTask = Task.Run(ProcessLoop); } public Task AddressMessage(Message message) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); lock (_lockObj) { _messages ??= new(); _messages.Add((message, completion)); } _workSignal.Signal(); return completion.Task; } private List<(Message Message, TaskCompletionSource Completion)> GetMessages() { lock (_lockObj) { if (_messages is { Count: > 0 } result) { _messages = null; return result; } return null; } } private async Task ProcessLoop() { Action signalWaiter = _workSignal.Signal; while (true) { try { // Start processing new requests var messages = GetMessages(); if (messages is not null) { foreach (var message in messages) { var target = message.Message.TargetGrain; var workItem = GetOrAddWorkItem(target); workItem.Messages.Add(message); if (workItem.Result is null) { // Note that the first message is used as the target to place the message, // so if subsequent messages do not agree with the first message's interface // type or version, then they may be sent to an incompatible silo, which is // fine since the remote silo will handle that incompatibility. workItem.Result = GetOrPlaceActivationAsync(message.Message); // Wake up this processing loop when the task completes workItem.Result.GetAwaiter().UnsafeOnCompleted(signalWaiter); } } } // Complete processing any completed request foreach (var pair in _inProgress) { var workItem = pair.Value; if (workItem.Result.IsCompleted) { AddressWaitingMessages(workItem); _inProgress.Remove(pair.Key); } } } catch (Exception exception) { LogWarnInPlacementWorker(_logger, exception); } await _workSignal.WaitAsync(); } GrainPlacementWorkItem GetOrAddWorkItem(GrainId target) { ref var workItem = ref CollectionsMarshal.GetValueRefOrAddDefault(_inProgress, target, out _); workItem ??= new(); return workItem; } } private void AddressWaitingMessages(GrainPlacementWorkItem completedWorkItem) { var resultTask = completedWorkItem.Result; var messages = completedWorkItem.Messages; try { var siloAddress = resultTask.GetAwaiter().GetResult(); foreach (var message in messages) { _placementService.SetMessageTargetPlacement(message.Message, siloAddress); message.Completion.TrySetResult(); } } catch (Exception exception) { var originalException = exception switch { AggregateException ae when ae.InnerExceptions.Count == 1 => ae.InnerException, _ => exception, }; foreach (var message in messages) { message.Completion.TrySetException(originalException); } } messages.Clear(); } private async Task GetOrPlaceActivationAsync(Message firstMessage) { await Task.Yield(); // InnerGetOrPlaceActivationAsync may set a new activity as current from the RequestContextData, // so we need to save and restore the current activity. var currentActivity = Activity.Current; var activationLocation = await InnerGetOrPlaceActivationAsync(); Activity.Current = currentActivity; return activationLocation; async Task InnerGetOrPlaceActivationAsync() { // Restore activity context from the message's request context data // This ensures directory lookups are properly traced as children of the original request using var restoredActivity = TryRestoreActivityContext(firstMessage.RequestContextData, ActivityNames.PlaceGrain); var target = new PlacementTarget( firstMessage.TargetGrain, firstMessage.RequestContextData, firstMessage.InterfaceType, firstMessage.InterfaceVersion); var targetGrain = target.GrainIdentity; var result = await _placementService._grainLocator.Lookup(targetGrain); if (result is not null) { return result.SiloAddress; } var strategy = _placementService._strategyResolver.GetPlacementStrategy(target.GrainIdentity.Type); var director = _placementService._directorResolver.GetPlacementDirector(strategy); var siloAddress = await director.OnAddActivation(strategy, target, _placementService); // Give the grain locator one last chance to tell us that the grain has already been placed if (_placementService._grainLocator.TryLookupInCache(targetGrain, out result) && _placementService.CachedAddressIsValid(firstMessage, result)) { return result.SiloAddress; } _placementService._grainLocator.InvalidateCache(targetGrain); _placementService._grainLocator.UpdateCache(targetGrain, siloAddress); return siloAddress; } } private class GrainPlacementWorkItem { public List<(Message Message, TaskCompletionSource Completion)> Messages { get; } = new(); public Task Result { get; set; } } } /// /// Wraps a filter's output enumerable so that an Activity span is created when the /// sequence is actually enumerated, not when the filter is composed. This avoids /// per-filter array materialization while still giving accurate span timings. /// private static IEnumerable InstrumentFilteredSilos( IEnumerable silos, PlacementFilterStrategy filter, GrainType grainType, ActivityContext? parentActivityContext) { using var filterSpan = parentActivityContext is { } parentContext ? ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.FilterPlacementCandidates, ActivityKind.Internal, parentContext) : ActivitySources.LifecycleGrainSource.StartActivity(ActivityNames.FilterPlacementCandidates); filterSpan?.SetTag(ActivityTagKeys.PlacementFilterType, filter.GetType().Name); filterSpan?.SetTag(ActivityTagKeys.GrainType, grainType.ToString()); foreach (var silo in silos) { yield return silo; } } /// /// Attempts to restore the parent activity context from request context data. /// private static Activity TryRestoreActivityContext(Dictionary requestContextData, string operationName) { if (requestContextData is null) { return null; } var activityContext = requestContextData.TryGetActivityContext(); if (activityContext is {} parentContext) { // Start the activity from the Catalog's ActivitySource to properly associate it with activation tracing return ActivitySources.LifecycleGrainSource.StartActivity(operationName, ActivityKind.Internal, parentContext); } return null; } [LoggerMessage( Level = LogLevel.Debug, Message = "Found address {Address} for grain {GrainId} in cache for message {Message}" )] private partial void LogDebugFoundAddress(GrainAddress address, GrainId grainId, Message message); [LoggerMessage( Level = LogLevel.Debug, Message = "Looking up address for grain {GrainId} for message {Message}" )] private partial void LogDebugLookingUpAddress(GrainId grainId, Message message); [LoggerMessage( Level = LogLevel.Trace, Message = "AddressMessage Placement SelectTarget {Message}" )] private partial void LogTraceAddressMessageSelectTarget(Message message); [LoggerMessage( EventId = (int)ErrorCode.Catalog_GetApproximateSiloStatuses, Level = LogLevel.Warning, Message = "AllActiveSilos SiloStatusOracle.GetApproximateSiloStatuses empty" )] private partial void LogWarningAllActiveSilos(); [LoggerMessage( Level = LogLevel.Debug, Message = "Invalidating {Count} cached entries for message {Message}" )] private partial void LogDebugInvalidatingCachedEntries(int count, Message message); [LoggerMessage( Level = LogLevel.Warning, Message = "Error in placement worker." )] private static partial void LogWarnInPlacementWorker(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Runtime/Placement/PlacementStrategyResolver.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using System.Linq; using Orleans.Metadata; using System.Collections.Immutable; using System.Collections.Concurrent; using Orleans.Runtime.Hosting; using System.Collections.Frozen; using Orleans.GrainDirectory; namespace Orleans.Runtime.Placement { /// /// Responsible for resolving an for a . /// public sealed class PlacementStrategyResolver { private readonly ConcurrentDictionary _resolvedStrategies = new(); private readonly Func _getStrategyInternal; private readonly IPlacementStrategyResolver[] _resolvers; private readonly GrainPropertiesResolver _grainPropertiesResolver; private readonly PlacementStrategy _defaultPlacementStrategy; private readonly IServiceProvider _services; /// /// Create a instance. /// public PlacementStrategyResolver( IServiceProvider services, IEnumerable resolvers, GrainPropertiesResolver grainPropertiesResolver) { _services = services; _getStrategyInternal = GetPlacementStrategyInternal; _resolvers = resolvers.ToArray(); _grainPropertiesResolver = grainPropertiesResolver; _defaultPlacementStrategy = services.GetService(); } /// /// Gets the placement strategy associated with the provided grain type. /// public PlacementStrategy GetPlacementStrategy(GrainType grainType) => _resolvedStrategies.GetOrAdd(grainType, _getStrategyInternal); private bool TryGetNonDefaultPlacementStrategy(GrainType grainType, out PlacementStrategy strategy) { _grainPropertiesResolver.TryGetGrainProperties(grainType, out var properties); foreach (var resolver in _resolvers) { if (resolver.TryResolvePlacementStrategy(grainType, properties, out strategy)) { return true; } } if (properties is not null && properties.Properties.TryGetValue(WellKnownGrainTypeProperties.PlacementStrategy, out var placementStrategyId) && !string.IsNullOrWhiteSpace(placementStrategyId)) { strategy = _services.GetKeyedService(placementStrategyId); if (strategy is not null) { strategy.Initialize(properties); return true; } else { throw new KeyNotFoundException($"Could not resolve placement strategy {placementStrategyId} for grain type {grainType}."); } } strategy = default; return false; } private PlacementStrategy GetPlacementStrategyInternal(GrainType grainType) { if (TryGetNonDefaultPlacementStrategy(grainType, out var result)) { return result; } return _defaultPlacementStrategy; } } } ================================================ FILE: src/Orleans.Runtime/Placement/PreferLocalPlacementDirector.cs ================================================ using System.Linq; using System.Threading.Tasks; namespace Orleans.Runtime.Placement { /// /// PreferLocalPlacementDirector is a single activation placement. /// It is similar to RandomPlacementDirector except for how new activations are placed. /// When activation is requested (OnSelectActivation), it uses the same algorithm as RandomPlacementDirector to pick one if one already exists. /// That is, it checks with the Distributed Directory. /// If none exits, it prefers to place a new one in the local silo. If there are no races (only one silo at a time tries to activate this grain), /// the local silo wins. In the case of concurrent activations of the first activation of this grain, only one silo wins. /// internal class PreferLocalPlacementDirector : RandomPlacementDirector, IPlacementDirector { private Task _cachedLocalSilo; public override Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { // if local silo is not active or does not support this type of grain, revert to random placement if (context.LocalSiloStatus != SiloStatus.Active || !context.GetCompatibleSilos(target).Contains(context.LocalSilo)) { return base.OnAddActivation(strategy, target, context); } return _cachedLocalSilo ??= Task.FromResult(context.LocalSilo); } } } ================================================ FILE: src/Orleans.Runtime/Placement/RandomPlacementDirector.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Runtime.Placement { internal class RandomPlacementDirector : IPlacementDirector { public virtual Task OnAddActivation( PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { var compatibleSilos = context.GetCompatibleSilos(target); // If a valid placement hint was specified, use it. if (IPlacementDirector.GetPlacementHint(target.RequestContextData, compatibleSilos) is { } placementHint) { return Task.FromResult(placementHint); } return Task.FromResult(compatibleSilos[Random.Shared.Next(compatibleSilos.Length)]); } } } ================================================ FILE: src/Orleans.Runtime/Placement/Rebalancing/ActivationRebalancerMonitor.cs ================================================ using System; using Orleans.Runtime.Placement.Repartitioning; using System.Threading.Tasks; using Orleans.Placement.Rebalancing; using System.Collections.Immutable; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Threading; using Orleans.Runtime.Scheduler; #nullable enable namespace Orleans.Runtime.Placement.Rebalancing; internal sealed partial class ActivationRebalancerMonitor : SystemTarget, IActivationRebalancerMonitor, ILifecycleParticipant { private IGrainTimer? _monitorTimer; private RebalancingReport _latestReport; private long _lastHeartbeatTimestamp; private readonly TimeProvider _timeProvider; private readonly ActivationDirectory _activationDirectory; private readonly IActivationRebalancerWorker _rebalancerGrain; private readonly ILogger _logger; private readonly List _statusListeners = []; // Check on the worker with double the period the worker reports to me. private readonly static TimeSpan TimerPeriod = 2 * IActivationRebalancerMonitor.WorkerReportPeriod; public ActivationRebalancerMonitor( TimeProvider timeProvider, ActivationDirectory activationDirectory, ILoggerFactory loggerFactory, IGrainFactory grainFactory, SystemTargetShared shared) : base(Constants.ActivationRebalancerMonitorType, shared) { _timeProvider = timeProvider; _activationDirectory = activationDirectory; _logger = loggerFactory.CreateLogger(); _rebalancerGrain = grainFactory.GetGrain(0); _lastHeartbeatTimestamp = _timeProvider.GetTimestamp(); _latestReport = new() { ClusterImbalance = 1, Host = SiloAddress.Zero, Status = RebalancerStatus.Suspended, SuspensionDuration = Timeout.InfiniteTimeSpan, Statistics = [] }; shared.ActivationDirectory.RecordNewTarget(this); } public void Participate(ISiloLifecycle observer) { observer.Subscribe( nameof(ActivationRepartitioner), ServiceLifecycleStage.Active, OnStart, _ => Task.CompletedTask); observer.Subscribe( nameof(ActivationRepartitioner), ServiceLifecycleStage.ApplicationServices, _ => Task.CompletedTask, OnStop); } private async Task OnStart(CancellationToken cancellationToken) { await this.RunOrQueueTask(() => { _monitorTimer = RegisterGrainTimer(async ct => { var elapsedSinceHeartbeat = _timeProvider.GetElapsedTime(_lastHeartbeatTimestamp); var shouldFetchReport = _latestReport.Host == SiloAddress.Zero || elapsedSinceHeartbeat >= IActivationRebalancerMonitor.WorkerReportPeriod; if (shouldFetchReport) { LogStartingRebalancer(elapsedSinceHeartbeat, IActivationRebalancerMonitor.WorkerReportPeriod); _latestReport = await _rebalancerGrain.GetReport().AsTask().WaitAsync(ct); } }, TimeSpan.Zero, TimerPeriod); return Task.CompletedTask; }); } private async Task OnStop(CancellationToken cancellationToken) { await this.RunOrQueueTask(() => { if (_latestReport is { } report && Silo.IsSameLogicalSilo(report.Host)) { if (_activationDirectory.FindTarget(_rebalancerGrain.GetGrainId()) is { } activation) { LogMigratingRebalancer(Silo); activation.Migrate(null, cancellationToken); // migrate it anywhere else } } _monitorTimer?.Dispose(); return Task.CompletedTask; }); } public Task ResumeRebalancing() => _rebalancerGrain.ResumeRebalancing(); public Task SuspendRebalancing(TimeSpan? duration) => _rebalancerGrain.SuspendRebalancing(duration); public async ValueTask GetRebalancingReport(bool force = false) { if (force) { _latestReport = await _rebalancerGrain.GetReport(); } return _latestReport; } public Task Report(RebalancingReport report) { _latestReport = report; _lastHeartbeatTimestamp = _timeProvider.GetTimestamp(); foreach (var listener in _statusListeners) { try { listener.OnReport(report); } catch (Exception ex) { LogErrorWhileNotifyingListener(ex); } } return Task.CompletedTask; } public void SubscribeToReports(IActivationRebalancerReportListener listener) { if (!_statusListeners.Contains(listener)) { _statusListeners.Add(listener); } } public void UnsubscribeFromReports(IActivationRebalancerReportListener listener) => _statusListeners.Remove(listener); [LoggerMessage( Level = LogLevel.Trace, Message = "I have not received a report from the activation rebalancer for the last {Duration} which is more than the " + "allowed interval {Period}. I will now try to wake it up with the assumption that it has has been stopped ungracefully." )] private partial void LogStartingRebalancer(TimeSpan duration, TimeSpan period); [LoggerMessage( Level = LogLevel.Trace, Message = "My silo '{Silo}' is stopping now, and I am the host of the activation rebalancer. " + "I will attempt to migrate the rebalancer to another silo." )] private partial void LogMigratingRebalancer(SiloAddress silo); [LoggerMessage( Level = LogLevel.Error, Message = "An unexpected error occurred while notifying rebalancer listener." )] private partial void LogErrorWhileNotifyingListener(Exception exception); } ================================================ FILE: src/Orleans.Runtime/Placement/Rebalancing/ActivationRebalancerWorker.Log.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Statistics; namespace Orleans.Runtime.Placement.Rebalancing; internal partial class ActivationRebalancerWorker { [LoggerMessage(Level = LogLevel.Trace, Message = "Activation rebalancer has been scheduled to start after {DueTime}.")] private partial void LogScheduledToStart(TimeSpan dueTime); [LoggerMessage(Level = LogLevel.Trace, Message = "I have started a new rebalancing session.")] private partial void LogSessionStarted(); [LoggerMessage(Level = LogLevel.Trace, Message = "I have stopped my current rebalancing session.")] private partial void LogSessionStopped(); [LoggerMessage(Level = LogLevel.Trace, Message = "I have been told to suspend rebalancing indefinitely.")] private partial void LogSuspended(); [LoggerMessage(Level = LogLevel.Trace, Message = "I have been told to suspend rebalancing for {Duration}.")] private partial void LogSuspendedFor(TimeSpan duration); [LoggerMessage(Level = LogLevel.Trace, Message = "Can not continue with rebalancing because there are less than 2 silos.")] private partial void LogNotEnoughSilos(); [LoggerMessage(Level = LogLevel.Trace, Message = "Can not continue with rebalancing because I have statistics information for less than 2 silos.")] private partial void LogNotEnoughStatistics(); [LoggerMessage(Level = LogLevel.Warning, Message = "Can not continue with rebalancing because at least one of the silos is reporting 0 memory usage. " + $"This can indicate that there is no implementation of {nameof(IEnvironmentStatisticsProvider)}.")] private partial void LogInvalidSiloMemory(); [LoggerMessage(Level = LogLevel.Trace, Message = "The current rebalancing session has stopped due to {StagnantCycles} stagnant cycles having passed, which is the maximum allowed.")] private partial void LogMaxStagnantCyclesReached(int stagnantCycles); [LoggerMessage(Level = LogLevel.Trace, Message = "The current rebalancing session has stopped due to a {EntropyDeviation} " + "entropy deviation between the current {CurrentEntropy} and maximum possible {MaximumEntropy}. " + "The difference is less than the required {AllowedEntropyDeviation} deviation.")] private partial void LogMaxEntropyDeviationReached(double entropyDeviation, double currentEntropy, double maximumEntropy, double allowedEntropyDeviation); [LoggerMessage(Level = LogLevel.Trace, Message = "The relative change in entropy {EntropyChange} is less than the quantum {EntropyQuantum}. " + "This is practically not considered an improvement, therefore this cycle will be marked as stagnant.")] private partial void LogInsufficientEntropyQuantum(double entropyChange, double entropyQuantum); [LoggerMessage(Level = LogLevel.Trace, Message = "Stagnant cycle count has been reset as we are improving now.")] private partial void LogStagnantCyclesReset(); [LoggerMessage(Level = LogLevel.Trace, Message = "Failed session count has been reset as we are improving now.")] private partial void LogFailedSessionsReset(); [LoggerMessage(Level = LogLevel.Trace, Message = "I have decided to migrate {Delta} activations.\n" + "Adjusted activations for {LowSilo} will be [{LowSiloPreActivations} -> {LowSiloPostActivations}].\n" + "Adjusted activations for {HighSilo} will be [{HighSiloPreActivations} -> {HighSiloPostActivations}].")] private partial void LogSiloMigrations(int delta, SiloAddress lowSilo, int lowSiloPreActivations, int lowSiloPostActivations, SiloAddress highSilo, int highSiloPreActivations, int highSiloPostActivations); [LoggerMessage(Level = LogLevel.Trace, Message = "Rebalancing cycle {RebalancingCycle} has finished. " + "[ Stagnant Cycles: {StagnantCycles} | Previous Entropy: {PreviousEntropy} | " + "Current Entropy: {CurrentEntropy} | Maximum Entropy: {MaximumEntropy} | Entropy Deviation: {EntropyDeviation} ]")] private partial void LogCycleOutcome( int rebalancingCycle, int stagnantCycles, double previousEntropy, double currentEntropy, double maximumEntropy, double entropyDeviation); } ================================================ FILE: src/Orleans.Runtime/Placement/Rebalancing/ActivationRebalancerWorker.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Placement; using Orleans.Placement.Rebalancing; #nullable enable namespace Orleans.Runtime.Placement.Rebalancing; // See: https://www.ledjonbehluli.com/posts/orleans_adaptive_rebalancing/ [KeepAlive, Immovable] internal sealed partial class ActivationRebalancerWorker( DeploymentLoadPublisher loadPublisher, ILoggerFactory loggerFactory, ISiloStatusOracle siloStatusOracle, IInternalGrainFactory grainFactory, ILocalSiloDetails localSiloDetails, IOptions options, IFailedSessionBackoffProvider backoffProvider) : Grain, IActivationRebalancerWorker, ISiloStatisticsChangeListener, IGrainMigrationParticipant { private readonly record struct ResourceStatistics(long MemoryUsage, int ActivationCount); [GenerateSerializer, Immutable, Alias("RebalancerState")] internal readonly record struct RebalancerState( int StagnantCycles, int FailedSessions, int RebalancingCycle, double LatestEntropy, double EntropyDeviation, TimeSpan? SuspensionDuration, ImmutableArray Statistics); private enum StopReason { /// /// A new session is about to start. /// SessionStarting, /// /// Current session has stagnated. /// SessionStagnated, /// /// Current session has completed successfully till end /// SessionCompleted, /// /// Rebalancer was asked to suspend activity. /// RebalancerSuspended } private const string StateKey = "REBALANCER_STATE"; private int _stagnantCycles; private int _failedSessions; private int _rebalancingCycle; private double _previousEntropy; private double _entropyDeviation; private long _suspendedUntilTs; private IGrainTimer? _sessionTimer; private IGrainTimer? _triggerTimer; private IGrainTimer? _monitorTimer; private readonly ActivationRebalancerOptions _options = options.Value; private readonly Dictionary _siloStatistics = []; private readonly Dictionary _rebalancingStatistics = []; private readonly ILogger _logger = loggerFactory.CreateLogger(); private TimeSpan? RemainingSuspensionDuration => Runtime.TimeProvider.GetElapsedTime(Runtime.TimeProvider.GetTimestamp(), _suspendedUntilTs) switch { { } result when result > TimeSpan.Zero => result, _ => null }; public override Task OnActivateAsync(CancellationToken cancellationToken) { _monitorTimer = this.RegisterGrainTimer(ReportAllMonitors, new() { DueTime = TimeSpan.Zero, Period = IActivationRebalancerMonitor.WorkerReportPeriod, }); _triggerTimer = this.RegisterGrainTimer(TriggerRebalancing, new() { Interleave = true, Period = 0.5 * _options.SessionCyclePeriod, // Make trigger-period half that of the session cycle-period. DueTime = _options.RebalancerDueTime }); LogScheduledToStart(_options.RebalancerDueTime); loadPublisher.SubscribeToStatisticsChangeEvents(this); return Task.CompletedTask; } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { loadPublisher.UnsubscribeStatisticsChangeEvents(this); return Task.CompletedTask; } public void OnDehydrate(IDehydrationContext context) { context.TryAddValue(StateKey, new(_stagnantCycles, _failedSessions, _rebalancingCycle, _previousEntropy, _entropyDeviation, RemainingSuspensionDuration, [.. _rebalancingStatistics.Values])); } public void OnRehydrate(IRehydrationContext context) { if (context.TryGetValue(StateKey, out var rebalancerState) && rebalancerState is { } state) { _rebalancingCycle = state.RebalancingCycle; _stagnantCycles = state.StagnantCycles; _failedSessions = state.FailedSessions; _previousEntropy = state.LatestEntropy; _entropyDeviation = state.EntropyDeviation; foreach (var statistics in state.Statistics) { if (siloStatusOracle.IsDeadSilo(statistics.SiloAddress)) { continue; } _rebalancingStatistics.TryAdd(statistics.SiloAddress, statistics); } if (state.SuspensionDuration is { } value) { SuspendFor(value); } } } void ISiloStatisticsChangeListener.RemoveSilo(SiloAddress silo) { GrainContext.Scheduler.QueueAction(() => { _siloStatistics.Remove(silo); _rebalancingStatistics.Remove(silo); // Remove that silo's rebalancing stats, as it has been removed. }); } void ISiloStatisticsChangeListener.SiloStatisticsChangeNotification(SiloAddress address, SiloRuntimeStatistics statistics) { GrainContext.Scheduler.QueueAction(() => _siloStatistics[address] = new(statistics.EnvironmentStatistics.FilteredMemoryUsageBytes, statistics.ActivationCount)); } public ValueTask GetReport() => new(BuildReport()); public async Task ResumeRebalancing() { StartSession(); await ReportAllMonitors(CancellationToken.None); } public async Task SuspendRebalancing(TimeSpan? duration) { StopSession(StopReason.RebalancerSuspended, duration); if (duration.HasValue) { LogSuspendedFor(duration.Value); } else { LogSuspended(); } await ReportAllMonitors(CancellationToken.None); } private async Task ReportAllMonitors(CancellationToken cancellationToken) { var tasks = new List(); var report = BuildReport(); foreach (var silo in siloStatusOracle.GetActiveSilos()) { tasks.Add(grainFactory.GetSystemTarget (Constants.ActivationRebalancerMonitorType, silo).Report(report)); } await Task.WhenAll(tasks).WaitAsync(cancellationToken); } private RebalancingReport BuildReport() { var suspensionRemaining = RemainingSuspensionDuration; return new RebalancingReport() { Host = localSiloDetails.SiloAddress, Status = suspensionRemaining is { } ? RebalancerStatus.Suspended : RebalancerStatus.Executing, SuspensionDuration = suspensionRemaining, ClusterImbalance = _entropyDeviation, Statistics = [.. _rebalancingStatistics.Values] }; } private Task TriggerRebalancing() { if (_sessionTimer != null) { return Task.CompletedTask; } if (RemainingSuspensionDuration.HasValue) { return Task.CompletedTask; } StartSession(); return Task.CompletedTask; } private async Task RunRebalancingCycle(CancellationToken cancellationToken) { var siloCount = siloStatusOracle.GetActiveSilos().Length; if (siloCount < 2) { LogNotEnoughSilos(); return; } var snapshot = _siloStatistics.ToDictionary(); if (snapshot.Count < 2) { LogNotEnoughStatistics(); return; } if (snapshot.Any(x => x.Value.MemoryUsage == 0)) { LogInvalidSiloMemory(); return; } _rebalancingCycle++; if (_stagnantCycles >= _options.MaxStagnantCycles) { LogMaxStagnantCyclesReached(_stagnantCycles); StopSession(StopReason.SessionStagnated); return; } var totalActivations = snapshot.Sum(x => x.Value.ActivationCount); var meanMemoryUsage = ComputeHarmonicMean(snapshot.Values); var maximumEntropy = Math.Log(siloCount); var currentEntropy = ComputeEntropy(snapshot.Values, totalActivations, meanMemoryUsage); var allowedDeviation = ComputeAllowedEntropyDeviation(totalActivations); var entropyDeviation = (maximumEntropy - currentEntropy) / maximumEntropy; _entropyDeviation = entropyDeviation; if (entropyDeviation < allowedDeviation) { // The deviation from maximum is practically considered "0" i.e: we've reached maximum. LogMaxEntropyDeviationReached(entropyDeviation, currentEntropy, maximumEntropy, allowedDeviation); StopSession(StopReason.SessionCompleted); return; } // We use the normalized, absolute entropy change, because it is more useful for understanding how significant // the change is, relative to the maximum possible. Values closer to 1 reflect higher significance than those closer to 0. // Since max entropy is a function of the natural log of the cluster's size, this value is very robust against changes // in silo number within the cluster. var entropyChange = Math.Abs((currentEntropy - _previousEntropy) / maximumEntropy); Debug.Assert(entropyChange is >= 0 and <= 1); if (entropyChange < _options.EntropyQuantum) { // Entropy change is too low to be considered an improvement, chances are we are reaching the maximum, or the system // is dynamically changing too fast i.e. new activations are being created at a high rate with an imbalanced distribution, // we need to start "cooling-down". As a matter of fact, entropy could also become negative if the current entropy is less // than the previous, due to many activation changes happening during this and the previous cycle. LogInsufficientEntropyQuantum(entropyChange, _options.EntropyQuantum); _stagnantCycles++; _previousEntropy = currentEntropy; return; } if (_stagnantCycles > 0) { _stagnantCycles = 0; LogStagnantCyclesReset(); } if (_failedSessions > 0) { _failedSessions = 0; LogFailedSessionsReset(); } var idealDistributions = snapshot.Select(x => new ValueTuple // n_i = (N / S) * (M_m / m_i) (x.Key, ((double)totalActivations / siloCount) * (meanMemoryUsage / x.Value.MemoryUsage))) .ToDictionary(); var alpha = currentEntropy / maximumEntropy; var scalingFactor = ComputeAdaptiveScaling(siloCount, _rebalancingCycle); var addressPairs = FormSiloPairs(snapshot); var migrationTasks = new List(); for (var i = 0; i < addressPairs.Count; i++) { (var lowSilo, var highSilo) = addressPairs[i]; var difference = Math.Abs( (snapshot[lowSilo].ActivationCount - idealDistributions[lowSilo]) - (snapshot[highSilo].ActivationCount - idealDistributions[highSilo])); var delta = (int)(alpha * scalingFactor * (difference / 2)); if (delta == 0) { continue; } var lowCount = snapshot[lowSilo].ActivationCount; var highCount = snapshot[highSilo].ActivationCount; if (delta > highCount) { delta = highCount; } if (delta > _options.ActivationMigrationCountLimit) { delta = _options.ActivationMigrationCountLimit; } migrationTasks.Add(grainFactory .GetSystemTarget(Constants.SiloControlType, highSilo) .MigrateRandomActivations(lowSilo, delta)); UpdateStatistics(lowSilo, highSilo, delta); LogSiloMigrations(delta, lowSilo, lowCount, lowCount + delta, highSilo, highCount, highCount - delta); } if (migrationTasks.Count > 0) { await Task.WhenAll(migrationTasks).WaitAsync(cancellationToken); } LogCycleOutcome(_rebalancingCycle, _stagnantCycles, _previousEntropy, currentEntropy, maximumEntropy, entropyDeviation); _previousEntropy = currentEntropy; } private void UpdateStatistics(SiloAddress lowSilo, SiloAddress highSilo, int delta) { Debug.Assert(delta > 0); var now = Runtime.TimeProvider.GetUtcNow().DateTime; ref var lowStats = ref CollectionsMarshal.GetValueRefOrAddDefault(_rebalancingStatistics, lowSilo, out _); lowStats = new() { TimeStamp = now, SiloAddress = lowSilo, DispersedActivations = lowStats.DispersedActivations, AcquiredActivations = lowStats.AcquiredActivations + (ulong)delta }; ref var highStats = ref CollectionsMarshal.GetValueRefOrAddDefault(_rebalancingStatistics, highSilo, out _); highStats = new() { TimeStamp = now, SiloAddress = highSilo, DispersedActivations = highStats.DispersedActivations + (ulong)delta, AcquiredActivations = highStats.AcquiredActivations }; } private static double ComputeEntropy( Dictionary.ValueCollection values, int totalActivations, double meanMemoryUsage) { Debug.Assert(totalActivations > 0); Debug.Assert(meanMemoryUsage > 0); var ratios = values.Select(x => // p_i = (n_i / N) * (m_i / M_m) ((double)x.ActivationCount / totalActivations) * (x.MemoryUsage / meanMemoryUsage)); var ratiosSum = ratios.Sum(); var normalizedRatios = ratios.Select(r => r / ratiosSum); const double epsilon = 1e-10d; var entropy = -normalizedRatios.Sum(p => { var value = Math.Max(p, epsilon); // Avoid log(0) return value * Math.Log(value); // - sum(p_i * log(p_i)) }); Debug.Assert(entropy > 0); return entropy; } private static double ComputeHarmonicMean(Dictionary.ValueCollection values) { var result = 0d; foreach (var value in values) { var count = value.ActivationCount; Debug.Assert(count > 0); result += 1.0 / count; } return values.Count / result; } private double ComputeAllowedEntropyDeviation(int totalActivations) { if (!_options.ScaleAllowedEntropyDeviation || totalActivations < _options.ScaledEntropyDeviationActivationThreshold) { return _options.AllowedEntropyDeviation; } Debug.Assert(totalActivations > 0); var logFactor = (int)Math.Log10(totalActivations / _options.ScaledEntropyDeviationActivationThreshold); var adjustedDeviation = _options.AllowedEntropyDeviation * Math.Pow(10, logFactor); return Math.Min(adjustedDeviation, ActivationRebalancerOptions.MAX_SCALED_ENTROPY_DEVIATION); } private double ComputeAdaptiveScaling(int siloCount, int rebalancingCycle) { Debug.Assert(rebalancingCycle > 0); var cycleFactor = 1 - Math.Exp(-_options.CycleNumberWeight * rebalancingCycle); var siloFactor = 1 / (1 + _options.SiloNumberWeight * (siloCount - 1)); return (double)(cycleFactor * siloFactor); } private static List<(SiloAddress, SiloAddress)> FormSiloPairs( Dictionary statistics) { var pairs = new List<(SiloAddress, SiloAddress)>(); var sorted = statistics.OrderBy(x => x.Value.ActivationCount).ToList(); var left = 0; var right = sorted.Count - 1; while (left < right) { pairs.Add((sorted[left].Key, sorted[right].Key)); left++; right--; } return pairs; } private void StartSession() { StopSession(StopReason.SessionStarting); _sessionTimer = this.RegisterGrainTimer(RunRebalancingCycle, new() { DueTime = TimeSpan.Zero, Period = _options.SessionCyclePeriod }); LogSessionStarted(); } private void StopSession(StopReason reason, TimeSpan? duration = null) { _previousEntropy = 0; _rebalancingCycle = 0; _stagnantCycles = 0; _sessionTimer?.Dispose(); _sessionTimer = null; switch (reason) { case StopReason.SessionStarting: { _failedSessions = 0; _suspendedUntilTs = 0; } break; case StopReason.SessionStagnated: { _failedSessions++; SuspendFor(backoffProvider.Next(_failedSessions)); } break; case StopReason.SessionCompleted: { _failedSessions = 0; SuspendFor(_options.SessionCyclePeriod); } break; case StopReason.RebalancerSuspended: { _failedSessions = 0; if (duration.HasValue) { SuspendFor(duration.Value); } else { _suspendedUntilTs = long.MaxValue; } } break; } LogSessionStopped(); } private void SuspendFor(TimeSpan duration) { ArgumentOutOfRangeException.ThrowIfLessThan(duration, TimeSpan.Zero); var now = Runtime.TimeProvider.GetTimestamp(); var suspendUntil = now + (long)(Runtime.TimeProvider.TimestampFrequency * duration.TotalSeconds); if (suspendUntil < now) { // Clamp overflow at max value. suspendUntil = long.MaxValue; } _suspendedUntilTs = Math.Max(_suspendedUntilTs, suspendUntil); } } ================================================ FILE: src/Orleans.Runtime/Placement/Rebalancing/FailedSessionBackoffProvider.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Placement.Rebalancing; namespace Orleans.Runtime.Placement.Rebalancing; internal sealed class FailedSessionBackoffProvider(IOptions options) : FixedBackoff(options.Value.SessionCyclePeriod), IFailedSessionBackoffProvider; ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/ActivationRepartitioner.Log.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.Placement.Repartitioning; internal partial class ActivationRepartitioner { [LoggerMessage(Level = LogLevel.Debug, Message = "I will periodically initiate the exchange protocol every {MinPeriod} to {MaxPeriod} starting in {DueTime}.")] private partial void LogPeriodicallyInvokeProtocol(TimeSpan minPeriod, TimeSpan maxPeriod, TimeSpan dueTime); [LoggerMessage(Level = LogLevel.Debug, Message = "Activation repartitioning is enabled, but the cluster contains only one silo. Waiting for at least another silo to join the cluster to proceed.")] private partial void LogSingleSiloCluster(); [LoggerMessage(Level = LogLevel.Debug, Message = "Exchange set for candidate silo {CandidateSilo} is empty. I will try the next best candidate (if one is available), otherwise I will wait for my next period to come.")] private partial void LogExchangeSetIsEmpty(SiloAddress candidateSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "Beginning exchange protocol between {ThisSilo} and {CandidateSilo}.")] private partial void LogBeginningProtocol(SiloAddress thisSilo, SiloAddress candidateSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "I got an exchange request from {SendingSilo}, but I have been recently involved in another exchange {LastExchangeDuration} ago. My recovery period is {RecoveryPeriod}")] private partial void LogExchangedRecently(SiloAddress sendingSilo, TimeSpan lastExchangeDuration, TimeSpan recoveryPeriod); [LoggerMessage(Level = LogLevel.Debug, Message = "Exchange request from {ThisSilo} rejected: {CandidateSilo} was recently involved in another exchange. Attempting the next best candidate (if one is available) or waiting for my next period to come.")] private partial void LogExchangedRecentlyResponse(SiloAddress thisSilo, SiloAddress candidateSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "Rejecting exchange request from {SendingSilo} since we are already exchanging with that host.")] private partial void LogMutualExchangeAttempt(SiloAddress sendingSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "Exchange request from {ThisSilo} superseded by a mutual exchange attempt with {CandidateSilo}.")] private partial void LogMutualExchangeAttemptResponse(SiloAddress thisSilo, SiloAddress candidateSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "Finalized exchange protocol: migrating {GivingActivationCount} activations, receiving {TakingActivationCount} activations.")] private partial void LogProtocolFinalized(int givingActivationCount, int takingActivationCount); [LoggerMessage(Level = LogLevel.Trace, Message = "Finalized exchange protocol: migrating [{GivingActivations}], receiving [{TakingActivations}].")] private partial void LogProtocolFinalizedTrace(string givingActivations, string takingActivations); [LoggerMessage(Level = LogLevel.Warning, Message = "Error performing exchange request from {ThisSilo} to {CandidateSilo}. I will try the next best candidate (if one is available), otherwise I will wait for my next period to come.")] private partial void LogErrorOnProtocolExecution(Exception exception, SiloAddress thisSilo, SiloAddress candidateSilo); [LoggerMessage(Level = LogLevel.Warning, Message = "Error migrating exchange set.")] private partial void LogErrorOnMigratingActivations(Exception exception); [LoggerMessage(Level = LogLevel.Debug, Message = "Received AcceptExchangeRequest from {SendingSilo}, offering to send {ExchangeSetCount} activations from a total of {ActivationCount} activations.")] private partial void LogReceivedExchangeRequest(SiloAddress sendingSilo, int exchangeSetCount, int activationCount); [LoggerMessage(Level = LogLevel.Debug, Message = "Imbalance is {Imbalance} (remote: {RemoteCount} vs local {LocalCount})")] private partial void LogImbalance(int imbalance, int remoteCount, int localCount); [LoggerMessage(Level = LogLevel.Debug, Message = "Computing transfer set took {Elapsed}. Anticipated imbalance after transfer is {AnticipatedImbalance}.")] private partial void LogTransferSetComputed(TimeSpan elapsed, int anticipatedImbalance); [LoggerMessage(Level = LogLevel.Warning, Message = "Error accepting exchange request from {SendingSilo}.")] private partial void LogErrorAcceptingExchangeRequest(Exception exception, SiloAddress sendingSilo); [LoggerMessage(Level = LogLevel.Debug, Message = "Waiting an additional {CoolDown} to cool down before initiating the exchange protocol.")] private partial void LogCoolingDown(TimeSpan coolDown); [LoggerMessage(Level = LogLevel.Debug, Message = "Adding {NewlyAnchoredGrains} newly anchored grains to set on host {Silo}. EdgeWeights contains {EdgeWeightCount} elements.")] private partial void LogAddingAnchoredGrains(int newlyAnchoredGrains, SiloAddress silo, int edgeWeightCount); [LoggerMessage(Level = LogLevel.Trace, Message = "Candidate sets computed in {Elapsed}.")] private partial void LogComputedCandidateSets(TimeSpan elapsed); [LoggerMessage(Level = LogLevel.Trace, Message = "Candidate heaps created in {Elapsed}.")] private partial void LogComputedCandidateHeaps(TimeSpan elapsed); } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/ActivationRepartitioner.MessageSink.cs ================================================ #nullable enable using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using Orleans.Placement.Repartitioning; using Orleans.Runtime.Internal; namespace Orleans.Runtime.Placement.Repartitioning; internal sealed partial class ActivationRepartitioner : IMessageStatisticsSink { private readonly CancellationTokenSource _shutdownCts = new(); // This filter contains grain ids which will are anchored to the current silo. // Ids are inserted when a grain is found to have a negative transfer score. private readonly AnchoredGrainsFilter? _anchoredFilter; private Task? _processPendingEdgesTask; public void StartProcessingEdges() { using var _ = new ExecutionContextSuppressor(); _processPendingEdgesTask = ProcessPendingEdges(_shutdownCts.Token); LogTraceServiceStarted(_logger, nameof(ActivationRepartitioner)); } public async Task StopProcessingEdgesAsync(CancellationToken cancellationToken) { _shutdownCts.Cancel(); if (_processPendingEdgesTask is null) { return; } _pendingMessageEvent.Signal(); await _processPendingEdgesTask.WaitAsync(cancellationToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); LogTraceServiceStopped(_logger, nameof(ActivationRepartitioner)); } private async Task ProcessPendingEdges(CancellationToken cancellationToken) { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext); var drainBuffer = new Message[128]; var iteration = 0; const int MaxIterationsPerYield = 128; while (!cancellationToken.IsCancellationRequested) { var count = _pendingMessages.DrainTo(drainBuffer); if (count > 0) { foreach (var message in drainBuffer[..count]) { if (!IsFullyAddressed(message) || // The silo addresses (likely the target) is set null some time later (after the message is recorded), this can lead to a NRE !_messageFilter.IsAcceptable(message, out var isSenderMigratable, out var isTargetMigratable)) { continue; } EdgeVertex sourceVertex; if (_anchoredFilter != null && _anchoredFilter.Contains(message.SendingGrain) && Silo.Equals(message.SendingSilo)) { sourceVertex = new(GrainId, Silo, isMigratable: false); } else { sourceVertex = new(message.SendingGrain, message.SendingSilo, isSenderMigratable); } EdgeVertex destinationVertex; if (_anchoredFilter != null && _anchoredFilter.Contains(message.TargetGrain) && Silo.Equals(message.TargetSilo)) { destinationVertex = new(GrainId, Silo, isMigratable: false); } else { destinationVertex = new(message.TargetGrain, message.TargetSilo, isTargetMigratable); } if (!sourceVertex.IsMigratable && !destinationVertex.IsMigratable) { // Ignore edges between two non-migratable grains. continue; } Edge edge = new(sourceVertex, destinationVertex); _edgeWeights.Add(edge); } if (++iteration >= MaxIterationsPerYield) { iteration = 0; await Task.Delay(TimeSpan.FromTicks(TimeSpan.TicksPerMillisecond), CancellationToken.None); } } else { iteration = 0; await _pendingMessageEvent.WaitAsync(); } } } public Action? GetMessageObserver() => RecordMessage; private void RecordMessage(Message message) { if (!_enableMessageSampling || message.IsSystemMessage) { return; } // It must have a direction, and must not be a 'response' as it would skew analysis. if (message.Direction is Message.Directions.None or Message.Directions.Response) { return; } // Sender and target need to be fully addressable to know where to move to or towards. if (!IsFullyAddressed(message)) { return; } if (_pendingMessages.TryAdd(message) == Utilities.BufferStatus.Success) { _pendingMessageEvent.Signal(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsFullyAddressed(Message message) => message.IsSenderFullyAddressed && message.IsTargetFullyAddressed; async ValueTask IActivationRepartitionerSystemTarget.FlushBuffers() { while (_pendingMessages.Count > 0) { await Task.Delay(TimeSpan.FromMilliseconds(30)); } } [LoggerMessage( Level = LogLevel.Trace, Message = "{Service} has started." )] private static partial void LogTraceServiceStarted(ILogger logger, string service); [LoggerMessage( Level = LogLevel.Trace, Message = "{Service} has stopped." )] private static partial void LogTraceServiceStopped(ILogger logger, string service); } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/ActivationRepartitioner.cs ================================================ #nullable enable using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Runtime.CompilerServices; using System.Diagnostics; using System.Collections.Immutable; using System.Data; using Orleans.Internal; using Orleans.Configuration; using Orleans.Runtime.Utilities; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Orleans.Placement.Repartitioning; namespace Orleans.Runtime.Placement.Repartitioning; // See: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/06/eurosys16loca_camera_ready-1.pdf internal sealed partial class ActivationRepartitioner : SystemTarget, IActivationRepartitionerSystemTarget, ILifecycleParticipant, IDisposable, ISiloStatusListener { private readonly ILogger _logger; private readonly ISiloStatusOracle _siloStatusOracle; private readonly IInternalGrainFactory _grainFactory; private readonly IRepartitionerMessageFilter _messageFilter; private readonly IImbalanceToleranceRule _toleranceRule; private readonly IActivationMigrationManager _migrationManager; private readonly ActivationDirectory _activationDirectory; private readonly TimeProvider _timeProvider; private readonly ActivationRepartitionerOptions _options; private readonly StripedMpscBuffer _pendingMessages; private readonly SingleWaiterAutoResetEvent _pendingMessageEvent = new() { RunContinuationsAsynchronously = true }; private readonly FrequentEdgeCounter _edgeWeights; private readonly IGrainTimer _timer; private SiloAddress? _currentExchangeSilo; private CoarseStopwatch _lastExchangedStopwatch; private int _activationCountOffset; private bool _enableMessageSampling; public ActivationRepartitioner( ISiloStatusOracle siloStatusOracle, ILoggerFactory loggerFactory, IInternalGrainFactory internalGrainFactory, IRepartitionerMessageFilter messageFilter, IImbalanceToleranceRule toleranceRule, IActivationMigrationManager migrationManager, ActivationDirectory activationDirectory, IOptions options, TimeProvider timeProvider, SystemTargetShared shared) : base(Constants.ActivationRepartitionerType, shared) { _logger = loggerFactory.CreateLogger(); _options = options.Value; _siloStatusOracle = siloStatusOracle; _grainFactory = internalGrainFactory; _messageFilter = messageFilter; _toleranceRule = toleranceRule; _migrationManager = migrationManager; _activationDirectory = activationDirectory; _timeProvider = timeProvider; _edgeWeights = new(options.Value.MaxEdgeCount); _lastExchangedStopwatch = CoarseStopwatch.StartNew(); _pendingMessages = new StripedMpscBuffer(Environment.ProcessorCount, options.Value.MaxUnprocessedEdges / Environment.ProcessorCount); shared.ActivationDirectory.RecordNewTarget(this); _siloStatusOracle.SubscribeToSiloStatusEvents(this); _timer = RegisterTimer(_ => TriggerExchangeRequest().AsTask(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); if (options.Value.AnchoringFilterEnabled) { _anchoredFilter = new AnchoredGrainsFilter(100_000, options.Value.ProbabilisticFilteringMaxAllowedErrorRate, options.Value.AnchoringFilterGenerations); } } private Task OnActiveStart(CancellationToken cancellationToken) { Scheduler.QueueAction(() => { // Schedule the first timer tick. UpdateTimer(); StartProcessingEdges(); }); return Task.CompletedTask; } public ValueTask ResetCounters() { _pendingMessages.Clear(); _edgeWeights.Clear(); _anchoredFilter?.Reset(); return ValueTask.CompletedTask; } ValueTask IActivationRepartitionerSystemTarget.GetActivationCount() => new(_activationDirectory.Count); ValueTask IActivationRepartitionerSystemTarget.SetActivationCountOffset(int activationCountOffset) { _activationCountOffset = activationCountOffset; return ValueTask.CompletedTask; } private void UpdateTimer() => UpdateTimer(RandomTimeSpan.Next(_options.MinRoundPeriod, _options.MaxRoundPeriod)); private void UpdateTimer(TimeSpan dueTime) { _timer.Change(dueTime, dueTime); LogPeriodicallyInvokeProtocol(_options.MinRoundPeriod, _options.MaxRoundPeriod, dueTime); } public async ValueTask TriggerExchangeRequest() { if (_anchoredFilter is { } filter) { filter.Rotate(); } var coolDown = _options.RecoveryPeriod - _lastExchangedStopwatch.Elapsed; if (coolDown > TimeSpan.Zero) { LogCoolingDown(coolDown); await Task.Delay(coolDown, _timeProvider); } // Schedule the next timer tick. UpdateTimer(); if (_currentExchangeSilo is not null) { // Skip this round if we are already in the process of exchanging with another silo. return; } var silos = _siloStatusOracle.GetActiveSilos(); if (silos.Length == 1) { LogSingleSiloCluster(); return; } else if (!_enableMessageSampling) { return; } var sw = ValueStopwatch.StartNew(); var migrationCandidates = GetMigrationCandidates(); var sets = CreateCandidateSets(migrationCandidates, silos); var anchoredSet = ComputeAnchoredGrains(migrationCandidates); LogComputedCandidateSets(sw.Elapsed); foreach ((var candidateSilo, var offeredGrains, var _) in sets) { if (offeredGrains.Count == 0) { LogExchangeSetIsEmpty(candidateSilo); continue; } try { // Set the exchange partner for the duration of the operation. // This prevents other requests from interleaving. _currentExchangeSilo = candidateSilo; LogBeginningProtocol(Silo, candidateSilo); var remoteRef = IActivationRepartitionerSystemTarget.GetReference(_grainFactory, candidateSilo); var response = await remoteRef.AcceptExchangeRequest(new(Silo, [.. offeredGrains], GetLocalActivationCount())); switch (response.Type) { case AcceptExchangeResponse.ResponseType.Success: // Exchange was successful, no need to iterate over another candidate. await FinalizeProtocol(response.AcceptedGrainIds, response.GivenGrainIds, candidateSilo, anchoredSet); return; case AcceptExchangeResponse.ResponseType.ExchangedRecently: // The remote silo has been recently involved in another exchange, try the next best candidate. LogExchangedRecentlyResponse(Silo, candidateSilo); continue; case AcceptExchangeResponse.ResponseType.MutualExchangeAttempt: // The remote silo is exchanging with this silo already and the exchange the remote silo initiated // took precedence over the one this silo is initiating. LogMutualExchangeAttemptResponse(Silo, candidateSilo); return; } } catch (Exception ex) { LogErrorOnProtocolExecution(ex, Silo, candidateSilo); continue; // there was some problem, try the next best candidate } finally { _currentExchangeSilo = null; } } } private int GetLocalActivationCount() => _activationDirectory.Count + _activationCountOffset; public async ValueTask AcceptExchangeRequest(AcceptExchangeRequest request) { LogReceivedExchangeRequest(request.SendingSilo, request.ExchangeSet.Length, request.ActivationCountSnapshot); if (request.SendingSilo.Equals(_currentExchangeSilo) && Silo.CompareTo(request.SendingSilo) <= 0) { // Reject the request, as we are already in the process of exchanging with the sending silo. // The '<=' comparison here is used to break the tie in case both silos are exchanging with each other. // We pick some random time between 'min' and 'max' and than subtract from it 'min'. We do this so this silo doesn't have to wait for 'min + random', // as it did the very first time this was started. It is guaranteed that 'random - min' >= 0; as 'random' will be at the least equal to 'min'. UpdateTimer(RandomTimeSpan.Next(_options.MinRoundPeriod, _options.MaxRoundPeriod) - _options.MinRoundPeriod); LogMutualExchangeAttempt(request.SendingSilo); return AcceptExchangeResponse.CachedMutualExchangeAttempt; } var lastExchangeElapsed = _lastExchangedStopwatch.Elapsed; if (lastExchangeElapsed < _options.RecoveryPeriod) { LogExchangedRecently(request.SendingSilo, lastExchangeElapsed, _options.RecoveryPeriod); return AcceptExchangeResponse.CachedExchangedRecently; } // Set the exchange silo for the duration of the request. // This prevents other requests from interleaving. _currentExchangeSilo = request.SendingSilo; try { var acceptedGrains = ImmutableArray.CreateBuilder(); var givingGrains = ImmutableArray.CreateBuilder(); var remoteSet = request.ExchangeSet; var migrationCandidates = GetMigrationCandidates(); var localSet = GetCandidatesForSilo(migrationCandidates, request.SendingSilo); var anchoredSet = ComputeAnchoredGrains(migrationCandidates); // We need to determine 2 subsets: // - One that originates from sending silo (request.ExchangeSet) and will be (partially) accepted from this silo. // - One that originates from this silo (candidateSet) and will be (fully) accepted from the sending silo. var remoteActivations = request.ActivationCountSnapshot; var localActivations = GetLocalActivationCount(); var initialImbalance = CalculateImbalance(remoteActivations, localActivations); int imbalance = initialImbalance; LogImbalance(imbalance, remoteActivations, localActivations); var stopwatch = ValueStopwatch.StartNew(); var (localHeap, remoteHeap) = CreateCandidateHeaps(localSet, remoteSet); LogComputedCandidateHeaps(stopwatch.Elapsed); stopwatch.Restart(); var iterations = 0; var yieldStopwatch = CoarseStopwatch.StartNew(); while (true) { if (++iterations % 128 == 0 && yieldStopwatch.ElapsedMilliseconds > 25) { // Give other tasks a chance to execute periodically. yieldStopwatch.Restart(); await Task.Delay(1); } // If more is gained by giving grains to the remote silo than taking from it, we will try giving first. var localScore = localHeap.FirstOrDefault()?.AccumulatedTransferScore ?? 0; var remoteScore = remoteHeap.FirstOrDefault()?.AccumulatedTransferScore ?? 0; if (localScore > remoteScore || localScore == remoteScore && localActivations > remoteActivations) { if (TryMigrateLocalToRemote()) continue; if (TryMigrateRemoteToLocal()) continue; } else { if (TryMigrateRemoteToLocal()) continue; if (TryMigrateLocalToRemote()) continue; } // No more migrations can be made, so the candidate set has been calculated. break; } LogTransferSetComputed(stopwatch.Elapsed, imbalance); var giving = givingGrains.ToImmutable(); var accepting = acceptedGrains.ToImmutable(); await FinalizeProtocol(giving, accepting, request.SendingSilo, anchoredSet); return new(AcceptExchangeResponse.ResponseType.Success, accepting, giving); bool TryMigrateLocalToRemote() { if (!TryMigrateCore(localHeap, localDelta: -1, remoteDelta: 1, out var chosenVertex)) { return false; } givingGrains.Add(chosenVertex.Id); foreach (var (connectedVertex, transferScore) in chosenVertex.ConnectedVertices) { switch (connectedVertex.Location) { case VertexLocation.Local: // Add the transfer score as these two vectors will now be remote to each other. connectedVertex.AccumulatedTransferScore += transferScore; localHeap.OnIncreaseElementPriority(connectedVertex); break; case VertexLocation.Remote: // Subtract the transfer score as these two vectors will now be local to each other. connectedVertex.AccumulatedTransferScore -= transferScore; remoteHeap.OnDecreaseElementPriority(connectedVertex); break; } } // We will perform any future operations assuming the vector is remote. chosenVertex.Location = VertexLocation.Remote; Debug.Assert(((IHeapElement)chosenVertex).HeapIndex == -1); return true; } bool TryMigrateRemoteToLocal() { if (!TryMigrateCore(remoteHeap, localDelta: 1, remoteDelta: -1, out var chosenVertex)) { return false; } acceptedGrains.Add(chosenVertex.Id); foreach (var (connectedVertex, transferScore) in chosenVertex.ConnectedVertices) { switch (connectedVertex.Location) { case VertexLocation.Local: // Subtract the transfer score as these two vectors will now be local to each other. connectedVertex.AccumulatedTransferScore -= transferScore; localHeap.OnDecreaseElementPriority(connectedVertex); break; case VertexLocation.Remote: // Add the transfer score as these two vectors will now be remote to each other. connectedVertex.AccumulatedTransferScore += transferScore; remoteHeap.OnIncreaseElementPriority(connectedVertex); break; } } // We will perform any future operations assuming the vector is local. chosenVertex.Location = VertexLocation.Local; return true; } bool TryMigrateCore(MaxHeap sourceHeap, int localDelta, int remoteDelta, [NotNullWhen(true)] out CandidateVertexHeapElement? chosenVertex) { var anticipatedImbalance = CalculateImbalance(localActivations + localDelta, remoteActivations + remoteDelta); if (anticipatedImbalance >= imbalance && !_toleranceRule.IsSatisfiedBy((uint)anticipatedImbalance)) { // Taking from this heap would not improve imbalance. chosenVertex = null; return false; } if (!sourceHeap.TryPop(out chosenVertex)) { // Heap is empty. return false; } if (chosenVertex.AccumulatedTransferScore <= 0) { // If it got affected by a previous run, and the score is zero or negative, simply pop and ignore it. return false; } localActivations += localDelta; remoteActivations += remoteDelta; imbalance = anticipatedImbalance; return true; } } catch (Exception exception) { LogErrorAcceptingExchangeRequest(exception, request.SendingSilo); throw; } finally { _currentExchangeSilo = null; } } private static int CalculateImbalance(int left, int right) => Math.Abs(Math.Abs(left) - Math.Abs(right)); private static (MaxHeap Local, MaxHeap Remote) CreateCandidateHeaps(List local, ImmutableArray remote) { Dictionary sourceIndex = new(local.Count + remote.Length); foreach (var element in local) { sourceIndex[element.Id] = element; } foreach (var element in remote) { sourceIndex[element.Id] = element; } Dictionary heapIndex = []; List localVertexList = new(local.Count); foreach (var element in local) { var vertex = CreateVertex(sourceIndex, heapIndex, element); vertex.Location = VertexLocation.Local; localVertexList.Add(vertex); } List remoteVertexList = new(remote.Length); foreach (var element in remote) { var vertex = CreateVertex(sourceIndex, heapIndex, element); if (vertex.Location is not VertexLocation.Unknown) { // This vertex is already part of the local set, so assume that the vertex is local and ignore the remote vertex. continue; } vertex.Location = VertexLocation.Remote; remoteVertexList.Add(vertex); } var localHeap = new MaxHeap(localVertexList); var remoteHeap = new MaxHeap(remoteVertexList); return (localHeap, remoteHeap); static CandidateVertexHeapElement CreateVertex(Dictionary sourceIndex, Dictionary index, CandidateVertex element) { var vertex = GetOrAddVertex(index, element); foreach (var connectedVertex in element.ConnectedVertices) { if (sourceIndex.TryGetValue(connectedVertex.Id, out var connected)) { vertex.ConnectedVertices.Add((GetOrAddVertex(index, connected), connectedVertex.TransferScore)); } else { // The connected vertex is not part of either migration candidate set, so we will ignore it. } } return vertex; static CandidateVertexHeapElement GetOrAddVertex(Dictionary index, CandidateVertex element) { ref var vertex = ref CollectionsMarshal.GetValueRefOrAddDefault(index, element.Id, out var exists); vertex ??= new(element); return vertex; } } } /// /// /// Initiates the actual migration process of to 'this' silo. /// Updates the affected counters within to reflect all . /// /// /// The grain ids to migrate to the remote host. /// The grain ids to which are migrating to the local host. private async Task FinalizeProtocol(ImmutableArray giving, ImmutableArray accepting, SiloAddress targetSilo, HashSet newlyAnchoredGrains) { // The protocol concluded that 'this' silo should take on 'set', so we hint to the director accordingly. try { Dictionary migrationRequestContext = new() { [IPlacementDirector.PlacementHintKey] = targetSilo }; var deactivationTasks = new List(); foreach (var grainId in giving) { if (_activationDirectory.FindTarget(grainId) is { } localActivation) { localActivation.Migrate(migrationRequestContext); deactivationTasks.Add(localActivation.Deactivated); } } await Task.WhenAll(deactivationTasks); } catch (Exception exception) { // This should happen rarely, but at this point we cant really do much, as its out of our control. // Even if some fail, the algorithm will eventually run again, so activations will have more chances to migrate. LogErrorOnMigratingActivations(exception); } // Avoid mutating the source while enumerating it. var iterations = 0; var toRemove = new List(); var affected = new HashSet(giving.Length + accepting.Length); if (_anchoredFilter is { } filter) { LogAddingAnchoredGrains(newlyAnchoredGrains.Count, Silo, _edgeWeights.Count); foreach (var id in newlyAnchoredGrains) { filter.Add(id); } } foreach (var id in accepting) { affected.Add(id); } foreach (var id in giving) { affected.Add(id); } var yieldStopwatch = CoarseStopwatch.StartNew(); if (affected.Count > 0) { foreach (var (edge, _, _) in _edgeWeights.Elements) { if (affected.Contains(edge.Source.Id) || affected.Contains(edge.Target.Id) || _anchoredFilter is not null && (_anchoredFilter.Contains(edge.Source.Id) || _anchoredFilter.Contains(edge.Target.Id))) { toRemove.Add(edge); } } foreach (var edge in toRemove) { if (++iterations % 128 == 0 && yieldStopwatch.ElapsedMilliseconds > 25) { // Give other tasks a chance to execute periodically. yieldStopwatch.Restart(); await Task.Delay(1); } // Totally remove this counter, as one or both vertices has migrated. By not doing this it would skew results for the next protocol cycle. // We remove only the affected counters, as there could be other counters that 'this' silo has connections with another silo (which is not part of this exchange cycle). _edgeWeights.Remove(edge); } } // Stamp this silos exchange for a potential next pair exchange request. _lastExchangedStopwatch.Restart(); if (_logger.IsEnabled(LogLevel.Trace)) { LogProtocolFinalizedTrace(string.Join(", ", giving), string.Join(", ", accepting)); } else if (_logger.IsEnabled(LogLevel.Debug)) { LogProtocolFinalized(giving.Length, accepting.Length); } } private List<(SiloAddress Silo, List Candidates, long TransferScore)> CreateCandidateSets(List> migrationCandidates, ImmutableArray silos) { List<(SiloAddress Silo, List Candidates, long TransferScore)> candidateSets = new(silos.Length - 1); foreach (var siloAddress in silos) { if (siloAddress.Equals(Silo)) { // We aren't going to exchange anything with ourselves, so skip this silo. continue; } var candidatesForRemote = GetCandidatesForSilo(migrationCandidates, siloAddress); var totalAccTransferScore = candidatesForRemote.Sum(x => x.AccumulatedTransferScore); candidateSets.Add(new(siloAddress, [.. candidatesForRemote], totalAccTransferScore)); } // Order them by the highest accumulated transfer score candidateSets.Sort(static (a, b) => -a.TransferScore.CompareTo(b.TransferScore)); return candidateSets; } private List GetCandidatesForSilo(List> migrationCandidates, SiloAddress otherSilo) { Debug.Assert(!otherSilo.Equals(Silo)); List result = []; // We skip types that cant be migrated. Instead the same edge will be recorded from the receiver, so its hosting silo will add it as a candidate to be migrated (over here). // We are sure that the receiver is an migratable grain, because the gateway forbids edges that have non-migratable vertices on both sides. foreach (var grainEdges in migrationCandidates) { var accLocalScore = 0L; var accRemoteScore = 0L; foreach (var edge in grainEdges) { if (edge.Direction is Direction.LocalToLocal) { // Since its L2L, it means the partner silo will be 'this' silo, so we don't need to filter by the partner silo. accLocalScore += edge.Weight; } else if (edge.PartnerSilo.Equals(otherSilo)) { Debug.Assert(edge.Direction is Direction.RemoteToLocal or Direction.LocalToRemote); // We need to filter here by 'otherSilo' since any L2R or R2L edge can be between the current vertex and a vertex in a silo that is not in 'otherSilo'. accRemoteScore += edge.Weight; } } if (accLocalScore >= accRemoteScore) { // We skip vertices for which local calls outweigh the remote ones. continue; } var totalAccScore = accRemoteScore - accLocalScore; var connVertices = ImmutableArray.CreateBuilder(); foreach (var edge in grainEdges) { // Note that the connected vertices can be of types which are not migratable, it is important to keep them, // as they too impact the migration cost of the current candidate vertex, especially if they are local to the candidate // as those calls would be potentially converted to remote calls, after the migration of the current candidate. // 'Weight' here represent the weight of a single edge, not the accumulated like above. connVertices.Add(new CandidateConnectedVertex(edge.TargetId, edge.Weight)); } CandidateVertex candidate = new() { Id = grainEdges.Key, AccumulatedTransferScore = totalAccScore, ConnectedVertices = connVertices.ToImmutable() }; result.Add(candidate); } return result; } private static HashSet ComputeAnchoredGrains(List> migrationCandidates) { HashSet anchoredGrains = []; foreach (var grainEdges in migrationCandidates) { var accLocalScore = 0L; var accRemoteScore = 0L; foreach (var edge in grainEdges) { if (edge.Direction is Direction.LocalToLocal) { accLocalScore += edge.Weight; } else { Debug.Assert(edge.Direction is Direction.RemoteToLocal or Direction.LocalToRemote); accRemoteScore += edge.Weight; } } if (accLocalScore > accRemoteScore) { anchoredGrains.Add(grainEdges.Key); } } return anchoredGrains; } private List> GetMigrationCandidates() => CreateLocalVertexEdges().Where(x => x.IsMigratable).GroupBy(x => x.SourceId).ToList(); /// /// Creates a collection of 'local' vertex edges. Multiple entries can have the same Id. /// /// The is guaranteed to belong to a grain that is local to this silo, while might belong to a local or remote silo. private IEnumerable CreateLocalVertexEdges() { foreach (var (edge, count, error) in _edgeWeights.Elements) { if (count == 0) { continue; } var vertexEdge = CreateVertexEdge(edge, count); if (vertexEdge.Direction is Direction.Unspecified) { // This can occur when a message is re-routed via this silo. continue; } yield return vertexEdge; if (vertexEdge.Direction == Direction.LocalToLocal) { // The reason we do this flipping is because when the edge is Local-to-Local, we have 2 grains that are linked via an communication edge. // Once an edge exists it means 2 grains are temporally linked, this means that there is a cost associated to potentially move either one of them. // Since the construction of the candidate set takes into account also local connection (which increases the cost of migration), we need // to take into account the edge not only from a source's perspective, but also the target's one, as it too will take part on the candidate set. var flippedEdge = CreateVertexEdge(edge.Flip(), count); yield return flippedEdge; } } VertexEdge CreateVertexEdge(in Edge edge, long weight) { return (IsSourceLocal(edge), IsTargetLocal(edge)) switch { (true, true) => new( SourceId: edge.Source.Id, // 'local' vertex was the 'source' of the communication TargetId: edge.Target.Id, IsMigratable: edge.Source.IsMigratable, PartnerSilo: Silo, // the partner was 'local' (note: this.Silo = Source.Silo = Target.Silo) Direction: Direction.LocalToLocal, Weight: weight), (true, false) => new( SourceId: edge.Source.Id, // 'local' vertex was the 'source' of the communication TargetId: edge.Target.Id, IsMigratable: edge.Source.IsMigratable, PartnerSilo: edge.Target.Silo, // the partner was 'remote' Direction: Direction.LocalToRemote, Weight: weight), (false, true) => new( SourceId: edge.Target.Id, // 'local' vertex was the 'target' of the communication TargetId: edge.Source.Id, IsMigratable: edge.Target.IsMigratable, PartnerSilo: edge.Source.Silo, // the partner was 'remote' Direction: Direction.RemoteToLocal, Weight: weight), _ => default }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsSourceLocal(in Edge edge) => edge.Source.Silo.Equals(Silo); [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsTargetLocal(in Edge edge) => edge.Target.Silo.Equals(Silo); } public void Participate(ISiloLifecycle observer) { // Start when the silo becomes active. observer.Subscribe( nameof(ActivationRepartitioner), ServiceLifecycleStage.Active, OnActiveStart, ct => Task.CompletedTask); // Stop when the silo stops application services. observer.Subscribe( nameof(ActivationRepartitioner), ServiceLifecycleStage.ApplicationServices, ct => Task.CompletedTask, StopProcessingEdgesAsync); } void IDisposable.Dispose() { base.Dispose(); _enableMessageSampling = false; _siloStatusOracle.UnSubscribeFromSiloStatusEvents(this); _shutdownCts.Cancel(); } void ISiloStatusListener.SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status) { _enableMessageSampling = _siloStatusOracle.GetActiveSilos().Length > 1; } public ValueTask> GetGrainCallFrequencies() { var result = ImmutableArray.CreateBuilder<(Edge, ulong)>(_edgeWeights.Count); foreach (var (edge, count, _) in _edgeWeights.Elements) { result.Add((edge, count)); } return new(result.ToImmutable()); } private enum Direction : byte { Unspecified, LocalToLocal, LocalToRemote, RemoteToLocal } /// /// Represents a connection between 2 vertices. /// /// The id of the grain it represents. /// The id of the connected vertex (the one the communication took place with). /// Specifies if the vertex with is a migratable type. /// The silo partner which interacted with the silo of vertex with . /// The edge's direction /// The number of estimated messages exchanged between and . private readonly record struct VertexEdge(GrainId SourceId, GrainId TargetId, bool IsMigratable, SiloAddress PartnerSilo, Direction Direction, long Weight); } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/BlockedBloomFilter.cs ================================================ using System.Diagnostics; using System.IO.Hashing; using System.Numerics; using System.Runtime.CompilerServices; namespace Orleans.Runtime.Placement.Repartitioning; #nullable enable /// /// A tuned version of a blocked bloom filter implementation. /// /// /// This is a tuned version of BBF in order to meet the required FP rate. /// Tuning takes a lot of time so this filter can accept FP rates in the rage of [0.1% - 1%] /// Any value with the range, at any precision is supported as the FP rate is regressed via polynomial regression /// More information can be read from Section 3: https://www.cs.amherst.edu/~ccmcgeoch/cs34/papers/cacheefficientbloomfilters-jea.pdf /// internal sealed class BlockedBloomFilter { private const int BlockSize = 32; // higher value yields better speed, but at a high cost of space private const double Ln2Squared = 0.4804530139182014246671025263266649717305529515945455; private const double MinFpRate = 0.001; // 0.1% private const double MaxFpRate = 0.01; // 1% private readonly int _blocks; private readonly int[] _filter; // Regression coefficients (derived via polynomial regression) to match 'fpRate' as the actual deviates significantly with lower and lower 'fpRate' // Eg, see https://gist.github.com/ledjon-behluli/d339cbd54568ceb5464d3a947ac8f08e private static readonly double[] Coefficients = [ 4.0102253166524500e-003, -1.6272682781603145e+001, 2.7169897602930665e+004, -2.4527698904812500e+007, 1.3273846004698063e+010, -4.4943809759769805e+012, 9.5588839677303638e+014, -1.2081452101930328e+017, 6.8958853188430172e+018, 2.6889929911921561e+020, -7.1061179529975569e+022, 4.4109449793357217e+024, -9.8041203512310751e+025 ]; /// The estimated population size. /// Bounded within [ - ] /// public BlockedBloomFilter(int capacity, double fpRate) { if (fpRate is < MinFpRate or > MaxFpRate) { throw new ArgumentOutOfRangeException($"False positive rate '{fpRate}', is outside of the allowed range '{MinFpRate} - {MaxFpRate}'"); } var adjFpRate = RegressFpRate(fpRate); Debug.Assert(adjFpRate < fpRate); var bits = (int)(-1 * capacity * Math.Log(adjFpRate) / Ln2Squared); _blocks = bits / BlockSize; _filter = new int[_blocks + 1]; } private static double RegressFpRate(double fpRate) { double temp = 1; double result = 0; foreach (var coefficient in Coefficients) { result += coefficient * temp; temp *= fpRate; } return Math.Abs(result); } public void Add(GrainId id) { var hash = XxHash3.HashToUInt64(id.Key.AsSpan(), id.GetUniformHashCode()); var index = GetBlockIndex(hash, _blocks); // important to get index before rotating the hash hash ^= BitOperations.RotateLeft(hash, 32); // We use 2 masks to distribute the bits of the hash value across multiple positions in the filter var mask1 = ComputeMask1(hash); var mask2 = ComputeMask2(hash); // We set the bits across 2 blocks so that the bits from a single hash value, are spread out more evenly across the filter. _filter[index] |= mask1; _filter[index + 1] |= mask2; } public bool Contains(GrainId id) { var hash = XxHash3.HashToUInt64(id.Key.AsSpan(), id.GetUniformHashCode()); var index = GetBlockIndex(hash, _blocks); // important to get index before rotating the hash hash ^= BitOperations.RotateLeft(hash, 32); var block1 = _filter[index]; var block2 = _filter[index + 1]; var mask1 = ComputeMask1(hash); var mask2 = ComputeMask2(hash); return (mask1 & block1) == mask1 && (mask2 & block2) == mask2; } public void Reset() => Array.Clear(_filter); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetBlockIndex(ulong hash, int buckets) => (int)(((int)hash & 0xffffffffL) * buckets >> 32); /// /// Sets the bits of corresponding to the lower-order bits, and the bits shifted by 6 positions to the right /// /// The rotated hash [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ComputeMask1(ulong hash) => (1 << (int)hash) | (1 << ((int)hash >> 6)); /// /// Sets the bits of , and the bits shifted by 12 and 18 positions to the right /// /// The rotated hash [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ComputeMask2(ulong hash) => (1 << ((int)hash >> 12)) | (1 << ((int)hash >> 18)); } /// /// Implemented as a set of (s) that rotate anchored grains. /// If a grain is "hot" it will likely survive multiple cycles, otherwise it gets dropped out. /// internal sealed class AnchoredGrainsFilter { // Index 0 is the "active" filter. // Indexes 1 through N-1 are the "retiring" filters. private readonly BlockedBloomFilter[] _filters; public AnchoredGrainsFilter(int capacity, double fpRate, int generations) { if (generations < 1) { throw new ArgumentOutOfRangeException(nameof(generations), "Must have at least 1 generation."); } _filters = new BlockedBloomFilter[generations]; for (var i = 0; i < generations; i++) { _filters[i] = new BlockedBloomFilter(capacity, fpRate); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(GrainId id) => _filters[0].Add(id); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(GrainId id) { for (var i = 0; i < _filters.Length; i++) { if (_filters[i].Contains(id)) { return true; } } return false; } public void Rotate() { // Idea is to rotate the inner filters to implement a sort of sliding window. // The previous active (index 0) shifts down, remaining "checkable" for n-1 more cycles, then fades unless re-added. // This ensures "cold" grains eventually drop out, while "hot" ones stay anchored. var length = _filters.Length; // We grab the oldest filter and reset it so it becomes the new active one. var nextActive = _filters[length - 1]; nextActive.Reset(); // Need to shift all existing generations down by 1. We iterate backwards // so we dont overwrite elements we havent shifted yet. for (var i = length - 1; i > 0; i--) { _filters[i] = _filters[i - 1]; } // The newly reset filter goes at the front. _filters[0] = nextActive; } public void Reset() { var filters = _filters; for (var i = 0; i < filters.Length; i++) { filters[i].Reset(); } } } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/FrequentItemCollection.cs ================================================ #nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using Orleans.Placement.Repartitioning; namespace Orleans.Runtime.Placement.Repartitioning; internal sealed class FrequentEdgeCounter(int capacity) : FrequentItemCollection(capacity) { protected override ulong GetKey(in Edge element) => ((ulong)element.Source.Id.GetUniformHashCode()) << 32 | element.Target.Id.GetUniformHashCode(); public void Clear() => ClearCore(); public void Remove(in Edge element) => RemoveCore(GetKey(element)); } // This is Implementation of "Filtered Space-Saving" from "Finding top-k elements in data streams" // by Nuno Homem & Joao Paulo Carvalho (https://www.hlt.inesc-id.pt/~fmmb/references/misnis.ref0a.pdf). // In turn, this is a modification of the "Space-Saving" algorithm by Metwally, Agrawal, and Abbadi, // Described in "Efficient Computation of Frequent and Top-k Elements in Data Streams" (https://www.cs.emory.edu/~cheung/Courses/584/Syllabus/papers/Frequency-count/2005-Metwally-Top-k-elements.pdf). // This is implemented using an in-lined version of .NET's PriorityQueue which has been modified // to support incrementing a value and with an index mapping key hashes to heap indexes. internal abstract class FrequentItemCollection(int capacity) where TElement : notnull where TKey : notnull { /// /// Represents an implicit heap-ordered complete d-ary tree, stored as an array. /// private Counter[] _heap = []; /// /// A dictionary that maps the hash of a key to its index in the heap. /// private readonly Dictionary _heapIndex = []; /// /// The number of nodes in the heap. /// private int _heapSize; /// /// Specifies the arity of the d-ary heap, which here is quaternary. /// It is assumed that this value is a power of 2. /// private const int Arity = 4; /// /// The binary logarithm of . /// private const int Log2Arity = 2; /// /// Contains count estimates for keys that are not being tracked, indexed by the hash of the key. /// Collisions are expected. /// private readonly uint[] _sketch = new uint[GetSketchSize(capacity)]; /// /// Gets the number of elements contained in the . /// public int Count => _heapSize; /// /// Gets the number of elements which the will track. /// public int Capacity { get; } = capacity; #if DEBUG static FrequentItemCollection() { Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity); } #endif /// /// Returns a collection of up to keys, along with their count estimates, in unspecified order. /// public ElementEnumerator Elements => new(this); protected abstract TKey GetKey(in TElement element); public void Add(in TElement element) { const int Increment = 1; var nodeIndexHash = GetKey(element); // Increase count of a key that is already being tracked. // There is a minute chance of a hash collision, which is deemed acceptable and ignored. if (_heapIndex.TryGetValue(nodeIndexHash, out var index)) { ref var counter = ref _heap[index]; counter.Count += Increment; MoveUpHeap(counter, index, nodeIndexHash); return; } // Key is not being tracked, but can fit in the top K, so add it. if (Count < Capacity) { InsertHeap(new Counter(element, Increment, error: 0), nodeIndexHash); return; } var min = _heap[0]; // Filter out values which are estimated to have appeared less frequently than the minimum. var sketchMask = _sketch.Length - 1; var sketchHash = nodeIndexHash.GetHashCode(); var countEstimate = _sketch[sketchHash & sketchMask]; if (countEstimate + Increment < min.Count) { // Increase the count estimate. _sketch[sketchHash & sketchMask] += Increment; return; } // Remove the minimum element from the hash index. var minIndexHash = GetKey(min.Element); _heapIndex.Remove(minIndexHash); // While evicting the minimum element, update its counter in the sketch to improve the chance of it // passing the filter in the future. var minHash = minIndexHash.GetHashCode(); _sketch[minHash & sketchMask] = min.Count; // Push the new element in place of the last and move it down until it's in position. MoveDownHeap(new Counter(element, countEstimate + Increment, error: countEstimate), 0, nodeIndexHash); } /// /// Removes the counter corresponding to the specified hash. /// /// The key of the value to remove. /// if matching entry was found and removed, otherwise. protected bool RemoveCore(TKey key) { // Remove the element from the sketch var sketchMask = _sketch.Length - 1; var sketchHash = key.GetHashCode(); _sketch[sketchHash & sketchMask] = 0; // Remove the element from the heap index if (!_heapIndex.Remove(key, out var index)) { return false; } // Remove the element from the heap var nodes = _heap; var newSize = --_heapSize; if (index < newSize) { // We're removing an element from the middle of the heap. // Pop the last element in the collection and sift downward from the removed index. var lastNode = nodes[newSize]; MoveDownHeap(lastNode, index, GetKey(lastNode.Element)); } nodes[newSize] = default; return true; } protected void ClearCore() { Array.Clear(_heap, 0, _heapSize); _heapIndex.Clear(); Array.Clear(_sketch); _heapSize = 0; } private static int GetSketchSize(int capacity) { // Suggested constants in the paper "Finding top-k elements in data streams", chap 6. equation (24) // Round to nearest power of 2 for cheaper binning without modulo const int SketchEntriesPerHeapEntry = 6; return 1 << 32 - int.LeadingZeroCount(capacity * SketchEntriesPerHeapEntry); } /// /// Adds the specified element to the . /// /// The element to add. private void InsertHeap(Counter element, TKey key) { // Virtually add the node at the end of the underlying array. // Note that the node being enqueued does not need to be physically placed // there at this point, as such an assignment would be redundant. var currentSize = _heapSize; if (_heap.Length == currentSize) { GrowHeap(currentSize + 1); } _heapSize = currentSize + 1; MoveUpHeap(element, currentSize, key); } /// /// Grows the priority queue to match the specified min capacity. /// private void GrowHeap(int minCapacity) { Debug.Assert(_heap.Length < minCapacity); const int GrowFactor = 2; const int MinimumGrow = 4; var newCapacity = GrowFactor * _heap.Length; // Allow the queue to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _nodes.Length overflowed thanks to the (uint) cast if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; // Ensure minimum growth is respected. newCapacity = Math.Max(newCapacity, _heap.Length + MinimumGrow); // If the computed capacity is still less than specified, set to the original argument. // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. if (newCapacity < minCapacity) newCapacity = minCapacity; Array.Resize(ref _heap, newCapacity); } /// /// Gets the index of an element's parent. /// private static int GetParentIndex(int index) => index - 1 >> Log2Arity; /// /// Gets the index of the first child of an element. /// private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1; /// /// Moves a node up in the tree to restore heap order. /// private void MoveUpHeap(Counter node, int nodeIndex, TKey nodeKey) { // Instead of swapping items all the way to the root, we will perform // a similar optimization as in the insertion sort. Debug.Assert(0 <= nodeIndex && nodeIndex < _heapSize); var nodes = _heap; var hashIndex = _heapIndex; while (nodeIndex > 0) { var parentIndex = GetParentIndex(nodeIndex); var parent = nodes[parentIndex]; if (node.CompareTo(parent) < 0) { nodes[nodeIndex] = parent; hashIndex[GetKey(parent.Element)] = nodeIndex; nodeIndex = parentIndex; } else { break; } } nodes[nodeIndex] = node; hashIndex[nodeKey] = nodeIndex; } /// /// Moves a node down in the tree to restore heap order. /// private void MoveDownHeap(Counter node, int nodeIndex, TKey nodeKey) { // The node to move down will not actually be swapped every time. // Rather, values on the affected path will be moved up, thus leaving a free spot // for this value to drop in. Similar optimization as in the insertion sort. Debug.Assert(0 <= nodeIndex && nodeIndex < _heapSize); var nodes = _heap; var size = _heapSize; var hashIndex = _heapIndex; int i; while ((i = GetFirstChildIndex(nodeIndex)) < size) { // Find the child node with the minimal priority var minChild = nodes[i]; var minChildIndex = i; var childIndexUpperBound = Math.Min(i + Arity, size); while (++i < childIndexUpperBound) { var nextChild = nodes[i]; if (nextChild.CompareTo(minChild) < 0) { minChild = nextChild; minChildIndex = i; } } // Heap property is satisfied; insert node in this location. if (node.CompareTo(minChild) <= 0) { break; } // Move the minimal child up by one node and continue recursively from its location. nodes[nodeIndex] = minChild; hashIndex[GetKey(minChild.Element)] = nodeIndex; nodeIndex = minChildIndex; } hashIndex[nodeKey] = nodeIndex; nodes[nodeIndex] = node; } private struct Counter(TElement element, uint count, uint error) : IComparable { public readonly TElement Element = element; public uint Count = count; public uint Error = error; public readonly int CompareTo(Counter other) => ((ulong)Count << 32 | uint.MaxValue - Error).CompareTo((ulong)other.Count << 32 | uint.MaxValue - other.Error); public override readonly string ToString() => $"{Element}: Count: {Count} Error: {Error}"; } /// /// Enumerates the element and priority pairs of a , /// without any ordering guarantees. /// public struct ElementEnumerator : IEnumerator<(TElement Element, uint Count, uint Error)>, IEnumerable<(TElement Element, uint Count, uint Error)> { private readonly FrequentItemCollection _heap; private int _index; private Counter _current; internal ElementEnumerator(FrequentItemCollection heap) { _heap = heap; _index = 0; _current = default; } /// /// Releases all resources used by the . /// public readonly void Dispose() { } /// /// Advances the enumerator to the next element of the heap. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { var localHeap = _heap; if ((uint)_index < (uint)localHeap._heapSize) { _current = localHeap._heap[_index]; _index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { _index = _heap._heapSize + 1; _current = default; return false; } /// /// Gets the element at the current position of the enumerator. /// public readonly (TElement Element, uint Count, uint Error) Current => (_current.Element, _current.Count, _current.Error); readonly object IEnumerator.Current => _current; void IEnumerator.Reset() { _index = 0; _current = default; } public readonly ElementEnumerator GetEnumerator() => this; readonly IEnumerator<(TElement Element, uint Count, uint Error)> IEnumerable<(TElement Element, uint Count, uint Error)>.GetEnumerator() => this; readonly IEnumerator IEnumerable.GetEnumerator() => this; } } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/MaxHeap.cs ================================================ #nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Orleans.Placement.Repartitioning; namespace Orleans.Runtime.Placement.Repartitioning; internal enum VertexLocation { Unknown, Local, Remote } [DebuggerDisplay("{Vertex} @ {Location}")] internal sealed class CandidateVertexHeapElement(CandidateVertex value) : IHeapElement { public CandidateVertex Vertex { get; } = value; public List<(CandidateVertexHeapElement Element, long TransferScore)> ConnectedVertices { get; } = []; public GrainId Id => Vertex.Id; public long AccumulatedTransferScore { get => Vertex.AccumulatedTransferScore; set => Vertex.AccumulatedTransferScore = value; } public VertexLocation Location { get; set; } int IHeapElement.HeapIndex { get; set; } = -1; int IHeapElement.CompareTo(CandidateVertexHeapElement other) => AccumulatedTransferScore.CompareTo(other.AccumulatedTransferScore); } internal interface IHeapElement where TElement : notnull { int HeapIndex { get; set; } int CompareTo(TElement other); } /// /// Represents a max heap. /// /// Specifies the type of elements in the heap. /// /// Implements an array-backed quaternary max-heap. /// Elements with the lowest priority get removed first. /// Note: this is based on .NET's PriorityQueue: https://github.com/dotnet/runtime/blob/e78b72b1fdf43d9678877400bcfe801b38c14681/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs /// [DebuggerDisplay("Count = {Count}")] internal sealed class MaxHeap where TElement : notnull, IHeapElement { /// /// Represents an implicit heap-ordered complete d-ary tree, stored as an array. /// private readonly TElement?[] _nodes; /// /// The number of nodes in the heap. /// private int _size; /// /// Specifies the arity of the d-ary heap, which here is quaternary. /// It is assumed that this value is a power of 2. /// private const int Arity = 4; /// /// The binary logarithm of . /// private const int Log2Arity = 2; #if DEBUG static MaxHeap() { Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity); } #endif /// /// Initializes a new instance of the class /// that is populated with the specified elements and priorities. /// /// The pairs of elements and priorities with which to populate the queue. /// /// The specified argument was . /// /// /// Constructs the heap using a heapify operation, /// which is generally faster than enqueuing individual elements sequentially. /// public MaxHeap(List items) { ArgumentNullException.ThrowIfNull(items); _size = items.Count; var nodes = new TElement[_size]; var i = 0; foreach (var item in items) { nodes[i] = item; Debug.Assert(item.HeapIndex == -1); item.HeapIndex = i; i++; } _nodes = nodes; if (_size > 1) { Heapify(); } else if (_size == 1) { _nodes[0]!.HeapIndex = 0; } } /// /// Gets the number of elements contained in the . /// public int Count => _size; public TElement? FirstOrDefault() => _size > 0 ? _nodes[0] : default; public bool TryPeek([NotNullWhen(true)] out TElement value) { if (_size > 0) { value = _nodes[0]!; return true; } value = default!; return false; } /// /// Returns the maximal element from the without removing it. /// /// The is empty. /// The maximal element of the . public TElement Peek() { if (_size == 0) { throw new InvalidOperationException("Collection is empty."); } return _nodes[0]!; } public bool TryPop([NotNullWhen(true)] out TElement value) { if (_size > 0) { value = Pop(); return true; } value = default!; return false; } /// /// Removes and returns the maximal element from the . /// /// The queue is empty. /// The maximal element of the . public TElement Pop() { if (_size == 0) { throw new InvalidOperationException("Collection is empty."); } var element = _nodes[0]!; RemoveRootNode(); element.HeapIndex = -1; return element; void RemoveRootNode() { var lastNodeIndex = --_size; if (lastNodeIndex > 0) { var lastNode = _nodes[lastNodeIndex]!; MoveDown(lastNode, 0); } if (RuntimeHelpers.IsReferenceOrContainsReferences()) { _nodes[lastNodeIndex] = default!; } } } /// /// Gets the index of an element's parent. /// private static int GetParentIndex(int index) => (index - 1) >> Log2Arity; /// /// Gets the index of the first child of an element. /// private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1; public void OnDecreaseElementPriority(TElement element) { // If the element has already been removed from the heap, this is a no-op. if (element.HeapIndex < 0) { return; } // The element's priority has decreased, so move it down as necessary to restore the heap property. MoveDown(element, element.HeapIndex); } public void OnIncreaseElementPriority(TElement element) { // If the element has already been removed from the heap, this is a no-op. if (element.HeapIndex <= 0) { return; } // The element's priority has increased, so move it down as necessary to restore the heap property. MoveUp(element, element.HeapIndex); } /// /// Converts an unordered list into a heap. /// public void Heapify() { // Leaves of the tree are in fact 1-element heaps, for which there // is no need to correct them. The heap property needs to be restored // only for higher nodes, starting from the first node that has children. // It is the parent of the very last element in the array. var nodes = _nodes; var lastParentWithChildren = GetParentIndex(_size - 1); for (var index = lastParentWithChildren; index >= 0; --index) { MoveDown(nodes[index]!, index); } } /// /// Gets the elements in this collection with specified order. /// public UnorderedElementEnumerable UnorderedElements => new(this); /// /// Moves a node up in the tree to restore heap order. /// private void MoveUp(TElement node, int nodeIndex) { Debug.Assert(0 <= nodeIndex && nodeIndex < _size); var nodes = _nodes; while (nodeIndex > 0) { var parentIndex = GetParentIndex(nodeIndex); var parentNode = nodes[parentIndex]!; if (node.CompareTo(parentNode) <= 0) { // The parent is more larger than the current node. break; } nodes[nodeIndex] = parentNode; parentNode.HeapIndex = nodeIndex; nodeIndex = parentIndex; } nodes[nodeIndex] = node; node.HeapIndex = nodeIndex; } /// /// Moves a node down in the tree to restore heap order. /// private void MoveDown(TElement node, int nodeIndex) { // The node to move down will not actually be swapped every time. // Rather, values on the affected path will be moved up, thus leaving a free spot // for this value to drop in. Similar optimization as in the insertion sort. Debug.Assert(0 <= nodeIndex && nodeIndex < _size); var nodes = _nodes; var size = _size; int i; while ((i = GetFirstChildIndex(nodeIndex)) < size) { // Find the child node with the maximal priority var maxChild = nodes[i]!; var maxChildIndex = i; var childIndexUpperBound = Math.Min(i + Arity, size); while (++i < childIndexUpperBound) { var nextChild = nodes[i]!; if (nextChild.CompareTo(maxChild) > 0) { maxChild = nextChild; maxChildIndex = i; } } // Heap property is satisfied; insert node in this location. if (node.CompareTo(maxChild) >= 0) { break; } // Move the maximal child up by one node and // continue recursively from its location. nodes[nodeIndex] = maxChild; maxChild.HeapIndex = nodeIndex; nodeIndex = maxChildIndex; } nodes[nodeIndex] = node; node.HeapIndex = nodeIndex; } /// /// Enumerates the element and priority pairs of a /// without any ordering guarantees. /// public struct UnorderedElementEnumerable : IEnumerator, IEnumerable { private readonly MaxHeap _heap; private int _index; private TElement? _current; internal UnorderedElementEnumerable(MaxHeap heap) { _heap = heap; _index = 0; _current = default; } /// /// Releases all resources used by the . /// public readonly void Dispose() { } /// /// Advances the enumerator to the next element of the heap. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { var localHeap = _heap; if ((uint)_index < (uint)localHeap._size) { _current = localHeap._nodes[_index]; _index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { _index = _heap._size + 1; _current = default; return false; } /// /// Gets the element at the current position of the enumerator. /// public readonly TElement Current => _current ?? throw new InvalidOperationException("Current element is not valid."); readonly object IEnumerator.Current => Current; public readonly UnorderedElementEnumerable GetEnumerator() => this; readonly IEnumerator IEnumerable.GetEnumerator() => this; void IEnumerator.Reset() { _index = 0; _current = default; } readonly IEnumerator IEnumerable.GetEnumerator() => this; } } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/RebalancerCompatibleRule.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using System.Threading; using Orleans.Placement.Repartitioning; using Orleans.Placement.Rebalancing; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime.Placement.Repartitioning; #nullable enable /// /// Tolerance rule which is aware of the cluster size, and if rebalancer is enabled, it scales with the clusters imbalance. /// /// https://www.ledjonbehluli.com/posts/orleans_repartioner_rebalancer_coexistence/ internal class RebalancerCompatibleRule(IServiceProvider provider) : IImbalanceToleranceRule, ILifecycleParticipant, ILifecycleObserver, ISiloStatusListener, IActivationRebalancerReportListener { #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif private readonly Dictionary _silos = []; private uint _pairwiseImbalance; private double _clusterImbalance; // If rebalancer is not registered this has no effect on computing the tolerance. private readonly ISiloStatusOracle _oracle = provider.GetRequiredService(); private readonly IActivationRebalancer? _rebalancer = provider.GetService(); public bool IsSatisfiedBy(uint imbalance) => imbalance <= Volatile.Read(ref _pairwiseImbalance); public void SiloStatusChangeNotification(SiloAddress silo, SiloStatus status) { lock (_lock) { ref var statusRef = ref CollectionsMarshal.GetValueRefOrAddDefault(_silos, silo, out _); statusRef = status; UpdatePairwiseImbalance(); } } public void Participate(ISiloLifecycle lifecycle) => lifecycle.Subscribe(nameof(RebalancerCompatibleRule), ServiceLifecycleStage.ApplicationServices, this); public void OnReport(RebalancingReport report) { lock (_lock) { _clusterImbalance = report.ClusterImbalance; UpdatePairwiseImbalance(); } } private void UpdatePairwiseImbalance() { var activeSilos = _silos.Count(s => s.Value == SiloStatus.Active); var percentageOfBaseline = 100d / (1 + Math.Exp(0.07d * activeSilos - 4.8d)); if (percentageOfBaseline < 10d) percentageOfBaseline = 10d; var pairwiseImbalance = (uint)Math.Round(10.1d * percentageOfBaseline, 0); var toleranceFactor = Math.Cos(Math.PI * _clusterImbalance / 2); // This will always be 1 if rebalancer is not registered. _pairwiseImbalance = (uint)Math.Max(pairwiseImbalance * toleranceFactor, 0); } public Task OnStart(CancellationToken cancellationToken) { _oracle.SubscribeToSiloStatusEvents(this); _rebalancer?.SubscribeToReports(this); return Task.CompletedTask; } public Task OnStop(CancellationToken cancellationToken) { _oracle.UnSubscribeFromSiloStatusEvents(this); _rebalancer?.UnsubscribeFromReports(this); return Task.CompletedTask; } } ================================================ FILE: src/Orleans.Runtime/Placement/Repartitioning/RepartitionerMessageFilter.cs ================================================ #nullable enable using Orleans.Placement; namespace Orleans.Runtime.Placement.Repartitioning; internal interface IRepartitionerMessageFilter { bool IsAcceptable(Message message, out bool isSenderMigratable, out bool isTargetMigratable); } internal sealed class RepartitionerMessageFilter(GrainMigratabilityChecker checker) : IRepartitionerMessageFilter { public bool IsAcceptable(Message message, out bool isSenderMigratable, out bool isTargetMigratable) { isSenderMigratable = false; isTargetMigratable = false; // There are some edge cases when this can happen i.e. a grain invoking another one of its methods via AsReference<>, but we still exclude it // as wherever this grain would be located in the cluster, it would always be a local call (since it targets itself), this would add negative transfer cost // which would skew a potential relocation of this grain, while it shouldn't, because whenever this grain is located, it would still make local calls to itself. if (message.SendingGrain == message.TargetGrain) { return false; } isSenderMigratable = checker.IsMigratable(message.SendingGrain.Type, ImmovableKind.Repartitioner); isTargetMigratable = checker.IsMigratable(message.TargetGrain.Type, ImmovableKind.Repartitioner); // If both are not migratable types we ignore this. But if one of them is not, then we allow passing, as we wish to move grains closer to them, as with any type of grain. return isSenderMigratable || isTargetMigratable; } } ================================================ FILE: src/Orleans.Runtime/Placement/ResourceOptimizedPlacementDirector.cs ================================================ #nullable enable using System; using System.Buffers; using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Placement; // See: https://www.ledjonbehluli.com/posts/orleans_resource_placement_kalman/ internal sealed class ResourceOptimizedPlacementDirector : IPlacementDirector, ISiloStatisticsChangeListener { private const int FourKiloByte = 4096; private readonly SiloAddress _localSilo; private readonly NormalizedWeights _weights; private readonly float _localSiloPreferenceMargin; private readonly ConcurrentDictionary _siloStatistics = []; private readonly Task _cachedLocalSilo; public ResourceOptimizedPlacementDirector( ILocalSiloDetails localSiloDetails, DeploymentLoadPublisher deploymentLoadPublisher, IOptions options) { _localSilo = localSiloDetails.SiloAddress; _cachedLocalSilo = Task.FromResult(_localSilo); _weights = NormalizeWeights(options.Value); _localSiloPreferenceMargin = (float)options.Value.LocalSiloPreferenceMargin / 100; deploymentLoadPublisher.SubscribeToStatisticsChangeEvents(this); } private static NormalizedWeights NormalizeWeights(ResourceOptimizedPlacementOptions input) { int totalWeight = input.CpuUsageWeight + input.MemoryUsageWeight + input.AvailableMemoryWeight + input.MaxAvailableMemoryWeight + input.ActivationCountWeight; return totalWeight == 0 ? new(0f, 0f, 0f, 0f, 0f) : new( CpuUsageWeight: (float)input.CpuUsageWeight / totalWeight, MemoryUsageWeight: (float)input.MemoryUsageWeight / totalWeight, AvailableMemoryWeight: (float)input.AvailableMemoryWeight / totalWeight, MaxAvailableMemoryWeight: (float)input.MaxAvailableMemoryWeight / totalWeight, ActivationCountWeight: (float)input.ActivationCountWeight / totalWeight); } public Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { var compatibleSilos = context.GetCompatibleSilos(target); if (IPlacementDirector.GetPlacementHint(target.RequestContextData, compatibleSilos) is { } placementHint) { return Task.FromResult(placementHint); } if (compatibleSilos.Length == 0) { throw new SiloUnavailableException($"Cannot place grain '{target.GrainIdentity}' because there are no compatible silos."); } if (compatibleSilos.Length == 1) { return Task.FromResult(compatibleSilos[0]); } if (_siloStatistics.IsEmpty) { return Task.FromResult(compatibleSilos[Random.Shared.Next(compatibleSilos.Length)]); } // It is good practice not to allocate more than 1[KB] on the stack // but the size of ValueTuple = 24 bytes, by increasing // the limit to 4[KB] we can stackalloc for up to 4096 / 24 ~= 170 silos in a cluster. (int Index, float Score, float? LocalSiloScore) pick; int compatibleSilosCount = compatibleSilos.Length; if (compatibleSilosCount * Unsafe.SizeOf<(int, ResourceStatistics)>() <= FourKiloByte) { pick = MakePick(stackalloc (int, ResourceStatistics)[compatibleSilosCount]); } else { var relevantSilos = ArrayPool<(int, ResourceStatistics)>.Shared.Rent(compatibleSilosCount); pick = MakePick(relevantSilos.AsSpan()); ArrayPool<(int, ResourceStatistics)>.Shared.Return(relevantSilos); } var localSiloScore = pick.LocalSiloScore; if (!localSiloScore.HasValue || context.LocalSiloStatus != SiloStatus.Active || localSiloScore.Value - _localSiloPreferenceMargin > pick.Score) { var bestCandidate = compatibleSilos[pick.Index]; return Task.FromResult(bestCandidate); } return _cachedLocalSilo; (int PickIndex, float PickScore, float? LocalSiloScore) MakePick(scoped Span<(int, ResourceStatistics)> relevantSilos) { // Get all compatible silos which aren't overloaded int relevantSilosCount = 0; float maxMaxAvailableMemory = 0; int maxActivationCount = 0; ResourceStatistics? localSiloStatistics = null; for (var i = 0; i < compatibleSilos.Length; ++i) { var silo = compatibleSilos[i]; if (_siloStatistics.TryGetValue(silo, out var stats)) { if (!stats.IsOverloaded) { relevantSilos[relevantSilosCount++] = new(i, stats); } if (stats.MaxAvailableMemory > maxMaxAvailableMemory) { maxMaxAvailableMemory = stats.MaxAvailableMemory; } if (stats.ActivationCount > maxActivationCount) { maxActivationCount = stats.ActivationCount; } if (silo.Equals(_localSilo)) { localSiloStatistics = stats; } } } // Limit to the number of candidates added. relevantSilos = relevantSilos[0..relevantSilosCount]; Debug.Assert(relevantSilos.Length == relevantSilosCount); // Pick K silos from the list of compatible silos, where K is equal to the square root of the number of silos. // Eg, from 10 silos, we choose from 4. int candidateCount = (int)Math.Ceiling(Math.Sqrt(relevantSilosCount)); ShufflePrefix(relevantSilos, candidateCount); var candidates = relevantSilos[0..candidateCount]; (int Index, float Score) pick = (0, 1f); foreach (var (index, statistics) in candidates) { float score = CalculateScore(in statistics, maxMaxAvailableMemory, maxActivationCount); // It's very unlikely, but there could be more than 1 silo that has the same score, // so we apply some jittering to avoid pick the first one in the short-list. float scoreJitter = Random.Shared.NextSingle() / 100_000f; if (score + scoreJitter < pick.Score) { pick = (index, score); } } float? localSiloScore = null; if (localSiloStatistics.HasValue && !localSiloStatistics.Value.IsOverloaded) { var localStats = localSiloStatistics.Value; localSiloScore = CalculateScore(in localStats, maxMaxAvailableMemory, maxActivationCount); } return (pick.Index, pick.Score, localSiloScore); } // Variant of the Modern Fisher-Yates shuffle which stops after shuffling the first `prefixLength` elements, // which are the only elements we are interested in. // See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle static void ShufflePrefix(Span<(int SiloIndex, ResourceStatistics SiloStatistics)> values, int prefixLength) { Debug.Assert(prefixLength >= 0 && prefixLength <= values.Length); var max = values.Length; for (var i = 0; i < prefixLength; i++) { var chosen = Random.Shared.Next(i, max); if (chosen != i) { (values[chosen], values[i]) = (values[i], values[chosen]); } } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private float CalculateScore(ref readonly ResourceStatistics stats, float maxMaxAvailableMemory, int maxActivationCount) { float normalizedCpuUsage = stats.CpuUsage / 100f; Debug.Assert(normalizedCpuUsage >= 0f && normalizedCpuUsage <= 1.01f, "CPU usage should be normalized to [0, 1] range"); float score = _weights.CpuUsageWeight * normalizedCpuUsage; if (stats.MaxAvailableMemory > 0) { float normalizedMemoryUsage = stats.NormalizedMemoryUsage; Debug.Assert(normalizedMemoryUsage >= 0f && normalizedMemoryUsage <= 1.01f, "Memory usage should be normalized to [0, 1] range"); float normalizedAvailableMemory = stats.NormalizedAvailableMemory; Debug.Assert(normalizedAvailableMemory >= 0f && normalizedAvailableMemory <= 1.01f, "Available memory should be normalized to [0, 1] range"); float normalizedMaxAvailableMemory = stats.MaxAvailableMemory / maxMaxAvailableMemory; Debug.Assert(normalizedMaxAvailableMemory >= 0f && normalizedMaxAvailableMemory <= 1.01f, "Max available memory should be normalized to [0, 1] range"); score += _weights.MemoryUsageWeight * normalizedMemoryUsage + _weights.AvailableMemoryWeight * (1 - normalizedAvailableMemory) + _weights.MaxAvailableMemoryWeight * (1 - normalizedMaxAvailableMemory); } var normalizedActivationCount = stats.ActivationCount / (float)maxActivationCount; Debug.Assert(normalizedActivationCount >= 0f && normalizedActivationCount <= 1.01f, "Activation count should be normalized to [0, 1] range"); score += _weights.ActivationCountWeight * normalizedActivationCount; Debug.Assert(score >= 0f && score <= 1.01f, "Score should be normalized to [0, 1] range"); return score; } public void RemoveSilo(SiloAddress address) => _siloStatistics.TryRemove(address, out _); public void SiloStatisticsChangeNotification(SiloAddress address, SiloRuntimeStatistics statistics) => _siloStatistics.AddOrUpdate( key: address, factoryArgument: statistics, addValueFactory: static (_, statistics) => ResourceStatistics.FromRuntime(statistics), updateValueFactory: static (_, _, statistics) => ResourceStatistics.FromRuntime(statistics)); private record NormalizedWeights(float CpuUsageWeight, float MemoryUsageWeight, float AvailableMemoryWeight, float MaxAvailableMemoryWeight, float ActivationCountWeight); private readonly record struct ResourceStatistics(bool IsOverloaded, float CpuUsage, float NormalizedMemoryUsage, float NormalizedAvailableMemory, float MaxAvailableMemory, int ActivationCount) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ResourceStatistics FromRuntime(SiloRuntimeStatistics statistics) => new( IsOverloaded: statistics.IsOverloaded, CpuUsage: statistics.EnvironmentStatistics.FilteredCpuUsagePercentage, NormalizedMemoryUsage: statistics.EnvironmentStatistics.NormalizedFilteredMemoryUsage, NormalizedAvailableMemory: statistics.EnvironmentStatistics.NormalizedFilteredAvailableMemory, MaxAvailableMemory: statistics.EnvironmentStatistics.MaximumAvailableMemoryBytes, ActivationCount: statistics.ActivationCount); } } ================================================ FILE: src/Orleans.Runtime/Placement/SiloRoleBasedPlacementDirector.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Orleans.Runtime.MembershipService; namespace Orleans.Runtime.Placement { internal class SiloRoleBasedPlacementDirector : IPlacementDirector { private readonly MembershipTableManager membershipTableManager; public SiloRoleBasedPlacementDirector(MembershipTableManager membershipTableManager) { this.membershipTableManager = membershipTableManager; } public virtual Task OnAddActivation( PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { var siloRole = target.GrainIdentity.Key.ToString(); var compatibleSilos = membershipTableManager.MembershipTableSnapshot.Entries .Where(s => s.Value.Status == SiloStatus.Active && s.Value.RoleName == siloRole) .Select(s => s.Key) .Intersect(context.GetCompatibleSilos(target)) .ToArray(); if (compatibleSilos == null || compatibleSilos.Length == 0) { throw new OrleansException($"Cannot place grain with RoleName {siloRole}. Either Role name is invalid or there are no active silos with type {siloRole} in MembershipTableSnapshot registered yet."); } // If a valid placement hint was specified, use it. if (IPlacementDirector.GetPlacementHint(target.RequestContextData, compatibleSilos) is { } placementHint) { return Task.FromResult(placementHint); } return Task.FromResult(compatibleSilos[Random.Shared.Next(compatibleSilos.Length)]); } } } ================================================ FILE: src/Orleans.Runtime/Placement/StatelessWorkerDirector.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Runtime.Placement { internal class StatelessWorkerDirector : IPlacementDirector { public Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context) { var compatibleSilos = context.GetCompatibleSilos(target); // If the current silo is not shutting down, place locally if we are compatible if (!context.LocalSiloStatus.IsTerminating()) { foreach (var silo in compatibleSilos) { if (silo.Equals(context.LocalSilo)) { return Task.FromResult(context.LocalSilo); } } } // otherwise, place somewhere else return Task.FromResult(compatibleSilos[Random.Shared.Next(compatibleSilos.Length)]); } } } ================================================ FILE: src/Orleans.Runtime/Properties/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } ================================================ FILE: src/Orleans.Runtime/README.md ================================================ # Microsoft Orleans Runtime ## Introduction Microsoft Orleans Runtime is the core server-side component of Orleans. It hosts and executes grains, manages grain lifecycles, and provides all the runtime services necessary for a functioning Orleans server (silo). ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Runtime ``` This package is automatically included when you reference the Orleans Server metapackage. ## Example - Configuring a Silo ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering(); }); await builder.Build().RunAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Server configuration](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) - [Silo lifecycle](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/silo-lifecycle) - [Clustering](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Runtime/Scheduler/ActivationTaskScheduler.cs ================================================ #nullable enable using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime.Scheduler { /// /// A single-concurrency, in-order task scheduler for per-activation work scheduling. /// [DebuggerDisplay("ActivationTaskScheduler RunQueue={workerGroup.ExternalWorkItemCount} GrainContext={workerGroup.GrainContext}")] internal sealed partial class ActivationTaskScheduler : TaskScheduler { private readonly ILogger logger; private static long idCounter; private readonly long myId; private readonly WorkItemGroup workerGroup; #if EXTRA_STATS private readonly CounterStatistic turnsExecutedStatistic; #endif internal ActivationTaskScheduler(WorkItemGroup workGroup, ILogger logger) { this.logger = logger; myId = Interlocked.Increment(ref idCounter); workerGroup = workGroup; #if EXTRA_STATS turnsExecutedStatistic = CounterStatistic.FindOrCreate(name + ".TasksExecuted"); #endif LogCreatedTaskScheduler(this, workerGroup.GrainContext); } /// Gets an enumerable of the tasks currently scheduled on this scheduler. /// An enumerable of the tasks currently scheduled. protected override IEnumerable GetScheduledTasks() => this.workerGroup.GetScheduledTasks(); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RunTaskFromWorkItemGroup(Task task) { bool done = TryExecuteTask(task); if (!done) { #if DEBUG LogWarnTryExecuteTaskNotDone(task.Id, task.Status); #endif } } /// Queues a task to the scheduler. /// The task to be queued. protected override void QueueTask(Task task) { #if DEBUG LogTraceQueueTask(myId, task.Id); #endif workerGroup.EnqueueTask(task); } /// /// Determines whether the provided can be executed synchronously in this call, and if it can, executes it. /// /// /// A Boolean value indicating whether the task was executed inline. /// /// The to be executed. /// A Boolean denoting whether or not task has previously been queued. If this parameter is True, then the task may have been previously queued (scheduled); if False, then the task is known not to have been queued, and this call is being made in order to execute the task inline without queuing it. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { var canExecuteInline = !taskWasPreviouslyQueued && Equals(RuntimeContext.Current, workerGroup.GrainContext); #if DEBUG LogTraceTryExecuteTaskInline( myId, task.Id, task.Status, taskWasPreviouslyQueued, canExecuteInline, workerGroup.ExternalWorkItemCount); #endif if (!canExecuteInline) { #if DEBUG LogTraceTryExecuteTaskInlineNotDone(myId, task.Id, task.Status); #endif return false; } #if EXTRA_STATS turnsExecutedStatistic.Increment(); #endif #if DEBUG LogTraceTryExecuteTaskInlineYes(myId, task.Id, System.Environment.CurrentManagedThreadId); #endif // Try to run the task. bool done = TryExecuteTask(task); #if DEBUG if (!done) { LogWarnTryExecuteTaskNotDone(task.Id, task.Status); } LogTraceTryExecuteTaskInlineCompleted(myId, task.Id, System.Environment.CurrentManagedThreadId, done); #endif return done; } public override string ToString() => $"{GetType().Name}-{myId}:Queued={workerGroup.ExternalWorkItemCount}"; [LoggerMessage( Level = LogLevel.Debug, Message = "Created {TaskScheduler} with GrainContext={GrainContext}" )] private partial void LogCreatedTaskScheduler(ActivationTaskScheduler taskScheduler, IGrainContext grainContext); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.SchedulerTaskExecuteIncomplete4, Message = "RunTask: Incomplete base.TryExecuteTask for Task Id={TaskId} with Status={TaskStatus}" )] private partial void LogWarnTryExecuteTaskNotDone(int taskId, TaskStatus taskStatus); [LoggerMessage( Level = LogLevel.Trace, Message = "{TaskScheduler} QueueTask Task Id={TaskId}" )] private partial void LogTraceQueueTask(long taskScheduler, int taskId); [LoggerMessage( Level = LogLevel.Trace, Message = "{TaskScheduler} TryExecuteTaskInline Task Id={TaskId} Status={Status} PreviouslyQueued={PreviouslyQueued} CanExecute={CanExecute} Queued={Queued}" )] private partial void LogTraceTryExecuteTaskInline(long taskScheduler, int taskId, TaskStatus status, bool previouslyQueued, bool canExecute, int queued); [LoggerMessage( Level = LogLevel.Trace, Message = "{TaskScheduler} Completed TryExecuteTaskInline Task Id={TaskId} Status={Status} Execute=No" )] private partial void LogTraceTryExecuteTaskInlineNotDone(long taskScheduler, int taskId, TaskStatus status); [LoggerMessage( Level = LogLevel.Trace, Message = "{TaskScheduler} TryExecuteTaskInline Task Id={TaskId} Thread={Thread} Execute=Yes" )] private partial void LogTraceTryExecuteTaskInlineYes(long taskScheduler, int taskId, int thread); [LoggerMessage( Level = LogLevel.Trace, Message = "{TaskScheduler} Completed TryExecuteTaskInline Task Id={TaskId} Thread={Thread} Execute=Done Ok={Ok}" )] private partial void LogTraceTryExecuteTaskInlineCompleted(long taskScheduler, int taskId, int thread, bool ok); } } ================================================ FILE: src/Orleans.Runtime/Scheduler/ClosureWorkItem.cs ================================================ #nullable enable using System; using System.Threading.Tasks; namespace Orleans.Runtime.Scheduler { internal sealed class AsyncClosureWorkItem : WorkItemBase { private readonly TaskCompletionSource completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly Func continuation; private readonly string? name; public override string Name => this.name ?? GetMethodName(this.continuation); public Task Task => this.completion.Task; public AsyncClosureWorkItem(Func closure, string name, IGrainContext grainContext) { this.continuation = closure; this.name = name; this.GrainContext = grainContext; } public AsyncClosureWorkItem(Func closure, IGrainContext grainContext) { this.continuation = closure; this.GrainContext = grainContext; } public override async void Execute() { try { RequestContext.Clear(); await this.continuation(); this.completion.TrySetResult(true); } catch (Exception exception) { this.completion.TrySetException(exception); } } public override IGrainContext GrainContext { get; } internal static string GetMethodName(Delegate action) => $"{action.Target}->{action.Method}"; } internal sealed class AsyncClosureWorkItem : WorkItemBase { private readonly TaskCompletionSource completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly Func> continuation; private readonly string? name; public override string Name => this.name ?? AsyncClosureWorkItem.GetMethodName(this.continuation); public Task Task => this.completion.Task; public AsyncClosureWorkItem(Func> closure, string name, IGrainContext grainContext) { this.continuation = closure; this.name = name; this.GrainContext = grainContext; } public AsyncClosureWorkItem(Func> closure, IGrainContext grainContext) { this.continuation = closure; this.GrainContext = grainContext; } public override async void Execute() { try { RequestContext.Clear(); var result = await this.continuation(); this.completion.TrySetResult(result); } catch (Exception exception) { this.completion.TrySetException(exception); } } public override IGrainContext GrainContext { get; } } internal sealed class ClosureWorkItem(Action closure, TState state, string? name, IGrainContext grainContext) : WorkItemBase { private readonly TaskCompletionSource _completion = new(TaskCreationOptions.RunContinuationsAsynchronously); public override string Name => name ?? AsyncClosureWorkItem.GetMethodName(closure); public Task Task => _completion.Task; public override void Execute() { try { RequestContext.Clear(); closure(state); _completion.TrySetResult(true); } catch (Exception exception) { _completion.TrySetException(exception); } } public override IGrainContext GrainContext { get; } = grainContext; } internal sealed class StatefulAsyncClosureWorkItem : WorkItemBase { private readonly TaskCompletionSource _completion = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly Func _continuation; private readonly TState _state; private readonly string? _name; public override string Name => _name ?? AsyncClosureWorkItem.GetMethodName(_continuation); public Task Task => _completion.Task; public StatefulAsyncClosureWorkItem(Func closure, TState state, IGrainContext grainContext) { _continuation = closure; _state = state; GrainContext = grainContext; } public StatefulAsyncClosureWorkItem(Func closure, TState state, string name, IGrainContext grainContext) { _continuation = closure; _state = state; _name = name; GrainContext = grainContext; } public override async void Execute() { try { RequestContext.Clear(); await _continuation(_state); _completion.TrySetResult(true); } catch (Exception exception) { _completion.TrySetException(exception); } } public override IGrainContext GrainContext { get; } } } ================================================ FILE: src/Orleans.Runtime/Scheduler/IWorkItem.cs ================================================ using System; namespace Orleans.Runtime.Scheduler { internal interface IWorkItem { string Name { get; } IGrainContext GrainContext { get; } void Execute(); internal static readonly Action ExecuteWorkItem = state => ((IWorkItem)state).Execute(); } } ================================================ FILE: src/Orleans.Runtime/Scheduler/SchedulerExtensions.cs ================================================ #nullable enable using System; using System.Threading.Tasks; namespace Orleans.Runtime.Scheduler { internal static class SchedulerExtensions { internal static Task QueueTask(this IGrainContext targetContext, Func taskFunc) { var workItem = new AsyncClosureWorkItem(taskFunc, targetContext); targetContext.Scheduler.QueueWorkItem(workItem); return workItem.Task; } internal static Task QueueTask(this WorkItemGroup scheduler, Func taskFunc, IGrainContext targetContext) { var workItem = new AsyncClosureWorkItem(taskFunc, targetContext); scheduler.QueueWorkItem(workItem); return workItem.Task; } internal static Task QueueAction(this IGrainContext targetContext, Action action, TState state, string? name = null) { var workItem = new ClosureWorkItem(action, state, name, targetContext); targetContext.Scheduler.QueueWorkItem(workItem); return workItem.Task; } internal static Task RunOrQueueTask(this IGrainContext targetContext, Func taskFunc) { var currentContext = RuntimeContext.Current; if (currentContext != null && currentContext.Equals(targetContext)) { try { return taskFunc(); } catch (Exception exc) { return Task.FromResult(exc); } } var workItem = new AsyncClosureWorkItem(taskFunc, targetContext); targetContext.Scheduler.QueueWorkItem(workItem); return workItem.Task; } internal static ValueTask RunOrQueueTask(this IGrainContext targetContext, Func taskFunc, TState state) { var currentContext = RuntimeContext.Current; if (currentContext != null && currentContext.Equals(targetContext)) { try { return taskFunc(state); } catch (Exception exc) { return new(Task.FromException(exc)); } } var workItem = new StatefulAsyncClosureWorkItem(taskFunc, state, targetContext); targetContext.Scheduler.QueueWorkItem(workItem); return new(workItem.Task); } } } ================================================ FILE: src/Orleans.Runtime/Scheduler/TaskSchedulerUtils.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Orleans.Runtime.Internal; namespace Orleans.Runtime.Scheduler { internal static class TaskSchedulerUtils { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void QueueAction(this ActivationTaskScheduler taskScheduler, Action action) { using var suppressExecutionContext = new ExecutionContextSuppressor(); var task = new Task(action); task.Start(taskScheduler); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void QueueAction(this ActivationTaskScheduler taskScheduler, Action action, object state) { using var suppressExecutionContext = new ExecutionContextSuppressor(); var task = new Task(action, state); task.Start(taskScheduler); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void QueueWorkItem(this WorkItemGroup scheduler, IWorkItem workItem) { QueueAction(scheduler.TaskScheduler, IWorkItem.ExecuteWorkItem, workItem); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void QueueWorkItem(this IWorkItemScheduler scheduler, IWorkItem workItem) { scheduler.QueueAction(IWorkItem.ExecuteWorkItem, workItem); } } } ================================================ FILE: src/Orleans.Runtime/Scheduler/WorkItemBase.cs ================================================ using System; namespace Orleans.Runtime.Scheduler { internal abstract class WorkItemBase : IWorkItem, ISpanFormattable { public abstract IGrainContext GrainContext { get; } public abstract string Name { get; } public abstract void Execute(); public sealed override string ToString() => $"{this}"; string IFormattable.ToString(string format, IFormatProvider formatProvider) => ToString(); public virtual bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) => destination.TryWrite($"[{GetType().Name} WorkItem Name={Name}, Ctx={GrainContext}{(GrainContext != null ? null : "null")}]", out charsWritten); } } ================================================ FILE: src/Orleans.Runtime/Scheduler/WorkItemGroup.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime.Scheduler; [DebuggerDisplay("WorkItemGroup Context={GrainContext} State={_state}")] internal sealed class WorkItemGroup : IThreadPoolWorkItem, IWorkItemScheduler { private enum WorkGroupStatus : byte { Waiting = 0, Runnable = 1, Running = 2 } private readonly ILogger _log; #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly Queue _workItems = new(); private readonly SchedulingOptions _schedulingOptions; private long _totalItemsEnqueued; private long _totalItemsProcessed; private long _lastLongQueueWarningTimestamp; private WorkGroupStatus _state; private Task? _currentTask; private long _currentTaskStarted; internal ActivationTaskScheduler TaskScheduler { get; } public IGrainContext GrainContext { get; set; } internal int ExternalWorkItemCount { get { lock (_lockObj) { return _workItems.Count; } } } public WorkItemGroup( IGrainContext grainContext, ILogger logger, ILogger activationTaskSchedulerLogger, IOptions schedulingOptions) { ArgumentNullException.ThrowIfNull(grainContext); GrainContext = grainContext; _schedulingOptions = schedulingOptions.Value; _state = WorkGroupStatus.Waiting; _log = logger; TaskScheduler = new ActivationTaskScheduler(this, activationTaskSchedulerLogger); } /// /// Adds a task to this activation. /// If we're adding it to the run list and we used to be waiting, now we're runnable. /// /// The work item to add. public void EnqueueTask(Task task) { #if DEBUG if (_log.IsEnabled(LogLevel.Trace)) { _log.LogTrace( "EnqueueWorkItem {Task} into {GrainContext} when TaskScheduler.Current={TaskScheduler}", task, GrainContext, System.Threading.Tasks.TaskScheduler.Current); } #endif lock (_lockObj) { long thisSequenceNumber = _totalItemsEnqueued++; int count = _workItems.Count; _workItems.Enqueue(task); int maxPendingItemsLimit = _schedulingOptions.MaxPendingWorkItemsSoftLimit; if (maxPendingItemsLimit > 0 && count > maxPendingItemsLimit) { var now = Environment.TickCount64; if (now > _lastLongQueueWarningTimestamp + 10_000) { LogTooManyTasksInQueue(count, maxPendingItemsLimit); } _lastLongQueueWarningTimestamp = now; } if (_state != WorkGroupStatus.Waiting) { return; } _state = WorkGroupStatus.Runnable; #if DEBUG if (_log.IsEnabled(LogLevel.Trace)) { _log.LogTrace( "Add to RunQueue {Task}, #{SequenceNumber}, onto {GrainContext}", task, thisSequenceNumber, GrainContext); } #endif ScheduleExecution(this); } } [MethodImpl(MethodImplOptions.NoInlining)] private void LogTooManyTasksInQueue(int count, int maxPendingItemsLimit) { _log.LogWarning( (int)ErrorCode.SchedulerTooManyPendingItems, "{PendingWorkItemCount} pending work items for group {WorkGroupName}, exceeding the warning threshold of {WarningThreshold}", count, GrainContext?.ToString() ?? "Unknown", maxPendingItemsLimit); } /// /// For debugger purposes only. /// internal IEnumerable GetScheduledTasks() { foreach (var task in _workItems) { yield return task; } } // Execute one or more turns for this activation. // This method is always called in a single-threaded environment -- that is, no more than one // thread will be in this method at once -- but other async threads may still be queueing tasks, etc. public void Execute() { RuntimeContext.SetExecutionContext(GrainContext, out var originalContext); var turnWarningDurationMs = (long)Math.Ceiling(_schedulingOptions.TurnWarningLengthThreshold.TotalMilliseconds); var activationSchedulingQuantumMs = (long)_schedulingOptions.ActivationSchedulingQuantum.TotalMilliseconds; try { // Process multiple items -- drain the queue (up to max items) for this activation long loopStart, taskStart, taskEnd; loopStart = taskStart = taskEnd = Environment.TickCount64; do { Task task; lock (_lockObj) { _state = WorkGroupStatus.Running; // Get the first Work Item on the list if (_workItems.Count > 0) { _currentTask = task = _workItems.Dequeue(); _currentTaskStarted = taskStart; } else { // If the list is empty, then we're done break; } } #if DEBUG LogTaskStart(task); #endif try { TaskScheduler.RunTaskFromWorkItemGroup(task); } finally { _totalItemsProcessed++; taskEnd = Environment.TickCount64; var taskDurationMs = taskEnd - taskStart; taskStart = taskEnd; if (taskDurationMs > turnWarningDurationMs) { SchedulerInstruments.LongRunningTurnsCounter.Add(1); LogLongRunningTurn(task, taskDurationMs); } _currentTask = null; } } while (activationSchedulingQuantumMs <= 0 || taskEnd - loopStart < activationSchedulingQuantumMs); } catch (Exception ex) { LogTaskLoopError(ex); } finally { // Now we're not Running anymore. // If we left work items on our run list, we're Runnable, and need to go back on the silo run queue; // If our run list is empty, then we're waiting. lock (_lockObj) { if (_workItems.Count > 0) { _state = WorkGroupStatus.Runnable; ScheduleExecution(this); } else { _state = WorkGroupStatus.Waiting; } } RuntimeContext.ResetExecutionContext(originalContext); } } #if DEBUG [MethodImpl(MethodImplOptions.NoInlining)] private void LogTaskStart(Task task) { if (_log.IsEnabled(LogLevel.Trace)) { _log.LogTrace( "About to execute task '{Task}' in GrainContext={GrainContext}", task, GrainContext); } } #endif [MethodImpl(MethodImplOptions.NoInlining)] private void LogTaskLoopError(Exception ex) { _log.LogError( (int)ErrorCode.Runtime_Error_100032, ex, "Worker thread {Thread} caught an exception thrown from IWorkItem.Execute", Environment.CurrentManagedThreadId); } [MethodImpl(MethodImplOptions.NoInlining)] private void LogLongRunningTurn(Task task, long taskDurationMs) { if (Debugger.IsAttached) { return; } var taskDuration = TimeSpan.FromMilliseconds(taskDurationMs); _log.LogWarning( (int)ErrorCode.SchedulerTurnTooLong3, "Task {Task} in WorkGroup {GrainContext} took elapsed time {Duration} for execution, which is longer than {TurnWarningLengthThreshold}. Running on thread {Thread}", task.AsyncState ?? task, GrainContext.ToString(), taskDuration.ToString("g"), _schedulingOptions.TurnWarningLengthThreshold, Environment.CurrentManagedThreadId.ToString()); } public override string ToString() => $"{(GrainContext is SystemTarget ? "System*" : "")}WorkItemGroup:Name={GrainContext?.ToString() ?? "Unknown"},WorkGroupStatus={_state}"; public string DumpStatus() { lock (_lockObj) { var sb = new StringBuilder(); sb.Append(this); sb.AppendFormat(". Currently QueuedWorkItems={0}; Total Enqueued={1}; Total processed={2}; ", _workItems.Count, _totalItemsEnqueued, _totalItemsProcessed); if (_currentTask is Task task) { sb.AppendFormat(" Executing Task Id={0} Status={1} for {2}.", task.Id, task.Status, TimeSpan.FromMilliseconds(Environment.TickCount64 - _currentTaskStarted)); } sb.AppendFormat("TaskRunner={0}; ", TaskScheduler); if (GrainContext != null) { var detailedStatus = GrainContext switch { ActivationData activationData => activationData.ToDetailedString(includeExtraDetails: true), SystemTarget systemTarget => systemTarget.ToDetailedString(), object obj => obj.ToString(), _ => "None" }; sb.AppendFormat("Detailed context=<{0}>", detailedStatus); } return sb.ToString(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ScheduleExecution(WorkItemGroup workItem) => ThreadPool.UnsafeQueueUserWorkItem(workItem, preferLocal: true); public void QueueAction(Action action) => TaskScheduler.QueueAction(action); public void QueueAction(Action action, object state) => TaskScheduler.QueueAction(action, state); public void QueueTask(Task task) => task.Start(TaskScheduler); } ================================================ FILE: src/Orleans.Runtime/Services/GrainService.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime.ConsistentRing; using Orleans.Runtime.Scheduler; using Orleans.Services; namespace Orleans.Runtime { /// Base class for implementing a grain-like partitioned service with per silo instances of it automatically instantiated and started by silo runtime public abstract partial class GrainService : SystemTarget, IRingRangeListener, IGrainService { private readonly IConsistentRingProvider ring; private readonly string typeName; private GrainServiceStatus status; private readonly ILogger Logger; /// Gets the token for signaling cancellation upon stopping of grain service protected CancellationTokenSource StoppedCancellationTokenSource { get; } /// Gets the monotonically increasing serial number of the version of the ring range owned by the grain service instance protected int RangeSerialNumber { get; private set; } /// Gets the range of the partitioning ring currently owned by the grain service instance protected IRingRange RingRange { get; private set; } /// Gets the status of the grain service instance protected GrainServiceStatus Status { get { return status; } set { OnStatusChange(status, value); status = value; } } /// Only to make Reflection happy. Do not use it in your implementation [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [Obsolete("Do not call the empty constructor.")] protected GrainService() : base() { throw new Exception("This should not be constructed by client code."); } /// Constructor to use for grain services internal GrainService(GrainId grainId, IConsistentRingProvider ringProvider, SystemTargetShared shared) : base(SystemTargetGrainId.Create(grainId.Type, shared.SiloAddress), shared) { typeName = this.GetType().FullName; Logger = shared.LoggerFactory.CreateLogger(typeName); ring = ringProvider; StoppedCancellationTokenSource = new CancellationTokenSource(); } /// Constructor to use for grain services protected GrainService(GrainId grainId, Silo silo, ILoggerFactory loggerFactory) : this(grainId, silo.RingProvider, silo.Services.GetRequiredService()) { } /// /// Invoked upon initialization of the service /// /// The service provider. /// A representing the work performed. public virtual Task Init(IServiceProvider serviceProvider) { return Task.CompletedTask; } private void OnStatusChange(GrainServiceStatus oldStatus, GrainServiceStatus newStatus) { if (oldStatus != GrainServiceStatus.Started && newStatus == GrainServiceStatus.Started) { ring.SubscribeToRangeChangeEvents(this); } if (oldStatus != GrainServiceStatus.Stopped && newStatus == GrainServiceStatus.Stopped) { ring.UnSubscribeFromRangeChangeEvents(this); } } /// Invoked when service is being started /// A representing the work performed. public virtual Task Start() { RingRange = ring.GetMyRange(); LogInformationServiceStarting(Logger, this.typeName, Silo, new(Silo), RingRange); StartInBackground().Ignore(); return Task.CompletedTask; } /// /// Deferred part of initialization that executes after the service is already started (to speed up startup). /// Sets Status to Started. /// /// A representing the work performed. protected virtual Task StartInBackground() { Status = GrainServiceStatus.Started; return Task.CompletedTask; } /// Invoked when service is being stopped /// A representing the work performed. public virtual Task Stop() { StoppedCancellationTokenSource.Cancel(); LogInformationServiceStopping(Logger, typeName); Status = GrainServiceStatus.Stopped; return Task.CompletedTask; } /// void IRingRangeListener.RangeChangeNotification(IRingRange oldRange, IRingRange newRange, bool increased) { this.WorkItemGroup.QueueTask(() => OnRangeChange(oldRange, newRange, increased), this).Ignore(); } /// /// Invoked when the ring range owned by the service instance changes because of a change in the cluster state /// /// The old range. /// The new range. /// A value indicating whether the range has increased. /// A representing the work performed. public virtual Task OnRangeChange(IRingRange oldRange, IRingRange newRange, bool increased) { LogInformationRangeChanged(Logger, oldRange, newRange, increased); RingRange = newRange; RangeSerialNumber++; return Task.CompletedTask; } private readonly struct SiloAddressHashCodeLogValue(SiloAddress silo) { public override string ToString() => silo.GetConsistentHashCode().ToString("X8"); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.RS_ServiceStarting, Message = "Starting {TypeName} grain service on: {Silo} x{HashCode}, with range {RingRange}" )] private static partial void LogInformationServiceStarting(ILogger logger, string typeName, SiloAddress silo, SiloAddressHashCodeLogValue hashCode, IRingRange ringRange); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.RS_ServiceStopping, Message = "Stopping {TypeName} grain service" )] private static partial void LogInformationServiceStopping(ILogger logger, string typeName); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.RS_RangeChanged, Message = "My range changed from {OldRange} to {NewRange} increased = {Increased}" )] private static partial void LogInformationRangeChanged(ILogger logger, IRingRange oldRange, IRingRange newRange, bool increased); /// Possible statuses of a grain service protected enum GrainServiceStatus { /// Initialization is in progress Booting = 0, /// Service successfully started Started, /// Service has been stopped Stopped, } } } ================================================ FILE: src/Orleans.Runtime/Services/GrainServiceClient.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; using Orleans.Runtime.ConsistentRing; using Orleans.Services; namespace Orleans.Runtime.Services { /// /// Proxies requests to the appropriate GrainService based on the appropriate Ring partitioning strategy. /// /// public abstract class GrainServiceClient : IGrainServiceClient where TGrainService : IGrainService { private readonly IInternalGrainFactory grainFactory; private readonly IConsistentRingProvider ringProvider; private readonly GrainType grainType; /// /// Currently we only support a single GrainService per Silo, when multiple are supported we will request the number of GrainServices to partition per silo here. /// /// The service provider. protected GrainServiceClient(IServiceProvider serviceProvider) { grainFactory = serviceProvider.GetRequiredService(); ringProvider = serviceProvider.GetRequiredService(); // GrainInterfaceMap only holds IGrain types, not ISystemTarget types, so resolved via Orleans.CodeGeneration. // Resolve this before merge. var grainTypeCode = GrainInterfaceUtils.GetGrainClassTypeCode(typeof(TGrainService)); grainType = SystemTargetGrainId.CreateGrainServiceGrainType(grainTypeCode, null); } /// /// Gets a reference to the the currently executing grain. /// protected GrainReference CurrentGrainReference => RuntimeContext.Current?.GrainReference; /// /// Get a reference to the responsible for actioning the request based on the . /// protected TGrainService GetGrainService(GrainId callingGrainId) { return GetGrainService(callingGrainId.GetUniformHashCode()); } /// /// Get a reference to the responsible for actioning the request based on the . /// protected TGrainService GetGrainService(uint key) { return GetGrainService(ringProvider.GetPrimaryTargetSilo(key)); } /// /// Get a reference to the responsible for actioning the request based on the . /// protected TGrainService GetGrainService(SiloAddress destination) { var grainId = SystemTargetGrainId.CreateGrainServiceGrainId(grainType, destination); var grainService = grainFactory.GetSystemTarget(grainId); return grainService; } } } ================================================ FILE: src/Orleans.Runtime/Services/GrainServiceFactory.cs ================================================ using Orleans.Services; namespace Orleans.Runtime { /// /// Functionality for interacting with grain services. /// public interface IGrainServiceFactory { /// /// Casts a grain reference to a typed grain service reference. /// Used by grain indexing. /// /// The grain service interface. /// The grain reference. /// A reference to the specified grain service. T CastToGrainServiceReference(GrainReference grainReference) where T : IGrainService; } internal class GrainServiceFactory : IGrainServiceFactory { private readonly IRuntimeClient runtimeClient; public GrainServiceFactory(IRuntimeClient runtimeClient) { this.runtimeClient = runtimeClient; } public T CastToGrainServiceReference(GrainReference grainReference) where T : IGrainService => this.runtimeClient.InternalGrainFactory.GetSystemTarget(grainReference.GrainId); } } ================================================ FILE: src/Orleans.Runtime/Services/GrainServicesSiloBuilderExtensions.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; using Orleans.Runtime; using Orleans.Services; namespace Orleans.Hosting { /// /// Extension methods for registering grain services. /// public static class GrainServicesSiloBuilderExtensions { /// /// Registers an application grain service to be started with the silo. /// /// The grain service implementation type. /// The builder. /// The silo builder. public static ISiloBuilder AddGrainService(this ISiloBuilder builder) where T : GrainService { return builder.ConfigureServices(services => services.AddGrainService()); } private static IGrainService GrainServiceFactory(Type serviceType, IServiceProvider services) { var grainServiceInterfaceType = Array.Find(serviceType.GetInterfaces(), x => x.GetInterfaces().Contains(typeof(IGrainService))); if (grainServiceInterfaceType is null) { throw new InvalidOperationException(string.Format($"Cannot find an interface on {serviceType.FullName} which implements IGrainService")); } var typeCode = GrainInterfaceUtils.GetGrainClassTypeCode(grainServiceInterfaceType); var grainId = SystemTargetGrainId.CreateGrainServiceGrainId(typeCode, null, SiloAddress.Zero); var grainService = (IGrainService)ActivatorUtilities.CreateInstance(services, serviceType, grainId); return grainService; } /// /// Registers an application grain service to be started with the silo. /// /// The grain service implementation type. /// The service collection. /// The service collection. public static IServiceCollection AddGrainService(this IServiceCollection services) { return services.AddGrainService(typeof(T)); } /// /// Registers an application grain service to be started with the silo. /// /// The service collection. /// The grain service implementation type. /// The service collection. public static IServiceCollection AddGrainService(this IServiceCollection services, Type grainServiceType) { return services.AddSingleton(sp => GrainServiceFactory(grainServiceType, sp)); } } } ================================================ FILE: src/Orleans.Runtime/Silo/LocalSiloDetails.cs ================================================ using System; using System.Net; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime { internal class LocalSiloDetails : ILocalSiloDetails { private readonly Lazy siloAddressLazy; private readonly Lazy gatewayAddressLazy; public LocalSiloDetails( IOptions siloOptions, IOptions clusterOptions, IOptions siloEndpointOptions) { this.Name = siloOptions.Value.SiloName; this.ClusterId = clusterOptions.Value.ClusterId; this.DnsHostName = Dns.GetHostName(); var endpointOptions = siloEndpointOptions.Value; this.siloAddressLazy = new Lazy(() => SiloAddress.New(endpointOptions.AdvertisedIPAddress, endpointOptions.SiloPort, SiloAddress.AllocateNewGeneration())); this.gatewayAddressLazy = new Lazy(() => { var publicProxyEndpoint = endpointOptions.GetPublicProxyEndpoint(); return publicProxyEndpoint != null ? SiloAddress.New(publicProxyEndpoint, 0) : null; }); } /// public string Name { get; } /// public string ClusterId { get; } /// public string DnsHostName { get; } /// public SiloAddress SiloAddress => this.siloAddressLazy.Value; /// public SiloAddress GatewayAddress => this.gatewayAddressLazy.Value; } } ================================================ FILE: src/Orleans.Runtime/Silo/Silo.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime.ConsistentRing; using Orleans.Runtime.Messaging; using Orleans.Runtime.Scheduler; using Orleans.Services; using Orleans.Configuration; using Orleans.Internal; using System.Net; namespace Orleans.Runtime { /// /// Orleans silo. /// public sealed partial class Silo : IAsyncDisposable, IDisposable { /// Standard name for Primary silo. public const string PrimarySiloName = "Primary"; private readonly ILocalSiloDetails siloDetails; private readonly MessageCenter messageCenter; private readonly ILogger logger; private readonly TaskCompletionSource siloTerminatedTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly InsideRuntimeClient runtimeClient; private readonly Watchdog platformWatchdog; private readonly TimeSpan waitForMessageToBeQueuedForOutbound; private readonly TimeSpan initTimeout; #if NET9_0_OR_GREATER private readonly Lock lockable = new(); #else private readonly object lockable = new(); #endif private readonly GrainFactory grainFactory; private readonly ISiloLifecycleSubject siloLifecycle; private readonly List grainServices = new List(); private readonly ILoggerFactory loggerFactory; internal IConsistentRingProvider RingProvider { get; } internal SystemStatus SystemStatus { get; set; } internal IServiceProvider Services { get; } /// Gets the address of this silo. public SiloAddress SiloAddress => this.siloDetails.SiloAddress; /// /// Gets a which completes once the silo has terminated. /// public Task SiloTerminated { get { return this.siloTerminatedTask.Task; } } // one event for all types of termination (shutdown, stop and fast kill). /// /// Initializes a new instance of the class. /// /// The silo initialization parameters /// Dependency Injection container [Obsolete("This constructor is obsolete and may be removed in a future release. Use SiloHostBuilder to create an instance of ISiloHost instead.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Should not Dispose of messageCenter in this method because it continues to run / exist after this point.")] public Silo(ILocalSiloDetails siloDetails, IServiceProvider services) { SystemStatus = SystemStatus.Creating; Services = services; RingProvider = services.GetRequiredService(); platformWatchdog = services.GetRequiredService(); this.siloDetails = siloDetails; IOptions clusterMembershipOptions = services.GetRequiredService>(); initTimeout = clusterMembershipOptions.Value.MaxJoinAttemptTime; if (Debugger.IsAttached) { initTimeout = StandardExtensions.Max(TimeSpan.FromMinutes(10), clusterMembershipOptions.Value.MaxJoinAttemptTime); } var localEndpoint = this.siloDetails.SiloAddress.Endpoint; //set PropagateActivityId flag from node config IOptions messagingOptions = services.GetRequiredService>(); this.waitForMessageToBeQueuedForOutbound = messagingOptions.Value.WaitForMessageToBeQueuedForOutboundTime; this.loggerFactory = this.Services.GetRequiredService(); logger = this.loggerFactory.CreateLogger(); LogSiloStartingWithGC(logger, GCSettings.IsServerGC, GCSettings.LatencyMode); if (!GCSettings.IsServerGC) { LogWarningSiloGcNotRunningWithServerGC(logger); LogWarningSiloGcMultiCoreSystem(logger); } if (logger.IsEnabled(LogLevel.Debug)) { var highestLogLevel = logger.IsEnabled(LogLevel.Trace) ? nameof(LogLevel.Trace) : nameof(LogLevel.Debug); LogWarningSiloGcVerboseLOggingConfigured(logger, highestLogLevel); } LogInfoSiloInitializing(logger, siloDetails.DnsHostName, Environment.MachineName, localEndpoint, siloDetails.SiloAddress.Generation); LogInfoSiloInitConfig(logger, siloDetails.Name); try { grainFactory = Services.GetRequiredService(); } catch (InvalidOperationException exc) { LogErrorSiloStartGrainFactoryNotRegistered(logger, exc); throw; } runtimeClient = Services.GetRequiredService(); // Initialize the message center messageCenter = Services.GetRequiredService(); messageCenter.SniffIncomingMessage = runtimeClient.SniffIncomingMessage; this.SystemStatus = SystemStatus.Created; this.siloLifecycle = this.Services.GetRequiredService(); // register all lifecycle participants IEnumerable> lifecycleParticipants = this.Services.GetServices>(); foreach (ILifecycleParticipant participant in lifecycleParticipants) { participant?.Participate(this.siloLifecycle); } // add self to lifecycle this.Participate(this.siloLifecycle); LogInfoSiloInitializingFinished(logger, SiloAddress, new(SiloAddress)); } /// /// Starts the silo. /// /// A cancellation token which can be used to cancel the operation. /// A representing the operation. public async Task StartAsync(CancellationToken cancellationToken) { try { await Task.Run(() => this.siloLifecycle.OnStart(cancellationToken), cancellationToken); } catch (Exception exc) { LogErrorSiloStart(logger, exc); throw; } } private Task OnRuntimeInitializeStart(CancellationToken ct) { lock (lockable) { if (!this.SystemStatus.Equals(SystemStatus.Created)) throw new InvalidOperationException(string.Format("Calling Silo.Start() on a silo which is not in the Created state. This silo is in the {0} state.", this.SystemStatus)); this.SystemStatus = SystemStatus.Starting; } LogInfoSiloStarting(logger); return Task.CompletedTask; } private void StartTaskWithPerfAnalysis(string taskName, Action task, Stopwatch stopWatch) { stopWatch.Restart(); task.Invoke(); stopWatch.Stop(); LogInfoSiloStartPerfMeasure(logger, taskName, stopWatch.ElapsedMilliseconds); } private async Task StartAsyncTaskWithPerfAnalysis(string taskName, Func task, Stopwatch stopWatch) { stopWatch.Restart(); await task.Invoke(); stopWatch.Stop(); LogInfoSiloStartPerfMeasure(logger, taskName, stopWatch.ElapsedMilliseconds); } private Task OnRuntimeServicesStart(CancellationToken ct) { return Task.CompletedTask; } private async Task OnRuntimeGrainServicesStart(CancellationToken ct) { var stopWatch = Stopwatch.StartNew(); // Load and init grain services before silo becomes active. await StartAsyncTaskWithPerfAnalysis("Init grain services", () => CreateGrainServices(), stopWatch); try { // Start background timer tick to watch for platform execution stalls, such as when GC kicks in this.platformWatchdog.Start(); } catch (Exception exc) { LogErrorStartingSiloGoingToFastKill(logger, exc, SiloAddress); throw; } LogDebugSiloStartComplete(logger, this.SystemStatus); } private Task OnBecomeActiveStart(CancellationToken ct) { this.SystemStatus = SystemStatus.Running; return Task.CompletedTask; } private async Task OnActiveStart(CancellationToken ct) { foreach (var grainService in grainServices) { await StartGrainService(grainService); } } private async Task CreateGrainServices() { var grainServices = this.Services.GetServices(); foreach (var grainService in grainServices) { await RegisterGrainService(grainService); } } private async Task RegisterGrainService(IGrainService service) { var grainService = (GrainService)service; var activationDirectory = this.Services.GetRequiredService(); activationDirectory.RecordNewTarget(grainService); grainServices.Add(grainService); try { await grainService.QueueTask(() => grainService.Init(Services)).WaitAsync(this.initTimeout); } catch (TimeoutException exception) { LogErrorGrainInitializationTimeout(logger, exception, initTimeout); throw; } LogInfoGrainServiceRegistered(logger, service.GetType().FullName); } private async Task StartGrainService(IGrainService service) { var grainService = (GrainService)service; try { await grainService.QueueTask(grainService.Start).WaitAsync(this.initTimeout); } catch (TimeoutException exception) { LogErrorGrainStartupTimeout(logger, exception, initTimeout); throw; } LogInfoGrainServiceStarted(logger, service.GetType().FullName); } /// /// Gracefully stop the run time system only, but not the application. /// Applications requests would be abruptly terminated, while the internal system state gracefully stopped and saved as much as possible. /// Grains are not deactivated. /// public void Stop() { } /// /// Gracefully stop the run time system only, but not the application. /// Applications requests would be abruptly terminated, while the internal system state gracefully stopped and saved as much as possible. /// /// /// A cancellation token which can be used to promptly terminate the silo. /// /// A representing the operation. public async Task StopAsync(CancellationToken cancellationToken) { bool gracefully = !cancellationToken.IsCancellationRequested; bool stopAlreadyInProgress = false; lock (lockable) { if (this.SystemStatus.Equals(SystemStatus.Stopping) || this.SystemStatus.Equals(SystemStatus.ShuttingDown) || this.SystemStatus.Equals(SystemStatus.Terminated)) { stopAlreadyInProgress = true; // Drop through to wait below } else if (!this.SystemStatus.Equals(SystemStatus.Running)) { throw new InvalidOperationException($"Attempted to shutdown a silo which is not in the {nameof(SystemStatus.Running)} state. This silo is in the {this.SystemStatus} state."); } else { if (gracefully) this.SystemStatus = SystemStatus.ShuttingDown; else this.SystemStatus = SystemStatus.Stopping; } } if (stopAlreadyInProgress) { LogDebugSiloStopInProgress(logger); var pause = TimeSpan.FromSeconds(1); while (!this.SystemStatus.Equals(SystemStatus.Terminated)) { LogDebugSiloStopStillInProgress(logger); await Task.Delay(pause).ConfigureAwait(false); } await this.SiloTerminated.ConfigureAwait(false); return; } if (gracefully) { LogSiloShuttingDown(logger, LogLevel.Debug, "graceful"); } else { LogSiloShuttingDown(logger, LogLevel.Debug, "non-graceful"); } try { await Task.Run(() => this.siloLifecycle.OnStop(cancellationToken), CancellationToken.None).ConfigureAwait(false); } finally { // log final status if (gracefully) { LogSiloShutDown(logger, LogLevel.Debug, "graceful"); } else { LogSiloShutDown(logger, LogLevel.Warning, "non-graceful"); } // signal to all awaiters that the silo has terminated. await Task.Run(() => this.siloTerminatedTask.TrySetResult(0)).ConfigureAwait(false); } } private Task OnRuntimeServicesStop(CancellationToken ct) { // Start rejecting all silo to silo application messages messageCenter.BlockApplicationMessages(); return Task.CompletedTask; } private async Task OnRuntimeInitializeStop(CancellationToken ct) { try { await messageCenter.StopAsync(); } catch (Exception exception) { LogErrorStoppingMessageCenter(logger, exception); } SystemStatus = SystemStatus.Terminated; } private async Task OnBecomeActiveStop(CancellationToken ct) { try { try { var catalog = this.Services.GetRequiredService(); await catalog.DeactivateAllActivations(ct); } catch (Exception exception) { if (!ct.IsCancellationRequested) { LogErrorDeactivatingActivations(logger, exception); } else { LogWarningSomeGrainsFailedToDeactivate(logger); } } // Wait for all queued message sent to OutboundMessageQueue before MessageCenter stop and OutboundMessageQueue stop. await Task.Delay(waitForMessageToBeQueuedForOutbound, ct).SuppressThrowing(); } catch (Exception exc) { LogErrorSiloFailedToStopMembership(logger, exc); } // Stop the gateway await messageCenter.StopAcceptingClientMessages(); } private async Task OnActiveStop(CancellationToken ct) { if (ct.IsCancellationRequested) return; if (this.messageCenter.Gateway != null) { try { await Task.Run(() => this.messageCenter.Gateway.SendStopSendMessages(this.grainFactory, ct), CancellationToken.None).WaitAsync(ct); } catch (Exception exception) { LogErrorSendingDisconnectRequests(logger, exception); if (!ct.IsCancellationRequested) { throw; } } } foreach (var grainService in grainServices) { try { await grainService .QueueTask(grainService.Stop) .WaitAsync(ct); } catch (Exception exception) { LogErrorStoppingGrainService(logger, grainService, exception); if (!ct.IsCancellationRequested) { throw; } } LogDebugGrainServiceStopped(logger, grainService.GetType().FullName, grainService.GetGrainId()); } } /// public override string ToString() => $"Silo: {SiloAddress}"; private void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.RuntimeInitialize, (ct) => Task.Run(() => OnRuntimeInitializeStart(ct)), (ct) => Task.Run(() => OnRuntimeInitializeStop(ct))); lifecycle.Subscribe(ServiceLifecycleStage.RuntimeServices, (ct) => Task.Run(() => OnRuntimeServicesStart(ct)), (ct) => Task.Run(() => OnRuntimeServicesStop(ct))); lifecycle.Subscribe(ServiceLifecycleStage.RuntimeGrainServices, (ct) => Task.Run(() => OnRuntimeGrainServicesStart(ct))); lifecycle.Subscribe(ServiceLifecycleStage.BecomeActive, (ct) => Task.Run(() => OnBecomeActiveStart(ct)), (ct) => Task.Run(() => OnBecomeActiveStop(ct))); lifecycle.Subscribe(ServiceLifecycleStage.Active, (ct) => Task.Run(() => OnActiveStart(ct)), (ct) => Task.Run(() => OnActiveStop(ct))); } public async ValueTask DisposeAsync() { using var cts = new CancellationTokenSource(); cts.Cancel(); await StopAsync(cts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } public void Dispose() { try { using var cts = new CancellationTokenSource(); cts.Cancel(); StopAsync(cts.Token).Wait(); } catch { } } [LoggerMessage( Level = LogLevel.Information, Message = "Silo starting with GC settings: ServerGC={ServerGC} GCLatencyMode={GCLatencyMode}" )] private static partial void LogSiloStartingWithGC(ILogger logger, bool serverGC, GCLatencyMode gcLatencyMode); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.SiloGcWarning, Message = "Note: Silo not running with ServerGC turned on - recommend checking app config : --" )] private static partial void LogWarningSiloGcNotRunningWithServerGC(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.SiloGcWarning, Message = "Note: ServerGC only kicks in on multi-core systems (settings enabling ServerGC have no effect on single-core machines)." )] private static partial void LogWarningSiloGcMultiCoreSystem(ILogger logger); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.SiloGcWarning, Message = $"A verbose logging level ({{HighestLogLevel}}) is configured. This will impact performance. The recommended log level is {nameof(LogLevel.Information)}." )] private static partial void LogWarningSiloGcVerboseLOggingConfigured(ILogger logger, string highestLogLevel); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.SiloInitializing, Message = "-------------- Initializing silo on host {HostName} MachineName {MachineName} at {LocalEndpoint}, gen {Generation} --------------" )] private static partial void LogInfoSiloInitializing(ILogger logger, string hostName, string machineName, IPEndPoint localEndpoint, int generation); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.SiloInitConfig, Message = "Starting silo {SiloName}" )] private static partial void LogInfoSiloInitConfig(ILogger logger, string siloName); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.SiloStartError, Message = "Exception during Silo.Start, GrainFactory was not registered in Dependency Injection container" )] private static partial void LogErrorSiloStartGrainFactoryNotRegistered(ILogger logger, Exception exc); private readonly struct SiloAddressConsistentHashCodeLogValue(SiloAddress siloAddress) { public override string ToString() => siloAddress.GetConsistentHashCode().ToString("X"); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.SiloInitializingFinished, Message = "-------------- Started silo {SiloAddress}, ConsistentHashCode {HashCode} --------------" )] private static partial void LogInfoSiloInitializingFinished(ILogger logger, SiloAddress siloAddress, SiloAddressConsistentHashCodeLogValue hashCode); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.SiloStartError, Message = "Exception during Silo.Start" )] private static partial void LogErrorSiloStart(ILogger logger, Exception exc); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.SiloStarting, Message = "Silo Start()" )] private static partial void LogInfoSiloStarting(ILogger logger); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.SiloStartPerfMeasure, Message = "{TaskName} took {ElapsedMilliseconds} milliseconds to finish" )] private static partial void LogInfoSiloStartPerfMeasure(ILogger logger, string taskName, long elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Runtime_Error_100330, Message = "Error starting silo {SiloAddress}. Going to FastKill()." )] private static partial void LogErrorStartingSiloGoingToFastKill(ILogger logger, Exception exc, SiloAddress siloAddress); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo.Start complete: System status = {SystemStatus}" )] private static partial void LogDebugSiloStartComplete(ILogger logger, SystemStatus systemStatus); [LoggerMessage( Level = LogLevel.Error, Message = "GrainService initialization timed out after '{Timeout}'." )] private static partial void LogErrorGrainInitializationTimeout(ILogger logger, Exception exception, TimeSpan timeout); [LoggerMessage( Level = LogLevel.Information, Message = "Grain Service {GrainServiceType} registered successfully." )] private static partial void LogInfoGrainServiceRegistered(ILogger logger, string grainServiceType); [LoggerMessage( Level = LogLevel.Error, Message = "GrainService startup timed out after '{Timeout}'." )] private static partial void LogErrorGrainStartupTimeout(ILogger logger, Exception exception, TimeSpan timeout); [LoggerMessage( Level = LogLevel.Information, Message = "Grain Service {GrainServiceType} started successfully." )] private static partial void LogInfoGrainServiceStarted(ILogger logger, string grainServiceType); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.SiloStopInProgress, Message = "Silo shutdown in progress. Waiting for shutdown to be completed." )] private static partial void LogDebugSiloStopInProgress(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.SiloStopInProgress, Message = "Silo shutdown still in progress." )] private static partial void LogDebugSiloStopStillInProgress(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.SiloShuttingDown, Message = "Silo shutdown initiated ({Gracefully})." )] private static partial void LogSiloShuttingDown(ILogger logger, LogLevel logLevel, string gracefully); [LoggerMessage( EventId = (int)ErrorCode.SiloShutDown, Message = "Silo shutdown completed ({Gracefully})." )] private static partial void LogSiloShutDown(ILogger logger, LogLevel logLevel, string gracefully); [LoggerMessage( Level = LogLevel.Error, Message = "Error stopping message center." )] private static partial void LogErrorStoppingMessageCenter(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error deactivating activations." )] private static partial void LogErrorDeactivatingActivations(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Some grains failed to deactivate promptly." )] private static partial void LogWarningSomeGrainsFailedToDeactivate(ILogger logger); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to stop gracefully. About to terminate ungracefully." )] private static partial void LogErrorSiloFailedToStopMembership(ILogger logger, Exception exc); [LoggerMessage( Level = LogLevel.Error, Message = "Error sending disconnect requests to connected clients." )] private static partial void LogErrorSendingDisconnectRequests(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Stopping GrainService '{GrainService}' failed." )] private static partial void LogErrorStoppingGrainService(ILogger logger, GrainService grainService, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "{GrainServiceType} Grain Service with Id {GrainServiceId} stopped successfully." )] private static partial void LogDebugGrainServiceStopped(ILogger logger, string grainServiceType, GrainId grainServiceId); } } ================================================ FILE: src/Orleans.Runtime/Silo/SiloControl.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.Metadata; using Orleans.Placement; using Orleans.Providers; using Orleans.Runtime.GrainDirectory; using Orleans.Runtime.Placement; using Orleans.Runtime.Versions; using Orleans.Runtime.Versions.Compatibility; using Orleans.Runtime.Versions.Selector; using Orleans.Serialization.TypeSystem; using Orleans.Statistics; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime { internal sealed partial class SiloControl : SystemTarget, ISiloControl, ILifecycleParticipant { private readonly ILogger logger; private readonly ILocalSiloDetails localSiloDetails; private readonly DeploymentLoadPublisher deploymentLoadPublisher; private readonly CachedVersionSelectorManager cachedVersionSelectorManager; private readonly CompatibilityDirectorManager compatibilityDirectorManager; private readonly VersionSelectorManager selectorManager; private readonly IServiceProvider services; private readonly ActivationCollector _activationCollector; private readonly ActivationDirectory activationDirectory; private readonly IActivationWorkingSet activationWorkingSet; private readonly IEnvironmentStatisticsProvider environmentStatisticsProvider; private readonly IOptions loadSheddingOptions; private readonly GrainCountStatistics _grainCountStatistics; private readonly GrainPropertiesResolver grainPropertiesResolver; private readonly GrainMigratabilityChecker _migratabilityChecker; public SiloControl( ILocalSiloDetails localSiloDetails, DeploymentLoadPublisher deploymentLoadPublisher, CachedVersionSelectorManager cachedVersionSelectorManager, CompatibilityDirectorManager compatibilityDirectorManager, VersionSelectorManager selectorManager, IServiceProvider services, ILoggerFactory loggerFactory, IMessageCenter messageCenter, ActivationCollector activationCollector, ActivationDirectory activationDirectory, IActivationWorkingSet activationWorkingSet, IEnvironmentStatisticsProvider environmentStatisticsProvider, IOptions loadSheddingOptions, GrainCountStatistics grainCountStatistics, GrainPropertiesResolver grainPropertiesResolver, GrainMigratabilityChecker migratabilityChecker, SystemTargetShared shared) : base(Constants.SiloControlType, shared) { this.localSiloDetails = localSiloDetails; this.logger = loggerFactory.CreateLogger(); this.deploymentLoadPublisher = deploymentLoadPublisher; this.cachedVersionSelectorManager = cachedVersionSelectorManager; this.compatibilityDirectorManager = compatibilityDirectorManager; this.selectorManager = selectorManager; this.services = services; _activationCollector = activationCollector; this.activationDirectory = activationDirectory; this.activationWorkingSet = activationWorkingSet; this.environmentStatisticsProvider = environmentStatisticsProvider; this.loadSheddingOptions = loadSheddingOptions; _grainCountStatistics = grainCountStatistics; this.grainPropertiesResolver = grainPropertiesResolver; _migratabilityChecker = migratabilityChecker; shared.ActivationDirectory.RecordNewTarget(this); } public Task Ping(string message) { LogInformationPing(); return Task.CompletedTask; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")] public Task ForceGarbageCollection() { LogInformationForceGarbageCollection(); GC.Collect(); return Task.CompletedTask; } public Task ForceActivationCollection(TimeSpan ageLimit) { LogInformationForceActivationCollection(); return _activationCollector.CollectActivations(ageLimit, CancellationToken.None); } public Task ForceRuntimeStatisticsCollection() { LogDebugForceRuntimeStatisticsCollection(); return this.deploymentLoadPublisher.RefreshClusterStatistics(); } public Task GetRuntimeStatistics() { LogDebugGetRuntimeStatistics(); var activationCount = this.activationDirectory.Count; var stats = new SiloRuntimeStatistics( activationCount, activationWorkingSet.Count, this.environmentStatisticsProvider, this.loadSheddingOptions, DateTime.UtcNow); return Task.FromResult(stats); } public Task>> GetGrainStatistics() { LogInformationGetGrainStatistics(); var counts = new Dictionary>(); lock (activationDirectory) { foreach (var activation in activationDirectory) { var data = activation.Value; if (data == null || data.GrainInstance == null) continue; // TODO: generic type expansion var grainTypeName = RuntimeTypeNameFormatter.Format(data.GrainInstance.GetType()); Dictionary? grains; int n; if (!counts.TryGetValue(grainTypeName, out grains)) { counts.Add(grainTypeName, new Dictionary { { data.GrainId, 1 } }); } else if (!grains.TryGetValue(data.GrainId, out n)) grains[data.GrainId] = 1; else grains[data.GrainId] = n + 1; } } return Task.FromResult(counts .SelectMany(p => p.Value.Select(p2 => Tuple.Create(p2.Key, p.Key, p2.Value))) .ToList()); } public Task> GetDetailedGrainStatistics(string[]? types = null) { var stats = GetDetailedGrainStatisticsCore(types); return Task.FromResult(stats); } public Task GetSimpleGrainStatistics() { return Task.FromResult(_grainCountStatistics.GetSimpleGrainStatistics().Select(p => new SimpleGrainStatistic { SiloAddress = this.localSiloDetails.SiloAddress, GrainType = p.Key, ActivationCount = (int)p.Value }).ToArray()); } public async Task GetDetailedGrainReport(GrainId grainId) { string? grainClassName; try { var properties = this.grainPropertiesResolver.GetGrainProperties(grainId.Type); properties.Properties.TryGetValue(WellKnownGrainTypeProperties.TypeName, out grainClassName); } catch (Exception exc) { grainClassName = exc.ToString(); } var activation = activationDirectory.FindTarget(grainId) switch { ActivationData data => data.ToDetailedString(), var a => a?.ToString() }; var resolver = services.GetRequiredService(); var defaultDirectory = services.GetService(); var dir = resolver.Resolve(grainId.Type) ?? defaultDirectory; GrainAddress? localCacheActivationAddress = null; GrainAddress? localDirectoryActivationAddress = null; SiloAddress? primaryForGrain = null; if (dir is DistributedGrainDirectory distributedGrainDirectory) { var grainLocator = services.GetRequiredService(); grainLocator.TryLookupInCache(grainId, out localCacheActivationAddress); localDirectoryActivationAddress = await ((DistributedGrainDirectory.ITestHooks)distributedGrainDirectory).GetLocalRecord(grainId); primaryForGrain = ((DistributedGrainDirectory.ITestHooks)distributedGrainDirectory).GetPrimaryForGrain(grainId); } else if (dir is null && services.GetService() is { } localGrainDirectory) { localCacheActivationAddress = localGrainDirectory.GetLocalCacheData(grainId); localDirectoryActivationAddress = localGrainDirectory.GetLocalDirectoryData(grainId).Address; primaryForGrain = localGrainDirectory.GetPrimaryForGrain(grainId); } var report = new DetailedGrainReport() { Grain = grainId, SiloAddress = localSiloDetails.SiloAddress, SiloName = localSiloDetails.Name, LocalCacheActivationAddress = localCacheActivationAddress, LocalDirectoryActivationAddress = localDirectoryActivationAddress, PrimaryForGrain = primaryForGrain, GrainClassTypeName = grainClassName, LocalActivation = activation, }; return report; } public Task GetActivationCount() { return Task.FromResult(this.activationDirectory.Count); } public Task SendControlCommandToProvider(string providerName, int command, object arg) where T : IControllable { var t = services .GetKeyedServices(providerName); var controllable = services .GetKeyedServices(providerName) .FirstOrDefault(svc => svc.GetType() == typeof(T)); if (controllable == null) { LogErrorProviderNotFound(typeof(IControllable).FullName!, providerName); throw new ArgumentException($"Could not find a controllable service for type {typeof(IControllable).FullName} and name {providerName}."); } return controllable.ExecuteCommand(command, arg); } public Task SetCompatibilityStrategy(CompatibilityStrategy strategy) { this.compatibilityDirectorManager.SetStrategy(strategy); this.cachedVersionSelectorManager.ResetCache(); return Task.CompletedTask; } public Task SetSelectorStrategy(VersionSelectorStrategy strategy) { this.selectorManager.SetSelector(strategy); this.cachedVersionSelectorManager.ResetCache(); return Task.CompletedTask; } public Task SetCompatibilityStrategy(GrainInterfaceType interfaceId, CompatibilityStrategy strategy) { this.compatibilityDirectorManager.SetStrategy(interfaceId, strategy); this.cachedVersionSelectorManager.ResetCache(); return Task.CompletedTask; } public Task SetSelectorStrategy(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy) { this.selectorManager.SetSelector(interfaceType, strategy); this.cachedVersionSelectorManager.ResetCache(); return Task.CompletedTask; } public Task> GetActiveGrains(GrainType grainType) { var results = new List(); foreach (var pair in activationDirectory) { if (grainType.Equals(pair.Key.Type)) { results.Add(pair.Key); } } return Task.FromResult(results); } public Task MigrateRandomActivations(SiloAddress target, int count) { ArgumentNullException.ThrowIfNull(target); ArgumentOutOfRangeException.ThrowIfNegative(count); var migrationContext = new Dictionary() { [IPlacementDirector.PlacementHintKey] = target }; // Loop until we've migrated the desired count of activations or run out of activations to try. // Note that we have a weak pseudorandom enumeration here, and lossy counting: this is not a precise // or deterministic operation. var remainingCount = count; foreach (var (grainId, grainContext) in activationDirectory) { if (!_migratabilityChecker.IsMigratable(grainId.Type, ImmovableKind.Rebalancer)) { continue; } if (--remainingCount <= 0) { break; } grainContext.Migrate(migrationContext); } return Task.CompletedTask; } private List GetDetailedGrainStatisticsCore(string[]? types = null) { var stats = new List(); lock (activationDirectory) { foreach (var activation in activationDirectory) { var data = activation.Value; if (data == null || data.GrainInstance == null) continue; var grainType = RuntimeTypeNameFormatter.Format(data.GrainInstance.GetType()); if (types == null || types.Contains(grainType)) { stats.Add(new DetailedGrainStatistic() { GrainType = grainType, GrainId = data.GrainId, SiloAddress = Silo }); } } } return stats; } void ILifecycleParticipant.Participate(ISiloLifecycle lifecycle) { // Do nothing, just ensure that this instance is created so that it can register itself in the activation directory. } [LoggerMessage( Level = LogLevel.Information, Message = "Ping" )] private partial void LogInformationPing(); [LoggerMessage( Level = LogLevel.Information, Message = "ForceGarbageCollection" )] private partial void LogInformationForceGarbageCollection(); [LoggerMessage( Level = LogLevel.Information, Message = "ForceActivationCollection" )] private partial void LogInformationForceActivationCollection(); [LoggerMessage( Level = LogLevel.Debug, Message = "ForceRuntimeStatisticsCollection" )] private partial void LogDebugForceRuntimeStatisticsCollection(); [LoggerMessage( Level = LogLevel.Debug, Message = "GetRuntimeStatistics" )] private partial void LogDebugGetRuntimeStatistics(); [LoggerMessage( Level = LogLevel.Information, Message = "GetGrainStatistics" )] private partial void LogInformationGetGrainStatistics(); [LoggerMessage( EventId = (int)ErrorCode.Provider_ProviderNotFound, Level = LogLevel.Error, Message = "Could not find a controllable service for type {ProviderTypeFullName} and name {ProviderName}." )] private partial void LogErrorProviderNotFound(string providerTypeFullName, string providerName); } } ================================================ FILE: src/Orleans.Runtime/Silo/SiloOptions.cs ================================================ namespace Orleans.Configuration { /// /// Silo configuration options. /// public class SiloOptions { /// /// Gets or sets the silo name. /// public string SiloName { get; set; } } } ================================================ FILE: src/Orleans.Runtime/Silo/SiloProviderRuntime.cs ================================================ using System; using Orleans.Providers; namespace Orleans.Runtime.Providers { internal class SiloProviderRuntime : IProviderRuntime { private readonly IGrainContextAccessor _grainContextAccessor; public SiloProviderRuntime( IGrainContextAccessor grainContextAccessor, IGrainFactory grainFactory, IServiceProvider serviceProvider) { _grainContextAccessor = grainContextAccessor; GrainFactory = grainFactory; ServiceProvider = serviceProvider; } public IGrainFactory GrainFactory { get; } public IServiceProvider ServiceProvider { get; } public (TExtension, TExtensionInterface) BindExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { return _grainContextAccessor.GrainContext.GetComponent().GetOrSetExtension(newExtensionFunc); } } } ================================================ FILE: src/Orleans.Runtime/Silo/TestHooks/ITestHooksSystemTarget.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Runtime.TestHooks { internal interface ITestHooks { Task GetConsistentRingPrimaryTargetSilo(uint key); Task GetConsistentRingProviderDiagnosticInfo(); Task GetServiceId(); Task HasStorageProvider(string providerName); Task HasStreamProvider(string providerName); Task UnregisterGrainForTesting(GrainId grain); Task> GetApproximateSiloStatuses(); } internal interface ITestHooksSystemTarget : ITestHooks, ISystemTarget { } } ================================================ FILE: src/Orleans.Runtime/Silo/TestHooks/TestHooksSystemTarget.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime.ConsistentRing; using Orleans.Storage; using Orleans.Statistics; using Orleans.Runtime.Messaging; namespace Orleans.Runtime.TestHooks { /// /// A fake, test-only implementation of . /// internal class TestHooksEnvironmentStatisticsProvider : IEnvironmentStatisticsProvider { private static EnvironmentStatisticsProvider _realStatisticsProvider = new(); private EnvironmentStatistics? _currentStats = null; public EnvironmentStatistics GetEnvironmentStatistics() { var stats = _currentStats ?? new(); if (!stats.IsValid()) { stats = _realStatisticsProvider.GetEnvironmentStatistics(); } return stats; } public void LatchHardwareStatistics(EnvironmentStatistics stats) => _currentStats = stats; public void UnlatchHardwareStatistics() => _currentStats = null; } /// /// Test hook functions for white box testing implemented as a SystemTarget /// internal sealed class TestHooksSystemTarget : SystemTarget, ITestHooksSystemTarget { private readonly IServiceProvider serviceProvider; private readonly ISiloStatusOracle siloStatusOracle; private readonly IConsistentRingProvider consistentRingProvider; public TestHooksSystemTarget( IServiceProvider serviceProvider, ISiloStatusOracle siloStatusOracle, SystemTargetShared shared) : base(Constants.TestHooksSystemTargetType, shared) { this.serviceProvider = serviceProvider; this.siloStatusOracle = siloStatusOracle; this.consistentRingProvider = this.serviceProvider.GetRequiredService(); shared.ActivationDirectory.RecordNewTarget(this); } public void Initialize() { // No-op } public Task GetConsistentRingPrimaryTargetSilo(uint key) { return Task.FromResult(consistentRingProvider.GetPrimaryTargetSilo(key)); } public Task GetConsistentRingProviderDiagnosticInfo() { return Task.FromResult(consistentRingProvider.ToString()); } public Task GetServiceId() => Task.FromResult(this.serviceProvider.GetRequiredService>().Value.ServiceId); public Task HasStorageProvider(string providerName) { return Task.FromResult(this.serviceProvider.GetKeyedService(providerName) != null); } public Task HasStreamProvider(string providerName) { return Task.FromResult(this.serviceProvider.GetKeyedService(providerName) != null); } public Task UnregisterGrainForTesting(GrainId grain) => Task.FromResult(this.serviceProvider.GetRequiredService().UnregisterGrainForTesting(grain)); public Task> GetApproximateSiloStatuses() => Task.FromResult(this.siloStatusOracle.GetApproximateSiloStatuses()); } } ================================================ FILE: src/Orleans.Runtime/Silo/Watchdog.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Runtime { /// /// Monitors runtime and component health periodically, reporting complaints. /// internal partial class Watchdog(IOptions clusterMembershipOptions, IEnumerable participants, ILogger logger) : IDisposable { private static readonly TimeSpan PlatformWatchdogHeartbeatPeriod = TimeSpan.FromMilliseconds(1000); private readonly CancellationTokenSource _cancellation = new(); private readonly TimeSpan _componentHealthCheckPeriod = clusterMembershipOptions.Value.LocalHealthDegradationMonitoringPeriod; private readonly List _participants = participants.ToList(); private readonly ILogger _logger = logger; private ValueStopwatch _platformWatchdogStopwatch; private ValueStopwatch _componentWatchdogStopwatch; // GC pause duration since process start. private TimeSpan _cumulativeGCPauseDuration; private DateTime _lastComponentHealthCheckTime; private Thread? _platformWatchdogThread; private Thread? _componentWatchdogThread; public void Start() { LogDebugStartingSiloWatchdog(_logger); if (_platformWatchdogThread is not null) { throw new InvalidOperationException("Watchdog.Start may not be called more than once"); } var now = DateTime.UtcNow; _platformWatchdogStopwatch = ValueStopwatch.StartNew(); _cumulativeGCPauseDuration = GC.GetTotalPauseDuration(); _platformWatchdogThread = new Thread(RunPlatformWatchdog) { IsBackground = true, Name = "Orleans.Runtime.Watchdog.Platform", }; _platformWatchdogThread.Start(); _componentWatchdogStopwatch = ValueStopwatch.StartNew(); _lastComponentHealthCheckTime = DateTime.UtcNow; _componentWatchdogThread = new Thread(RunComponentWatchdog) { IsBackground = true, Name = "Orleans.Runtime.Watchdog.Component", }; _componentWatchdogThread.Start(); LogDebugSiloWatchdogStartedSuccessfully(_logger); } public void Stop() { Dispose(); } protected void RunPlatformWatchdog() { while (!_cancellation.IsCancellationRequested) { try { CheckRuntimeHealth(); } catch (Exception exc) { LogErrorPlatformWatchdogInternalError(_logger, exc); } _platformWatchdogStopwatch.Restart(); _cumulativeGCPauseDuration = GC.GetTotalPauseDuration(); _cancellation.Token.WaitHandle.WaitOne(PlatformWatchdogHeartbeatPeriod); } } private void CheckRuntimeHealth() { if (Debugger.IsAttached) { return; } var pauseDurationSinceLastTick = GC.GetTotalPauseDuration() - _cumulativeGCPauseDuration; var timeSinceLastTick = _platformWatchdogStopwatch.Elapsed; if (timeSinceLastTick > PlatformWatchdogHeartbeatPeriod.Multiply(2)) { var gc = new[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; LogWarningSiloHeartbeatTimerStalled(_logger, timeSinceLastTick, pauseDurationSinceLastTick, GC.GetTotalMemory(false) / (1024 * 1024), gc[0], gc[1], gc[2]); } var timeSinceLastParticipantCheck = _componentWatchdogStopwatch.Elapsed; if (timeSinceLastParticipantCheck > _componentHealthCheckPeriod.Multiply(2)) { LogWarningParticipantCheckThreadStalled(_logger, timeSinceLastParticipantCheck); } } protected void RunComponentWatchdog() { while (!_cancellation.IsCancellationRequested) { try { CheckComponentHealth(); } catch (Exception exc) { LogErrorComponentHealthCheckInternalError(_logger, exc); } _componentWatchdogStopwatch.Restart(); _cancellation.Token.WaitHandle.WaitOne(_componentHealthCheckPeriod); } } private void CheckComponentHealth() { WatchdogInstruments.HealthChecks.Add(1); var numFailedChecks = 0; StringBuilder? complaints = null; // Restart the timer before the check to reduce false positives for the stall checker. _componentWatchdogStopwatch.Restart(); foreach (var participant in _participants) { try { var ok = participant.CheckHealth(_lastComponentHealthCheckTime, out var complaint); if (!ok) { complaints ??= new StringBuilder(); if (complaints.Length > 0) { complaints.Append(' '); } complaints.Append($"{participant.GetType()} failed health check with complaint \"{complaint}\"."); ++numFailedChecks; } } catch (Exception exc) { LogWarningHealthCheckParticipantException(_logger, exc, participant.GetType()); } } if (complaints != null) { WatchdogInstruments.FailedHealthChecks.Add(1); LogWarningHealthCheckFailure(_logger, numFailedChecks, _participants.Count, complaints); } _lastComponentHealthCheckTime = DateTime.UtcNow; } public void Dispose() { try { _cancellation.Cancel(); } catch { // Ignore. } try { _componentWatchdogThread?.Join(); } catch { // Ignore. } try { _platformWatchdogThread?.Join(); } catch { // Ignore. } } private readonly struct ParticipantTypeLogValue(Type participantType) { public override string ToString() => participantType.ToString(); } [LoggerMessage( Level = LogLevel.Debug, Message = "Starting silo watchdog" )] private static partial void LogDebugStartingSiloWatchdog(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "Silo watchdog started successfully." )] private static partial void LogDebugSiloWatchdogStartedSuccessfully(ILogger logger); [LoggerMessage( EventId = (int)ErrorCode.Watchdog_InternalError, Level = LogLevel.Error, Message = "Platform watchdog encountered an internal error" )] private static partial void LogErrorPlatformWatchdogInternalError(ILogger logger, Exception exc); [LoggerMessage( EventId = (int)ErrorCode.SiloHeartbeatTimerStalled, Level = LogLevel.Warning, Message = ".NET Runtime Platform stalled for {TimeSinceLastTick}. Total GC Pause duration during that period: {PauseDurationSinceLastTick}. We are now using a total of {TotalMemory}MB memory. Collection counts per generation: 0: {GCGen0Count}, 1: {GCGen1Count}, 2: {GCGen2Count}" )] private static partial void LogWarningSiloHeartbeatTimerStalled(ILogger logger, TimeSpan timeSinceLastTick, TimeSpan pauseDurationSinceLastTick, long totalMemory, int gcGen0Count, int gcGen1Count, int gcGen2Count); [LoggerMessage( EventId = (int)ErrorCode.SiloHeartbeatTimerStalled, Level = LogLevel.Warning, Message = "Participant check thread has not completed for {TimeSinceLastTick}, potentially indicating lock contention or deadlock, CPU starvation, or another execution anomaly." )] private static partial void LogWarningParticipantCheckThreadStalled(ILogger logger, TimeSpan timeSinceLastTick); [LoggerMessage( EventId = (int)ErrorCode.Watchdog_InternalError, Level = LogLevel.Error, Message = "Component health check encountered an internal error" )] private static partial void LogErrorComponentHealthCheckInternalError(ILogger logger, Exception exc); [LoggerMessage( EventId = (int)ErrorCode.Watchdog_ParticipantThrownException, Level = LogLevel.Warning, Message = "Health check participant {Participant} has thrown an exception from its CheckHealth method." )] private static partial void LogWarningHealthCheckParticipantException(ILogger logger, Exception exc, Type participant); [LoggerMessage( EventId = (int)ErrorCode.Watchdog_HealthCheckFailure, Level = LogLevel.Warning, Message = "{FailedChecks} of {ParticipantCount} components reported issues. Complaints: {Complaints}" )] private static partial void LogWarningHealthCheckFailure(ILogger logger, int failedChecks, int participantCount, StringBuilder complaints); } } ================================================ FILE: src/Orleans.Runtime/Storage/StateStorageBridge.cs ================================================ #nullable enable using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Diagnostics; using Orleans.Serialization.Activators; using Orleans.Serialization.Serializers; using Orleans.Storage; namespace Orleans.Core { /// /// Provides functionality for operating on grain state. /// Implements the /// /// The underlying state type. /// public partial class StateStorageBridge : IStorage, IGrainMigrationParticipant { private readonly IGrainContext _grainContext; private readonly StateStorageBridgeShared _shared; private GrainState? _grainState; /// public TState State { get { GrainRuntime.CheckRuntimeContext(RuntimeContext.Current); if (_grainState is { } grainState) { return grainState.State; } return default!; } set { GrainRuntime.CheckRuntimeContext(RuntimeContext.Current); GrainState.State = value; } } private GrainState GrainState => _grainState ??= new GrainState(_shared.Activator.Create()); internal bool IsStateInitialized { get; private set; } internal string Name => _shared.Name; /// public string? Etag { get => _grainState?.ETag; set => GrainState.ETag = value; } /// public bool RecordExists => IsStateInitialized switch { true => GrainState.RecordExists, _ => throw new InvalidOperationException("State has not yet been loaded") }; [Obsolete("Use StateStorageBridge(string, IGrainContext, IGrainStorage) instead.")] public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage store, ILoggerFactory loggerFactory, IActivatorProvider activatorProvider) : this(name, grainContext, store) { } public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage store) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(grainContext); ArgumentNullException.ThrowIfNull(store); _grainContext = grainContext; var sharedInstances = ActivatorUtilities.GetServiceOrCreateInstance(grainContext.ActivationServices); _shared = sharedInstances.Get(name, store); } /// public async Task ReadStateAsync() { try { GrainRuntime.CheckRuntimeContext(RuntimeContext.Current); // Try to get the parent activity context from the current activity or from the activation's stored activity var parentContext = Activity.Current?.Context; if (parentContext is null && _grainContext is ActivationData activationData) { // If we're in activation context and there's an activation activity, use it as parent parentContext = activationData.GetActivationActivityContext(); } using var activity = parentContext.HasValue ? ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageRead, ActivityKind.Client, parentContext.Value) : ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageRead, ActivityKind.Client); activity?.SetTag(ActivityTagKeys.GrainId, _grainContext.GrainId.ToString()); activity?.SetTag(ActivityTagKeys.StorageProvider, _shared.ProviderTypeName); activity?.SetTag(ActivityTagKeys.StorageStateName, _shared.Name); activity?.SetTag(ActivityTagKeys.StorageStateType, _shared.StateTypeName); var sw = ValueStopwatch.StartNew(); await _shared.Store.ReadStateAsync(_shared.Name, _grainContext.GrainId, GrainState); IsStateInitialized = true; StorageInstruments.OnStorageRead(sw.Elapsed, _shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); } catch (Exception exc) { StorageInstruments.OnStorageReadError(_shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); OnError(exc, ErrorCode.StorageProvider_ReadFailed, nameof(ReadStateAsync)); } } /// public async Task WriteStateAsync() { try { GrainRuntime.CheckRuntimeContext(RuntimeContext.Current); // Try to get the parent activity context from the current activity or from the activation's stored activity var parentContext = Activity.Current?.Context; if (parentContext is null && _grainContext is ActivationData activationData) { parentContext = activationData.GetActivationActivityContext(); } using var activity = parentContext.HasValue ? ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageWrite, ActivityKind.Client, parentContext.Value) : ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageWrite, ActivityKind.Client); activity?.SetTag(ActivityTagKeys.GrainId, _grainContext.GrainId.ToString()); activity?.SetTag(ActivityTagKeys.StorageProvider, _shared.ProviderTypeName); activity?.SetTag(ActivityTagKeys.StorageStateName, _shared.Name); activity?.SetTag(ActivityTagKeys.StorageStateType, _shared.StateTypeName); var sw = ValueStopwatch.StartNew(); await _shared.Store.WriteStateAsync(_shared.Name, _grainContext.GrainId, GrainState); StorageInstruments.OnStorageWrite(sw.Elapsed, _shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); } catch (Exception exc) { StorageInstruments.OnStorageWriteError(_shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); OnError(exc, ErrorCode.StorageProvider_WriteFailed, nameof(WriteStateAsync)); } } /// public async Task ClearStateAsync() { try { GrainRuntime.CheckRuntimeContext(RuntimeContext.Current); // Try to get the parent activity context from the current activity or from the activation's stored activity var parentContext = Activity.Current?.Context; if (parentContext is null && _grainContext is ActivationData activationData) { parentContext = activationData.GetActivationActivityContext(); } using var activity = parentContext.HasValue ? ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageClear, ActivityKind.Client, parentContext.Value) : ActivitySources.StorageGrainSource.StartActivity(ActivityNames.StorageClear, ActivityKind.Client); activity?.SetTag(ActivityTagKeys.GrainId, _grainContext.GrainId.ToString()); activity?.SetTag(ActivityTagKeys.StorageProvider, _shared.ProviderTypeName); activity?.SetTag(ActivityTagKeys.StorageStateName, _shared.Name); activity?.SetTag(ActivityTagKeys.StorageStateType, _shared.StateTypeName); var sw = ValueStopwatch.StartNew(); // Clear state in external storage await _shared.Store.ClearStateAsync(_shared.Name, _grainContext.GrainId, GrainState); sw.Stop(); // Update counters StorageInstruments.OnStorageDelete(sw.Elapsed, _shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); } catch (Exception exc) { StorageInstruments.OnStorageDeleteError(_shared.ProviderTypeName, _shared.Name, _shared.StateTypeName); OnError(exc, ErrorCode.StorageProvider_DeleteFailed, nameof(ClearStateAsync)); } } /// public void OnDehydrate(IDehydrationContext dehydrationContext) { try { dehydrationContext.TryAddValue(_shared.MigrationContextKey, _grainState); } catch (Exception exception) { LogErrorOnDehydrate(_shared.Logger, exception, _shared.Name, _grainContext.GrainId); throw; } } /// public void OnRehydrate(IRehydrationContext rehydrationContext) { try { if (rehydrationContext.TryGetValue>(_shared.MigrationContextKey, out var grainState)) { _grainState = grainState; IsStateInitialized = true; } } catch (Exception exception) { LogErrorOnRehydrate(_shared.Logger, exception, _shared.Name, _grainContext.GrainId); } } [DoesNotReturn] private void OnError(Exception exception, ErrorCode id, string operation) { string? errorCode = null; (_shared.Store as IRestExceptionDecoder)?.DecodeException(exception, out _, out errorCode, true); var errorString = errorCode is { Length: > 0 } ? $" Error: {errorCode}" : null; var grainId = _grainContext.GrainId; // TODO: pending on https://github.com/dotnet/runtime/issues/110570 _shared.Logger.LogError((int)id, exception, "Error from storage provider {ProviderName}.{StateName} during {Operation} for grain {GrainId}{ErrorCode}", _shared.ProviderTypeName, _shared.Name, operation, grainId, errorString); // If error is not specialization of OrleansException, wrap it if (exception is not OrleansException) { var errMsg = $"Error from storage provider {_shared.ProviderTypeName}.{_shared.Name} during {operation} for grain {grainId}{errorString}{Environment.NewLine} {LogFormatter.PrintException(exception)}"; throw new OrleansException(errMsg, exception); } ExceptionDispatchInfo.Throw(exception); } [LoggerMessage( Level = LogLevel.Error, Message = "Failed to dehydrate state named {StateName} for grain {GrainId}" )] private static partial void LogErrorOnDehydrate(ILogger logger, Exception exception, string stateName, GrainId grainId); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to rehydrate state named {StateName} for grain {GrainId}" )] private static partial void LogErrorOnRehydrate(ILogger logger, Exception exception, string stateName, GrainId grainId); } internal sealed class StateStorageBridgeSharedMap(ILoggerFactory loggerFactory, IActivatorProvider activatorProvider) { private readonly ConcurrentDictionary<(string Name, IGrainStorage Store, Type StateType), object> _instances = new(); private readonly ILoggerFactory _loggerFactory = loggerFactory; private readonly IActivatorProvider _activatorProvider = activatorProvider; public StateStorageBridgeShared Get(string name, IGrainStorage store) => (StateStorageBridgeShared)_instances.GetOrAdd( (name, store, typeof(TState)), static (key, self) => new StateStorageBridgeShared( key.Name, key.Store, self._loggerFactory.CreateLogger(key.Store.GetType()), self._activatorProvider.GetActivator()), this); } internal sealed class StateStorageBridgeShared(string name, IGrainStorage store, ILogger logger, IActivator activator) { private string? _migrationContextKey; public readonly string Name = name; public readonly string ProviderTypeName = store.GetType().Name; public readonly string StateTypeName = typeof(TState).Name; public readonly IGrainStorage Store = store; public readonly ILogger Logger = logger; public readonly IActivator Activator = activator; public string MigrationContextKey => _migrationContextKey ??= $"state.{Name}"; } } ================================================ FILE: src/Orleans.Runtime/Timers/AsyncTimer.cs ================================================ using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal partial class AsyncTimer : IAsyncTimer { /// /// Timers can fire up to 3 seconds late before a warning is emitted and the instance is deemed unhealthy. /// private static readonly TimeSpan TimerDelaySlack = TimeSpan.FromSeconds(3); private readonly CancellationTokenSource cancellation = new CancellationTokenSource(); private readonly TimeSpan period; private readonly string name; private readonly ILogger log; private DateTime lastFired = DateTime.MinValue; private DateTime expected; public AsyncTimer(TimeSpan period, string name, ILogger log) { this.log = log; this.period = period; this.name = name; } /// /// Returns a task which completes after the required delay. /// /// An optional override to this timer's configured period. /// if the timer completed or if the timer was cancelled public async Task NextTick(TimeSpan? overrideDelay = default) { if (cancellation.IsCancellationRequested) return false; var start = DateTime.UtcNow; var delay = overrideDelay switch { { } value => value, _ when lastFired == DateTime.MinValue => period, _ => lastFired.Add(period).Subtract(start) }; if (delay < TimeSpan.Zero) delay = TimeSpan.Zero; var dueTime = start.Add(delay); this.expected = dueTime; if (delay > TimeSpan.Zero) { // for backwards compatibility, support timers with periods up to ReminderRegistry.MaxSupportedTimeout var maxDelay = TimeSpan.FromMilliseconds(int.MaxValue); while (delay > maxDelay) { delay -= maxDelay; var task2 = await Task.WhenAny(Task.Delay(maxDelay, cancellation.Token)).ConfigureAwait(false); if (task2.IsCanceled) { await Task.Yield(); expected = default; return false; } } var task = await Task.WhenAny(Task.Delay(delay, cancellation.Token)).ConfigureAwait(false); if (task.IsCanceled) { await Task.Yield(); expected = default; return false; } } var now = this.lastFired = DateTime.UtcNow; var overshoot = GetOvershootDelay(now, dueTime); if (overshoot > TimeSpan.Zero) { if (!Debugger.IsAttached) { LogTimerOvershoot(this.log, dueTime, now, overshoot); } } expected = default; return true; } private static TimeSpan GetOvershootDelay(DateTime now, DateTime dueTime) { if (dueTime == default) return TimeSpan.Zero; if (dueTime > now) return TimeSpan.Zero; var overshoot = now.Subtract(dueTime); if (overshoot > TimerDelaySlack) return overshoot; return TimeSpan.Zero; } public bool CheckHealth(DateTime lastCheckTime, out string reason) { var now = DateTime.UtcNow; var due = this.expected; var overshoot = GetOvershootDelay(now, due); if (overshoot > TimeSpan.Zero && !Debugger.IsAttached) { reason = $"{this.name} timer should have fired at {due}, which is {overshoot} ago"; return false; } reason = default; return true; } public void Dispose() { this.expected = default; this.cancellation.Cancel(); } [LoggerMessage( Level = LogLevel.Warning, Message = "Timer should have fired at {DueTime} but fired at {CurrentTime}, which is {Overshoot} longer than expected" )] private static partial void LogTimerOvershoot(ILogger logger, DateTime dueTime, DateTime currentTime, TimeSpan overshoot); } } ================================================ FILE: src/Orleans.Runtime/Timers/AsyncTimerFactory.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.Runtime { internal class AsyncTimerFactory : IAsyncTimerFactory { private readonly ILoggerFactory loggerFactory; public AsyncTimerFactory(ILoggerFactory loggerFactory) { this.loggerFactory = loggerFactory; } public IAsyncTimer Create(TimeSpan period, string name) { var log = this.loggerFactory.CreateLogger($"{typeof(AsyncTimer).FullName}.{name}"); return new AsyncTimer(period, name, log); } } } ================================================ FILE: src/Orleans.Runtime/Timers/GrainTimer.cs ================================================ #nullable enable using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.CodeGeneration; using Orleans.Runtime.Internal; using Orleans.Serialization.Invocation; using Orleans.Timers; namespace Orleans.Runtime; internal abstract partial class GrainTimer : IGrainTimer { protected static readonly GrainInterfaceType InvokableInterfaceType = GrainInterfaceType.Create("Orleans.Runtime.IGrainTimerInvoker"); protected static readonly TimerCallback TimerCallback = (state) => ((GrainTimer)state!).ScheduleTickOnActivation(); protected static readonly MethodInfo InvokableMethodInfo = typeof(IGrainTimerInvoker).GetMethod(nameof(IGrainTimerInvoker.InvokeCallbackAsync), BindingFlags.Instance | BindingFlags.Public)!; private readonly CancellationTokenSource _cts = new(); private readonly ITimer _timer; private readonly IGrainContext _grainContext; private readonly TimerRegistry _shared; private readonly bool _interleave; private readonly bool _keepAlive; private readonly TimerTickInvoker _invoker; private bool _changed; private bool _firing; private TimeSpan _dueTime; private TimeSpan _period; public GrainTimer(TimerRegistry shared, IGrainContext grainContext, bool interleave, bool keepAlive) { ArgumentNullException.ThrowIfNull(shared); ArgumentNullException.ThrowIfNull(grainContext); _interleave = interleave; _keepAlive = keepAlive; _shared = shared; _grainContext = grainContext; _dueTime = Timeout.InfiniteTimeSpan; _period = Timeout.InfiniteTimeSpan; _invoker = new(this); // Avoid capturing async locals. using (new ExecutionContextSuppressor()) { _timer = shared.TimeProvider.CreateTimer(TimerCallback, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); } } protected IGrainContext GrainContext => _grainContext; private ILogger Logger => _shared.TimerLogger; [DoesNotReturn] private static void ThrowIncorrectGrainContext() => throw new InvalidOperationException("Current grain context differs from specified grain context."); [DoesNotReturn] private static void ThrowInvalidSchedulingContext() { throw new InvalidSchedulingContextException( "Current grain context is null. " + "Please make sure you are not trying to create a Timer from outside Orleans Task Scheduler, " + "which will be the case if you create it inside Task.Run."); } protected void ScheduleTickOnActivation() { try { // Indicate that the timer is firing so that the effect of the next change call is deferred until after the tick completes. _firing = true; // Note: this does not execute on the activation's execution context. var msg = _shared.MessageFactory.CreateMessage(body: _invoker, options: InvokeMethodOptions.OneWay); msg.SetInfiniteTimeToLive(); msg.SendingGrain = _grainContext.GrainId; msg.TargetGrain = _grainContext.GrainId; msg.SendingSilo = _shared.LocalSiloDetails.SiloAddress; msg.TargetSilo = _shared.LocalSiloDetails.SiloAddress; msg.InterfaceType = InvokableInterfaceType; msg.IsKeepAlive = _keepAlive; msg.IsAlwaysInterleave = _interleave; // Prevent the message from being forwarded in the case of deactivation. msg.IsLocalOnly = true; _grainContext.ReceiveMessage(msg); } catch (Exception exception) { try { LogErrorScheduleTickOnActivation(Logger, exception, this); } catch { // Ignore. // Allowing an exception to escape here would crash the process. } } } protected abstract Task InvokeCallbackAsync(CancellationToken cancellationToken); private ValueTask InvokeGrainTimerCallbackAsync() { try { LogTraceBeforeCallback(Logger, this); _changed = false; var task = InvokeCallbackAsync(_cts.Token); // If the task is not completed, we need to await the tick asynchronously. if (task is { IsCompletedSuccessfully: false }) { // Complete asynchronously. return AwaitCallbackTask(task); } else { // Complete synchronously. LogTraceAfterCallback(Logger, this); OnTickCompleted(); return new(Response.Completed); } } catch (Exception exc) { OnTickCompleted(); return new(OnCallbackException(exc)); } } private void OnTickCompleted() { // Schedule the next tick. try { if (_cts.IsCancellationRequested) { // The instance has been disposed. No further ticks should be fired. return; } if (!_changed) { // If the timer was not modified during the tick, schedule the next tick based on the period. _timer.Change(_period, Timeout.InfiniteTimeSpan); } else { // If the timer was modified during the tick, schedule the next tick based on the new due time. _timer.Change(_dueTime, Timeout.InfiniteTimeSpan); } } catch (ObjectDisposedException) { } finally { _firing = false; } } private Response OnCallbackException(Exception exc) { LogWarningCallbackException(Logger, exc, this); return Response.FromException(exc); } private async ValueTask AwaitCallbackTask(Task task) { try { await task; LogTraceAfterCallback(Logger, this); return Response.Completed; } catch (Exception exc) { return OnCallbackException(exc); } finally { OnTickCompleted(); } } public void Change(TimeSpan dueTime, TimeSpan period) { ValidateArguments(dueTime, period); _changed = true; _dueTime = dueTime; _period = period; // If the timer is currently firing, the change will be deferred until after the tick completes. // Otherwise, perform the change now. if (!_firing) { try { // This method resets the timer, so the next tick will be scheduled at the new due time and subsequent // ticks will be scheduled after the specified period. _timer.Change(dueTime, Timeout.InfiniteTimeSpan); } catch (ObjectDisposedException) { } } } private static void ValidateArguments(TimeSpan dueTime, TimeSpan period) { // See https://github.com/dotnet/runtime/blob/78b5f40a60d9e095abb2b0aabd8c062b171fb9ab/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs#L824-L825 const uint MaxSupportedTimeout = 0xfffffffe; // See https://github.com/dotnet/runtime/blob/78b5f40a60d9e095abb2b0aabd8c062b171fb9ab/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs#L927-L930 long dueTm = (long)dueTime.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(dueTm, -1, nameof(dueTime)); ArgumentOutOfRangeException.ThrowIfGreaterThan(dueTm, MaxSupportedTimeout, nameof(dueTime)); long periodTm = (long)period.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(periodTm, -1, nameof(period)); ArgumentOutOfRangeException.ThrowIfGreaterThan(periodTm, MaxSupportedTimeout, nameof(period)); } public void Dispose() { try { _cts.Cancel(); } catch (Exception exception) { LogErrorCancellingCallback(Logger, exception); } _timer.Dispose(); var timerRegistry = _grainContext.GetComponent(); timerRegistry?.OnTimerDisposed(this); } public override string ToString() => $"[{GetType()}] Grain: '{_grainContext}'"; private sealed class TimerTickInvoker(GrainTimer timer) : IInvokable, IGrainTimerInvoker { public object? GetTarget() => this; public void SetTarget(ITargetHolder holder) { if (timer._grainContext != holder) { throw new InvalidOperationException($"Invalid target holder. Expected {timer._grainContext}, received {holder}."); } } public ValueTask Invoke() => timer.InvokeGrainTimerCallbackAsync(); // This method is declared for the sake of IGrainTimerCore, but it is not intended to be called directly. // It exists for grain call interceptors which inspect the implementation method. Task IGrainTimerInvoker.InvokeCallbackAsync() => throw new InvalidOperationException(); public int GetArgumentCount() => 0; public object? GetArgument(int index) => throw new InvalidOperationException(); public void SetArgument(int index, object? value) => throw new InvalidOperationException(); public string GetMethodName() => nameof(IGrainTimerInvoker.InvokeCallbackAsync); public string GetInterfaceName() => nameof(IGrainTimerInvoker); public string GetActivityName() => $"{nameof(IGrainTimerInvoker)}/{nameof(IGrainTimerInvoker.InvokeCallbackAsync)}"; public MethodInfo GetMethod() => InvokableMethodInfo; public Type GetInterfaceType() => typeof(IGrainTimerInvoker); public TimeSpan? GetDefaultResponseTimeout() => null; public void Dispose() { // Do nothing. Instances are disposed after invocation, but this instance will be reused for the lifetime of the timer. } public override string ToString() => timer.ToString(); } [LoggerMessage( Level = LogLevel.Error, Message = "Error invoking timer tick for timer '{Timer}'." )] private static partial void LogErrorScheduleTickOnActivation(ILogger logger, Exception exception, GrainTimer timer); [LoggerMessage( EventId = (int)ErrorCode.TimerBeforeCallback, Level = LogLevel.Trace, Message = "About to invoke callback for timer {Timer}" )] private static partial void LogTraceBeforeCallback(ILogger logger, GrainTimer timer); [LoggerMessage( EventId = (int)ErrorCode.TimerAfterCallback, Level = LogLevel.Trace, Message = "Completed timer callback for timer '{Timer}'." )] private static partial void LogTraceAfterCallback(ILogger logger, GrainTimer timer); [LoggerMessage( EventId = (int)ErrorCode.Timer_GrainTimerCallbackError, Level = LogLevel.Warning, Message = "Caught and ignored exception thrown from timer callback for timer '{Timer}'." )] private static partial void LogWarningCallbackException(ILogger logger, Exception exception, GrainTimer timer); [LoggerMessage( Level = LogLevel.Error, Message = "Error cancelling timer callback." )] private static partial void LogErrorCancellingCallback(ILogger logger, Exception exception); } internal sealed class GrainTimer : GrainTimer { private readonly Func _callback; private readonly T _state; public GrainTimer( TimerRegistry shared, IGrainContext grainContext, Func callback, T state, bool interleave, bool keepAlive) : base( shared, grainContext, interleave, keepAlive) { ArgumentNullException.ThrowIfNull(callback); _callback = callback; _state = state; } protected override Task InvokeCallbackAsync(CancellationToken cancellationToken) => _callback(_state, cancellationToken); public override string ToString() => $"{base.ToString()} Callback: '{_callback?.Target}.{_callback?.Method}'. State: '{_state}'"; } internal sealed class InterleavingGrainTimer : GrainTimer { private readonly Func _callback; private readonly object? _state; public InterleavingGrainTimer( TimerRegistry shared, IGrainContext grainContext, Func callback, object? state) : base( shared, grainContext, interleave: true, keepAlive: false) { ArgumentNullException.ThrowIfNull(callback); _callback = callback; _state = state; } protected override Task InvokeCallbackAsync(CancellationToken cancellationToken) => _callback(_state); public override string ToString() => $"{base.ToString()} Callback: '{_callback?.Target}.{_callback?.Method}'. State: '{_state}'"; } // This interface exists for the IInvokable implementation, so that call filters behave as intended. internal interface IGrainTimerInvoker : IAddressable { /// /// Invokes the callback. /// Task InvokeCallbackAsync(); } ================================================ FILE: src/Orleans.Runtime/Timers/IAsyncTimer.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Runtime { internal interface IAsyncTimer : IDisposable, IHealthCheckable { Task NextTick(TimeSpan? overrideDelay = default); } } ================================================ FILE: src/Orleans.Runtime/Timers/IAsyncTimerFactory.cs ================================================ using System; namespace Orleans.Runtime { internal interface IAsyncTimerFactory { IAsyncTimer Create(TimeSpan period, string name); } } ================================================ FILE: src/Orleans.Runtime/Timers/IGrainTimerRegistry.cs ================================================ #nullable enable namespace Orleans.Runtime; /// /// Provides functionality to record the creation and deletion of grain timers. /// internal interface IGrainTimerRegistry { /// /// Signals to the registry that a timer was created. /// /// /// The timer. /// void OnTimerCreated(IGrainTimer timer); /// /// Signals to the registry that a timer was disposed. /// /// /// The timer. /// void OnTimerDisposed(IGrainTimer timer); } ================================================ FILE: src/Orleans.Runtime/Timers/TimerRegistry.cs ================================================ #nullable enable using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.Timers; internal class TimerRegistry(ILoggerFactory loggerFactory, TimeProvider timeProvider, MessageFactory messageFactory, ILocalSiloDetails localSiloDetails) : ITimerRegistry { public ILogger TimerLogger { get; } = loggerFactory.CreateLogger(); public TimeProvider TimeProvider { get; } = timeProvider; public MessageFactory MessageFactory { get; } = messageFactory; public ILocalSiloDetails LocalSiloDetails { get; } = localSiloDetails; public IDisposable RegisterTimer(IGrainContext grainContext, Func callback, object? state, TimeSpan dueTime, TimeSpan period) { ArgumentNullException.ThrowIfNull(grainContext); ArgumentNullException.ThrowIfNull(callback); var timer = new InterleavingGrainTimer(this, grainContext, callback, state); grainContext.GetComponent()?.OnTimerCreated(timer); timer.Change(dueTime, period); return timer; } public IGrainTimer RegisterGrainTimer(IGrainContext grainContext, Func callback, T state, GrainTimerCreationOptions options) { ArgumentNullException.ThrowIfNull(grainContext); ArgumentNullException.ThrowIfNull(callback); var timer = new GrainTimer(this, grainContext, callback, state, options.Interleave, options.KeepAlive); grainContext.GetComponent()?.OnTimerCreated(timer); timer.Change(options.DueTime, options.Period); return timer; } } ================================================ FILE: src/Orleans.Runtime/Utilities/FactoryUtility.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime.Utilities { /// /// Utility methods for creating factories which construct instances of objects using an . /// internal static class FactoryUtility { private static readonly object[] EmptyArguments = new object[0]; /// /// Creates a factory returning a new . /// /// The instance type. /// The service provider. /// A new factory. public static Factory Create(IServiceProvider serviceProvider) { var factory = ActivatorUtilities.CreateFactory(typeof(TInstance), Type.EmptyTypes); return () => (TInstance)factory(serviceProvider, EmptyArguments); } /// /// Creates a factory returning a new given an argument of type . /// /// The type of the parameter to the factory. /// The instance type. /// The service provider. /// A new factory. public static Factory Create(IServiceProvider serviceProvider) { var factory = ActivatorUtilities.CreateFactory(typeof(TInstance), new[] { typeof(TParam1) }); return arg1 => (TInstance)factory(serviceProvider, new object[] { arg1 }); } /// /// Creates a factory returning a new given arguments of the specified types. /// /// The type of the 1st parameter to the factory. /// The type of the 2nd parameter to the factory. /// The instance type. /// The service provider. /// A new factory. public static Factory Create(IServiceProvider serviceProvider) { var factory = ActivatorUtilities.CreateFactory(typeof(TInstance), new[] { typeof(TParam1), typeof(TParam2) }); return (arg1, arg2) => (TInstance)factory(serviceProvider, new object[] { arg1, arg2 }); } /// /// Creates a factory returning a new given arguments of the specified types. /// /// The type of the 1st parameter to the factory. /// The type of the 2nd parameter to the factory. /// The type of the 3rd parameter to the factory. /// The instance type. /// The service provider. /// A new factory. public static Factory Create(IServiceProvider serviceProvider) { var factory = ActivatorUtilities.CreateFactory(typeof(TInstance), new[] { typeof(TParam1), typeof(TParam2), typeof(TParam3) }); return (arg1, arg2, arg3) => (TInstance)factory(serviceProvider, new object[] { arg1, arg2, arg3 }); } } } ================================================ FILE: src/Orleans.Runtime/Utilities/OrleansDebuggerHelper.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace Orleans.Runtime.Utilities { /// /// Utility methods for aiding debugger sessions. /// public static class OrleansDebuggerHelper { /// /// Returns the grain instance corresponding to the provided if it is activated on current silo. /// /// The grain reference. /// /// The grain instance corresponding to the provided if it is activated on current silo, or otherwise. /// public static object GetGrainInstance(object grainReference) { switch (grainReference) { case Grain: case IGrainBase: case ISystemTargetBase: return grainReference; case GrainReference reference: { var runtime = (reference.Runtime as GrainReferenceRuntime)?.RuntimeClient; var activations = runtime?.ServiceProvider.GetService(); var grains = activations?.FindTarget(reference.GrainId); return grains?.GrainInstance; } default: return null; } } } } ================================================ FILE: src/Orleans.Runtime/Utilities/SearchAlgorithms.cs ================================================ using System; using System.Diagnostics; using System.Runtime.CompilerServices; namespace Orleans.Runtime.Utilities; internal static class SearchAlgorithms { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int BinarySearch(int length, TState state, Func comparer) { var left = 0; var right = length - 1; while (left <= right) { var mid = left + (right - left) / 2; var comparison = comparer(mid, state); if (comparison == 0) { return mid; } else if (comparison < 0) { left = mid + 1; } else { right = mid - 1; } } return -1; } // Binary search for collections of ranges along a ring (eg, a consistent hash ring), sorted by the starting point of each range. // This differs from a standard binary search in that the search can wrap around from the start to the last element in the collection. // This is accommodated by checking the last element in the collection before returning a negative result, to handle the case where a // range wraps around from end to start. See RingRange [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RingRangeBinarySearch( int length, TCollection collection, Func getEntry, TKey key) where TElement : IComparable { if (length == 0) return -1; var left = 0; var right = length - 1; TElement entry; while (left <= right) { var mid = left + (right - left) / 2; entry = getEntry(collection, mid); var comparison = entry.CompareTo(key); if (comparison == 0) { return mid; } else if (comparison < 0) { // Go right. left = mid + 1; } else { // Go left. right = mid - 1; } } // Try the last element. entry = getEntry(collection, length - 1); if (entry.CompareTo(key) == 0) { return length - 1; } #if DEBUG // Try the first element. entry = getEntry(collection, 0); if (entry.CompareTo(key) == 0) { Debug.Fail("Sort order invariant violated."); } #endif return -1; } } ================================================ FILE: src/Orleans.Runtime/Utilities/StripedMpscBuffer.cs ================================================ using System; using System.Linq; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Numerics; using System.Runtime.CompilerServices; namespace Orleans.Runtime.Utilities; /// /// Provides a striped bounded buffer. Add operations use thread ID to index into /// the underlying array of buffers, and if TryAdd is contended the thread ID is /// rehashed to select a different buffer to retry up to 3 times. Using this approach /// writes scale linearly with number of concurrent threads. /// /// /// Derived from BitFaster.Caching by Alex Peck: https://github.com/bitfaster/BitFaster.Caching/blob/275b9b072c0218e20f549b769cd183df1374e2ee/BitFaster.Caching/Buffers/StripedMpscBuffer.cs /// [DebuggerDisplay("Count = {Count}/{Capacity}")] internal sealed class StripedMpscBuffer where T : class { private const int MaxAttempts = 3; private readonly MpscBoundedBuffer[] _buffers; /// /// Initializes a new instance of the StripedMpscBuffer class with the specified stripe count and buffer size. /// /// The stripe count. /// The buffer size. public StripedMpscBuffer(int stripeCount, int bufferSize) { _buffers = new MpscBoundedBuffer[stripeCount]; for (var i = 0; i < stripeCount; i++) { _buffers[i] = new MpscBoundedBuffer(bufferSize); } } /// /// Gets the number of items contained in the buffer. /// public int Count => _buffers.Sum(b => b.Count); /// /// The bounded capacity. /// public int Capacity => _buffers.Length * _buffers[0].Capacity; /// /// Drains the buffer into the specified array. /// /// The output buffer /// The number of items written to the output buffer. /// /// Thread safe for single try take/drain + multiple try add. /// public int DrainTo(T[] outputBuffer) => DrainTo(outputBuffer.AsSpan()); /// /// Drains the buffer into the specified span. /// /// The output buffer /// The number of items written to the output buffer. /// /// Thread safe for single try take/drain + multiple try add. /// public int DrainTo(Span outputBuffer) { var count = 0; for (var i = 0; i < _buffers.Length; i++) { if (count == outputBuffer.Length) { break; } var segment = outputBuffer[count..]; count += _buffers[i].DrainTo(segment); } return count; } /// /// Tries to add the specified item. /// /// The item to be added. /// A BufferStatus value indicating whether the operation succeeded. /// /// Thread safe. /// public BufferStatus TryAdd(T item) { var z = BitOps.Mix64((ulong)Environment.CurrentManagedThreadId); var inc = (int)(z >> 32) | 1; var h = (int)z; var mask = _buffers.Length - 1; var result = BufferStatus.Empty; for (var i = 0; i < MaxAttempts; i++) { result = _buffers[h & mask].TryAdd(item); if (result == BufferStatus.Success) { break; } h += inc; } return result; } /// /// Removes all values from the buffer. /// /// /// Not thread safe. /// public void Clear() { for (var i = 0; i < _buffers.Length; i++) { _buffers[i].Clear(); } } } /// /// Provides a multi-producer, single-consumer thread-safe ring buffer. When the buffer is full, /// TryAdd fails and returns false. When the buffer is empty, TryTake fails and returns false. /// /// Based on the BoundedBuffer class in the Caffeine library by ben.manes@gmail.com (Ben Manes). [DebuggerDisplay("Count = {Count}/{Capacity}")] internal sealed class MpscBoundedBuffer where T : class { private T[] _buffer; private readonly int _mask; private PaddedHeadAndTail _headAndTail; // mutable struct, don't mark readonly /// /// Initializes a new instance of the MpscBoundedBuffer class with the specified bounded capacity. /// /// The bounded length. /// public MpscBoundedBuffer(int boundedLength) { ArgumentOutOfRangeException.ThrowIfLessThan(boundedLength, 0); // must be power of 2 to use & slotsMask instead of % boundedLength = BitOps.CeilingPowerOfTwo(boundedLength); _buffer = new T[boundedLength]; _mask = boundedLength - 1; } /// /// The bounded capacity. /// public int Capacity => _buffer.Length; /// /// Gets the number of items contained in the buffer. /// public int Count { get { var spinner = new SpinWait(); while (true) { var headNow = Volatile.Read(ref _headAndTail.Head); var tailNow = Volatile.Read(ref _headAndTail.Tail); if (headNow == Volatile.Read(ref _headAndTail.Head) && tailNow == Volatile.Read(ref _headAndTail.Tail)) { return GetCount(headNow, tailNow); } spinner.SpinOnce(); } } } private int GetCount(int head, int tail) { if (head != tail) { head &= _mask; tail &= _mask; return head < tail ? tail - head : _buffer.Length - head + tail; } return 0; } /// /// Tries to add the specified item. /// /// The item to be added. /// A BufferStatus value indicating whether the operation succeeded. /// /// Thread safe. /// public BufferStatus TryAdd(T item) { int head = Volatile.Read(ref _headAndTail.Head); int tail = _headAndTail.Tail; int size = tail - head; if (size >= _buffer.Length) { return BufferStatus.Full; } if (Interlocked.CompareExchange(ref _headAndTail.Tail, tail + 1, tail) == tail) { int index = tail & _mask; Volatile.Write(ref _buffer[index], item); return BufferStatus.Success; } return BufferStatus.Contended; } /// /// Tries to remove an item. /// /// The item to be removed. /// A BufferStatus value indicating whether the operation succeeded. /// /// Thread safe for single try take/drain + multiple try add. /// public BufferStatus TryTake(out T item) { int head = Volatile.Read(ref _headAndTail.Head); int tail = _headAndTail.Tail; int size = tail - head; if (size == 0) { item = default; return BufferStatus.Empty; } int index = head & _mask; item = Volatile.Read(ref _buffer[index]); if (item == null) { // not published yet return BufferStatus.Contended; } _buffer[index] = null; Volatile.Write(ref _headAndTail.Head, ++head); return BufferStatus.Success; } /// /// Drains the buffer into the specified array segment. /// /// The output buffer /// The number of items written to the output buffer. /// /// Thread safe for single try take/drain + multiple try add. /// public int DrainTo(ArraySegment output) => DrainTo(output.AsSpan()); /// /// Drains the buffer into the specified span. /// /// The output buffer /// The number of items written to the output buffer. /// /// Thread safe for single try take/drain + multiple try add. /// public int DrainTo(Span output) => DrainToImpl(output); // use an outer wrapper method to force the JIT to inline the inner adaptor methods [MethodImpl(MethodImplOptions.AggressiveInlining)] private int DrainToImpl(Span output) { int head = Volatile.Read(ref _headAndTail.Head); int tail = _headAndTail.Tail; int size = tail - head; if (size == 0) { return 0; } var localBuffer = _buffer.AsSpan(); int outCount = 0; do { int index = head & _mask; T item = Volatile.Read(ref localBuffer[index]); if (item == null) { // not published yet break; } localBuffer[index] = null; Write(output, outCount++, item); head++; } while (head != tail && outCount < Length(output)); _headAndTail.Head = head; return outCount; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Write(Span output, int index, T item) => output[index] = item; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Length(Span output) => output.Length; /// /// Removes all values from the buffer. /// /// /// Not thread safe. /// public void Clear() { _buffer = new T[_buffer.Length]; _headAndTail = new PaddedHeadAndTail(); } } /// /// Specifies the status of buffer operations. /// internal enum BufferStatus { /// /// The buffer is full. /// Full, /// /// The buffer is empty. /// Empty, /// /// The buffer operation succeeded. /// Success, /// /// The buffer operation was contended. /// Contended, } /// /// Provides utility methods for bit-twiddling operations. /// internal static class BitOps { /// /// Calculate the smallest power of 2 greater than the input parameter. /// /// The input parameter. /// Smallest power of two greater than or equal to x. public static int CeilingPowerOfTwo(int x) => (int)CeilingPowerOfTwo((uint)x); /// /// Calculate the smallest power of 2 greater than the input parameter. /// /// The input parameter. /// Smallest power of two greater than or equal to x. public static uint CeilingPowerOfTwo(uint x) => 1u << -BitOperations.LeadingZeroCount(x - 1); /// /// Computes Stafford variant 13 of 64-bit mix function. /// /// The input parameter. /// A bit mix of the input parameter. /// /// See http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html /// public static ulong Mix64(ulong z) { z = (z ^ z >> 30) * 0xbf58476d1ce4e5b9L; z = (z ^ z >> 27) * 0x94d049bb133111ebL; return z ^ z >> 31; } } [DebuggerDisplay("Head = {Head}, Tail = {Tail}")] [StructLayout(LayoutKind.Explicit, Size = 3 * Padding.CACHE_LINE_SIZE)] // padding before/between/after fields internal struct PaddedHeadAndTail { [FieldOffset(1 * Padding.CACHE_LINE_SIZE)] public int Head; [FieldOffset(2 * Padding.CACHE_LINE_SIZE)] public int Tail; } internal static class Padding { #if TARGET_ARM64 || TARGET_LOONGARCH64 internal const int CACHE_LINE_SIZE = 128; #else internal const int CACHE_LINE_SIZE = 64; #endif } ================================================ FILE: src/Orleans.Runtime/Versions/CachedVersionSelectorManager.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Orleans.Metadata; using Orleans.Runtime.Versions.Compatibility; using Orleans.Runtime.Versions.Selector; namespace Orleans.Runtime.Versions { internal class CachedVersionSelectorManager { private readonly ConcurrentDictionary<(GrainType Type, GrainInterfaceType Interface, ushort Version), CachedEntry> suitableSilosCache; private readonly GrainVersionManifest grainInterfaceVersions; public CachedVersionSelectorManager(GrainVersionManifest grainInterfaceVersions, VersionSelectorManager versionSelectorManager, CompatibilityDirectorManager compatibilityDirectorManager) { this.grainInterfaceVersions = grainInterfaceVersions; this.VersionSelectorManager = versionSelectorManager; this.CompatibilityDirectorManager = compatibilityDirectorManager; this.suitableSilosCache = new ConcurrentDictionary<(GrainType Type, GrainInterfaceType Interface, ushort Version), CachedEntry>(); } public VersionSelectorManager VersionSelectorManager { get; } public CompatibilityDirectorManager CompatibilityDirectorManager { get; } public CachedEntry GetSuitableSilos(GrainType grainType, GrainInterfaceType interfaceId, ushort requestedVersion) { var key = ValueTuple.Create(grainType, interfaceId, requestedVersion); if (!suitableSilosCache.TryGetValue(key, out var entry) || entry.Version < this.grainInterfaceVersions.LatestVersion) { entry = suitableSilosCache[key] = GetSuitableSilosImpl(key); } return entry; } public void ResetCache() { this.suitableSilosCache.Clear(); } private CachedEntry GetSuitableSilosImpl((GrainType Type, GrainInterfaceType Interface, ushort Version) key) { var grainType = key.Type; var interfaceType = key.Interface; var requestedVersion = key.Version; var versionSelector = this.VersionSelectorManager.GetSelector(interfaceType); var compatibilityDirector = this.CompatibilityDirectorManager.GetDirector(interfaceType); (var version, var available) = this.grainInterfaceVersions.GetAvailableVersions(interfaceType); var versions = versionSelector.GetSuitableVersion( requestedVersion, available, compatibilityDirector); (_, var result) = this.grainInterfaceVersions.GetSupportedSilos(grainType, interfaceType, versions); return new CachedEntry { Version = version, SuitableSilos = result.SelectMany(sv => sv.Value).Distinct().OrderBy(addr => addr).ToArray(), SuitableSilosByVersion = result, }; } internal struct CachedEntry { public MajorMinorVersion Version { get; set; } public SiloAddress[] SuitableSilos { get; set; } public Dictionary SuitableSilosByVersion { get; set; } } } } ================================================ FILE: src/Orleans.Runtime/Versions/Compatibility/AllVersionsCompatibilityDirector.cs ================================================ using Orleans.Versions.Compatibility; namespace Orleans.Runtime.Versions.Compatibility { internal class AllVersionsCompatibilityDirector : ICompatibilityDirector { public bool IsCompatible(ushort requestedVersion, ushort currentVersion) { return true; } } } ================================================ FILE: src/Orleans.Runtime/Versions/Compatibility/BackwardCompatilityDirector.cs ================================================ using Orleans.Versions.Compatibility; namespace Orleans.Runtime.Versions.Compatibility { internal class BackwardCompatilityDirector : ICompatibilityDirector { public bool IsCompatible(ushort requestedVersion, ushort currentVersion) { return requestedVersion <= currentVersion; } } } ================================================ FILE: src/Orleans.Runtime/Versions/Compatibility/CompatibilityDirectorManager.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Versions.Compatibility; namespace Orleans.Runtime.Versions.Compatibility { internal class CompatibilityDirectorManager { private readonly CompatibilityStrategy strategyFromConfig; private readonly IServiceProvider serviceProvider; private readonly Dictionary compatibilityDirectors; public ICompatibilityDirector Default { get; private set; } public CompatibilityDirectorManager(IServiceProvider serviceProvider, IOptions options) { this.serviceProvider = serviceProvider; this.strategyFromConfig = serviceProvider.GetRequiredKeyedService(options.Value.DefaultCompatibilityStrategy); this.compatibilityDirectors = new Dictionary(); Default = ResolveVersionDirector(serviceProvider, this.strategyFromConfig); } public ICompatibilityDirector GetDirector(GrainInterfaceType interfaceType) { ICompatibilityDirector director; return compatibilityDirectors.TryGetValue(interfaceType, out director) ? director : Default; } public void SetStrategy(CompatibilityStrategy strategy) { var director = ResolveVersionDirector(this.serviceProvider, strategy ?? this.strategyFromConfig); Default = director; } public void SetStrategy(GrainInterfaceType interfaceType, CompatibilityStrategy strategy) { if (strategy == null) { compatibilityDirectors.Remove(interfaceType); } else { var selector = ResolveVersionDirector(this.serviceProvider, strategy); compatibilityDirectors[interfaceType] = selector; } } private static ICompatibilityDirector ResolveVersionDirector(IServiceProvider serviceProvider, CompatibilityStrategy compatibilityStrategy) { var strategyType = compatibilityStrategy.GetType(); return serviceProvider.GetRequiredKeyedService(strategyType); } } } ================================================ FILE: src/Orleans.Runtime/Versions/Compatibility/StrictVersionCompatibilityDirector.cs ================================================ using Orleans.Versions.Compatibility; namespace Orleans.Runtime.Versions.Compatibility { internal class StrictVersionCompatibilityDirector : ICompatibilityDirector { public bool IsCompatible(ushort requestedVersion, ushort currentVersion) { return requestedVersion == currentVersion; } } } ================================================ FILE: src/Orleans.Runtime/Versions/GrainVersionStore.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Storage; using Orleans.Versions; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; using Microsoft.Extensions.DependencyInjection; using System.Threading; namespace Orleans.Runtime.Versions { internal class GrainVersionStore : IVersionStore, ILifecycleParticipant { private readonly IInternalGrainFactory grainFactory; private readonly IServiceProvider services; private readonly string clusterId; private IVersionStoreGrain StoreGrain => this.grainFactory.GetGrain(this.clusterId); public bool IsEnabled { get; private set; } public GrainVersionStore(IInternalGrainFactory grainFactory, ILocalSiloDetails siloDetails, IServiceProvider services) { this.grainFactory = grainFactory; this.services = services; this.clusterId = siloDetails.ClusterId; this.IsEnabled = false; } public async Task SetCompatibilityStrategy(CompatibilityStrategy strategy) { ThrowIfNotEnabled(); await StoreGrain.SetCompatibilityStrategy(strategy); } public async Task SetSelectorStrategy(VersionSelectorStrategy strategy) { ThrowIfNotEnabled(); await StoreGrain.SetSelectorStrategy(strategy); } public async Task SetCompatibilityStrategy(GrainInterfaceType interfaceType, CompatibilityStrategy strategy) { ThrowIfNotEnabled(); await StoreGrain.SetCompatibilityStrategy(interfaceType, strategy); } public async Task SetSelectorStrategy(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy) { ThrowIfNotEnabled(); await StoreGrain.SetSelectorStrategy(interfaceType, strategy); } public async Task> GetCompatibilityStrategies() { ThrowIfNotEnabled(); return await StoreGrain.GetCompatibilityStrategies(); } public async Task> GetSelectorStrategies() { ThrowIfNotEnabled(); return await StoreGrain.GetSelectorStrategies(); } public async Task GetCompatibilityStrategy() { ThrowIfNotEnabled(); return await StoreGrain.GetCompatibilityStrategy(); } public async Task GetSelectorStrategy() { ThrowIfNotEnabled(); return await StoreGrain.GetSelectorStrategy(); } private void ThrowIfNotEnabled() { if (!IsEnabled) ThrowDisabled(); static void ThrowDisabled() => throw new OrleansException("Version store not enabled, make sure the store is configured"); } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.ApplicationServices, this.OnStart); } private Task OnStart(CancellationToken token) { this.IsEnabled = this.services.GetService() != null; return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.Runtime/Versions/Selector/AllCompatibleVersionsSelector.cs ================================================ using System.Linq; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime.Versions.Selector { internal class AllCompatibleVersionsSelector : IVersionSelector { public ushort[] GetSuitableVersion(ushort requestedVersion, ushort[] availableVersions, ICompatibilityDirector compatibilityDirector) { return availableVersions.Where(v => compatibilityDirector.IsCompatible(requestedVersion, v)).ToArray(); } } } ================================================ FILE: src/Orleans.Runtime/Versions/Selector/LatestVersionDirector.cs ================================================ using System; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime.Versions.Selector { internal sealed class LatestVersionSelector : IVersionSelector { public ushort[] GetSuitableVersion(ushort requestedVersion, ushort[] availableVersions, ICompatibilityDirector compatibilityDirector) { var max = int.MinValue; foreach (var version in availableVersions) { if (compatibilityDirector.IsCompatible(requestedVersion, version) && version > max) { max = version; } } if (max < 0) return Array.Empty(); return new[] { (ushort)max }; } } } ================================================ FILE: src/Orleans.Runtime/Versions/Selector/MinimumVersionSelector.cs ================================================ using System.Linq; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime.Versions.Selector { internal sealed class MinimumVersionSelector : IVersionSelector { public ushort[] GetSuitableVersion(ushort requestedVersion, ushort[] availableVersions, ICompatibilityDirector compatibilityDirector) { return new[] { availableVersions.Where(v => compatibilityDirector.IsCompatible(requestedVersion, v)).Min() }; } } } ================================================ FILE: src/Orleans.Runtime/Versions/Selector/VersionDirectorManager.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Versions.Selector; namespace Orleans.Runtime.Versions.Selector { internal class VersionSelectorManager { private readonly VersionSelectorStrategy strategyFromConfig; private readonly IServiceProvider serviceProvider; private readonly Dictionary versionSelectors; public IVersionSelector Default { get; set; } public VersionSelectorManager(IServiceProvider serviceProvider, IOptions options) { this.serviceProvider = serviceProvider; this.strategyFromConfig = serviceProvider.GetRequiredKeyedService(options.Value.DefaultVersionSelectorStrategy); Default = ResolveVersionSelector(serviceProvider, this.strategyFromConfig); versionSelectors = new Dictionary(); } public IVersionSelector GetSelector(GrainInterfaceType interfaceType) { IVersionSelector selector; return this.versionSelectors.TryGetValue(interfaceType, out selector) ? selector : Default; } public void SetSelector(VersionSelectorStrategy strategy) { var selector = ResolveVersionSelector(this.serviceProvider, strategy ?? this.strategyFromConfig); Default = selector; } public void SetSelector(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy) { if (strategy == null) { versionSelectors.Remove(interfaceType); } else { var selector = ResolveVersionSelector(this.serviceProvider, strategy); versionSelectors[interfaceType] = selector; } } private static IVersionSelector ResolveVersionSelector(IServiceProvider serviceProvider, VersionSelectorStrategy strategy) { var policyType = strategy.GetType(); return serviceProvider.GetRequiredKeyedService(policyType); } } } ================================================ FILE: src/Orleans.Runtime/Versions/SingleWaiterAutoResetEvent.cs ================================================ using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; namespace Orleans.Runtime { /// /// Represents a synchronization event that, when signaled, resets automatically after releasing a single waiter. /// This type supports concurrent signalers but only a single waiter. /// internal sealed class SingleWaiterAutoResetEvent : IValueTaskSource { // Signaled indicates that the event has been signaled and not yet reset. private const uint SignaledFlag = 1; // Waiting indicates that a waiter is present and waiting for the event to be signaled. private const uint WaitingFlag = 1 << 1; // ResetMask is used to clear both status flags. private const uint ResetMask = ~SignaledFlag & ~WaitingFlag; private ManualResetValueTaskSourceCore _waitSource; private volatile uint _status; public bool RunContinuationsAsynchronously { get => _waitSource.RunContinuationsAsynchronously; set => _waitSource.RunContinuationsAsynchronously = value; } ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _waitSource.GetStatus(token); void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _waitSource.OnCompleted(continuation, state, token, flags); void IValueTaskSource.GetResult(short token) { // Reset the wait source. _waitSource.GetResult(token); _waitSource.Reset(); // Reset the status. ResetStatus(); } /// /// Signal the waiter. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Signal() { if ((_status & SignaledFlag) == SignaledFlag) { // The event is already signaled. return; } // Set the signaled flag. var status = Interlocked.Or(ref _status, SignaledFlag); // If there was a waiter and the signaled flag was unset, wake the waiter now. if ((status & SignaledFlag) != SignaledFlag && (status & WaitingFlag) == WaitingFlag) { // Note that in this assert we are checking the volatile _status field. // This is a sanity check to ensure that the signaling conditions are true: // that "Signaled" and "Waiting" flags are both set. Debug.Assert((_status & (SignaledFlag | WaitingFlag)) == (SignaledFlag | WaitingFlag)); _waitSource.SetResult(true); } } /// /// Wait for the event to be signaled. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask WaitAsync() { // Indicate that there is a waiter. var status = Interlocked.Or(ref _status, WaitingFlag); // If there was already a waiter, that is an error since this class is designed for use with a single waiter. if ((status & WaitingFlag) == WaitingFlag) { ThrowConcurrentWaitersNotSupported(); } // If the event was already signaled, immediately wake the waiter. if ((status & SignaledFlag) == SignaledFlag) { // Reset just the status because the _waitSource has not been set. // We know that _waitSource has not been set because _waitSource is only set when // Signal() observes that the "Waiting" flag had been set but not the "Signaled" flag. ResetStatus(); return default; } return new(this, _waitSource.Version); } /// /// Called when a waiter handles the event signal. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ResetStatus() { // The event is being handled, so clear the "Signaled" flag now. // The waiter is no longer waiting, so clear the "Waiting" flag, too. var status = Interlocked.And(ref _status, ResetMask); // If both the "Waiting" and "Signaled" flags were not already set, something has gone catastrophically wrong. Debug.Assert((status & (WaitingFlag | SignaledFlag)) == (WaitingFlag | SignaledFlag)); } private static void ThrowConcurrentWaitersNotSupported() => throw new InvalidOperationException("Concurrent waiters are not supported"); } } ================================================ FILE: src/Orleans.Runtime/Versions/VersionStoreGrain.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Providers; using Orleans.Versions.Compatibility; using Orleans.Versions.Selector; namespace Orleans.Runtime.Versions { internal interface IVersionStoreGrain : IGrainWithStringKey { Task> GetCompatibilityStrategies(); Task> GetSelectorStrategies(); Task GetCompatibilityStrategy(); Task GetSelectorStrategy(); Task SetCompatibilityStrategy(CompatibilityStrategy strategy); Task SetSelectorStrategy(VersionSelectorStrategy strategy); Task SetCompatibilityStrategy(GrainInterfaceType interfaceType, CompatibilityStrategy strategy); Task SetSelectorStrategy(GrainInterfaceType interfaceType, VersionSelectorStrategy strategy); } [GenerateSerializer] internal sealed class VersionStoreGrainState { [Id(0)] public readonly Dictionary CompatibilityStrategies = new(); [Id(1)] public readonly Dictionary VersionSelectorStrategies = new(); [Id(2)] public VersionSelectorStrategy SelectorOverride; [Id(3)] public CompatibilityStrategy CompatibilityOverride; } [StorageProvider(ProviderName = ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)] internal class VersionStoreGrain : Grain, IVersionStoreGrain { public async Task SetCompatibilityStrategy(CompatibilityStrategy strategy) { this.State.CompatibilityOverride = strategy; await this.WriteStateAsync(); } public async Task SetSelectorStrategy(VersionSelectorStrategy strategy) { this.State.SelectorOverride = strategy; await this.WriteStateAsync(); } public async Task SetCompatibilityStrategy(GrainInterfaceType ifaceId, CompatibilityStrategy strategy) { this.State.CompatibilityStrategies[ifaceId] = strategy; await this.WriteStateAsync(); } public async Task SetSelectorStrategy(GrainInterfaceType ifaceId, VersionSelectorStrategy strategy) { this.State.VersionSelectorStrategies[ifaceId] = strategy; await this.WriteStateAsync(); } public bool IsEnabled { get; } public Task> GetCompatibilityStrategies() { return Task.FromResult(this.State.CompatibilityStrategies); } public Task> GetSelectorStrategies() { return Task.FromResult(this.State.VersionSelectorStrategies); } public Task GetCompatibilityStrategy() { return Task.FromResult(this.State.CompatibilityOverride); } public Task GetSelectorStrategy() { return Task.FromResult(this.State.SelectorOverride); } } } ================================================ FILE: src/Orleans.Sdk/Orleans.Sdk.csproj ================================================ Microsoft.Orleans.Sdk Microsoft Orleans meta package to bring in the base Orleans packages for all project types. $(DefaultTargetFrameworks) false true false false false MSB3277 README.md %(Identity) true true true %(Identity) true true %(Identity) true ================================================ FILE: src/Orleans.Sdk/README.md ================================================ # Microsoft Orleans SDK ## Introduction Microsoft Orleans SDK is a metapackage that includes all the necessary components to build Orleans applications. It provides both client and server functionality, making it easy to get started with Orleans without having to reference individual packages. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Sdk ``` ## Example - Creating a Grain Interface and Implementation ```csharp // Define a grain interface public interface IHelloGrain : IGrainWithStringKey { Task SayHello(string greeting); } // Implement the grain interface public class HelloGrain : Grain, IHelloGrain { public Task SayHello(string greeting) { return Task.FromResult($"Hello, {greeting}!"); } } ``` ## Example - Configuring an Orleans Application ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace ExampleGrains; // Create the host var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering(); }); // Start the host var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("World"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Getting started with Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/tutorials-and-samples/tutorial-1) - [Grains](https://learn.microsoft.com/en-us/dotnet/orleans/grains/) - [Hosting Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/host/) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Sdk/build/Microsoft.Orleans.Sdk.targets ================================================ ================================================ FILE: src/Orleans.Sdk/buildMultiTargeting/Microsoft.Orleans.Sdk.targets ================================================ ================================================ FILE: src/Orleans.Sdk/buildTransitive/Microsoft.Orleans.Sdk.targets ================================================ ================================================ FILE: src/Orleans.Serialization/Activators/DefaultActivator.cs ================================================ using System; using System.Reflection.Emit; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Activators { internal abstract class DefaultActivator : IActivator { private static readonly Func DefaultConstructorFunction = Init(); protected readonly Func Constructor = DefaultConstructorFunction; protected readonly Type Type = typeof(T); private static Func Init() { var ctor = typeof(T).GetConstructor(Type.EmptyTypes); if (ctor is null) return null; var method = new DynamicMethod(nameof(DefaultActivator), typeof(T), new[] { typeof(object) }); var il = method.GetILGenerator(); il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Ret); return (Func)method.CreateDelegate(typeof(Func)); } public abstract T Create(); } internal sealed class DefaultReferenceTypeActivator : DefaultActivator where T : class { public override T Create() => Constructor is { } ctor ? ctor() : Unsafe.As(RuntimeHelpers.GetUninitializedObject(Type)); } internal sealed class DefaultValueTypeActivator : DefaultActivator where T : struct { public override T Create() => Constructor is { } ctor ? ctor() : (T)RuntimeHelpers.GetUninitializedObject(Type); } } ================================================ FILE: src/Orleans.Serialization/Activators/IActivator.cs ================================================ namespace Orleans.Serialization.Activators { /// /// Functionality for creating object instances. /// /// The instance type which this implementation creates. public interface IActivator { /// /// Creates an instance of type . /// /// An instance of type . T Create(); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/ArrayStreamBufferWriter.cs ================================================ using System; using System.Buffers; using System.IO; namespace Orleans.Serialization.Buffers.Adaptors { /// /// An implementation of which writes to a , using an array as an intermediate buffer. /// public struct ArrayStreamBufferWriter : IBufferWriter { public const int DefaultInitialBufferSize = 256; private readonly Stream _stream; private byte[] _buffer; private int _index; /// /// Initializes a new instance of the struct. /// /// The stream. /// The size hint. public ArrayStreamBufferWriter(Stream stream, int sizeHint = 0) { if (sizeHint == 0) { sizeHint = DefaultInitialBufferSize; } _stream = stream; _buffer = new byte[sizeHint]; _index = 0; } /// public void Advance(int count) { if (count < 0) { ThrowNegativeAdvanceCount(); return; } if (_index > _buffer.Length - count) { ThrowAdvancePastCapacity(); return; } _index += count; _stream.Write(_buffer, 0, _index); _index = 0; } /// public Memory GetMemory(int sizeHint = 0) { CheckAndResizeBuffer(sizeHint); return _buffer.AsMemory(_index); } /// public Span GetSpan(int sizeHint = 0) { CheckAndResizeBuffer(sizeHint); return _buffer.AsSpan(_index); } private void CheckAndResizeBuffer(int sizeHint) { if (sizeHint < 0) { ThrowNegativeSizeHint(); return; } if (sizeHint == 0) { sizeHint = 1; } if (sizeHint > _buffer.Length - _index) { int growBy = Math.Max(sizeHint, _buffer.Length); if (_buffer.Length == 0) { growBy = Math.Max(growBy, DefaultInitialBufferSize); } int newSize = checked(_buffer.Length + growBy); Array.Resize(ref _buffer, newSize); } } private static void ThrowNegativeSizeHint() => throw new ArgumentException("Negative values are not supported", "sizeHint"); private static void ThrowNegativeAdvanceCount() => throw new ArgumentException("Negative values are not supported", "count"); private static void ThrowAdvancePastCapacity() => throw new InvalidOperationException("Cannod advance past the end of the current capacity"); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/BufferSliceReaderInput.cs ================================================ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using static Orleans.Serialization.Buffers.PooledBuffer; namespace Orleans.Serialization.Buffers.Adaptors; /// /// Input type for to support buffers. /// public struct BufferSliceReaderInput { private static readonly SequenceSegment InitialSegmentSentinel = new(); private static readonly SequenceSegment FinalSegmentSentinel = new(); private readonly BufferSlice _slice; private SequenceSegment _segment; private int _position; /// /// Initializes a new instance of the type. /// /// The underlying buffer. public BufferSliceReaderInput(in BufferSlice slice) { _slice = slice; _segment = InitialSegmentSentinel; } internal readonly PooledBuffer Buffer => _slice._buffer; internal readonly int Position => _position; internal readonly int Offset => _slice._offset; internal readonly int Length => _slice._length; internal long PreviousBuffersSize; internal readonly BufferSliceReaderInput ForkFrom(int position) { var sliced = _slice.Slice(position); return new BufferSliceReaderInput(in sliced); } internal ReadOnlySpan GetNext() { if (ReferenceEquals(_segment, InitialSegmentSentinel)) { _segment = _slice._buffer.First; } var endPosition = Offset + Length; while (_segment != null && _segment != FinalSegmentSentinel) { var segment = _segment.CommittedMemory.Span; // Find the starting segment and the offset to copy from. int segmentOffset; if (_position < Offset) { if (_position + segment.Length <= Offset) { // Start is in a subsequent segment _position += segment.Length; _segment = _segment.Next as SequenceSegment; continue; } else { // Start is in this segment segmentOffset = Offset - _position; } } else { segmentOffset = 0; } var segmentLength = Math.Min(segment.Length - segmentOffset, endPosition - (_position + segmentOffset)); if (segmentLength == 0) { ThrowInsufficientData(); return default; } var result = segment.Slice(segmentOffset, segmentLength); _position += segmentOffset + segmentLength; _segment = _segment.Next as SequenceSegment; return result; } if (_segment != FinalSegmentSentinel && Buffer.CurrentPosition > 0 && Buffer.WriteHead is { } head && _position < endPosition) { var finalOffset = Math.Max(Offset - _position, 0); var finalLength = Math.Min(Buffer.CurrentPosition, endPosition - (_position + finalOffset)); if (finalLength == 0) { ThrowInsufficientData(); return default; } var result = head.Array.AsSpan(finalOffset, finalLength); _position += finalOffset + finalLength; Debug.Assert(_position == endPosition); _segment = FinalSegmentSentinel; return result; } ThrowInsufficientData(); return default; } [DoesNotReturn] private static void ThrowInsufficientData() => throw new InvalidOperationException("Insufficient data present in buffer."); } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/BufferWriterBox.cs ================================================ using System; using System.Buffers; namespace Orleans.Serialization.Buffers.Adaptors { /// /// A implementation which boxes another buffer writer. /// public class BufferWriterBox : IBufferWriter where TBufferWriter : struct, IBufferWriter { private TBufferWriter _bufferWriter; public BufferWriterBox(TBufferWriter bufferWriter) { _bufferWriter = bufferWriter; } /// /// Gets a reference to the underlying buffer writer. /// public ref TBufferWriter Value => ref _bufferWriter; /// public void Advance(int count) => _bufferWriter.Advance(count); /// public Memory GetMemory(int sizeHint = 0) => _bufferWriter.GetMemory(sizeHint); /// public Span GetSpan(int sizeHint = 0) => _bufferWriter.GetSpan(sizeHint); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/BufferWriterExtensions.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Buffers.Adaptors; internal static class BufferWriterExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(ref TBufferWriter writer, ReadOnlySpan value) where TBufferWriter : IBufferWriter { var destination = writer.GetSpan(); // Fast path, try copying to the available memory directly if (value.Length <= destination.Length) { value.CopyTo(destination); writer.Advance(value.Length); } else { WriteMultiSegment(ref writer, value, destination); } } private static void WriteMultiSegment(ref TBufferWriter writer, in ReadOnlySpan source, Span destination) where TBufferWriter : IBufferWriter { var input = source; while (true) { var writeSize = Math.Min(destination.Length, input.Length); input[..writeSize].CopyTo(destination); writer.Advance(writeSize); input = input[writeSize..]; if (input.Length > 0) { destination = writer.GetSpan(); continue; } return; } } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/MemoryBufferWriter.cs ================================================ using System; using System.Buffers; namespace Orleans.Serialization.Buffers.Adaptors { /// /// A implementation for /// public struct MemoryBufferWriter : IBufferWriter { private readonly Memory _buffer; private int _bytesWritten; /// /// Initializes a new instance of the struct. /// /// The buffer. internal MemoryBufferWriter(Memory buffer) { _buffer = buffer; _bytesWritten = 0; } /// /// Gets the number of bytes written. /// /// The number of bytes written. public readonly int BytesWritten => _bytesWritten; /// public void Advance(int count) { if (_bytesWritten > _buffer.Length) { ThrowInvalidCount(); static void ThrowInvalidCount() => throw new InvalidOperationException("Cannot advance past the end of the buffer"); } _bytesWritten += count; } /// public Memory GetMemory(int sizeHint = 0) { if (_bytesWritten + sizeHint >= _buffer.Length) { ThrowInsufficientCapacity(sizeHint); } return _buffer[_bytesWritten..]; } /// public Span GetSpan(int sizeHint = 0) { if (_bytesWritten + sizeHint >= _buffer.Length) { ThrowInsufficientCapacity(sizeHint); } return _buffer.Span[_bytesWritten..]; } private void ThrowInsufficientCapacity(int sizeHint) => throw new InvalidOperationException($"Insufficient capacity to perform the requested operation. Buffer size is {_buffer.Length}. Current length is {_bytesWritten} and requested size increase is {sizeHint}"); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/MemoryStreamBufferWriter.cs ================================================ using System; using System.Buffers; using System.IO; namespace Orleans.Serialization.Buffers.Adaptors { /// /// An implementation of which writes to a . /// public readonly struct MemoryStreamBufferWriter : IBufferWriter { private readonly MemoryStream _stream; private const int MinRequestSize = 256; /// /// Initializes a new instance of the struct. /// /// The stream. public MemoryStreamBufferWriter(MemoryStream stream) { _stream = stream; } /// public void Advance(int count) { _stream.Position += count; } /// public Memory GetMemory(int sizeHint = 0) { if (sizeHint < MinRequestSize) { sizeHint = MinRequestSize; } if (_stream.Capacity - _stream.Position < sizeHint) { _stream.Capacity += sizeHint; _stream.SetLength(_stream.Capacity); } return _stream.GetBuffer().AsMemory((int)_stream.Position); } /// public Span GetSpan(int sizeHint = 0) { if (sizeHint < MinRequestSize) { sizeHint = MinRequestSize; } if (_stream.Capacity - _stream.Position < sizeHint) { _stream.Capacity += sizeHint; _stream.SetLength(_stream.Capacity); } return _stream.GetBuffer().AsSpan((int)_stream.Position); } } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/PooledBufferStream.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.IO; using Microsoft.Extensions.ObjectPool; namespace Orleans.Serialization.Buffers.Adaptors { /// /// A implementation which boxes another buffer writer. /// public sealed class PooledBufferStream : Stream { private static readonly ObjectPool StreamPool = ObjectPool.Create(new PooledStreamPolicy()); private static readonly ArrayPool Pool = ArrayPool.Shared; private readonly List _segments; private readonly int _minAllocationSize; private long _length; private long _capacity; /// /// Initializes a new instance of the class. /// public PooledBufferStream() : this(0) { } /// /// Initializes a new instance of the class. /// /// Minimum size of the allocation. public PooledBufferStream(int minAllocationSize = 0) { _segments = new(); _length = 0; _minAllocationSize = minAllocationSize > 0 ? minAllocationSize : 4096; } /// /// Gets an object from the pool if one is available, otherwise creates one. /// /// A . public static PooledBufferStream Rent() => StreamPool.Get(); /// /// Return an object to the pool. /// public static void Return(PooledBufferStream stream) { #if NET5_0_OR_GREATER ArgumentNullException.ThrowIfNull(stream); #else if (stream is null) { throw new ArgumentNullException(nameof(stream)); } #endif StreamPool.Return(stream); } /// Gets the total length which has been written. public override long Length => _length; /// /// Returns the data which has been written as an array. /// /// The data which has been written. public byte[] ToArray() { var result = new byte[_length]; var resultSpan = result.AsSpan(); var remaining = _length; foreach (var buffer in _segments) { var copyLength = (int)Math.Min(buffer.Length, remaining); buffer.AsSpan(0, copyLength).CopyTo(resultSpan); resultSpan = resultSpan[copyLength..]; remaining -= copyLength; } return result; } /// Copies the contents of this writer to another writer. public void CopyTo(ref Writer writer) where TBufferWriter : IBufferWriter { var remaining = _length; foreach (var buffer in _segments) { var copyLength = (int)Math.Min(buffer.Length, remaining); writer.Write(buffer.AsSpan(0, copyLength)); remaining -= copyLength; } } public void Reset() { foreach (var buffer in _segments) { Pool.Return(buffer); } _segments.Clear(); _length = 0; _capacity = 0; Position = 0; } /// /// Returns a new which must be used and returned before resetting this instance via the method. /// public ReadOnlySequence RentReadOnlySequence() { if (_length == 0) { return ReadOnlySequence.Empty; } if (_segments.Count == 1) { var buffer = _segments[0]; return new ReadOnlySequence(buffer, 0, (int)Math.Min(buffer.Length, _length)); } var runningIndex = 0L; var firstSegment = default(BufferSegment); var previousSegment = default(BufferSegment); var remaining = _length; foreach (var buffer in _segments) { var segment = BufferSegment.Pool.Get(); var segmentLength = Math.Min(buffer.Length, remaining); segment.Initialize(new ReadOnlyMemory(buffer, 0, (int)segmentLength), runningIndex); runningIndex += segmentLength; remaining -= segmentLength; previousSegment?.SetNext(segment); firstSegment ??= segment; previousSegment = segment; } return new ReadOnlySequence(firstSegment, 0, previousSegment, previousSegment.Memory.Length); } /// /// Returns a previously rented by ; /// public void ReturnReadOnlySequence(in ReadOnlySequence sequence) { if (sequence.Start.GetObject() is not BufferSegment segment) { return; } while (segment is not null) { var next = segment.Next as BufferSegment; BufferSegment.Pool.Return(segment); segment = next; } } public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => true; public override long Position { get; set; } public override long Seek(long offset, SeekOrigin origin) { var newPosition = origin switch { SeekOrigin.Begin => offset, SeekOrigin.Current => Position + offset, SeekOrigin.End => Length - offset, _ => throw new ArgumentOutOfRangeException(nameof(origin)) }; if (newPosition < 0) throw new InvalidOperationException("Attempted to seek past beginning of stream"); if (newPosition > Length) throw new InvalidOperationException("Attempted to seek past end of stream"); Position = newPosition; return newPosition; } public override int Read(byte[] buffer, int offset, int count) { #if NET5_0_OR_GREATER ArgumentNullException.ThrowIfNull(buffer); #else if (buffer is null) { throw new ArgumentNullException(nameof(buffer)); } #endif var destination = buffer.AsSpan(offset, count); FindCurrentSegment(out var segmentIndex, out var indexIntoSegment); if (segmentIndex < 0) { return 0; } var totalRead = 0; var remaining = (int)Math.Min(count, _length - Position); while (remaining > 0 && segmentIndex < _segments.Count) { var segment = _segments[segmentIndex]; var readLength = Math.Min(remaining, Math.Min(segment.Length - indexIntoSegment, destination.Length)); segment.AsSpan(indexIntoSegment, readLength).CopyTo(destination); destination = destination[readLength..]; remaining -= readLength; totalRead += readLength; ++segmentIndex; indexIntoSegment = 0; } Position += totalRead; return totalRead; } public override void SetLength(long value) { if (value == Length) { // Do nothing return; } else if (value == 0) { Reset(); } else { if (value < Length) { // Truncate/remove already-written buffers var excess = Length - value; while (excess > 0) { var lastSegment = _segments[^1]; if (excess >= lastSegment.Length) { // Remove the entire segment. excess -= lastSegment.Length; _segments.RemoveAt(_segments.Count - 1); _capacity -= lastSegment.Length; Pool.Return(lastSegment); } else { // Partially truncate the last segment - we don't need to do anything // since we're just reducing the logical length, not the physical buffer break; } } } else { // Append empty buffers var deficit = value - Length; while (deficit > 0) { var array = Grow(); var length = Math.Min(deficit, array.Length); deficit -= length; } } _length = value; Position = Math.Min(Position, Length); } } private byte[] Grow() { var array = Pool.Rent(_minAllocationSize); _segments.Add(array); _capacity += array.Length; return array; } public override void Write(byte[] buffer, int offset, int count) { #if NET5_0_OR_GREATER ArgumentNullException.ThrowIfNull(buffer); #else if (buffer is null) { throw new ArgumentNullException(nameof(buffer)); } #endif var data = new ReadOnlyMemory(buffer, offset, count); if (Position < Length) { FindCurrentSegment(out var segmentIndex, out var indexIntoSegment); while (Position < Length && data.Length > 0) { var writeHead = _segments[segmentIndex].AsMemory(indexIntoSegment); var writeLength = Math.Min(writeHead.Length, data.Length); // Copy the data and update the input data[..writeLength].CopyTo(writeHead); data = data[writeLength..]; // Update the cursor Position += writeLength; // Advance to the next segment; ++segmentIndex; indexIntoSegment = 0; } } // Append any remaining data. Append(ref data); } private void FindCurrentSegment(out int segmentIndex, out int indexIntoSegment) { segmentIndex = -1; indexIntoSegment = -1; var segmentStartPos = 0; for (var i = 0; i < _segments.Count; i++) { var currentSegment = _segments[i]; // Check if this segment contains the current position. if (segmentStartPos + currentSegment.Length > Position) { segmentIndex = i; indexIntoSegment = (int)(Position - segmentStartPos); break; } segmentStartPos += currentSegment.Length; } } private void Append(ref ReadOnlyMemory data) { while (data.Length > 0) { if (_length == _capacity) { Grow(); } var writeHead = GetWriteHead(); var writeLength = Math.Min(writeHead.Length, data.Length); data[..writeLength].CopyTo(writeHead); data = data[writeLength..]; _length += writeLength; } Position = _length; } public override void Flush() { } private Memory GetWriteHead() => _segments[^1].AsMemory((int)(_segments[^1].Length - (_capacity - _length))); private sealed class PooledStreamPolicy : PooledObjectPolicy { public override PooledBufferStream Create() => new(); public override bool Return(PooledBufferStream obj) { obj.Reset(); return true; } } // Internal for testing purposes only. internal sealed class BufferSegment : ReadOnlySequenceSegment { public static readonly ObjectPool Pool = ObjectPool.Create(new SegmentPoolPolicy()); public void Initialize(ReadOnlyMemory memory, long runningIndex) { Memory = memory; RunningIndex = runningIndex; } public void SetNext(BufferSegment next) => Next = next; public void Reset() { Memory = default; RunningIndex = default; Next = default; } private sealed class SegmentPoolPolicy : PooledObjectPolicy { public override BufferSegment Create() => new(); public override bool Return(BufferSegment obj) { obj.Reset(); return true; } } } } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/PoolingStreamBufferWriter.cs ================================================ using System.Buffers; namespace Orleans.Serialization.Buffers.Adaptors { /// /// An implementation of for writing to a , using pooled arrays as an intermediate buffer. /// public struct PoolingStreamBufferWriter : IBufferWriter, IDisposable { private readonly Stream _stream; private byte[] _buffer; private const int MinRequestSize = 256; /// /// Initializes a new instance of the struct. /// /// The stream. /// The size hint. internal PoolingStreamBufferWriter(Stream stream, int sizeHint) { _stream = stream; _buffer = ArrayPool.Shared.Rent(Math.Max(sizeHint, MinRequestSize)); } /// public void Advance(int count) => _stream.Write(_buffer, 0, count); /// public Memory GetMemory(int sizeHint = 0) => sizeHint <= _buffer.Length ? _buffer : Resize(sizeHint); /// public Span GetSpan(int sizeHint = 0) => sizeHint <= _buffer.Length ? _buffer : Resize(sizeHint); private byte[] Resize(int sizeHint) { var newBuffer = ArrayPool.Shared.Rent(sizeHint); ArrayPool.Shared.Return(_buffer); return _buffer = newBuffer; } /// public void Dispose() { if (_buffer is { } buf) { _buffer = null; ArrayPool.Shared.Return(buf); } } } } ================================================ FILE: src/Orleans.Serialization/Buffers/Adaptors/SpanBufferWriter.cs ================================================ using System; using System.Buffers; namespace Orleans.Serialization.Buffers.Adaptors { /// /// A special-purpose implementation for supporting in . /// public struct SpanBufferWriter : IBufferWriter { private readonly int _maxLength; private int _bytesWritten; /// /// Initializes a new instance of the struct. /// /// The buffer. internal SpanBufferWriter(Span buffer) { _maxLength = buffer.Length; _bytesWritten = 0; } /// /// Gets the number of bytes written. /// /// The number of bytes written. public readonly int BytesWritten => _bytesWritten; /// public void Advance(int count) => _bytesWritten += count; /// public readonly Memory GetMemory(int sizeHint = 0) => throw GetException(sizeHint); /// public readonly Span GetSpan(int sizeHint = 0) => throw GetException(sizeHint); private readonly Exception GetException(int sizeHint) { return _bytesWritten + sizeHint > _maxLength ? new InvalidOperationException($"Insufficient capacity to perform the requested operation. Buffer size is {_maxLength}. Current length is {_bytesWritten} and requested size increase is {sizeHint}") : new NotSupportedException("Method is not supported on this instance"); } } } ================================================ FILE: src/Orleans.Serialization/Buffers/ArcBufferWriter.cs ================================================ #nullable enable using System; using System.Buffers; using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Collections.Generic; using System.Collections; #if NET6_0_OR_GREATER using System.Numerics; #else using Orleans.Serialization.Utilities; #endif namespace Orleans.Serialization.Buffers; /// /// A implementation implemented using pooled arrays which is specialized for creating instances. /// [StructLayout(LayoutKind.Auto)] [Immutable] public sealed class ArcBufferWriter : IBufferWriter, IDisposable { // The first page. This is the page which consumers will consume from. // This may be equal to the current page, or it may be a previous page. private ArcBufferPage _readPage; // The current page. This is the page which will be written to when the next write occurs. private ArcBufferPage _writePage; // The current page. This is the last page which was allocated. In the linked list formed by the pages, _first <= _current <= _tail. private ArcBufferPage _tail; // The offset into the first page which has been consumed already. When this reaches the end of the page, the page can be unpinned. private int _readIndex; // The total length of the buffer. private int _totalLength; // Indicates whether the writer has been disposed. private bool _disposed; /// /// Gets the minimum page size. /// public const int MinimumPageSize = ArcBufferPagePool.MinimumPageSize; /// /// Initializes a new instance of the struct. /// public ArcBufferWriter() { _readPage = _writePage = _tail = ArcBufferPagePool.Shared.Rent(); Debug.Assert(_readPage.ReferenceCount == 0); _readPage.Pin(_readPage.Version); } /// /// Gets the number of unconsumed bytes. /// public int Length { get { ThrowIfDisposed(); return _totalLength - _readIndex; } } /// /// Adds additional buffers to the destination list until the list has reached its capacity. /// /// The destination to add buffers to. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReplenishBuffers(List> buffers) { ThrowIfDisposed(); // Skip half-full pages in an attempt to minimize the number of buffers added to the destination // at the expense of under-utilized memory. This could be tweaked up to increase page utilization. const int MinimumUsablePageSize = MinimumPageSize / 2; while (buffers.Count < buffers.Capacity) { // Only use the current page if it is greater than the minimum "usable" page size. if (_tail.WriteCapacity > MinimumUsablePageSize) { buffers.Add(_tail.WritableArraySegment); } // Allocate a new page. AllocatePage(0); } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] void IBufferWriter.Advance(int count) => AdvanceWriter(count); /// /// Advances the writer by the specified number of bytes. /// /// The numbers of bytes to advance the writer by. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AdvanceWriter(int count) { ThrowIfDisposed(); #if NET5_0_OR_GREATER ArgumentOutOfRangeException.ThrowIfLessThan(count, 0); #else if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Length must be greater than or equal to 0."); #endif _totalLength += count; while (true) { var amount = Math.Min(_writePage.WriteCapacity, count); _writePage.Advance(amount); count -= amount; if (count == 0) { break; } var next = _writePage.Next; Debug.Assert(next is not null); _writePage = next; } } /// /// Resets this instance, returning all memory. /// public void Reset() { ThrowIfDisposed(); UnpinAll(); _totalLength = _readIndex = 0; _readPage = _writePage = _tail = ArcBufferPagePool.Shared.Rent(); Debug.Assert(_readPage.ReferenceCount == 0); _readPage.Pin(_readPage.Version); } /// public void Dispose() { if (_disposed) return; UnpinAll(); _totalLength = _readIndex = 0; _readPage = _writePage = _tail = null!; _disposed = true; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory GetMemory(int sizeHint = 0) { ThrowIfDisposed(); if (sizeHint >= _writePage.WriteCapacity) { return GetMemorySlow(sizeHint); } return _writePage.WritableMemory; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(int sizeHint = 0) { ThrowIfDisposed(); if (sizeHint >= _writePage.WriteCapacity) { return GetSpanSlow(sizeHint); } return _writePage.WritableSpan; } /// /// Attempts to read the provided number of bytes from the buffer. /// /// The destination, which may be used to hold the requested data if the data needs to be copied. /// A span of either zero length, if the data is unavailable, or at least the requested length if the data is available. public ReadOnlySpan Peek(scoped in Span destination) { ThrowIfDisposed(); // Single span. var firstSpan = _readPage.AsSpan(_readIndex, _readPage.Length - _readIndex); if (firstSpan.Length >= destination.Length) { return firstSpan; } // Multiple spans. Create a slice without pinning it, since we would be immediately unpinning it. Peek(destination); return destination; } /// Copies the contents of this writer to a span. /// This method does not advance the read cursor. public int Peek(Span output) { ThrowIfDisposed(); var bytesCopied = 0; var current = _readPage; var offset = _readIndex; while (output.Length > 0 && current != null) { var segment = current.AsSpan(offset, current.Length - offset); var copyLength = Math.Min(segment.Length, output.Length); bytesCopied += copyLength; var slice = segment[..copyLength]; slice.CopyTo(output); output = output[slice.Length..]; current = current.Next; offset = 0; } return bytesCopied; } /// /// Writes the provided sequence to this buffer. /// /// The data to write. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySequence input) { ThrowIfDisposed(); foreach (var segment in input) { Write(segment.Span); } } /// /// Writes the provided value to this buffer. /// /// The data to write. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySpan value) { ThrowIfDisposed(); var destination = GetSpan(); // Fast path, try copying to the available memory directly if (value.Length <= destination.Length) { value.CopyTo(destination); AdvanceWriter(value.Length); } else { WriteMultiSegment(value, destination); } } private void WriteMultiSegment(in ReadOnlySpan source, Span destination) { var input = source; while (true) { var writeSize = Math.Min(destination.Length, input.Length); input[..writeSize].CopyTo(destination); AdvanceWriter(writeSize); input = input[writeSize..]; if (input.Length > 0) { destination = GetSpan(); continue; } return; } } /// /// Unpins all pages. /// private void UnpinAll() { var current = _readPage; while (current != null) { var previous = current; current = previous.Next; previous.Unpin(previous.Version); } } /// /// Returns a slice of the provided length without marking the data referred to it as consumed. /// /// The number of bytes to consume. /// A slice of unconsumed data. public ArcBuffer PeekSlice(int count) { ThrowIfDisposed(); #if NET6_0_OR_GREATER ArgumentOutOfRangeException.ThrowIfLessThan(count, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(count, Length); #else if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Length must be greater than or equal to 0."); if (count > Length) throw new ArgumentOutOfRangeException(nameof(count), "Length must be less than or equal to the unconsumed length of the buffer."); #endif Debug.Assert(count >= 0); Debug.Assert(count <= Length); var result = new ArcBuffer(_readPage, token: _readPage.Version, offset: _readIndex, count); result.Pin(); return result; } /// /// Consumes a slice of the provided length. /// /// The number of bytes to consume. /// A buffer representing the consumed data. public ArcBuffer ConsumeSlice(int count) { ThrowIfDisposed(); var result = PeekSlice(count); // Advance the cursor so that subsequent slice calls will return the next slice. AdvanceReader(count); return result; } /// /// Advances the reader by the specified number of bytes. /// /// The number of bytes to advance the reader. public void AdvanceReader(int count) { ThrowIfDisposed(); Debug.Assert(count >= 0); Debug.Assert(count <= Length); _readIndex += count; // If this call would consume the entire first page and the page is not the current write page, unpin it. while (_readIndex >= _readPage.Length && _writePage != _readPage) { // Advance the consumed length. var current = _readPage; _readIndex -= current.Length; _totalLength -= current.Length; // Advance to the next page Debug.Assert(current.Next is not null); _readPage = current.Next!; // Unpin the page. current.Unpin(current.Version); } Debug.Assert(_readPage is not null); Debug.Assert(_readIndex <= _readPage.Length); } [MethodImpl(MethodImplOptions.NoInlining)] private Span GetSpanSlow(int sizeHint) => AdvanceWritePage(sizeHint).Array; [MethodImpl(MethodImplOptions.NoInlining)] private Memory GetMemorySlow(int sizeHint) => AdvanceWritePage(sizeHint).AsMemory(0); private ArcBufferPage AllocatePage(int sizeHint) { Debug.Assert(_tail.Next is null); var newBuffer = ArcBufferPagePool.Shared.Rent(sizeHint); Debug.Assert(newBuffer.ReferenceCount == 0); newBuffer.Pin(newBuffer.Version); _tail.SetNext(newBuffer, _tail.Version); return _tail = newBuffer; } private ArcBufferPage AdvanceWritePage(int sizeHint) { var next = _writePage.Next; if (next is null) { next = AllocatePage(sizeHint); } _writePage = next; return next; } private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(ArcBufferWriter)); } } internal sealed class ArcBufferPagePool { public static ArcBufferPagePool Shared { get; } = new(); public const int MinimumPageSize = 16 * 1024; private readonly ConcurrentQueue _pages = new(); private readonly ConcurrentQueue _largePages = new(); private ArcBufferPagePool() { } public ArcBufferPage Rent(int size = -1) { ArcBufferPage? block; if (size <= MinimumPageSize) { if (!_pages.TryDequeue(out block)) { block = new ArcBufferPage(size); } } else if (_largePages.TryDequeue(out block)) { block.ResizeLargeSegment(size); return block; } return block ?? new ArcBufferPage(size); } internal void Return(ArcBufferPage block) { Debug.Assert(block.IsValid); if (block.IsMinimumSize) { _pages.Enqueue(block); } else { _largePages.Enqueue(block); } } } /// /// A page of data. /// public sealed class ArcBufferPage { // The current version of the page. Each time the page is return to the pool, the version is incremented. // This helps to ensure that the page is not consumed after it has been returned to the pool. // This is a guard against certain programming bugs. private int _version; // The current reference count. This is used to ensure that a page is not returned to the pool while it is still in use. private int _refCount; internal ArcBufferPage() { Array = []; } internal ArcBufferPage(int length) { #if !NET6_0_OR_GREATER Array = null!; #endif InitializeArray(length); } public void ResizeLargeSegment(int length) { Debug.Assert(length > ArcBufferPagePool.MinimumPageSize); InitializeArray(length); } #if NET6_0_OR_GREATER [MemberNotNull(nameof(Array))] #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] private void InitializeArray(int length) { if (length <= ArcBufferPagePool.MinimumPageSize) { Debug.Assert(Array is null); #if NET6_0_OR_GREATER var array = GC.AllocateUninitializedArray(ArcBufferPagePool.MinimumPageSize, pinned: true); #else var array = new byte[ArcBufferPagePool.MinimumPageSize]; #endif Array = array; } else { // Round up to a power of two. length = (int)BitOperations.RoundUpToPowerOf2((uint)length); if (Array is not null) { // The segment has an appropriate size already. if (Array.Length == length) { return; } // The segment is being resized. ArrayPool.Shared.Return(Array); } Array = ArrayPool.Shared.Rent(length); } } /// /// Gets the array underpinning the page. /// public byte[] Array { get; private set; } /// /// Gets the number of bytes which have been written to the page. /// public int Length { get; private set; } /// /// A containing the readable bytes from this page. /// public ReadOnlySpan ReadableSpan => Array.AsSpan(0, Length); /// /// A containing the readable bytes from this page. /// public ReadOnlyMemory ReadableMemory => AsMemory(0, Length); /// /// An containing the readable bytes from this page. /// public ArraySegment ReadableArraySegment => new(Array, 0, Length); /// /// An containing the writable bytes from this page. /// public ArraySegment WritableArraySegment => new(Array, Length, Array.Length - Length); /// /// Gets the next node. /// public ArcBufferPage? Next { get; protected set; } /// /// Gets the current page version. /// public int Version => _version; /// /// Gets a value indicating whether this page is valid. /// public bool IsValid => Array is { Length: > 0 }; /// /// Gets a value indicating whether this page is equal to the minimum page size. /// public bool IsMinimumSize => Array.Length == ArcBufferPagePool.MinimumPageSize; /// /// Gets the number of bytes in the page which are available for writing. /// public int WriteCapacity => Array.Length - Length; /// /// Gets the writable memory in the page. /// public Memory WritableMemory => AsMemory(Length); /// /// Gets a span representing the writable memory in the page. /// public Span WritableSpan => AsSpan(Length); /// /// Gets the current page reference count. /// internal int ReferenceCount => _refCount; /// /// Creates a new memory region over the portion of the target page beginning at a specified position. /// /// The offset into the array to return memory from. /// The memory region. public Memory AsMemory(int offset) { #if NET6_0_OR_GREATER if (IsMinimumSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length - offset); } #endif return Array.AsMemory(offset); } /// /// Creates a new memory region over the portion of the target page beginning at a specified position with a specified length. /// /// The offset into the array that the memory region starts from. /// The length of the memory region. /// The memory region. public Memory AsMemory(int offset, int length) { #if NET6_0_OR_GREATER if (IsMinimumSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, length); } #endif return Array.AsMemory(offset, length); } /// /// Returns an array segment pointing to the underlying array, starting from the provided offset, and having the provided length. /// /// The offset into the array that the array segment starts from. /// The length of the array segment. /// The array segment. public ArraySegment AsArraySegment(int offset, int length) => new(Array, offset, length); /// /// Gets a span pointing to the underlying array, starting from the provided offset. /// /// The offset. /// The span. public Span AsSpan(int offset) => Array.AsSpan(offset); /// /// Gets a span pointing to the underlying array, starting from the provided offset. /// /// The offset. /// The length. /// The span. public Span AsSpan(int offset, int length) => Array.AsSpan(offset, length); /// /// Increases the number of bytes written to the page by the provided amount. /// /// The number of bytes to increase the length of this page by. public void Advance(int bytes) { Debug.Assert(bytes >= 0, "Advance called with negative bytes"); Length += bytes; Debug.Assert(Length <= Array.Length); } /// /// Sets the next page in the sequence. /// /// The next page in the sequence. /// The token, which must match the page's for this operation to be allowed. public void SetNext(ArcBufferPage next, int token) { Debug.Assert(Next is null); CheckValidity(token); Debug.Assert(next is not null, "SetNext called with null next page"); Debug.Assert(next != this, "SetNext called with self as next page"); Next = next; } /// /// Pins this page to prevent it from being returned to the page pool. /// /// The token, which must match the page's for this operation to be allowed. public void Pin(int token) { if (token != _version) { ThrowInvalidVersion(); } Interlocked.Increment(ref _refCount); } /// /// Unpins this page, allowing it to be returned to the page pool. /// /// The token, which must match the page's for this operation to be allowed. public void Unpin(int token) { CheckValidity(token); if (Interlocked.Decrement(ref _refCount) == 0) { Return(); } } private void Return() { Debug.Assert(_refCount == 0); Length = 0; Next = default; Interlocked.Increment(ref _version); ArcBufferPagePool.Shared.Return(this); } /// /// Throws if the provided does not match the page's . /// /// The token, which must match the page's . public void CheckValidity(int token) { if (token != _version) { ThrowInvalidVersion(); } if (_refCount <= 0) { ThrowAccessViolation(); } } [DoesNotReturn] private static void ThrowInvalidVersion() => throw new InvalidOperationException("An invalid token was provided when attempting to perform an operation on this page."); [DoesNotReturn] private static void ThrowAccessViolation() => throw new InvalidOperationException("An attempt was made to access a page with an invalid reference count."); } /// /// Provides reader access to an . /// /// The writer. public readonly struct ArcBufferReader(ArcBufferWriter writer) { /// /// Gets the number of unconsumed bytes. /// public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => writer.Length; } /// /// Attempts to read the provided number of bytes from the buffer. /// /// The destination, which may be used to hold the requested data if the data needs to be copied. /// A span of either zero length, if the data is unavailable, or the requested length if the data is available. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Peek(scoped in Span destination) => writer.Peek(in destination); /// /// Returns a slice of the provided length without marking the data referred to it as consumed. /// /// The number of bytes to consume. /// A slice of unconsumed data. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArcBuffer PeekSlice(int count) => writer.PeekSlice(count); /// /// Consumes a slice of the provided length. /// /// The number of bytes to consume. /// A buffer representing the consumed data. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArcBuffer ConsumeSlice(int count) => writer.ConsumeSlice(count); /// /// Consumes the amount of data present in the span. /// /// public void Consume(Span output) { var count = writer.Peek(output); if (count != output.Length) { throw new InvalidOperationException("Attempted to consume more data than is available."); } writer.AdvanceReader(count); } public void Skip(int count) { writer.AdvanceReader(count); } } /// /// Represents a slice of a . /// /// /// Initializes a new instance of the type. /// /// The first page in the sequence. /// The token of the first page in the sequence. /// The offset into the buffer at which this slice begins. /// The length of this slice. public struct ArcBuffer(ArcBufferPage first, int token, int offset, int length) : IDisposable { /// /// Gets the token of the first page pointed to by this slice. /// private int _firstPageToken = token; /// /// Gets the first page. /// public readonly ArcBufferPage First = first; /// /// Gets the offset into the first page at which this slice begins. /// public readonly int Offset = offset; /// /// Gets the length of this sequence. /// public readonly int Length = length; /// Copies the contents of this writer to a span. public readonly int CopyTo(Span output) { CheckValidity(); if (output.Length < Length) { throw new ArgumentException("Destination span is not large enough to hold the buffer contents.", nameof(output)); } var copied = 0; foreach (var span in this) { var slice = span[..Math.Min(span.Length, output.Length)]; slice.CopyTo(output); output = output[slice.Length..]; copied += slice.Length; } return copied; } /// Copies the contents of this writer to a pooled buffer. public readonly void CopyTo(ArcBufferWriter output) { CheckValidity(); foreach (var span in this) { output.Write(span); } } /// Copies the contents of this writer to a buffer writer. public readonly void CopyTo(ref TBufferWriter output) where TBufferWriter : IBufferWriter { CheckValidity(); foreach (var span in this) { Write(ref output, span); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Write(ref TBufferWriter writer, ReadOnlySpan value) where TBufferWriter : IBufferWriter { var destination = writer.GetSpan(); // Fast path, try copying to the available memory directly if (value.Length <= destination.Length) { value.CopyTo(destination); writer.Advance(value.Length); } else { WriteMultiSegment(ref writer, value, destination); } } private static void WriteMultiSegment(ref TBufferWriter writer, in ReadOnlySpan source, Span destination) where TBufferWriter : IBufferWriter { var input = source; while (true) { var writeSize = Math.Min(destination.Length, input.Length); input[..writeSize].CopyTo(destination); writer.Advance(writeSize); input = input[writeSize..]; if (input.Length > 0) { destination = writer.GetSpan(); if (destination.IsEmpty) { ThrowInsufficientSpaceException(); } continue; } return; } } [DoesNotReturn] private static void ThrowInsufficientSpaceException() => throw new InvalidOperationException("Insufficient capacity in provided buffer"); /// /// Returns a new which must not be accessed after disposing this instance. /// public readonly ReadOnlySequence AsReadOnlySequence() { var runningIndex = 0L; ReadOnlySequenceSegment? first = null; ReadOnlySequenceSegment? previous = null; var endIndex = 0; foreach (var memory in MemorySegments) { var current = new ReadOnlySequenceSegment(memory, runningIndex); first ??= current; endIndex = memory.Length; runningIndex += endIndex; previous?.SetNext(current); previous = current; } if (first is null) { return ReadOnlySequence.Empty; } Debug.Assert(first is not null); Debug.Assert(previous is not null); if (previous == first) { return new ReadOnlySequence(first.Memory); } return new ReadOnlySequence(first, 0, previous, endIndex); } /// /// Returns the data which has been written as an array. /// /// The data which has been written. public readonly byte[] ToArray() { CheckValidity(); var result = new byte[Length]; CopyTo(result); return result; } /// /// Throws if the buffer it no longer valid. /// private readonly void CheckValidity() => First.CheckValidity(_firstPageToken); public readonly ArcBuffer Slice(int offset) => Slice(offset, Length - offset); public readonly ArcBuffer Slice(int offset, int length) { var result = UnsafeSlice(offset, length); result.Pin(); return result; } public readonly ArcBuffer UnsafeSlice(int offset, int length) { #if NET6_0_OR_GREATER ArgumentOutOfRangeException.ThrowIfLessThan(length, 0); ArgumentOutOfRangeException.ThrowIfLessThan(offset, 0); ArgumentOutOfRangeException.ThrowIfGreaterThan(length + offset, Length); #else if (length < 0) throw new ArgumentOutOfRangeException(nameof(length), "Length must be greater than or equal to 0."); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be greater than or equal to 0."); if (length + offset > Length) throw new ArgumentOutOfRangeException($"{nameof(length)} + {nameof(offset)}", "Length plus offset must be less than or equal to the length of the buffer."); #endif CheckValidity(); Debug.Assert(offset >= 0); Debug.Assert(length >= 0); Debug.Assert(offset + length <= Length); ArcBuffer result; // Navigate to the offset page & calculate the offset into the page. if (Offset + offset < First.Length || length == 0) { // The slice starts within this page. result = new ArcBuffer(First, token: _firstPageToken, Offset + offset, length); } else { // The slice starts within a subsequent page. // Account for the first page, then navigate to the page which the offset falls in. offset -= First.Length - Offset; var page = First.Next; Debug.Assert(page is not null); while (offset >= page.Length) { offset -= page.Length; page = page.Next; Debug.Assert(page is not null); } result = new ArcBuffer(page, token: page.Version, offset, length); } return result; } /// /// Pins this slice, preventing the referenced pages from being returned to the pool. /// public readonly void Pin() { CheckValidity(); var pageEnumerator = Pages.GetEnumerator(); if (pageEnumerator.MoveNext()) { var page = pageEnumerator.Current!; page.Pin(_firstPageToken); } while (pageEnumerator.MoveNext()) { var page = pageEnumerator.Current!; page.Pin(page.Version); } } /// /// Unpins this slice, allowing the referenced pages to be returned to the pool. /// public void Unpin() { var pageEnumerator = Pages.GetEnumerator(); if (pageEnumerator.MoveNext()) { var page = pageEnumerator.Current!; page.Unpin(_firstPageToken); } while (pageEnumerator.MoveNext()) { var page = pageEnumerator.Current!; page.Unpin(page.Version); } _firstPageToken = -1; } /// public void Dispose() { if (_firstPageToken == -1) { // Already disposed. return; } Unpin(); } /// /// Returns an enumerator which can be used to enumerate the span segments referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly SpanEnumerator GetEnumerator() => new(this); /// /// Returns an enumerator which can be used to enumerate the pages referenced by this instance. /// /// An enumerator for the data contained in this instance. internal readonly PageEnumerator Pages => new(this); /// /// Returns an enumerator which can be used to enumerate the pages referenced by this instance. /// /// An enumerator for the data contained in this instance. internal readonly PageSegmentEnumerator PageSegments => new(this); /// /// Returns an enumerator which can be used to enumerate the span segments referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly SpanEnumerator SpanSegments => new(this); /// /// Returns an enumerator which can be used to enumerate the memory segments referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly MemoryEnumerator MemorySegments => new(this); /// /// Returns an enumerator which can be used to enumerate the array segments referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly ArraySegmentEnumerator ArraySegments => new(this); /// /// Defines a region of data within a page. /// public readonly struct PageSegment(ArcBufferPage page, int offset, int length) { /// /// Gets the page which this segment refers to. /// public readonly ArcBufferPage Page = page; /// /// Gets the offset into the page at which this region begins. /// public readonly int Offset = offset; /// /// Gets the length of this region. /// public readonly int Length = length; /// /// Gets a representation of this region. /// public readonly ReadOnlySpan Span => Page.AsSpan(Offset, Length); /// /// Gets a representation of this region. /// public readonly ReadOnlyMemory Memory => Page.AsMemory(Offset, Length); /// /// Gets an representation of this region. /// public readonly ArraySegment ArraySegment => Page.AsArraySegment(Offset, Length); } /// /// Enumerates over page segments in a . /// /// /// Initializes a new instance of the type. /// /// The buffer to enumerate. internal struct PageSegmentEnumerator(ArcBuffer slice) : IEnumerable, IEnumerator { internal readonly ArcBuffer Slice = slice; private int _position; private ArcBufferPage? _page = slice.Length > 0 ? slice.First : null; internal readonly ArcBufferPage First => Slice.First; internal readonly int Offset => Slice.Offset; internal readonly int Length => Slice.Length; /// /// Gets this instance as an enumerator. /// /// This instance. public readonly PageSegmentEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public PageSegment Current { get; private set; } /// readonly object? IEnumerator.Current => Current; /// /// Gets a value indicating whether enumeration has completed. /// public readonly bool IsCompleted => _page is null || _position == Length; /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { Debug.Assert(_position <= Length, "Enumerator position exceeds slice length"); if (_page is null || _position == Length) { Current = default; Debug.Assert(_position == Length, "Enumerator ended before reaching full length"); return false; } if (_page == First) { Debug.Assert(_position == 0); Slice.CheckValidity(); var offset = Offset; var length = Math.Min(Length, _page.Length - offset); Debug.Assert(length >= 0, "Calculated negative length for first segment"); _position += length; Current = new PageSegment(_page, offset, length); _page = _page.Next; return true; } { var length = Math.Min(Length - _position, _page.Length); Debug.Assert(length >= 0, "Calculated negative length for subsequent segment"); _position += length; Current = new PageSegment(_page, 0, length); _page = _page.Next; return true; } } /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// void IEnumerator.Reset() { _position = 0; _page = Slice.Length > 0 ? Slice.First : null; Current = default; } /// readonly void IDisposable.Dispose() { } } /// /// Enumerates over pages in a . /// /// /// Initializes a new instance of the type. /// /// The slice to enumerate. internal struct PageEnumerator(ArcBuffer slice) : IEnumerable, IEnumerator { private PageSegmentEnumerator _enumerator = slice.PageSegments; /// /// Gets this instance as an enumerator. /// /// This instance. public readonly PageEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ArcBufferPage? Current { get; private set; } /// readonly object? IEnumerator.Current => Current; /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (_enumerator.MoveNext()) { Current = _enumerator.Current.Page; return true; } Current = default; return false; } /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// void IEnumerator.Reset() { _enumerator = _enumerator.Slice.PageSegments; Current = default; } /// readonly void IDisposable.Dispose() { } } /// /// Enumerates over spans of bytes in a . /// /// /// Initializes a new instance of the type. /// /// The slice to enumerate. public ref struct SpanEnumerator(ArcBuffer slice) { private PageSegmentEnumerator _enumerator = slice.PageSegments; /// /// Gets this instance as an enumerator. /// /// This instance. public readonly SpanEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ReadOnlySpan Current { get; private set; } /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (_enumerator.MoveNext()) { Current = _enumerator.Current.Span; return true; } Current = default; return false; } } /// /// Enumerates over sequences of bytes in a . /// /// /// Initializes a new instance of the type. /// /// The slice to enumerate. public struct MemoryEnumerator(ArcBuffer slice) : IEnumerable>, IEnumerator> { private PageSegmentEnumerator _enumerator = slice.PageSegments; /// /// Gets this instance as an enumerator. /// /// This instance. public readonly MemoryEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ReadOnlyMemory Current { get; private set; } /// readonly object? IEnumerator.Current => Current; /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (_enumerator.MoveNext()) { Current = _enumerator.Current.Memory; return true; } Current = default; return false; } /// readonly IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// void IEnumerator.Reset() { _enumerator = _enumerator.Slice.PageSegments; Current = default; } /// readonly void IDisposable.Dispose() { } } /// /// Enumerates over array segments in a . /// /// /// Initializes a new instance of the type. /// /// The slice to enumerate. public struct ArraySegmentEnumerator(ArcBuffer slice) : IEnumerable>, IEnumerator> { private PageSegmentEnumerator _enumerator = slice.PageSegments; /// /// Gets this instance as an enumerator. /// /// This instance. public readonly ArraySegmentEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ArraySegment Current { get; private set; } /// readonly object? IEnumerator.Current => Current; /// /// Gets a value indicating whether enumeration has completed. /// public readonly bool IsCompleted => _enumerator.IsCompleted; /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (_enumerator.MoveNext()) { Current = _enumerator.Current.ArraySegment; return true; } Current = default; return false; } /// readonly IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// void IEnumerator.Reset() { _enumerator = _enumerator.Slice.PageSegments; Current = default; } /// readonly void IDisposable.Dispose() { } } private sealed class ReadOnlySequenceSegment : ReadOnlySequenceSegment { public ReadOnlySequenceSegment(ReadOnlyMemory memory, long runningIndex) { Memory = memory; RunningIndex = runningIndex; } public void SetNext(ReadOnlySequenceSegment next) { Debug.Assert(Next is null); Next = next; } } } ================================================ FILE: src/Orleans.Serialization/Buffers/BufferWriterExtensions.cs ================================================ using Orleans.Serialization.Session; using System; using System.Buffers; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Buffers { /// /// Extensions for working with implementations. /// public static class BufferWriterExtensions { /// /// Creates a instance for the provided buffer. /// /// The type of the buffer writer. /// The buffer. /// The session. /// A new writer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer CreateWriter(this TBufferWriter buffer, SerializerSession session) where TBufferWriter : IBufferWriter { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(session); #else if (session is null) throw new ArgumentNullException(nameof(session)); #endif return Writer.Create(buffer, session); } } } ================================================ FILE: src/Orleans.Serialization/Buffers/PooledBuffer.cs ================================================ using System; using System.Buffers; using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if NET6_0_OR_GREATER using System.Numerics; #else using Orleans.Serialization.Utilities; #endif namespace Orleans.Serialization.Buffers; /// /// A implementation implemented using pooled arrays which is specialized for creating instances. /// [StructLayout(LayoutKind.Auto)] [Immutable] public partial struct PooledBuffer : IBufferWriter, IDisposable { internal SequenceSegment First; internal SequenceSegment Last; internal SequenceSegment WriteHead; internal int TotalLength; internal int CurrentPosition; /// /// Initializes a new instance of the struct. /// public PooledBuffer() { First = Last = null; WriteHead = null; TotalLength = 0; CurrentPosition = 0; } /// Gets the total length which has been written. public readonly int Length => TotalLength + CurrentPosition; /// /// Returns the data which has been written as an array. /// /// The data which has been written. public readonly byte[] ToArray() { var result = new byte[Length]; var resultSpan = result.AsSpan(); var current = First; while (current != null) { var span = current.CommittedMemory.Span; span.CopyTo(resultSpan); resultSpan = resultSpan.Slice(span.Length); current = current.Next as SequenceSegment; } if (CurrentPosition > 0) { WriteHead.Array.AsSpan(0, CurrentPosition).CopyTo(resultSpan); } return result; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int bytes) { if (WriteHead is null || CurrentPosition + bytes > WriteHead.Array.Length) { ThrowInvalidOperation(); } CurrentPosition += bytes; static void ThrowInvalidOperation() => throw new InvalidOperationException("Attempted to advance past the end of a buffer."); } /// /// Resets this instance, returning all memory. /// public void Reset() { var current = First; while (current != null) { var previous = current; current = previous.Next as SequenceSegment; previous.Return(); Debug.Assert(current == null || current != WriteHead); } WriteHead?.Return(); First = Last = WriteHead = null; CurrentPosition = TotalLength = 0; } /// public void Dispose() => Reset(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory GetMemory(int sizeHint = 0) { if (WriteHead is null || sizeHint >= WriteHead.Array.Length - CurrentPosition) { return GetMemorySlow(sizeHint); } return WriteHead.AsMemory(CurrentPosition); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(int sizeHint = 0) { if (WriteHead is { Array: var head } && sizeHint < head.Length - CurrentPosition) { return head.AsSpan(CurrentPosition); } return GetSpanSlow(sizeHint); } /// Copies the contents of this writer to a span. public readonly void CopyTo(Span output) { var current = First; while (output.Length > 0 && current != null) { var segment = current.CommittedMemory.Span; var slice = segment[..Math.Min(segment.Length, output.Length)]; slice.CopyTo(output); output = output[slice.Length..]; current = current.Next as SequenceSegment; } if (output.Length > 0 && CurrentPosition > 0) { var span = WriteHead.Array.AsSpan(0, Math.Min(output.Length, CurrentPosition)); span.CopyTo(output); } } /// Copies the contents of this writer to another writer. public readonly void CopyTo(ref Writer writer) where TBufferWriter : IBufferWriter { var current = First; while (current != null) { var span = current.CommittedMemory.Span; writer.Write(span); current = current.Next as SequenceSegment; } if (CurrentPosition > 0) { writer.Write(WriteHead.Array.AsSpan(0, CurrentPosition)); } } /// Copies the contents of this writer to another writer. public readonly void CopyTo(ref TBufferWriter writer) where TBufferWriter : IBufferWriter { var current = First; while (current != null) { var span = current.CommittedMemory.Span; Adaptors.BufferWriterExtensions.Write(ref writer, span); current = current.Next as SequenceSegment; } if (CurrentPosition > 0) { Adaptors.BufferWriterExtensions.Write(ref writer, WriteHead.Array.AsSpan(0, CurrentPosition)); } } /// /// Returns a new which must not be accessed after disposing this instance. /// public ReadOnlySequence AsReadOnlySequence() { if (Length == 0) { return ReadOnlySequence.Empty; } Commit(); if (First == Last) { return new ReadOnlySequence(First!.CommittedMemory); } return new ReadOnlySequence(First!, 0, Last!, Last!.CommittedMemory.Length); } /// /// Returns a covering this entire buffer. /// /// /// The lifetime of the returned must be shorter than the lifetime of this instance. /// /// A covering this entire buffer. public readonly BufferSlice Slice() => new(this, 0, Length); /// /// Returns a slice of this buffer, beginning at the specified offset. /// /// /// The lifetime of the returned must be shorter than the lifetime of this instance. /// /// A slice representing a subset of this instance, beginning at the specified offset. public readonly BufferSlice Slice(int offset) => new(this, offset, Length - offset); /// /// Returns a slice of this buffer, beginning at the specified offset and having the specified length. /// /// /// The lifetime of the returned must be shorter than the lifetime of this instance. /// /// A slice representing a subset of this instance, beginning at the specified offset. public readonly BufferSlice Slice(int offset, int length) => new(this, offset, length); /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly MemoryEnumerator MemorySegments => new(this); /// /// Writes the provided sequence to this buffer. /// /// The data to write. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySequence input) { foreach (var segment in input) { Write(segment.Span); } } /// /// Writes the provided value to this buffer. /// /// The data to write. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(ReadOnlySpan value) { // Fast path, try copying to the available memory directly if (WriteHead is { Array: { } head }) { var destination = head.AsSpan(CurrentPosition); if ((uint)value.Length <= (uint)destination.Length) { value.CopyTo(destination); CurrentPosition += value.Length; return; } } WriteMultiSegment(value); } private void WriteMultiSegment(in ReadOnlySpan source) { var input = source; while (true) { var destination = GetSpan(); var writeSize = Math.Min(destination.Length, input.Length); input[..writeSize].CopyTo(destination); CurrentPosition += writeSize; input = input.Slice(writeSize); if (input.Length > 0) { continue; } return; } } [MethodImpl(MethodImplOptions.NoInlining)] private Span GetSpanSlow(int sizeHint) => Grow(sizeHint).Array; [MethodImpl(MethodImplOptions.NoInlining)] private Memory GetMemorySlow(int sizeHint) => Grow(sizeHint).AsMemory(0); private SequenceSegment Grow(int sizeHint) { Commit(); var newBuffer = SequenceSegmentPool.Shared.Rent(sizeHint); return WriteHead = newBuffer; } private void Commit() { if (CurrentPosition == 0 || WriteHead is null) { return; } WriteHead.Commit(TotalLength, CurrentPosition); TotalLength += CurrentPosition; if (First is null) { First = WriteHead; } else { Debug.Assert(Last is not null); Last.SetNext(WriteHead); } Last = WriteHead; WriteHead = null; CurrentPosition = 0; } /// /// Enumerates over sequences of bytes in a . /// public struct MemoryEnumerator { private static readonly SequenceSegment InitialSegmentSentinel = new(); private static readonly SequenceSegment FinalSegmentSentinel = new(); private readonly PooledBuffer _buffer; private int _position; private SequenceSegment _segment; /// /// Initializes a new instance of the type. /// /// The buffer to enumerate. public MemoryEnumerator(PooledBuffer buffer) { _buffer = buffer; _segment = InitialSegmentSentinel; CurrentMemory = ReadOnlyMemory.Empty; } /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly MemoryEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public readonly ReadOnlyMemory Current => CurrentMemory; public ReadOnlyMemory CurrentMemory; /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (ReferenceEquals(_segment, InitialSegmentSentinel)) { _segment = _buffer.First; } var endPosition = _buffer.Length; while (_segment != null && _segment != FinalSegmentSentinel) { var segment = _segment.CommittedMemory; // Find the starting segment and the offset to copy from. var segmentLength = segment.Length; if (segmentLength == 0) { CurrentMemory = ReadOnlyMemory.Empty; _segment = FinalSegmentSentinel; return false; } CurrentMemory = segment[..segmentLength]; _position += segmentLength; _segment = _segment.Next as SequenceSegment; return true; } // Account for the uncommitted data at the end of the buffer. // The write head is only linked to the previous buffers when Commit() is called and it is set to null afterwards, // meaning that if the write head is not null, the other buffers are not linked to it and it therefore has not been enumerated. if (_segment != FinalSegmentSentinel && _buffer.CurrentPosition > 0 && _buffer.WriteHead is { } head) { var finalLength = _buffer.CurrentPosition; if (finalLength == 0) { CurrentMemory = ReadOnlyMemory.Empty; _segment = FinalSegmentSentinel; return false; } CurrentMemory = head.Array.AsMemory(0, finalLength); _position += finalLength; Debug.Assert(_position == _buffer.Length); _segment = FinalSegmentSentinel; return true; } return false; } } /// /// Enumerates over sequences of bytes in a . /// public ref struct SpanEnumerator { private static readonly SequenceSegment InitialSegmentSentinel = new(); private static readonly SequenceSegment FinalSegmentSentinel = new(); private readonly #if NET8_0_OR_GREATER ref readonly #endif PooledBuffer _buffer; private int _position; private SequenceSegment _segment; /// /// Initializes a new instance of the type. /// /// The buffer to enumerate. public SpanEnumerator(ref PooledBuffer buffer) { _buffer = #if NET8_0_OR_GREATER ref #endif buffer; _segment = InitialSegmentSentinel; Current = ReadOnlySpan.Empty; } /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly SpanEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ReadOnlySpan Current { get; private set; } /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (ReferenceEquals(_segment, InitialSegmentSentinel)) { _segment = _buffer.First; } var endPosition = _buffer.Length; while (_segment != null && _segment != FinalSegmentSentinel) { var segment = _segment.CommittedMemory; // Find the starting segment and the offset to copy from. var segmentLength = Math.Min(segment.Length, endPosition - _position); if (segmentLength == 0) { Current = ReadOnlySpan.Empty; _segment = FinalSegmentSentinel; return false; } Current = segment.Span[..segmentLength]; _position += segmentLength; _segment = _segment.Next as SequenceSegment; return true; } // Account for the uncommitted data at the end of the buffer. // The write head is only linked to the previous buffers when Commit() is called and it is set to null afterwards, // meaning that if the write head is not null, the other buffers are not linked to it and it therefore has not been enumerated. if (_segment != FinalSegmentSentinel && _buffer.CurrentPosition > 0 && _buffer.WriteHead is { } head && _position < endPosition) { var finalLength = Math.Min(_buffer.CurrentPosition, endPosition - _position); if (finalLength == 0) { Current = ReadOnlySpan.Empty; _segment = FinalSegmentSentinel; return false; } Current = head.Array.AsSpan(0, finalLength); _position += finalLength; Debug.Assert(_position == endPosition); _segment = FinalSegmentSentinel; return true; } return false; } } /// /// Represents a slice of a . /// public readonly struct BufferSlice { #pragma warning disable IDE1006 // Naming Styles internal readonly PooledBuffer _buffer; internal readonly int _offset; internal readonly int _length; #pragma warning restore IDE1006 // Naming Styles /// /// Initializes a new instance of the type. /// /// The buffer. /// The offset into the buffer at which this slice begins. /// The length of this slice. public BufferSlice(in PooledBuffer buffer, int offset, int length) { _buffer = buffer; _offset = offset; _length = length; } /// /// Gets the underlying . /// public readonly PooledBuffer Buffer => _buffer; /// /// Gets the offset into the underlying buffer at which this slice begins. /// public readonly int Offset => _offset; /// /// Gets the length of this slice. /// public readonly int Length => _length; /// /// Forms a slice out of this instance, beginning at the specified offset into this slice. /// /// The offset into this slice where the newly formed slice will begin. /// A slice instance. public readonly BufferSlice Slice(int offset) => new(in _buffer, _offset + offset, _length - offset); /// /// Forms a slice out of this instance, beginning at the specified offset into this slice and having the specified length. /// /// The offset into this slice where the newly formed slice will begin. /// The length of the new slice. /// A slice instance. public readonly BufferSlice Slice(int offset, int length) => new(in _buffer, _offset + offset, length); /// Copies the contents of this writer to a span. public readonly int CopyTo(Span output) { var copied = 0; foreach (var span in this) { var slice = span[..Math.Min(span.Length, output.Length)]; slice.CopyTo(output); output = output[slice.Length..]; copied += slice.Length; } return copied; } /// Copies the contents of this writer to a pooled buffer. public readonly void CopyTo(ref PooledBuffer output) { foreach (var span in this) { output.Write(span); } } /// Copies the contents of this writer to a buffer writer. public readonly void CopyTo(ref TBufferWriter output) where TBufferWriter : struct, IBufferWriter { foreach (var span in this) { Adaptors.BufferWriterExtensions.Write(ref output, span); } } /// /// Returns the data which has been written as an array. /// /// The data which has been written. public readonly byte[] ToArray() { var result = new byte[_length]; CopyTo(result); return result; } /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly SpanEnumerator GetEnumerator() => new(this); /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly MemoryEnumerator MemorySegments => new(this); /// /// Enumerates over spans of bytes in a . /// public ref struct SpanEnumerator { private static readonly SequenceSegment InitialSegmentSentinel = new(); private static readonly SequenceSegment FinalSegmentSentinel = new(); private readonly BufferSlice _slice; private int _position; private SequenceSegment _segment; /// /// Initializes a new instance of the type. /// /// The slice to enumerate. public SpanEnumerator(BufferSlice slice) { _slice = slice; _segment = InitialSegmentSentinel; Current = Span.Empty; } internal readonly PooledBuffer Buffer => _slice._buffer; internal readonly int Offset => _slice._offset; internal readonly int Length => _slice._length; /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly SpanEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ReadOnlySpan Current { get; private set; } /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (ReferenceEquals(_segment, InitialSegmentSentinel)) { _segment = Buffer.First; } var endPosition = Offset + Length; while (_segment != null && _segment != FinalSegmentSentinel) { var segment = _segment.CommittedMemory.Span; // Find the starting segment and the offset to copy from. int segmentOffset; if (_position < Offset) { if (_position + segment.Length <= Offset) { // Start is in a subsequent segment _position += segment.Length; _segment = _segment.Next as SequenceSegment; continue; } else { // Start is in this segment segmentOffset = Offset - _position; } } else { segmentOffset = 0; } var segmentLength = Math.Min(segment.Length - segmentOffset, endPosition - (_position + segmentOffset)); if (segmentLength == 0) { Current = Span.Empty; _segment = FinalSegmentSentinel; return false; } Current = segment.Slice(segmentOffset, segmentLength); _position += segmentOffset + segmentLength; _segment = _segment.Next as SequenceSegment; return true; } // Account for the uncommitted data at the end of the buffer. // The write head is only linked to the previous buffers when Commit() is called and it is set to null afterwards, // meaning that if the write head is not null, the other buffers are not linked to it and it therefore has not been enumerated. if (_segment != FinalSegmentSentinel && Buffer.CurrentPosition > 0 && Buffer.WriteHead is { } head && _position < endPosition) { var finalOffset = Math.Max(Offset - _position, 0); var finalLength = Math.Min(Buffer.CurrentPosition, endPosition - (_position + finalOffset)); if (finalLength == 0) { Current = Span.Empty; _segment = FinalSegmentSentinel; return false; } Current = head.Array.AsSpan(finalOffset, finalLength); _position += finalOffset + finalLength; Debug.Assert(_position == endPosition); _segment = FinalSegmentSentinel; return true; } return false; } } /// /// Enumerates over sequences of bytes in a . /// public struct MemoryEnumerator { private static readonly SequenceSegment InitialSegmentSentinel = new(); private static readonly SequenceSegment FinalSegmentSentinel = new(); private readonly BufferSlice _slice; private int _position; private SequenceSegment _segment; /// /// Initializes a new instance of the type. /// /// The slice to enumerate. public MemoryEnumerator(BufferSlice slice) { _slice = slice; _segment = InitialSegmentSentinel; Current = ReadOnlyMemory.Empty; } internal readonly PooledBuffer Buffer => _slice._buffer; internal readonly int Offset => _slice._offset; internal readonly int Length => _slice._length; /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public readonly MemoryEnumerator GetEnumerator() => this; /// /// Gets the element in the collection at the current position of the enumerator. /// public ReadOnlyMemory Current { get; private set; } /// /// Advances the enumerator to the next element of the collection. /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { if (ReferenceEquals(_segment, InitialSegmentSentinel)) { _segment = Buffer.First; } var endPosition = Offset + Length; while (_segment != null && _segment != FinalSegmentSentinel) { var segment = _segment.CommittedMemory; // Find the starting segment and the offset to copy from. int segmentOffset; if (_position < Offset) { if (_position + segment.Length <= Offset) { // Start is in a subsequent segment _position += segment.Length; _segment = _segment.Next as SequenceSegment; continue; } else { // Start is in this segment segmentOffset = Offset - _position; } } else { segmentOffset = 0; } var segmentLength = Math.Min(segment.Length - segmentOffset, endPosition - (_position + segmentOffset)); if (segmentLength == 0) { Current = ReadOnlyMemory.Empty; _segment = FinalSegmentSentinel; return false; } Current = segment.Slice(segmentOffset, segmentLength); _position += segmentOffset + segmentLength; _segment = _segment.Next as SequenceSegment; return true; } // Account for the uncommitted data at the end of the buffer. // The write head is only linked to the previous buffers when Commit() is called and it is set to null afterwards, // meaning that if the write head is not null, the other buffers are not linked to it and it therefore has not been enumerated. if (_segment != FinalSegmentSentinel && Buffer.CurrentPosition > 0 && Buffer.WriteHead is { } head && _position < endPosition) { var finalOffset = Math.Max(Offset - _position, 0); var finalLength = Math.Min(Buffer.CurrentPosition, endPosition - (_position + finalOffset)); if (finalLength == 0) { Current = ReadOnlyMemory.Empty; _segment = FinalSegmentSentinel; return false; } Current = head.Array.AsMemory(finalOffset, finalLength); _position += finalOffset + finalLength; Debug.Assert(_position == endPosition); _segment = FinalSegmentSentinel; return true; } return false; } } } private sealed class SequenceSegmentPool { public static SequenceSegmentPool Shared { get; } = new(); public const int MinimumBlockSize = 4 * 1024; private readonly ConcurrentQueue _blocks = new(); private readonly ConcurrentQueue _largeBlocks = new(); private SequenceSegmentPool() { } public SequenceSegment Rent(int size = -1) { SequenceSegment block; if (size <= MinimumBlockSize) { return _blocks.TryDequeue(out block) ? block : new(large: false); } if (!_largeBlocks.TryDequeue(out block)) { block = new(large: true); } block.ResizeLargeSegment(size); return block; } internal void Return(SequenceSegment block) { Debug.Assert(block.IsValid); (block.IsMinimumSize ? _blocks : _largeBlocks).Enqueue(block); } } internal sealed class SequenceSegment : ReadOnlySequenceSegment { internal SequenceSegment() { Array = System.Array.Empty(); } internal SequenceSegment(bool large) { if (!large) { #if NET6_0_OR_GREATER Array = GC.AllocateUninitializedArray(SequenceSegmentPool.MinimumBlockSize, pinned: true); #else Array = new byte[SequenceSegmentPool.MinimumBlockSize]; #endif } } public void ResizeLargeSegment(int length) { Debug.Assert(length > SequenceSegmentPool.MinimumBlockSize); // Round up to a power of two. length = (int)BitOperations.RoundUpToPowerOf2((uint)length); if (Array is { } array) { // The segment has an appropriate size already. if (array.Length == length) { return; } // The segment is being resized. Array = null; ArrayPool.Shared.Return(array); } Array = ArrayPool.Shared.Rent(length); } public byte[] Array { get; private set; } public ReadOnlyMemory CommittedMemory => Memory; public bool IsValid => Array is { Length: > 0 }; public bool IsMinimumSize => Array.Length == SequenceSegmentPool.MinimumBlockSize; public Memory AsMemory(int offset) { #if NET6_0_OR_GREATER if (IsMinimumSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length - offset); } #endif return Array.AsMemory(offset); } public Memory AsMemory(int offset, int length) { #if NET6_0_OR_GREATER if (IsMinimumSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, length); } #endif return Array.AsMemory(offset, length); } public void Commit(long runningIndex, int length) { RunningIndex = runningIndex; Memory = AsMemory(0, length); } public void SetNext(SequenceSegment next) => Next = next; public void Return() { RunningIndex = default; Next = default; Memory = default; SequenceSegmentPool.Shared.Return(this); } } } /// /// Extensions for . /// public static class PooledBufferExtensions { /// /// Returns an enumerator which can be used to enumerate the data referenced by this instance. /// /// An enumerator for the data contained in this instance. public static PooledBuffer.SpanEnumerator GetEnumerator(this ref PooledBuffer buffer) => new(ref buffer); } ================================================ FILE: src/Orleans.Serialization/Buffers/Reader.cs ================================================ using System; using System.Buffers; using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.IO; #if NETCOREAPP3_1_OR_GREATER using System.Numerics; #endif using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Session; using static Orleans.Serialization.Buffers.PooledBuffer; #if !NETCOREAPP3_1_OR_GREATER using Orleans.Serialization.Utilities; #endif namespace Orleans.Serialization.Buffers { /// /// Functionality for reading binary data. /// public abstract class ReaderInput { /// /// Gets the position. /// /// The position. public abstract long Position { get; } /// /// Gets the length. /// /// The length. public abstract long Length { get; } /// /// Skips the specified number of bytes. /// /// The number of bytes to skip. public abstract void Skip(long count); /// /// Seeks to the specified position. /// /// The position. public abstract void Seek(long position); /// /// Reads a byte from the input. /// /// The byte which was read. public abstract byte ReadByte(); /// /// Reads a from the input. /// /// The which was read. public abstract uint ReadUInt32(); /// /// Reads a from the input. /// /// The which was read. public abstract ulong ReadUInt64(); /// /// Fills the destination span with data from the input. /// /// The destination. public abstract void ReadBytes(Span destination); /// /// Reads bytes from the input into the destination array. /// /// The destination array. /// The offset into the destination to start writing bytes. /// The number of bytes to copy into destination. public abstract void ReadBytes(byte[] destination, int offset, int length); /// /// Tries to read the specified number of bytes from the input. /// /// The number of bytes to read.. /// The bytes which were read.. /// if the number of bytes were successfully read, otherwise. public abstract bool TryReadBytes(int length, out ReadOnlySpan bytes); } internal sealed class StreamReaderInput : ReaderInput { [ThreadStatic] private static byte[] Scratch; private readonly Stream _stream; private readonly ArrayPool _memoryPool; public override long Position => _stream.Position; public override long Length => _stream.Length; public StreamReaderInput(Stream stream, ArrayPool memoryPool) { _stream = stream; _memoryPool = memoryPool; } public override byte ReadByte() { var c = _stream.ReadByte(); if (c < 0) { ThrowInsufficientData(); } return (byte)c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void ReadBytes(Span destination) { var count = _stream.Read(destination); if (count < destination.Length) { ThrowInsufficientData(); } } public override void ReadBytes(byte[] destination, int offset, int length) { var count = _stream.Read(destination, offset, length); if (count < length) { ThrowInsufficientData(); } } #if NET5_0_OR_GREATER [SkipLocalsInit] #endif public override uint ReadUInt32() { Span buffer = stackalloc byte[sizeof(uint)]; ReadBytes(buffer); return BinaryPrimitives.ReadUInt32LittleEndian(buffer); } #if NET5_0_OR_GREATER [SkipLocalsInit] #endif public override ulong ReadUInt64() { Span buffer = stackalloc byte[sizeof(ulong)]; ReadBytes(buffer); return BinaryPrimitives.ReadUInt64LittleEndian(buffer); } public override void Skip(long count) => _ = _stream.Seek(count, SeekOrigin.Current); public override void Seek(long position) => _ = _stream.Seek(position, SeekOrigin.Begin); public override bool TryReadBytes(int length, out ReadOnlySpan destination) { // Cannot get a span pointing to a stream's internal buffer. destination = default; return false; } private static void ThrowInsufficientData() => throw new InvalidOperationException("Insufficient data present in buffer."); private static byte[] GetScratchBuffer() => Scratch ??= new byte[1024]; } /// /// Helper methods for . /// public static class Reader { /// /// Creates a reader for the provided buffer. /// /// The input. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(PooledBuffer input, SerializerSession session) => Create(input.Slice(), session); /// /// Creates a reader for the provided buffer. /// /// The input. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(BufferSlice input, SerializerSession session) => new(new BufferSliceReaderInput(in input), session, 0); /// /// Creates a reader for the provided input stream. /// /// The stream. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(Stream stream, SerializerSession session) => new(new StreamReaderInput(stream, ArrayPool.Shared), session, 0); /// /// Creates a reader for the provided buffer. /// /// The buffer. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(ReadOnlySequence sequence, SerializerSession session) => new(new ReadOnlySequenceInput { Sequence = sequence }, session, 0); /// /// Creates a reader for the provided buffer. /// /// The buffer. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(ReadOnlySpan buffer, SerializerSession session) => new(buffer, session, 0); /// /// Creates a reader for the provided buffer. /// /// The buffer. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(byte[] buffer, SerializerSession session) => new(buffer, session, 0); /// /// Creates a reader for the provided buffer. /// /// The buffer. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Reader Create(ReadOnlyMemory buffer, SerializerSession session) => new(buffer.Span, session, 0); } /// /// Marker type for objects which operate over buffers. /// public readonly struct SpanReaderInput { } /// /// Input type for to support buffers. /// public struct ReadOnlySequenceInput { internal ReadOnlySequence Sequence; internal SequencePosition NextSequencePosition; internal long PreviousBuffersSize; } /// /// Provides functionality for parsing data from binary input. /// /// The underlying buffer reader type. public ref struct Reader { private readonly static bool IsSpanInput = typeof(TInput) == typeof(SpanReaderInput); private readonly static bool IsReadOnlySequenceInput = typeof(TInput) == typeof(ReadOnlySequenceInput); private readonly static bool IsReaderInput = typeof(ReaderInput).IsAssignableFrom(typeof(TInput)); private readonly static bool IsBufferSliceInput = typeof(TInput) == typeof(BufferSliceReaderInput); private ReadOnlySpan _currentSpan; private int _bufferPos; private int _bufferSize; private readonly long _sequenceOffset; private TInput _input; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Reader(TInput input, SerializerSession session, long globalOffset) { if (IsReadOnlySequenceInput) { ref var typedInput = ref Unsafe.As(ref input); var sequence = typedInput.Sequence; typedInput.NextSequencePosition = sequence.Start; _input = input; _currentSpan = sequence.First.Span; _bufferPos = 0; _bufferSize = _currentSpan.Length; _sequenceOffset = globalOffset; } else if (IsBufferSliceInput) { _input = input; ref var slice = ref Unsafe.As(ref _input); _currentSpan = slice.GetNext(); _bufferPos = 0; _bufferSize = _currentSpan.Length; _sequenceOffset = globalOffset; } else if (IsReaderInput) { _input = input; _currentSpan = default; _bufferPos = 0; _bufferSize = default; _sequenceOffset = globalOffset; } else { throw new NotSupportedException($"Type {typeof(TInput)} is not supported by this constructor"); } Session = session; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Reader(ReadOnlySpan input, SerializerSession session, long globalOffset) { if (IsSpanInput) { _input = default; _currentSpan = input; _bufferPos = 0; _bufferSize = _currentSpan.Length; _sequenceOffset = globalOffset; } else { throw new NotSupportedException($"Type {typeof(TInput)} is not supported by this constructor"); } Session = session; } /// /// Gets the serializer session. /// /// The serializer session. public SerializerSession Session { get; } /// /// Gets the current reader position. /// /// The current position. public long Position { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (IsReadOnlySequenceInput) { var previousBuffersSize = Unsafe.As(ref _input).PreviousBuffersSize; return _sequenceOffset + previousBuffersSize + _bufferPos; } else if (IsBufferSliceInput) { var previousBuffersSize = Unsafe.As(ref _input).PreviousBuffersSize; return _sequenceOffset + previousBuffersSize + _bufferPos; } else if (IsSpanInput) { return _sequenceOffset + _bufferPos; } else if (_input is ReaderInput readerInput) { return readerInput.Position; } else { return ThrowNotSupportedInput(); } } } /// /// Gets the input length. /// /// The input length. public long Length { get { if (IsReadOnlySequenceInput) { return Unsafe.As(ref _input).Sequence.Length; } else if (IsBufferSliceInput) { return Unsafe.As(ref _input).Length; } else if (IsSpanInput) { return _currentSpan.Length; } else if (_input is ReaderInput readerInput) { return readerInput.Length; } else { return ThrowNotSupportedInput(); } } } /// /// Skips the specified number of bytes. /// /// The number of bytes to skip. public void Skip(long count) { if (IsReadOnlySequenceInput) { var end = Position + count; while (Position < end) { var previousBuffersSize = Unsafe.As(ref _input).PreviousBuffersSize; if (end - previousBuffersSize <= _bufferSize) { _bufferPos = (int)(end - previousBuffersSize); } else { MoveNext(); } } } else if (IsBufferSliceInput) { var end = Position + count; while (Position < end) { var previousBuffersSize = Unsafe.As(ref _input).PreviousBuffersSize; if (end - previousBuffersSize <= _bufferSize) { _bufferPos = (int)(end - previousBuffersSize); } else { MoveNext(); } } } else if (IsSpanInput) { _bufferPos += (int)count; if (_bufferPos > _currentSpan.Length || count > int.MaxValue) { ThrowInsufficientData(); } } else if (_input is ReaderInput input) { input.Skip(count); } else { ThrowNotSupportedInput(); } } /// /// Creates a new reader beginning at the specified position. /// /// /// The position in the input stream to fork from. /// /// /// The forked reader instance. /// public void ForkFrom(long position, out Reader forked) { if (IsReadOnlySequenceInput) { ref var typedInput = ref Unsafe.As(ref _input); var slicedSequence = typedInput.Sequence.Slice(position - _sequenceOffset); var newInput = new ReadOnlySequenceInput { Sequence = slicedSequence }; forked = new Reader(Unsafe.As(ref newInput), Session, position); if (forked.Position != position) { ThrowInvalidPosition(position, forked.Position); } } else if (IsBufferSliceInput) { ref var input = ref Unsafe.As(ref _input); var newInput = input.ForkFrom(checked((int)position)); forked = new Reader(Unsafe.As(ref newInput), Session, position); if (forked.Position != position) { ThrowInvalidPosition(position, forked.Position); } } else if (IsSpanInput) { forked = new Reader(_currentSpan[(int)position..], Session, position); if (forked.Position != position || position > int.MaxValue) { ThrowInvalidPosition(position, forked.Position); } } else if (_input is ReaderInput input) { input.Seek(position); forked = new Reader(_input, Session, 0); if (forked.Position != position) { ThrowInvalidPosition(position, forked.Position); } } else { throw new NotSupportedException($"Type {typeof(TInput)} is not supported"); } static void ThrowInvalidPosition(long expectedPosition, long actualPosition) { throw new InvalidOperationException($"Expected to arrive at position {expectedPosition} after ForkFrom, but resulting position is {actualPosition}"); } } /// /// Resumes the reader from the specified position after forked readers are no longer in use. /// /// /// The position to resume reading from. /// public void ResumeFrom(long position) { if (IsReadOnlySequenceInput) { // Nothing is required. } else if (IsBufferSliceInput) { // Nothing is required. } else if (IsSpanInput) { // Nothing is required. } else if (_input is ReaderInput input) { // Seek the input stream. input.Seek(Position); } else { throw new NotSupportedException($"Type {typeof(TInput)} is not supported"); } if (position != Position) { ThrowInvalidPosition(position, Position); } static void ThrowInvalidPosition(long expectedPosition, long actualPosition) { throw new InvalidOperationException($"Expected to arrive at position {expectedPosition} after ResumeFrom, but resulting position is {actualPosition}"); } } [MethodImpl(MethodImplOptions.NoInlining)] private void MoveNext() { if (IsReadOnlySequenceInput) { ref var typedInput = ref Unsafe.As(ref _input); typedInput.PreviousBuffersSize += _bufferSize; // If this is the first call to MoveNext then nextSequencePosition is invalid and must be moved to the second position. if (typedInput.NextSequencePosition.Equals(typedInput.Sequence.Start)) { _ = typedInput.Sequence.TryGet(ref typedInput.NextSequencePosition, out _); } if (!typedInput.Sequence.TryGet(ref typedInput.NextSequencePosition, out var memory)) { _currentSpan = memory.Span; ThrowInsufficientData(); } _currentSpan = memory.Span; _bufferPos = 0; _bufferSize = _currentSpan.Length; } else if (IsBufferSliceInput) { ref var slice = ref Unsafe.As(ref _input); slice.PreviousBuffersSize += _bufferSize; _currentSpan = slice.GetNext(); _bufferPos = 0; _bufferSize = _currentSpan.Length; } else if (IsSpanInput) { ThrowInsufficientData(); } else { ThrowNotSupportedInput(); } } /// /// Reads a byte from the input. /// /// The byte which was read. [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { var pos = _bufferPos; if ((uint)pos < (uint)_currentSpan.Length) { // https://github.com/dotnet/runtime/issues/72004 var result = Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), (uint)pos); _bufferPos = pos + 1; return result; } return ReadByteSlow(ref this); } else if (_input is ReaderInput readerInput) { return readerInput.ReadByte(); } else { return ThrowNotSupportedInput(); } } [MethodImpl(MethodImplOptions.NoInlining)] private static byte ReadByteSlow(ref Reader reader) { reader.MoveNext(); return reader._currentSpan[reader._bufferPos++]; } /// /// Reads an from the input. /// /// The which was read. public int ReadInt32() => (int)ReadUInt32(); /// /// Reads a from the input. /// /// The which was read. public uint ReadUInt32() { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { const int width = 4; if (_bufferPos + width > _bufferSize) { return ReadSlower(ref this); } var result = BinaryPrimitives.ReadUInt32LittleEndian(_currentSpan.Slice(_bufferPos, width)); _bufferPos += width; return result; static uint ReadSlower(ref Reader r) { uint b1 = r.ReadByte(); uint b2 = r.ReadByte(); uint b3 = r.ReadByte(); uint b4 = r.ReadByte(); return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); } } else if (_input is ReaderInput readerInput) { return readerInput.ReadUInt32(); } else { return ThrowNotSupportedInput(); } } /// /// Reads a from the input. /// /// The which was read. public long ReadInt64() => (long)ReadUInt64(); /// /// Reads a from the input. /// /// The which was read. public ulong ReadUInt64() { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { const int width = 8; if (_bufferPos + width > _bufferSize) { return ReadSlower(ref this); } var result = BinaryPrimitives.ReadUInt64LittleEndian(_currentSpan.Slice(_bufferPos, width)); _bufferPos += width; return result; static ulong ReadSlower(ref Reader r) { ulong b1 = r.ReadByte(); ulong b2 = r.ReadByte(); ulong b3 = r.ReadByte(); ulong b4 = r.ReadByte(); ulong b5 = r.ReadByte(); ulong b6 = r.ReadByte(); ulong b7 = r.ReadByte(); ulong b8 = r.ReadByte(); return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24) | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56); } } else if (_input is ReaderInput readerInput) { return readerInput.ReadUInt64(); } else { return ThrowNotSupportedInput(); } } [DoesNotReturn] private static void ThrowInsufficientData() => throw new InvalidOperationException("Insufficient data present in buffer."); /// /// Reads the specified number of bytes into the provided writer. /// public void ReadBytes(scoped ref TBufferWriter writer, int count) where TBufferWriter : IBufferWriter { int chunkSize; for (var remaining = count; remaining > 0; remaining -= chunkSize) { var span = writer.GetSpan(); if (span.Length > remaining) { span = span[..remaining]; } ReadBytes(span); chunkSize = span.Length; writer.Advance(chunkSize); } } /// /// Reads an array of bytes from the input. /// /// The length of the array to read. /// The array wihch was read. public byte[] ReadBytes(uint count) { if (count == 0) { return Array.Empty(); } if (count > 10240 && count > Length) { ThrowInvalidSizeException(count); } var bytes = new byte[count]; if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { var destination = new Span(bytes); ReadBytes(destination); } else if (_input is ReaderInput readerInput) { readerInput.ReadBytes(bytes, 0, (int)count); } return bytes; } /// /// Fills with bytes read from the input. /// /// The destination. public void ReadBytes(scoped Span destination) { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { if (_bufferPos + destination.Length <= _bufferSize) { _currentSpan.Slice(_bufferPos, destination.Length).CopyTo(destination); _bufferPos += destination.Length; return; } ReadBytesMultiSegment(destination); } else if (_input is ReaderInput readerInput) { readerInput.ReadBytes(destination); } else { ThrowNotSupportedInput(); } } private void ReadBytesMultiSegment(scoped Span dest) { while (true) { var writeSize = Math.Min(dest.Length, _currentSpan.Length - _bufferPos); _currentSpan.Slice(_bufferPos, writeSize).CopyTo(dest); _bufferPos += writeSize; dest = dest[writeSize..]; if (dest.Length == 0) { break; } MoveNext(); } } /// /// Tries the read the specified number of bytes from the input. /// /// The length. /// The bytes which were read. /// if the specified number of bytes were read from the input, otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryReadBytes(int length, out ReadOnlySpan bytes) { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { if (_bufferPos + length <= _bufferSize) { bytes = _currentSpan.Slice(_bufferPos, length); _bufferPos += length; return true; } bytes = default; return false; } else if (_input is ReaderInput readerInput) { return readerInput.TryReadBytes(length, out bytes); } else { bytes = default; return ThrowNotSupportedInput(); } } [MethodImpl(MethodImplOptions.NoInlining)] internal uint ReadVarUInt32NoInlining() => ReadVarUInt32(); /// /// Reads a variable-width from the input. /// /// The which was read. [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe uint ReadVarUInt32() { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { var pos = _bufferPos; if (!BitConverter.IsLittleEndian || pos + 8 > _currentSpan.Length) { return ReadVarUInt32Slow(); } // The number of zeros in the msb position dictates the number of bytes to be read. // Up to a maximum of 5 for a 32bit integer. ref byte readHead = ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos); ulong result = Unsafe.ReadUnaligned(ref readHead); var bytesNeeded = BitOperations.TrailingZeroCount((uint)result) + 1; if (bytesNeeded > 5) ThrowOverflowException(); _bufferPos = pos + bytesNeeded; result &= (1UL << (bytesNeeded * 8)) - 1; result >>= bytesNeeded; return checked((uint)result); } else { return ReadVarUInt32Slow(); } } private static void ThrowOverflowException() => throw new OverflowException(); [MethodImpl(MethodImplOptions.NoInlining)] private uint ReadVarUInt32Slow() { var header = ReadByte(); var numBytes = BitOperations.TrailingZeroCount(0x0100U | header) + 1; // Widen to a ulong for the 5-byte case ulong result = header; // Read additional bytes as needed var shiftBy = 8; var i = numBytes; while (--i > 0) { result |= (ulong)ReadByte() << shiftBy; shiftBy += 8; } result >>= numBytes; return checked((uint)result); } /// /// Reads a variable-width from the input. /// /// The which was read. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadVarUInt64() { if (IsReadOnlySequenceInput || IsSpanInput || IsBufferSliceInput) { var pos = _bufferPos; if (!BitConverter.IsLittleEndian || pos + 10 > _currentSpan.Length) { return ReadVarUInt64Slow(); } // The number of zeros in the msb position dictates the number of bytes to be read. // Up to a maximum of 5 for a 32bit integer. ref byte readHead = ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos); ulong result = Unsafe.ReadUnaligned(ref readHead); var bytesNeeded = BitOperations.TrailingZeroCount(result) + 1; result >>= bytesNeeded; _bufferPos += bytesNeeded; ushort upper = Unsafe.ReadUnaligned(ref Unsafe.Add(ref readHead, sizeof(ulong))); result |= ((ulong)upper) << (64 - bytesNeeded); // Mask off invalid data var fullWidthReadMask = ~((ulong)bytesNeeded - 10 + 1); var mask = ((1UL << (bytesNeeded * 7)) - 1) | fullWidthReadMask; result &= mask; return result; } else { return ReadVarUInt64Slow(); } } [MethodImpl(MethodImplOptions.NoInlining)] private ulong ReadVarUInt64Slow() { var header = ReadByte(); var numBytes = BitOperations.TrailingZeroCount(0x0100U | header) + 1; // Widen to a ulong for the 5-byte case ulong result = header; // Read additional bytes as needed if (numBytes < 9) { var shiftBy = 8; var i = numBytes; while (--i > 0) { result |= (ulong)ReadByte() << shiftBy; shiftBy += 8; } result >>= numBytes; return result; } else { result |= (ulong)ReadByte() << 8; // If there was more than one byte worth of trailing zeros, read again now that we have more data. numBytes = BitOperations.TrailingZeroCount(result) + 1; if (numBytes == 9) { result |= (ulong)ReadByte() << 16; result |= (ulong)ReadByte() << 24; result |= (ulong)ReadByte() << 32; result |= (ulong)ReadByte() << 40; result |= (ulong)ReadByte() << 48; result |= (ulong)ReadByte() << 56; result >>= 9; var upper = (ushort)ReadByte(); result |= ((ulong)upper) << (64 - 9); return result; } else if (numBytes == 10) { result |= (ulong)ReadByte() << 16; result |= (ulong)ReadByte() << 24; result |= (ulong)ReadByte() << 32; result |= (ulong)ReadByte() << 40; result |= (ulong)ReadByte() << 48; result |= (ulong)ReadByte() << 56; result >>= 10; var upper = (ushort)(ReadByte() | (ushort)(ReadByte() << 8)); result |= ((ulong)upper) << (64 - 10); return result; } } return ExceptionHelper.ThrowArgumentOutOfRange("value"); } private static T ThrowNotSupportedInput() => throw new NotSupportedException($"Type {typeof(TInput)} is not supported"); private static void ThrowNotSupportedInput() => throw new NotSupportedException($"Type {typeof(TInput)} is not supported"); private static void ThrowInvalidSizeException(uint length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(byte[])}, {length}, is greater than total length of input."); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Writer.FieldHeader.cs ================================================ using System; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.TypeSystem; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Buffers { public ref partial struct Writer { /// /// Writes the field header. /// /// The field identifier. /// The expected type. /// The actual type. /// The wire type. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteFieldHeader(uint fieldId, Type expectedType, Type actualType, WireType wireType) { var embeddedFieldId = fieldId > Tag.MaxEmbeddedFieldIdDelta ? Tag.FieldIdCompleteMask : fieldId; var tag = (uint)wireType | embeddedFieldId; if (actualType is null || actualType == expectedType) { WriteByte((byte)tag); // SchemaType.Expected=0 if (fieldId > Tag.MaxEmbeddedFieldIdDelta) { WriteVarUInt32(fieldId); } return; } uint typeOrReferenceId; if (Session.WellKnownTypes.TryGetWellKnownTypeId(actualType, out var typeId)) { typeOrReferenceId = typeId; tag |= (byte)SchemaType.WellKnown; } else if ((typeOrReferenceId = Session.ReferencedTypes.GetOrAddTypeReference(actualType)) != 0) { tag |= (byte)SchemaType.Referenced; } else { WriteFieldHeaderEncodeType(fieldId, actualType, tag); return; } WriteByte((byte)tag); if (fieldId > Tag.MaxEmbeddedFieldIdDelta) WriteVarUInt32(fieldId); WriteVarUInt32(typeOrReferenceId); } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteFieldHeaderEncodeType(uint fieldId, Type actualType, uint tag) { WriteByte((byte)(tag | (byte)SchemaType.Encoded)); if (fieldId > Tag.MaxEmbeddedFieldIdDelta) WriteVarUInt32(fieldId); Session.TypeCodec.WriteEncodedType(ref this, actualType); } /// /// Writes an expected field header value. /// /// The field identifier. /// The wire type of the field. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteFieldHeaderExpected(uint fieldId, WireType wireType) { var embeddedFieldId = fieldId > Tag.MaxEmbeddedFieldIdDelta ? Tag.FieldIdCompleteMask : fieldId; WriteByte((byte)((uint)wireType | embeddedFieldId)); if (fieldId > Tag.MaxEmbeddedFieldIdDelta) WriteVarUInt32(fieldId); } } } namespace Orleans.Serialization.Codecs { /// /// Codec for operating with the wire format. /// public static class FieldHeaderCodec { /// /// Reads a field header. /// /// The reader input type. /// The reader. /// The field header. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReadFieldHeader(ref this Reader reader, scoped ref Field field) { var tag = (uint)reader.ReadByte(); field.Tag = new(tag); // If the id or schema type are required and were not encoded into the tag, read the extended header data. if (tag < (byte)WireType.Extended && (tag & (Tag.FieldIdCompleteMask | Tag.SchemaTypeMask)) >= Tag.FieldIdCompleteMask) { ReadExtendedFieldHeader(ref reader, ref field); } else { field.FieldIdDeltaRaw = tag & Tag.FieldIdMask; field.FieldTypeRaw = default; } } /// /// Reads a field header. /// /// The reader input type. /// The reader. /// The field header. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Field ReadFieldHeader(ref this Reader reader) { Unsafe.SkipInit(out Field field); var tag = (uint)reader.ReadByte(); field.Tag = new(tag); // If the id or schema type are required and were not encoded into the tag, read the extended header data. if (tag < (byte)WireType.Extended && (tag & (Tag.FieldIdCompleteMask | Tag.SchemaTypeMask)) >= Tag.FieldIdCompleteMask) { ReadExtendedFieldHeader(ref reader, ref field); } else { field.FieldIdDeltaRaw = tag & Tag.FieldIdMask; field.FieldTypeRaw = default; } return field; } /// /// Reads an extended field header. /// /// The reader input type. /// The reader. /// The field. [MethodImpl(MethodImplOptions.NoInlining)] private static void ReadExtendedFieldHeader(ref this Reader reader, scoped ref Field field) { var fieldId = field.Tag.FieldIdDelta; if (fieldId == Tag.FieldIdCompleteMask) { field.FieldIdDeltaRaw = reader.ReadVarUInt32NoInlining(); } else { field.FieldIdDeltaRaw = fieldId; } // If schema type is valid, read the type. var schemaType = field.Tag.SchemaType; if (schemaType != SchemaType.Expected) { field.FieldTypeRaw = reader.ReadType(schemaType); } else { field.FieldTypeRaw = default; } } [MethodImpl(MethodImplOptions.NoInlining)] private static Type ReadType(this ref Reader reader, SchemaType schemaType) { switch (schemaType) { case SchemaType.WellKnown: var typeId = reader.ReadVarUInt32(); return reader.Session.WellKnownTypes.GetWellKnownType(typeId); case SchemaType.Encoded: var encoded = reader.Session.TypeCodec.TryRead(ref reader); reader.Session.ReferencedTypes.RecordReferencedType(encoded); return encoded; case SchemaType.Referenced: var reference = reader.ReadVarUInt32(); return reader.Session.ReferencedTypes.GetReferencedType(reference); default: return ExceptionHelper.ThrowArgumentOutOfRange(nameof(SchemaType)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static (Type type, string typeName) ReadTypeForAnalysis(this ref Reader reader, SchemaType schemaType) { switch (schemaType) { case SchemaType.WellKnown: { var typeId = reader.ReadVarUInt32(); var found = reader.Session.WellKnownTypes.TryGetWellKnownType(typeId, out var type); return (type, $"WellKnown {typeId} ({(found ? type is null ? "null" : RuntimeTypeNameFormatter.Format(type) : "unknown")})"); } case SchemaType.Encoded: { var found = reader.Session.TypeCodec.TryReadForAnalysis(ref reader, out Type encoded, out var typeString); reader.Session.ReferencedTypes.RecordReferencedType(encoded); return (encoded, $"Encoded \"{typeString}\" ({(found ? encoded is null ? "null" : RuntimeTypeNameFormatter.Format(encoded) : "not found")})"); } case SchemaType.Referenced: { var reference = reader.ReadVarUInt32(); var found = reader.Session.ReferencedTypes.TryGetReferencedType(reference, out var type); return (type, $"Referenced {reference} ({(found ? type is null ? "null" : RuntimeTypeNameFormatter.Format(type) : "not found")})"); } default: throw new ArgumentOutOfRangeException(nameof(schemaType)); } } /// /// Reads a field header for diagnostic purposes. /// /// The reader input type. /// The reader. /// The value which was read. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Field Field, string Type) ReadFieldHeaderForAnalysis(ref this Reader reader) { Unsafe.SkipInit(out Field field); string type = default; var tag = (uint)reader.ReadByte(); field.Tag = new(tag); if (tag < (byte)WireType.Extended && ((tag & Tag.FieldIdCompleteMask) == Tag.FieldIdCompleteMask || (tag & Tag.SchemaTypeMask) != (byte)SchemaType.Expected)) { ReadFieldHeaderForAnalysisSlow(ref reader, ref field, ref type); } else { field.FieldIdDeltaRaw = tag & Tag.FieldIdMask; field.FieldTypeRaw = default; type = "Expected"; } return (field, type); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ReadFieldHeaderForAnalysisSlow(ref this Reader reader, scoped ref Field field, scoped ref string type) { var fieldId = field.Tag.FieldIdDelta; if (fieldId == Tag.FieldIdCompleteMask) { field.FieldIdDeltaRaw = reader.ReadVarUInt32NoInlining(); } else { field.FieldIdDeltaRaw = fieldId; } // If schema type is valid, read the type. var schemaType = field.Tag.SchemaType; if (schemaType != SchemaType.Expected) { (field.FieldTypeRaw, type) = reader.ReadTypeForAnalysis(schemaType); } else { field.FieldTypeRaw = default; type = "Expected"; } } } } ================================================ FILE: src/Orleans.Serialization/Buffers/Writer.TagDelimitedField.cs ================================================ using System; using System.Runtime.CompilerServices; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Buffers { public ref partial struct Writer { /// /// Writes the start object tag. /// /// The field identifier. /// The expected type. /// The actual type. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteStartObject(uint fieldId, Type expectedType, Type actualType) => WriteFieldHeader(fieldId, expectedType, actualType, WireType.TagDelimited); /// /// Writes the end object tag. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteEndObject() => WriteByte((byte)WireType.Extended | (byte)ExtendedWireType.EndTagDelimited); /// /// Writes the end base tag. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteEndBase() => WriteByte((byte)WireType.Extended | (byte)ExtendedWireType.EndBaseFields); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Writer.VarInt.cs ================================================ using System.Runtime.CompilerServices; namespace Orleans.Serialization.Buffers { public ref partial struct Writer { /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarInt8(sbyte value) => WriteVarUInt28(ZigZagEncode(value)); /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarInt16(short value) => WriteVarUInt28(ZigZagEncode(value)); /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarInt32(int value) => WriteVarUInt32(ZigZagEncode(value)); /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarInt64(long value) => WriteVarUInt64(ZigZagEncode(value)); /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarUInt8(byte value) => WriteVarUInt28(value); /// /// Writes a variable-width . /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarUInt16(ushort value) => WriteVarUInt28(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ZigZagEncode(int value) => (uint)((value << 1) ^ (value >> 31)); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong ZigZagEncode(long value) => (ulong)((value << 1) ^ (value >> 63)); } } ================================================ FILE: src/Orleans.Serialization/Buffers/Writer.cs ================================================ using System; using System.Buffers; using System.Buffers.Binary; using System.Diagnostics; using System.IO; #if NETCOREAPP3_1_OR_GREATER using System.Numerics; #else using Orleans.Serialization.Utilities; #endif using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Session; namespace Orleans.Serialization.Buffers { /// /// Helper methods for creating instances. /// public static class Writer { /// /// Creates a writer which writes to the specified destination. /// /// The buffer writer output type. /// The destination. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(TBufferWriter destination, SerializerSession session) where TBufferWriter : IBufferWriter => new(destination, session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(MemoryStream destination, SerializerSession session) => new(new MemoryStreamBufferWriter(destination), session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// The size hint. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer CreatePooled(Stream destination, SerializerSession session, int sizeHint = 0) => new(new PoolingStreamBufferWriter(destination, sizeHint), session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// The size hint. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(Stream destination, SerializerSession session, int sizeHint = 0) => new(new ArrayStreamBufferWriter(destination, sizeHint), session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(byte[] output, SerializerSession session) => Create(output.AsSpan(), session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(Memory output, SerializerSession session) => new(new MemoryBufferWriter(output), session); /// /// Creates a writer which writes to the specified destination. /// /// The destination. /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer Create(Span output, SerializerSession session) => new(new SpanBufferWriter(output), output, session); /// /// Creates a writer which writes to a pooled buffer. /// /// The session. /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer CreatePooled(SerializerSession session) => new(new PooledBuffer(), session); } /// /// Provides functionality for writing to an output stream. /// /// The underlying buffer writer type. public ref partial struct Writer where TBufferWriter : IBufferWriter { /// /// The output buffer writer. /// /// /// Modifying the output directly may corrupt the state of the writer. /// public TBufferWriter Output; /// /// The current write span. /// private Span _currentSpan; /// /// The buffer position within the current span. /// private int _bufferPos; /// /// The previous buffer's size. /// private int _previousBuffersSize; /// /// Max segment buffer size hint (1MB) /// internal const int MaxMultiSegmentSizeHint = 1024 * 1024; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Writer(TBufferWriter output, SerializerSession session) { Debug.Assert(output is not SpanBufferWriter); Output = output; Session = session; _currentSpan = Output.GetSpan(); _bufferPos = default; _previousBuffersSize = default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Writer(TBufferWriter output, Span span, SerializerSession session) { Debug.Assert(output is SpanBufferWriter); Output = output; Session = session; _currentSpan = span; _bufferPos = default; _previousBuffersSize = default; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { // Avoid boxing the struct, for better perf and codegen. if (typeof(TBufferWriter).IsValueType) { if (Output is IDisposable) { ((IDisposable)Output).Dispose(); } } else { (Output as IDisposable)?.Dispose(); } } /// /// Gets the serializer session. /// /// The serializer session. public SerializerSession Session { get; } /// /// Gets the position. /// /// The position. public readonly int Position => _previousBuffersSize + _bufferPos; /// /// Gets the current writable span. /// /// The current writable span. public readonly Span WritableSpan { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _currentSpan[_bufferPos..]; } /// /// Advance the write position in the current span. /// /// The number of bytes to advance wirte position by. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AdvanceSpan(int length) => _bufferPos += length; /// /// Commit the currently written buffers. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Commit() { Output.Advance(_bufferPos); if (!typeof(TBufferWriter).IsValueType || typeof(TBufferWriter) != typeof(SpanBufferWriter)) { _previousBuffersSize += _bufferPos; _currentSpan = default; _bufferPos = default; } } /// /// Ensures that there are at least contiguous bytes available to be written. /// /// The number of contiguous bytes to ensure. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureContiguous(int length) { // The current buffer is adequate. if (_bufferPos + length <= _currentSpan.Length) { return; } // The current buffer is inadequate, allocate another. Allocate(length); #if DEBUG // Throw if the allocation does not satisfy the request. if (_currentSpan.Length < length) throw new InvalidOperationException($"Requested buffer length {length} cannot be satisfied by the writer."); #endif } /// /// Allocates buffer space for the specified number of bytes. /// /// The number of bytes to reserve. [MethodImpl(MethodImplOptions.NoInlining)] public void Allocate(int sizeHint) { // Commit the bytes which have been written. Output.Advance(_bufferPos); // Request a new buffer with at least the requested number of available bytes. _currentSpan = Output.GetSpan(sizeHint); // Update internal state for the new buffer. _previousBuffersSize += _bufferPos; _bufferPos = 0; } /// /// Writes the specified value. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(scoped ReadOnlySpan value) { // Fast path, try copying to the current buffer. var destination = WritableSpan; if ((uint)value.Length <= (uint)destination.Length) { value.CopyTo(destination); _bufferPos += value.Length; } else { WriteMultiSegment(value); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteMultiSegment(scoped ReadOnlySpan input) { while (true) { // Write as much as possible/necessary into the current segment. var span = WritableSpan; var writeSize = Math.Min(span.Length, input.Length); input[..writeSize].CopyTo(span); _bufferPos += writeSize; input = input[writeSize..]; if (input.Length == 0) { return; } // The current segment is full but there is more to write. Allocate(Math.Min(input.Length, MaxMultiSegmentSizeHint)); } } /// /// Writes the provided to the output buffer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteByte(byte value) { nuint bufferPos = (uint)_bufferPos; if ((uint)bufferPos < (uint)_currentSpan.Length) { // https://github.com/dotnet/runtime/issues/72004 Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), bufferPos) = value; _bufferPos = (int)(uint)bufferPos + 1; } else { WriteByteSlow(value); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteByteSlow(byte value) { Allocate(1); _currentSpan[0] = value; _bufferPos = 1; } /// /// Writes the provided to the output buffer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteInt32(int value) => WriteUInt32((uint)value); /// /// Writes the provided to the output buffer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteInt64(long value) => WriteUInt64((ulong)value); /// /// Writes the provided to the output buffer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteUInt32(uint value) { nuint pos = (uint)_bufferPos; int newPos = (int)(uint)pos + sizeof(uint); if ((uint)newPos <= (uint)_currentSpan.Length) { _bufferPos = newPos; if (!BitConverter.IsLittleEndian) value = BinaryPrimitives.ReverseEndianness(value); Unsafe.WriteUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos), value); } else { WriteUInt32Slow(value); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteUInt32Slow(uint value) { Allocate(sizeof(uint)); BinaryPrimitives.WriteUInt32LittleEndian(_currentSpan, value); _bufferPos = sizeof(uint); } /// /// Writes the provided to the output buffer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteUInt64(ulong value) { nuint pos = (uint)_bufferPos; int newPos = (int)(uint)pos + sizeof(ulong); if ((uint)newPos <= (uint)_currentSpan.Length) { _bufferPos = newPos; if (!BitConverter.IsLittleEndian) value = BinaryPrimitives.ReverseEndianness(value); Unsafe.WriteUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos), value); } else { WriteUInt64Slow(value); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteUInt64Slow(ulong value) { Allocate(sizeof(ulong)); BinaryPrimitives.WriteUInt64LittleEndian(_currentSpan, value); _bufferPos = sizeof(ulong); } /// /// Writes a 7-bit unsigned value as a variable-width integer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteVarUInt7(uint value) { Debug.Assert(value < 1 << 7); WriteByte((byte)((value << 1) + 1)); } /// /// Writes a 28-bit unsigned value as a variable-width integer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteVarUInt28(uint value) { Debug.Assert(value < 1 << 28); var neededBytes = (int)((uint)BitOperations.Log2(value) / 7); uint lower = ((value << 1) + 1) << neededBytes; nuint pos = (uint)_bufferPos; if ((uint)pos + sizeof(uint) <= (uint)_currentSpan.Length) { _bufferPos = (int)(uint)pos + neededBytes + 1; if (!BitConverter.IsLittleEndian) lower = BinaryPrimitives.ReverseEndianness(lower); Unsafe.WriteUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos), lower); } else { WriteVarUInt28Slow(lower); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteVarUInt28Slow(uint lower) { Allocate(sizeof(uint)); var neededBytes = BitOperations.TrailingZeroCount(lower) + 1; BinaryPrimitives.WriteUInt32LittleEndian(_currentSpan, lower); _bufferPos = neededBytes; } /// /// Writes the provided to the output buffer as a variable-width integer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarUInt32(uint value) { var neededBytes = (int)((uint)BitOperations.Log2(value) / 7); ulong lower = (((ulong)value << 1) + 1) << neededBytes; nuint pos = (uint)_bufferPos; if ((uint)pos + sizeof(ulong) <= (uint)_currentSpan.Length) { _bufferPos = (int)(uint)pos + neededBytes + 1; if (!BitConverter.IsLittleEndian) lower = BinaryPrimitives.ReverseEndianness(lower); Unsafe.WriteUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos), lower); } else { WriteVarUInt56Slow(lower); } } /// /// Writes a 56-bit unsigned value as a variable-width integer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteVarUInt56(ulong value) { Debug.Assert(value < 1UL << 56); var neededBytes = (int)((uint)BitOperations.Log2(value) / 7); ulong lower = ((value << 1) + 1) << neededBytes; nuint pos = (uint)_bufferPos; if ((uint)pos + sizeof(ulong) <= (uint)_currentSpan.Length) { _bufferPos = (int)(uint)pos + neededBytes + 1; if (!BitConverter.IsLittleEndian) lower = BinaryPrimitives.ReverseEndianness(lower); Unsafe.WriteUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos), lower); } else { WriteVarUInt56Slow(lower); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteVarUInt56Slow(ulong lower) { Allocate(sizeof(ulong)); var neededBytes = BitOperations.TrailingZeroCount((uint)lower) + 1; BinaryPrimitives.WriteUInt64LittleEndian(_currentSpan, lower); _bufferPos = neededBytes; } /// /// Writes the provided to the output buffer as a variable-width integer. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarUInt64(ulong value) { nuint pos = (uint)_bufferPos; // Since this method writes a ulong plus a ushort worth of bytes unconditionally, ensure that there is sufficient space. if ((uint)pos + sizeof(ulong) + sizeof(ushort) <= (uint)_currentSpan.Length) { var neededBytes = (int)((uint)BitOperations.Log2(value) / 7); _bufferPos = (int)(uint)pos + neededBytes + 1; ulong lower = ((value << 1) + 1) << neededBytes; ref var writeHead = ref Unsafe.Add(ref MemoryMarshal.GetReference(_currentSpan), pos); if (!BitConverter.IsLittleEndian) lower = BinaryPrimitives.ReverseEndianness(lower); Unsafe.WriteUnaligned(ref writeHead, lower); // Write the 2 byte overflow unconditionally var upper = value >> (63 - neededBytes); writeHead = ref Unsafe.Add(ref writeHead, sizeof(ulong)); if (!BitConverter.IsLittleEndian) upper = BinaryPrimitives.ReverseEndianness((ushort)upper); Unsafe.WriteUnaligned(ref writeHead, (ushort)upper); } else { WriteVarUInt64Slow(value); } } [MethodImpl(MethodImplOptions.NoInlining)] private void WriteVarUInt64Slow(ulong value) { Allocate(sizeof(ulong) + sizeof(ushort)); var neededBytes = (int)((uint)BitOperations.Log2(value) / 7); _bufferPos = neededBytes + 1; ulong lower = ((value << 1) + 1) << neededBytes; BinaryPrimitives.WriteUInt64LittleEndian(_currentSpan, lower); // Write the 2 byte overflow unconditionally var upper = value >> (63 - neededBytes); BinaryPrimitives.WriteUInt16LittleEndian(_currentSpan[sizeof(ulong)..], (ushort)upper); } } } ================================================ FILE: src/Orleans.Serialization/Cloning/IDeepCopier.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net; using System.Reflection; using System.Threading; using Microsoft.Extensions.ObjectPool; using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities; namespace Orleans.Serialization.Cloning { /// /// Provides instances. /// public interface IDeepCopierProvider { /// /// Gets a deep copier capable of copying instances of type . /// /// The type supported by the copier. /// A deep copier capable of copying instances of type . IDeepCopier GetDeepCopier(); /// /// Gets a deep copier capable of copying instances of type , or returns if an appropriate copier was not found. /// /// The type supported by the copier. /// A deep copier capable of copying instances of type , or if an appropriate copier was not found. IDeepCopier? TryGetDeepCopier(); /// /// Gets a deep copier capable of copying instances of type . /// /// /// The type supported by the returned copier. /// /// A deep copier capable of copying instances of type . IDeepCopier GetDeepCopier(Type type); /// /// Gets a deep copier capable of copying instances of type , or returns if an appropriate copier was not found. /// /// /// The type supported by the returned copier. /// /// A deep copier capable of copying instances of type , or if an appropriate copier was not found. IDeepCopier? TryGetDeepCopier(Type type); /// /// Gets a base type copier capable of copying instances of type . /// /// /// The type supported by the returned copier. /// /// A base type copier capable of copying instances of type . IBaseCopier GetBaseCopier() where T : class; } /// /// Marker type for deep copiers. /// public interface IDeepCopier { /// /// Creates a deep copy of the provided untyped input. The type must still match the copier instance! /// object? DeepCopy(object? input, CopyContext context); } /// /// Marker interface for deep copiers of types that could optionally be shallow-copyable. /// public interface IOptionalDeepCopier : IDeepCopier { /// /// Returns true if the type is shallow-copyable. /// bool IsShallowCopyable(); } internal sealed class ShallowCopier : IOptionalDeepCopier { public static readonly ShallowCopier Instance = new(); public bool IsShallowCopyable() => true; public object? DeepCopy(object? input, CopyContext _) => input; } /// /// Base type for deep copiers of types that are actually shallow-copyable. /// public class ShallowCopier : IOptionalDeepCopier, IDeepCopier { public bool IsShallowCopyable() => true; /// Returns the input value. public T DeepCopy(T input, CopyContext _) => input; /// Returns the input value. public object? DeepCopy(object? input, CopyContext _) => input; } /// /// Provides functionality for creating clones of objects of type . /// /// The type of objects which this instance can copy. /// public interface IDeepCopier : IDeepCopier { /// /// Creates a deep copy of the provided input. /// /// The input. /// The context. /// A copy of . T DeepCopy(T input, CopyContext context); object? IDeepCopier.DeepCopy(object? input, CopyContext context) => input is null ? null : DeepCopy((T)input, context); } /// /// Marker type for base type copiers. /// public interface IBaseCopier { } /// /// Provides functionality for copying members from one object to another. /// /// The type of objects which this instance can copy. /// public interface IBaseCopier : IBaseCopier where T : class { /// /// Clones members from and copies them to . /// /// The input. /// The output. /// The context. void DeepCopy(T input, T output, CopyContext context); } /// /// Indicates that an implementation generalizes over all sub-types. /// public interface IDerivedTypeCopier : IDeepCopier { } /// /// Provides functionality for copying objects of multiple types. /// public interface IGeneralizedCopier : IDeepCopier { /// /// Returns a value indicating whether the provided type is supported by this implementation. /// /// The type. /// if the type is supported type by this implementation; otherwise, . bool IsSupportedType(Type type); } /// /// Provides functionality for creating instances which support a given type. /// public interface ISpecializableCopier { /// /// Returns a value indicating whether the provided type is supported by this implementation. /// /// The type. /// if the type is supported type by this implementation; otherwise, . bool IsSupportedType(Type type); /// /// Gets an implementation which supports the specified type. /// /// The type. /// An implementation which supports the specified type. IDeepCopier GetSpecializedCopier(Type type); } /// /// Provides context for a copy operation. /// /// /// /// Initializes a new instance of the class. /// /// The codec provider. /// The action to call when this context is disposed. public sealed class CopyContext(CodecProvider codecProvider, Action onDisposed) : IDisposable { private readonly Dictionary _copies = new(ReferenceEqualsComparer.Default); private readonly CodecProvider _copierProvider = codecProvider; private readonly Action _onDisposed = onDisposed; /// /// Returns the previously recorded copy of the provided object, if it exists. /// /// The object type. /// The original object. /// The previously recorded copy of . /// if a copy of has been recorded, otherwise. public bool TryGetCopy(object? original, out T? result) where T : class { if (original is null) { result = null; return true; } if (_copies.TryGetValue(original, out var existing)) { result = existing as T; return true; } result = null; return false; } /// /// Records a copy of an object. /// /// The original value. /// The copy of . public void RecordCopy(object original, object copy) { _copies[original] = copy; } /// /// Resets this instance. /// public void Reset() => _copies.Clear(); /// /// Copies the provided value. /// /// The value type. /// The value. /// A copy of the provided value. public T? DeepCopy(T? value) { if (!typeof(T).IsValueType) { if (value is null) return default; } var copier = _copierProvider.GetDeepCopier(value!.GetType()); return (T?)copier.DeepCopy(value, this); } /// public void Dispose() => _onDisposed?.Invoke(this); } internal static class ShallowCopyableTypes { private static readonly ConcurrentDictionary Types = new() { [typeof(decimal)] = true, [typeof(DateTime)] = true, #if NET6_0_OR_GREATER [typeof(DateOnly)] = true, [typeof(TimeOnly)] = true, #endif [typeof(DateTimeOffset)] = true, [typeof(TimeSpan)] = true, [typeof(IPAddress)] = true, [typeof(IPEndPoint)] = true, [typeof(string)] = true, [typeof(CancellationToken)] = true, [typeof(Guid)] = true, [typeof(BitVector32)] = true, [typeof(CompareInfo)] = true, [typeof(CultureInfo)] = true, [typeof(Version)] = true, [typeof(Uri)] = true, #if NET7_0_OR_GREATER [typeof(UInt128)] = true, [typeof(Int128)] = true, #endif [typeof(System.Numerics.BigInteger)] = true, #if NET5_0_OR_GREATER [typeof(Half)] = true, #endif }; public static bool Contains(Type type) { if (Types.TryGetValue(type, out var result)) { return result; } return Types.GetOrAdd(type, IsShallowCopyableInternal(type)); } private static bool IsShallowCopyableInternal(Type type) { if (type.IsPrimitive || type.IsEnum) { return true; } if (type.IsSealed && type.IsDefined(typeof(ImmutableAttribute), false)) { return true; } if (type.IsConstructedGenericType) { var def = type.GetGenericTypeDefinition(); if (def == typeof(Nullable<>) || def == typeof(Tuple<>) || def == typeof(Tuple<,>) || def == typeof(Tuple<,,>) || def == typeof(Tuple<,,,>) || def == typeof(Tuple<,,,,>) || def == typeof(Tuple<,,,,,>) || def == typeof(Tuple<,,,,,,>) || def == typeof(Tuple<,,,,,,,>)) { return Array.TrueForAll(type.GenericTypeArguments, a => Contains(a)); } } if (type.IsValueType && !type.IsGenericTypeDefinition) { return Array.TrueForAll(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), f => Contains(f.FieldType)); } if (typeof(Exception).IsAssignableFrom(type)) return true; if (typeof(Type).IsAssignableFrom(type)) return true; return false; } } /// /// Converts an untyped copier into a strongly-typed copier. /// internal sealed class UntypedCopierWrapper(IDeepCopier copier) : IDeepCopier { private readonly IDeepCopier _copier = copier; public T DeepCopy(T original, CopyContext context) => (T)_copier.DeepCopy(original, context)!; public object? DeepCopy(object? original, CopyContext context) => _copier.DeepCopy(original, context); } /// /// Object pool for instances. /// public sealed class CopyContextPool { private readonly ConcurrentObjectPool _pool; /// /// Initializes a new instance of the class. /// /// The codec provider. public CopyContextPool(CodecProvider codecProvider) { var sessionPoolPolicy = new PoolPolicy(codecProvider, Return); _pool = new ConcurrentObjectPool(sessionPoolPolicy); } /// /// Gets a . /// /// A . public CopyContext GetContext() => _pool.Get(); /// /// Returns the specified copy context to the pool. /// /// The context. private void Return(CopyContext context) => _pool.Return(context); private readonly struct PoolPolicy(CodecProvider codecProvider, Action onDisposed) : IPooledObjectPolicy { private readonly CodecProvider _codecProvider = codecProvider; private readonly Action _onDisposed = onDisposed; public CopyContext Create() => new(_codecProvider, _onDisposed); public bool Return(CopyContext obj) { obj.Reset(); return true; } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ArrayCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for arrays of rank 1. /// /// The element type. [RegisterSerializer] public sealed class ArrayCodec : IFieldCodec { private readonly IFieldCodec _fieldCodec; private readonly Type CodecElementType = typeof(T); /// /// Initializes a new instance of the class. /// /// The field codec. public ArrayCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, T[] value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Length > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Length); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public T[] ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); T[] result = null; uint fieldId = 0; var length = 0; var index = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new T[length]; ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } if (index >= length) { ThrowIndexOutOfRangeException(length); } result[index] = _fieldCodec.ReadValue(ref reader, header); ++index; break; default: reader.ConsumeUnknownField(header); break; } } if (result is null) { result = Array.Empty(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } private static void ThrowIndexOutOfRangeException(int length) => throw new IndexOutOfRangeException( $"Encountered too many elements in array of type {typeof(T[])} with declared length {length}."); private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(T[])}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); } /// /// Copier for arrays of rank 1. /// /// The element type. [RegisterCopier] public sealed class ArrayCopier : IDeepCopier { private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public ArrayCopier(IDeepCopier elementCopier) { _elementCopier = OrleansGeneratedCodeHelper.UnwrapService(this, elementCopier); } /// public T[] DeepCopy(T[] input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } result = new T[input.Length]; context.RecordCopy(input, result); for (var i = 0; i < input.Length; i++) { result[i] = _elementCopier.DeepCopy(input[i], context); } return result; } } /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ReadOnlyMemoryCodec : IFieldCodec> { private readonly Type CodecType = typeof(ReadOnlyMemory); private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public ReadOnlyMemoryCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ReadOnlyMemory value) where TBufferWriter : IBufferWriter { if (!MemoryMarshal.TryGetArray(value, out var segment) || segment.Array.Length != value.Length) { ReferenceCodec.MarkValueField(writer.Session); } else if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, CodecType, segment.Array)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, CodecType, WireType.TagDelimited); UInt32Codec.WriteField(ref writer, 0, (uint)value.Length); uint innerFieldIdDelta = 1; foreach (var element in value.Span) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } writer.WriteEndObject(); } /// public ReadOnlyMemory ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); T[] result = null; uint fieldId = 0; var length = 0; var index = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new T[length]; ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } if (index >= length) { ThrowIndexOutOfRangeException(length); } result[index] = _fieldCodec.ReadValue(ref reader, header); ++index; break; default: reader.ConsumeUnknownField(header); break; } } return result; } private static void ThrowIndexOutOfRangeException(int length) => throw new IndexOutOfRangeException( $"Encountered too many elements in array of type {typeof(T[])} with declared length {length}."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(ReadOnlyMemory)}, {length}, is greater than total length of input."); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ReadOnlyMemoryCopier : IDeepCopier> { private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public ReadOnlyMemoryCopier(IDeepCopier elementCopier) { _elementCopier = OrleansGeneratedCodeHelper.UnwrapService(this, elementCopier); } /// public ReadOnlyMemory DeepCopy(ReadOnlyMemory input, CopyContext context) { if (input.IsEmpty) { return input; } var inputSpan = input.Span; var result = new T[inputSpan.Length]; // Note that there is a possibility for unbounded recursion if the underlying object in the input is // able to take part in a cyclic reference. If we could get that object then we could prevent that cycle. // It is also possible that an IMemoryOwner is the backing object, in which case this will not work. if (MemoryMarshal.TryGetArray(input, out var segment) && segment.Array.Length == result.Length) { if (context.TryGetCopy(segment.Array, out T[] existing)) return existing; context.RecordCopy(segment.Array, result); } for (var i = 0; i < inputSpan.Length; i++) { result[i] = _elementCopier.DeepCopy(inputSpan[i], context); } return result; } } /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class MemoryCodec : IFieldCodec> { private readonly Type CodecType = typeof(Memory); private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public MemoryCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Memory value) where TBufferWriter : IBufferWriter { if (!MemoryMarshal.TryGetArray(value, out var segment) || segment.Array.Length != value.Length) { ReferenceCodec.MarkValueField(writer.Session); } else if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, CodecType, segment.Array)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, CodecType, WireType.TagDelimited); UInt32Codec.WriteField(ref writer, 0, (uint)value.Length); uint innerFieldIdDelta = 1; foreach (var element in value.Span) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } writer.WriteEndObject(); } /// public Memory ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); T[] result = null; uint fieldId = 0; var length = 0; var index = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new T[length]; ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } if (index >= length) { ThrowIndexOutOfRangeException(length); } result[index] = _fieldCodec.ReadValue(ref reader, header); ++index; break; default: reader.ConsumeUnknownField(header); break; } } return result; } private static void ThrowIndexOutOfRangeException(int length) => throw new IndexOutOfRangeException( $"Encountered too many elements in array of type {typeof(T[])} with declared length {length}."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(Memory)}, {length}, is greater than total length of input."); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class MemoryCopier : IDeepCopier> { private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public MemoryCopier(IDeepCopier elementCopier) { _elementCopier = OrleansGeneratedCodeHelper.UnwrapService(this, elementCopier); } /// public Memory DeepCopy(Memory input, CopyContext context) { if (input.IsEmpty) { return input; } var inputSpan = input.Span; var result = new T[inputSpan.Length]; // Note that there is a possibility for unbounded recursion if the underlying object in the input is // able to take part in a cyclic reference. If we could get that object then we could prevent that cycle. // It is also possible that an IMemoryOwner is the backing object, in which case this will not work. if (MemoryMarshal.TryGetArray(input, out var segment) && segment.Array.Length == result.Length) { if (context.TryGetCopy(segment.Array, out T[] existing)) return existing; context.RecordCopy(segment.Array, result); } for (var i = 0; i < inputSpan.Length; i++) { result[i] = _elementCopier.DeepCopy(inputSpan[i], context); } return result; } } /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ArraySegmentCodec : IFieldCodec> { private readonly Type CodecType = typeof(ArraySegment); private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public ArraySegmentCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ArraySegment value) where TBufferWriter : IBufferWriter { if (value.Array?.Length != value.Count) { ReferenceCodec.MarkValueField(writer.Session); } else if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, CodecType, value.Array)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, CodecType, WireType.TagDelimited); if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value.AsSpan()) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public ArraySegment ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); T[] result = null; uint fieldId = 0; var length = 0; var index = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new T[length]; ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } if (index >= length) { ThrowIndexOutOfRangeException(length); } result[index] = _fieldCodec.ReadValue(ref reader, header); ++index; break; default: reader.ConsumeUnknownField(header); break; } } return result; } private static void ThrowIndexOutOfRangeException(int length) => throw new IndexOutOfRangeException( $"Encountered too many elements in array of type {typeof(T[])} with declared length {length}."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(ArraySegment)}, {length}, is greater than total length of input."); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ArraySegmentCopier : IDeepCopier> { private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public ArraySegmentCopier(IDeepCopier elementCopier) { _elementCopier = OrleansGeneratedCodeHelper.UnwrapService(this, elementCopier); } /// public ArraySegment DeepCopy(ArraySegment input, CopyContext context) { if (input.Array is null) { return input; } var inputSpan = input.AsSpan(); var result = new T[inputSpan.Length]; context.RecordCopy(input.Array, result); for (var i = 0; i < inputSpan.Length; i++) { result[i] = _elementCopier.DeepCopy(inputSpan[i], context); } return new ArraySegment(result); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ArrayListCodec.cs ================================================ using System.Collections; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class ArrayListCodec : GeneralizedReferenceTypeSurrogateCodec { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ArrayListCodec(IValueSerializer surrogateSerializer) : base(surrogateSerializer) { } /// public override ArrayList ConvertFromSurrogate(ref ArrayListSurrogate surrogate) => new(surrogate.Values); /// public override void ConvertToSurrogate(ArrayList value, ref ArrayListSurrogate surrogate) => surrogate.Values = value.ToArray(); } /// /// Surrogate type used by . /// [GenerateSerializer] public struct ArrayListSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public object[] Values; } /// /// Copier for . /// [RegisterCopier] public sealed class ArrayListCopier : IDeepCopier, IBaseCopier { /// public ArrayList DeepCopy(ArrayList input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } if (input.GetType() != typeof(ArrayList)) { return context.DeepCopy(input); } result = new ArrayList(input.Count); context.RecordCopy(input, result); foreach (var item in input) { result.Add(ObjectCopier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(ArrayList input, ArrayList output, CopyContext context) { foreach (var item in input) { output.Add(ObjectCopier.DeepCopy(item, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/BigIntegerCodec.cs ================================================ using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs; /// /// Serializer for . /// [RegisterSerializer] public sealed class BigIntegerCodec : IFieldCodec { /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, BigInteger value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(BigInteger), WireType.LengthPrefixed); WriteField(ref writer, value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, BigInteger value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); WriteField(ref writer, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteField(ref Writer writer, BigInteger value) where TBufferWriter : IBufferWriter { var byteCount = value.GetByteCount(); writer.WriteVarUInt32((uint)byteCount); writer.EnsureContiguous(byteCount); if (value.TryWriteBytes(writer.WritableSpan, out var bytesWritten)) { writer.AdvanceSpan(bytesWritten); } else { writer.Write(value.ToByteArray()); } } /// BigInteger IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BigInteger ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); if (field.WireType != WireType.LengthPrefixed) { throw new UnexpectedLengthPrefixValueException(nameof(BigInteger), 0, 0); } var length = reader.ReadVarUInt32(); var bytes = reader.ReadBytes(length); return new BigInteger(bytes); } } ================================================ FILE: src/Orleans.Serialization/Codecs/BitVector32Codec.cs ================================================ using System; using System.Buffers; using System.Collections.Specialized; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class BitVector32Codec : IFieldCodec { /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, BitVector32 value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(BitVector32), WireType.Fixed32); writer.WriteInt32(value.Data); // BitVector32.Data gets the value of the BitVector32 as an Int32 } /// public BitVector32 ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.Fixed32); return new BitVector32(reader.ReadInt32()); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ByteArrayCodec.cs ================================================ using System; using System.Buffers; using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for arrays. /// [RegisterSerializer] public sealed partial class BitArrayCodec : IFieldCodec { #if NET10_0_OR_GREATER #elif NET8_0_OR_GREATER [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_array")] extern static ref int[] GetSetArray(BitArray bitArray); #else private static int[] GetSetArray(BitArray bitArray) => typeof(BitArray).GetField("m_array", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(bitArray) as int[]; #endif BitArray IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static BitArray ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var numBytes = reader.ReadVarUInt32(); var result = new BitArray((int)numBytes * 8, false); #if NET10_0_OR_GREATER reader.ReadBytes(CollectionsMarshal.AsBytes(result)[..(int)numBytes]); #else var resultArray = GetSetArray(result); reader.ReadBytes(MemoryMarshal.AsBytes(resultArray.AsSpan()).Slice(0, (int)numBytes)); #endif ReferenceCodec.RecordObject(reader.Session, result); return result; } void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, BitArray value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(BitArray), WireType.LengthPrefixed); #if NET10_0_OR_GREATER var bytes = CollectionsMarshal.AsBytes(value); writer.WriteVarUInt32((uint)bytes.Length); writer.Write(bytes); #else var numBytes = GetByteArrayLengthFromBitLength(value.Length); writer.WriteVarUInt32((uint)numBytes); writer.Write(MemoryMarshal.AsBytes(GetSetArray(value).AsSpan()).Slice(0, numBytes)); #endif #if !NET10_0_OR_GREATER static int GetByteArrayLengthFromBitLength(int n) { const int BitShiftPerByte = 3; Debug.Assert(n >= 0); return (int)((uint)(n - 1 + (1 << BitShiftPerByte)) >> BitShiftPerByte); } #endif } } /// /// Copier for arrays. /// [RegisterCopier] public sealed class BitArrayCopier : IDeepCopier { /// BitArray IDeepCopier.DeepCopy(BitArray input, CopyContext context) => DeepCopy(input, context); /// /// Creates a deep copy of the provided input. /// /// The input. /// The context. /// A copy of . public static BitArray DeepCopy(BitArray input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } result = new(input); context.RecordCopy(input, result); return result; } } /// /// Serializer for arrays. /// [RegisterSerializer] public sealed class ByteArrayCodec : IFieldCodec { byte[] IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static byte[] ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var length = reader.ReadVarUInt32(); var result = reader.ReadBytes(length); ReferenceCodec.RecordObject(reader.Session, result); return result; } void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, byte[] value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(byte[]), WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); writer.Write(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, byte[] value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) { return; } writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); writer.Write(value); } } /// /// Copier for arrays. /// [RegisterCopier] public sealed class ByteArrayCopier : IDeepCopier { /// byte[] IDeepCopier.DeepCopy(byte[] input, CopyContext context) => DeepCopy(input, context); /// /// Creates a deep copy of the provided input. /// /// The input. /// The context. /// A copy of . public static byte[] DeepCopy(byte[] input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } result = new byte[input.Length]; context.RecordCopy(input, result); input.CopyTo(result.AsSpan()); return result; } } /// /// Serializer for . /// [RegisterSerializer] public sealed class ReadOnlyMemoryOfByteCodec : IFieldCodec> { /// ReadOnlyMemory IFieldCodec>.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static byte[] ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var length = reader.ReadVarUInt32(); var result = reader.ReadBytes(length); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ReadOnlyMemory value) => WriteField(ref writer, fieldIdDelta, expectedType, value); /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, ReadOnlyMemory value) where TBufferWriter : IBufferWriter => WriteField(ref writer, fieldIdDelta, typeof(ReadOnlyMemory), value); private static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ReadOnlyMemory value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(ReadOnlyMemory), WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); writer.Write(value.Span); } } /// /// Copier for . /// [RegisterCopier] public sealed class ReadOnlyMemoryOfByteCopier : IDeepCopier> { /// ReadOnlyMemory IDeepCopier>.DeepCopy(ReadOnlyMemory input, CopyContext _) => DeepCopy(input, _); /// /// Copies the input. /// /// The input. /// The copy context. /// A copy of the input. public static ReadOnlyMemory DeepCopy(ReadOnlyMemory input, CopyContext copyContext) { if (input.IsEmpty) { return default; } return input.ToArray(); } } /// /// Copier for . /// [RegisterCopier] public sealed class ArraySegmentOfByteCopier : IDeepCopier> { /// ArraySegment IDeepCopier>.DeepCopy(ArraySegment input, CopyContext _) => DeepCopy(input, _); /// /// Copies the input. /// /// The input. /// The copy context. /// A copy of the input. public static ArraySegment DeepCopy(ArraySegment input, CopyContext copyContext) { if (input.Array is null) { return default; } return input.AsSpan().ToArray(); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class MemoryOfByteCodec : IFieldCodec> { /// Memory IFieldCodec>.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static Memory ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var length = reader.ReadVarUInt32(); var result = reader.ReadBytes(length); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Memory value) => WriteField(ref writer, fieldIdDelta, expectedType, value); /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, Memory value) where TBufferWriter : IBufferWriter => WriteField(ref writer, fieldIdDelta, typeof(Memory), value); private static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Memory value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(Memory), WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); writer.Write(value.Span); } } /// /// Copier for of . /// [RegisterCopier] public sealed class MemoryOfByteCopier : IDeepCopier> { /// Memory IDeepCopier>.DeepCopy(Memory input, CopyContext _) => DeepCopy(input, _); /// /// Copies the input. /// /// The input. /// The copy context. /// A copy of the input. public static Memory DeepCopy(Memory input, CopyContext copyContext) { if (input.IsEmpty) { return default; } return input.ToArray(); } } /// /// Serializer for instances. /// [RegisterSerializer] public sealed class PooledBufferCodec : IFieldCodec { public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, PooledBuffer value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(PooledBuffer), WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); value.CopyTo(ref writer); // Dispose of the value after sending it. // PooledBuffer is special in this sense. // Senders must not use the value after sending. // Receivers must dispose of the value after use. value.Reset(); } public PooledBuffer ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.LengthPrefixed); var value = new PooledBuffer(); const int MaxSpanLength = 4096; var length = (int)reader.ReadVarUInt32(); while (length > 0) { var copied = Math.Min(length, MaxSpanLength); var span = value.GetSpan(copied)[..copied]; reader.ReadBytes(span); value.Advance(copied); length -= copied; } Debug.Assert(length == 0); return value; } } /// /// Copier for instances, which are assumed to be immutable. /// [RegisterCopier] public sealed class PooledBufferCopier : IDeepCopier, IOptionalDeepCopier { public PooledBuffer DeepCopy(PooledBuffer input, CopyContext context) => input; public bool IsShallowCopyable() => true; } } ================================================ FILE: src/Orleans.Serialization/Codecs/CollectionCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; using Orleans.Serialization.Serializers; namespace Orleans.Serialization.Codecs; /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class CollectionCodec : IFieldCodec>, IBaseCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public CollectionCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Collection value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); Serialize(ref writer, value); writer.WriteEndObject(); } /// public Collection ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); Collection result = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new Collection(new List(length)); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } result.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } if (result is null) { result = new(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(Collection)}, {length}, is greater than total length of input."); private void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); public void Serialize(ref Writer writer, Collection value) where TBufferWriter : IBufferWriter { if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } } public void Deserialize(ref Reader reader, Collection value) { // If the value has some values added by the constructor, clear them. // If those values are in the serialized payload, they will be added below. value.Clear(); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } break; case 1: value.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } } } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class CollectionCopier : IDeepCopier>, IBaseCopier> { private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public CollectionCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public Collection DeepCopy(Collection input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() != typeof(Collection)) { return context.DeepCopy(input); } result = new Collection(new List(input.Count)); context.RecordCopy(input, result); foreach (var item in input) { result.Add(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(Collection input, Collection output, CopyContext context) { output.Clear(); foreach (var item in input) { output.Add(_copier.DeepCopy(item, context)); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/CommonCodecTypeFilter.cs ================================================ using System; using System.CodeDom.Compiler; using System.Linq; using System.Reflection; using Orleans.Metadata; namespace Orleans.Serialization.Codecs; /// /// Defines common type filtering operations. /// public class CommonCodecTypeFilter { /// /// Returns true if the provided type is a framework or abstract type. /// /// The type to check. /// if the type is a framework or abstract type, otherwise . public static bool IsAbstractOrFrameworkType(Type type) { if (type.IsAbstract || type.GetCustomAttributes().Any(a => a.Tool.Equals("OrleansCodeGen")) || type.Assembly.GetCustomAttribute() is not null) { return true; } return false; } } ================================================ FILE: src/Orleans.Serialization/Codecs/CompareInfoCodec.cs ================================================ using System; using System.Buffers; using System.Globalization; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class CompareInfoCodec : IFieldCodec { /// public CompareInfo ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); uint fieldId = 0; string name = null; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: name = StringCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = CompareInfo.GetCompareInfo(name); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, CompareInfo value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); StringCodec.WriteField(ref writer, 0, value.Name); writer.WriteEndObject(); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ConcurrentDictionaryCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The type of the t key. /// The type of the t value. [RegisterSerializer] public sealed class ConcurrentDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ConcurrentDictionarySurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ConcurrentDictionaryCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ConcurrentDictionary ConvertFromSurrogate(ref ConcurrentDictionarySurrogate surrogate) => new(surrogate.Values, surrogate.Values.Comparer); /// public override void ConvertToSurrogate(ConcurrentDictionary value, ref ConcurrentDictionarySurrogate surrogate) #if NET6_0_OR_GREATER => surrogate.Values = new(value, value.Comparer); #else => surrogate.Values = new(value); #endif } /// /// Surrogate type used by . /// /// The key type. /// The value type. [GenerateSerializer] public struct ConcurrentDictionarySurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public Dictionary Values; } /// /// Copier for . /// /// The type of the t key. /// The type of the t value. [RegisterCopier] public sealed class ConcurrentDictionaryCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(ConcurrentDictionary); private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public ConcurrentDictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } /// public ConcurrentDictionary DeepCopy(ConcurrentDictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } #if NET6_0_OR_GREATER result = new(input.Comparer); #else result = new(); #endif context.RecordCopy(input, result); foreach (var pair in input) { result[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } return result; } /// public void DeepCopy(ConcurrentDictionary input, ConcurrentDictionary output, CopyContext context) { foreach (var pair in input) { output[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ConcurrentQueueCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ConcurrentQueueCodec : GeneralizedReferenceTypeSurrogateCodec, ConcurrentQueueSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ConcurrentQueueCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ConcurrentQueue ConvertFromSurrogate(ref ConcurrentQueueSurrogate surrogate) => new(surrogate.Values); /// public override void ConvertToSurrogate(ConcurrentQueue value, ref ConcurrentQueueSurrogate surrogate) => surrogate.Values = new(value); } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ConcurrentQueueSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public Queue Values; } /// /// Copier for . /// /// [RegisterCopier] public sealed class ConcurrentQueueCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(ConcurrentQueue); private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public ConcurrentQueueCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public ConcurrentQueue DeepCopy(ConcurrentQueue input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new ConcurrentQueue(); context.RecordCopy(input, result); foreach (var item in input) { result.Enqueue(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(ConcurrentQueue input, ConcurrentQueue output, CopyContext context) { foreach (var item in input) { output.Enqueue(_copier.DeepCopy(item, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ConsumeFieldExtension.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Extension methods for consuming unknown fields. /// public static class ConsumeFieldExtension { /// /// Consumes an unknown field. /// /// The reader input type. /// The reader. /// The field. public static void ConsumeUnknownField(this ref Reader reader, Field field) => ConsumeUnknownField(ref reader, ref field); /// /// Consumes an unknown field. /// /// The reader input type. /// The reader. /// The field. public static void ConsumeUnknownField(this ref Reader reader, scoped ref Field field) { // References cannot themselves be referenced. if (field.WireType == WireType.Reference) { ReferenceCodec.MarkValueField(reader.Session); _ = reader.ReadVarUInt32(); return; } // Record a placeholder so that this field can later be correctly deserialized if it is referenced. ReferenceCodec.RecordObject(reader.Session, new UnknownFieldMarker(field, reader.Position)); switch (field.WireType) { case WireType.VarInt: _ = reader.ReadVarUInt64(); break; case WireType.TagDelimited: // Since tag delimited fields can be comprised of other fields, recursively consume those, too. reader.ConsumeTagDelimitedField(); break; case WireType.LengthPrefixed: SkipFieldExtension.SkipLengthPrefixedField(ref reader); break; case WireType.Fixed32: reader.Skip(4); break; case WireType.Fixed64: reader.Skip(8); break; case WireType.Extended: SkipFieldExtension.ThrowUnexpectedExtendedWireType(field); break; default: SkipFieldExtension.ThrowUnexpectedWireType(field); break; } } /// /// Consumes a tag-delimited field. /// /// The reader input type. /// The reader. private static void ConsumeTagDelimitedField(this ref Reader reader) { while (true) { var field = reader.ReadFieldHeader(); if (field.IsEndObject) { break; } if (field.IsEndBaseFields) { continue; } reader.ConsumeUnknownField(field); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/CultureInfoCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System.Globalization; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class CultureInfoCodec : GeneralizedReferenceTypeSurrogateCodec { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public CultureInfoCodec(IValueSerializer surrogateSerializer) : base(surrogateSerializer) { } /// public override CultureInfo ConvertFromSurrogate(ref CultureInfoSurrogate surrogate) => new(surrogate.Name); /// public override void ConvertToSurrogate(CultureInfo value, ref CultureInfoSurrogate surrogate) => surrogate.Name = value.Name; } /// /// Surrogate type used by . /// [GenerateSerializer] public struct CultureInfoSurrogate { /// /// Gets or sets the name. /// /// The name. [Id(0)] public string Name; } [RegisterCopier] internal sealed class CultureInfoCopier : ShallowCopier, IDerivedTypeCopier { } } ================================================ FILE: src/Orleans.Serialization/Codecs/DateOnlyCodec.cs ================================================ #if NET6_0_OR_GREATER using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs; /// /// Serializer for . /// [RegisterSerializer] public sealed class DateOnlyCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, DateOnly value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(DateOnly), WireType.Fixed32); writer.WriteInt32(value.DayNumber); } /// /// Writes a field without type info (expected type is statically known). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, DateOnly value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed32); writer.WriteInt32(value.DayNumber); } /// DateOnly IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static DateOnly ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.Fixed32); return DateOnly.FromDayNumber(reader.ReadInt32()); } } #endif ================================================ FILE: src/Orleans.Serialization/Codecs/DateTimeCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class DateTimeCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, DateTime value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(DateTime), WireType.Fixed64); writer.WriteInt64(value.ToBinary()); } /// /// Writes a field without type info (expected type is statically known). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, DateTime value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed64); writer.WriteInt64(value.ToBinary()); } /// DateTime IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static DateTime ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.Fixed64); return DateTime.FromBinary(reader.ReadInt64()); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/DateTimeOffsetCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class DateTimeOffsetCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, DateTimeOffset value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(DateTimeOffset), WireType.TagDelimited); DateTimeCodec.WriteField(ref writer, 0, value.DateTime); TimeSpanCodec.WriteField(ref writer, 1, value.Offset); writer.WriteEndObject(); } /// /// Writes a field without type info (expected type is statically known). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, DateTimeOffset value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.TagDelimited); DateTimeCodec.WriteField(ref writer, 0, value.DateTime); TimeSpanCodec.WriteField(ref writer, 1, value.Offset); writer.WriteEndObject(); } /// DateTimeOffset IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// public static DateTimeOffset ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireTypeTagDelimited(); uint fieldId = 0; TimeSpan offset = default; DateTime dateTime = default; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: dateTime = DateTimeCodec.ReadValue(ref reader, header); break; case 1: offset = TimeSpanCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return new DateTimeOffset(dateTime, offset); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/DictionaryCodec.cs ================================================ #nullable enable using System; using System.Buffers; using System.Collections.Generic; using System.Reflection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class DictionaryCodec : IFieldCodec> where TKey : notnull { private readonly Type _keyFieldType = typeof(TKey); private readonly Type _valueFieldType = typeof(TValue); private readonly IFieldCodec _keyCodec; private readonly IFieldCodec _valueCodec; private readonly IFieldCodec> _comparerCodec; /// /// Initializes a new instance of the class. /// /// The key codec. /// The value codec. /// The comparer codec. public DictionaryCodec( IFieldCodec keyCodec, IFieldCodec valueCodec, IFieldCodec> comparerCodec) { _keyCodec = OrleansGeneratedCodeHelper.UnwrapService(this, keyCodec); _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); _comparerCodec = OrleansGeneratedCodeHelper.UnwrapService(this, comparerCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Dictionary value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Comparer is var comparer && comparer != EqualityComparer.Default) { _comparerCodec.WriteField(ref writer, 0, null, comparer); } if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 1, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _keyCodec.WriteField(ref writer, innerFieldIdDelta, _keyFieldType, element.Key); _valueCodec.WriteField(ref writer, 0, _valueFieldType, element.Value); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public Dictionary ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); TKey? key = default; var valueExpected = false; Dictionary? result = null; IEqualityComparer? comparer = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: comparer = _comparerCodec.ReadValue(ref reader, header); break; case 1: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = CreateInstance(length, comparer, reader.Session, placeholderReferenceId); break; case 2: if (result is null) ThrowLengthFieldMissing(); if (!valueExpected) { key = _keyCodec.ReadValue(ref reader, header); valueExpected = true; } else { result!.Add(key!, _valueCodec.ReadValue(ref reader, header)); valueExpected = false; } break; default: reader.ConsumeUnknownField(header); break; } } result ??= CreateInstance(0, comparer, reader.Session, placeholderReferenceId); return result; } private Dictionary CreateInstance(int length, IEqualityComparer? comparer, SerializerSession session, uint placeholderReferenceId) { var result = new Dictionary(length, comparer); ReferenceCodec.RecordObject(session, result, placeholderReferenceId); return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(Dictionary)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized dictionary is missing its length field."); } /// /// Copier for . /// /// The type of the t key. /// The type of the t value. [RegisterCopier] public sealed class DictionaryCopier : IDeepCopier>, IBaseCopier> where TKey : notnull { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; private readonly ConstructorInfo _baseConstructor; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public DictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; _baseConstructor = typeof(Dictionary).GetConstructor([typeof(int), typeof(IEqualityComparer)])!; } /// public Dictionary DeepCopy(Dictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result!; } if (input.GetType() != typeof(Dictionary)) { return context.DeepCopy(input)!; } result = new Dictionary(input.Count, input.Comparer); context.RecordCopy(input, result); foreach (var pair in input) { result[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } return result; } /// public void DeepCopy(Dictionary input, Dictionary output, CopyContext context) { output.Clear(); if (input.Comparer != EqualityComparer.Default) { _baseConstructor.Invoke(output, [input.Count, input.Comparer]); } else { output.EnsureCapacity(input.Count); } foreach (var pair in input) { output[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } } } /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class DictionaryBaseCodec : IBaseCodec> where TKey : notnull { private readonly Type _keyFieldType = typeof(TKey); private readonly Type _valueFieldType = typeof(TValue); private readonly IFieldCodec _keyCodec; private readonly IFieldCodec _valueCodec; private readonly IFieldCodec> _comparerCodec; private readonly ConstructorInfo _baseConstructor; /// /// Initializes a new instance of the class. /// /// The key codec. /// The value codec. /// The comparer codec. public DictionaryBaseCodec( IFieldCodec keyCodec, IFieldCodec valueCodec, IFieldCodec> comparerCodec) { _keyCodec = OrleansGeneratedCodeHelper.UnwrapService(this, keyCodec); _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); _comparerCodec = OrleansGeneratedCodeHelper.UnwrapService(this, comparerCodec); _baseConstructor = typeof(Dictionary).GetConstructor([typeof(int), typeof(IEqualityComparer)])!; } void IBaseCodec>.Serialize(ref Writer writer, Dictionary value) { if (value.Comparer is var comparer && comparer != EqualityComparer.Default) { _comparerCodec.WriteField(ref writer, 0, null, comparer); } if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 1, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _keyCodec.WriteField(ref writer, innerFieldIdDelta, _keyFieldType, element.Key); _valueCodec.WriteField(ref writer, 0, _valueFieldType, element.Value); innerFieldIdDelta = 0; } } } void IBaseCodec>.Deserialize(ref Reader reader, Dictionary value) { // If the dictionary has some values added by the default constructor, clear them. // If those values are in the serialized payload, they will be added below. value.Clear(); TKey? key = default; var valueExpected = false; IEqualityComparer? comparer = null; uint fieldId = 0; bool hasLengthField = false; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: comparer = _comparerCodec.ReadValue(ref reader, header); break; case 1: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } hasLengthField = true; if (comparer is not null) { _baseConstructor.Invoke(value, [length, comparer]); } else { value.EnsureCapacity(length); } break; case 2: if (!hasLengthField) { ThrowLengthFieldMissing(); } if (!valueExpected) { key = _keyCodec.ReadValue(ref reader, header); valueExpected = true; } else { value.Add(key!, _valueCodec.ReadValue(ref reader, header)); valueExpected = false; } break; default: reader.ConsumeUnknownField(header); break; } } } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(Dictionary)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized dictionary is missing its length field."); } } ================================================ FILE: src/Orleans.Serialization/Codecs/Enum32BaseCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for enum types with a 32-bit base. /// /// /// public abstract class Enum32BaseCodec : IFieldCodec where T : unmanaged, Enum { private readonly Type CodecFieldType = typeof(T); /// public unsafe void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, T value) where TBufferWriter : IBufferWriter { HolderStruct holder; holder.Value = value; var intValue = *(int*)&holder; Int32Codec.WriteField(ref writer, fieldIdDelta, expectedType, intValue, CodecFieldType); } /// public unsafe T ReadValue(ref Reader reader, Field field) { var intValue = Int32Codec.ReadValue(ref reader, field); return *(T*)&intValue; } [StructLayout(LayoutKind.Sequential)] private struct HolderStruct { public T Value; public int Padding; } } /// /// Serializer and copier for . /// [RegisterSerializer] internal sealed class DateTimeKindCodec : Enum32BaseCodec { } /// /// Serializer and copier for . /// [RegisterSerializer] internal sealed class DayOfWeekCodec : Enum32BaseCodec { } } ================================================ FILE: src/Orleans.Serialization/Codecs/FloatCodec.cs ================================================ using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class FloatCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, float value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(float), WireType.Fixed32); #if NET6_0_OR_GREATER writer.WriteUInt32(BitConverter.SingleToUInt32Bits(value)); #else writer.WriteUInt32((uint)BitConverter.SingleToInt32Bits(value)); #endif } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, float value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed32); #if NET6_0_OR_GREATER writer.WriteUInt32(BitConverter.SingleToUInt32Bits(value)); #else writer.WriteUInt32((uint)BitConverter.SingleToInt32Bits(value)); #endif } /// float IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static float ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); switch (field.WireType) { case WireType.Fixed32: return ReadFloatRaw(ref reader); case WireType.Fixed64: { var value = DoubleCodec.ReadDoubleRaw(ref reader); if ((value > float.MaxValue || value < float.MinValue) && !double.IsInfinity(value) && !double.IsNaN(value)) { ThrowValueOutOfRange(value); } return (float)value; } case WireType.LengthPrefixed: return (float)DecimalCodec.ReadDecimalRaw(ref reader); #if NET6_0_OR_GREATER case WireType.VarInt: return (float)HalfCodec.ReadHalfRaw(ref reader); #endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; } } /// /// Reads a value without any protocol framing. /// /// The reader input type. /// The reader. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if NET6_0_OR_GREATER public static float ReadFloatRaw(ref Reader reader) => BitConverter.UInt32BitsToSingle(reader.ReadUInt32()); #else public static float ReadFloatRaw(ref Reader reader) => BitConverter.Int32BitsToSingle((int)reader.ReadUInt32()); #endif private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); private static void ThrowValueOutOfRange(T value) => throw new OverflowException( $"The {typeof(T)} value has a magnitude too high {value} to be converted to {typeof(float)}."); } /// /// Serializer for . /// [RegisterSerializer] public sealed class DoubleCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, double value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(double), WireType.Fixed64); #if NET6_0_OR_GREATER writer.WriteUInt64(BitConverter.DoubleToUInt64Bits(value)); #else writer.WriteUInt64((ulong)BitConverter.DoubleToInt64Bits(value)); #endif } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, double value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed64); #if NET6_0_OR_GREATER writer.WriteUInt64(BitConverter.DoubleToUInt64Bits(value)); #else writer.WriteUInt64((ulong)BitConverter.DoubleToInt64Bits(value)); #endif } /// double IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static double ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); switch (field.WireType) { case WireType.Fixed32: return FloatCodec.ReadFloatRaw(ref reader); case WireType.Fixed64: return ReadDoubleRaw(ref reader); case WireType.LengthPrefixed: return (double)DecimalCodec.ReadDecimalRaw(ref reader); #if NET6_0_OR_GREATER case WireType.VarInt: return (double)HalfCodec.ReadHalfRaw(ref reader); #endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; } } /// /// Reads a value without any protocol framing. /// /// The reader input type. /// The reader. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] #if NET6_0_OR_GREATER public static double ReadDoubleRaw(ref Reader reader) => BitConverter.UInt64BitsToDouble(reader.ReadUInt64()); #else public static double ReadDoubleRaw(ref Reader reader) => BitConverter.Int64BitsToDouble((long)reader.ReadUInt64()); #endif private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); } /// /// Serializer for . /// [RegisterSerializer] public sealed class DecimalCodec : IFieldCodec { private const int Width = 16; void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, decimal value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(decimal), WireType.LengthPrefixed); WriteRaw(ref writer, ref value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, decimal value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); WriteRaw(ref writer, ref value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteRaw(ref Writer writer, ref decimal value) where TBufferWriter : IBufferWriter { writer.WriteVarUInt7(Width); #if NET6_0_OR_GREATER if (BitConverter.IsLittleEndian) { writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); return; } #endif ref var holder = ref Unsafe.As(ref value); writer.WriteUInt32(holder.Flags); writer.WriteUInt32(holder.Hi32); writer.WriteUInt64(holder.Lo64); } /// decimal IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static decimal ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); switch (field.WireType) { case WireType.Fixed32: { var value = FloatCodec.ReadFloatRaw(ref reader); if (value > (float)decimal.MaxValue || value < (float)decimal.MinValue) { ThrowValueOutOfRange(value); } return (decimal)value; } case WireType.Fixed64: { var value = DoubleCodec.ReadDoubleRaw(ref reader); if (value > (double)decimal.MaxValue || value < (double)decimal.MinValue) { ThrowValueOutOfRange(value); } return (decimal)value; } case WireType.LengthPrefixed: return ReadDecimalRaw(ref reader); #if NET6_0_OR_GREATER case WireType.VarInt: return (decimal)HalfCodec.ReadHalfRaw(ref reader); #endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; } } /// /// Reads a value without protocol framing. /// /// The reader input type. /// The reader. /// The value. public static decimal ReadDecimalRaw(ref Reader reader) { var length = reader.ReadVarUInt32(); if (length != Width) { throw new UnexpectedLengthPrefixValueException("decimal", Width, length); } #if NET6_0_OR_GREATER if (BitConverter.IsLittleEndian) { Unsafe.SkipInit(out decimal res); reader.ReadBytes(MemoryMarshal.AsBytes(new Span(ref res))); return res; } #endif DecimalConverter holder; holder.Flags = reader.ReadUInt32(); holder.Hi32 = reader.ReadUInt32(); holder.Lo64 = reader.ReadUInt64(); return Unsafe.As(ref holder); } private struct DecimalConverter { public uint Flags; public uint Hi32; public ulong Lo64; } private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); private static void ThrowValueOutOfRange(T value) => throw new OverflowException( $"The {typeof(T)} value has a magnitude too high {value} to be converted to {typeof(decimal)}."); } #if NET6_0_OR_GREATER /// /// Serializer for . /// [RegisterSerializer] public sealed class HalfCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Half value) { var asUShort = BitConverter.HalfToUInt16Bits(value); UInt16Codec.WriteField(ref writer, fieldIdDelta, expectedType, asUShort, typeof(Half)); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, Half value) where TBufferWriter : IBufferWriter { var asUShort = BitConverter.HalfToUInt16Bits(value); UInt16Codec.WriteField(ref writer, fieldIdDelta, asUShort); } /// Half IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static Half ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); switch (field.WireType) { case WireType.VarInt: return ReadHalfRaw(ref reader); case WireType.Fixed32: { var value = FloatCodec.ReadFloatRaw(ref reader); if ((value > (float)Half.MaxValue || value < (float)Half.MinValue) && !float.IsInfinity(value) && !float.IsNaN(value)) { ThrowValueOutOfRange(value); } return (Half)value; } case WireType.Fixed64: { var value = DoubleCodec.ReadDoubleRaw(ref reader); if ((value > (double)Half.MaxValue || value < (double)Half.MinValue) && !double.IsInfinity(value) && !double.IsNaN(value)) { ThrowValueOutOfRange(value); } return (Half)value; } case WireType.LengthPrefixed: { var value = DecimalCodec.ReadDecimalRaw(ref reader); if (value > (decimal)Half.MaxValue || value < (decimal)Half.MinValue) { ThrowValueOutOfRange(value); } return (Half)value; } default: ThrowWireTypeOutOfRange(field.WireType); return default; } } /// /// Reads a value without protocol framing. /// /// The reader input type. /// The reader. /// The value. internal static Half ReadHalfRaw(ref Reader reader) => BitConverter.UInt16BitsToHalf(reader.ReadVarUInt16()); [DoesNotReturn] private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); [DoesNotReturn] private static void ThrowValueOutOfRange(T value) => throw new OverflowException( $"The {typeof(T)} value has a magnitude too high {value} to be converted to {typeof(Half)}."); } #endif } ================================================ FILE: src/Orleans.Serialization/Codecs/GeneralizedReferenceTypeSurrogateCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; namespace Orleans.Serialization.Codecs { /// /// Surrogate serializer for and all sub-types. /// /// The type which the implementation of this class supports. /// The surrogate type serialized in place of . public abstract class GeneralizedReferenceTypeSurrogateCodec : IFieldCodec, IDerivedTypeCodec where TField : class where TSurrogate : struct { private readonly Type CodecFieldType = typeof(TField); private readonly IValueSerializer _surrogateSerializer; /// /// Initializes a new instance of the class. /// /// The surrogate serializer. protected GeneralizedReferenceTypeSurrogateCodec(IValueSerializer surrogateSerializer) { _surrogateSerializer = surrogateSerializer; } /// public TField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); var result = ConvertFromSurrogate(ref surrogate); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, CodecFieldType, value)) { return; } writer.WriteStartObject(fieldIdDelta, expectedType, CodecFieldType); TSurrogate surrogate = default; ConvertToSurrogate(value, ref surrogate); _surrogateSerializer.Serialize(ref writer, ref surrogate); writer.WriteEndObject(); } /// /// Converts a value from the surrogate type to the field type. /// /// The surrogate. /// The value. public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); /// /// Converts a value to the surrogate type. /// /// The value. /// The surrogate. public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); } } ================================================ FILE: src/Orleans.Serialization/Codecs/GeneralizedValueTypeSurrogateCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; namespace Orleans.Serialization.Codecs { /// /// Surrogate serializer for and all sub-types. /// /// The type which the implementation of this class supports. /// The surrogate type serialized in place of . public abstract class GeneralizedValueTypeSurrogateCodec : IFieldCodec where TField : struct where TSurrogate : struct { private readonly Type CodecFieldType = typeof(TField); private readonly IValueSerializer _surrogateSerializer; /// /// Initializes a new instance of the class. /// /// The surrogate serializer. protected GeneralizedValueTypeSurrogateCodec(IValueSerializer surrogateSerializer) { _surrogateSerializer = surrogateSerializer; } /// public TField ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); var result = ConvertFromSurrogate(ref surrogate); return result; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, CodecFieldType); TSurrogate surrogate = default; ConvertToSurrogate(value, ref surrogate); _surrogateSerializer.Serialize(ref writer, ref surrogate); writer.WriteEndObject(); } /// /// Converts a value from the surrogate type to the field type. /// /// The surrogate. /// The value. public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); /// /// Converts a value to the surrogate type. /// /// The value. /// The surrogate. public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); } } ================================================ FILE: src/Orleans.Serialization/Codecs/GuidCodec.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class GuidCodec : IFieldCodec { private const int Width = 16; void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Guid value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(Guid), WireType.LengthPrefixed); writer.WriteVarUInt7(Width); WriteRaw(ref writer, value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. public static void WriteField(ref Writer writer, uint fieldIdDelta, Guid value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); writer.WriteVarUInt7(Width); WriteRaw(ref writer, value); } /// Guid IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static Guid ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.LengthPrefixed); uint length = reader.ReadVarUInt32(); if (length != Width) { throw new UnexpectedLengthPrefixValueException(nameof(Guid), Width, length); } return ReadRaw(ref reader); } /// /// Writes the raw GUID content. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRaw(ref Writer writer, Guid value) where TBufferWriter : IBufferWriter { if (BitConverter.IsLittleEndian) { #if NET7_0_OR_GREATER writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); #else writer.EnsureContiguous(Width); if (value.TryWriteBytes(writer.WritableSpan)) { writer.AdvanceSpan(Width); return; } writer.Write(value.ToByteArray()); #endif } else { writer.EnsureContiguous(Width); var done = value.TryWriteBytes(writer.WritableSpan); Debug.Assert(done); writer.AdvanceSpan(Width); } } /// /// Reads the raw GUID content. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Guid ReadRaw(ref Reader reader) { #if NET7_0_OR_GREATER Unsafe.SkipInit(out Guid res); var bytes = MemoryMarshal.AsBytes(new Span(ref res)); reader.ReadBytes(bytes); if (BitConverter.IsLittleEndian) return res; return new Guid(bytes); #else if (reader.TryReadBytes(Width, out var readOnly)) { return new Guid(readOnly); } Span bytes = stackalloc byte[Width]; reader.ReadBytes(bytes); return new Guid(bytes); #endif } } } ================================================ FILE: src/Orleans.Serialization/Codecs/HashSetCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class HashSetCodec : IFieldCodec>, IBaseCodec> { private readonly Type CodecElementType = typeof(T); private readonly Type _comparerType = typeof(IEqualityComparer); private readonly ConstructorInfo _baseConstructor; private readonly IFieldCodec _fieldCodec; private readonly IFieldCodec> _comparerCodec; /// /// Initializes a new instance of the class. /// /// The field codec. /// The comparer codec. public HashSetCodec(IFieldCodec fieldCodec, IFieldCodec> comparerCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); _comparerCodec = OrleansGeneratedCodeHelper.UnwrapService(this, comparerCodec); _baseConstructor = typeof(HashSet).GetConstructor([typeof(int), typeof(IEqualityComparer)])!; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, HashSet value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); Serialize(ref writer, value); writer.WriteEndObject(); } /// public HashSet ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); HashSet result = null; IEqualityComparer comparer = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: comparer = _comparerCodec.ReadValue(ref reader, header); break; case 1: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = CreateInstance(length, comparer, reader.Session, placeholderReferenceId); break; case 2: if (result is null) ThrowLengthFieldMissing(); result.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } result ??= CreateInstance(0, comparer, reader.Session, placeholderReferenceId); return result; } private static HashSet CreateInstance(int length, IEqualityComparer comparer, SerializerSession session, uint placeholderReferenceId) { var result = new HashSet(length, comparer); ReferenceCodec.RecordObject(session, result, placeholderReferenceId); return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(HashSet)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized set is missing its length field."); public void Serialize(ref Writer writer, HashSet value) where TBufferWriter : IBufferWriter { if (value.Comparer != EqualityComparer.Default) { _comparerCodec.WriteField(ref writer, 0, _comparerType, value.Comparer); } if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 1, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } } void IBaseCodec>.Deserialize(ref Reader reader, HashSet value) { // If the value has some values added by the constructor, clear them. // If those values are in the serialized payload, they will be added below. value.Clear(); IEqualityComparer comparer = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: comparer = _comparerCodec.ReadValue(ref reader, header); break; case 1: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } // Re-initialize the class by calling the constructor. if (comparer is not null) { _baseConstructor.Invoke(value, [length, comparer]); } else { value.EnsureCapacity(length); } break; case 2: value.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } } } /// /// Copier for . /// /// /// /// Initializes a new instance of the class. /// /// The value copier. [RegisterCopier] public sealed class HashSetCopier(IDeepCopier valueCopier) : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(HashSet); private readonly IDeepCopier _copier = valueCopier; private readonly ConstructorInfo _baseConstructor = typeof(HashSet).GetConstructor([typeof(int), typeof(IEqualityComparer)])!; /// public HashSet DeepCopy(HashSet input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new(input.Count, input.Comparer); context.RecordCopy(input, result); foreach (var item in input) { result.Add(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(HashSet input, HashSet output, CopyContext context) { // If the value has some values added by the constructor, clear them. // If those values are in the serialized payload, they will be added below. output.Clear(); if (input.Comparer != EqualityComparer.Default) { _baseConstructor.Invoke(output, [input.Count, input.Comparer]); } else { output.EnsureCapacity(input.Count); } foreach (var item in input) { output.Add(_copier.DeepCopy(item, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/IFieldCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; namespace Orleans.Serialization.Codecs { /// /// Marker type for field codecs. /// public interface IFieldCodec { /// /// Writes a field using the provided untyped value. The type must still match the codec instance! /// void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter; /// /// Reads a value and returns it untyped. The type must still match the codec instance! /// object ReadValue(ref Reader reader, Field field); } /// /// Provides functionality for reading and writing values of a specified type. /// Implements the /// /// The type which this implementation can read and write. /// public interface IFieldCodec : IFieldCodec { /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, T value) where TBufferWriter : IBufferWriter; /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. new T ReadValue(ref Reader reader, Field field); void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) => WriteField(ref writer, fieldIdDelta, expectedType, (T)value); object IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); } /// /// Marker interface for codecs which directly support serializing all derived types of their specified type. /// public interface IDerivedTypeCodec : IFieldCodec { } /// /// Hooks for stages in serialization and copying. /// /// The underlying value type. public interface ISerializationCallbacks { /// /// Called when serializing. /// /// The value. void OnSerializing(T value); /// /// Called when a value has been serialized. /// /// The value. void OnSerialized(T value); /// /// Called when deserializing. /// /// The value. void OnDeserializing(T value); /// /// Called when a value has been deserialized. /// /// The value. void OnDeserialized(T value); /// /// Called when copying. /// /// The original value. /// The copy. void OnCopying(T original, T result); /// /// Called when a value has been copied. /// /// The original value. /// The copy. void OnCopied(T original, T result); } internal sealed class UntypedCodecWrapper : IFieldCodec { private readonly IFieldCodec _codec; public UntypedCodecWrapper(IFieldCodec codec) => _codec = codec; public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter => _codec.WriteField(ref writer, fieldIdDelta, expectedType, value); void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) => _codec.WriteField(ref writer, fieldIdDelta, expectedType, value); public TField ReadValue(ref Reader reader, Field field) => (TField)_codec.ReadValue(ref reader, field); object IFieldCodec.ReadValue(ref Reader reader, Field field) => _codec.ReadValue(ref reader, field); } } ================================================ FILE: src/Orleans.Serialization/Codecs/IPAddressCodec.cs ================================================ using System; using System.Buffers; using System.Net; using System.Runtime.CompilerServices; using Orleans.Serialization.Cloning; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class IPAddressCodec : IFieldCodec, IDerivedTypeCodec { IPAddress IFieldCodec.ReadValue(ref Buffers.Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static IPAddress ReadValue(ref Buffers.Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var result = ReadRaw(ref reader); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// /// Reads the raw length prefixed IP address value. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IPAddress ReadRaw(ref Buffers.Reader reader) { var length = reader.ReadVarUInt32(); #if NET5_0_OR_GREATER if (reader.TryReadBytes((int)length, out var bytes)) return new(bytes); #endif return new(reader.ReadBytes(length)); } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, Type expectedType, IPAddress value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, typeof(IPAddress), value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(IPAddress), WireType.LengthPrefixed); WriteRaw(ref writer, value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, IPAddress value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) { return; } writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); WriteRaw(ref writer, value); } /// /// Writes the raw length prefixed IP address value. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRaw(ref Buffers.Writer writer, IPAddress value) where TBufferWriter : IBufferWriter { writer.EnsureContiguous(1 + 16); var span = writer.WritableSpan; if (!value.TryWriteBytes(span[1..], out var length)) ThrowNotSupported(); span[0] = (byte)(length * 2 + 1); // VarInt length writer.AdvanceSpan(1 + length); } private static void ThrowNotSupported() => throw new NotSupportedException(); } [RegisterCopier] internal sealed class IPAddressCopier : ShallowCopier, IDerivedTypeCopier { } } ================================================ FILE: src/Orleans.Serialization/Codecs/IPEndPointCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.Net; #nullable enable namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class IPEndPointCodec : IFieldCodec, IDerivedTypeCodec { IPEndPoint IFieldCodec.ReadValue(ref Buffers.Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static IPEndPoint ReadValue(ref Buffers.Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var referencePlaceholder = ReferenceCodec.CreateRecordPlaceholder(reader.Session); Field header = default; var port = 0; reader.ReadFieldHeader(ref header); if (!header.HasFieldId || header.FieldIdDelta != 0) throw new RequiredFieldMissingException("Serialized IPEndPoint is missing its address field."); var address = IPAddressCodec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); if (header.HasFieldId && header.FieldIdDelta == 1) { port = UInt16Codec.ReadValue(ref reader, header); reader.ReadFieldHeader(ref header); } reader.ConsumeEndBaseOrEndObject(ref header); var result = new IPEndPoint(address, port); ReferenceCodec.RecordObject(reader.Session, result, referencePlaceholder); return result; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, Type expectedType, IPEndPoint value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, typeof(IPEndPoint), value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(IPEndPoint), WireType.TagDelimited); IPAddressCodec.WriteField(ref writer, 0, value.Address); if (value.Port != 0) UInt16Codec.WriteField(ref writer, 1, (ushort)value.Port); writer.WriteEndObject(); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, IPEndPoint value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) { return; } writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.TagDelimited); IPAddressCodec.WriteField(ref writer, 0, value.Address); if (value.Port != 0) UInt16Codec.WriteField(ref writer, 1, (ushort)value.Port); writer.WriteEndObject(); } } [RegisterCopier] internal sealed class EndPointCopier : ShallowCopier, IDerivedTypeCopier { } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableArrayCodec.cs ================================================ using System.Collections.Immutable; using System.Linq; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableArrayCodec : GeneralizedValueTypeSurrogateCodec, ImmutableArraySurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableArrayCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableArray ConvertFromSurrogate(ref ImmutableArraySurrogate surrogate) => surrogate.Values is { } v ? ImmutableArray.Create(v) : default; /// public override void ConvertToSurrogate(ImmutableArray value, ref ImmutableArraySurrogate surrogate) => surrogate.Values = value.IsDefault ? null : value.ToArray(); } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ImmutableArraySurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public T[] Values; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableArrayCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableArrayCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; object IDeepCopier.DeepCopy(object input, CopyContext context) { if (_copier is null) return input; var array = (ImmutableArray)input; return array.IsDefaultOrEmpty ? input : DeepCopy(array, context); } /// public ImmutableArray DeepCopy(ImmutableArray input, CopyContext context) => _copier is null || input.IsDefaultOrEmpty ? input : ImmutableArray.CreateRange(input, (i, s) => s._copier.DeepCopy(i, s.context), (_copier, context)); } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableDictionaryCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class ImmutableDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableDictionarySurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableDictionaryCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableDictionary ConvertFromSurrogate(ref ImmutableDictionarySurrogate surrogate) => ImmutableDictionary.CreateRange(surrogate.Values.Comparer, surrogate.Values); /// public override void ConvertToSurrogate(ImmutableDictionary value, ref ImmutableDictionarySurrogate surrogate) => surrogate.Values = new(value, value.KeyComparer); } /// /// Surrogate type used by . /// /// The key type. /// The value type. [GenerateSerializer] public struct ImmutableDictionarySurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public Dictionary Values; } /// /// Copier for . /// /// The key type. /// The value type. [RegisterCopier] public sealed class ImmutableDictionaryCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; public ImmutableDictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(keyCopier); _valueCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(valueCopier); } public bool IsShallowCopyable() => _keyCopier is null && _valueCopier is null; /// public ImmutableDictionary DeepCopy(ImmutableDictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _keyCopier is null && _valueCopier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List>(input.Count); foreach (var item in input) items.Add(new(_keyCopier is null ? item.Key : _keyCopier.DeepCopy(item.Key, context), _valueCopier is null ? item.Value : _valueCopier.DeepCopy(item.Value, context))); var res = ImmutableDictionary.CreateRange(input.KeyComparer, input.ValueComparer, items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableHashSetCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableHashSetCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableHashSetSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableHashSetCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableHashSet ConvertFromSurrogate(ref ImmutableHashSetSurrogate surrogate) => ImmutableHashSet.CreateRange(surrogate.KeyComparer, surrogate.Values); /// public override void ConvertToSurrogate(ImmutableHashSet value, ref ImmutableHashSetSurrogate surrogate) { surrogate.Values = new(value); surrogate.KeyComparer = value.KeyComparer != EqualityComparer.Default ? value.KeyComparer : null; } } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ImmutableHashSetSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; /// /// Gets or sets the key comparer. /// /// The key comparer. [Id(1)] public IEqualityComparer KeyComparer; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableHashSetCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableHashSetCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public ImmutableHashSet DeepCopy(ImmutableHashSet input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _copier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List(input.Count); foreach (var item in input) items.Add(_copier.DeepCopy(item, context)); var res = ImmutableHashSet.CreateRange(input.KeyComparer, items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableListCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableListCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableListSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableListCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableList ConvertFromSurrogate(ref ImmutableListSurrogate surrogate) => ImmutableList.CreateRange(surrogate.Values); /// public override void ConvertToSurrogate(ImmutableList value, ref ImmutableListSurrogate surrogate) => surrogate.Values = new(value); } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ImmutableListSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableListCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableListCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public ImmutableList DeepCopy(ImmutableList input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _copier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List(input.Count); foreach (var item in input) items.Add(_copier.DeepCopy(item, context)); var res = ImmutableList.CreateRange(items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableQueueCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableQueueCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableQueueSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableQueueCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableQueue ConvertFromSurrogate(ref ImmutableQueueSurrogate surrogate) => ImmutableQueue.CreateRange(surrogate.Values); /// public override void ConvertToSurrogate(ImmutableQueue value, ref ImmutableQueueSurrogate surrogate) => surrogate.Values = new(value); } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ImmutableQueueSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableQueueCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableQueueCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public ImmutableQueue DeepCopy(ImmutableQueue input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _copier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List(); foreach (var item in input) items.Add(_copier.DeepCopy(item, context)); var res = ImmutableQueue.CreateRange(items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableSortedDictionaryCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class ImmutableSortedDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableSortedDictionarySurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableSortedDictionaryCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableSortedDictionary ConvertFromSurrogate(ref ImmutableSortedDictionarySurrogate surrogate) => ImmutableSortedDictionary.CreateRange(surrogate.KeyComparer, surrogate.ValueComparer, surrogate.Values); /// public override void ConvertToSurrogate(ImmutableSortedDictionary value, ref ImmutableSortedDictionarySurrogate surrogate) { surrogate.Values = new(value); surrogate.KeyComparer = value.KeyComparer != Comparer.Default ? value.KeyComparer : null; surrogate.ValueComparer = value.ValueComparer != EqualityComparer.Default ? value.ValueComparer : null; } } /// /// Surrogate type used by . /// /// The key type. /// The value type. [GenerateSerializer] public struct ImmutableSortedDictionarySurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List> Values; /// /// Gets or sets the key comparer. /// /// The key comparer. [Id(1)] public IComparer KeyComparer; /// /// Gets or sets the value comparer. /// [Id(2)] public IEqualityComparer ValueComparer; } /// /// Copier for . /// /// The key type. /// The value type. [RegisterCopier] public sealed class ImmutableSortedDictionaryCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; public ImmutableSortedDictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(keyCopier); _valueCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(valueCopier); } public bool IsShallowCopyable() => _keyCopier is null && _valueCopier is null; /// public ImmutableSortedDictionary DeepCopy(ImmutableSortedDictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _keyCopier is null && _valueCopier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List>(input.Count); foreach (var item in input) items.Add(new(_keyCopier is null ? item.Key : _keyCopier.DeepCopy(item.Key, context), _valueCopier is null ? item.Value : _valueCopier.DeepCopy(item.Value, context))); var res = ImmutableSortedDictionary.CreateRange(input.KeyComparer, input.ValueComparer, items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableSortedSetCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Immutable; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableSortedSetCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableSortedSetSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableSortedSetCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableSortedSet ConvertFromSurrogate(ref ImmutableSortedSetSurrogate surrogate) => ImmutableSortedSet.CreateRange(surrogate.KeyComparer, surrogate.Values); /// public override void ConvertToSurrogate(ImmutableSortedSet value, ref ImmutableSortedSetSurrogate surrogate) { surrogate.Values = new(value); surrogate.KeyComparer = value.KeyComparer != Comparer.Default ? value.KeyComparer : null; } } /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ImmutableSortedSetSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; /// /// Gets or sets the key comparer. /// /// The key comparer. [Id(1)] public IComparer KeyComparer; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableSortedSetCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableSortedSetCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public ImmutableSortedSet DeepCopy(ImmutableSortedSet input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _copier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List(input.Count); foreach (var item in input) items.Add(_copier.DeepCopy(item, context)); var res = ImmutableSortedSet.CreateRange(input.KeyComparer, items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ImmutableStackCodec.cs ================================================ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ImmutableStackCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableStackSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ImmutableStackCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ImmutableStack ConvertFromSurrogate(ref ImmutableStackSurrogate surrogate) => ImmutableStack.CreateRange(Enumerable.Reverse(surrogate.Values)); /// public override void ConvertToSurrogate(ImmutableStack value, ref ImmutableStackSurrogate surrogate) => surrogate.Values = new(value); } /// /// Surrogate type for . /// /// The element type. [GenerateSerializer] public struct ImmutableStackSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ImmutableStackCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; public ImmutableStackCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public ImmutableStack DeepCopy(ImmutableStack input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) return result; if (input.IsEmpty || _copier is null) return input; // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var items = new List(); foreach (var item in input) items.Add(_copier.DeepCopy(item, context)); var res = ImmutableStack.CreateRange(items); context.RecordCopy(input, res); return res; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/IntegerCodec.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class BoolCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, bool value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(bool), WireType.VarInt); writer.WriteByte(value ? (byte)3 : (byte)1); // writer.WriteVarUInt32(value ? 1U : 0U); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, bool value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteByte(value ? (byte)3 : (byte)1); // writer.WriteVarUInt32(value ? 1U : 0U); } /// bool IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadUInt8(field.WireType) != 0; } } /// /// Serializer for . /// [RegisterSerializer] public sealed class CharCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, char value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(char), WireType.VarInt); writer.WriteVarUInt28(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, char value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteVarUInt28(value); } /// char IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static char ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return (char)reader.ReadUInt16(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class ByteCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, byte value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(byte), WireType.VarInt); writer.WriteVarUInt28(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, byte value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteVarUInt28(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, byte value, Type actualType) where TBufferWriter : IBufferWriter => UInt16Codec.WriteField(ref writer, fieldIdDelta, expectedType, value, actualType); /// byte IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadUInt8(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class SByteCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, sbyte value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(sbyte), WireType.VarInt); writer.WriteVarInt8(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, sbyte value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteVarInt8(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, sbyte value, Type actualType) where TBufferWriter : IBufferWriter => Int16Codec.WriteField(ref writer, fieldIdDelta, expectedType, value, actualType); /// sbyte IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadInt8(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class UInt16Codec : IFieldCodec { /// ushort IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadUInt16(field.WireType); } void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ushort value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(ushort), WireType.VarInt); writer.WriteVarUInt28(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, ushort value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteVarUInt28(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ushort value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, WireType.VarInt); writer.WriteVarUInt28(value); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class Int16Codec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, short value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(short), WireType.VarInt); writer.WriteVarInt16(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, short value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.VarInt); writer.WriteVarInt16(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, short value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, WireType.VarInt); writer.WriteVarInt16(value); } /// short IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadInt16(field.WireType); } } /// /// Serialzier for . /// [RegisterSerializer] public sealed class UInt32Codec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, uint value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(uint), value < 1 << 21 ? WireType.VarInt : WireType.Fixed32); if (value < 1 << 21) writer.WriteVarUInt28(value); else writer.WriteUInt32(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, uint value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, value < 1 << 21 ? WireType.VarInt : WireType.Fixed32); if (value < 1 << 21) writer.WriteVarUInt28(value); else writer.WriteUInt32(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, uint value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, value < 1 << 21 ? WireType.VarInt : WireType.Fixed32); if (value < 1 << 21) writer.WriteVarUInt28(value); else writer.WriteUInt32(value); } /// uint IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadUInt32(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class Int32Codec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, int value) { ReferenceCodec.MarkValueField(writer.Session); var wireType = value < 1 << 20 && value > -1 << 20 ? WireType.VarInt : WireType.Fixed32; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(int), wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt28(Writer.ZigZagEncode(value)); else writer.WriteInt32(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, int value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value < 1 << 20 && value > -1 << 20 ? WireType.VarInt : WireType.Fixed32; writer.WriteFieldHeaderExpected(fieldIdDelta, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt28(Writer.ZigZagEncode(value)); else writer.WriteInt32(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, int value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value < 1 << 20 && value > -1 << 20 ? WireType.VarInt : WireType.Fixed32; writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt28(Writer.ZigZagEncode(value)); else writer.WriteInt32(value); } /// int IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadInt32(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class Int64Codec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, long value) { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 20 and > -1 << 20 => WireType.VarInt, <= int.MaxValue and >= int.MinValue => WireType.Fixed32, < 1L << 48 and > -1L << 48 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(long), wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(Writer.ZigZagEncode(value)); else if (wireType == WireType.Fixed32) writer.WriteInt32((int)value); else writer.WriteInt64(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, long value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 20 and > -1 << 20 => WireType.VarInt, <= int.MaxValue and >= int.MinValue => WireType.Fixed32, < 1L << 48 and > -1L << 48 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeaderExpected(fieldIdDelta, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(Writer.ZigZagEncode(value)); else if (wireType == WireType.Fixed32) writer.WriteInt32((int)value); else writer.WriteInt64(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, long value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 20 and > -1 << 20 => WireType.VarInt, <= int.MaxValue and >= int.MinValue => WireType.Fixed32, < 1L << 48 and > -1L << 48 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(Writer.ZigZagEncode(value)); else if (wireType == WireType.Fixed32) writer.WriteInt32((int)value); else writer.WriteInt64(value); } /// long IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadInt64(field.WireType); } } /// /// Serializer for . /// [RegisterSerializer] public sealed class UInt64Codec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ulong value) { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 21 => WireType.VarInt, <= uint.MaxValue => WireType.Fixed32, < 1UL << 49 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(ulong), wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(value); else if (wireType == WireType.Fixed32) writer.WriteUInt32((uint)value); else writer.WriteUInt64(value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, ulong value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 21 => WireType.VarInt, <= uint.MaxValue => WireType.Fixed32, < 1UL << 49 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeaderExpected(fieldIdDelta, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(value); else if (wireType == WireType.Fixed32) writer.WriteUInt32((uint)value); else writer.WriteUInt64(value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. /// The actual type. public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ulong value, Type actualType) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); var wireType = value switch { < 1 << 21 => WireType.VarInt, <= uint.MaxValue => WireType.Fixed32, < 1UL << 49 => WireType.VarInt, _ => WireType.Fixed64, }; writer.WriteFieldHeader(fieldIdDelta, expectedType, actualType, wireType); if (wireType == WireType.VarInt) writer.WriteVarUInt56(value); else if (wireType == WireType.Fixed32) writer.WriteUInt32((uint)value); else writer.WriteUInt64(value); } /// ulong IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); return reader.ReadUInt64(field.WireType); } } #if NET7_0_OR_GREATER /// /// Serializer for . /// [RegisterSerializer] public sealed class Int128Codec : IFieldCodec { /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Int128 value) => WriteField(ref writer, fieldIdDelta, expectedType, value); /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, Int128 value) where TBufferWriter : IBufferWriter => WriteField(ref writer, fieldIdDelta, typeof(Int128), value); private static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Int128 value) where TBufferWriter : IBufferWriter { if (value <= (Int128)long.MaxValue && value >= (Int128)long.MinValue) { Int64Codec.WriteField(ref writer, fieldIdDelta, expectedType, (long)value, typeof(Int128)); } else { ReferenceCodec.MarkValueField(writer.Session); const int byteCount = 128 / 8; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(Int128), WireType.LengthPrefixed); writer.WriteVarUInt7(byteCount); if (BitConverter.IsLittleEndian) { writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); } else { writer.EnsureContiguous(byteCount); ((IBinaryInteger)value).TryWriteLittleEndian(writer.WritableSpan, out var bytesWritten); Debug.Assert(bytesWritten == byteCount); writer.AdvanceSpan(byteCount); } } } /// Int128 IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Int128 ReadValue(ref Reader reader, Field field) { if (field.WireType != WireType.LengthPrefixed) { return Int64Codec.ReadValue(ref reader, field); } ReferenceCodec.MarkValueField(reader.Session); return ReadRaw(ref reader); } /// /// Reads a value without protocol framing. /// /// The reader input type. /// The reader. /// The value. internal static Int128 ReadRaw(ref Reader reader) { var byteCount = reader.ReadVarUInt32(); if (byteCount != 128 / 8) throw new UnexpectedLengthPrefixValueException(nameof(Int128), 128 / 8, byteCount); Unsafe.SkipInit(out Int128 res); var bytes = MemoryMarshal.AsBytes(new Span(ref res)); reader.ReadBytes(bytes); if (BitConverter.IsLittleEndian) return res; var done = TryReadLittleEndian(bytes, out var value); Debug.Assert(done); return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryReadLittleEndian(ReadOnlySpan source, out T value) where T : IBinaryInteger => T.TryReadLittleEndian(source, isUnsigned: false, out value); } /// /// Serializer for . /// [RegisterSerializer] public sealed class UInt128Codec : IFieldCodec { /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, UInt128 value) => WriteField(ref writer, fieldIdDelta, expectedType, value); /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, UInt128 value) where TBufferWriter : IBufferWriter => WriteField(ref writer, fieldIdDelta, typeof(UInt128), value); private static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, UInt128 value) where TBufferWriter : IBufferWriter { if (value <= (UInt128)ulong.MaxValue) { UInt64Codec.WriteField(ref writer, fieldIdDelta, expectedType, (ulong)value, typeof(UInt128)); } else { ReferenceCodec.MarkValueField(writer.Session); const int byteCount = 128 / 8; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(UInt128), WireType.LengthPrefixed); writer.WriteVarUInt7(byteCount); if (BitConverter.IsLittleEndian) { writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); } else { writer.EnsureContiguous(byteCount); ((IBinaryInteger)value).TryWriteLittleEndian(writer.WritableSpan, out var bytesWritten); Debug.Assert(bytesWritten == byteCount); writer.AdvanceSpan(byteCount); } } } /// UInt128 IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 ReadValue(ref Reader reader, Field field) { if (field.WireType != WireType.LengthPrefixed) { return UInt64Codec.ReadValue(ref reader, field); } ReferenceCodec.MarkValueField(reader.Session); return ReadRaw(ref reader); } /// /// Reads a value without protocol framing. /// /// The reader input type. /// The reader. /// The value. internal static UInt128 ReadRaw(ref Reader reader) { var byteCount = reader.ReadVarUInt32(); if (byteCount != 128 / 8) throw new UnexpectedLengthPrefixValueException(nameof(UInt128), 128 / 8, byteCount); Unsafe.SkipInit(out UInt128 res); var bytes = MemoryMarshal.AsBytes(new Span(ref res)); reader.ReadBytes(bytes); if (BitConverter.IsLittleEndian) return res; var done = TryReadLittleEndian(bytes, out var value); Debug.Assert(done); return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryReadLittleEndian(ReadOnlySpan source, out T value) where T : IBinaryInteger => T.TryReadLittleEndian(source, isUnsigned: true, out value); } #endif } ================================================ FILE: src/Orleans.Serialization/Codecs/KeyValuePairCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class KeyValuePairCodec : IFieldCodec> { private readonly IFieldCodec _keyCodec; private readonly IFieldCodec _valueCodec; private readonly Type CodecKeyType = typeof(TKey); private readonly Type CodecValueType = typeof(TValue); /// /// Initializes a new instance of the class. /// /// The key codec. /// The value codec. public KeyValuePairCodec(IFieldCodec keyCodec, IFieldCodec valueCodec) { _keyCodec = OrleansGeneratedCodeHelper.UnwrapService(this, keyCodec); _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, KeyValuePair value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _keyCodec.WriteField(ref writer, 0, CodecKeyType, value.Key); _valueCodec.WriteField(ref writer, 1, CodecValueType, value.Value); writer.WriteEndObject(); } /// public KeyValuePair ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); var key = default(TKey); var value = default(TValue); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: key = _keyCodec.ReadValue(ref reader, header); break; case 1: value = _valueCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return new KeyValuePair(key, value); } } /// /// Copier for . /// /// The key type. /// The value type. [RegisterCopier] public sealed class KeyValuePairCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public KeyValuePairCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(keyCopier); _valueCopier = OrleansGeneratedCodeHelper.GetOptionalCopier(valueCopier); } public bool IsShallowCopyable() => _keyCopier is null && _valueCopier is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy((KeyValuePair)input, context); /// public KeyValuePair DeepCopy(KeyValuePair input, CopyContext context) { return new(_keyCopier is null ? input.Key : _keyCopier.DeepCopy(input.Key, context), _valueCopier is null ? input.Value : _valueCopier.DeepCopy(input.Value, context)); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ListCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ListCodec : IFieldCodec>, IBaseCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public ListCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, List value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); Serialize(ref writer, value); writer.WriteEndObject(); } /// public List ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); List result = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new(length); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ListCodec.ThrowLengthFieldMissing(); } result.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } if (result is null) { result = new(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(List)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its length field."); public void Serialize(ref Writer writer, List value) where TBufferWriter : IBufferWriter { if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } } public void Deserialize(ref Reader reader, List value) { // If the value has some values added by the constructor, clear them. // If those values are in the serialized payload, they will be added below. value.Clear(); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } #if NET6_0_OR_GREATER value.EnsureCapacity(length); #endif break; case 1: value.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } } } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ListCopier : IDeepCopier>, IBaseCopier> { private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public ListCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public List DeepCopy(List input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() != typeof(List)) { return context.DeepCopy(input); } result = new List(input.Count); context.RecordCopy(input, result); foreach (var item in input) { result.Add(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(List input, List output, CopyContext context) { output.Clear(); #if NET6_0_OR_GREATER output.EnsureCapacity(input.Count); #endif foreach (var item in input) { output.Add(_copier.DeepCopy(item, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/MultiDimensionalArrayCodec.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for multi-dimensional arrays. /// /// The array element type. internal sealed class MultiDimensionalArrayCodec : IGeneralizedCodec { private readonly Type DimensionFieldType = typeof(int[]); private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _intArrayCodec; private readonly IFieldCodec _elementCodec; /// /// Initializes a new instance of the class. /// /// The int array codec. /// The element codec. public MultiDimensionalArrayCodec(IFieldCodec intArrayCodec, IFieldCodec elementCodec) { _intArrayCodec = OrleansGeneratedCodeHelper.UnwrapService(this, intArrayCodec); _elementCodec = OrleansGeneratedCodeHelper.UnwrapService(this, elementCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); var array = (Array)value; var rank = array.Rank; var lengths = new int[rank]; var indices = new int[rank]; // Write array lengths. for (var i = 0; i < rank; i++) { lengths[i] = array.GetLength(i); } _intArrayCodec.WriteField(ref writer, 0, DimensionFieldType, lengths); var remaining = array.Length; uint innerFieldIdDelta = 1; while (remaining-- > 0) { var element = array.GetValue(indices); _elementCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, (T)element); innerFieldIdDelta = 0; // Increment the indices array by 1. if (remaining > 0) { var idx = rank - 1; while (idx >= 0 && ++indices[idx] >= lengths[idx]) { indices[idx] = 0; --idx; if (idx < 0) { ThrowIndexOutOfRangeException(lengths); } } } } writer.WriteEndObject(); } /// public object ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); Array result = null; uint fieldId = 0; int[] lengths = null; int[] indices = null; var rank = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: { lengths = _intArrayCodec.ReadValue(ref reader, header); rank = lengths.Length; // Multi-dimensional arrays must be indexed using indexing arrays, so create one now. indices = new int[rank]; result = Array.CreateInstance(CodecElementType, lengths); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; } case 1: { if (result is null || indices is null || lengths is null) { ThrowLengthsFieldMissing(); } var element = _elementCodec.ReadValue(ref reader, header); result.SetValue(element, indices); // Increment the indices array by 1. var idx = rank - 1; while (idx >= 0 && ++indices[idx] >= lengths[idx]) { indices[idx] = 0; --idx; } break; } default: reader.ConsumeUnknownField(header); break; } } return result; } /// public bool IsSupportedType(Type type) => type.IsArray && !type.IsSZArray; private void ThrowIndexOutOfRangeException(int[] lengths) => throw new IndexOutOfRangeException( $"Encountered too many elements in array of type {CodecElementType} with declared lengths {string.Join(", ", lengths)}."); private static void ThrowLengthsFieldMissing() => throw new RequiredFieldMissingException("Serialized array is missing its lengths field."); } /// /// Copier for multi-dimensional arrays. /// /// The array element type. internal sealed class MultiDimensionalArrayCopier : IGeneralizedCopier { /// public object DeepCopy(object original, CopyContext context) { if (context.TryGetCopy(original, out var result)) { return result; } var type = original.GetType(); var originalArray = (Array)original; var elementType = type.GetElementType(); if (ShallowCopyableTypes.Contains(elementType)) { return originalArray.Clone(); } // We assume that all arrays have lower bound 0. In .NET 4.0, it's hard to create an array with a non-zero lower bound. var rank = originalArray.Rank; var lengths = new int[rank]; for (var i = 0; i < rank; i++) { lengths[i] = originalArray.GetLength(i); } result = Array.CreateInstance(elementType, lengths); context.RecordCopy(original, result); if (rank == 1) { for (var i = 0; i < lengths[0]; i++) { result.SetValue(ObjectCopier.DeepCopy(originalArray.GetValue(i), context), i); } } else if (rank == 2) { for (var i = 0; i < lengths[0]; i++) { for (var j = 0; j < lengths[1]; j++) { result.SetValue(ObjectCopier.DeepCopy(originalArray.GetValue(i, j), context), i, j); } } } else { var index = new int[rank]; var sizes = new int[rank]; sizes[rank - 1] = 1; for (var k = rank - 2; k >= 0; k--) { sizes[k] = sizes[k + 1] * lengths[k + 1]; } for (var i = 0; i < originalArray.Length; i++) { int k = i; for (int n = 0; n < rank; n++) { int offset = k / sizes[n]; k -= offset * sizes[n]; index[n] = offset; } result.SetValue(ObjectCopier.DeepCopy(originalArray.GetValue(index), context), index); } } return result; } /// public bool IsSupportedType(Type type) => type.IsArray && !type.IsSZArray; } } ================================================ FILE: src/Orleans.Serialization/Codecs/NameValueCollectionCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System.Collections.Generic; using System.Collections.Specialized; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class NameValueCollectionCodec : GeneralizedReferenceTypeSurrogateCodec { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public NameValueCollectionCodec(IValueSerializer surrogateSerializer) : base(surrogateSerializer) { } /// public override NameValueCollection ConvertFromSurrogate(ref NameValueCollectionSurrogate surrogate) { var result = new NameValueCollection(surrogate.Values.Count); foreach (var value in surrogate.Values) { result.Add(value.Key, value.Value); } return result; } /// public override void ConvertToSurrogate(NameValueCollection value, ref NameValueCollectionSurrogate surrogate) { var result = new Dictionary(value.Count); for (var i = 0; i < value.Count; i++) { result.Add(value.GetKey(i), value.Get(i)); } surrogate.Values = result; } } /// /// Surrogate type used by . /// [GenerateSerializer] public struct NameValueCollectionSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public Dictionary Values; } /// /// Copier for . /// [RegisterCopier] public sealed class NameValueCollectionCopier : IDeepCopier { /// public NameValueCollection DeepCopy(NameValueCollection input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } if (input.GetType() != typeof(NameValueCollection)) { return context.DeepCopy(input); } result = new NameValueCollection(input.Count); context.RecordCopy(input, result); for (var i = 0; i < input.Count; i++) { result.Add(input.GetKey(i), input.Get(i)); } return result; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/NullableCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class NullableCodec : IFieldCodec where T : struct { private readonly Type CodecFieldType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public NullableCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, T? value) where TBufferWriter : IBufferWriter { // If the value is null, write it as the null reference. if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } // The value is not null. _fieldCodec.WriteField(ref writer, fieldIdDelta, CodecFieldType, value.GetValueOrDefault()); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T? ReadValue(ref Reader reader, Field field) { // This will only be true if the value is null. if (field.WireType == WireType.Reference) { ReferenceCodec.MarkValueField(reader.Session); var reference = reader.ReadVarUInt32(); if (reference != 0) ThrowInvalidReference(reference); return null; } // Read the non-null value. return _fieldCodec.ReadValue(ref reader, field); } private static void ThrowInvalidReference(uint reference) => throw new ReferenceNotFoundException(typeof(T?), reference); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class NullableCopier : IDeepCopier, IOptionalDeepCopier where T : struct { private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The copier. public NullableCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => input is null || _copier is null ? input : _copier.DeepCopy(input, context); /// public T? DeepCopy(T? input, CopyContext context) => input is null || _copier is null ? input : _copier.DeepCopy(input.GetValueOrDefault(), context); } } ================================================ FILE: src/Orleans.Serialization/Codecs/ObjectCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class ObjectCodec : IFieldCodec { /// object IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static object ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType ?? typeof(object)); } if (field.FieldType is null || field.FieldType == typeof(object)) return ReadObject(ref reader, field); var specificSerializer = reader.Session.CodecProvider.GetCodec(field.FieldType); return specificSerializer.ReadValue(ref reader, field); } [MethodImpl(MethodImplOptions.NoInlining)] private static object ReadObject(ref Reader reader, Field field) { field.EnsureWireType(WireType.LengthPrefixed); var length = reader.ReadVarUInt32(); if (length != 0) throw new UnexpectedLengthPrefixValueException(nameof(Object), 0, length); var result = new object(); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) => WriteField(ref writer, fieldIdDelta, expectedType, value); /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } var specificSerializer = writer.Session.CodecProvider.GetCodec(value.GetType()); specificSerializer.WriteField(ref writer, fieldIdDelta, expectedType, value); } /// /// Writes a field. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, object value) where TBufferWriter : IBufferWriter => WriteField(ref writer, fieldIdDelta, null, value); void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { // only the untyped writer will need to support actual object type values if (value is null || value.GetType() == typeof(object)) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(object), WireType.LengthPrefixed); writer.WriteVarUInt7(0U); return; } var specificSerializer = writer.Session.CodecProvider.GetCodec(value.GetType()); specificSerializer.WriteField(ref writer, fieldIdDelta, expectedType, value); } } /// /// Copier for . /// [RegisterCopier] public sealed class ObjectCopier : IDeepCopier { /// /// Creates a deep copy of the provided input. /// /// The input. /// The context. /// A copy of . public static object DeepCopy(object input, CopyContext context) { return context.TryGetCopy(input, out var result) ? result : input.GetType() == typeof(object) ? input : context.DeepCopy(input); } object IDeepCopier.DeepCopy(object input, CopyContext context) { return context.TryGetCopy(input, out var result) ? result : input.GetType() == typeof(object) ? input : context.DeepCopy(input); } object IDeepCopier.DeepCopy(object input, CopyContext context) => input is null || input.GetType() == typeof(object) ? input : context.DeepCopy(input); } } ================================================ FILE: src/Orleans.Serialization/Codecs/QueueCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class QueueCodec : IFieldCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public QueueCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Queue value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public Queue ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); Queue result = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); result = new Queue(length); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } result.Enqueue(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } if (result is null) { result = new(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized queue is missing its length field."); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class QueueCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(Queue); private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public QueueCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public Queue DeepCopy(Queue input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new Queue(input.Count); context.RecordCopy(input, result); foreach (var item in input) { result.Enqueue(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(Queue input, Queue output, CopyContext context) { foreach (var item in input) { output.Enqueue(_copier.DeepCopy(item, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ReadOnlyCollectionCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Orleans.Serialization.Codecs { /// /// Surrogate type used by . /// /// The element type. [GenerateSerializer] public struct ReadOnlyCollectionSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; } /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ReadOnlyCollectionCodec : GeneralizedReferenceTypeSurrogateCodec, ReadOnlyCollectionSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public ReadOnlyCollectionCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override ReadOnlyCollection ConvertFromSurrogate(ref ReadOnlyCollectionSurrogate surrogate) => new(surrogate.Values); /// public override void ConvertToSurrogate(ReadOnlyCollection value, ref ReadOnlyCollectionSurrogate surrogate) => surrogate.Values = new(value); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ReadOnlyCollectionCopier : IDeepCopier> { private readonly Type _fieldType = typeof(ReadOnlyCollection); private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public ReadOnlyCollectionCopier(IDeepCopier elementCopier) { _elementCopier = elementCopier; } /// public ReadOnlyCollection DeepCopy(ReadOnlyCollection input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var tempResult = new T[input.Count]; for (var i = 0; i < tempResult.Length; i++) { tempResult[i] = _elementCopier.DeepCopy(input[i], context); } result = new ReadOnlyCollection(tempResult); context.RecordCopy(input, result); return result; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ReadOnlyDictionaryCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Orleans.Serialization.Codecs { [RegisterSerializer] public sealed class ReadOnlyDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ReadOnlyDictionarySurrogate> { public ReadOnlyDictionaryCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } public override ReadOnlyDictionary ConvertFromSurrogate(ref ReadOnlyDictionarySurrogate surrogate) => new(surrogate.Values); public override void ConvertToSurrogate(ReadOnlyDictionary value, ref ReadOnlyDictionarySurrogate surrogate) => surrogate.Values = new(value); } [GenerateSerializer] public struct ReadOnlyDictionarySurrogate { [Id(0)] public Dictionary Values; } [RegisterCopier] public sealed class ReadOnlyDictionaryCopier : IDeepCopier> { private readonly Type _fieldType = typeof(ReadOnlyDictionary); private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; public ReadOnlyDictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } public ReadOnlyDictionary DeepCopy(ReadOnlyDictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } // There is a possibility for infinite recursion here if any value in the input collection is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var temp = new Dictionary(input.Count); foreach (var pair in input) { temp[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } result = new ReadOnlyDictionary(temp); context.RecordCopy(input, result); return result; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/ReferenceCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Codecs { /// /// Functionality for reading and writing object references. /// public static class ReferenceCodec { /// /// Indicates that the field being serialized or deserialized is a value type. /// /// The serializer session. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void MarkValueField(SerializerSession session) => session.ReferencedObjects.MarkValueField(); /// /// Write an object reference if has already been written. /// This overload is suitable only for static codecs where expected type is statically known. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryWriteReferenceFieldExpected(ref Writer writer, uint fieldId, object value) where TBufferWriter : IBufferWriter { if (!writer.Session.ReferencedObjects.GetOrAddReference(value, out var reference)) { return false; } writer.WriteFieldHeaderExpected(fieldId, WireType.Reference); writer.WriteVarUInt32(reference); return true; } /// /// Write an object reference if has already been written and has been tracked via . /// /// The buffer writer type. /// The writer. /// The field identifier. /// The expected type. /// The value. /// if a reference was written, otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryWriteReferenceField( ref Writer writer, uint fieldId, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (!writer.Session.ReferencedObjects.GetOrAddReference(value, out var reference)) { return false; } writer.WriteFieldHeader(fieldId, expectedType, value?.GetType(), WireType.Reference); writer.WriteVarUInt32(reference); return true; } /// /// Write an object reference if has already been written and has been tracked via . /// /// /// This overload allows specifying a fixed reference type for codecs that implement . /// The buffer writer type. /// The writer. /// The field identifier. /// The expected type. /// The actual type. /// The value. /// if a reference was written, otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryWriteReferenceField( ref Writer writer, uint fieldId, Type expectedType, Type actualType, object value) where TBufferWriter : IBufferWriter { if (!writer.Session.ReferencedObjects.GetOrAddReference(value, out var reference)) { return false; } writer.WriteFieldHeader(fieldId, expectedType, value is null ? null : actualType, WireType.Reference); writer.WriteVarUInt32(reference); return true; } /// /// Writes the null reference. /// /// The buffer writer type. /// The writer. /// The field identifier. [MethodImpl(MethodImplOptions.NoInlining)] public static void WriteNullReference( ref Writer writer, uint fieldId) where TBufferWriter : IBufferWriter { writer.Session.ReferencedObjects.MarkValueField(); writer.WriteFieldHeaderExpected(fieldId, WireType.Reference); writer.WriteByte(1); // writer.WriteVarUInt32(0U); } /// /// Reads a referenced value. /// /// The type of the referenced object. /// The reader input type. /// The reader. /// The field. /// The referenced value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T ReadReference(ref Reader reader, Field field) => (T)ReadReference(ref reader, field.FieldType ?? typeof(T)); /// /// Reads the reference. /// /// The reader input type. /// The reader. /// The field type. /// The referenced value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object ReadReference(ref Reader reader, Type fieldType) { MarkValueField(reader.Session); var reference = reader.ReadVarUInt32(); if (reference == 0) return null; return ReadReference(ref reader, fieldType, reference); } private static object ReadReference(ref Reader reader, Type fieldType, uint reference) { var value = reader.Session.ReferencedObjects.TryGetReferencedObject(reference); if (value is null) throw new ReferenceNotFoundException(fieldType, reference); return value switch { UnknownFieldMarker marker => DeserializeFromMarker(ref reader, fieldType, marker, reference), _ => value, }; } private static object DeserializeFromMarker( ref Reader reader, Type fieldType, UnknownFieldMarker marker, uint reference) { // Capture state from the reader and session. var session = reader.Session; var originalPosition = reader.Position; var referencedObjects = session.ReferencedObjects; var originalCurrentReferenceId = referencedObjects.CurrentReferenceId; var originalReferenceToObjectCount = referencedObjects.ReferenceToObjectCount; // Deserialize the object, replacing the marker in the session. try { // Create a reader at the position specified by the marker. reader.ForkFrom(marker.Position, out var referencedReader); // Determine the correct type for the field. fieldType = marker.Field.FieldType ?? fieldType; // Get a serializer for that type. var specificSerializer = session.CodecProvider.GetCodec(fieldType); // Reset the session's reference id so that the deserialized objects overwrite the placeholder markers. referencedObjects.CurrentReferenceId = reference - 1; referencedObjects.ReferenceToObjectCount = referencedObjects.GetReferenceIndex(marker); return specificSerializer.ReadValue(ref referencedReader, marker.Field); } finally { // Revert the reference id. referencedObjects.CurrentReferenceId = originalCurrentReferenceId; referencedObjects.ReferenceToObjectCount = originalReferenceToObjectCount; reader.ResumeFrom(originalPosition); } } /// /// Records that an object was read or written. /// /// The session. /// The value. public static void RecordObject(SerializerSession session, object value) => session.ReferencedObjects.RecordReferenceField(value); /// /// Records that an object was read or written. /// /// The session. /// The value. /// The reference identifier. public static void RecordObject(SerializerSession session, object value, uint referenceId) => session.ReferencedObjects.RecordReferenceField(value, referenceId); /// /// Records and returns a placeholder reference id for objects which cannot be immediately deserialized. /// /// The session. /// The placeholder reference id. public static uint CreateRecordPlaceholder(SerializerSession session) => session.ReferencedObjects.CreateRecordPlaceholder(); } } ================================================ FILE: src/Orleans.Serialization/Codecs/ReferenceTypeSurrogateCodec.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Surrogate serializer for . /// /// The type which the implementation of this class supports. /// The surrogate type serialized in place of . public abstract class ReferenceTypeSurrogateCodec : IFieldCodec where TSurrogate : struct { private readonly Type CodecFieldType = typeof(TField); private readonly IValueSerializer _surrogateSerializer; /// /// Initializes a new instance of the class. /// /// The surrogate serializer. protected ReferenceTypeSurrogateCodec(IValueSerializer surrogateSerializer) { _surrogateSerializer = surrogateSerializer; } /// public TField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } var fieldType = field.FieldType; if (fieldType is null || fieldType == CodecFieldType) { field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); var result = ConvertFromSurrogate(ref surrogate); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } // The type is a descendant, not an exact match, so get the specific serializer for it. var specificSerializer = reader.Session.CodecProvider.GetCodec(fieldType); return (TField)specificSerializer.ReadValue(ref reader, field); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } var fieldType = value.GetType(); if (fieldType == CodecFieldType) { writer.WriteStartObject(fieldIdDelta, expectedType, fieldType); TSurrogate surrogate = default; ConvertToSurrogate(value, ref surrogate); _surrogateSerializer.Serialize(ref writer, ref surrogate); writer.WriteEndObject(); } else { writer.SerializeUnexpectedType(fieldIdDelta, expectedType, value); } } /// /// Converts a surrogate value to the field type. /// /// The surrogate. /// The value. public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); /// /// Converts a value to the surrogate type. /// /// The value. /// The surrogate. public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); } } ================================================ FILE: src/Orleans.Serialization/Codecs/SkipFieldExtension.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// A serializer which skips all fields which it encounters. /// public class SkipFieldCodec : IFieldCodec { /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { throw new NotImplementedException(); } /// public object ReadValue(ref Reader reader, Field field) { reader.SkipField(field); return null; } } /// /// Extension methods for to skip fields. /// public static class SkipFieldExtension { /// /// Skips the current field. /// /// The reader input type. /// The reader. /// The field. public static void SkipField(this ref Reader reader, Field field) { switch (field.WireType) { case WireType.Reference: case WireType.VarInt: reader.Session.ReferencedObjects.MarkValueField(); _ = reader.ReadVarUInt64(); break; case WireType.TagDelimited: reader.Session.ReferencedObjects.MarkValueField(); SkipTagDelimitedField(ref reader); break; case WireType.LengthPrefixed: reader.Session.ReferencedObjects.MarkValueField(); SkipLengthPrefixedField(ref reader); break; case WireType.Fixed32: reader.Session.ReferencedObjects.MarkValueField(); reader.Skip(4); break; case WireType.Fixed64: reader.Session.ReferencedObjects.MarkValueField(); reader.Skip(8); break; case WireType.Extended: if (!field.IsEndBaseOrEndObject) { ThrowUnexpectedExtendedWireType(field); } break; default: ThrowUnexpectedWireType(field); break; } } internal static void ThrowUnexpectedExtendedWireType(Field field) => throw new ArgumentOutOfRangeException( $"Unexpected {nameof(ExtendedWireType)} value [{field.ExtendedWireType}] in field {field} while skipping field."); internal static void ThrowUnexpectedWireType(Field field) => throw new ArgumentOutOfRangeException( $"Unexpected {nameof(WireType)} value [{field.WireType}] in field {field} while skipping field."); internal static void SkipLengthPrefixedField(ref Reader reader) { var length = reader.ReadVarUInt32(); reader.Skip(length); } private static void SkipTagDelimitedField(ref Reader reader) { while (true) { var field = reader.ReadFieldHeader(); if (field.IsEndObject) { break; } reader.SkipField(field); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/SortedDictionaryCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class SortedDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, SortedDictionarySurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public SortedDictionaryCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override SortedDictionary ConvertFromSurrogate(ref SortedDictionarySurrogate surrogate) { var result = new SortedDictionary(surrogate.Comparer); foreach (var kvp in surrogate.Values) { result.Add(kvp.Key, kvp.Value); } return result; } /// public override void ConvertToSurrogate(SortedDictionary value, ref SortedDictionarySurrogate surrogate) { surrogate.Values = new(value); surrogate.Comparer = value.Comparer == Comparer.Default ? null : value.Comparer; } } /// /// Surrogate type for . /// /// The key type. /// The value type. [GenerateSerializer] public struct SortedDictionarySurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List> Values; /// /// Gets or sets the comparer. /// /// The comparer. [Id(1)] public IComparer Comparer; } /// /// Copier for . /// /// The key type. /// The value type. [RegisterCopier] public sealed class SortedDictionaryCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(SortedDictionary); private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public SortedDictionaryCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } /// public SortedDictionary DeepCopy(SortedDictionary input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new SortedDictionary(input.Comparer); context.RecordCopy(input, result); foreach (var pair in input) { result[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } return result; } /// public void DeepCopy(SortedDictionary input, SortedDictionary output, CopyContext context) { foreach (var pair in input) { output[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/SortedListCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class SortedListCodec : GeneralizedReferenceTypeSurrogateCodec, SortedListSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public SortedListCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override SortedList ConvertFromSurrogate(ref SortedListSurrogate surrogate) { if (surrogate.Values is null) { return null; } else { var result = new SortedList(surrogate.Values.Count, surrogate.Comparer); foreach (var kvp in surrogate.Values) { result.Add(kvp.Key, kvp.Value); } return result; } } /// public override void ConvertToSurrogate(SortedList value, ref SortedListSurrogate surrogate) { surrogate.Values = new(value); surrogate.Comparer = value.Comparer == Comparer.Default ? null : value.Comparer; } } /// /// Surrogate type for . /// /// The key type. /// The value type. [GenerateSerializer] public struct SortedListSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List> Values; /// /// Gets or sets the comparer. /// /// The comparer. [Id(1)] public IComparer Comparer; } /// /// Copier for . /// /// The key type. /// The value type. [RegisterCopier] public sealed class SortedListCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(SortedList); private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public SortedListCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } /// public SortedList DeepCopy(SortedList input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new SortedList(input.Comparer); context.RecordCopy(input, result); foreach (var pair in input) { result[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } return result; } /// public void DeepCopy(SortedList input, SortedList output, CopyContext context) { foreach (var pair in input) { output[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/SortedSetCodec.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class SortedSetCodec : GeneralizedReferenceTypeSurrogateCodec, SortedSetSurrogate> { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public SortedSetCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } /// public override SortedSet ConvertFromSurrogate(ref SortedSetSurrogate surrogate) => new(surrogate.Values, surrogate.Comparer); /// public override void ConvertToSurrogate(SortedSet value, ref SortedSetSurrogate surrogate) { surrogate.Values = new(value); surrogate.Comparer = value.Comparer == Comparer.Default ? null : value.Comparer; } } /// /// Surrogate type for . /// /// The element type. [GenerateSerializer] public struct SortedSetSurrogate { /// /// Gets or sets the values. /// /// The values. [Id(0)] public List Values; /// /// Gets or sets the comparer. /// /// The comparer. [Id(1)] public IComparer Comparer; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class SortedSetCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(SortedSet); private readonly IDeepCopier _elementCopier; /// /// Initializes a new instance of the class. /// /// The element copier. public SortedSetCopier(IDeepCopier elementCopier) { _elementCopier = elementCopier; } /// public SortedSet DeepCopy(SortedSet input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() as object != _fieldType as object) { return context.DeepCopy(input); } result = new SortedSet(input.Comparer); context.RecordCopy(input, result); foreach (var element in input) { result.Add(_elementCopier.DeepCopy(element, context)); } return result; } /// public void DeepCopy(SortedSet input, SortedSet output, CopyContext context) { foreach (var element in input) { output.Add(_elementCopier.DeepCopy(element, context)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/StackCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs; /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class StackCodec : IFieldCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public StackCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Stack value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public Stack ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); T[] array = null; var i = 0; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } array = new T[length]; i = length - 1; break; case 1: if (array is null) { ThrowLengthFieldMissing(); } array[i--] = _fieldCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } array ??= []; var result = new Stack(array); ReferenceCodec.RecordObject(reader.Session, array, placeholderReferenceId); return result; } private void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(Stack)}, {length}, is greater than total length of input."); private void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized stack is missing its length field."); } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class StackCopier : IDeepCopier>, IBaseCopier> { private readonly Type _fieldType = typeof(Stack); private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public StackCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public Stack DeepCopy(Stack input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() != _fieldType) { return context.DeepCopy(input); } result = new Stack(input.Count); context.RecordCopy(input, result); var array = new T[input.Count]; input.CopyTo(array, 0); for (var i = array.Length - 1; i >= 0; --i) { result.Push(_copier.DeepCopy(array[i], context)); } return result; } /// public void DeepCopy(Stack input, Stack output, CopyContext context) { var array = new T[input.Count]; input.CopyTo(array, 0); for (var i = array.Length - 1; i >= 0; --i) { output.Push(_copier.DeepCopy(array[i], context)); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/StringCodec.cs ================================================ using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class StringCodec : IFieldCodec { /// string IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var result = ReadRaw(ref reader, reader.ReadVarUInt32()); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// /// Reads the raw string content. /// /// Encoded string length in bytes. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ReadRaw(ref Reader reader, uint numBytes) { if (reader.TryReadBytes((int)numBytes, out var span)) return Encoding.UTF8.GetString(span); return ReadMultiSegment(ref reader, numBytes); } private static string ReadMultiSegment(ref Reader reader, uint numBytes) { var array = ArrayPool.Shared.Rent((int)numBytes); var span = array.AsSpan(0, (int)numBytes); reader.ReadBytes(span); var res = Encoding.UTF8.GetString(span); ArrayPool.Shared.Return(array); return res; } void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, string value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) return; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(string), WireType.LengthPrefixed); var numBytes = Encoding.UTF8.GetByteCount(value); writer.WriteVarUInt32((uint)numBytes); WriteRaw(ref writer, value, numBytes); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, string value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) { return; } writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.LengthPrefixed); var numBytes = Encoding.UTF8.GetByteCount(value); writer.WriteVarUInt32((uint)numBytes); WriteRaw(ref writer, value, numBytes); } /// /// Writes the raw string content. /// /// String to be encoded. /// Encoded string length in bytes. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRaw(ref Writer writer, string value, int numBytes) where TBufferWriter : IBufferWriter { if (numBytes < 512) { writer.EnsureContiguous(numBytes); } var currentSpan = writer.WritableSpan; // If there is enough room in the current span for the encoded data, // then encode directly into the output buffer. if (numBytes <= currentSpan.Length) { writer.AdvanceSpan(Encoding.UTF8.GetBytes(value, currentSpan)); } else { WriteMultiSegment(ref writer, value, numBytes); } } private static void WriteMultiSegment(ref Writer writer, string value, int remainingBytes) where TBufferWriter : IBufferWriter { var encoder = Encoding.UTF8.GetEncoder(); var input = value.AsSpan(); while (true) { encoder.Convert(input, writer.WritableSpan, true, out var charsUsed, out var bytesWritten, out var completed); writer.AdvanceSpan(bytesWritten); if (completed) { Debug.Assert(charsUsed == input.Length && bytesWritten == remainingBytes); break; } remainingBytes -= bytesWritten; input = input[charsUsed..]; writer.Allocate(Math.Min(remainingBytes, Writer.MaxMultiSegmentSizeHint)); } } } } ================================================ FILE: src/Orleans.Serialization/Codecs/TimeOnlyCodec.cs ================================================ #if NET6_0_OR_GREATER using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs; /// /// Serializer for . /// [RegisterSerializer] public sealed class TimeOnlyCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TimeOnly value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(TimeOnly), WireType.Fixed64); writer.WriteInt64(value.Ticks); } /// /// Writes a field without type info (expected type is statically known). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, TimeOnly value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed64); writer.WriteInt64(value.Ticks); } /// TimeOnly IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static TimeOnly ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.Fixed64); return new TimeOnly(reader.ReadInt64()); } } #endif ================================================ FILE: src/Orleans.Serialization/Codecs/TimeSpanCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class TimeSpanCodec : IFieldCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TimeSpan value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(TimeSpan), WireType.Fixed64); writer.WriteInt64(value.Ticks); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, TimeSpan value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed64); writer.WriteInt64(value.Ticks); } /// TimeSpan IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static TimeSpan ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); field.EnsureWireType(WireType.Fixed64); return TimeSpan.FromTicks(reader.ReadInt64()); } } } ================================================ FILE: src/Orleans.Serialization/Codecs/TupleCodec.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _valueCodec; /// /// Initializes a new instance of the class. /// /// The value codec. public TupleCodec(IFieldCodec valueCodec) { _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _valueCodec.WriteField(ref writer, 1, CodecElementType, value.Item1); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _valueCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The copier. public TupleCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); public bool IsShallowCopyable() => _copier is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create(_copier is null ? input.Item1 : _copier.DeepCopy(input.Item1, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for /// /// The type of the tuple's first component. /// The type of the tuple's second component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; /// /// Initializes a new instance of the class. /// /// The copier for . /// The copier for . public TupleCopier(IDeepCopier copier1, IDeepCopier copier2) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null; public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); var item4 = default(T4); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: item4 = _item4Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3, item4); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context), _copier4 is null ? input.Item4 : _copier4.DeepCopy(input.Item4, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); var item4 = default(T4); var item5 = default(T5); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: item5 = _item5Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3, item4, item5); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context), _copier4 is null ? input.Item4 : _copier4.DeepCopy(input.Item4, context), _copier5 is null ? input.Item5 : _copier5.DeepCopy(input.Item5, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); var item4 = default(T4); var item5 = default(T5); var item6 = default(T6); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: item6 = _item6Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3, item4, item5, item6); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context), _copier4 is null ? input.Item4 : _copier4.DeepCopy(input.Item4, context), _copier5 is null ? input.Item5 : _copier5.DeepCopy(input.Item5, context), _copier6 is null ? input.Item6 : _copier6.DeepCopy(input.Item6, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly Type ElementType7 = typeof(T7); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; private readonly IFieldCodec _item7Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); _item7Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item7Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); _item7Codec.WriteField(ref writer, 1, ElementType7, value.Item7); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); var item4 = default(T4); var item5 = default(T5); var item6 = default(T6); var item7 = default(T7); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: item6 = _item6Codec.ReadValue(ref reader, header); break; case 7: item7 = _item7Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3, item4, item5, item6, item7); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; private readonly IDeepCopier _copier7; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6, IDeepCopier copier7) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); _copier7 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier7); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null && _copier7 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = Tuple.Create( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context), _copier4 is null ? input.Item4 : _copier4.DeepCopy(input.Item4, context), _copier5 is null ? input.Item5 : _copier5.DeepCopy(input.Item5, context), _copier6 is null ? input.Item6 : _copier6.DeepCopy(input.Item6, context), _copier7 is null ? input.Item7 : _copier7.DeepCopy(input.Item7, context)); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. /// The type of the tuple's eighth component. [RegisterSerializer] public sealed class TupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly Type ElementType7 = typeof(T7); private readonly Type ElementType8 = typeof(T8); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; private readonly IFieldCodec _item7Codec; private readonly IFieldCodec _item8Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public TupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec, IFieldCodec item8Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); _item7Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item7Codec); _item8Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item8Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Tuple value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); _item7Codec.WriteField(ref writer, 1, ElementType7, value.Item7); _item8Codec.WriteField(ref writer, 1, ElementType8, value.Rest); writer.WriteEndObject(); } /// public Tuple ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var item1 = default(T1); var item2 = default(T2); var item3 = default(T3); var item4 = default(T4); var item5 = default(T5); var item6 = default(T6); var item7 = default(T7); var item8 = default(T8); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: item6 = _item6Codec.ReadValue(ref reader, header); break; case 7: item7 = _item7Codec.ReadValue(ref reader, header); break; case 8: item8 = _item8Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } var result = new Tuple(item1, item2, item3, item4, item5, item6, item7, item8); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. /// The type of the tuple's eighth component. [RegisterCopier] public sealed class TupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly Type _fieldType = typeof(Tuple); private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; private readonly IDeepCopier _copier7; private readonly IDeepCopier _copier8; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public TupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6, IDeepCopier copier7, IDeepCopier copier8) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); _copier7 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier7); _copier8 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier8); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null && _copier7 is null && _copier8 is null; /// public Tuple DeepCopy(Tuple input, CopyContext context) { if (context.TryGetCopy(input, out Tuple existing)) return existing; if (input.GetType() as object != _fieldType as object) return context.DeepCopy(input); if (IsShallowCopyable()) return input; // There is a possibility for infinite recursion here if any value in the input tuple is able to take part in a cyclic reference. // Mitigate that by returning a shallow-copy in such a case. context.RecordCopy(input, input); var result = new Tuple( _copier1 is null ? input.Item1 : _copier1.DeepCopy(input.Item1, context), _copier2 is null ? input.Item2 : _copier2.DeepCopy(input.Item2, context), _copier3 is null ? input.Item3 : _copier3.DeepCopy(input.Item3, context), _copier4 is null ? input.Item4 : _copier4.DeepCopy(input.Item4, context), _copier5 is null ? input.Item5 : _copier5.DeepCopy(input.Item5, context), _copier6 is null ? input.Item6 : _copier6.DeepCopy(input.Item6, context), _copier7 is null ? input.Item7 : _copier7.DeepCopy(input.Item7, context), _copier8 is null ? input.Rest : _copier8.DeepCopy(input.Rest, context)); context.RecordCopy(input, result); return result; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/TypeSerializerCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Buffers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serialzier for . /// [RegisterSerializer] public sealed class TypeSerializerCodec : IFieldCodec, IDerivedTypeCodec { void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Type value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, typeof(Type), value)) { return; } writer.WriteStartObject(fieldIdDelta, expectedType, typeof(Type)); WriteField(ref writer, value); } /// /// Writes a field without type info (expected type is statically known). /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Writer writer, uint fieldIdDelta, Type value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) { return; } writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.TagDelimited); WriteField(ref writer, value); } private static void WriteField(ref Writer writer, Type value) where TBufferWriter : IBufferWriter { var schemaType = writer.Session.WellKnownTypes.TryGetWellKnownTypeId(value, out var id) ? SchemaType.WellKnown : writer.Session.ReferencedTypes.TryGetTypeReference(value, out id) ? SchemaType.Referenced : SchemaType.Encoded; // Write the encoding type. ByteCodec.WriteField(ref writer, 0, (byte)schemaType); if (schemaType == SchemaType.Encoded) { // If the type is encoded, write the length-prefixed bytes. ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value); } else { // If the type is referenced or well-known, write it as a varint. UInt32Codec.WriteField(ref writer, 2, id); } writer.WriteEndObject(); } /// Type IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// /// The reader input type. /// The reader. /// The field. /// The value. public static Type ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); uint fieldId = 0; var schemaType = default(SchemaType); uint id = 0; Type result = null; while (true) { reader.ReadFieldHeader(ref field); if (field.IsEndBaseOrEndObject) { break; } fieldId += field.FieldIdDelta; switch (fieldId) { case 0: schemaType = (SchemaType)ByteCodec.ReadValue(ref reader, field); break; case 1: ReferenceCodec.MarkValueField(reader.Session); result = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 2: id = UInt32Codec.ReadValue(ref reader, field); break; default: reader.ConsumeUnknownField(field); break; } } switch (schemaType) { case SchemaType.Referenced: result = reader.Session.ReferencedTypes.GetReferencedType(id); break; case SchemaType.WellKnown: if (!reader.Session.WellKnownTypes.TryGetWellKnownType(id, out result)) ThrowUnknownWellKnownType(id); break; case SchemaType.Encoded: // Type codec should not update the type reference map, otherwise unknown-field deserialization could be broken break; default: ThrowInvalidSchemaType(schemaType); break; } if (result is null) ThrowMissingType(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } private static void ThrowInvalidSchemaType(SchemaType schemaType) => throw new NotSupportedException( $"SchemaType {schemaType} is not supported by {nameof(TypeSerializerCodec)}."); private static void ThrowUnknownWellKnownType(uint id) => throw new UnknownWellKnownTypeException(id); private static void ThrowMissingType() => throw new TypeMissingException(); } } ================================================ FILE: src/Orleans.Serialization/Codecs/UnknownFieldMarker.cs ================================================ using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Marker object used to denote an unknown field and its position into a stream of data. /// public sealed class UnknownFieldMarker { /// /// Initializes a new instance of the class. /// /// The field. /// The position. public UnknownFieldMarker(Field field, long position) { Field = field; Position = position; } /// /// The position into the stream at which this field occurs. /// public long Position { get; } /// /// The field header. /// public Field Field { get; } /// public override string ToString() => $"{nameof(Position)}: 0x{Position:X}, {nameof(Field)}: {Field}"; } } ================================================ FILE: src/Orleans.Serialization/Codecs/UriCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class UriCodec : IFieldCodec, IDerivedTypeCodec { Uri IFieldCodec.ReadValue(ref Buffers.Reader reader, Field field) => ReadValue(ref reader, field); /// /// Reads a value. /// public static Uri ReadValue(ref Buffers.Reader reader, Field field) { if (field.WireType == WireType.Reference) return ReferenceCodec.ReadReference(ref reader, field); field.EnsureWireTypeTagDelimited(); var referencePlaceholder = ReferenceCodec.CreateRecordPlaceholder(reader.Session); reader.ReadFieldHeader(ref field); if (!field.HasFieldId || field.FieldIdDelta != 0) throw new RequiredFieldMissingException("Serialized Uri is missing its value."); var uriString = StringCodec.ReadValue(ref reader, field); reader.ReadFieldHeader(ref field); reader.ConsumeEndBaseOrEndObject(ref field); var result = new Uri(uriString, UriKind.RelativeOrAbsolute); ReferenceCodec.RecordObject(reader.Session, result, referencePlaceholder); return result; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, Type expectedType, Uri value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, typeof(Uri), value)) return; writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(Uri), WireType.TagDelimited); StringCodec.WriteField(ref writer, 0, value.OriginalString); writer.WriteEndObject(); } /// /// Writes a field without type info (expected type is statically known). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, Uri value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceFieldExpected(ref writer, fieldIdDelta, value)) return; writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.TagDelimited); StringCodec.WriteField(ref writer, 0, value.OriginalString); writer.WriteEndObject(); } } [RegisterCopier] internal sealed class UriCopier : ShallowCopier, IDerivedTypeCopier { } } ================================================ FILE: src/Orleans.Serialization/Codecs/ValueTupleCodec.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec { /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ValueTuple value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.VarInt); writer.WriteVarUInt7(0); } /// public ValueTuple ReadValue(ref Reader reader, Field field) { field.EnsureWireType(WireType.VarInt); ReferenceCodec.MarkValueField(reader.Session); var length = reader.ReadVarUInt32(); if (length != 0) throw new UnexpectedLengthPrefixValueException(nameof(ValueTuple), 0, length); return default; } } /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T); private readonly IFieldCodec _valueCodec; /// /// Initializes a new instance of the class. /// /// The value codec. public ValueTupleCodec(IFieldCodec valueCodec) { _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); } /// public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, ValueTuple value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _valueCodec.WriteField(ref writer, 1, ElementType1, value.Item1); writer.WriteEndObject(); } /// public ValueTuple ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); var item1 = default(T); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: item1 = _valueCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return new ValueTuple(item1); } } /// /// Copier for . /// [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier, IOptionalDeepCopier { /// public bool IsShallowCopyable() => true; /// object IDeepCopier.DeepCopy(object input, CopyContext context) => input; /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) => input; } /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The copier. public ValueTupleCopier(IDeepCopier copier) => _copier = OrleansGeneratedCodeHelper.GetOptionalCopier(copier); /// public bool IsShallowCopyable() => _copier is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy((ValueTuple)input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier != null) input.Item1 = _copier.DeepCopy(input.Item1, context); return input; } } /// /// Serializer for /// /// The type of the tuple's first component. /// The type of the tuple's second component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); } /// public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); writer.WriteEndObject(); } /// public (T1, T2) ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for /// /// The type of the tuple's first component. /// The type of the tuple's second component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; /// /// Initializes a new instance of the class. /// /// The copier for . /// The copier for . public ValueTupleCopier(IDeepCopier copier1, IDeepCopier copier2) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); } /// public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2, T3) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); writer.WriteEndObject(); } /// public (T1, T2, T3) ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2, T3) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2, T3))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); } /// public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2, T3, T4) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); writer.WriteEndObject(); } /// public (T1, T2, T3, T4) ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2, T3, T4) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: res.Item4 = _item4Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2, T3, T4))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); if (_copier4 != null) input.Item4 = _copier4.DeepCopy(input.Item4, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2, T3, T4, T5) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); writer.WriteEndObject(); } /// public (T1, T2, T3, T4, T5) ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2, T3, T4, T5) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: res.Item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: res.Item5 = _item5Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2, T3, T4, T5))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); if (_copier4 != null) input.Item4 = _copier4.DeepCopy(input.Item4, context); if (_copier5 != null) input.Item5 = _copier5.DeepCopy(input.Item5, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2, T3, T4, T5, T6) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); writer.WriteEndObject(); } /// public (T1, T2, T3, T4, T5, T6) ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2, T3, T4, T5, T6) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: res.Item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: res.Item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: res.Item6 = _item6Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2, T3, T4, T5, T6))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); if (_copier4 != null) input.Item4 = _copier4.DeepCopy(input.Item4, context); if (_copier5 != null) input.Item5 = _copier5.DeepCopy(input.Item5, context); if (_copier6 != null) input.Item6 = _copier6.DeepCopy(input.Item6, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly Type ElementType7 = typeof(T7); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; private readonly IFieldCodec _item7Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); _item7Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item7Codec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, (T1, T2, T3, T4, T5, T6, T7) value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); _item7Codec.WriteField(ref writer, 1, ElementType7, value.Item7); writer.WriteEndObject(); } /// public (T1, T2, T3, T4, T5, T6, T7) ReadValue( ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); (T1, T2, T3, T4, T5, T6, T7) res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: res.Item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: res.Item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: res.Item6 = _item6Codec.ReadValue(ref reader, header); break; case 7: res.Item7 = _item7Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; private readonly IDeepCopier _copier7; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6, IDeepCopier copier7) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); _copier7 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier7); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null && _copier7 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy(((T1, T2, T3, T4, T5, T6, T7))input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); if (_copier4 != null) input.Item4 = _copier4.DeepCopy(input.Item4, context); if (_copier5 != null) input.Item5 = _copier5.DeepCopy(input.Item5, context); if (_copier6 != null) input.Item6 = _copier6.DeepCopy(input.Item6, context); if (_copier7 != null) input.Item7 = _copier7.DeepCopy(input.Item7, context); return input; } } /// /// Serializer for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. /// The type of the tuple's eighth component. [RegisterSerializer] public sealed class ValueTupleCodec : IFieldCodec> where T8 : struct { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly Type ElementType7 = typeof(T7); private readonly Type ElementType8 = typeof(T8); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; private readonly IFieldCodec _item7Codec; private readonly IFieldCodec _item8Codec; /// /// Initializes a new instance of the class. /// /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. /// The codec. public ValueTupleCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec, IFieldCodec item8Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); _item7Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item7Codec); _item8Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item8Codec); } /// public void WriteField( ref Writer writer, uint fieldIdDelta, Type expectedType, ValueTuple value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); _item1Codec.WriteField(ref writer, 1, ElementType1, value.Item1); _item2Codec.WriteField(ref writer, 1, ElementType2, value.Item2); _item3Codec.WriteField(ref writer, 1, ElementType3, value.Item3); _item4Codec.WriteField(ref writer, 1, ElementType4, value.Item4); _item5Codec.WriteField(ref writer, 1, ElementType5, value.Item5); _item6Codec.WriteField(ref writer, 1, ElementType6, value.Item6); _item7Codec.WriteField(ref writer, 1, ElementType7, value.Item7); _item8Codec.WriteField(ref writer, 1, ElementType8, value.Rest); writer.WriteEndObject(); } /// public ValueTuple ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); ValueTuple res = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: res.Item1 = _item1Codec.ReadValue(ref reader, header); break; case 2: res.Item2 = _item2Codec.ReadValue(ref reader, header); break; case 3: res.Item3 = _item3Codec.ReadValue(ref reader, header); break; case 4: res.Item4 = _item4Codec.ReadValue(ref reader, header); break; case 5: res.Item5 = _item5Codec.ReadValue(ref reader, header); break; case 6: res.Item6 = _item6Codec.ReadValue(ref reader, header); break; case 7: res.Item7 = _item7Codec.ReadValue(ref reader, header); break; case 8: res.Rest = _item8Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return res; } } /// /// Copier for . /// /// The type of the tuple's first component. /// The type of the tuple's second component. /// The type of the tuple's third component. /// The type of the tuple's fourth component. /// The type of the tuple's fifth component. /// The type of the tuple's sixth component. /// The type of the tuple's seventh component. /// The type of the tuple's eighth component. [RegisterCopier] public sealed class ValueTupleCopier : IDeepCopier>, IOptionalDeepCopier where T8 : struct { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; private readonly IDeepCopier _copier7; private readonly IDeepCopier _copier8; /// /// Initializes a new instance of the class. /// /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. /// The copier. public ValueTupleCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6, IDeepCopier copier7, IDeepCopier copier8) { _copier1 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier1); _copier2 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier2); _copier3 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier3); _copier4 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier4); _copier5 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier5); _copier6 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier6); _copier7 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier7); _copier8 = OrleansGeneratedCodeHelper.GetOptionalCopier(copier8); } public bool IsShallowCopyable() => _copier1 is null && _copier2 is null && _copier3 is null && _copier4 is null && _copier5 is null && _copier6 is null && _copier7 is null && _copier8 is null; object IDeepCopier.DeepCopy(object input, CopyContext context) => IsShallowCopyable() ? input : DeepCopy((ValueTuple)input, context); /// public ValueTuple DeepCopy(ValueTuple input, CopyContext context) { if (_copier1 != null) input.Item1 = _copier1.DeepCopy(input.Item1, context); if (_copier2 != null) input.Item2 = _copier2.DeepCopy(input.Item2, context); if (_copier3 != null) input.Item3 = _copier3.DeepCopy(input.Item3, context); if (_copier4 != null) input.Item4 = _copier4.DeepCopy(input.Item4, context); if (_copier5 != null) input.Item5 = _copier5.DeepCopy(input.Item5, context); if (_copier6 != null) input.Item6 = _copier6.DeepCopy(input.Item6, context); if (_copier7 != null) input.Item7 = _copier7.DeepCopy(input.Item7, context); if (_copier8 != null) input.Rest = _copier8.DeepCopy(input.Rest, context); return input; } } } ================================================ FILE: src/Orleans.Serialization/Codecs/VersionCodec.cs ================================================ using Orleans.Serialization.Serializers; using System; namespace Orleans.Serialization.Codecs { /// /// Serializer for . /// [RegisterSerializer] public sealed class VersionCodec : GeneralizedReferenceTypeSurrogateCodec { /// /// Initializes a new instance of the class. /// /// The surrogate serializer. public VersionCodec(IValueSerializer surrogateSerializer) : base(surrogateSerializer) { } /// public override Version ConvertFromSurrogate(ref VersionSurrogate surrogate) { int revision = surrogate.Revision; int build = surrogate.Build; // ArgumentOutOfRangeException is thrown if any argument is less than zero // Build and Revision are -1 if they are not defined during construction if (revision != -1) { return new Version(surrogate.Major, surrogate.Minor, build, revision); } else if (build != -1) { return new Version(surrogate.Major, surrogate.Minor, build); } else { return new Version(surrogate.Major, surrogate.Minor); } } /// public override void ConvertToSurrogate(Version value, ref VersionSurrogate surrogate) { surrogate.Major = value.Major; surrogate.Minor = value.Minor; surrogate.Build = value.Build; surrogate.Revision = value.Revision; } } /// /// Surrogate type for . /// [GenerateSerializer] public struct VersionSurrogate { /// /// Gets or sets the major version component. /// /// The major version component. [Id(0)] public int Major; /// /// Gets or sets the minor version component. /// /// The minor version component. [Id(1)] public int Minor; /// /// Gets or sets the build number. /// /// The build number. [Id(2)] public int Build; /// /// Gets or sets the revision. /// /// The revision. [Id(3)] public int Revision; } } ================================================ FILE: src/Orleans.Serialization/Codecs/VoidCodec.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Codecs { /// /// Serializer for unknown types. /// internal sealed class VoidCodec : IFieldCodec { /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (!ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { ThrowNotNullException(value); } } /// public object ReadValue(ref Reader reader, Field field) { field.EnsureWireType(WireType.Reference); return ReferenceCodec.ReadReference(ref reader, field.FieldType); } private static void ThrowNotNullException(object value) => throw new InvalidOperationException( $"Expected a value of null, but encountered a value of '{value}'."); } /// /// Copier for unknown types. /// internal sealed class VoidCopier : IDeepCopier { public object DeepCopy(object input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } ThrowNotNullException(input); return null; } private static void ThrowNotNullException(object value) => throw new InvalidOperationException($"Expected a value of null, but encountered a value of type '{value.GetType()}'."); } } ================================================ FILE: src/Orleans.Serialization/Codecs/WellKnownStringComparerCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; #if !NET6_0_OR_GREATER using System.Runtime.Serialization; #endif namespace Orleans.Serialization.Codecs { /// /// Serializer for well-known types. /// [Alias("StringComparer")] public sealed class WellKnownStringComparerCodec : IGeneralizedCodec { private static readonly Type CodecType = typeof(WellKnownStringComparerCodec); private readonly StringComparer _ordinalComparer; private readonly StringComparer _ordinalIgnoreCaseComparer; private readonly EqualityComparer _defaultEqualityComparer; private readonly Type _ordinalType; private readonly Type _ordinalIgnoreCaseType; private readonly Type _defaultEqualityType; #if !NET6_0_OR_GREATER private readonly StreamingContext _streamingContext; private readonly FormatterConverter _formatterConverter; #endif /// /// Initializes a new instance of the class. /// public WellKnownStringComparerCodec() { _ordinalComparer = StringComparer.Ordinal; _ordinalIgnoreCaseComparer = StringComparer.OrdinalIgnoreCase; _defaultEqualityComparer = EqualityComparer.Default; _ordinalType = _ordinalComparer.GetType(); _ordinalIgnoreCaseType = _ordinalIgnoreCaseComparer.GetType(); _defaultEqualityType = _defaultEqualityComparer.GetType(); #if !NET6_0_OR_GREATER _streamingContext = new StreamingContext(StreamingContextStates.All); _formatterConverter = new FormatterConverter(); #endif } /// public bool IsSupportedType(Type type) => type == CodecType || type == _ordinalType || type == _ordinalIgnoreCaseType || type == _defaultEqualityType || !type.IsAbstract && typeof(IEqualityComparer).IsAssignableFrom(type) && type.Assembly.Equals(typeof(IEqualityComparer).Assembly); /// public object ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); uint type = default; CompareOptions options = default; int lcid = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: type = UInt32Codec.ReadValue(ref reader, header); break; case 1: options = (CompareOptions)UInt32Codec.ReadValue(ref reader, header); break; case 2: lcid = Int32Codec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } if (type == 0) { return null; } else if (type == 1) { if (options.HasFlag(CompareOptions.IgnoreCase)) { return StringComparer.OrdinalIgnoreCase; } else { return StringComparer.Ordinal; } } else if (type == 2) { if (lcid == CultureInfo.InvariantCulture.LCID) { if (options == CompareOptions.None) { return StringComparer.InvariantCulture; } else if (options == CompareOptions.IgnoreCase) { return StringComparer.InvariantCultureIgnoreCase; } // Otherwise, perhaps some other options were specified, in which case we fall-through to create a new comparer. } var cultureInfo = CultureInfo.GetCultureInfo(lcid); var result = StringComparer.Create(cultureInfo, options); return result; } ThrowNotSupported(field, type); return null; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { uint type; CompareOptions compareOptions = default; CompareInfo compareInfo = default; if (value is null) { type = 0; } else { #if NET6_0_OR_GREATER var comparer = (IEqualityComparer)value; if (StringComparer.IsWellKnownOrdinalComparer(comparer, out var ignoreCase)) { // Ordinal. This also handles EqualityComparer.Default. type = 1; if (ignoreCase) { compareOptions = CompareOptions.IgnoreCase; } } else if (StringComparer.IsWellKnownCultureAwareComparer(comparer, out compareInfo, out compareOptions)) { type = 2; } else { ThrowNotSupported(value.GetType()); return; } #else var isOrdinal = _ordinalComparer.Equals(value) || _defaultEqualityComparer.Equals(value); var isOrdinalIgnoreCase = _ordinalIgnoreCaseComparer.Equals(value); if (isOrdinal) { type = 1; } else if (isOrdinalIgnoreCase) { type = 1; compareOptions = CompareOptions.IgnoreCase; } else if (TryGetWellKnownCultureAwareComparerInfo(value, out compareInfo, out compareOptions, out var ignoreCase)) { type = 2; if (ignoreCase) { compareOptions |= CompareOptions.IgnoreCase; } } else { ThrowNotSupported(value.GetType()); return; } #endif } ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(WellKnownStringComparerCodec), WireType.TagDelimited); UInt32Codec.WriteField(ref writer, 0, type); UInt32Codec.WriteField(ref writer, 1, (uint)compareOptions); if (compareInfo is not null) { Int32Codec.WriteField(ref writer, 1, compareInfo.LCID); } writer.WriteEndObject(); } #if !NET6_0_OR_GREATER private bool TryGetWellKnownCultureAwareComparerInfo(object value, out CompareInfo compareInfo, out CompareOptions compareOptions, out bool ignoreCase) { compareInfo = default; compareOptions = default; ignoreCase = default; if (value is ISerializable serializable) { var info = new SerializationInfo(value.GetType(), _formatterConverter); serializable.GetObjectData(info, _streamingContext); foreach (var entry in info) { switch (entry.Name) { case "_compareInfo": compareInfo = entry.Value as CompareInfo; break; case "_options": compareOptions = (CompareOptions)entry.Value; break; case "_ignoreCase": ignoreCase = (bool)entry.Value; break; } } return compareInfo is not null; } return false; } #endif [DoesNotReturn] private static void ThrowNotSupported(Field field, uint value) => throw new NotSupportedException($"Values of type {field.FieldType} are not supported. Value: {value}"); [DoesNotReturn] private static void ThrowNotSupported(Type type) => throw new NotSupportedException($"Values of type {type} are not supported"); } /// /// Serializer for . /// /// The element type. [RegisterCopier] [RegisterSerializer] internal sealed class EqualityComparerBaseCodec : IBaseCodec>, IBaseCopier> { /// public void DeepCopy(EqualityComparer input, EqualityComparer output, CopyContext context) { } /// public void Deserialize(ref Reader reader, EqualityComparer value) => reader.ConsumeEndBaseOrEndObject(); /// public void Serialize(ref Writer writer, EqualityComparer value) where TBufferWriter : IBufferWriter { } } /// /// Serializer for . /// /// The element type. [RegisterCopier] [RegisterSerializer] internal sealed class ComparerBaseCodec : IBaseCodec>, IBaseCopier> { /// public void DeepCopy(Comparer input, Comparer output, CopyContext context) { } /// public void Deserialize(ref Reader reader, Comparer value) => reader.ConsumeEndBaseOrEndObject(); /// public void Serialize(ref Writer writer, Comparer value) where TBufferWriter : IBufferWriter { } } } ================================================ FILE: src/Orleans.Serialization/Configuration/DefaultTypeManifestProvider.cs ================================================ using System; using System.Net; using Microsoft.Extensions.Options; using Orleans.Serialization.Invocation; namespace Orleans.Serialization.Configuration { internal class DefaultTypeManifestProvider : TypeManifestProviderBase, IPostConfigureOptions { public void PostConfigure(string name, TypeManifestOptions options) { // Clean up the options bookkeeping. options.TypeManifestProviders.Clear(); } protected override void ConfigureInner(TypeManifestOptions typeManifest) { var wellKnownTypes = typeManifest.WellKnownTypeIds; wellKnownTypes[0] = typeof(void); // Represents the type of null wellKnownTypes[1] = typeof(int); wellKnownTypes[2] = typeof(string); wellKnownTypes[3] = typeof(bool); wellKnownTypes[4] = typeof(short); wellKnownTypes[5] = typeof(long); wellKnownTypes[6] = typeof(sbyte); wellKnownTypes[7] = typeof(uint); wellKnownTypes[8] = typeof(ushort); wellKnownTypes[9] = typeof(ulong); wellKnownTypes[10] = typeof(byte); wellKnownTypes[11] = typeof(float); wellKnownTypes[12] = typeof(double); wellKnownTypes[13] = typeof(decimal); wellKnownTypes[14] = typeof(char); wellKnownTypes[15] = typeof(Guid); wellKnownTypes[16] = typeof(DateTime); wellKnownTypes[17] = typeof(TimeSpan); wellKnownTypes[18] = typeof(DateTimeOffset); wellKnownTypes[19] = typeof(object); wellKnownTypes[20] = typeof(DotNetSerializableCodec); wellKnownTypes[21] = typeof(ExceptionCodec); wellKnownTypes[22] = typeof(byte[]); wellKnownTypes[23] = typeof(object[]); wellKnownTypes[24] = typeof(char[]); wellKnownTypes[25] = typeof(int[]); wellKnownTypes[26] = typeof(string[]); wellKnownTypes[27] = typeof(Type); #if NET6_0_OR_GREATER wellKnownTypes[28] = typeof(DateOnly); wellKnownTypes[29] = typeof(TimeOnly); #endif wellKnownTypes[30] = typeof(DayOfWeek); wellKnownTypes[31] = typeof(Uri); wellKnownTypes[32] = typeof(Version); wellKnownTypes[33] = typeof(IPAddress); wellKnownTypes[34] = typeof(IPEndPoint); wellKnownTypes[35] = typeof(ExceptionResponse); wellKnownTypes[36] = typeof(CompletedResponse); #if NET7_0_OR_GREATER wellKnownTypes[37] = typeof(Int128); wellKnownTypes[38] = typeof(UInt128); #endif #if NET5_0_OR_GREATER wellKnownTypes[39] = typeof(Half); #endif var allowedTypes = typeManifest.AllowedTypes; allowedTypes.Add("System.Globalization.CompareOptions"); } } } ================================================ FILE: src/Orleans.Serialization/Configuration/ITypeManifestProvider.cs ================================================ using Microsoft.Extensions.Options; namespace Orleans.Serialization.Configuration { /// /// Provides type manifest information. /// public interface ITypeManifestProvider : IConfigureOptions { } /// /// Base class for generated type manifest providers. /// public abstract class TypeManifestProviderBase : ITypeManifestProvider { /// void IConfigureOptions.Configure(TypeManifestOptions options) { if (options.TypeManifestProviders.Add(Key)) { ConfigureInner(options); } } /// /// Gets the unique identifier for this type manifest provider. /// public virtual object Key => GetType(); /// /// Configures the provided type manifest options. /// /// The type manifest options. protected abstract void ConfigureInner(TypeManifestOptions options); } } ================================================ FILE: src/Orleans.Serialization/Configuration/TypeManifestOptions.cs ================================================ using System; using System.Collections.Generic; using Orleans.Serialization.TypeSystem; namespace Orleans.Serialization.Configuration { /// /// Configuration of all types which are known to the code generator. /// public sealed class TypeManifestOptions { /// /// Gets or sets a value indicating whether should be enabled. /// /// /// This property does not cause to be invoked. /// That is the responsibility of the consuming framework. /// public bool? EnableConfigurationAnalysis { get; set; } /// /// Gets the set of known activators, which are responsible for creating instances of a given type. /// public HashSet Activators { get; } = new HashSet(); /// /// Gets the set of known field codecs, which are responsible for serializing and deserializing fields of a given type. /// public HashSet FieldCodecs { get; } = new HashSet(); /// /// Gets the set of known serializers, which are responsible for serializing and deserializing a given type. /// public HashSet Serializers { get; } = new HashSet(); /// /// Gets the set of copiers, which are responsible for creating deep copies of a given type. /// public HashSet Copiers { get; } = new HashSet(); /// /// Gets the set of converters, which are responsible for converting from one type to another. /// public HashSet Converters { get; } = new HashSet(); /// /// Gets the set of known interfaces, which are interfaces that have corresponding proxies in the collection. /// public HashSet Interfaces { get; } = new HashSet(); /// /// Gets the set of known interface proxies, which capture method invocations which can be serialized, deserialized, and invoked against an implementation of this interface. /// /// /// This allows decoupling the caller and target, so that remote procedure calls can be implemented by capturing an invocation, transmitting it, and later invoking it against a target object. /// public HashSet InterfaceProxies { get; } = new HashSet(); /// /// Gets the set of interface implementations, which are implementations of the interfaces present in . /// public HashSet InterfaceImplementations { get; } = new HashSet(); /// /// Gets the mapping of well-known type identifiers to their corresponding type. /// public Dictionary WellKnownTypeIds { get; } = new Dictionary(); /// /// Gets the mapping of well-known type aliases to their corresponding type. /// public Dictionary WellKnownTypeAliases { get; } = new Dictionary(); /// /// Gets the mapping of allowed type names. /// public HashSet AllowedTypes { get; } = new HashSet(StringComparer.Ordinal); /// /// Gets the mapping from compound type aliases to types. /// public CompoundTypeAliasTree CompoundTypeAliases { get; } = CompoundTypeAliasTree.Create(); /// /// Gets or sets a value indicating whether to allow all types by default. /// Default: . /// public bool AllowAllTypes { get; set; } /// /// Gets the set of type manifest providers which have configured this instance. /// internal HashSet TypeManifestProviders { get; } = new(); } } ================================================ FILE: src/Orleans.Serialization/Configuration/TypeManifestProviderAttribute.cs ================================================ using System; namespace Orleans.Serialization.Configuration { /// /// Defines a metadata provider for this assembly. /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class TypeManifestProviderAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The metadata provider type. public TypeManifestProviderAttribute(Type providerType) { if (providerType is null) { throw new ArgumentNullException(nameof(providerType)); } if (!typeof(ITypeManifestProvider).IsAssignableFrom(providerType)) { throw new ArgumentException($"Provided type {providerType} must implement {typeof(ITypeManifestProvider)}", nameof(providerType)); } ProviderType = providerType; } /// /// Gets the manifest provider type. /// public Type ProviderType { get; } } } ================================================ FILE: src/Orleans.Serialization/Exceptions.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Serialization { internal static class ExceptionHelper { public static T ThrowArgumentOutOfRange(string argument) => throw new ArgumentOutOfRangeException(argument); } /// /// Base exception for any serializer exception. /// [Serializable] [GenerateSerializer] public class SerializerException : Exception { /// /// Initializes a new instance of the class. /// public SerializerException() { } /// /// Initializes a new instance of the class. /// /// The message that describes the error. public SerializerException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. public SerializerException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif protected SerializerException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// An field identifier was expected but not present. /// [Serializable] [GenerateSerializer] public sealed class FieldIdNotPresentException : SerializerException { /// /// Initializes a new instance of the class. /// public FieldIdNotPresentException() : base("Attempted to access the field id from a tag which cannot have a field id.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private FieldIdNotPresentException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// The schema type is invalid. /// [Serializable] [GenerateSerializer] public sealed class SchemaTypeInvalidException : SerializerException { /// /// Initializes a new instance of the class. /// public SchemaTypeInvalidException() : base("Attempted to access the schema type from a tag which cannot have a schema type.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private SchemaTypeInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// The field type is invalid. /// [Serializable] [GenerateSerializer] public sealed class FieldTypeInvalidException : SerializerException { /// /// Initializes a new instance of the class. /// public FieldTypeInvalidException() : base("Attempted to access the schema type from a tag which cannot have a schema type.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private FieldTypeInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// A field type was expected but not present. /// [Serializable] [GenerateSerializer] public sealed class FieldTypeMissingException : SerializerException { /// /// Initializes a new instance of the class. /// /// The type. public FieldTypeMissingException(Type type) : base($"Attempted to deserialize an instance of abstract type {type}. No concrete type was provided.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private FieldTypeMissingException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// The extended wire type is invalid. /// [Serializable] [GenerateSerializer] public sealed class ExtendedWireTypeInvalidException : SerializerException { /// /// Initializes a new instance of the class. /// public ExtendedWireTypeInvalidException() : base( "Attempted to access the extended wire type from a tag which does not have an extended wire type.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private ExtendedWireTypeInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// The wire type is unsupported. /// [Serializable] [GenerateSerializer] public sealed class UnsupportedWireTypeException : SerializerException { /// /// Initializes a new instance of the class. /// public UnsupportedWireTypeException() { } /// /// Initializes a new instance of the class. /// /// The message that describes the error. public UnsupportedWireTypeException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private UnsupportedWireTypeException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// A referenced value was not found. /// [Serializable] [GenerateSerializer] public sealed class ReferenceNotFoundException : SerializerException { /// /// Gets the target reference. /// /// The target reference. [Id(0)] public uint TargetReference { get; } /// /// Gets the type of the target reference. /// /// The type of the target reference. [Id(1)] public Type TargetReferenceType { get; } /// /// Initializes a new instance of the class. /// /// Type of the target. /// The target identifier. public ReferenceNotFoundException(Type targetType, uint targetId) : base( $"Reference with id {targetId} and type {targetType} not found.") { TargetReference = targetId; TargetReferenceType = targetType; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private ReferenceNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { TargetReference = info.GetUInt32(nameof(TargetReference)); TargetReferenceType = (Type)info.GetValue(nameof(TargetReferenceType), typeof(Type)); } /// #if NET8_0_OR_GREATER [Obsolete] #endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(TargetReference), TargetReference); info.AddValue(nameof(TargetReferenceType), TargetReferenceType); } } /// /// A referenced type was not found. /// [Serializable] [GenerateSerializer] public sealed class UnknownReferencedTypeException : SerializerException { /// /// Initializes a new instance of the class. /// /// The reference. public UnknownReferencedTypeException(uint reference) : base($"Unknown referenced type {reference}.") { Reference = reference; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private UnknownReferencedTypeException(SerializationInfo info, StreamingContext context) : base(info, context) { info.AddValue(nameof(Reference), Reference); } /// /// Gets or sets the reference. /// /// The reference. [Id(0)] public uint Reference { get; set; } /// #if NET8_0_OR_GREATER [Obsolete] #endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); Reference = info.GetUInt32(nameof(Reference)); } } /// /// A reference to a value is not supported here. /// [Serializable] [GenerateSerializer] public sealed class ReferenceFieldNotSupportedException : SerializerException { /// /// Gets the type of the target reference. /// /// The type of the target reference. [Id(0)] public Type TargetReferenceType { get; } /// /// Initializes a new instance of the class. /// /// Type of the target. public ReferenceFieldNotSupportedException(Type targetType) : base( $"Reference with type {targetType} not allowed here.") { TargetReferenceType = targetType; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private ReferenceFieldNotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context) { TargetReferenceType = (Type)info.GetValue(nameof(TargetReferenceType), typeof(Type)); } /// #if NET8_0_OR_GREATER [Obsolete] #endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(TargetReferenceType), TargetReferenceType); } } /// /// A well-known type was not known. /// [Serializable] [GenerateSerializer] public sealed class UnknownWellKnownTypeException : SerializerException { /// /// Initializes a new instance of the class. /// /// The identifier. public UnknownWellKnownTypeException(uint id) : base($"Unknown well-known type {id}.") { Id = id; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private UnknownWellKnownTypeException(SerializationInfo info, StreamingContext context) : base(info, context) { info.AddValue(nameof(Id), Id); } /// /// Gets or sets the identifier. /// /// The identifier. [Id(0)] public uint Id { get; set; } /// #if NET8_0_OR_GREATER [Obsolete] #endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); Id = info.GetUInt32(nameof(Id)); } } /// /// A specified type is not allowed. /// [Serializable] [GenerateSerializer] public sealed class IllegalTypeException : SerializerException { /// /// Initializes a new instance of the class. /// /// Name of the type. public IllegalTypeException(string typeName) : base($"Type \"{typeName}\" is not allowed.") { TypeName = typeName; } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private IllegalTypeException(SerializationInfo info, StreamingContext context) : base(info, context) { TypeName = info.GetString(nameof(TypeName)); } /// /// Gets the name of the type. /// /// The name of the type. [Id(0)] public string TypeName { get; } /// #if NET8_0_OR_GREATER [Obsolete] #endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(TypeName), TypeName); } } /// /// A type was expected but not found. /// [Serializable] [GenerateSerializer] public sealed class TypeMissingException : SerializerException { /// /// Initializes a new instance of the class. /// public TypeMissingException() : base("Expected a type but none were encountered.") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private TypeMissingException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// A required field was not present. /// [Serializable] [GenerateSerializer] public sealed class RequiredFieldMissingException : SerializerException { /// /// Initializes a new instance of the class. /// /// The message that describes the error. public RequiredFieldMissingException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private RequiredFieldMissingException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// No suitable serializer codec was found for a specified type. /// [Serializable] [GenerateSerializer] public sealed class CodecNotFoundException : SerializerException { /// /// Initializes a new instance of the class. /// /// The message that describes the error. public CodecNotFoundException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private CodecNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// A length encoded field which is expected to have a length /// [Serializable] [GenerateSerializer] public sealed class UnexpectedLengthPrefixValueException : SerializerException { /// /// Initializes a new instance of the class. /// /// The message that describes the error. public UnexpectedLengthPrefixValueException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// Name of the type. /// The expected length. /// The actual length. public UnexpectedLengthPrefixValueException(string typeName, uint expectedLength, uint actualLength) : base($"VarInt length specified in header for {typeName} should be {expectedLength} but is instead {actualLength}") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. #if NET8_0_OR_GREATER [Obsolete] #endif private UnexpectedLengthPrefixValueException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Serialization/GeneratedCodeHelpers/OrleansGeneratedCodeHelper.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Activators; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.GeneratedCodeHelpers { /// /// Utilities for use by generated code. /// public static class OrleansGeneratedCodeHelper { private static readonly ThreadLocal ResolutionState = new ThreadLocal(() => new RecursiveServiceResolutionState()); private sealed class RecursiveServiceResolutionState { private int _depth; public List Callers { get; } = new List(); public void Enter(object caller) { ++_depth; if (caller is not null) { Callers.Add(caller); } } public void Exit() { if (--_depth <= 0) { Callers.Clear(); } } } /// /// Unwraps the provided service if it was wrapped. /// /// The service type. /// The caller. /// The codec provider. /// The unwrapped service. public static TService GetService(object caller, ICodecProvider codecProvider) { var state = ResolutionState.Value; try { state.Enter(caller); foreach (var c in state.Callers) { if (c is TService s && !(c is IServiceHolder)) { return s; } } var val = ActivatorUtilities.GetServiceOrCreateInstance(codecProvider.Services); while (val is IServiceHolder wrapping) { val = wrapping.Value; } return val; } finally { state.Exit(); } } /// /// Unwraps the provided service if it was wrapped. /// /// The service type. /// The caller. /// The service. /// The unwrapped service. public static TService UnwrapService(object caller, TService service) { var state = ResolutionState.Value; try { state.Enter(caller); foreach (var c in state.Callers) { if (c is TService s and not IServiceHolder) { return s; } } return Unwrap(service); } finally { state.Exit(); } static TService Unwrap(TService val) { while (val is IServiceHolder wrapping) { val = wrapping.Value; } return val; } } internal static object TryGetService(Type serviceType) { var state = ResolutionState.Value; foreach (var c in state.Callers) { var type = c?.GetType(); if (serviceType == type) { return c; } } return null; } /// /// Returns the provided copier if it's not shallow-copyable. /// public static IDeepCopier GetOptionalCopier(IDeepCopier copier) => copier is IOptionalDeepCopier o && o.IsShallowCopyable() ? null : copier; /// /// Generated code helper method which throws an . /// public static object InvokableThrowArgumentOutOfRange(int index, int maxArgs) => throw new ArgumentOutOfRangeException(message: $"The argument index value {index} must be between 0 and {maxArgs}", null); /// /// Expects empty content (a single field header of either or ), /// but will consume any unexpected fields also. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ConsumeEndBaseOrEndObject(this ref Reader reader) { Unsafe.SkipInit(out Field field); reader.ReadFieldHeader(ref field); reader.ConsumeEndBaseOrEndObject(ref field); } /// /// Expects empty content (a single field header of either or ), /// but will consume any unexpected fields also. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ConsumeEndBaseOrEndObject(this ref Reader reader, scoped ref Field field) { if (!field.IsEndBaseOrEndObject) ConsumeUnexpectedContent(ref reader, ref field); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ConsumeUnexpectedContent(this ref Reader reader, scoped ref Field field) { do { reader.ConsumeUnknownField(ref field); reader.ReadFieldHeader(ref field); } while (!field.IsEndBaseOrEndObject); } /// /// Serializes an unexpected value. /// /// The buffer writer type. /// The writer. /// The field identifier delta. /// The expected type. /// The value. [MethodImpl(MethodImplOptions.NoInlining)] public static void SerializeUnexpectedType(this ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { var specificSerializer = writer.Session.CodecProvider.GetCodec(value.GetType()); specificSerializer.WriteField(ref writer, fieldIdDelta, expectedType, value); } /// /// Deserializes an unexpected value. /// /// The reader input type. /// The value type. /// The reader. /// The field. /// The value. [MethodImpl(MethodImplOptions.NoInlining)] public static TField DeserializeUnexpectedType(this ref Reader reader, scoped ref Field field) where TField : class { var specificSerializer = reader.Session.CodecProvider.GetCodec(field.FieldType); return (TField)specificSerializer.ReadValue(ref reader, field); } /// /// Gets the matching the provided values. /// /// Type of the interface. /// Name of the method. /// The method type parameters. /// The parameter types. /// The corresponding . [MethodImpl(MethodImplOptions.NoInlining)] public static MethodInfo GetMethodInfoOrDefault(Type interfaceType, string methodName, Type[] methodTypeParameters, Type[] parameterTypes) { if (interfaceType is null) { return null; } foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { var current = method; if (current.Name != methodName) { continue; } if (current.ContainsGenericParameters != methodTypeParameters is { Length: > 0 }) { continue; } if (methodTypeParameters is { Length: > 0 }) { if (methodTypeParameters.Length != current.GetGenericArguments().Length) { continue; } current = current.MakeGenericMethod(methodTypeParameters); } var parameters = current.GetParameters(); if (parameters.Length != (parameterTypes?.Length ?? 0)) { continue; } var isMatch = true; for (int i = 0; i < parameters.Length; i++) { if (!parameters[i].ParameterType.Equals(parameterTypes[i])) { isMatch = false; break; } } if (!isMatch) { continue; } return current; } foreach (var implemented in interfaceType.GetInterfaces()) { if (GetMethodInfoOrDefault(implemented, methodName, methodTypeParameters, parameterTypes) is { } method) { return method; } } return null; } /// /// Default copier implementation for (rarely copied) exception classes /// public abstract class ExceptionCopier : IDeepCopier, IBaseCopier where T : B where B : Exception { private readonly Type _fieldType = typeof(T); private readonly IActivator _activator; private readonly IBaseCopier _baseTypeCopier; protected ExceptionCopier(ICodecProvider codecProvider) { _activator = GetService>(this, codecProvider); _baseTypeCopier = GetService>(this, codecProvider); } public T DeepCopy(T original, CopyContext context) { if (original is null) { return null; } if (original.GetType() != _fieldType) { return context.DeepCopy(original); } var result = _activator.Create(); DeepCopy(original, result, context); return result; } public virtual void DeepCopy(T input, T output, CopyContext context) => _baseTypeCopier.DeepCopy(input, output, context); } } } ================================================ FILE: src/Orleans.Serialization/Hosting/ISerializerBuilder.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace Orleans.Serialization { /// /// Builder interface for configuring serialization. /// public interface ISerializerBuilder { /// /// Gets the service collection. /// IServiceCollection Services { get; } } } ================================================ FILE: src/Orleans.Serialization/Hosting/ReferencedAssemblyProvider.cs ================================================ using Microsoft.Extensions.DependencyModel; using System; using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; #if NETCOREAPP3_1_OR_GREATER using System.Runtime.Loader; #endif namespace Orleans.Serialization.Internal { public static class ReferencedAssemblyProvider { public static IEnumerable GetRelevantAssemblies() { var parts = new HashSet(); AddFromDependencyContext(parts); #if NETCOREAPP3_1_OR_GREATER AddFromAssemblyLoadContext(parts); #endif foreach (var loadedAsm in AppDomain.CurrentDomain.GetAssemblies()) { AddAssembly(parts, loadedAsm); } return parts; } public static void AddAssembly(HashSet parts, Assembly assembly) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } if (!assembly.IsDefined(typeof(ApplicationPartAttribute))) { return; } if (!parts.Add(assembly)) { return; } AddAssembly(parts, assembly); // Add all referenced application parts. foreach (var referencedAsm in GetApplicationPartAssemblies(assembly)) { AddAssembly(parts, referencedAsm); } } #if NETCOREAPP3_1_OR_GREATER public static void AddFromAssemblyLoadContext(HashSet parts, AssemblyLoadContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } foreach (var asm in context.Assemblies) { AddAssembly(parts, asm); } } public static void AddFromAssemblyLoadContext(HashSet parts, Assembly assembly = null) { assembly ??= typeof(ReferencedAssemblyProvider).Assembly; var assemblies = new HashSet(); var context = AssemblyLoadContext.GetLoadContext(assembly); foreach (var asm in context.Assemblies) { // Skip assemblies which have not had code generation executed against them and already-seen assemblies. if (!asm.IsDefined(typeof(ApplicationPartAttribute)) || !assemblies.Add(asm)) { continue; } AddAssembly(parts, asm); } } #endif public static void AddFromDependencyContext(HashSet parts, Assembly assembly = null) { assembly ??= Assembly.GetEntryAssembly(); DependencyContext dependencyContext; if (assembly is null || assembly.IsDynamic) { dependencyContext = DependencyContext.Default; } else { dependencyContext = DependencyContext.Load(assembly); } var assemblies = new HashSet(); if (assembly != null && assembly.IsDefined(typeof(ApplicationPartAttribute))) { AddAssembly(parts, assembly); assemblies.Add(assembly); } if (dependencyContext == null) { return; } #if NETCOREAPP3_1_OR_GREATER var assemblyContext = assembly is not null ? AssemblyLoadContext.GetLoadContext(assembly) ?? AssemblyLoadContext.Default : AssemblyLoadContext.Default; #endif foreach (var lib in dependencyContext.RuntimeLibraries) { if (!lib.Name.Contains("Orleans.Serialization", StringComparison.Ordinal) && !lib.Dependencies.Any(dep => dep.Name.Contains("Orleans.Serialization", StringComparison.Ordinal))) { continue; } try { #if NET5_0_OR_GREATER var name = lib.GetRuntimeAssemblyNames(dependencyContext, System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier).FirstOrDefault(); if (name is null) { continue; } var asm = assemblyContext.LoadFromAssemblyName(name); #else var name = lib.GetRuntimeAssemblyNames(dependencyContext, Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier()).FirstOrDefault(); if (name is null) { continue; } #if NETCOREAPP3_1_OR_GREATER var asm = assemblyContext.LoadFromAssemblyName(name); #else var asm = Assembly.Load(name); #endif #endif if (asm.IsDefined(typeof(ApplicationPartAttribute)) && assemblies.Add(asm)) { AddAssembly(parts, asm); } } catch { // Ignore any exceptions thrown during non-explicit assembly loading. } } } private static IEnumerable GetApplicationPartAssemblies(Assembly assembly) { if (!assembly.IsDefined(typeof(ApplicationPartAttribute))) { return Array.Empty(); } #if NETCOREAPP3_1_OR_GREATER var assemblyContext = AssemblyLoadContext.GetLoadContext(assembly) ?? AssemblyLoadContext.Default; #endif return ExpandApplicationParts( new[] { assembly }.Concat(assembly.GetCustomAttributes() .Select(name => #if NETCOREAPP3_1_OR_GREATER assemblyContext.LoadFromAssemblyName(new AssemblyName(name.AssemblyName)) #else Assembly.Load(new AssemblyName(name.AssemblyName)) #endif ))); static IEnumerable ExpandApplicationParts(IEnumerable assemblies) { if (assemblies == null) { throw new ArgumentNullException(nameof(assemblies)); } var relatedAssemblies = new HashSet(); foreach (var assembly in assemblies) { if (relatedAssemblies.Add(assembly)) { ExpandAssembly(relatedAssemblies, assembly); } } return relatedAssemblies.OrderBy(assembly => assembly.FullName, StringComparer.Ordinal); static void ExpandAssembly(HashSet assemblies, Assembly assembly) { var attributes = assembly.GetCustomAttributes().ToArray(); if (attributes.Length == 0) { return; } #if NETCOREAPP3_1_OR_GREATER var assemblyContext = AssemblyLoadContext.GetLoadContext(assembly) ?? AssemblyLoadContext.Default; #endif foreach (var attribute in attributes) { var assemblyName = new AssemblyName(attribute.AssemblyName); #if NETCOREAPP3_1_OR_GREATER var referenced = assemblyContext.LoadFromAssemblyName(assemblyName); #else var referenced = Assembly.Load(assemblyName); #endif if (assemblies.Add(referenced)) { ExpandAssembly(assemblies, referenced); } } } } } } } ================================================ FILE: src/Orleans.Serialization/Hosting/SerializerBuilderExtensions.cs ================================================ using Orleans.Serialization.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Reflection; using Microsoft.Extensions.Options; namespace Orleans.Serialization { /// /// Extensions for . /// public static class SerializerBuilderExtensions { private static readonly object _assembliesKey = new(); /// /// Configures the serialization builder. /// /// The builder. /// The factory. /// The serialization builder public static ISerializerBuilder Configure(this ISerializerBuilder builder, Func> factory) { builder.Services.AddSingleton>(factory); return builder; } /// /// Configures the serialization builder. /// /// The builder. /// The configuration delegate. /// The serialization builder public static ISerializerBuilder Configure(this ISerializerBuilder builder, IConfigureOptions configure) { builder.Services.AddSingleton>(configure); return builder; } /// /// Configures the serialization builder. /// /// The builder. /// The configuration delegate. /// The serialization builder public static ISerializerBuilder Configure(this ISerializerBuilder builder, Action configure) { builder.Services.Configure(configure); return builder; } /// /// Adds an assembly to the builder. /// /// The builder. /// The assembly. /// The serialization builder public static ISerializerBuilder AddAssembly(this ISerializerBuilder builder, Assembly assembly) { var attrs = assembly.GetCustomAttributes(); foreach (var attr in attrs) { _ = builder.Services.AddSingleton(typeof(IConfigureOptions), attr.ProviderType); } return builder; } } } ================================================ FILE: src/Orleans.Serialization/Hosting/SerializerConfigurationAnalyzer.cs ================================================ using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Orleans.Serialization { /// /// Analyzes serializer configuration to find likely configuration issues. /// public static class SerializerConfigurationAnalyzer { /// /// Analyzes grain interface methods to find parameter types and return types which are not serializable. /// /// /// The codec provider. /// /// /// The type manifest options. /// /// /// A collection of types which have serializability issues. /// public static Dictionary AnalyzeSerializerAvailability(ICodecProvider codecProvider, TypeManifestOptions options) { var allComplaints = new Dictionary(); foreach (var @interface in options.Interfaces) { foreach (var method in @interface.GetMethods(BindingFlags.Instance | BindingFlags.Public)) { if (typeof(Task).IsAssignableFrom(method.ReturnType)) { if (method.ReturnType.IsConstructedGenericType && typeof(Task<>).IsAssignableFrom(method.ReturnType.GetGenericTypeDefinition())) { VisitType(method.ReturnType.GetGenericArguments()[0], method); } } if (method.ReturnType.IsConstructedGenericType && typeof(ValueTask<>).IsAssignableFrom(method.ReturnType.GetGenericTypeDefinition())) { VisitType(method.ReturnType.GetGenericArguments()[0], method); } foreach (var param in method.GetParameters()) { VisitType(param.ParameterType, method); } } } return allComplaints; void VisitType(Type type, MethodInfo methodInfo) { if (!IsEligibleType(type)) { return; } var hasCodec = codecProvider.TryGetCodec(type) is not null; var hasCopier = codecProvider.TryGetDeepCopier(type) is not null; if (!hasCodec || !hasCopier) { if (!allComplaints.TryGetValue(type, out var complaint)) { complaint = allComplaints[type] = new() { HasSerializer = hasCodec, HasCopier = hasCopier, }; } if (!complaint.Methods.TryGetValue(methodInfo.DeclaringType, out var methodList)) { methodList = complaint.Methods[methodInfo.DeclaringType] = new HashSet(); } methodList.Add(methodInfo); } } bool IsEligibleType(Type type) { if (type.IsGenericTypeParameter || type.IsGenericMethodParameter || type.ContainsGenericParameters || type.Equals(typeof(CancellationToken))) { return false; } return true; } } /// /// Represents a configuration issue regarding the serializability of a type used in interface methods. /// public class SerializerConfigurationComplaint { /// /// Gets a collection of interface types which reference the type this complaint represents. /// public Dictionary> Methods { get; } = new (); /// /// Gets or sets a value indicating whether a serializer is available for this type. /// public bool HasSerializer { get; set; } /// /// Gets or sets a value indicating whether a copier is available for this type. /// public bool HasCopier { get; set; } } } } ================================================ FILE: src/Orleans.Serialization/Hosting/ServiceCollectionExtensions.cs ================================================ using System; using System.Buffers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Serialization.Activators; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Configuration; using Orleans.Serialization.Internal; using Orleans.Serialization.Serializers; using Orleans.Serialization.Session; using Orleans.Serialization.TypeSystem; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization { /// /// extensions. /// /// /// Extensions for . /// public static class ServiceCollectionExtensions { /// /// Adds serializer support. /// /// The service collection. /// The configuration delegate. /// The service collection. public static IServiceCollection AddSerializer(this IServiceCollection services, Action configure = null) { // Only add the services once. var context = GetFromServices(services); if (context is null) { context = new ConfigurationContext(services); foreach (var asm in ReferencedAssemblyProvider.GetRelevantAssemblies()) { context.Builder.AddAssembly(asm); } services.Add(context.CreateServiceDescriptor()); services.AddOptions(); services.AddSingleton, DefaultTypeManifestProvider>(); services.AddSingleton, DefaultTypeManifestProvider>(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(sp => sp.GetRequiredService()); services.TryAddSingleton(typeof(IFieldCodec<>), typeof(FieldCodecHolder<>)); services.TryAddSingleton(typeof(IBaseCodec<>), typeof(BaseCodecHolder<>)); services.TryAddSingleton(typeof(IValueSerializer<>), typeof(ValueSerializerHolder<>)); services.TryAddSingleton(typeof(IActivator<>), typeof(ActivatorHolder<>)); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(typeof(IDeepCopier<>), typeof(CopierHolder<>)); services.TryAddSingleton(typeof(IBaseCopier<>), typeof(BaseCopierHolder<>)); // Type filtering services.AddSingleton(); // Session services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); // Serializer services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(typeof(Serializer<>)); services.TryAddSingleton(typeof(ValueSerializer<>)); services.TryAddSingleton(); services.TryAddSingleton(typeof(DeepCopier<>)); } configure?.Invoke(context.Builder); return services; } private static T GetFromServices(IServiceCollection services) { foreach (var service in services) { if (service.ServiceType == typeof(T)) { return (T)service.ImplementationInstance; } } return default; } private sealed class ConfigurationContext { public ConfigurationContext(IServiceCollection services) => Builder = new SerializerBuilder(services); public ServiceDescriptor CreateServiceDescriptor() => new ServiceDescriptor(typeof(ConfigurationContext), this); public ISerializerBuilder Builder { get; } } private class SerializerBuilder : ISerializerBuilder { public SerializerBuilder(IServiceCollection services) => Services = services; public IServiceCollection Services { get; } } private sealed class ActivatorHolder : IActivator, IServiceHolder> { private readonly IActivatorProvider _activatorProvider; private IActivator _activator; public ActivatorHolder(IActivatorProvider codecProvider) { _activatorProvider = codecProvider; } public IActivator Value => _activator ??= _activatorProvider.GetActivator(); public T Create() => Value.Create(); } private sealed class FieldCodecHolder : IFieldCodec, IServiceHolder> { private readonly IFieldCodecProvider _codecProvider; private IFieldCodec _codec; public FieldCodecHolder(IFieldCodecProvider codecProvider) { _codecProvider = codecProvider; } public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter => Value.WriteField(ref writer, fieldIdDelta, expectedType, value); public TField ReadValue(ref Reader reader, Field field) => Value.ReadValue(ref reader, field); public IFieldCodec Value => _codec ??= _codecProvider.GetCodec(); } private sealed class BaseCodecHolder : IBaseCodec, IServiceHolder> where TField : class { private readonly IBaseCodecProvider _provider; private IBaseCodec _baseCodec; public BaseCodecHolder(IBaseCodecProvider provider) { _provider = provider; } public void Serialize(ref Writer writer, TField value) where TBufferWriter : IBufferWriter => Value.Serialize(ref writer, value); public void Deserialize(ref Reader reader, TField value) => Value.Deserialize(ref reader, value); public IBaseCodec Value => _baseCodec ??= _provider.GetBaseCodec(); } private sealed class ValueSerializerHolder : IValueSerializer, IServiceHolder> where TField : struct { private readonly IValueSerializerProvider _provider; private IValueSerializer _serializer; public ValueSerializerHolder(IValueSerializerProvider provider) { _provider = provider; } public void Serialize(ref Writer writer, scoped ref TField value) where TBufferWriter : IBufferWriter => Value.Serialize(ref writer, ref value); public void Deserialize(ref Reader reader, scoped ref TField value) => Value.Deserialize(ref reader, ref value); public IValueSerializer Value => _serializer ??= _provider.GetValueSerializer(); } private sealed class CopierHolder : IDeepCopier, IServiceHolder>, IOptionalDeepCopier { private readonly IDeepCopierProvider _codecProvider; private IDeepCopier _copier; public CopierHolder(IDeepCopierProvider codecProvider) { _codecProvider = codecProvider; } public T DeepCopy(T original, CopyContext context) => Value.DeepCopy(original, context); public object DeepCopy(object original, CopyContext context) => Value.DeepCopy(original, context); public bool IsShallowCopyable() => (Value as IOptionalDeepCopier)?.IsShallowCopyable() ?? false; public IDeepCopier Value => _copier ??= _codecProvider.GetDeepCopier(); } private sealed class BaseCopierHolder : IBaseCopier, IServiceHolder> where T : class { private readonly IDeepCopierProvider _codecProvider; private IBaseCopier _copier; public BaseCopierHolder(IDeepCopierProvider codecProvider) { _codecProvider = codecProvider; } public void DeepCopy(T original, T copy, CopyContext context) => Value.DeepCopy(original, copy, context); public IBaseCopier Value => _copier ??= _codecProvider.GetBaseCopier(); } } /// /// Holds a reference to a service. /// /// The service type. internal interface IServiceHolder { /// /// Gets the service. /// /// The service. T Value { get; } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/DotNetSerializableCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { /// /// Serializer for types which implement the pattern. /// [Alias("ISerializable")] public class DotNetSerializableCodec : IGeneralizedCodec { public static readonly Type CodecType = typeof(DotNetSerializableCodec); private static readonly Type SerializableType = typeof(ISerializable); private readonly SerializationCallbacksFactory _serializationCallbacks; private readonly Func> _createConstructorDelegate; private readonly ConcurrentDictionary> _constructors = new(); #pragma warning disable SYSLIB0050 // Type or member is obsolete private readonly IFormatterConverter _formatterConverter; #pragma warning restore SYSLIB0050 // Type or member is obsolete private readonly StreamingContext _streamingContext; private readonly SerializationEntryCodec _entrySerializer; private readonly TypeConverter _typeConverter; private readonly ValueTypeSerializerFactory _valueTypeSerializerFactory; /// /// Initializes a new instance of the class. /// /// The type resolver. public DotNetSerializableCodec(TypeConverter typeResolver) { #pragma warning disable SYSLIB0050 // Type or member is obsolete _streamingContext = new StreamingContext(StreamingContextStates.All); #pragma warning restore SYSLIB0050 // Type or member is obsolete _typeConverter = typeResolver; _entrySerializer = new SerializationEntryCodec(); _serializationCallbacks = new SerializationCallbacksFactory(); #pragma warning disable SYSLIB0050 // Type or member is obsolete _formatterConverter = new FormatterConverter(); #pragma warning restore SYSLIB0050 // Type or member is obsolete var constructorFactory = new SerializationConstructorFactory(); _createConstructorDelegate = constructorFactory.GetSerializationConstructorDelegate; _valueTypeSerializerFactory = new ValueTypeSerializerFactory( _entrySerializer, constructorFactory, _serializationCallbacks, _formatterConverter, _streamingContext); } /// [SecurityCritical] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } var type = value.GetType(); writer.WriteFieldHeader(fieldIdDelta, expectedType, CodecType, WireType.TagDelimited); if (type.IsValueType) { var serializer = _valueTypeSerializerFactory.GetSerializer(type); serializer.WriteValue(ref writer, value); } else { WriteObject(ref writer, type, value); } writer.WriteEndObject(); } /// [SecurityCritical] public object ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); Type type; var header = reader.ReadFieldHeader(); if (header.FieldIdDelta == 1) { // This is an exception type, so deserialize it as an exception. var typeName = StringCodec.ReadValue(ref reader, header); if (!_typeConverter.TryParse(typeName, out type)) { return ReadFallbackException(ref reader, typeName, placeholderReferenceId); } } else { type = TypeSerializerCodec.ReadValue(ref reader, header); if (type.IsValueType) { var serializer = _valueTypeSerializerFactory.GetSerializer(type); return serializer.ReadValue(ref reader, type); } } return ReadObject(ref reader, type, placeholderReferenceId); } private object ReadFallbackException(ref Reader reader, string typeName, uint placeholderReferenceId) { // Deserialize into a fallback type for unknown exceptions. This means that missing fields will not be represented. var result = (UnavailableExceptionFallbackException)ReadObject(ref reader, typeof(UnavailableExceptionFallbackException), placeholderReferenceId); result.ExceptionType = typeName; return result; } private object ReadObject(ref Reader reader, Type type, uint placeholderReferenceId) { var callbacks = _serializationCallbacks.GetReferenceTypeCallbacks(type); #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(type, _formatterConverter); #pragma warning restore SYSLIB0050 // Type or member is obsolete var result = RuntimeHelpers.GetUninitializedObject(type); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); callbacks.OnDeserializing?.Invoke(result, _streamingContext); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; if (fieldId == 1) { var entry = _entrySerializer.ReadValue(ref reader, header); if (entry.ObjectType is { } entryType) { info.AddValue(entry.Name, entry.Value, entryType); } else { info.AddValue(entry.Name, entry.Value); } } else { reader.ConsumeUnknownField(header); } } var constructor = _constructors.GetOrAdd(info.ObjectType, _createConstructorDelegate); constructor(result, info, _streamingContext); callbacks.OnDeserialized?.Invoke(result, _streamingContext); if (result is IDeserializationCallback callback) { callback.OnDeserialization(_streamingContext.Context); } return result; } private void WriteObject(ref Writer writer, Type type, object value) where TBufferWriter : IBufferWriter { var callbacks = _serializationCallbacks.GetReferenceTypeCallbacks(type); #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(type, _formatterConverter); #pragma warning restore SYSLIB0050 // Type or member is obsolete // Serialize the type name according to the value populated in the SerializationInfo. if (value is Exception) { // For exceptions, the type is serialized as a string to facilitate safe deserialization. var typeName = _typeConverter.Format(info.ObjectType); StringCodec.WriteField(ref writer, 1, typeName); } else { TypeSerializerCodec.WriteField(ref writer, 0, info.ObjectType); } callbacks.OnSerializing?.Invoke(value, _streamingContext); #pragma warning disable SYSLIB0050 // Type or member is obsolete ((ISerializable)value).GetObjectData(info, _streamingContext); #pragma warning restore SYSLIB0050 // Type or member is obsolete var first = true; foreach (var field in info) { var surrogate = new SerializationEntrySurrogate { Name = field.Name, Value = field.Value, ObjectType = field.ObjectType }; _entrySerializer.WriteField(ref writer, first ? 1 : (uint)0, typeof(SerializationEntrySurrogate), surrogate); if (first) { first = false; } } callbacks.OnSerialized?.Invoke(value, _streamingContext); } /// [SecurityCritical] public bool IsSupportedType(Type type) => type == CodecType || typeof(Exception).IsAssignableFrom(type) || SerializableType.IsAssignableFrom(type) && SerializationConstructorFactory.HasSerializationConstructor(type); } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/ExceptionCodec.cs ================================================ using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using Microsoft.Extensions.Options; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization { /// /// Serializer for types. /// [RegisterSerializer] [RegisterCopier] [Alias("Exception")] public sealed class ExceptionCodec : IFieldCodec, IBaseCodec, IGeneralizedCodec, IGeneralizedBaseCodec, IBaseCopier { private readonly StreamingContext _streamingContext; #pragma warning disable SYSLIB0050 // Type or member is obsolete private readonly FormatterConverter _formatterConverter; #pragma warning restore SYSLIB0050 // Type or member is obsolete private readonly Action _baseExceptionConstructor; private readonly TypeConverter _typeConverter; private readonly IFieldCodec> _dictionaryCodec; private readonly IDeepCopier> _dictionaryCopier; private readonly IDeepCopier _exceptionCopier; private readonly ExceptionSerializationOptions _options; /// /// Initializes a new instance of the class. /// /// The type converter. /// The dictionary codec. /// The dictionary copier. /// The exception copier. /// The exception serialization options. public ExceptionCodec( TypeConverter typeConverter, IFieldCodec> dictionaryCodec, IDeepCopier> dictionaryCopier, IDeepCopier exceptionCopier, IOptions exceptionSerializationOptions) { #pragma warning disable SYSLIB0050 // Type or member is obsolete _streamingContext = new StreamingContext(StreamingContextStates.All); _formatterConverter = new FormatterConverter(); #pragma warning restore SYSLIB0050 // Type or member is obsolete _baseExceptionConstructor = new SerializationConstructorFactory().GetSerializationConstructorDelegate(typeof(Exception)); _typeConverter = typeConverter; _dictionaryCodec = dictionaryCodec; _dictionaryCopier = dictionaryCopier; _exceptionCopier = exceptionCopier; _options = exceptionSerializationOptions.Value; } /// public void Deserialize(ref Reader reader, Exception value) { uint fieldId = 0; string message = null; string stackTrace = null; Exception innerException = null; Dictionary data = null; int hResult = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: message = StringCodec.ReadValue(ref reader, header); break; case 1: stackTrace = StringCodec.ReadValue(ref reader, header); break; case 2: innerException = ReadValue(ref reader, header); break; case 3: hResult = Int32Codec.ReadValue(ref reader, header); break; case 4: data = _dictionaryCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } SetBaseProperties(value, message, stackTrace, innerException, hResult, data); } /// /// Gets the object data from the provided exception. /// /// The value. /// A populated value. public SerializationInfo GetObjectData(Exception value) { #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(value.GetType(), _formatterConverter); #pragma warning disable SYSLIB0051 // Type or member is obsolete value.GetObjectData(info, _streamingContext); #pragma warning restore SYSLIB0051 // Type or member is obsolete #pragma warning restore SYSLIB0050 // Type or member is obsolete return info; } /// /// Sets base properties on the provided exception. /// /// The value. /// The message. /// The stack trace. /// The inner exception. /// The HResult. /// The data. public void SetBaseProperties(Exception value, string message, string stackTrace, Exception innerException, int hResult, Dictionary data) { #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(typeof(Exception), _formatterConverter); #pragma warning restore SYSLIB0050 // Type or member is obsolete info.AddValue("Message", message, typeof(string)); info.AddValue("StackTraceString", null, typeof(string)); info.AddValue("InnerException", innerException, typeof(Exception)); info.AddValue("ClassName", value.GetType().ToString(), typeof(string)); info.AddValue("Data", null, typeof(IDictionary)); info.AddValue("HelpURL", null, typeof(string)); #if NET6_0_OR_GREATER info.AddValue("RemoteStackTraceString", null, typeof(string)); #else info.AddValue("RemoteStackTraceString", stackTrace, typeof(string)); #endif info.AddValue("RemoteStackIndex", 0, typeof(int)); info.AddValue("ExceptionMethod", null, typeof(string)); info.AddValue("HResult", hResult); info.AddValue("Source", null, typeof(string)); info.AddValue("WatsonBuckets", null, typeof(byte[])); _baseExceptionConstructor(value, info, _streamingContext); if (data is { }) { foreach (var pair in data) { value.Data[pair.Key] = pair.Value; } } #if NET6_0_OR_GREATER if (stackTrace is not null) { ExceptionDispatchInfo.SetRemoteStackTrace(value, stackTrace); } #endif } /// /// Gets the data property from the provided exception. /// /// The exception. /// The provided exception's property. public Dictionary GetDataProperty(Exception exception) { if (exception.Data is null or { Count: 0 }) { return null; } var tmp = new Dictionary(exception.Data.Count); var enumerator = exception.Data.GetEnumerator(); while (enumerator.MoveNext()) { var entry = enumerator.Entry; tmp[entry.Key] = entry.Value; } return tmp; } /// public void Serialize(ref Writer writer, Exception value) where TBufferWriter : IBufferWriter { StringCodec.WriteField(ref writer, 0, value.Message); StringCodec.WriteField(ref writer, 1, value.StackTrace); WriteField(ref writer, 1, typeof(Exception), value.InnerException); Int32Codec.WriteField(ref writer, 1, value.HResult); if (GetDataProperty(value) is { } dataDictionary) { _dictionaryCodec.WriteField(ref writer, 1, typeof(Dictionary), dataDictionary); } } /// public void SerializeException(ref Writer writer, Exception value) where TBufferWriter : IBufferWriter { StringCodec.WriteField(ref writer, 0, _typeConverter.Format(value.GetType())); StringCodec.WriteField(ref writer, 1, value.Message); StringCodec.WriteField(ref writer, 1, value.StackTrace); WriteField(ref writer, 1, typeof(Exception), value.InnerException); Int32Codec.WriteField(ref writer, 1, value.HResult); if (GetDataProperty(value) is { } dataDictionary) { _dictionaryCodec.WriteField(ref writer, 1, typeof(Dictionary), dataDictionary); } } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Exception value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } if (value.GetType() == typeof(Exception)) { // Exceptions are never written as references. This ensures that reference cycles in exceptions are not possible and is a security precaution. ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, typeof(ExceptionCodec)); SerializeException(ref writer, value); writer.WriteEndObject(); } else { writer.SerializeUnexpectedType(fieldIdDelta, expectedType, value); } } /// public bool IsSupportedType(Type type) { if (type == typeof(ExceptionCodec)) { return true; } if (type == typeof(AggregateException)) { return false; } if (typeof(Exception).IsAssignableFrom(type) && type.Namespace is { } ns) { if (_options.SupportedExceptionTypeFilter is { } filter && filter(type)) { return true; } foreach (var prefix in _options.SupportedNamespacePrefixes) { if (ns.StartsWith(prefix)) { return true; } } } return false; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, typeof(ExceptionCodec)); SerializeException(ref writer, (Exception)value); writer.WriteEndObject(); } /// public Exception ReadValue(ref Reader reader, Field field) { // In order to handle null values. if (field.WireType == WireType.Reference) { var referencedException = ReferenceCodec.ReadReference(ref reader, field); // We do not allow exceptions to participate in reference cycles because cycles involving InnerException are not allowed by .NET // Exceptions must never form cyclic graphs via their well-known properties/fields (eg, InnerException). if (referencedException is not null) { throw new ReferenceFieldNotSupportedException(field.FieldType); } return null; } Type valueType = field.FieldType; if (valueType is null || valueType == typeof(Exception)) { return DeserializeException(ref reader, field); } return reader.DeserializeUnexpectedType(ref field); } /// object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } return DeserializeException(ref reader, field); } public Exception DeserializeException(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); uint fieldId = 0; string typeName = null; string message = null; string stackTrace = null; Exception innerException = null; Dictionary data = null; int hResult = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: typeName = StringCodec.ReadValue(ref reader, header); break; case 1: message = StringCodec.ReadValue(ref reader, header); break; case 2: stackTrace = StringCodec.ReadValue(ref reader, header); break; case 3: innerException = ReadValue(ref reader, header); break; case 4: hResult = Int32Codec.ReadValue(ref reader, header); break; case 5: data = _dictionaryCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } Exception result; if (!_typeConverter.TryParse(typeName, out var type)) { result = new UnavailableExceptionFallbackException { ExceptionType = typeName }; } else if (typeof(Exception).IsAssignableFrom(type)) { try { if (type.GetConstructor(Array.Empty()) is not null) { result = (Exception)Activator.CreateInstance(type); } else { result = (Exception)RuntimeHelpers.GetUninitializedObject(type); } } catch (Exception constructorException) { result = new UnavailableExceptionFallbackException($"Failed to construct exception of type \"{type}\"", constructorException) { ExceptionType = typeName }; } } else { throw new NotSupportedException($"Type {type} is not supported"); } SetBaseProperties(result, message, stackTrace, innerException, hResult, data); return result; } /// public void DeepCopy(Exception input, Exception output, CopyContext context) { var info = GetObjectData(input); SetBaseProperties( output, // Get the message from object data in case the property is overridden as it is with AggregateException info.GetString("Message"), input.StackTrace, _exceptionCopier.DeepCopy(input.InnerException, context), input.HResult, _dictionaryCopier.DeepCopy(GetDataProperty(input), context)); } /// public void Serialize(ref Writer writer, object value) where TBufferWriter : IBufferWriter => Serialize(ref writer, (Exception)value); /// public void Deserialize(ref Reader reader, object value) => Deserialize(ref reader, (Exception)value); } /// /// Serializer for . /// [RegisterSerializer] internal sealed class AggregateExceptionCodec : GeneralizedReferenceTypeSurrogateCodec { private readonly ExceptionCodec _baseCodec; /// /// Initializes a new instance of the class. /// /// The base codec. /// The surrogate serializer. public AggregateExceptionCodec(ExceptionCodec baseCodec, IValueSerializer surrogateSerializer) : base(surrogateSerializer) { _baseCodec = baseCodec; } /// public override AggregateException ConvertFromSurrogate(ref AggregateExceptionSurrogate surrogate) { var result = new AggregateException(surrogate.InnerExceptions); var innerException = surrogate.InnerExceptions is { Count: > 0 } innerExceptions ? innerExceptions[0] : null; _baseCodec.SetBaseProperties(result, surrogate.Message, surrogate.StackTrace, innerException, surrogate.HResult, surrogate.Data); return result; } /// public override void ConvertToSurrogate(AggregateException value, ref AggregateExceptionSurrogate surrogate) { var info = _baseCodec.GetObjectData(value); surrogate.Message = info.GetString("Message"); surrogate.StackTrace = value.StackTrace; surrogate.HResult = value.HResult; var data = info.GetValue("Data", typeof(IDictionary)); if (data is { }) { surrogate.Data = _baseCodec.GetDataProperty(value); } surrogate.InnerExceptions = value.InnerExceptions; } } /// /// Surrogate type for . /// [GenerateSerializer] internal struct AggregateExceptionSurrogate { [Id(0)] public string Message; [Id(1)] public string StackTrace; [Id(2)] public Dictionary Data; [Id(3)] public int HResult; [Id(4)] public ReadOnlyCollection InnerExceptions; } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/ExceptionSerializationOptions.cs ================================================ using System; using System.Collections.Generic; namespace Orleans.Serialization { /// /// Options for exception serialization. /// public class ExceptionSerializationOptions { /// /// Gets the collection of supported namespace prefixes for the exception serializer. /// Any exception type which has a namespace with one of these prefixes will be serialized using the exception serializer. /// public HashSet SupportedNamespacePrefixes { get; } = new HashSet(StringComparer.Ordinal) { "Microsoft", "System", "Azure" }; /// /// Gets or sets the predicate used to enable serialization for an exception type. /// public Func SupportedExceptionTypeFilter { get; set; } = _ => false; } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/SerializationCallbacksFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; using System.Reflection.Emit; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { /// /// Creates delegates for calling methods marked with serialization attributes. /// internal sealed class SerializationCallbacksFactory { private readonly ConcurrentDictionary _cache = new(); private readonly Func _factory = t => CreateTypedCallbacks>(t, typeof(object)); /// /// Gets serialization callbacks for reference types. /// /// The type. /// Serialization callbacks. [SecurityCritical] public SerializationCallbacks> GetReferenceTypeCallbacks(Type type) => ( SerializationCallbacks>)_cache.GetOrAdd(type, _factory); /// /// Gets serialization callbacks for value types. /// /// The declaring type. /// The delegate type. /// The type. /// Serialization callbacks. [SecurityCritical] public SerializationCallbacks GetValueTypeCallbacks(Type type) where TOwner : struct where TDelegate : Delegate => GetValueTypeCallbacks(type, typeof(TOwner)); private SerializationCallbacks GetValueTypeCallbacks(Type type, Type owner) where TDelegate : Delegate => (SerializationCallbacks)_cache.GetOrAdd(type, CreateTypedCallbacks, owner); [SecurityCritical] private static SerializationCallbacks CreateTypedCallbacks(Type type, Type owner) where TDelegate : Delegate { var onDeserializing = default(TDelegate); var onDeserialized = default(TDelegate); var onSerializing = default(TDelegate); var onSerialized = default(TDelegate); foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var parameterInfos = method.GetParameters(); if (parameterInfos.Length != 1) { continue; } if (parameterInfos[0].ParameterType != typeof(StreamingContext)) { continue; } if (method.IsDefined(typeof(OnDeserializingAttribute), false)) { onDeserializing = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnDeserializedAttribute), false)) { onDeserialized = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnSerializingAttribute), false)) { onSerializing = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnSerializedAttribute), false)) { onSerialized = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } } return new SerializationCallbacks(onDeserializing, onDeserialized, onSerializing, onSerialized); } [SecurityCritical] private static DynamicMethod GetSerializationMethod(Type type, MethodInfo callbackMethod, Type owner) { Type[] callbackParameterTypes; if (owner.IsValueType) { callbackParameterTypes = new[] { typeof(object), owner.MakeByRefType(), typeof(StreamingContext) }; } else { callbackParameterTypes = new[] { typeof(object), typeof(object), typeof(StreamingContext) }; } var method = new DynamicMethod($"{callbackMethod.Name}_Trampoline", null, callbackParameterTypes, type, skipVisibility: true); var il = method.GetILGenerator(); // arg0 is unused for better delegate performance (avoids argument shuffling thunk) il.Emit(OpCodes.Ldarg_1); if (type != owner) { il.Emit(OpCodes.Castclass, type); } il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Callvirt, callbackMethod); il.Emit(OpCodes.Ret); return method; } /// /// Serialization callbacks. /// /// The delegate type for each callback. public sealed class SerializationCallbacks { /// /// Initializes a new instance of the class. /// /// The callback invoked during deserialization. /// The callback invoked once a value is deserialized. /// The callback invoked during serialization. /// The callback invoked once a value is serialized. public SerializationCallbacks( TDelegate onDeserializing, TDelegate onDeserialized, TDelegate onSerializing, TDelegate onSerialized) { OnDeserializing = onDeserializing; OnDeserialized = onDeserialized; OnSerializing = onSerializing; OnSerialized = onSerialized; } /// /// Gets the callback invoked while deserializing. /// public readonly TDelegate OnDeserializing; /// /// Gets the callback invoked once a value has been deserialized. /// public readonly TDelegate OnDeserialized; /// /// Gets the callback invoked during serialization. /// /// The on serializing. public readonly TDelegate OnSerializing; /// /// Gets the callback invoked once a value has been serialized. /// public readonly TDelegate OnSerialized; } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/SerializationConstructorFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; using System.Reflection.Emit; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { /// /// Creates delegates for calling ISerializable-conformant constructors. /// internal sealed class SerializationConstructorFactory { private static readonly Type[] SerializationConstructorParameterTypes = { typeof(SerializationInfo), typeof(StreamingContext) }; private readonly Func _createConstructorDelegate = t => GetSerializationConstructorInvoker(t, typeof(object), typeof(Action)); private readonly ConcurrentDictionary _constructors = new(); /// /// Determines whether the provided type has a serialization constructor. /// /// The type. /// if the provided type has a serialization constructor; otherwise, . [SecurityCritical] public static bool HasSerializationConstructor(Type type) => GetSerializationConstructor(type) != null; [SecurityCritical] public Action GetSerializationConstructorDelegate(Type type) => (Action)_constructors.GetOrAdd(type, _createConstructorDelegate); [SecurityCritical] public TConstructor GetSerializationConstructorDelegate() where TConstructor : Delegate => (TConstructor)GetSerializationConstructorDelegate(typeof(TOwner), typeof(TConstructor)); private object GetSerializationConstructorDelegate(Type owner, Type delegateType) => _constructors.GetOrAdd(owner, (t, d) => GetSerializationConstructorInvoker(t, t, d), delegateType); [SecurityCritical] private static ConstructorInfo GetSerializationConstructor(Type type) => type.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, SerializationConstructorParameterTypes, null); [SecurityCritical] private static Delegate GetSerializationConstructorInvoker(Type type, Type owner, Type delegateType) { var constructor = GetSerializationConstructor(type) ?? (typeof(Exception).IsAssignableFrom(type) ? GetSerializationConstructor(typeof(Exception)) : null); if (constructor is null) { throw new SerializationException($"{nameof(ISerializable)} constructor not found on type {type}."); } Type[] parameterTypes; if (owner.IsValueType) { parameterTypes = new[] { typeof(object), owner.MakeByRefType(), typeof(SerializationInfo), typeof(StreamingContext) }; } else { parameterTypes = new[] { typeof(object), typeof(object), typeof(SerializationInfo), typeof(StreamingContext) }; } var method = new DynamicMethod($"{type}_serialization_ctor", null, parameterTypes, type, skipVisibility: true); var il = method.GetILGenerator(); // arg0 is unused for better delegate performance (avoids argument shuffling thunk) il.Emit(OpCodes.Ldarg_1); if (type != owner) { il.Emit(OpCodes.Castclass, type); } il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ldarg_3); il.Emit(OpCodes.Call, constructor); il.Emit(OpCodes.Ret); return method.CreateDelegate(delegateType); } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/SerializationConstructorNotFoundException.cs ================================================ using System; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { /// /// Thrown when a type has no serialization constructor. /// [Serializable] public class SerializationConstructorNotFoundException : Exception { /// /// Initializes a new instance of the class. /// /// The type. [SecurityCritical] public SerializationConstructorNotFoundException(Type type) : base( (string)$"Could not find a suitable serialization constructor on type {type.FullName}") { } /// /// Initializes a new instance of the class. /// /// The serialization information. /// The context. [SecurityCritical] #if NET8_0_OR_GREATER [Obsolete] #endif protected SerializationConstructorNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/SerializationEntryCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.Security; namespace Orleans.Serialization { internal sealed class SerializationEntryCodec : IFieldCodec { [SecurityCritical] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, SerializationEntrySurrogate value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(SerializationEntrySurrogate), WireType.TagDelimited); StringCodec.WriteField(ref writer, 0, value.Name); ObjectCodec.WriteField(ref writer, 1, value.Value); if (value.ObjectType is { } objectType) { TypeSerializerCodec.WriteField(ref writer, 1, objectType); } writer.WriteEndObject(); } [SecurityCritical] public SerializationEntrySurrogate ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); var result = new SerializationEntrySurrogate(); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: result.Name = StringCodec.ReadValue(ref reader, header); break; case 1: result.Value = ObjectCodec.ReadValue(ref reader, header); break; case 2: result.ObjectType = TypeSerializerCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return result; } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/SerializationEntrySurrogate.cs ================================================ using System; namespace Orleans.Serialization { [GenerateSerializer] internal struct SerializationEntrySurrogate { [Id(0)] public string Name; [Id(1)] public object Value; [Id(2)] public Type ObjectType; } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/UnavailableExceptionFallbackException.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.Serialization; namespace Orleans.Serialization { /// /// Represents an exception which has a type which is unavailable during deserialization. /// [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] public sealed class UnavailableExceptionFallbackException : Exception { /// public UnavailableExceptionFallbackException() { } /// #if NET8_0_OR_GREATER [Obsolete] #endif public UnavailableExceptionFallbackException(SerializationInfo info, StreamingContext context) : base(info, context) { foreach (var pair in info) { Properties[pair.Name] = pair.Value; } } /// public UnavailableExceptionFallbackException(string message, Exception innerException) : base(message, innerException) { } /// /// Gets the serialized properties of the exception. /// public Dictionary Properties { get; } = new(); /// /// Gets the exception type name. /// public string ExceptionType { get; internal set; } /// public override string ToString() => string.IsNullOrWhiteSpace(ExceptionType) ? $"Unknown exception: {base.ToString()}" : $"Unknown exception of type {ExceptionType}: {base.ToString()}"; private string GetDebuggerDisplay() => ToString(); } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/ValueTypeSerializer.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using System; using System.Buffers; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { internal abstract class ValueTypeSerializer { public abstract void WriteValue(ref Writer writer, object value) where TBufferWriter : IBufferWriter; public abstract object ReadValue(ref Reader reader, Type type); } /// /// Serializer for ISerializable value types. /// /// The type which this serializer can serialize. internal class ValueTypeSerializer : ValueTypeSerializer where T : struct { public delegate void ValueConstructor(ref T value, SerializationInfo info, StreamingContext context); public delegate void SerializationCallback(ref T value, StreamingContext context); private static readonly Type Type = typeof(T); private readonly ValueConstructor _constructor; private readonly SerializationCallbacksFactory.SerializationCallbacks _callbacks; #pragma warning disable SYSLIB0050 // Type or member is obsolete private readonly IFormatterConverter _formatterConverter; #pragma warning restore SYSLIB0050 // Type or member is obsolete private readonly StreamingContext _streamingContext; private readonly SerializationEntryCodec _entrySerializer; [SecurityCritical] public ValueTypeSerializer( ValueConstructor constructor, SerializationCallbacksFactory.SerializationCallbacks callbacks, SerializationEntryCodec entrySerializer, StreamingContext streamingContext, #pragma warning disable SYSLIB0050 // Type or member is obsolete IFormatterConverter formatterConverter) #pragma warning restore SYSLIB0050 // Type or member is obsolete { _constructor = constructor; _callbacks = callbacks; _entrySerializer = entrySerializer; _streamingContext = streamingContext; _formatterConverter = formatterConverter; } [SecurityCritical] public override void WriteValue(ref Writer writer, object value) { var item = (T)value; _callbacks.OnSerializing?.Invoke(ref item, _streamingContext); #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(Type, _formatterConverter); #pragma warning restore SYSLIB0050 // Type or member is obsolete #pragma warning disable SYSLIB0050 // Type or member is obsolete ((ISerializable)value).GetObjectData(info, _streamingContext); #pragma warning restore SYSLIB0050 // Type or member is obsolete TypeSerializerCodec.WriteField(ref writer, 0, info.ObjectType); var first = true; foreach (var field in info) { var surrogate = new SerializationEntrySurrogate { Name = field.Name, Value = field.Value, ObjectType = field.ObjectType }; _entrySerializer.WriteField(ref writer, first ? 1 : (uint)0, typeof(SerializationEntrySurrogate), surrogate); if (first) { first = false; } } _callbacks.OnSerialized?.Invoke(ref item, _streamingContext); } [SecurityCritical] public override object ReadValue(ref Reader reader, Type type) { #pragma warning disable SYSLIB0050 // Type or member is obsolete var info = new SerializationInfo(Type, _formatterConverter); #pragma warning restore SYSLIB0050 // Type or member is obsolete T result = default; _callbacks.OnDeserializing?.Invoke(ref result, _streamingContext); uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; if (fieldId == 1) { var entry = _entrySerializer.ReadValue(ref reader, header); if (entry.ObjectType is { } entryType) { info.AddValue(entry.Name, entry.Value, entryType); } else { info.AddValue(entry.Name, entry.Value); } } else { reader.ConsumeUnknownField(header); } } _constructor(ref result, info, _streamingContext); _callbacks.OnDeserialized?.Invoke(ref result, _streamingContext); if (result is IDeserializationCallback callback) { callback.OnDeserialization(_streamingContext.Context); } return result; } } } ================================================ FILE: src/Orleans.Serialization/ISerializableSerializer/ValueTypeSerializerFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; using System.Runtime.Serialization; using System.Security; namespace Orleans.Serialization { internal class ValueTypeSerializerFactory { private readonly SerializationConstructorFactory _constructorFactory; private readonly SerializationCallbacksFactory _callbacksFactory; private readonly SerializationEntryCodec _entrySerializer; private readonly StreamingContext _streamingContext; #pragma warning disable SYSLIB0050 // Type or member is obsolete private readonly IFormatterConverter _formatterConverter; #pragma warning restore SYSLIB0050 // Type or member is obsolete private readonly Func _createSerializerDelegate; private readonly ConcurrentDictionary _serializers = new(); private readonly MethodInfo _createTypedSerializerMethodInfo = typeof(ValueTypeSerializerFactory).GetMethod( nameof(CreateTypedSerializer), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); [SecurityCritical] public ValueTypeSerializerFactory( SerializationEntryCodec entrySerializer, SerializationConstructorFactory constructorFactory, SerializationCallbacksFactory callbacksFactory, #pragma warning disable SYSLIB0050 // Type or member is obsolete IFormatterConverter formatterConverter, #pragma warning restore SYSLIB0050 // Type or member is obsolete StreamingContext streamingContext) { _constructorFactory = constructorFactory; _callbacksFactory = callbacksFactory; _entrySerializer = entrySerializer; _streamingContext = streamingContext; _formatterConverter = formatterConverter; _createSerializerDelegate = type => (ValueTypeSerializer)_createTypedSerializerMethodInfo.MakeGenericMethod(type).Invoke(this, null); } [SecurityCritical] public ValueTypeSerializer GetSerializer(Type type) => _serializers.GetOrAdd(type, _createSerializerDelegate); [SecurityCritical] private ValueTypeSerializer CreateTypedSerializer() where T : struct { var constructor = _constructorFactory.GetSerializationConstructorDelegate.ValueConstructor>(); var callbacks = _callbacksFactory.GetValueTypeCallbacks.SerializationCallback>(typeof(T)); var serializer = new ValueTypeSerializer(constructor, callbacks, _entrySerializer, _streamingContext, _formatterConverter); return serializer; } } } ================================================ FILE: src/Orleans.Serialization/Invocation/IInvokable.cs ================================================ #nullable enable using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Orleans.Serialization.Invocation { /// /// Represents an object which can be invoked asynchronously. /// public interface IInvokable : IDisposable { /// /// Gets the invocation target. /// /// The invocation target. object? GetTarget(); /// /// Sets the invocation target from an instance of . /// /// The invocation target. void SetTarget(ITargetHolder holder); /// /// Invoke the object. /// ValueTask Invoke(); /// /// Gets the number of arguments. /// int GetArgumentCount(); /// /// Gets the argument at the specified index. /// /// The argument index. /// The argument at the specified index. object? GetArgument(int index); /// /// Sets the argument at the specified index. /// /// The argument index. /// The argument value void SetArgument(int index, object value); /// /// Gets the method name. /// string GetMethodName(); /// /// Gets the full interface name. /// string GetInterfaceName(); /// /// Gets the activity name, which refers to both the interface name and method name. /// string GetActivityName(); /// /// Gets the method info object, which may be . /// MethodInfo GetMethod(); /// /// Gets the interface type. /// Type GetInterfaceType(); /// /// Gets the default response timeout. /// TimeSpan? GetDefaultResponseTimeout() => default; /// /// Gets the cancellation token for this request. If the request does not accept a cancellation token, this will be . /// /// The cancellation token for this request. CancellationToken GetCancellationToken() => default; /// /// Tries to cancel the invocation. /// /// This is only valid after has been called, and only has an effect for requests which support cancellation. /// if cancellation was requested, otherwise . bool TryCancel() => false; /// /// if this request supports cancellation; otherwise. /// bool IsCancellable => false; } } ================================================ FILE: src/Orleans.Serialization/Invocation/IResponseCompletionSource.cs ================================================ namespace Orleans.Serialization.Invocation { /// /// Represents a fulfillable promise for a response to a request. /// public interface IResponseCompletionSource { /// /// Sets the result. /// /// The result value. void Complete(Response value); /// /// Sets the result to the default value. /// void Complete(); } } ================================================ FILE: src/Orleans.Serialization/Invocation/ITargetHolder.cs ================================================ #nullable enable using System; namespace Orleans.Serialization.Invocation; /// /// Represents an object which holds an invocation target as well as target extensions. /// public interface ITargetHolder { /// /// Gets the target instance. /// /// The target. object? GetTarget(); /// /// Gets the component with the specified type. /// /// The component type. /// The component with the specified type. object? GetComponent(Type componentType); } ================================================ FILE: src/Orleans.Serialization/Invocation/Pools/ConcurrentObjectPool.cs ================================================ using System.Collections.Generic; using System.Threading; using Microsoft.Extensions.ObjectPool; namespace Orleans.Serialization.Invocation { internal sealed class ConcurrentObjectPool : ConcurrentObjectPool> where T : class, new() { public ConcurrentObjectPool() : base(new()) { } } internal class ConcurrentObjectPool : ObjectPool where T : class where TPoolPolicy : IPooledObjectPolicy { private readonly ThreadLocal> _objects = new(() => new()); private readonly TPoolPolicy _policy; public ConcurrentObjectPool(TPoolPolicy policy) => _policy = policy; public int MaxPoolSize { get; set; } = int.MaxValue; public override T Get() { var stack = _objects.Value; if (stack.TryPop(out var result)) { return result; } return _policy.Create(); } public override void Return(T obj) { if (_policy.Return(obj)) { var stack = _objects.Value; if (stack.Count < MaxPoolSize) { stack.Push(obj); } } } } } ================================================ FILE: src/Orleans.Serialization/Invocation/Pools/DefaultConcurrentObjectPoolPolicy.cs ================================================ using Microsoft.Extensions.ObjectPool; namespace Orleans.Serialization.Invocation { internal readonly struct DefaultConcurrentObjectPoolPolicy : IPooledObjectPolicy where T : class, new() { public T Create() => new(); public bool Return(T obj) => true; } } ================================================ FILE: src/Orleans.Serialization/Invocation/Pools/InvokablePool.cs ================================================ namespace Orleans.Serialization.Invocation { /// /// Object pool for implementations. /// public static class InvokablePool { /// /// Gets a value from the pool. /// /// The type of the value. /// A value from the pool. public static T Get() where T : class, IInvokable, new() => TypedPool.Pool.Get(); /// /// Returns a value to the pool. /// /// The type of the value. /// The value to return. public static void Return(T obj) where T : class, IInvokable, new() => TypedPool.Pool.Return(obj); private static class TypedPool where T : class, IInvokable, new() { public static readonly ConcurrentObjectPool Pool = new(); } } } ================================================ FILE: src/Orleans.Serialization/Invocation/Pools/ResponseCompletionSourcePool.cs ================================================ namespace Orleans.Serialization.Invocation { /// /// Object pool for and . /// public static class ResponseCompletionSourcePool { internal static readonly ConcurrentObjectPool> UntypedPool = new(new()); /// /// Gets a value from the pool. /// /// The underlying result type. /// A value from the pool. public static ResponseCompletionSource Get() => TypedPool.Pool.Get(); /// /// Returns a value to the pool. /// /// The underlying result type. /// The value to return to the pool public static void Return(ResponseCompletionSource obj) => TypedPool.Pool.Return(obj); /// /// Gets a value from the pool. /// /// A value from the pool. public static ResponseCompletionSource Get() => UntypedPool.Get(); /// /// Returns a value to the pool. /// /// The value to return to the pool public static void Return(ResponseCompletionSource obj) => UntypedPool.Return(obj); private static class TypedPool { public static readonly ConcurrentObjectPool, DefaultConcurrentObjectPoolPolicy>> Pool = new(new()); } } } ================================================ FILE: src/Orleans.Serialization/Invocation/Pools/ResponsePool.cs ================================================ namespace Orleans.Serialization.Invocation { /// /// Object pool for values. /// public static class ResponsePool { /// /// Gets a value from the pool. /// /// The underlying response type. /// A value from the pool. public static Response Get() => TypedPool.Pool.Get(); /// /// Returns a value to the pool. /// /// The underlying response type. /// The value to return to the pool. public static void Return(Response obj) => TypedPool.Pool.Return(obj); private static class TypedPool { public static readonly ConcurrentObjectPool> Pool = new(); } } } ================================================ FILE: src/Orleans.Serialization/Invocation/Response.cs ================================================ #nullable enable using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using Orleans.Serialization.Activators; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Invocation { /// /// Represents the result of a method invocation. /// [SerializerTransparent] public abstract class Response : IDisposable { /// /// Creates a new response representing an exception. /// /// The exception. /// A new response. public static Response FromException(Exception exception) => new ExceptionResponse { Exception = exception }; /// /// Creates a new response object which has been fulfilled with the provided value. /// /// The underlying result type. /// The value. /// A new response. public static Response FromResult(TResult value) { var result = ResponsePool.Get(); result.TypedResult = value; return result; } /// /// Gets a completed response. /// public static Response Completed => CompletedResponse.Instance; /// public abstract object? Result { get; set; } public virtual Type? GetSimpleResultType() => null; /// public abstract Exception? Exception { get; set; } /// public abstract T GetResult(); /// public abstract void Dispose(); /// public override string ToString() => Exception is { } ex ? ex.ToString() : Result?.ToString() ?? "[null]"; } /// /// Represents a completed . /// [GenerateSerializer, Immutable, UseActivator, SuppressReferenceTracking] public sealed class CompletedResponse : Response { /// /// Gets the singleton instance of this class. /// public static CompletedResponse Instance { get; } = new CompletedResponse(); /// public override object? Result { get => null; set => throw new InvalidOperationException($"Type {nameof(CompletedResponse)} is read-only"); } /// public override Exception? Exception { get => null; set => throw new InvalidOperationException($"Type {nameof(CompletedResponse)} is read-only"); } /// public override T GetResult() => default!; /// public override void Dispose() { } /// public override string ToString() => "[Completed]"; } /// /// Activator for . /// [RegisterActivator] internal sealed class CompletedResponseActivator : IActivator { /// public CompletedResponse Create() => CompletedResponse.Instance; } /// /// A which represents an exception, a broken promise. /// [GenerateSerializer, Immutable] public sealed class ExceptionResponse : Response { /// public override object? Result { get { ExceptionDispatchInfo.Capture(Exception!).Throw(); return null; } set => throw new InvalidOperationException($"Cannot set result property on response of type {nameof(ExceptionResponse)}"); } /// [Id(0)] public override Exception? Exception { get; set; } /// public override T GetResult() { ExceptionDispatchInfo.Capture(Exception!).Throw(); return default; } /// public override void Dispose() { } /// public override string ToString() => Exception?.ToString() ?? "[null]"; } /// /// A which represents a typed value. /// /// The underlying result type. [UseActivator, SuppressReferenceTracking] public sealed class Response : Response { [Id(0)] private TResult? _result; public TResult? TypedResult { get => _result; set => _result = value; } public override Exception? Exception { get => null; set => throw new InvalidOperationException($"Cannot set {nameof(Exception)} property for type {nameof(Response)}"); } public override object? Result { get => _result; set => _result = (TResult?)value; } public override Type GetSimpleResultType() => typeof(TResult); public override T GetResult() { if (typeof(TResult).IsValueType && typeof(T).IsValueType && typeof(T) == typeof(TResult)) return Unsafe.As(ref _result!); return (T)(object)_result!; } public override void Dispose() { _result = default; ResponsePool.Return(this); } public override string ToString() => _result?.ToString() ?? "[null]"; } /// /// Supports raw serialization of values. /// public abstract class ResponseCodec { public abstract void WriteRaw(ref Writer writer, object value) where TBufferWriter : IBufferWriter; public abstract object ReadRaw(ref Reader reader, scoped ref Field field); } [RegisterSerializer] internal sealed class PooledResponseCodec : ResponseCodec, IFieldCodec> { private readonly Type _codecFieldType = typeof(Response); private readonly Type _resultType = typeof(TResult); private readonly IFieldCodec _codec; public PooledResponseCodec(ICodecProvider codecProvider) => _codec = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Response value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, _codecFieldType); if (value.TypedResult is not null) _codec.WriteField(ref writer, 0, _resultType, value.TypedResult); writer.WriteEndObject(); } public Response ReadValue(ref Reader reader, Field field) { if (field.IsReference) return ReferenceCodec.ReadReference, TInput>(ref reader, field); field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); var result = ResponsePool.Get(); reader.ReadFieldHeader(ref field); if (!field.IsEndBaseOrEndObject) { result.TypedResult = _codec.ReadValue(ref reader, field); reader.ReadFieldHeader(ref field); reader.ConsumeEndBaseOrEndObject(ref field); } return result; } public override void WriteRaw(ref Writer writer, object value) { writer.WriteStartObject(0, null, _resultType); var holder = (Response)value; if (holder.TypedResult is not null) _codec.WriteField(ref writer, 0, _resultType, holder.TypedResult); writer.WriteEndObject(); } public override object ReadRaw(ref Reader reader, scoped ref Field field) { field.EnsureWireTypeTagDelimited(); var result = ResponsePool.Get(); reader.ReadFieldHeader(ref field); if (!field.IsEndBaseOrEndObject) { result.TypedResult = _codec.ReadValue(ref reader, field); reader.ReadFieldHeader(ref field); reader.ConsumeEndBaseOrEndObject(ref field); } return result; } } [RegisterCopier] internal sealed class PooledResponseCopier : IDeepCopier> { private readonly IDeepCopier _copier; public PooledResponseCopier(ICodecProvider codecProvider) => _copier = OrleansGeneratedCodeHelper.GetService>(this, codecProvider); public Response DeepCopy(Response? input, CopyContext context) { if (input is null) return null!; var result = ResponsePool.Get(); result.TypedResult = _copier.DeepCopy(input.TypedResult!, context); return result; } } [RegisterActivator] internal sealed class PooledResponseActivator : IActivator> { public Response Create() => ResponsePool.Get(); } public static class ResponseExtensions { public static void ThrowIfExceptionResponse(this Response response) { if (response.Exception is { } exception) { ExceptionDispatchInfo.Capture(exception).Throw(); } } } } ================================================ FILE: src/Orleans.Serialization/Invocation/ResponseCompletionSource.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Threading.Tasks.Sources; namespace Orleans.Serialization.Invocation { /// /// A fulfillable promise. /// public sealed class ResponseCompletionSource : IResponseCompletionSource, IValueTaskSource, IValueTaskSource { private ManualResetValueTaskSourceCore _core = new() { RunContinuationsAsynchronously = true }; /// /// Returns this instance as a . /// /// This instance, as a . public ValueTask AsValueTask() => new(this, _core.Version); /// /// Returns this instance as a . /// /// This instance, as a . public ValueTask AsVoidValueTask() => new(this, _core.Version); /// public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); /// public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); /// /// Resets this instance. /// public void Reset() { _core.Reset(); ResponseCompletionSourcePool.Return(this); } /// /// Completes this instance with an exception. /// /// The exception. public void SetException(Exception exception) => _core.SetException(exception); /// /// Completes this instance with a result. /// /// The result. public void SetResult(Response result) { if (result.Exception is not { } exception) { _core.SetResult(result); } else { _core.SetException(exception); } } /// /// Completes this instance with a result. /// /// The result value. public void Complete(Response value) => SetResult(value); /// /// Completes this instance with the default result. /// public void Complete() => SetResult(Response.Completed); /// public Response GetResult(short token) { bool isValid = token == _core.Version; try { return _core.GetResult(token); } finally { if (isValid) { Reset(); } } } /// void IValueTaskSource.GetResult(short token) { bool isValid = token == _core.Version; try { _ = _core.GetResult(token); } finally { if (isValid) { Reset(); } } } } /// /// A fulfillable promise. /// /// The underlying result type. public sealed class ResponseCompletionSource : IResponseCompletionSource, IValueTaskSource, IValueTaskSource { private ManualResetValueTaskSourceCore _core = new() { RunContinuationsAsynchronously = true }; /// /// Returns this instance as a . /// /// This instance, as a . public ValueTask AsValueTask() => new(this, _core.Version); /// /// Returns this instance as a . /// /// This instance, as a . public ValueTask AsVoidValueTask() => new(this, _core.Version); /// public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); /// public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); /// /// Resets this instance. /// public void Reset() { _core.Reset(); ResponseCompletionSourcePool.Return(this); } /// /// Completes this instance with an exception. /// /// The exception. public void SetException(Exception exception) => _core.SetException(exception); /// /// Completes this instance with a result. /// /// The result. public void SetResult(TResult result) => _core.SetResult(result); /// public void Complete(Response value) { if (value is Response typed) { Complete(typed); } else if (value.Exception is { } exception) { SetException(exception); } else { var result = value.Result; if (result is null) { SetResult(default); } else if (result is TResult typedResult) { SetResult(typedResult); } else { SetInvalidCastException(result); } } } [MethodImpl(MethodImplOptions.NoInlining)] private void SetInvalidCastException(object result) { var exception = new InvalidCastException($"Cannot cast object of type {result.GetType()} to {typeof(TResult)}"); #if NET5_0_OR_GREATER System.Runtime.ExceptionServices.ExceptionDispatchInfo.SetCurrentStackTrace(exception); SetException(exception); #else try { throw exception; } catch (Exception ex) { SetException(ex); } #endif } /// /// Completes this instance with a result. /// /// The result value. public void Complete(Response value) { if (value.Exception is { } exception) { SetException(exception); } else { SetResult(value.TypedResult); } } /// public void Complete() => SetResult(default); /// public TResult GetResult(short token) { bool isValid = token == _core.Version; try { return _core.GetResult(token); } finally { if (isValid) { Reset(); } } } /// void IValueTaskSource.GetResult(short token) { bool isValid = token == _core.Version; try { _ = _core.GetResult(token); } finally { if (isValid) { Reset(); } } } } } ================================================ FILE: src/Orleans.Serialization/Invocation/TargetHolderExtensions.cs ================================================ #nullable enable using Orleans.Serialization.Invocation; namespace Orleans.Runtime; /// /// Extension methods for . /// public static class TargetHolderExtensions { /// /// Gets the component with the specified type. /// /// The component type. /// The target holder from which to retrieve the component. /// The component with the specified type. public static TComponent? GetComponent(this ITargetHolder targetHolder) where TComponent : class => targetHolder.GetComponent(typeof(TComponent)) as TComponent; } ================================================ FILE: src/Orleans.Serialization/Orleans.Serialization.csproj ================================================  Microsoft.Orleans.Serialization Fast, flexible, and version-tolerant serializer for .NET $(DefaultTargetFrameworks);netstandard2.1 true true false README.md ================================================ FILE: src/Orleans.Serialization/Properties/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } ================================================ FILE: src/Orleans.Serialization/README.md ================================================ # Microsoft Orleans Serialization ## Introduction Microsoft Orleans Serialization is a fast, flexible, and version-tolerant serializer for .NET. It provides the core serialization capabilities for Orleans, enabling efficient serialization and deserialization of data across the network and for storage. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization ``` This package is automatically included when you reference the Orleans SDK or the Orleans client/server metapackages. ## Example ```csharp // Creating a serializer var services = new ServiceCollection(); services.AddSerializer(); var serviceProvider = services.BuildServiceProvider(); var serializer = serviceProvider.GetRequiredService(); // Serializing an object var bytes = serializer.SerializeToArray(myObject); // Deserializing an object var deserializedObject = serializer.Deserialize(bytes); ``` ## Supporting your own Types To make your types serializable in Orleans, mark them with the `[GenerateSerializer]` attribute and mark each field/property which should be serialized with the `[Id(int)]` attribute: ```csharp [GenerateSerializer] public class MyClass { [Id(0)] public string Name { get; set; } [Id(1)] public int Value { get; set; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Serialization in Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [Orleans type serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization-attributes) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization/Serializer.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.Session; using System; using System.Buffers; using System.IO; namespace Orleans.Serialization { /// /// Serializes and deserializes values. /// public sealed class Serializer { private readonly SerializerSessionPool _sessionPool; /// /// Initializes a new instance of the class. /// /// The session pool. public Serializer(SerializerSessionPool sessionPool) => _sessionPool = sessionPool; /// /// Gets the serializer session pool. /// public SerializerSessionPool SessionPool => _sessionPool; /// /// Returns a serializer which is specialized to the provided type parameter. /// /// The underlying type for the returned serializer. public Serializer GetSerializer() => new(_sessionPool); /// /// Returns if the provided type, , can be serialized, and otherwise. /// public bool CanSerialize() => _sessionPool.CodecProvider.TryGetCodec(typeof(T)) is { }; /// /// Returns if the provided type, , can be serialized, and otherwise. /// public bool CanSerialize(Type type) => _sessionPool.CodecProvider.TryGetCodec(type) is { }; /// /// Serializes the provided into a new array. /// /// The expected type of . /// The value to serialize. /// A byte array containing the serialized value. public byte[] SerializeToArray(T value) { using var session = _sessionPool.GetSession(); var writer = Writer.CreatePooled(session); try { var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); return writer.Output.ToArray(); } finally { writer.Dispose(); } } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(T value, ref Memory destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(T value, ref Memory destination, SerializerSession session) { var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(T value, Stream destination, int sizeHint = 0) { using var session = _sessionPool.GetSession(); if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(T value, Stream destination, SerializerSession session, int sizeHint = 0) { if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The expected type of . /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(T value, TBufferWriter destination) where TBufferWriter : IBufferWriter { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The expected type of . /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. public void Serialize(T value, TBufferWriter destination, SerializerSession session) where TBufferWriter : IBufferWriter { var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The expected type of . /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(T value, ref Writer destination) where TBufferWriter : IBufferWriter { var codec = destination.Session.CodecProvider.GetCodec(); codec.WriteField(ref destination, 0, typeof(T), value); destination.Commit(); } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(T value, ref Span destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(T value, ref Span destination, SerializerSession session) { var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The length of the serialized data. public int Serialize(T value, byte[] destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The length of the serialized data. public int Serialize(T value, ArraySegment destination) { var destinationSpan = destination.AsSpan(); Serialize(value, ref destinationSpan); return destinationSpan.Length; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The length of the serialized data. public int Serialize(T value, ArraySegment destination, SerializerSession session) { var destinationSpan = destination.AsSpan(); Serialize(value, ref destinationSpan, session); return destinationSpan.Length; } /// /// Serializes the provided into . /// /// The expected type of . /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The length of the serialized data. public int Serialize(T value, byte[] destination, SerializerSession session) { var writer = Writer.Create(destination, session); var codec = session.CodecProvider.GetCodec(); codec.WriteField(ref writer, 0, typeof(T), value); writer.Commit(); return writer.Position; } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(Stream source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(Stream source, SerializerSession session) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlySequence source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlySequence source, SerializerSession session) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(PooledBuffer.BufferSlice source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(PooledBuffer.BufferSlice source, SerializerSession session) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlySpan source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlySpan source, SerializerSession session) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(byte[] source) => Deserialize(source.AsSpan()); /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(byte[] source, SerializerSession session) => Deserialize(source.AsSpan(), session); /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlyMemory source) => Deserialize(source.Span); /// /// Deserialize a value of type from . /// /// The serialized type. /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlyMemory source, SerializerSession session) => Deserialize(source.Span, session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(ArraySegment source) => Deserialize(source.AsSpan()); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ArraySegment source, SerializerSession session) => Deserialize(source.AsSpan(), session); /// /// Deserialize a value of type from . /// /// The serialized type. /// The reader input type. /// The source buffer. /// The deserialized value. public T Deserialize(ref Reader source) { var codec = source.Session.CodecProvider.GetCodec(); var field = source.ReadFieldHeader(); return codec.ReadValue(ref source, field); } } /// /// Serializes and deserializes values. /// /// The type of value which this instance serializes and deserializes. public sealed class Serializer { private readonly IFieldCodec _codec; private readonly SerializerSessionPool _sessionPool; private readonly Type _expectedType = typeof(T); /// /// Initializes a new instance of the class. /// /// The session pool. public Serializer(SerializerSessionPool sessionPool) : this(OrleansGeneratedCodeHelper.UnwrapService(null, sessionPool.CodecProvider.GetCodec()), sessionPool) { } /// /// Initializes a new instance of the class. /// /// The codec. /// The session pool. public Serializer(IFieldCodec codec, SerializerSessionPool sessionPool) { _codec = codec; _sessionPool = sessionPool; } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(T value, ref Writer destination) where TBufferWriter : IBufferWriter { _codec.WriteField(ref destination, 0, _expectedType, value); destination.Commit(); } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(T value, TBufferWriter destination) where TBufferWriter : IBufferWriter { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. public void Serialize(T value, TBufferWriter destination, SerializerSession session) where TBufferWriter : IBufferWriter { var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into a new array. /// /// The value to serialize. /// A byte array containing the serialized value. public byte[] SerializeToArray(T value) { using var session = _sessionPool.GetSession(); var writer = Writer.CreatePooled(session); try { _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); return writer.Output.ToArray(); } finally { writer.Dispose(); } } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(T value, ref Memory destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(T value, ref Memory destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(T value, ref Span destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(T value, ref Span destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The length of the serialized data. public int Serialize(T value, byte[] destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The length of the serialized data. public int Serialize(T value, byte[] destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(T value, Stream destination, int sizeHint = 0) { using var session = _sessionPool.GetSession(); if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(T value, Stream destination, SerializerSession session, int sizeHint = 0) { if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { _codec.WriteField(ref writer, 0, _expectedType, value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Deserialize a value of type from . /// /// The reader input type. /// The source buffer. /// The deserialized value. public T Deserialize(ref Reader source) { var field = source.ReadFieldHeader(); return _codec.ReadValue(ref source, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(Stream source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(Stream source, SerializerSession session) { var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlySequence source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(ArraySegment source) => Deserialize(source.AsSpan()); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlySequence source, SerializerSession session) { var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(PooledBuffer.BufferSlice source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(PooledBuffer.BufferSlice source, SerializerSession session) { var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlySpan source) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlySpan source, SerializerSession session) { var reader = Reader.Create(source, session); var field = reader.ReadFieldHeader(); return _codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(byte[] source) => Deserialize(source.AsSpan()); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(byte[] source, SerializerSession session) => Deserialize(source.AsSpan(), session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. public T Deserialize(ReadOnlyMemory source) => Deserialize(source.Span); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ReadOnlyMemory source, SerializerSession session) => Deserialize(source.Span, session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The deserialized value. public T Deserialize(ArraySegment source, SerializerSession session) => Deserialize(source.AsSpan(), session); } /// /// Serializes and deserializes value types. /// /// The type which this instance operates on. public sealed class ValueSerializer where T : struct { private readonly IValueSerializer _codec; private readonly SerializerSessionPool _sessionPool; /// /// Initializes a new instance of the class. /// /// The codec provider. /// The session pool. public ValueSerializer(IValueSerializerProvider codecProvider, SerializerSessionPool sessionPool) { _sessionPool = sessionPool; _codec = OrleansGeneratedCodeHelper.UnwrapService(null, codecProvider.GetValueSerializer()); } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(scoped ref T value, ref Writer destination) where TBufferWriter : IBufferWriter { _codec.Serialize(ref destination, ref value); destination.WriteEndObject(); destination.Commit(); } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. public void Serialize(scoped ref T value, TBufferWriter destination) where TBufferWriter : IBufferWriter { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. public void Serialize(scoped ref T value, TBufferWriter destination, SerializerSession session) where TBufferWriter : IBufferWriter { var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into a new array. /// /// The value to serialize. /// A byte array containing the serialized value. public byte[] SerializeToArray(scoped ref T value) { using var session = _sessionPool.GetSession(); var writer = Writer.CreatePooled(session); try { _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); return writer.Output.ToArray(); } finally { writer.Dispose(); } } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(scoped ref T value, ArraySegment destination) { var destinationSpan = destination.AsSpan(); Serialize(ref value, ref destinationSpan); } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(scoped ref T value, ref Memory destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(scoped ref T value, ref Memory destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// This method slices the to the serialized data length. public void Serialize(scoped ref T value, ref Span destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// This method slices the to the serialized data length. public void Serialize(scoped ref T value, ref Span destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The length of the serialized data. public int Serialize(scoped ref T value, byte[] destination) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The length of the serialized data. public int Serialize(scoped ref T value, byte[] destination, SerializerSession session) { var writer = Writer.Create(destination, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(scoped ref T value, Stream destination, int sizeHint = 0) { using var session = _sessionPool.GetSession(); if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(scoped ref T value, Stream destination, SerializerSession session, int sizeHint = 0) { if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { _codec.Serialize(ref writer, ref value); writer.WriteEndObject(); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Deserialize a value of type from . /// /// The reader input type. /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(ref Reader source, scoped ref T result) { _codec.Deserialize(ref source, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(Stream source, scoped ref T result) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(Stream source, scoped ref T result, SerializerSession session) { var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(ReadOnlySequence source, scoped ref T result) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(ReadOnlySequence source, scoped ref T result, SerializerSession session) { var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(ArraySegment source, scoped ref T result, SerializerSession session) => Deserialize(source.AsSpan(), ref result, session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(ReadOnlySpan source, scoped ref T result) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(ReadOnlySpan source, scoped ref T result, SerializerSession session) { var reader = Reader.Create(source, session); _codec.Deserialize(ref reader, ref result); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(byte[] source, scoped ref T result) => Deserialize(source.AsSpan(), ref result); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(byte[] source, scoped ref T result, SerializerSession session) => Deserialize(source.AsSpan(), ref result, session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(ReadOnlyMemory source, scoped ref T result) => Deserialize(source.Span, ref result); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The serializer session. /// The deserialized value. public void Deserialize(ReadOnlyMemory source, scoped ref T result, SerializerSession session) => Deserialize(source.Span, ref result, session); /// /// Deserialize a value of type from . /// /// The source buffer. /// The deserialized value. /// The deserialized value. public void Deserialize(ArraySegment source, scoped ref T result) => Deserialize(source.AsSpan(), ref result); } /// /// Provides methods for serializing and deserializing values which have types which are not statically known. /// public sealed class ObjectSerializer { private readonly SerializerSessionPool _sessionPool; /// /// Initializes a new instance of the class. /// /// The session pool. public ObjectSerializer(SerializerSessionPool sessionPool) => _sessionPool = sessionPool; /// /// Returns if the provided type, , can be serialized, and otherwise. /// public bool CanSerialize(Type type) => _sessionPool.CodecProvider.TryGetCodec(type) is { }; /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. /// This method slices the to the serialized data length. public void Serialize(object value, ref Memory destination, Type type) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. /// This method slices the to the serialized data length. public void Serialize(object value, ref Memory destination, SerializerSession session, Type type) { var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(object value, Stream destination, Type type, int sizeHint = 0) { using var session = _sessionPool.GetSession(); if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. /// The estimated upper bound for the length of the serialized data. /// The destination stream will not be flushed by this method. public void Serialize(object value, Stream destination, SerializerSession session, Type type, int sizeHint = 0) { if (destination is MemoryStream memoryStream) { var writer = Writer.Create(memoryStream, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); } else { var writer = Writer.CreatePooled(destination, session, sizeHint); try { ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); } finally { writer.Dispose(); } } } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. public void Serialize(object value, TBufferWriter destination, Type type) where TBufferWriter : IBufferWriter { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. public void Serialize(object value, TBufferWriter destination, SerializerSession session, Type type) where TBufferWriter : IBufferWriter { var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); // Do not dispose, since the buffer writer is not owned by the method. } /// /// Serializes the provided into . /// /// The output buffer writer. /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. public void Serialize(object value, ref Writer destination, Type type) where TBufferWriter : IBufferWriter { ObjectCodec.WriteField(ref destination, 0, type, value); destination.Commit(); } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. /// This method slices the to the serialized data length. public void Serialize(object value, ref Span destination, Type type) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. /// This method slices the to the serialized data length. public void Serialize(object value, ref Span destination, SerializerSession session, Type type) { var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); destination = destination[..writer.Position]; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. /// The length of the serialized data. public int Serialize(object value, byte[] destination, Type type) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); return writer.Position; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The expected type of the value. /// The length of the serialized data. public int Serialize(object value, ArraySegment destination, Type type) { var destinationSpan = destination.AsSpan(); Serialize(value, ref destinationSpan, type); return destinationSpan.Length; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. /// The length of the serialized data. public int Serialize(object value, ArraySegment destination, SerializerSession session, Type type) { var destinationSpan = destination.AsSpan(); Serialize(value, ref destinationSpan, session, type); return destinationSpan.Length; } /// /// Serializes the provided into . /// /// The value to serialize. /// The destination where serialized data will be written. /// The serializer session. /// The expected type of the value. /// The length of the serialized data. public int Serialize(object value, byte[] destination, SerializerSession session, Type type) { var writer = Writer.Create(destination, session); ObjectCodec.WriteField(ref writer, 0, type, value); writer.Commit(); return writer.Position; } /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(Stream source, Type type) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(Stream source, SerializerSession session, Type type) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlySequence source, Type type) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlySequence source, SerializerSession session, Type type) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlySpan source, Type type) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlySpan source, SerializerSession session, Type type) { var reader = Reader.Create(source, session); var codec = session.CodecProvider.GetCodec(type); var field = reader.ReadFieldHeader(); return codec.ReadValue(ref reader, field); } /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(byte[] source, Type type) => Deserialize(source.AsSpan(), type); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(byte[] source, SerializerSession session, Type type) => Deserialize(source.AsSpan(), session, type); /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlyMemory source, Type type) => Deserialize(source.Span, type); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(ReadOnlyMemory source, SerializerSession session, Type type) => Deserialize(source.Span, session, type); /// /// Deserialize a value of type from . /// /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(ArraySegment source, Type type) => Deserialize(source.AsSpan(), type); /// /// Deserialize a value of type from . /// /// The source buffer. /// The serializer session. /// The expected type of the value. /// The deserialized value. public object Deserialize(ArraySegment source, SerializerSession session, Type type) => Deserialize(source.AsSpan(), session, type); /// /// Deserialize a value of type from . /// /// The reader input type. /// The source buffer. /// The expected type of the value. /// The deserialized value. public object Deserialize(ref Reader source, Type type) { var codec = source.Session.CodecProvider.GetCodec(type); var field = source.ReadFieldHeader(); return codec.ReadValue(ref source, field); } } /// /// Provides functionality for copying object and values. /// public sealed class DeepCopier { private readonly CodecProvider _codecProvider; private readonly CopyContextPool _contextPool; /// /// Initializes a new instance of the class. /// /// The codec provider. /// The context pool. public DeepCopier(CodecProvider codecProvider, CopyContextPool contextPool) { _codecProvider = codecProvider; _contextPool = contextPool; } /// /// Returns a copier which is specialized to the provided type parameter. /// /// The underlying type for the returned copier. public DeepCopier GetCopier() => new(_codecProvider.GetDeepCopier(), _contextPool); /// /// Creates a copy of the provided value. /// /// The type of the value to copy. /// The value to copy. /// A copy of the provided value. public T Copy(T value) { using var context = _contextPool.GetContext(); return context.DeepCopy(value); } } /// /// Provides functionality for copying objects and values. /// public sealed class DeepCopier { private readonly IDeepCopier _copier; private readonly CopyContextPool _contextPool; /// /// Initializes a new instance of the class. /// /// The copier. /// The context pool. public DeepCopier(IDeepCopier copier, CopyContextPool contextPool) { _copier = copier; _contextPool = contextPool; } /// /// Creates a copy of the provided value. /// /// The value to copy. /// A copy of the provided value. public T Copy(T value) { using var context = _contextPool.GetContext(); return _copier.DeepCopy(value, context); } } } ================================================ FILE: src/Orleans.Serialization/Serializers/AbstractTypeSerializer.cs ================================================ using System; using System.Buffers; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Serializers { /// /// Serializer for types which are abstract and therefore cannot be instantiated themselves, such as abstract classes and interface types. /// public class AbstractTypeSerializer : AbstractTypeSerializer, IFieldCodec, IBaseCodec where TField : class { protected AbstractTypeSerializer() : base(typeof(TField)) { } public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter => base.WriteField(ref writer, fieldIdDelta, expectedType, value); public new TField ReadValue(ref Reader reader, Field field) => (TField)base.ReadValue(ref reader, field); public virtual void Serialize(ref Writer writer, TField instance) where TBufferWriter : IBufferWriter { } public virtual void Deserialize(ref Reader reader, TField instance) => reader.ConsumeEndBaseOrEndObject(); } // without the class type constraint internal sealed class AbstractTypeSerializerWrapper : AbstractTypeSerializer, IFieldCodec { public AbstractTypeSerializerWrapper() : base(typeof(TField)) { } public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter => base.WriteField(ref writer, fieldIdDelta, expectedType, value); public new TField ReadValue(ref Reader reader, Field field) => (TField)base.ReadValue(ref reader, field); } public class AbstractTypeSerializer : IFieldCodec { private readonly Type _fieldType; protected internal AbstractTypeSerializer(Type fieldType) => _fieldType = fieldType; public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter { if (value is null) { ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); return; } var specificSerializer = writer.Session.CodecProvider.GetCodec(value.GetType()); specificSerializer.WriteField(ref writer, fieldIdDelta, expectedType, value); } public object ReadValue(ref Reader reader, Field field) { if (field.IsReference) return ReferenceCodec.ReadReference(ref reader, field.FieldType ?? _fieldType); var fieldType = field.FieldType; if (fieldType is null) ThrowMissingFieldType(); var specificSerializer = reader.Session.CodecProvider.GetCodec(fieldType); return specificSerializer.ReadValue(ref reader, field); } private void ThrowMissingFieldType() => throw new FieldTypeMissingException(_fieldType); } } ================================================ FILE: src/Orleans.Serialization/Serializers/CodecProvider.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Serialization.Activators; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Configuration; using Orleans.Serialization.GeneratedCodeHelpers; namespace Orleans.Serialization.Serializers { /// /// Provides access to serializers and related objects. /// public sealed class CodecProvider : ICodecProvider { private static readonly Type ObjectType = typeof(object); #if NET9_0_OR_GREATER private readonly Lock _initializationLock = new(); #else private readonly object _initializationLock = new(); #endif private readonly ConcurrentDictionary _untypedCodecs = new(); private readonly ConcurrentDictionary _typedCodecs = new(); private readonly ConcurrentDictionary _typedBaseCodecs = new(); private readonly ConcurrentDictionary _untypedCopiers = new(); private readonly ConcurrentDictionary _typedCopiers = new(); private readonly ConcurrentDictionary _instantiatedBaseCopiers = new(); private readonly ConcurrentDictionary _instantiatedValueSerializers = new(); private readonly ConcurrentDictionary _instantiatedActivators = new(); private readonly Dictionary _baseCodecs = new(); private readonly Dictionary _valueSerializers = new(); private readonly Dictionary _fieldCodecs = new(); private readonly Dictionary _copiers = new(); private readonly Dictionary _converters = new(); private readonly Dictionary _baseCopiers = new(); private readonly Dictionary _activators = new(); private readonly List _generalizedCodecs = new(); private readonly List _specializableCodecs = new(); private readonly List _generalizedBaseCodecs = new(); private readonly List _specializableBaseCodecs = new(); private readonly List _generalizedCopiers = new(); private readonly List _specializableCopiers = new(); private readonly ObjectCodec _objectCodec = new(); private readonly VoidCodec _voidCodec = new(); private readonly ObjectCopier _objectCopier = new(); private readonly IServiceProvider _serviceProvider; private readonly VoidCopier _voidCopier = new(); private bool _initialized; /// /// Initializes a new instance of the class. /// /// The service provider. /// The codec configuration. public CodecProvider(IServiceProvider serviceProvider, IOptions codecConfiguration) { _serviceProvider = serviceProvider; ConsumeMetadata(codecConfiguration); } /// public IServiceProvider Services => _serviceProvider; private void Initialize() { lock (_initializationLock) { if (_initialized) { return; } _generalizedCodecs.AddRange(_serviceProvider.GetServices()); _generalizedBaseCodecs.AddRange(_serviceProvider.GetServices()); _generalizedCopiers.AddRange(_serviceProvider.GetServices()); _specializableCodecs.AddRange(_serviceProvider.GetServices()); _specializableCopiers.AddRange(_serviceProvider.GetServices()); _specializableBaseCodecs.AddRange(_serviceProvider.GetServices()); _initialized = true; } } private void ConsumeMetadata(IOptions codecConfiguration) { var metadata = codecConfiguration.Value; AddFromMetadata(_baseCodecs, metadata.Serializers, typeof(IBaseCodec<>)); AddFromMetadata(_valueSerializers, metadata.Serializers, typeof(IValueSerializer<>)); AddFromMetadata(_fieldCodecs, metadata.Serializers, typeof(IFieldCodec<>)); AddFromMetadata(_fieldCodecs, metadata.FieldCodecs, typeof(IFieldCodec<>)); AddFromMetadata(_activators, metadata.Activators, typeof(IActivator<>)); AddFromMetadata(_copiers, metadata.Copiers, typeof(IDeepCopier<>)); AddFromMetadata(_converters, metadata.Converters, typeof(IConverter<,>)); AddFromMetadata(_baseCopiers, metadata.Copiers, typeof(IBaseCopier<>)); static void AddFromMetadata(Dictionary resultCollection, HashSet metadataCollection, Type genericType) { Debug.Assert(genericType.GetGenericArguments().Length >= 1); foreach (var type in metadataCollection) { var interfaces = type.GetInterfaces(); foreach (var @interface in interfaces) { if (!@interface.IsGenericType) { continue; } if (genericType != @interface.GetGenericTypeDefinition()) { continue; } var genericArgument = @interface.GetGenericArguments()[0]; if (typeof(object) == genericArgument) { continue; } if (genericArgument.IsConstructedGenericType && Array.Exists(genericArgument.GenericTypeArguments, arg => arg.IsGenericParameter)) { genericArgument = genericArgument.GetGenericTypeDefinition(); } resultCollection[genericArgument] = type; } } } } /// public IFieldCodec TryGetCodec() { var fieldType = typeof(TField); if (_typedCodecs.TryGetValue(fieldType, out var existing)) return (IFieldCodec)existing; if (TryGetCodec(fieldType) is not { } untypedResult) return null; var typedResult = untypedResult switch { IFieldCodec typed => typed, _ when untypedResult.GetType() == typeof(AbstractTypeSerializer) => new AbstractTypeSerializerWrapper(), _ => new UntypedCodecWrapper(untypedResult) }; return (IFieldCodec)_typedCodecs.GetOrAdd(fieldType, typedResult); } /// public IFieldCodec GetCodec(Type fieldType) { var res = TryGetCodec(fieldType); if (res is null) ThrowCodecNotFound(fieldType); return res; } /// public IFieldCodec TryGetCodec(Type fieldType) { // If the field type is unavailable, return the void codec which can at least handle references. return fieldType is null ? _voidCodec : _untypedCodecs.TryGetValue(fieldType, out var existing) ? existing : TryCreateCodec(fieldType) is { } res ? _untypedCodecs.GetOrAdd(fieldType, res) : null; } private IFieldCodec TryCreateCodec(Type fieldType) { if (!_initialized) Initialize(); ThrowIfUnsupportedType(fieldType); if (CreateCodecInstance(fieldType, fieldType.IsConstructedGenericType ? fieldType.GetGenericTypeDefinition() : fieldType) is { } res) return res; foreach (var specializableCodec in _specializableCodecs) { if (specializableCodec.IsSupportedType(fieldType)) return specializableCodec.GetSpecializedCodec(fieldType); } foreach (var dynamicCodec in _generalizedCodecs) { if (dynamicCodec.IsSupportedType(fieldType)) return dynamicCodec; } return fieldType.IsInterface || fieldType.IsAbstract ? new AbstractTypeSerializer(fieldType) : null; } /// public IFieldCodec GetCodec() { var res = TryGetCodec(); if (res is null) ThrowCodecNotFound(typeof(TField)); return res; } /// public IActivator GetActivator() { var type = typeof(T); var searchType = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type; var res = GetActivatorInner(type, searchType); if (res is null) ThrowActivatorNotFound(type); return (IActivator)res; } private IBaseCodec TryCreateBaseCodec(Type fieldType) where TField : class { if (!_initialized) Initialize(); ThrowIfUnsupportedType(fieldType); // Try to find the codec from the configured codecs. var untypedResult = CreateBaseCodecInstance(fieldType, fieldType.IsConstructedGenericType ? fieldType.GetGenericTypeDefinition() : fieldType); if (untypedResult is null) { foreach (var specializableCodec in _specializableBaseCodecs) { if (specializableCodec.IsSupportedType(fieldType)) { untypedResult = specializableCodec.GetSpecializedCodec(fieldType); break; } } if (untypedResult is null) { foreach (var dynamicCodec in _generalizedBaseCodecs) { if (dynamicCodec.IsSupportedType(fieldType)) { untypedResult = dynamicCodec; break; } } if (untypedResult is null) return null; } } if (untypedResult is not IBaseCodec typedResult) ThrowCannotConvert(untypedResult); return (IBaseCodec)_typedBaseCodecs.GetOrAdd(fieldType, typedResult); static void ThrowCannotConvert(object rawCodec) => throw new InvalidOperationException($"Cannot convert codec of type {rawCodec.GetType()} to codec of type {typeof(IBaseCodec)}."); } /// public IBaseCodec GetBaseCodec() where TField : class { var type = typeof(TField); if (_typedBaseCodecs.TryGetValue(type, out var existing)) return (IBaseCodec)existing; var result = TryCreateBaseCodec(type); if (result is null) ThrowBaseCodecNotFound(type); return result; } /// public IValueSerializer GetValueSerializer() where TField : struct { var type = typeof(TField); var searchType = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type; var res = GetValueSerializerInner(type, searchType); if (res is null) ThrowValueSerializerNotFound(type); return (IValueSerializer)res; } /// public IBaseCopier GetBaseCopier() where TField : class { var type = typeof(TField); var searchType = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type; var res = GetBaseCopierInner(type, searchType); if (res is null) ThrowBaseCopierNotFound(type); return (IBaseCopier)res; } /// public IDeepCopier GetDeepCopier() { var res = TryGetDeepCopier(); if (res is null) ThrowCopierNotFound(typeof(T)); return res; } /// public IDeepCopier TryGetDeepCopier() { var type = typeof(T); if (_typedCopiers.TryGetValue(type, out var existing)) return (IDeepCopier)existing; if (TryGetDeepCopier(type) is not { } untypedResult) return null; var typedResult = untypedResult switch { IDeepCopier typed => typed, IOptionalDeepCopier optional when optional.IsShallowCopyable() => new ShallowCopier(), _ => new UntypedCopierWrapper(untypedResult) }; return (IDeepCopier)_typedCopiers.GetOrAdd(type, typedResult); } /// public IDeepCopier GetDeepCopier(Type fieldType) { var res = TryGetDeepCopier(fieldType); if (res is null) ThrowCopierNotFound(fieldType); return res; } /// public IDeepCopier TryGetDeepCopier(Type fieldType) { // If the field type is unavailable, return the void copier which can at least handle references. return fieldType is null ? _voidCopier : _untypedCopiers.TryGetValue(fieldType, out var existing) ? existing : TryCreateCopier(fieldType) is { } res ? _untypedCopiers.GetOrAdd(fieldType, res) : null; } private IDeepCopier TryCreateCopier(Type fieldType) { if (!_initialized) Initialize(); ThrowIfUnsupportedType(fieldType); if (CreateCopierInstance(fieldType, fieldType.IsConstructedGenericType ? fieldType.GetGenericTypeDefinition() : fieldType) is { } res) return res; foreach (var specializableCopier in _specializableCopiers) { if (specializableCopier.IsSupportedType(fieldType)) return specializableCopier.GetSpecializedCopier(fieldType); } foreach (var dynamicCopier in _generalizedCopiers) { if (dynamicCopier.IsSupportedType(fieldType)) return dynamicCopier; } return fieldType.IsInterface || fieldType.IsAbstract ? _objectCopier : null; } private object GetValueSerializerInner(Type concreteType, Type searchType) { if (!_initialized) Initialize(); ThrowIfUnsupportedType(concreteType); object[] constructorArguments = null; if (_valueSerializers.TryGetValue(searchType, out var serializerType)) { if (serializerType.IsGenericTypeDefinition) { serializerType = serializerType.MakeGenericType(concreteType.GetGenericArguments()); } } else if (TryGetSurrogateCodec(concreteType, searchType, out var surrogateCodecType, out constructorArguments) && typeof(IValueSerializer).IsAssignableFrom(surrogateCodecType)) { serializerType = surrogateCodecType; } else { return null; } if (!_instantiatedValueSerializers.TryGetValue(serializerType, out var result)) { result = _instantiatedValueSerializers.GetOrAdd(serializerType, GetServiceOrCreateInstance(serializerType, constructorArguments)); } return result; } private object GetBaseCopierInner(Type concreteType, Type searchType) { if (!_initialized) Initialize(); ThrowIfUnsupportedType(concreteType); object[] constructorArguments = null; if (_baseCopiers.TryGetValue(searchType, out var copierType)) { // Use the detected copier type. if (copierType.IsGenericTypeDefinition) { copierType = copierType.MakeGenericType(concreteType.GetGenericArguments()); } } else if (TryGetSurrogateCodec(concreteType, searchType, out var surrogateCodecType, out constructorArguments) && typeof(IBaseCopier).IsAssignableFrom(surrogateCodecType)) { copierType = surrogateCodecType; } else { return null; } if (!_instantiatedBaseCopiers.TryGetValue(copierType, out var result)) { result = _instantiatedBaseCopiers.GetOrAdd(copierType, GetServiceOrCreateInstance(copierType, constructorArguments)); } return result; } private object GetActivatorInner(Type concreteType, Type searchType) { if (!_initialized) Initialize(); ThrowIfUnsupportedType(concreteType); if (!_activators.TryGetValue(searchType, out var activatorType)) { if (searchType.IsValueType) { activatorType = typeof(DefaultValueTypeActivator<>).MakeGenericType(concreteType); } else { activatorType = typeof(DefaultReferenceTypeActivator<>).MakeGenericType(concreteType); } } else if (activatorType.IsGenericTypeDefinition) { activatorType = activatorType.MakeGenericType(concreteType.GetGenericArguments()); } if (!_instantiatedActivators.TryGetValue(activatorType, out var result)) { result = _instantiatedActivators.GetOrAdd(activatorType, GetServiceOrCreateInstance(activatorType)); } return result; } private static void ThrowIfUnsupportedType(Type fieldType) { if (fieldType.IsGenericTypeDefinition) { ThrowGenericTypeDefinition(fieldType); } if (fieldType.IsPointer) { ThrowPointerType(fieldType); } if (fieldType.IsByRef) { ThrowByRefType(fieldType); } } private object GetServiceOrCreateInstance(Type type, object[] constructorArguments = null) { var result = OrleansGeneratedCodeHelper.TryGetService(type); if (result != null) { return result; } result = _serviceProvider.GetService(type); if (result != null) { return result; } result = ActivatorUtilities.CreateInstance(_serviceProvider, type, constructorArguments ?? Array.Empty()); return result; } private IFieldCodec CreateCodecInstance(Type fieldType, Type searchType) { if (searchType == ObjectType) return _objectCodec; object[] constructorArguments = null; if (_fieldCodecs.TryGetValue(searchType, out var codecType)) { if (codecType.IsGenericTypeDefinition) { codecType = codecType.MakeGenericType(fieldType.GetGenericArguments()); } } else if (_baseCodecs.TryGetValue(searchType, out var baseCodecType)) { if (baseCodecType.IsGenericTypeDefinition) { baseCodecType = baseCodecType.MakeGenericType(fieldType.GetGenericArguments()); } // If there is a base type serializer for this type, create a codec which will then accept that base type serializer. codecType = typeof(ConcreteTypeSerializer<,>).MakeGenericType(fieldType, baseCodecType); constructorArguments = new[] { GetServiceOrCreateInstance(baseCodecType) }; } else if (_valueSerializers.TryGetValue(searchType, out var valueSerializerType)) { if (valueSerializerType.IsGenericTypeDefinition) { valueSerializerType = valueSerializerType.MakeGenericType(fieldType.GetGenericArguments()); } // If there is a value serializer for this type, create a codec which will then accept that value serializer. codecType = typeof(ValueSerializer<,>).MakeGenericType(fieldType, valueSerializerType); constructorArguments = new[] { GetServiceOrCreateInstance(valueSerializerType) }; } else if (fieldType.IsArray) { // Depending on the type of the array, select the base array codec or the multi-dimensional codec. var arrayCodecType = fieldType.IsSZArray ? typeof(ArrayCodec<>) : typeof(MultiDimensionalArrayCodec<>); codecType = arrayCodecType.MakeGenericType(fieldType.GetElementType()); } else if (fieldType.IsEnum) { return CreateCodecInstance(fieldType, fieldType.GetEnumUnderlyingType()); } else if (TryGetSurrogateCodec(fieldType, searchType, out var surrogateCodecType, out constructorArguments)) { // Use the converter codecType = surrogateCodecType; } else if (searchType.BaseType is object && CreateCodecInstance( fieldType.BaseType, searchType.BaseType switch { { IsConstructedGenericType: true } => searchType.BaseType.GetGenericTypeDefinition(), _ => searchType.BaseType }) is IDerivedTypeCodec fieldCodec) { // Find codecs which generalize over all subtypes. return fieldCodec; } return codecType != null ? (IFieldCodec)GetServiceOrCreateInstance(codecType, constructorArguments) : null; } private bool TryGetSurrogateCodec(Type fieldType, Type searchType, out Type surrogateCodecType, out object[] constructorArguments) { if (_converters.TryGetValue(searchType, out var converterType)) { if (converterType.IsGenericTypeDefinition) { converterType = converterType.MakeGenericType(fieldType.GetGenericArguments()); } var converterInterfaceArgs = Array.Empty(); foreach (var @interface in converterType.GetInterfaces()) { if (@interface.IsConstructedGenericType && @interface.GetGenericTypeDefinition() == typeof(IConverter<,>) && @interface.GenericTypeArguments[0] == fieldType) { converterInterfaceArgs = @interface.GetGenericArguments(); } } if (converterInterfaceArgs is { Length: 0 }) { throw new InvalidOperationException($"A registered type converter {converterType} does not implement {typeof(IConverter<,>)}"); } var typeArgs = new Type[3] { converterInterfaceArgs[0], converterInterfaceArgs[1], converterType }; constructorArguments = new object[] { GetServiceOrCreateInstance(converterType) }; if (typeArgs[0].IsValueType) { surrogateCodecType = typeof(ValueTypeSurrogateCodec<,,>).MakeGenericType(typeArgs); } else { surrogateCodecType = typeof(SurrogateCodec<,,>).MakeGenericType(typeArgs); } return true; } surrogateCodecType = null; constructorArguments = null; return false; } private IBaseCodec CreateBaseCodecInstance(Type fieldType, Type searchType) { object[] constructorArguments = null; if (_baseCodecs.TryGetValue(searchType, out var codecType)) { if (codecType.IsGenericTypeDefinition) { codecType = codecType.MakeGenericType(fieldType.GetGenericArguments()); } } else if (TryGetSurrogateCodec(fieldType, searchType, out var surrogateCodecType, out constructorArguments) && typeof(IBaseCodec).IsAssignableFrom(surrogateCodecType)) { codecType = surrogateCodecType; } return codecType != null ? (IBaseCodec)GetServiceOrCreateInstance(codecType, constructorArguments) : null; } private IDeepCopier CreateCopierInstance(Type fieldType, Type searchType) { if (searchType == ObjectType) return _objectCopier; object[] constructorArguments = null; if (_copiers.TryGetValue(searchType, out var copierType)) { if (copierType.IsGenericTypeDefinition) { copierType = copierType.MakeGenericType(fieldType.GetGenericArguments()); } } else if (ShallowCopyableTypes.Contains(fieldType)) { return ShallowCopier.Instance; } else if (fieldType.IsArray) { // Depending on the type of the array, select the base array copier or the multi-dimensional copier. var arrayCopierType = fieldType.IsSZArray ? typeof(ArrayCopier<>) : typeof(MultiDimensionalArrayCopier<>); copierType = arrayCopierType.MakeGenericType(fieldType.GetElementType()); } else if (TryGetSurrogateCodec(fieldType, searchType, out var surrogateCodecType, out constructorArguments)) { copierType = surrogateCodecType; } else if (searchType.BaseType is { } baseType) { // Find copiers which generalize over all subtypes. if (CreateCopierInstance(fieldType, baseType) is IDerivedTypeCopier baseCopier) { return baseCopier; } else if (baseType.IsGenericType && baseType.IsConstructedGenericType && CreateCopierInstance(fieldType, baseType.GetGenericTypeDefinition()) is IDerivedTypeCopier genericBaseCopier) { return genericBaseCopier; } } return copierType != null ? (IDeepCopier)GetServiceOrCreateInstance(copierType, constructorArguments) : null; } private static void ThrowPointerType(Type fieldType) => throw new NotSupportedException($"Type {fieldType} is a pointer type and is therefore not supported."); private static void ThrowByRefType(Type fieldType) => throw new NotSupportedException($"Type {fieldType} is a by-ref type and is therefore not supported."); private static void ThrowGenericTypeDefinition(Type fieldType) => throw new InvalidOperationException($"Type {fieldType} is a non-constructed generic type and is therefore unsupported."); private static void ThrowCodecNotFound(Type fieldType) => throw new CodecNotFoundException($"Could not find a codec for type {fieldType}."); private static void ThrowCopierNotFound(Type type) => throw new CodecNotFoundException($"Could not find a copier for type {type}."); private static void ThrowBaseCodecNotFound(Type fieldType) => throw new KeyNotFoundException($"Could not find a base type serializer for type {fieldType}."); private static void ThrowValueSerializerNotFound(Type fieldType) => throw new KeyNotFoundException($"Could not find a value serializer for type {fieldType}."); private static void ThrowActivatorNotFound(Type type) => throw new KeyNotFoundException($"Could not find an activator for type {type}."); private static void ThrowBaseCopierNotFound(Type type) => throw new KeyNotFoundException($"Could not find a base type copier for type {type}."); } } ================================================ FILE: src/Orleans.Serialization/Serializers/ConcreteTypeSerializer.cs ================================================ using Orleans.Serialization.Activators; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; namespace Orleans.Serialization.Serializers { /// /// Serializer for reference types which can be instantiated. /// /// The field type. /// The partial serializer implementation type. public sealed class ConcreteTypeSerializer : IFieldCodec where TField : class where TBaseCodec : IBaseCodec { private readonly Type CodecFieldType = typeof(TField); private readonly IActivator _activator; private readonly TBaseCodec _serializer; /// /// Initializes a new instance of the class. /// /// The activator. /// The serializer. public ConcreteTypeSerializer(IActivator activator, TBaseCodec serializer) { _activator = activator; _serializer = serializer; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { if (value is null || value.GetType() as object == CodecFieldType as object) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteStartObject(fieldIdDelta, expectedType, CodecFieldType); _serializer.Serialize(ref writer, value); writer.WriteEndObject(); } else { writer.SerializeUnexpectedType(fieldIdDelta, expectedType, value); } } /// public TField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } var fieldType = field.FieldType; if (fieldType is null || fieldType == CodecFieldType) { var result = _activator.Create(); ReferenceCodec.RecordObject(reader.Session, result); _serializer.Deserialize(ref reader, result); return result; } return reader.DeserializeUnexpectedType(ref field); } public TField ReadValueSealed(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } var result = _activator.Create(); ReferenceCodec.RecordObject(reader.Session, result); _serializer.Deserialize(ref reader, result); return result; } } } ================================================ FILE: src/Orleans.Serialization/Serializers/IActivatorProvider.cs ================================================ using Orleans.Serialization.Activators; namespace Orleans.Serialization.Serializers { /// /// Provides activators. /// public interface IActivatorProvider { /// /// Gets an activator for the specified type. /// /// The type. /// The activator. IActivator GetActivator(); } } ================================================ FILE: src/Orleans.Serialization/Serializers/IBaseCodec.cs ================================================ using Orleans.Serialization.Buffers; using System; using System.Buffers; namespace Orleans.Serialization.Serializers { /// /// Functionality for serializing and deserializing members in a type hierarchy. /// /// The type supported by this codec. public interface IBaseCodec : IBaseCodec where T : class { /// /// Serializes the provided value. /// /// The buffer writer type. /// The writer. /// The value. void Serialize(ref Writer writer, T value) where TBufferWriter : IBufferWriter; /// /// Deserializes into the provided value. /// /// The reader input type. /// The reader. /// The value. void Deserialize(ref Reader reader, T value); } /// /// Marker interface for base serializers. /// public interface IBaseCodec { } /// /// A base type serializer which supports multiple types. /// public interface IGeneralizedBaseCodec : IBaseCodec { /// /// Determines whether the specified type is supported by this instance. /// /// The type. /// if the specified type is supported; otherwise, . bool IsSupportedType(Type type); } /// /// Provides functionality for creating instances which support a given type. /// public interface ISpecializableBaseCodec { /// /// Determines whether the specified type is supported by this instance. /// /// The type. /// if the specified type is supported; otherwise, . bool IsSupportedType(Type type); /// /// Gets an implementation which supports the specified type. /// /// The type. /// An implementation which supports the specified type. IBaseCodec GetSpecializedCodec(Type type); } } ================================================ FILE: src/Orleans.Serialization/Serializers/IBaseCodecProvider.cs ================================================ namespace Orleans.Serialization.Serializers { /// /// Provides access to implementations. /// public interface IBaseCodecProvider { /// /// Gets a base codec for the specified type. /// /// The underlying field type. /// A base codec. IBaseCodec GetBaseCodec() where TField : class; } } ================================================ FILE: src/Orleans.Serialization/Serializers/ICodecProvider.cs ================================================ using Orleans.Serialization.Cloning; using System; namespace Orleans.Serialization.Serializers { /// /// Provides functionality for accessing codecs, activators, and copiers. /// public interface ICodecProvider : IFieldCodecProvider, IBaseCodecProvider, IValueSerializerProvider, IActivatorProvider, IDeepCopierProvider { /// /// Gets the service provider. /// /// The service provider. IServiceProvider Services { get; } } } ================================================ FILE: src/Orleans.Serialization/Serializers/ICodecSelector.cs ================================================ using System; namespace Orleans.Serialization.Serializers; /// /// Functionality used by general-purpose codecs (such as a JSON codec) to allow types to opt-in to using them. /// public interface ICodecSelector { /// /// The well-known codec name, used to match an instance with a codec. /// public string CodecName { get; } /// /// Returns true if the specified codec should be used for this type. /// public bool IsSupportedType(Type type); } /// /// Functionality used by general-purpose copiers (such as a JSON copier) to allow types to opt-in to using them. /// public interface ICopierSelector { /// /// The well-known copier name, used to match an instance with a copier. /// public string CopierName { get; } /// /// Returns true if the specified copier should be used for this type. /// public bool IsSupportedType(Type type); } /// /// Implementation of which uses a delegate. /// public sealed class DelegateCodecSelector : ICodecSelector { public string CodecName { get; init; } public Func IsSupportedTypeDelegate { get; init; } public bool IsSupportedType(Type type) => IsSupportedTypeDelegate(type); } /// /// Implementation of which uses a delegate. /// public sealed class DelegateCopierSelector : ICopierSelector { public string CopierName { get; init; } public Func IsSupportedTypeDelegate { get; init; } public bool IsSupportedType(Type type) => IsSupportedTypeDelegate(type); } ================================================ FILE: src/Orleans.Serialization/Serializers/IFieldCodecProvider.cs ================================================ #nullable enable using Orleans.Serialization.Codecs; using System; namespace Orleans.Serialization.Serializers { /// /// Provides access to field codecs. /// public interface IFieldCodecProvider { /// /// Gets a codec for the specified type. /// /// The field type. /// A codec. IFieldCodec GetCodec(); /// /// Gets a codec for the specific type, or if no appropriate codec was found. /// /// The field type. /// A codec. IFieldCodec TryGetCodec(); /// /// Gets a codec for the specific type. /// /// /// The field type. /// /// A codec. IFieldCodec GetCodec(Type fieldType); /// /// Gets a codec for the specific type, or if no appropriate codec was found. /// /// /// The field type. /// /// A codec. IFieldCodec TryGetCodec(Type fieldType); } } ================================================ FILE: src/Orleans.Serialization/Serializers/IGeneralizedCodec.cs ================================================ using System; using Orleans.Serialization.Codecs; namespace Orleans.Serialization.Serializers { /// /// A codec which supports multiple types. /// public interface IGeneralizedCodec : IFieldCodec { /// /// Determines whether the specified type is supported by this instance. /// /// The type. /// if the specified type is supported; otherwise, . bool IsSupportedType(Type type); } /// /// Provides access to codecs for multiple types. /// public interface ISpecializableCodec { /// /// Determines whether the specified type is supported by this instance. /// /// The type. /// if the specified type is supported; otherwise, . bool IsSupportedType(Type type); /// /// Gets an implementation which supports the specified type. /// /// The type. /// An implementation which supports the specified type. IFieldCodec GetSpecializedCodec(Type type); } } ================================================ FILE: src/Orleans.Serialization/Serializers/IValueSerializer.cs ================================================ using Orleans.Serialization.Buffers; using System.Buffers; namespace Orleans.Serialization.Serializers { /// /// Functionality for serializing a value type. /// /// The value type. public interface IValueSerializer : IValueSerializer where T : struct { /// /// Serializes the provided value. /// /// The buffer writer type. /// The writer. /// The value. void Serialize(ref Writer writer, scoped ref T value) where TBufferWriter : IBufferWriter; /// /// Deserializes the specified type. /// /// The reader input type. /// The reader. /// The value. void Deserialize(ref Reader reader, scoped ref T value); } /// /// Marker interface for value type serializers. /// public interface IValueSerializer { } } ================================================ FILE: src/Orleans.Serialization/Serializers/IValueSerializerProvider.cs ================================================ namespace Orleans.Serialization.Serializers { /// /// Provides access to value type serializers. /// public interface IValueSerializerProvider { /// /// Gets the value serializer for the specified type. /// /// The value type. /// A value serializer for the specified type. IValueSerializer GetValueSerializer() where TField : struct; } } ================================================ FILE: src/Orleans.Serialization/Serializers/SurrogateCodec.cs ================================================ #nullable enable using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization.Serializers; /// /// Surrogate serializer for . /// /// The type which the implementation of this class supports. /// The surrogate type serialized in place of . /// The converter type which converts between and . public sealed class SurrogateCodec : IFieldCodec, IDeepCopier, IBaseCodec, IBaseCopier where TField : class where TSurrogate : struct where TConverter : IConverter { private readonly Type _fieldType = typeof(TField); private readonly IValueSerializer _surrogateSerializer; private readonly IDeepCopier _surrogateCopier; private readonly IPopulator? _populator; private readonly TConverter _converter; /// /// Initializes a new instance of the class. /// /// The surrogate serializer. /// The surrogate copier. /// The surrogate converter. public SurrogateCodec( IValueSerializer surrogateSerializer, IDeepCopier surrogateCopier, TConverter converter) { _surrogateSerializer = surrogateSerializer; _surrogateCopier = surrogateCopier; _converter = converter; _populator = converter as IPopulator; } /// public TField DeepCopy(TField input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result!; } var surrogate = _converter.ConvertToSurrogate(in input); var copy = _surrogateCopier.DeepCopy(surrogate, context); result = _converter.ConvertFromSurrogate(in copy); context.RecordCopy(input, result); return result; } /// public TField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } if (field.FieldType is null || field.FieldType == _fieldType) { field.EnsureWireTypeTagDelimited(); uint placeholderReferenceId = default; if (IsReferenceTrackingSupported) { placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); } TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); var result = _converter.ConvertFromSurrogate(in surrogate); if (IsReferenceTrackingSupported) { ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } return reader.DeserializeUnexpectedType(ref field); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { if (IsReferenceTrackingSupported && ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } if (value.GetType() as object == _fieldType as object) { writer.WriteStartObject(fieldIdDelta, expectedType, _fieldType); var surrogate = _converter.ConvertToSurrogate(in value); _surrogateSerializer.Serialize(ref writer, ref surrogate); writer.WriteEndObject(); } else { writer.SerializeUnexpectedType(fieldIdDelta, expectedType, value); } } private bool IsReferenceTrackingSupported => typeof(TField) != typeof(Exception) && !typeof(TField).IsSubclassOf(typeof(Exception)); /// public void Serialize(ref Writer writer, TField value) where TBufferWriter : IBufferWriter { if (_populator is null) ThrowNoPopulatorException(); var surrogate = _converter.ConvertToSurrogate(in value); _surrogateSerializer.Serialize(ref writer, ref surrogate); } /// public void Deserialize(ref Reader reader, TField value) { if (_populator is null) ThrowNoPopulatorException(); TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); _populator.Populate(surrogate, value); } /// public void DeepCopy(TField input, TField output, CopyContext context) { if (_populator is null) ThrowNoPopulatorException(); var surrogate = _converter.ConvertToSurrogate(in input); var copy = _surrogateCopier.DeepCopy(surrogate, context); _populator.Populate(copy, output); } [DoesNotReturn] private void ThrowNoPopulatorException() => throw new NotSupportedException($"Surrogate type {typeof(TConverter)} does not implement {typeof(IPopulator)} and therefore cannot be used in an inheritance hierarchy."); } ================================================ FILE: src/Orleans.Serialization/Serializers/ValueSerializer.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; namespace Orleans.Serialization.Serializers { /// /// Serializer for value types. /// /// The field type. /// The value-type serializer implementation type. public sealed class ValueSerializer : IFieldCodec where TField : struct where TValueSerializer : IValueSerializer { private readonly Type CodecFieldType = typeof(TField); private readonly TValueSerializer _serializer; /// /// Initializes a new instance of the class. /// /// The serializer. public ValueSerializer(TValueSerializer serializer) { _serializer = serializer; } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, CodecFieldType); _serializer.Serialize(ref writer, ref value); writer.WriteEndObject(); } /// public TField ReadValue(ref Reader reader, Field field) { ReferenceCodec.MarkValueField(reader.Session); var value = default(TField); _serializer.Deserialize(ref reader, ref value); return value; } } } ================================================ FILE: src/Orleans.Serialization/Serializers/ValueTypeSurrogateCodec.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Serializers; /// /// Surrogate serializer for . /// /// The type which the implementation of this class supports. /// The surrogate type serialized in place of . /// The converter type which converts between and . public sealed class ValueTypeSurrogateCodec : IFieldCodec, IDeepCopier, IValueSerializer where TField : struct where TSurrogate : struct where TConverter : IConverter { private readonly IValueSerializer _surrogateSerializer; private readonly IDeepCopier _surrogateCopier; private readonly TConverter _converter; /// /// Initializes a new instance of the class. /// /// The surrogate serializer. /// The surrogate copier. /// The surrogate converter. public ValueTypeSurrogateCodec( IValueSerializer surrogateSerializer, IDeepCopier surrogateCopier, TConverter converter) { _surrogateSerializer = surrogateSerializer; _surrogateCopier = surrogateCopier; _converter = converter; } /// public TField DeepCopy(TField input, CopyContext context) { var surrogate = _converter.ConvertToSurrogate(in input); var copy = _surrogateCopier.DeepCopy(surrogate, context); var result = _converter.ConvertFromSurrogate(in copy); return result; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Deserialize(ref Reader reader, scoped ref TField value) { TSurrogate surrogate = default; _surrogateSerializer.Deserialize(ref reader, ref surrogate); value = _converter.ConvertFromSurrogate(in surrogate); } /// public TField ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); TField result = default; Deserialize(ref reader, ref result); return result; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Serialize(ref Writer writer, scoped ref TField value) where TBufferWriter : IBufferWriter { var surrogate = _converter.ConvertToSurrogate(in value); _surrogateSerializer.Serialize(ref writer, ref surrogate); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, TField value) where TBufferWriter : IBufferWriter { ReferenceCodec.MarkValueField(writer.Session); writer.WriteStartObject(fieldIdDelta, expectedType, typeof(TField)); Serialize(ref writer, ref value); writer.WriteEndObject(); } } ================================================ FILE: src/Orleans.Serialization/Session/ReferencedObjectCollection.cs ================================================ using Orleans.Serialization.Codecs; using Orleans.Serialization.Utilities; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; #if NET6_0_OR_GREATER using System.Reflection.Metadata; #endif using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Orleans.Serialization.Session { /// /// A collection of objects which are referenced while serializing, deserializing, or copying. /// public sealed class ReferencedObjectCollection { private struct ReferencePair { public ReferencePair(uint id, object @object) { Id = id; Object = @object; } public uint Id; public object Object; } /// /// Gets or sets the reference to object count. /// /// The reference to object count. internal int ReferenceToObjectCount; private readonly ReferencePair[] _referenceToObject = new ReferencePair[64]; private int _objectToReferenceCount; private readonly ReferencePair[] _objectToReference = new ReferencePair[64]; private Dictionary _referenceToObjectOverflow; private Dictionary _objectToReferenceOverflow; private uint _currentReferenceId; /// /// Tries to get the referenced object with the specified id. /// /// The reference. /// The referenced object with the specified id if found, otherwise. public object TryGetReferencedObject(uint reference) { var refs = _referenceToObject.AsSpan(0, ReferenceToObjectCount); for (int i = 0; i < refs.Length; ++i) { if (refs[i].Id == reference) return refs[i].Object; } if (_referenceToObjectOverflow is { } overflow && overflow.TryGetValue(reference, out var value)) return value; return null; } /// /// Marks a value field. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkValueField() => ++_currentReferenceId; internal uint CreateRecordPlaceholder() => ++_currentReferenceId; /// /// Gets or adds a reference. /// /// The value. /// The reference. /// if a reference already existed, otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GetOrAddReference(object value, out uint reference) { // Unconditionally bump the reference counter since a call to this method signifies a potential reference. var nextReference = ++_currentReferenceId; // Null is always at reference 0 if (value is null) { reference = 0; return true; } var objects = _objectToReference.AsSpan(0, _objectToReferenceCount); for (int i = 0; i < objects.Length; ++i) { if (objects[i].Object == value) { reference = objects[i].Id; return true; } } if (_objectToReferenceOverflow is { } overflow) { #if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(overflow, value, out var exists); if (exists) { reference = refValue; return true; } refValue = nextReference; Unsafe.SkipInit(out reference); return false; #else if (overflow.TryGetValue(value, out var existing)) { reference = existing; return true; } else { overflow[value] = nextReference; Unsafe.SkipInit(out reference); return false; } #endif } // Add the reference. var objectsArray = _objectToReference; var objectsCount = _objectToReferenceCount; if ((uint)objectsCount < (uint)objectsArray.Length) { _objectToReferenceCount = objectsCount + 1; objectsArray[objectsCount].Id = nextReference; objectsArray[objectsCount].Object = value; } else { CreateObjectToReferenceOverflow(value); } Unsafe.SkipInit(out reference); return false; } /// /// Gets the index of the reference, or -1 if the object has not been encountered before. /// /// The value. /// The index of the reference, or -1 if the object has not been encountered before. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetReferenceIndex(object value) { if (value is null) { return -1; } var refs = _referenceToObject.AsSpan(0, ReferenceToObjectCount); for (int i = 0; i < refs.Length; ++i) { if (refs[i].Object == value) { return i; } } return -1; } [MethodImpl(MethodImplOptions.NoInlining)] private void CreateObjectToReferenceOverflow(object value) { var result = new Dictionary(_objectToReferenceCount * 2, ReferenceEqualsComparer.Default); var objects = _objectToReference; for (var i = 0; i < objects.Length; i++) { var record = objects[i]; result[record.Object] = record.Id; objects[i] = default; } result[value] = _currentReferenceId; _objectToReferenceCount = 0; _objectToReferenceOverflow = result; } private void AddToReferences(object value, uint reference) { if (_referenceToObjectOverflow is { } overflow) { #if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(overflow, reference, out var exists); if (exists && value is not UnknownFieldMarker && refValue is not UnknownFieldMarker) { // Unknown field markers can be replaced once the type is known. ThrowReferenceExistsException(reference); } refValue = value; #else if (overflow.TryGetValue(reference, out var existing) && value is not UnknownFieldMarker && existing is not UnknownFieldMarker) { // Unknown field markers can be replaced once the type is known. ThrowReferenceExistsException(reference); } overflow[reference] = value; #endif } else { var refs = _referenceToObject.AsSpan(0, ReferenceToObjectCount); for (var i = 0; i < refs.Length; i++) { if (refs[i].Id == reference) { if (value is not UnknownFieldMarker && refs[i].Object is not UnknownFieldMarker) { // Unknown field markers can be replaced once the type is known. ThrowReferenceExistsException(reference); } refs[i].Object = value; return; } } _referenceToObject[ReferenceToObjectCount++] = new ReferencePair(reference, value); if (ReferenceToObjectCount >= _referenceToObject.Length) { CreateReferenceToObjectOverflow(); } } [MethodImpl(MethodImplOptions.NoInlining)] void CreateReferenceToObjectOverflow() { var result = new Dictionary(ReferenceToObjectCount * 2); var refs = _referenceToObject.AsSpan(0, ReferenceToObjectCount); for (var i = 0; i < refs.Length; i++) { var record = refs[i]; result[record.Id] = record.Object; refs[i] = default; } ReferenceToObjectCount = 0; _referenceToObjectOverflow = result; } } [DoesNotReturn] private static void ThrowReferenceExistsException(uint reference) => throw new InvalidOperationException($"Reference {reference} already exists"); /// /// Records a reference field. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RecordReferenceField(object value) => RecordReferenceField(value, ++_currentReferenceId); /// /// Records a reference field with the specified identifier. /// /// The value. /// The reference identifier. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RecordReferenceField(object value, uint referenceId) { if (value is null) { return; } AddToReferences(value, referenceId); } /// /// Copies the reference table. /// /// A copy of the reference table. public Dictionary CopyReferenceTable() => _referenceToObject.Take(ReferenceToObjectCount).ToDictionary(r => r.Id, r => r.Object); /// /// Copies the identifier table. /// /// A copy of the identifier table. public Dictionary CopyIdTable() => _objectToReference.Take(_objectToReferenceCount).ToDictionary(r => r.Object, r => r.Id); /// /// Gets or sets the current reference identifier. /// /// The current reference identifier. public uint CurrentReferenceId { get => _currentReferenceId; set => _currentReferenceId = value; } /// /// Resets this instance. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _referenceToObject.AsSpan(0, ReferenceToObjectCount).Clear(); _objectToReference.AsSpan(0, _objectToReferenceCount).Clear(); ReferenceToObjectCount = 0; _objectToReferenceCount = 0; CurrentReferenceId = 0; _referenceToObjectOverflow = null; _objectToReferenceOverflow = null; } } } ================================================ FILE: src/Orleans.Serialization/Session/ReferencedTypeCollection.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Orleans.Serialization.Session { /// /// Collection of referenced instances. /// public sealed class ReferencedTypeCollection { private readonly Dictionary _referencedTypes = new(); private readonly Dictionary _referencedTypeToIdMap = new(); private uint _currentReferenceId; /// /// Gets the type with the specified reference id. /// /// The reference id. /// The referenced type. public Type GetReferencedType(uint reference) { if (!_referencedTypes.TryGetValue(reference, out var type)) ThrowUnknownReferencedType(reference); return type; } private static void ThrowUnknownReferencedType(uint id) => throw new UnknownReferencedTypeException(id); /// /// Gets the type with the specified reference id. /// /// The reference id. /// The referenced type. /// if the referenced type was found, otherwise. public bool TryGetReferencedType(uint reference, out Type type) => _referencedTypes.TryGetValue(reference, out type); /// /// Records a type with the specified identifier. /// public void RecordReferencedType(Type type) => _referencedTypes.Add(++_currentReferenceId, type); /// /// Gets the identifier for the specified type. /// /// The type. /// The reference. /// if the type has been previoulsy referenced, otherwise. public bool TryGetTypeReference(Type type, out uint reference) => _referencedTypeToIdMap.TryGetValue(type, out reference); /// /// Gets or adds the identifier for the specified type. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetOrAddTypeReference(Type type) { #if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(_referencedTypeToIdMap, type, out var exists); if (exists) return refValue; refValue = ++_currentReferenceId; #else if (_referencedTypeToIdMap.TryGetValue(type, out var existing)) { return existing; } else { _referencedTypeToIdMap[type] = ++_currentReferenceId; } #endif return 0; } /// /// Resets this instance. /// public void Reset() { _currentReferenceId = 0; _referencedTypes.Clear(); _referencedTypeToIdMap.Clear(); } } } ================================================ FILE: src/Orleans.Serialization/Session/SerializerSession.cs ================================================ using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using System; namespace Orleans.Serialization.Session { /// /// Contextual information for a serializer operation. /// public sealed class SerializerSession : IDisposable { /// /// Initializes a new instance of the class. /// /// The type codec. /// The well known types. /// The codec provider. public SerializerSession(TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes, CodecProvider codecProvider) { TypeCodec = typeCodec; WellKnownTypes = wellKnownTypes; CodecProvider = codecProvider; } /// /// Gets the type codec. /// /// The type codec. public TypeCodec TypeCodec { get; } /// /// Gets the well known types collection. /// /// The well known types collection. public WellKnownTypeCollection WellKnownTypes { get; } /// /// Gets the referenced type collection. /// /// The referenced type collection. public ReferencedTypeCollection ReferencedTypes { get; } = new ReferencedTypeCollection(); /// /// Gets the referenced object collection. /// /// The referenced object collection. public ReferencedObjectCollection ReferencedObjects { get; } = new ReferencedObjectCollection(); /// /// Gets the codec provider. /// /// The codec provider. public CodecProvider CodecProvider { get; } internal Action OnDisposed { get; set; } /// /// Resets the referenced objects collection. /// public void PartialReset() => ReferencedObjects.Reset(); /// /// Performs a full reset. /// public void Reset() { ReferencedObjects.Reset(); ReferencedTypes.Reset(); } public void Dispose() => OnDisposed?.Invoke(this); } } ================================================ FILE: src/Orleans.Serialization/Session/SerializerSessionPool.cs ================================================ using Microsoft.Extensions.ObjectPool; using Orleans.Serialization.Invocation; using Orleans.Serialization.Serializers; using Orleans.Serialization.TypeSystem; using System; namespace Orleans.Serialization.Session { /// /// Pool for objects. /// public sealed class SerializerSessionPool { private readonly ObjectPool _sessionPool; /// /// Initializes a new instance of the class. /// /// The type codec. /// The well known type collection. /// The codec provider. public SerializerSessionPool(TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes, CodecProvider codecProvider) { CodecProvider = codecProvider; var sessionPoolPolicy = new SerializerSessionPoolPolicy(typeCodec, wellKnownTypes, codecProvider, ReturnSession); _sessionPool = new ConcurrentObjectPool(sessionPoolPolicy); } /// /// Gets the codec provider. /// public CodecProvider CodecProvider { get; } /// /// Gets a serializer session from the pool. /// /// A serializer session. public SerializerSession GetSession() => _sessionPool.Get(); /// /// Returns a session to the pool. /// /// The session. private void ReturnSession(SerializerSession session) => _sessionPool.Return(session); private readonly struct SerializerSessionPoolPolicy : IPooledObjectPolicy { private readonly TypeCodec _typeCodec; private readonly WellKnownTypeCollection _wellKnownTypes; private readonly CodecProvider _codecProvider; private readonly Action _onSessionDisposed; public SerializerSessionPoolPolicy(TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes, CodecProvider codecProvider, Action onSessionDisposed) { _typeCodec = typeCodec; _wellKnownTypes = wellKnownTypes; _codecProvider = codecProvider; _onSessionDisposed = onSessionDisposed; } public SerializerSession Create() { return new SerializerSession(_typeCodec, _wellKnownTypes, _codecProvider) { OnDisposed = _onSessionDisposed }; } public bool Return(SerializerSession obj) { obj.Reset(); return true; } } } } ================================================ FILE: src/Orleans.Serialization/Session/WellKnownTypeCollection.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Serialization.Configuration; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Orleans.Serialization.Session { /// /// Collection of well-known types. /// public sealed class WellKnownTypeCollection { private readonly Dictionary _wellKnownTypes; private readonly Dictionary _wellKnownTypeToIdMap; /// /// Initializes a new instance of the class. /// /// The configuration. public WellKnownTypeCollection(IOptions config) { _wellKnownTypes = config?.Value.WellKnownTypeIds ?? throw new ArgumentNullException(nameof(config)); _wellKnownTypeToIdMap = new Dictionary(_wellKnownTypes.Count); foreach (var item in _wellKnownTypes) { _wellKnownTypeToIdMap[item.Value] = item.Key; } } /// /// Gets the type corresponding to the provided type identifier. /// /// The type identifier. /// A type. public Type GetWellKnownType(uint typeId) { if (typeId == 0) { return null; } return _wellKnownTypes[typeId]; } /// /// Tries to get the type corresponding to the provided type identifier. /// /// The type identifier. /// The type. /// if the corresponding type was found, otherwise. public bool TryGetWellKnownType(uint typeId, [NotNullWhen(true)] out Type type) { if (typeId == 0) { type = null; return true; } return _wellKnownTypes.TryGetValue(typeId, out type); } /// /// Tries the get the type identifier corresponding to the provided type. /// /// The type. /// The type identifier. /// if the type has a well-known identifier, otherwise. public bool TryGetWellKnownTypeId(Type type, out uint typeId) => _wellKnownTypeToIdMap.TryGetValue(type, out typeId); } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/CachedTypeResolver.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; namespace Orleans.Serialization.TypeSystem { /// /// Type resolver which caches results. /// public sealed class CachedTypeResolver : TypeResolver { private readonly ConcurrentDictionary _typeCache = new(); private readonly ConcurrentDictionary _assemblyCache = new(); private static readonly ConcurrentDictionary _assemblyNameCache = new(); /// /// Gets the cached assembly name. /// public static string GetName(Assembly assembly) => _assemblyNameCache.GetOrAdd(assembly, a => a.GetName().Name); /// public override Type ResolveType(string name) { if (TryResolveType(name, out var result)) { return result; } throw new TypeAccessException($"Unable to find a type named {name}"); } /// public override bool TryResolveType(string name, out Type type) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("A FullName must not be null nor consist of only whitespace.", nameof(name)); } if (TryGetCachedType(name, out type)) { return true; } if (!TryPerformUncachedTypeResolution(name, out type)) { return false; } AddTypeToCache(name, type); return true; } private bool TryPerformUncachedTypeResolution(string name, out Type type) { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); if (!TryPerformUncachedTypeResolution(name, out type, assemblies)) { return false; } if (type.Assembly.ReflectionOnly) { throw new InvalidOperationException($"Type resolution for {name} yielded reflection-only type."); } return true; } private bool TryGetCachedType(string name, out Type result) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("type name was null or whitespace"); } return _typeCache.TryGetValue(name, out result); } private void AddTypeToCache(string name, Type type) { var entry = _typeCache.GetOrAdd(name, type); if (!ReferenceEquals(entry, type)) { throw new InvalidOperationException("inconsistent type name association"); } } private bool TryPerformUncachedTypeResolution(string fullName, out Type type, Assembly[] assemblies) { if (null == assemblies) { throw new ArgumentNullException(nameof(assemblies)); } if (string.IsNullOrWhiteSpace(fullName)) { throw new ArgumentException("A type name must not be null nor consist of only whitespace.", nameof(fullName)); } foreach (var assembly in assemblies) { type = assembly.GetType(fullName, false); if (type != null) { return true; } } type = Type.GetType(fullName, throwOnError: false); if (type is null) { type = Type.GetType( fullName, ResolveAssembly, ResolveType, false); } return type != null; Assembly ResolveAssembly(AssemblyName assemblyName) { var fullAssemblyName = assemblyName.FullName; if (_assemblyCache.TryGetValue(fullAssemblyName, out var result)) { return result; } foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { _assemblyCache[assembly.FullName] = assembly; _assemblyCache[GetName(assembly)] = assembly; } if (_assemblyCache.TryGetValue(fullAssemblyName, out result)) { return result; } try { result = Assembly.Load(assemblyName); } catch(Exception ex) { throw new TypeLoadException($"Unable to load {fullName} from assembly {fullAssemblyName}", ex); } _assemblyCache[GetName(result)] = result; _assemblyCache[result.FullName] = result; return result; } static Type ResolveType(Assembly asm, string name, bool ignoreCase) { return asm?.GetType(name, throwOnError: false, ignoreCase: ignoreCase) ?? Type.GetType(name, throwOnError: false, ignoreCase: ignoreCase); } } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs ================================================ #nullable enable using System; using System.Collections.Generic; namespace Orleans.Serialization.TypeSystem; /// /// Represents a compound type aliases as a prefix tree. /// public class CompoundTypeAliasTree { private Dictionary? _children; /// /// Initializes a new instance of the class. /// private CompoundTypeAliasTree(object? key, Type? value) { Key = key; Value = value; } /// /// Gets the key for this node. /// public object? Key { get; } /// /// Gets the value for this node. /// public Type? Value { get; private set; } /// /// Creates a new tree with a root node which has no key or value. /// public static CompoundTypeAliasTree Create() => new(default, default); internal CompoundTypeAliasTree? GetChildOrDefault(object key) { TryGetChild(key, out var result); return result; } internal bool TryGetChild(object key, out CompoundTypeAliasTree? result) { if (_children is { } children) { return children.TryGetValue(key, out result); } result = default; return false; } /// /// Adds a node to the tree. /// /// The key for the new node. public CompoundTypeAliasTree Add(Type key) => AddInternal(key); /// /// Adds a node to the tree. /// /// The key for the new node. public CompoundTypeAliasTree Add(string key) => AddInternal(key); /// /// Adds a node to the tree. /// /// The key for the new node. /// The value for the new node. public CompoundTypeAliasTree Add(string key, Type value) => AddInternal(key, value); /// /// Adds a node to the tree. /// /// The key for the new node. /// The value for the new node. public CompoundTypeAliasTree Add(Type key, Type value) => AddInternal(key, value); private CompoundTypeAliasTree AddInternal(object key) => AddInternal(key, default); private CompoundTypeAliasTree AddInternal(object key, Type? value) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(key, nameof(key)); #else if (key is null) throw new ArgumentNullException(nameof(key)); #endif _children ??= new(); if (_children.TryGetValue(key, out var existing)) { if (value is not null && existing.Value is { } type && type != value) { // When the same grain interface is used across multiple assemblies which don't have cross references, // code-gen will generate code for both because it works in isolation, yet at startup they are combined. // In this case, if the key is present, and the value is the same as the one being added, // and due to them being logically the same, we can just return the existing CompoundTypeAliasTree. // The first one is allowed to win in this case. return existing; } existing.Value = value; return existing; } else { return _children[key] = new CompoundTypeAliasTree(key, value); } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/DefaultTypeFilter.cs ================================================ using System; namespace Orleans.Serialization.TypeSystem { /// /// Type which allows any exception type to be resolved. /// public sealed class DefaultTypeFilter : ITypeNameFilter { /// public bool? IsTypeNameAllowed(string typeName, string assemblyName) { if (assemblyName is { } && assemblyName.Contains("Orleans.Serialization", StringComparison.Ordinal)) { return true; } if (typeName.EndsWith(nameof(Exception), StringComparison.Ordinal)) { return true; } if (typeName.StartsWith("System.", StringComparison.Ordinal)) { if (typeName.EndsWith("Comparer", StringComparison.Ordinal)) { return true; } if (typeName.StartsWith("System.Collections.", StringComparison.Ordinal)) { return true; } if (typeName.StartsWith("System.Net.IP", StringComparison.Ordinal)) { return true; } } return null; } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/ITypeConverter.cs ================================================ using System; namespace Orleans.Serialization { /// /// Converts between and representations. /// public interface ITypeConverter { /// /// Formats the provided type as a string. /// bool TryFormat(Type type, out string formatted); /// /// Parses the provided type. /// bool TryParse(string formatted, out Type type); } /// /// Functionality for allowing types to be loaded and to participate in serialization, deserialization, etcetera. /// public interface ITypeNameFilter { /// /// Determines whether the specified type name corresponds to a type which is allowed to be loaded, serialized, deserialized, etcetera. /// /// Name of the type. /// Name of the assembly. /// if the specified type is allowed; if the type is not allowed; if the type is unknown by this filter. bool? IsTypeNameAllowed(string typeName, string assemblyName); } /// /// Functionality for allowing types to be loaded and to participate in serialization, deserialization, etcetera. /// public interface ITypeFilter { /// /// Determines whether the specified type is allowed to be serialized, deserialized, etcetera. /// /// The type /// if the specified type is allowed; if the type is not allowed; if the type is unknown by this filter. bool? IsTypeAllowed(Type type); } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/ITypeResolver.cs ================================================ using System; namespace Orleans.Serialization.TypeSystem { /// /// Provides methods for resolving a from a string. /// public abstract class TypeResolver { /// /// Returns the corresponding to the provided , throwing an exception if resolution fails. /// /// The type name. /// The corresponding to the provided . public abstract Type ResolveType(string name); /// /// Resolves the corresponding to the provided , returning true if resolution succeeded and false otherwise. /// /// The type name. /// The resolved type. /// if resolution succeeded; otherwise. public abstract bool TryResolveType(string name, out Type type); } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/QualifiedType.cs ================================================ #nullable enable using System; using System.Collections.Generic; namespace Orleans.Serialization.TypeSystem { /// /// Represents an assembly-qualifies type. /// public readonly struct QualifiedType { /// /// Gets the equality comparer. /// /// The equality comparer. public static QualifiedTypeEqualityComparer EqualityComparer { get; } = new QualifiedTypeEqualityComparer(); /// /// Initializes a new instance of the struct. /// /// The assembly. /// The type. public QualifiedType(string? assembly, string type) { Assembly = assembly; Type = type; } /// /// Gets the assembly. /// /// The assembly. public string? Assembly { get; } /// /// Gets the type. /// /// The type. public string Type { get; } /// /// Deconstructs this instance. /// /// The assembly. /// The type. public void Deconstruct(out string? assembly, out string type) { assembly = Assembly; type = Type; } /// public override bool Equals(object? obj) => obj is QualifiedType type && string.Equals(Assembly, type.Assembly, StringComparison.Ordinal) && string.Equals(Type, type.Type, StringComparison.Ordinal); /// public override int GetHashCode() => HashCode.Combine(Assembly, Type); /// /// Performs an implicit conversion from to . /// /// The arguments. /// The result of the conversion. public static implicit operator QualifiedType((string Assembly, string Type) args) => new(args.Assembly, args.Type); /// /// Compares two values for equality. /// /// The left. /// The right. /// The result of the operator. public static bool operator ==(QualifiedType left, QualifiedType right) { return left.Equals(right); } /// /// Compares two values for inequality. /// /// The left. /// The right. /// The result of the operator. public static bool operator !=(QualifiedType left, QualifiedType right) { return !(left == right); } /// /// Equality comparer for . /// public sealed class QualifiedTypeEqualityComparer : IEqualityComparer { /// public bool Equals(QualifiedType x, QualifiedType y) => x == y; /// public int GetHashCode(QualifiedType obj) => obj.GetHashCode(); } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/RuntimeTypeNameFormatter.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Text; namespace Orleans.Serialization.TypeSystem; /// /// Utility methods for formatting and instances in a way which can be parsed by /// . /// public static class RuntimeTypeNameFormatter { private static readonly Assembly SystemAssembly = typeof(int).Assembly; private static readonly ConcurrentDictionary Cache = new(); /// /// Returns a form of which can be parsed by . /// /// The type to format. /// /// A form of which can be parsed by . /// public static string Format(Type type) { if (type is null) { ThrowTypeArgumentNull(); } return Cache.GetOrAdd(type, static t => { var builder = new StringBuilder(); Format(builder, t, isElementType: false); return builder.ToString(); }); } [DoesNotReturn] private static void ThrowTypeArgumentNull() => throw new ArgumentNullException("type"); internal static string FormatInternalNoCache(Type type, bool allowAliases) { var builder = new StringBuilder(); Format(builder, type, isElementType: false, allowAliases: allowAliases); return builder.ToString(); } private static CompoundTypeAliasAttribute? GetCompoundTypeAlias(Type type) { var candidateValue = ulong.MaxValue; CompoundTypeAliasAttribute? candidate = null; foreach (var alias in type.GetCustomAttributes()) { if (alias.Components[^1] is string str && ulong.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { // For numeric aliases, arbitrarily pick the one with the lowest value. if (candidate is null || result < candidateValue) { candidate = alias; candidateValue = result; } } else { // If the value is not numeric, then prefer this alias over any numeric ones. return alias; } } return candidate; } private static void Format(StringBuilder builder, Type type, bool isElementType, bool allowAliases = true) { if (allowAliases && GetCompoundTypeAlias(type) is { } compoundAlias) { AddCompoundTypeAlias(builder, type, compoundAlias.Components); AddGenericParameters(builder, type); return; } // Arrays, pointers, and by-ref types are all element types and need to be formatted with their own adornments. if (type.HasElementType) { // Format the element type. Format(builder, type.GetElementType()!, isElementType: true); // Format this type's adornments to the element type. AddArrayRank(builder, type); AddPointerSymbol(builder, type); AddByRefSymbol(builder, type); } else { AddNamespace(builder, type); AddClassName(builder, type); AddGenericParameters(builder, type); } // Types which are used as elements are not formatted with their assembly name, since that is added after the // element type's adornments. if (!isElementType) { AddAssembly(builder, type); } } private static void AddCompoundTypeAlias(StringBuilder builder, Type type, object[] components) { // Start builder.Append('('); for (var i = 0; i < components.Length; ++i) { var component = components[i]; if (i > 0) { builder.Append(','); } // Append the component if (component is string s) { builder.Append($"\"{s}\""); } else if (component is Type t) { if (t == type) { throw new ArgumentException($"Element {i} of argument array, {component}, is equal to the attached type {type}, which is not supported"); } builder.Append('['); Format(builder, t, isElementType: false); builder.Append(']'); } else { throw new ArgumentException($"Element {i} of argument array, {component}, must be a Type or string but is an {component?.GetType().ToString() ?? "null"}"); } } // End builder.Append(')'); if (type.IsGenericType) { int parameterCount = type.IsConstructedGenericType switch { true => type.GenericTypeArguments.Length, false => type.GetTypeInfo().GenericTypeParameters.Length }; builder.Append($"`{parameterCount}"); } } private static void AddNamespace(StringBuilder builder, Type type) { if (string.IsNullOrWhiteSpace(type.Namespace)) { return; } _ = builder.Append(type.Namespace); _ = builder.Append('.'); } private static void AddClassName(StringBuilder builder, Type type) { // Format the declaring type. if (type.IsNested) { AddClassName(builder, type.DeclaringType!); _ = builder.Append('+'); } // Format the simple type name. var name = type.Name.AsSpan(); var index = name.IndexOfAny("`*[&"); _ = builder.Append(index > 0 ? name[..index] : name); // Format this type's generic arity. AddGenericArity(builder, type); } private static void AddGenericParameters(StringBuilder builder, Type type) { // Generic type definitions (eg, List<> without parameters) and non-generic types do not include any // parameters in their formatting. if (!type.IsConstructedGenericType) { return; } var args = type.GetGenericArguments(); _ = builder.Append('['); for (var i = 0; i < args.Length; i++) { _ = builder.Append('['); Format(builder, args[i], isElementType: false); _ = builder.Append(']'); if (i + 1 < args.Length) { _ = builder.Append(','); } } _ = builder.Append(']'); } private static void AddGenericArity(StringBuilder builder, Type type) { if (!type.IsGenericType) { return; } // The arity is the number of generic parameters minus the number of generic parameters in the declaring types. var baseTypeParameterCount = type.IsNested ? type.DeclaringType!.GetGenericArguments().Length : 0; var arity = type.GetGenericArguments().Length - baseTypeParameterCount; // If all of the generic parameters are in the declaring types then this type has no parameters of its own. if (arity == 0) { return; } _ = builder.Append('`'); _ = builder.Append(arity); } private static void AddPointerSymbol(StringBuilder builder, Type type) { if (!type.IsPointer) { return; } _ = builder.Append('*'); } private static void AddByRefSymbol(StringBuilder builder, Type type) { if (!type.IsByRef) { return; } _ = builder.Append('&'); } private static void AddArrayRank(StringBuilder builder, Type type) { if (!type.IsArray) { return; } _ = builder.Append('['); _ = builder.Append(',', type.GetArrayRank() - 1); _ = builder.Append(']'); } private static void AddAssembly(StringBuilder builder, Type type) { // Do not include the assembly name for the system assembly. var assembly = type.Assembly; if (SystemAssembly.Equals(assembly)) { return; } _ = builder.Append(','); _ = builder.Append(CachedTypeResolver.GetName(assembly)); } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/RuntimeTypeNameParser.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace Orleans.Serialization.TypeSystem; /// /// Utility class for parsing type names, as formatted by . /// public static class RuntimeTypeNameParser { internal const int MaxAllowedGenericArity = 64; internal const char CompoundAliasStartIndicator = '('; internal const char CompoundAliasEndIndicator = ')'; internal const char CompoundAliasElementSeparator = ','; internal const char LiteralDelimiter = '"'; internal const char PointerIndicator = '*'; internal const char ReferenceIndicator = '&'; internal const char ArrayStartIndicator = '['; internal const char ArrayDimensionIndicator = ','; internal const char ArrayEndIndicator = ']'; internal const char ParameterSeparator = ','; internal const char GenericTypeIndicator = '`'; internal const char NestedTypeIndicator = '+'; internal const char AssemblyIndicator = ','; internal static ReadOnlySpan LiteralDelimiters => new[] { LiteralDelimiter }; internal static ReadOnlySpan TupleDelimiters => new[] { CompoundAliasElementSeparator, CompoundAliasEndIndicator }; internal static ReadOnlySpan AssemblyDelimiters => new[] { ArrayEndIndicator, CompoundAliasElementSeparator, CompoundAliasEndIndicator }; internal static ReadOnlySpan TypeNameDelimiters => new[] { ArrayStartIndicator, ArrayEndIndicator, PointerIndicator, ReferenceIndicator, AssemblyIndicator, GenericTypeIndicator, NestedTypeIndicator }; /// /// Parse the provided value as a type name. /// /// The input. /// A parsed type specification. public static TypeSpec Parse(string input) => Parse(input.AsSpan()); /// /// Parse the provided value as a type name. /// /// The input. /// A parsed type specification. public static TypeSpec Parse(ReadOnlySpan input) => ParseInternal(ref input); private static TypeSpec ParseInternal(ref ReadOnlySpan input) { BufferReader s = default; s.Input = input; var result = ParseInternal(ref s); input = s.Remaining; return result; } private static TypeSpec ParseInternal(ref BufferReader input) { TypeSpec result; char c; var arity = 0; TypeSpec coreType = null; // Read tuple if (input.TryPeek(out c) && c == CompoundAliasStartIndicator) { input.ConsumeCharacter(CompoundAliasStartIndicator); var elements = new List(); while (input.TryPeek(out c) && c != CompoundAliasEndIndicator) { if (c == CompoundAliasElementSeparator) { input.ConsumeCharacter(CompoundAliasElementSeparator); } input.ConsumeWhitespace(); var element = ParseCompoundTypeAliasElement(ref input); elements.Add(element); } input.ConsumeCharacter(CompoundAliasEndIndicator); var genericArityStart = -1; while (input.TryPeek(out c)) { if (genericArityStart < 0 && c == GenericTypeIndicator) { genericArityStart = input.Index + 1; } else if (genericArityStart < 0 || !char.IsDigit(c)) { break; } input.ConsumeCharacter(c); } if (genericArityStart >= 0) { var aritySlice = input.Input[genericArityStart..input.Index]; #if NETCOREAPP3_1_OR_GREATER arity = int.Parse(aritySlice); #else arity = int.Parse(aritySlice.ToString()); #endif input.TotalGenericArity += arity; if (input.TotalGenericArity > MaxAllowedGenericArity) { ThrowGenericArityTooLarge(input.TotalGenericArity); } } coreType = new TupleTypeSpec([.. elements], input.TotalGenericArity); } else { // Read namespace and class name, including generic arity, which is a part of the class name. NamedTypeSpec named = null; while (true) { var typeName = ParseTypeName(ref input); named = new NamedTypeSpec(named, typeName.ToString(), input.TotalGenericArity); arity = named.Arity; if (input.TryPeek(out c) && c == NestedTypeIndicator) { // Consume the nested type indicator, then loop to parse the nested type. input.ConsumeCharacter(NestedTypeIndicator); continue; } break; } coreType = named; } // Parse generic type parameters if (input.TotalGenericArity > 0 && input.TryPeek(out c, out var d) && c == ArrayStartIndicator && d == ArrayStartIndicator) { input.ConsumeCharacter(ArrayStartIndicator); var arguments = new TypeSpec[input.TotalGenericArity]; for (var i = 0; i < input.TotalGenericArity; i++) { if (i > 0) { input.ConsumeCharacter(ParameterSeparator); } // Parse the argument type input.ConsumeCharacter(ArrayStartIndicator); var remaining = input.Remaining; arguments[i] = ParseInternal(ref remaining); var consumed = input.Remaining.Length - remaining.Length; input.Consume(consumed); input.ConsumeCharacter(ArrayEndIndicator); } input.ConsumeCharacter(ArrayEndIndicator); result = new ConstructedGenericTypeSpec(coreType, arity, arguments); } else { // This is not a constructed generic type result = coreType; } // Parse modifiers bool hadModifier; do { hadModifier = false; if (!input.TryPeek(out c)) { break; } switch (c) { case ArrayStartIndicator: var dimensions = ParseArraySpecifier(ref input); result = new ArrayTypeSpec(result, dimensions); hadModifier = true; break; case PointerIndicator: result = new PointerTypeSpec(result); input.ConsumeCharacter(PointerIndicator); hadModifier = true; break; case ReferenceIndicator: result = new ReferenceTypeSpec(result); input.ConsumeCharacter(ReferenceIndicator); hadModifier = true; break; } } while (hadModifier); // Extract the assembly, if specified. if (input.TryPeek(out c) && c == AssemblyIndicator) { input.ConsumeCharacter(AssemblyIndicator); var assembly = ExtractAssemblySpec(ref input); result = new AssemblyQualifiedTypeSpec(result, assembly.ToString()); } return result; } private static ReadOnlySpan ParseTypeName(ref BufferReader s) { var start = s.Index; var typeName = ParseSpan(ref s, TypeNameDelimiters); var genericArityStart = -1; while (s.TryPeek(out char c)) { if (genericArityStart < 0 && c == GenericTypeIndicator) { genericArityStart = s.Index + 1; } else if (genericArityStart < 0 || !char.IsDigit(c)) { break; } s.ConsumeCharacter(c); } if (genericArityStart >= 0) { // The generic arity is additive, so that a generic class nested in a generic class has an arity // equal to the sum of specified arity values. For example, "C`1+N`2" has an arity of 3. var aritySlice = s.Input[genericArityStart..s.Index]; #if NETCOREAPP3_1_OR_GREATER var arity = int.Parse(aritySlice); #else var arity = int.Parse(aritySlice.ToString()); #endif s.TotalGenericArity += arity; if (s.TotalGenericArity > MaxAllowedGenericArity) { ThrowGenericArityTooLarge(s.TotalGenericArity); } // Include the generic arity in the type name. typeName = s.Input[start..s.Index]; } return typeName; } private static int ParseArraySpecifier(ref BufferReader s) { s.ConsumeCharacter(ArrayStartIndicator); var dimensions = 1; while (s.TryPeek(out var c) && c != ArrayEndIndicator) { s.ConsumeCharacter(ArrayDimensionIndicator); ++dimensions; } s.ConsumeCharacter(ArrayEndIndicator); return dimensions; } private static ReadOnlySpan ExtractAssemblySpec(ref BufferReader s) { s.ConsumeWhitespace(); return ParseSpan(ref s, AssemblyDelimiters); } private static ReadOnlySpan ParseSpan(ref BufferReader s, ReadOnlySpan delimiters) { ReadOnlySpan result; if (s.Remaining.IndexOfAny(delimiters) is int index && index > 0) { result = s.Remaining[..index]; } else { result = s.Remaining; } s.Consume(result.Length); return result; } private static TypeSpec ParseCompoundTypeAliasElement(ref BufferReader input) { char c; // Read literal value if (input.TryPeek(out c)) { if (c == LiteralDelimiter) { input.ConsumeCharacter(LiteralDelimiter); var literalSpan = ParseSpan(ref input, LiteralDelimiters); input.ConsumeCharacter(LiteralDelimiter); return new LiteralTypeSpec(new string(literalSpan)); } else if (c == ArrayStartIndicator) { // Parse the argument type input.ConsumeCharacter(ArrayStartIndicator); var remaining = input.Remaining; var result = ParseInternal(ref remaining); var consumed = input.Remaining.Length - remaining.Length; input.Consume(consumed); input.ConsumeCharacter(ArrayEndIndicator); return result; } throw new ArgumentException($"Unexpected token '{c}' when reading compound type alias element.", nameof(input)); } else { throw new IndexOutOfRangeException("Attempted to read past the end of the input buffer."); } } private static void ThrowGenericArityTooLarge(int arity) => throw new NotSupportedException($"An arity of {arity} is not supported."); private ref struct BufferReader { public ReadOnlySpan Input; public int Index; public int TotalGenericArity; public readonly ReadOnlySpan Remaining => Input[Index..]; public readonly bool TryPeek(out char c) { if (Index < Input.Length) { c = Input[Index]; return true; } c = default; return false; } public readonly bool TryPeek(out char c, out char d) { var result = TryPeek(out c); result &= TryPeek(Index + 1, out d); return result; } public readonly bool TryPeek(int index, out char c) { if (index < Input.Length) { c = Input[index]; return true; } c = default; return false; } public void Consume(int chars) { if (Index < Input.Length) { Index += chars; return; } ThrowEndOfInput(); } public void ConsumeCharacter(char assertChar) { if (Index < Input.Length) { var c = Input[Index]; if (assertChar != c) { ThrowUnexpectedCharacter(Input, Index, assertChar, c); } ++Index; return; } ThrowEndOfInput(); } public void ConsumeWhitespace() { while (char.IsWhiteSpace(Input[Index])) { ++Index; } } private static void ThrowUnexpectedCharacter(ReadOnlySpan value, int position, char expected, char actual) { var valueString = new string(value); var posString = position > 0 ? new string(' ', position) : ""; var message = $"Encountered unexpected character. Expected '{expected}', actual '{actual}' in string:\n> {valueString}\n> {posString}^"; throw new InvalidOperationException(message); } private static void ThrowEndOfInput() => throw new InvalidOperationException("Tried to read past the end of the input"); public override readonly string ToString() { var result = new StringBuilder(); var i = 0; foreach (var c in Input) { if (i == Index) { result.Append("^^^"); } result.Append(c); if (i == Index) { result.Append("^^^"); } ++i; } return result.ToString(); } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/RuntimeTypeNameRewriter.cs ================================================ #nullable enable using System; namespace Orleans.Serialization.TypeSystem; /// /// Rewrites graphs. /// internal static class RuntimeTypeNameRewriter { /// /// Signature for a delegate which rewrites a . /// /// The input. /// The state provided to the rewriter method. /// The rewritten qualified type. public delegate QualifiedType Rewriter(in QualifiedType input, ref TState state); /// /// Signature for a delegate which resolves a compound type alias. /// /// The input. /// The state provided to the resolve method. /// The resolved type type. public delegate TypeSpec CompoundAliasResolver(TupleTypeSpec input, ref TState state); /// /// Rewrites a using the provided rewriter delegate. /// public static TypeSpec Rewrite(TypeSpec input, Rewriter rewriter, ref TState state) { var instance = new TypeRewriter(rewriter, null, ref state); var result = instance.Rewrite(input); state = instance._userState; return result; } /// /// Rewrites a using the provided rewriter delegate. /// public static TypeSpec Rewrite(TypeSpec input, Rewriter rewriter, CompoundAliasResolver? compoundAliasRewriter, ref TState state) { var instance = new TypeRewriter(rewriter, compoundAliasRewriter, ref state); var result = instance.Rewrite(input); state = instance._userState; return result; } private struct TypeRewriter { private readonly Rewriter _nameRewriter { get; } private readonly CompoundAliasResolver? _compoundTypeRewriter { get; } public TState _userState; public TypeRewriter(Rewriter nameRewriter, CompoundAliasResolver? compoundTypeRewriter, ref TState initialUserState) { _nameRewriter = nameRewriter; _compoundTypeRewriter = compoundTypeRewriter; _userState = initialUserState; } public TypeSpec Rewrite(TypeSpec input) { var result = ApplyInner(input, null); if (result.Assembly is not null) { // If the rewriter bubbled up an assembly, add it here. return new AssemblyQualifiedTypeSpec(result.Type, result.Assembly); } return result.Type; } private (TypeSpec Type, string? Assembly) ApplyInner(TypeSpec input, string? assemblyName) => // A type's assembly is passed downwards through the graph, and modifications to the assembly (from the user-provided delegate) flow upwards. input switch { ConstructedGenericTypeSpec type => HandleGeneric(type, assemblyName), NamedTypeSpec type => HandleNamedType(type, assemblyName), AssemblyQualifiedTypeSpec type => HandleAssembly(type, assemblyName), ArrayTypeSpec type => HandleArray(type, assemblyName), PointerTypeSpec type => HandlePointer(type, assemblyName), ReferenceTypeSpec type => HandleReference(type, assemblyName), TupleTypeSpec type => HandleCompoundType(type, assemblyName), LiteralTypeSpec type => (type, assemblyName), null => throw new ArgumentNullException(nameof(input)), _ => throw new NotSupportedException($"Argument of type {input.GetType()} is nut supported"), }; private (TypeSpec Type, string? Assembly) HandleGeneric(ConstructedGenericTypeSpec type, string? assemblyName) { var (unconstructed, replacementAssembly) = ApplyInner(type.UnconstructedType, assemblyName); if (unconstructed is AssemblyQualifiedTypeSpec assemblyQualified) { unconstructed = assemblyQualified.Type; replacementAssembly = assemblyQualified.Assembly; } var newArguments = new TypeSpec[type.Arguments.Length]; var didChange = false; for (var i = 0; i < type.Arguments.Length; i++) { // Generic type parameters do not inherit the assembly of the generic type. var args = ApplyInner(type.Arguments[i], null); if (args.Assembly is not null) { newArguments[i] = new AssemblyQualifiedTypeSpec(args.Type, args.Assembly); } else { newArguments[i] = args.Type; } didChange |= !ReferenceEquals(newArguments[i], type.Arguments[i]); } if (ReferenceEquals(type.UnconstructedType, unconstructed) && !didChange) { return (type, replacementAssembly); } return (new ConstructedGenericTypeSpec(unconstructed, newArguments.Length, newArguments), replacementAssembly); } private (TypeSpec Type, string? Assembly) HandleNamedType(NamedTypeSpec type, string? assemblyName) { var nsQualified = type.GetNamespaceQualifiedName(); var replacementName = _nameRewriter(new QualifiedType(assembly: assemblyName, type: nsQualified), ref _userState); if (!string.Equals(nsQualified, replacementName.Type, StringComparison.Ordinal)) { // Change the type name and potentially the assembly. var resultType = RuntimeTypeNameParser.Parse(replacementName.Type); if (!(resultType is NamedTypeSpec)) { throw new InvalidOperationException($"Replacement type name, \"{replacementName.Type}\", can not deviate from the original type structure of the input, \"{nsQualified}\""); } return (resultType, replacementName.Assembly); } else if (!string.Equals(assemblyName, replacementName.Assembly, StringComparison.Ordinal)) { // Only change the assembly; return (type, replacementName.Assembly); } else if (type.ContainingType is not null) { // Give the user an opportunity to change the parent, including the assembly. var replacementParent = ApplyInner(type.ContainingType, assemblyName); if (ReferenceEquals(replacementParent.Type, type.ContainingType)) { // No change to the type. return (type, replacementParent.Assembly); } // The parent type changed. var typedReplacement = (NamedTypeSpec)replacementParent.Type; return (new NamedTypeSpec(typedReplacement, type.Name, type.Arity), replacementParent.Assembly); } else { return (type, replacementName.Assembly); } } private (TypeSpec Type, string? Assembly) HandleAssembly(AssemblyQualifiedTypeSpec type, string? assemblyName) { var replacement = ApplyInner(type.Type, type.Assembly); // Assembly name changes never bubble up past the assembly qualifier node. if (string.IsNullOrWhiteSpace(replacement.Assembly)) { // Remove the assembly qualification return (replacement.Type, assemblyName); } else if (!string.Equals(replacement.Assembly, type.Assembly) || !ReferenceEquals(replacement.Type, type.Type)) { // Update the assembly or the type. return (new AssemblyQualifiedTypeSpec(replacement.Type, replacement.Assembly), assemblyName); } // No update. return (type, assemblyName); } private (TypeSpec Type, string? Assembly) HandleArray(ArrayTypeSpec type, string? assemblyName) { var element = ApplyInner(type.ElementType, assemblyName); if (ReferenceEquals(element.Type, type.ElementType)) { return (type, element.Assembly); } return (new ArrayTypeSpec(element.Type, type.Dimensions), element.Assembly); } private (TypeSpec Type, string? Assembly) HandleReference(ReferenceTypeSpec type, string? assemblyName) { var element = ApplyInner(type.ElementType, assemblyName); if (ReferenceEquals(element.Type, type.ElementType)) { return (type, element.Assembly); } return (new ReferenceTypeSpec(element.Type), element.Assembly); } private (TypeSpec Type, string? Assembly) HandlePointer(PointerTypeSpec type, string? assemblyName) { var element = ApplyInner(type.ElementType, assemblyName); if (ReferenceEquals(element.Type, type.ElementType)) { return (type, element.Assembly); } return (new PointerTypeSpec(element.Type), element.Assembly); } private (TypeSpec Type, string? Assembly) HandleCompoundType(TupleTypeSpec type, string? assemblyName) { var elements = new TypeSpec[type.Elements.Length]; for (var i = 0; i < type.Elements.Length; i++) { (elements[i], _) = ApplyInner(type.Elements[i], assemblyName); } // Resolve the compound type alias after first trying to resolve each of the individual elements. var replacementTypeSpec = new TupleTypeSpec(elements, type.Arity); if (_compoundTypeRewriter is { } rewriter) { var resolved = rewriter(replacementTypeSpec, ref _userState); if (resolved is TupleTypeSpec) { throw new InvalidOperationException($"Compound type alias resolver resolved {type} into {resolved} which is also an alias type."); } // Give the rewriter a chance to rewrite the resolved name. return ApplyInner(resolved, assemblyName); } return (replacementTypeSpec, assemblyName); } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/TypeCodec.cs ================================================ using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.IO.Hashing; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Orleans.Serialization.Buffers; namespace Orleans.Serialization.TypeSystem { /// /// Functionality for serializing and deserializing types. /// public sealed class TypeCodec { private const byte Version1 = 1; private readonly ConcurrentDictionary _typeCache = new ConcurrentDictionary(); private readonly ConcurrentDictionary _typeKeyCache = new ConcurrentDictionary(); private readonly TypeConverter _typeConverter; private readonly Func _getTypeKey; /// /// Initializes a new instance of the class. /// /// The type converter. public TypeCodec(TypeConverter typeConverter) { _typeConverter = typeConverter; _getTypeKey = type => new TypeKey(Encoding.UTF8.GetBytes(_typeConverter.Format(type))); } /// /// Writes a type with a length-prefix. /// /// The buffer writer type. /// The writer. /// The type. public void WriteLengthPrefixed(ref Writer writer, Type type) where TBufferWriter : IBufferWriter { var key = _typeCache.GetOrAdd(type, _getTypeKey); writer.WriteVarUInt32((uint)key.TypeName.Length); writer.Write(key.TypeName); } /// /// Writes a type. /// /// The buffer writer type. /// The writer. /// The type. public void WriteEncodedType(ref Writer writer, Type type) where TBufferWriter : IBufferWriter { var key = _typeCache.GetOrAdd(type, _getTypeKey); writer.WriteByte(Version1); writer.WriteInt32(key.HashCode); writer.WriteVarUInt32((uint)key.TypeName.Length); writer.Write(key.TypeName); } /// /// Reads a type. /// /// The reader input type. /// The reader. /// The type if it was successfully read, otherwise. public unsafe Type TryRead(ref Reader reader) { var version = reader.ReadByte(); if (version != Version1) { ThrowUnsupportedVersion(version); } var hashCode = reader.ReadInt32(); var count = (int)reader.ReadVarUInt32(); if (!reader.TryReadBytes(count, out var typeName)) { typeName = reader.ReadBytes((uint)count); } // Search through var candidateHashCode = hashCode; while (_typeKeyCache.TryGetValue(candidateHashCode, out var entry)) { var existingKey = entry.Key; if (existingKey.HashCode != hashCode) { break; } if (existingKey.TypeName.AsSpan().SequenceEqual(typeName)) { return entry.Type; } // Try the next entry. ++candidateHashCode; } // Allocate a string for the type name. string typeNameString; fixed (byte* typeNameBytes = typeName) { typeNameString = Encoding.UTF8.GetString(typeNameBytes, typeName.Length); } if (_typeConverter.TryParse(typeNameString, out var type)) { var key = new TypeKey(hashCode, typeName.ToArray()); while (!_typeKeyCache.TryAdd(candidateHashCode++, (key, type))) { // Insert the type at the first available position. } } return type; } /// /// Reads a length-prefixed type. /// /// The reader input type. /// The reader. /// Type. public unsafe Type ReadLengthPrefixed(ref Reader reader) { var count = (int)reader.ReadVarUInt32(); if (!reader.TryReadBytes(count, out var typeName)) { typeName = reader.ReadBytes((uint)count); } // Allocate a string for the type name. string typeNameString; fixed (byte* typeNameBytes = typeName) { typeNameString = Encoding.UTF8.GetString(typeNameBytes, typeName.Length); } var type = _typeConverter.Parse(typeNameString); return type; } /// /// Tries to read a type for diagnostics purposes. /// /// The reader input type. /// The reader. /// The type. /// The type name as a string. /// if a type was successfully read, otherwise. public unsafe bool TryReadForAnalysis(ref Reader reader, [NotNullWhen(true)] out Type type, out string typeString) { var version = reader.ReadByte(); var hashCode = reader.ReadInt32(); var count = (int)reader.ReadVarUInt32(); if (!reader.TryReadBytes(count, out var typeName)) { typeName = reader.ReadBytes((uint)count); } // Allocate a string for the type name. string typeNameString; fixed (byte* typeNameBytes = typeName) { typeNameString = Encoding.UTF8.GetString(typeNameBytes, count); } _ = _typeConverter.TryParse(typeNameString, out type); var key = new TypeKey(hashCode, typeName.ToArray()); typeString = key.ToString(); return type is not null; } [DoesNotReturn] private static void ThrowUnsupportedVersion(byte version) => throw new NotSupportedException($"Type encoding version {version} is not supported"); /// /// Represents a named type for the purposes of serialization. /// internal readonly struct TypeKey { public readonly int HashCode; public readonly byte[] TypeName; public TypeKey(int hashCode, byte[] key) { HashCode = hashCode; TypeName = key; } public TypeKey(byte[] key) { TypeName = key; Unsafe.SkipInit(out int hash); XxHash32.TryHash(key, MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref hash, 1)), out _); HashCode = BitConverter.IsLittleEndian ? hash : BinaryPrimitives.ReverseEndianness(hash); } public bool Equals(in TypeKey other) { if (HashCode != other.HashCode) { return false; } var a = TypeName; var b = other.TypeName; return ReferenceEquals(a, b) || a.AsSpan().SequenceEqual(b); } public override bool Equals(object obj) => obj is TypeKey key && Equals(key); public override int GetHashCode() => HashCode; public override string ToString() => $"TypeName \"{Encoding.UTF8.GetString(TypeName)}\" (hash {HashCode:X8})"; } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/TypeConverter.cs ================================================ using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using Microsoft.Extensions.Options; using Orleans.Serialization.Activators; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; namespace Orleans.Serialization.TypeSystem { /// /// Formats and parses instances using configured rules. /// public class TypeConverter { private readonly ITypeConverter[] _converters; private readonly ITypeNameFilter[] _typeNameFilters; private readonly ITypeFilter[] _typeFilters; private readonly bool _allowAllTypes; private readonly CompoundTypeAliasTree _compoundTypeAliases; private readonly TypeResolver _resolver; private readonly RuntimeTypeNameRewriter.Rewriter _convertToDisplayName; private readonly RuntimeTypeNameRewriter.Rewriter _convertFromDisplayName; private readonly RuntimeTypeNameRewriter.CompoundAliasResolver _compoundAliasResolver; private readonly Dictionary _wellKnownAliasToType; private readonly Dictionary _wellKnownTypeToAlias; private readonly ConcurrentDictionary _allowedTypes; private readonly HashSet _allowedTypesConfiguration; private static readonly List<(string DisplayName, string RuntimeName)> WellKnownTypeAliases = new() { ("object", "System.Object"), ("string", "System.String"), ("char", "System.Char"), ("sbyte", "System.SByte"), ("byte", "System.Byte"), ("bool", "System.Boolean"), ("short", "System.Int16"), ("ushort", "System.UInt16"), ("int", "System.Int32"), ("uint", "System.UInt32"), ("long", "System.Int64"), ("ulong", "System.UInt64"), ("float", "System.Single"), ("double", "System.Double"), ("decimal", "System.Decimal"), ("Guid", "System.Guid"), ("TimeSpan", "System.TimeSpan"), ("DateTime", "System.DateTime"), ("DateTimeOffset", "System.DateTimeOffset"), ("Type", "System.Type"), }; /// /// Initializes a new instance of the class. /// /// The type name formatters. /// The type name filters. /// The type filters. /// The options. /// The type resolver. public TypeConverter( IEnumerable formatters, IEnumerable typeNameFilters, IEnumerable typeFilters, IOptions options, TypeResolver typeResolver) { _resolver = typeResolver; _converters = formatters.ToArray(); _typeNameFilters = typeNameFilters.ToArray(); _typeFilters = typeFilters.ToArray(); _allowAllTypes = options.Value.AllowAllTypes; _compoundTypeAliases = options.Value.CompoundTypeAliases; _convertToDisplayName = ConvertToDisplayName; _convertFromDisplayName = ConvertFromDisplayName; _compoundAliasResolver = ResolveCompoundAliasType; _wellKnownAliasToType = new Dictionary(); _wellKnownTypeToAlias = new Dictionary(); _allowedTypes = new ConcurrentDictionary(QualifiedType.EqualityComparer); _allowedTypesConfiguration = new(StringComparer.Ordinal); if (!_allowAllTypes) { foreach (var t in options.Value.AllowedTypes) { _allowedTypesConfiguration.Add(t); } ConsumeMetadata(options.Value); } var aliases = options.Value.WellKnownTypeAliases; foreach (var item in aliases) { var alias = new QualifiedType(null, item.Key); var spec = RuntimeTypeNameParser.Parse(RuntimeTypeNameFormatter.Format(item.Value)); string asmName = null; if (spec is AssemblyQualifiedTypeSpec asm) { asmName = asm.Assembly; spec = asm.Type; } var originalQualifiedType = new QualifiedType(asmName, spec.Format()); _wellKnownTypeToAlias[originalQualifiedType] = alias; if (asmName is { Length: > 0 }) { _wellKnownTypeToAlias[new QualifiedType(null, spec.Format())] = alias; } _wellKnownAliasToType[alias] = originalQualifiedType; } } private void ConsumeMetadata(TypeManifestOptions metadata) { AddFromMetadata(metadata.Serializers, typeof(IBaseCodec<>)); AddFromMetadata(metadata.Serializers, typeof(IValueSerializer<>)); AddFromMetadata(metadata.Serializers, typeof(IFieldCodec<>)); AddFromMetadata(metadata.FieldCodecs, typeof(IFieldCodec<>)); AddFromMetadata(metadata.Activators, typeof(IActivator<>)); AddFromMetadata(metadata.Copiers, typeof(IDeepCopier<>)); AddFromMetadata(metadata.Converters, typeof(IConverter<,>)); foreach (var type in metadata.InterfaceProxies) { AddAllowedType(type switch { { IsGenericType: true } => type.GetGenericTypeDefinition(), _ => type }); } void AddFromMetadata(HashSet metadataCollection, Type genericType) { Debug.Assert(genericType.GetGenericArguments().Length >= 1); foreach (var type in metadataCollection) { var interfaces = type.GetInterfaces(); foreach (var @interface in interfaces) { if (!@interface.IsGenericType) { continue; } if (genericType != @interface.GetGenericTypeDefinition()) { continue; } foreach (var genericArgument in @interface.GetGenericArguments()) { InspectGenericArgument(genericArgument); } } } } void InspectGenericArgument(Type genericArgument) { if (typeof(object) == genericArgument) { return; } if (genericArgument.IsConstructedGenericType && Array.Exists(genericArgument.GenericTypeArguments, arg => arg.IsGenericParameter)) { genericArgument = genericArgument.GetGenericTypeDefinition(); } if (genericArgument.IsGenericParameter || genericArgument.IsArray) { return; } AddAllowedType(genericArgument); } void AddAllowedType(Type type) { FormatAndAddAllowedType(type); if (type.DeclaringType is { } declaring) { AddAllowedType(declaring); } foreach (var @interface in type.GetInterfaces()) { FormatAndAddAllowedType(@interface); } } void FormatAndAddAllowedType(Type type) { var formatted = RuntimeTypeNameFormatter.Format(type); var parsed = RuntimeTypeNameParser.Parse(formatted); // Use the type name rewriter to visit every component of the type. var converter = this; _ = RuntimeTypeNameRewriter.Rewrite(parsed, AddQualifiedType, ResolveCompoundAliasType, ref converter); static QualifiedType AddQualifiedType(in QualifiedType type, ref TypeConverter self) { self._allowedTypes[type] = true; return type; } } } /// /// Formats the provided type. /// /// The type. /// Whether all types are allowed or not. /// The formatted type name. public string Format(Type type, bool allowAllTypes = false) => FormatInternal(type); /// /// Formats the provided type, rewriting elements using the provided delegate. /// /// The type. /// A delegate used to rewrite the type. /// Whether all types are allowed or not. /// The formatted type name. public string Format(Type type, Func rewriter, bool allowAllTypes = false) => FormatInternal(type, rewriter); /// /// Parses the provided type string. /// /// The formatted type name. /// The parsed type. /// Unable to load the resulting type. public Type Parse(string formatted) { if (ParseInternal(formatted, out var type)) { return type; } throw new TypeLoadException($"Unable to parse or load type \"{formatted}\""); } /// /// Parses the provided type string. /// /// The formatted type name. /// The result. /// if the type was parsed and loaded; otherwise . public bool TryParse(string formatted, [NotNullWhen(true)] out Type result) { return ParseInternal(formatted, out result); } private string FormatInternal(Type type, Func rewriter = null) { string runtimeType = null; foreach (var converter in _converters) { if (converter.TryFormat(type, out var value)) { runtimeType = value; break; } } if (string.IsNullOrWhiteSpace(runtimeType)) { runtimeType = RuntimeTypeNameFormatter.Format(type); } var runtimeTypeSpec = RuntimeTypeNameParser.Parse(runtimeType); ValidationResult validationState = default; var displayTypeSpec = RuntimeTypeNameRewriter.Rewrite(runtimeTypeSpec, _convertToDisplayName, compoundAliasRewriter: null, ref validationState); if (rewriter is not null) { displayTypeSpec = rewriter(displayTypeSpec); } var formatted = displayTypeSpec.Format(); if (validationState.IsTypeNameAllowed == false) { ThrowTypeNotAllowed(formatted, validationState.ErrorTypes); } if (!_allowAllTypes && validationState.IsTypeNameAllowed != true) { if (InspectType(_typeFilters, type) == false) { ThrowTypeNotAllowed(type); } } return formatted; } private bool ParseInternal(string formatted, out Type type) { var parsed = RuntimeTypeNameParser.Parse(formatted); return ParseInternal(parsed, out type); } private bool ParseInternal(TypeSpec parsed, out Type type) { ValidationResult validationState = default; var runtimeTypeSpec = RuntimeTypeNameRewriter.Rewrite(parsed, _convertFromDisplayName, _compoundAliasResolver, ref validationState); var runtimeType = runtimeTypeSpec.Format(); if (validationState.IsTypeNameAllowed == false) { ThrowTypeNotAllowed(parsed.Format(), validationState.ErrorTypes); } foreach (var converter in _converters) { if (converter.TryParse(runtimeType, out type)) { return true; } } if (_resolver.TryResolveType(runtimeType, out type)) { if (!_allowAllTypes && validationState.IsTypeNameAllowed != true) { if (InspectType(_typeFilters, type) == false) { ThrowTypeNotAllowed(type); } } return true; } return false; } private bool? IsNameTypeAllowed(in QualifiedType type) { if (_allowAllTypes) { return true; } if (_allowedTypes.TryGetValue(type, out var allowed)) { return allowed; } foreach (var (displayName, runtimeName) in WellKnownTypeAliases) { if (displayName.Equals(type.Type) || runtimeName.Equals(type.Type)) { return true; } } if (_allowedTypesConfiguration.Contains(type.Type)) { return true; } foreach (var filter in _typeNameFilters) { var isAllowed = filter.IsTypeNameAllowed(type.Type, type.Assembly); if (isAllowed.HasValue) { allowed = _allowedTypes[type] = isAllowed.Value; return allowed; } } return null; } private QualifiedType ConvertToDisplayName(in QualifiedType input, ref ValidationResult state) { state = UpdateValidationResult(input, state); foreach (var (displayName, runtimeName) in WellKnownTypeAliases) { if (string.Equals(input.Type, runtimeName, StringComparison.OrdinalIgnoreCase)) { return new QualifiedType(null, displayName); } } if (_wellKnownTypeToAlias.TryGetValue(input, out var alias)) { return alias; } return input; } private QualifiedType ConvertFromDisplayName(in QualifiedType input, ref ValidationResult state) { state = UpdateValidationResult(input, state); foreach (var (displayName, runtimeName) in WellKnownTypeAliases) { if (string.Equals(input.Type, displayName, StringComparison.OrdinalIgnoreCase)) { return new QualifiedType(null, runtimeName); } } if (_wellKnownAliasToType.TryGetValue(input, out var type)) { return type; } return input; } private ValidationResult UpdateValidationResult(QualifiedType input, ValidationResult state) { // If there has not been an error yet, inspect this type to ensure it is allowed. if (IsNameTypeAllowed(input) is bool allowed) { var newAllowed = allowed && (state.IsTypeNameAllowed ?? true); var newErrorList = state.ErrorTypes ?? new List(); if (!allowed) { newErrorList.Add(input); } return new(newAllowed, newErrorList); } return state; } [DoesNotReturn] private static void ThrowTypeNotAllowed(string fullTypeName, List errors) { if (errors is { Count: 1 }) { var value = errors[0]; if (!string.IsNullOrWhiteSpace(value.Assembly)) { throw new InvalidOperationException($"Type \"{value.Type}\" from assembly \"{value.Assembly}\" is not allowed. To allow it, add it to {nameof(TypeManifestOptions)}.{nameof(TypeManifestOptions.AllowedTypes)} or register an {nameof(ITypeNameFilter)} instance which allows it."); } else { throw new InvalidOperationException($"Type \"{value.Type}\" is not allowed. To allow it, add it to {nameof(TypeManifestOptions)}.{nameof(TypeManifestOptions.AllowedTypes)} or register an {nameof(ITypeNameFilter)} instance which allows it."); } } StringBuilder message = new($"Some types in the type string \"{fullTypeName}\" are not allowed by configuration. To allow them, add them to {nameof(TypeManifestOptions)}.{nameof(TypeManifestOptions.AllowedTypes)} or register an {nameof(ITypeNameFilter)} instance which allows them."); foreach (var value in errors) { if (!string.IsNullOrWhiteSpace(value.Assembly)) { message.AppendLine($"Type \"{value.Type}\" from assembly \"{value.Assembly}\""); } else { message.AppendLine($"Type \"{value.Type}\""); } } throw new InvalidOperationException(message.ToString()); } [DoesNotReturn] private static void ThrowTypeNotAllowed(Type value) { var message = $"Type \"{value.FullName}\" is not allowed. To allow it, add it to {nameof(TypeManifestOptions)}.{nameof(TypeManifestOptions.AllowedTypes)} or register an {nameof(ITypeNameFilter)} or {nameof(ITypeFilter)} instance which allows it."; throw new InvalidOperationException(message); } private readonly struct ValidationResult { public ValidationResult(bool? isTypeNameAllowed, List errorTypes) { IsTypeNameAllowed = isTypeNameAllowed; ErrorTypes = errorTypes; } public bool? IsTypeNameAllowed { get; } public List ErrorTypes { get; } } private static bool? InspectType(ITypeFilter[] filters, Type type) { bool? result = null; if (type.HasElementType) { result = Combine(result, InspectType(filters, type.GetElementType())); return result; } foreach (var filter in filters) { result = Combine(result, filter.IsTypeAllowed(type)); if (result == false) { return false; } } if (type.IsConstructedGenericType) { foreach (var parameter in type.GenericTypeArguments) { result = Combine(result, InspectType(filters, parameter)); if (result == false) { return false; } } } return result; static bool? Combine(bool? left, bool? right) { if (left == false || right == false) { return false; } else if (left == true || right == true) { return true; } return null; } } private TypeSpec ResolveCompoundAliasType(TupleTypeSpec input, ref TState state) { var resolvedElements = new object[input.Elements.Length]; for (var i = 0; i < input.Elements.Length; i++) { var inputElement = input.Elements[i]; if (inputElement is LiteralTypeSpec literal) { resolvedElements[i] = literal.Value; } else { if (!ParseInternal(inputElement, out var type)) { throw new TypeLoadException($"Unable to parse or load type \"{inputElement.Format()}\"."); } resolvedElements[i] = type; } } var tree = _compoundTypeAliases; foreach (var element in resolvedElements) { tree = tree?.GetChildOrDefault(element); if (tree is null) break; } var resultType = tree?.Value; if (resultType is null) { throw new TypeLoadException($"Unable to resolve type alias \"{input.Format()}\"."); } var formatted = RuntimeTypeNameFormatter.FormatInternalNoCache(resultType, allowAliases: false); var parsed = RuntimeTypeNameParser.Parse(formatted); return parsed; } } } ================================================ FILE: src/Orleans.Serialization/TypeSystem/TypeSpec.cs ================================================ #nullable enable using System; using System.Linq; using System.Text; namespace Orleans.Serialization.TypeSystem { /// /// Represents a type. /// public abstract class TypeSpec { /// /// Formats this instance in a way that can be parsed by . /// public abstract string Format(); } /// /// Represents a pointer (*) type. /// public class PointerTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The element type. public PointerTypeSpec(TypeSpec elementType) { if (elementType is null) { throw new ArgumentNullException(nameof(elementType)); } ElementType = elementType; } /// /// Gets the element type. /// public TypeSpec ElementType { get; } /// public override string Format() => ToString(); /// public override string ToString() => $"{ElementType}*"; } /// /// Represents a reference (&) type. /// public class ReferenceTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The element type. public ReferenceTypeSpec(TypeSpec elementType) { if (elementType is null) { throw new ArgumentNullException(nameof(elementType)); } ElementType = elementType; } /// /// Gets the element type. /// public TypeSpec ElementType { get; } /// public override string Format() => ToString(); /// public override string ToString() => $"{ElementType}&"; } /// /// Represents an array type. /// public class ArrayTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The array element type. /// The number of dimensions for the array. public ArrayTypeSpec(TypeSpec elementType, int dimensions) { if (elementType is null) { throw new ArgumentNullException(nameof(elementType)); } if (dimensions <= 0) { throw new ArgumentOutOfRangeException($"An array cannot have a dimension count of {dimensions}"); } ElementType = elementType; Dimensions = dimensions; } /// /// Gets the number of array dimensions. /// public int Dimensions { get; } /// /// Gets the element type. /// public TypeSpec ElementType { get; } /// public override string Format() => ToString(); /// public override string ToString() => $"{ElementType}[{new string(',', Dimensions - 1)}]"; } /// /// Represents an constructed generic type. /// public class ConstructedGenericTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The unconstructed type. /// The expected number of type arguments. /// The generic type arguments. public ConstructedGenericTypeSpec(TypeSpec unconstructedType, int arity, TypeSpec[] arguments) { if (unconstructedType is AssemblyQualifiedTypeSpec) { throw new InvalidOperationException(); } if (unconstructedType is null) { throw new ArgumentNullException(nameof(unconstructedType)); } if (arguments is null) { throw new ArgumentNullException(nameof(arguments)); } if (arity != arguments.Length) { throw new ArgumentException($"Invalid number of arguments {arguments.Length} provided while constructing generic type of arity {arity}: {unconstructedType}", nameof(arguments)); } foreach (var arg in arguments) { if (arg is null) { throw new ArgumentNullException(nameof(arguments), "Cannot construct a generic type using a null argument"); } } UnconstructedType = unconstructedType; Arguments = arguments; } /// /// Gets the unconstructed type. /// public TypeSpec UnconstructedType { get; } /// /// Gets the type arguments. /// public TypeSpec[] Arguments { get; } /// public override string Format() => ToString(); /// public override string ToString() => $"{UnconstructedType}[{string.Join(",", Arguments.Select(a => $"[{a}]"))}]"; } /// /// Represents a named type, which may be an unconstructed generic type. /// public class NamedTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The containing type. /// The type name. /// The generic arity of the type, which must be greater than or equal to the generic arity of the containing type. public NamedTypeSpec(NamedTypeSpec containingType, string name, int arity) { ContainingType = containingType; Name = name; if (containingType is NamedTypeSpec c && c.Arity > arity) { throw new ArgumentException("A named type cannot have an arity less than that of its containing type", nameof(arity)); } if (arity < 0) { throw new ArgumentOutOfRangeException(nameof(arity), "A type cannot have a negative arity"); } Arity = arity; } /// /// Gets the number of generic parameters which this type requires. /// public int Arity { get; } /// /// Gets the type name, which includes the namespace if this is not a nested type. /// public string Name { get; } /// /// Gets the containing type. /// public NamedTypeSpec? ContainingType { get; } /// /// Gets the namespace-qualified type name, including containing types (for nested types). /// /// The namespace-qualified type name. public string GetNamespaceQualifiedName() { var builder = new StringBuilder(); GetQualifiedNameInternal(this, builder); return builder.ToString(); static void GetQualifiedNameInternal(NamedTypeSpec n, StringBuilder b) { if (n.ContainingType is not null) { GetQualifiedNameInternal(n.ContainingType, b); _ = b.Append('+'); } _ = b.Append(n.Name); } } /// public override string Format() => ToString(); /// public override string ToString() => ContainingType is not null ? $"{ContainingType}+{Name}" : Name; } /// /// Represents an assembly-qualified type. /// public class AssemblyQualifiedTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The type. /// The assembly. public AssemblyQualifiedTypeSpec(TypeSpec type, string? assembly) { if (type is null) { throw new ArgumentNullException(nameof(type)); } if (string.IsNullOrWhiteSpace(assembly)) { throw new ArgumentNullException(nameof(assembly)); } Type = type; Assembly = assembly; } /// /// Gets the assembly specification. /// public string? Assembly { get; } /// /// Gets the qualified type. /// public TypeSpec Type { get; } /// public override string Format() => ToString(); /// public override string ToString() => Assembly switch { { Length: > 0 } => $"{Type},{Assembly!}", _ => $"{Type}" }; } /// /// Represents a type function. /// public class TupleTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The tuple elements. /// The number of generic type parameters which the type accepts. public TupleTypeSpec(TypeSpec[] elements, int arity) { if (elements is null) { throw new ArgumentNullException(nameof(elements)); } if (elements is { Length: 0 }) { throw new ArgumentNullException(nameof(elements)); } if (arity < 0) { throw new ArgumentOutOfRangeException(nameof(arity), "A type cannot have a negative arity"); } Arity = arity; Elements = elements; } /// /// Gets the number of generic parameters which this type requires. /// public int Arity { get; } /// /// Gets the tuple elements. /// public TypeSpec[] Elements { get; } /// public override string Format() => ToString(); /// public override string ToString() { var elements = string.Join(",", Elements.Select(element => element switch { LiteralTypeSpec => element.ToString(), _ => $"[{element}]" })); return Arity switch { > 0 => $"({elements})`{Arity}", _ => $"({elements})" }; } } /// /// Represents a literal. /// public class LiteralTypeSpec : TypeSpec { /// /// Initializes a new instance of the class. /// /// The value. public LiteralTypeSpec(string value) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentNullException(nameof(value)); } Value = value; } /// /// Gets the value. /// public string Value { get; } /// public override string Format() => ToString(); /// public override string ToString() => $"\"{Value}\""; } } ================================================ FILE: src/Orleans.Serialization/Utilities/BitOperations.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Orleans.Serialization.Utilities { #if NETCOREAPP3_1_OR_GREATER #else /// /// BitOperations polyfill. /// // Extracted from https://github.com/dotnet/runtime/blob/e13871cb275b9f53fa82285b2a81ada28a859b50/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs internal static class BitOperations { private static ReadOnlySpan TrailingZeroCountDeBruijn => new byte[32] { 00, 01, 28, 02, 29, 14, 24, 03, 30, 22, 20, 15, 25, 17, 04, 08, 31, 27, 13, 23, 21, 19, 16, 07, 26, 12, 18, 06, 11, 05, 10, 09 }; private static ReadOnlySpan Log2DeBruijn => new byte[32] { 00, 09, 01, 10, 13, 21, 02, 29, 11, 14, 16, 18, 22, 25, 03, 30, 08, 12, 20, 28, 15, 17, 24, 07, 19, 27, 23, 06, 26, 05, 04, 31 }; /// /// Returns the integer (floor) log of the specified value, base 2. /// Note that by convention, input value 0 returns 0 since log(0) is undefined. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int Log2(uint value) { // The 0->0 contract is fulfilled by setting the LSB to 1. // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result. value |= 1; // Fallback contract is 0->0 return Log2SoftwareFallback(value); } /// /// Returns the integer (floor) log of the specified value, base 2. /// Note that by convention, input value 0 returns 0 since log(0) is undefined. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int Log2(ulong value) { value |= 1; uint hi = (uint)(value >> 32); if (hi == 0) { return Log2((uint)value); } return 32 + Log2(hi); } /// /// Returns the integer (floor) log of the specified value, base 2. /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. /// Does not directly use any hardware intrinsics, nor does it incur branching. /// /// The value. private static int Log2SoftwareFallback(uint value) { // No AggressiveInlining due to large method size // Has conventional contract 0->0 (Log(0) is undefined) // Fill trailing zeros with ones, eg 00010010 becomes 00011111 value |= value >> 01; value |= value >> 02; value |= value >> 04; value |= value >> 08; value |= value >> 16; // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check return Unsafe.AddByteOffset( // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u ref MemoryMarshal.GetReference(Log2DeBruijn), // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); } /// /// Count the number of trailing zero bits in an integer value. /// Similar in behavior to the x86 instruction TZCNT. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TrailingZeroCount(uint value) { // Unguarded fallback contract is 0->0, BSF contract is 0->undefined if (value == 0) { return 32; } // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check return Unsafe.AddByteOffset( // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_0111_1100_1011_0101_0011_0001u ref MemoryMarshal.GetReference(TrailingZeroCountDeBruijn), // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here (IntPtr)(int)(((value & (uint)-(int)value) * 0x077CB531u) >> 27)); // Multi-cast mitigates redundant conv.u8 } /// /// Count the number of trailing zero bits in a mask. /// Similar in behavior to the x86 instruction TZCNT. /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TrailingZeroCount(ulong value) { uint lo = (uint)value; if (lo == 0) { return 32 + TrailingZeroCount((uint)(value >> 32)); } return TrailingZeroCount(lo); } /// Round the given integral value up to a power of 2. /// The value. /// /// The smallest power of 2 which is greater than or equal to . /// If is 0 or the result overflows, returns 0. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint RoundUpToPowerOf2(uint value) { // Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 --value; value |= value >> 1; value |= value >> 2; value |= value >> 4; value |= value >> 8; value |= value >> 16; return value + 1; } } #endif } ================================================ FILE: src/Orleans.Serialization/Utilities/BitStreamFormatter.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; using System; using System.Buffers; using System.IO; using System.Text; namespace Orleans.Serialization.Utilities { /// /// Utilities for formatting an encoded bitstream in a textual manner. /// public static class BitStreamFormatter { /// /// Formats the provided buffer. /// /// The reader input type. /// The reader. /// The formatted input. public static string Format(ref Reader reader) { var res = new StringBuilder(); Format(ref reader, res); return res.ToString(); } /// /// Formats the specified array. /// /// The array. /// The session. /// The formatted input. public static string Format(PooledBuffer.BufferSlice slice, SerializerSession session) { var reader = Reader.Create(slice, session); return Format(ref reader); } /// /// Formats the specified array. /// /// The array. /// The session. /// The formatted input. public static string Format(byte[] array, SerializerSession session) { var reader = Reader.Create(array, session); return Format(ref reader); } /// /// Formats the specified buffer. /// /// The input buffer. /// The session. /// The formatted input. public static string Format(ReadOnlySpan input, SerializerSession session) { var reader = Reader.Create(input, session); return Format(ref reader); } /// /// Formats the specified buffer. /// /// The input buffer. /// The session. /// The formatted input. public static string Format(ReadOnlyMemory input, SerializerSession session) { var reader = Reader.Create(input, session); return Format(ref reader); } /// /// Formats the specified buffer. /// /// The input buffer. /// The session. /// The formatted input. public static string Format(ReadOnlySequence input, SerializerSession session) { var reader = Reader.Create(input, session); return Format(ref reader); } /// /// Formats the specified buffer. /// /// The input buffer. /// The session. /// The formatted input. public static string Format(Stream input, SerializerSession session) { var reader = Reader.Create(input, session); return Format(ref reader); } /// /// Formats the specified buffer. /// /// The reader input type. /// The reader. /// The destination string builder. public static void Format(ref Reader reader, StringBuilder result) { var (field, type) = reader.ReadFieldHeaderForAnalysis(); FormatField(ref reader, field, type, field.FieldIdDelta, result, indentation: 0); } private static void FormatField(ref Reader reader, Field field, string typeName, uint id, StringBuilder res, int indentation) { var indentString = new string(' ', indentation); AppendAddress(ref reader, res); res.Append(indentString); // References cannot themselves be referenced. if (field.WireType == WireType.Reference) { ReferenceCodec.MarkValueField(reader.Session); var refId = reader.ReadVarUInt32(); res.Append('['); FormatFieldHeader(res, reader.Session, field, id, typeName); if (refId == 0) { res.Append($" Reference: 0"); } else { var refd = reader.Session.ReferencedObjects.TryGetReferencedObject(refId); res.Append($" Reference: {refId} ({refd}{(refd is null ? "unknown" : null)})"); } res.Append(']'); return; } // Record a placeholder so that this field can later be correctly deserialized if it is referenced. ReferenceCodec.RecordObject(reader.Session, new UnknownFieldMarker(field, reader.Position)); res.Append('['); FormatFieldHeader(res, reader.Session, field, id, typeName); res.Append(']'); res.Append(" Value: "); switch (field.WireType) { case WireType.VarInt: { var a = reader.ReadVarUInt64(); if (a < 10240) { res.Append($"{a} (0x{a:X})"); } else { res.Append($"0x{a:X}"); } } break; case WireType.TagDelimited: // Since tag delimited fields can be comprised of other fields, recursively consume those, too. res.Append($"{{\n"); reader.FormatTagDelimitedField(res, indentation + 1); res.AppendLine(); AppendAddress(ref reader, res); res.Append($"{indentString}}}"); break; case WireType.LengthPrefixed: { var length = reader.ReadVarUInt32(); res.Append($"(length: {length}b) ["); var a = reader.ReadBytes(length); FormatByteArray(res, a); res.Append(']'); } break; case WireType.Fixed32: { var a = reader.ReadUInt32(); if (a < 10240) { res.Append($"{a} (0x{a:X})"); } else { res.Append($"0x{a:X}"); } } break; case WireType.Fixed64: { var a = reader.ReadUInt64(); if (a < 10240) { res.Append($"{a} (0x{a:X})"); } else { res.Append($"0x{a:X}"); } } break; case WireType.Extended: SkipFieldExtension.ThrowUnexpectedExtendedWireType(field); break; default: SkipFieldExtension.ThrowUnexpectedWireType(field); break; } } private static void FormatByteArray(StringBuilder res, byte[] a) { var isAscii = true; foreach (var b in a) { if (b >= 0x7F) { isAscii = false; } } if (isAscii) { res.Append('"'); res.Append(Encoding.ASCII.GetString(a)); res.Append('"'); } else { bool first = true; foreach (var b in a) { if (!first) { res.Append(' '); } else { first = false; } res.Append($"{b:X2}"); } } } private static void FormatFieldHeader(StringBuilder res, SerializerSession session, Field field, uint id, string typeName) { _ = res .Append($"#{session.ReferencedObjects.CurrentReferenceId} ") .Append((string)field.WireType.ToString()); if (field.HasFieldId) { _ = res.Append($" Id: {id}"); } if (field.IsSchemaTypeValid) { _ = res.Append($" SchemaType: {field.SchemaType}"); } if (field.HasExtendedSchemaType) { _ = res.Append($" RuntimeType: {field.FieldType} (name: {typeName})"); } if (field.WireType == WireType.Extended) { _ = res.Append($": {field.ExtendedWireType}"); } } /// /// Consumes a tag-delimited field. /// private static void FormatTagDelimitedField(this ref Reader reader, StringBuilder res, int indentation) { var id = 0U; var first = true; while (true) { var (field, type) = reader.ReadFieldHeaderForAnalysis(); if (field.IsEndObject) { break; } if (field.IsEndBaseFields) { res.AppendLine(); AppendAddress(ref reader, res); res.Append($"{new string(' ', indentation)}- End of base type fields -"); if (first) { first = false; } id = 0U; continue; } id += field.FieldIdDelta; if (!first) { res.AppendLine(); } else { first = false; } FormatField(ref reader, field, type, id, res, indentation); } } private static void AppendAddress(ref Reader reader, StringBuilder res) { res.Append($"0x{reader.Position:X4} "); } } } ================================================ FILE: src/Orleans.Serialization/Utilities/FieldAccessor.cs ================================================ using System; using System.Reflection; using System.Reflection.Emit; namespace Orleans.Serialization.Utilities { /// /// The delegate used to set fields in value types. /// /// The declaring type of the field. /// The field type. /// The instance having its field set. public delegate TField ValueTypeGetter(ref TDeclaring instance) where TDeclaring : struct; /// /// The delegate used to set fields in value types. /// /// The declaring type of the field. /// The field type. /// The instance having its field set. /// The value being set. public delegate void ValueTypeSetter(ref TDeclaring instance, TField value) where TDeclaring : struct; /// /// Functionality for accessing fields. /// public static class FieldAccessor { /// /// Returns a delegate to get the value of a specified field. /// /// A delegate to get the value of a specified field. public static Delegate GetGetter(Type declaringType, string fieldName) => GetGetter(declaringType, fieldName, false); /// /// Returns a delegate to get the value of a specified field. /// /// A delegate to get the value of a specified field. public static Delegate GetValueGetter(Type declaringType, string fieldName) => GetGetter(declaringType, fieldName, true); private static Delegate GetGetter(Type declaringType, string fieldName, bool byref) { var field = declaringType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); var parameterTypes = new[] { typeof(object), byref ? declaringType.MakeByRefType() : declaringType }; var method = new DynamicMethod(fieldName + "Get", field.FieldType, parameterTypes, typeof(FieldAccessor).Module, true); var emitter = method.GetILGenerator(); // arg0 is unused for better delegate performance (avoids argument shuffling thunk) emitter.Emit(OpCodes.Ldarg_1); emitter.Emit(OpCodes.Ldfld, field); emitter.Emit(OpCodes.Ret); return method.CreateDelegate((byref ? typeof(ValueTypeGetter<,>) : typeof(Func<,>)).MakeGenericType(declaringType, field.FieldType)); } /// /// Returns a delegate to set the value of this field for an instance. /// /// A delegate to set the value of this field for an instance. public static Delegate GetReferenceSetter(Type declaringType, string fieldName) => GetSetter(declaringType, fieldName, false); /// /// Returns a delegate to set the value of this field for an instance. /// /// A delegate to set the value of this field for an instance. public static Delegate GetValueSetter(Type declaringType, string fieldName) => GetSetter(declaringType, fieldName, true); private static Delegate GetSetter(Type declaringType, string fieldName, bool byref) { var field = declaringType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); var parameterTypes = new[] { typeof(object), byref ? declaringType.MakeByRefType() : declaringType, field.FieldType }; var method = new DynamicMethod(fieldName + "Set", null, parameterTypes, typeof(FieldAccessor).Module, true); var emitter = method.GetILGenerator(); // arg0 is unused for better delegate performance (avoids argument shuffling thunk) emitter.Emit(OpCodes.Ldarg_1); emitter.Emit(OpCodes.Ldarg_2); emitter.Emit(OpCodes.Stfld, field); emitter.Emit(OpCodes.Ret); return method.CreateDelegate((byref ? typeof(ValueTypeSetter<,>) : typeof(Action<,>)).MakeGenericType(declaringType, field.FieldType)); } } } ================================================ FILE: src/Orleans.Serialization/Utilities/ReferenceEqualsComparer.cs ================================================ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Utilities { internal sealed class ReferenceEqualsComparer : IEqualityComparer, IEqualityComparer { public static ReferenceEqualsComparer Default { get; } = new(); public new bool Equals(object x, object y) => ReferenceEquals(x, y); public int GetHashCode(object obj) => obj is null ? 0 : RuntimeHelpers.GetHashCode(obj); } } ================================================ FILE: src/Orleans.Serialization/Utilities/ServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Serialization.Utilities.Internal; /// /// Extension methods for configuring dependency injection. /// public static class InternalServiceCollectionExtensions { /// /// Registers an existing registration of as a provider of service type . /// /// The service type being provided. /// The implementation of . /// The service collection. public static void AddFromExisting(this IServiceCollection services) where TImplementation : TService { services.AddFromExisting(typeof(TService), typeof(TImplementation)); } /// /// Registers an existing registration of as a provider of service type . /// /// The service collection. /// The service type being provided. /// The implementation of . public static void AddFromExisting(this IServiceCollection services, Type service, Type implementation) { ServiceDescriptor registration = null; foreach (var descriptor in services) { if (descriptor.ServiceType == implementation) { registration = descriptor; break; } } if (registration is null) { throw new ArgumentNullException(nameof(implementation), $"Unable to find previously registered ServiceType of '{implementation.FullName}'"); } var newRegistration = new ServiceDescriptor( service, sp => sp.GetRequiredService(implementation), registration.Lifetime); services.Add(newRegistration); } } ================================================ FILE: src/Orleans.Serialization/Utilities/VarIntReaderExtensions.cs ================================================ using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; using System.Runtime.CompilerServices; namespace Orleans.Serialization.Buffers { /// /// Extension method for working with variable-width integers. /// public static class VarIntReaderExtensions { /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte ReadVarInt8(this ref Reader reader) => ZigZagDecode(checked((byte)reader.ReadVarUInt32())); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ReadVarInt16(this ref Reader reader) => ZigZagDecode(checked((ushort)reader.ReadVarUInt32())); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadVarUInt8(this ref Reader reader) => checked((byte)reader.ReadVarUInt32()); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadVarUInt16(this ref Reader reader) => checked((ushort)reader.ReadVarUInt32()); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadVarInt32(this ref Reader reader) => ZigZagDecode(reader.ReadVarUInt32()); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ReadVarInt64(this ref Reader reader) => ZigZagDecode(reader.ReadVarUInt64()); /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadUInt8(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarUInt8(), WireType.Fixed32 => checked((byte)reader.ReadUInt32()), WireType.Fixed64 => checked((byte)reader.ReadUInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((byte)UInt128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadUInt16(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarUInt16(), WireType.Fixed32 => checked((ushort)reader.ReadUInt32()), WireType.Fixed64 => checked((ushort)reader.ReadUInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((ushort)UInt128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ReadUInt32(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarUInt32(), WireType.Fixed32 => reader.ReadUInt32(), WireType.Fixed64 => checked((uint)reader.ReadUInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((uint)UInt128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ReadUInt64(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarUInt64(), WireType.Fixed32 => reader.ReadUInt32(), WireType.Fixed64 => reader.ReadUInt64(), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((ulong)UInt128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte ReadInt8(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarInt8(), WireType.Fixed32 => checked((sbyte)reader.ReadInt32()), WireType.Fixed64 => checked((sbyte)reader.ReadInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((sbyte)Int128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ReadInt16(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarInt16(), WireType.Fixed32 => checked((short)reader.ReadInt32()), WireType.Fixed64 => checked((short)reader.ReadInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((short)Int128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadInt32(this ref Reader reader, WireType wireType) { if (wireType == WireType.VarInt) { return reader.ReadVarInt32(); } return ReadInt32Slower(ref reader, wireType); } [MethodImpl(MethodImplOptions.NoInlining)] private static int ReadInt32Slower(this ref Reader reader, WireType wireType) => wireType switch { WireType.Fixed32 => reader.ReadInt32(), WireType.Fixed64 => checked((int)reader.ReadInt64()), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((int)Int128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; /// /// Reads a variable-width . /// /// The reader input type. /// The reader. /// The wire type. /// A variable-width integer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ReadInt64(this ref Reader reader, WireType wireType) => wireType switch { WireType.VarInt => reader.ReadVarInt64(), WireType.Fixed32 => reader.ReadInt32(), WireType.Fixed64 => reader.ReadInt64(), #if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((long)Int128Codec.ReadRaw(ref reader)), #endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; private const sbyte Int8Msb = unchecked((sbyte)0x80); private const short Int16Msb = unchecked((short)0x8000); private const int Int32Msb = unchecked((int)0x80000000); private const long Int64Msb = unchecked((long)0x8000000000000000); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static sbyte ZigZagDecode(byte encoded) { var value = (sbyte)encoded; return (sbyte)(-(value & 0x01) ^ ((sbyte)(value >> 1) & ~Int8Msb)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static short ZigZagDecode(ushort encoded) { var value = (short)encoded; return (short)(-(value & 0x01) ^ ((short)(value >> 1) & ~Int16Msb)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ZigZagDecode(uint encoded) { var value = (int)encoded; return -(value & 0x01) ^ ((value >> 1) & ~Int32Msb); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static long ZigZagDecode(ulong encoded) { var value = (long)encoded; return -(value & 0x01L) ^ ((value >> 1) & ~Int64Msb); } } } ================================================ FILE: src/Orleans.Serialization/WireProtocol/ExtendedWireType.cs ================================================ namespace Orleans.Serialization.WireProtocol { /// /// Represents an extended wire type /// public enum ExtendedWireType : uint { /// /// Marks the end of a tag-delimited field. /// EndTagDelimited = 0b00 << 3, /// /// Marks the end of base-type fields in a tag-delimited object. /// EndBaseFields = 0b01 << 3, } } ================================================ FILE: src/Orleans.Serialization/WireProtocol/Field.cs ================================================ using System; using System.Runtime.CompilerServices; using System.Text; namespace Orleans.Serialization.WireProtocol { /// /// Represents a field header. /// public struct Field { /// /// The tag byte. /// public Tag Tag; /// /// The raw field identifier delta. /// public uint FieldIdDeltaRaw; /// /// The raw field type. /// public Type FieldTypeRaw; /// /// Initializes a new instance of the struct. /// /// The tag. public Field(Tag tag) { Tag = tag; FieldIdDeltaRaw = tag.FieldIdDelta; FieldTypeRaw = null; } /// /// Initializes a new instance of the struct. /// /// The tag. /// The extended field identifier delta. /// The type. public Field(Tag tag, uint extendedFieldIdDelta, Type type) { Tag = tag; FieldIdDeltaRaw = extendedFieldIdDelta; FieldTypeRaw = type; } /// /// Gets or sets the field identifier delta. /// /// The field identifier delta. public uint FieldIdDelta { // If the embedded field id delta is valid, return it, otherwise return the extended field id delta. // The extended field id might not be valid if this field has the Extended wire type. [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { #if DEBUG if (!HasFieldId) throw new FieldIdNotPresentException(); #endif return FieldIdDeltaRaw; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { // If the field id delta can fit into the tag, embed it there, otherwise invalidate the embedded field id delta and set the full field id delta. if (value <= Tag.MaxEmbeddedFieldIdDelta) { Tag.FieldIdDelta = value; } else { Tag.SetFieldIdInvalid(); } FieldIdDeltaRaw = value; } } /// /// Gets or sets the type of the field. /// /// The type of the field. public Type FieldType { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get { #if DEBUG if (!IsSchemaTypeValid) { throw new FieldTypeInvalidException(); } #endif return FieldTypeRaw; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { #if DEBUG if (!IsSchemaTypeValid) { throw new FieldTypeInvalidException(); } #endif FieldTypeRaw = value; } } /// /// Gets a value indicating whether this instance has a field identifier. /// /// if this instance has a field identifier; otherwise, . public readonly bool HasFieldId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !Tag.HasExtendedWireType; } /// /// Gets a value indicating whether this instance has an extended field identifier. /// /// if this instance has an extended field identifier; otherwise, . public readonly bool HasExtendedFieldId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Tag.HasExtendedFieldId; } /// /// Gets or sets the wire type. /// /// The wire type. public WireType WireType { readonly get => Tag.WireType; set => Tag.WireType = value; } /// /// Gets or sets the schema type. /// /// The schema type. public SchemaType SchemaType { readonly get { #if DEBUG if (!IsSchemaTypeValid) { throw new SchemaTypeInvalidException(); } #endif return Tag.SchemaType; } set => Tag.SchemaType = value; } /// /// Gets or sets the extended wire type. /// /// The extended wire type. public ExtendedWireType ExtendedWireType { readonly get { #if DEBUG if (WireType != WireType.Extended) { throw new ExtendedWireTypeInvalidException(); } #endif return Tag.ExtendedWireType; } set => Tag.ExtendedWireType = value; } /// /// Gets a value indicating whether this instance has a valid schema type. /// /// if this instance has a valid schema; otherwise, . public readonly bool IsSchemaTypeValid => Tag.IsSchemaTypeValid; /// /// Gets a value indicating whether this instance has an extended schema type. /// /// if this instance has an extended schema type; otherwise, . public readonly bool HasExtendedSchemaType => Tag.IsSchemaTypeValid && Tag.SchemaType != SchemaType.Expected; /// /// Gets a value indicating whether this instance represents the end of base fields in a tag-delimited structure. /// /// if this instance represents end of base fields in a tag-delimited structure; otherwise, . public readonly bool IsEndBaseFields => Tag.IsEndBaseFields; /// /// Gets a value indicating whether this instance represents the end of a tag-delimited structure. /// /// if this instance represents end of a tag-delimited structure; otherwise, . public readonly bool IsEndObject => Tag.IsEndObject; /// /// Gets a value indicating whether this instance represents the end of a tag-delimited structure or the end of base fields in a tag-delimited structure. /// /// if this instance represents the end of a tag-delimited structure or the end of base fields in a tag-delimited structure; otherwise, . public readonly bool IsEndBaseOrEndObject { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Tag.HasExtendedWireType/* && Tag.ExtendedWireType <= ExtendedWireType.EndBaseFields*/; } /// /// Gets a value indicating whether this instance has a wire type of . /// public readonly bool IsReference => Tag.WireType == WireType.Reference; /// /// Ensures that the wire type is . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureWireTypeTagDelimited() { if (Tag.WireType != WireType.TagDelimited) UnsupportedWireType(); } private void UnsupportedWireType() => throw new UnsupportedWireTypeException($"A WireType value of {nameof(WireType.TagDelimited)} is expected by this codec. {this}"); /// /// Ensures that the wire type is supported. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureWireType(WireType expectedType) { if (Tag.WireType != expectedType) UnsupportedWireType(expectedType); } private void UnsupportedWireType(WireType expectedType) => throw new UnsupportedWireTypeException($"A WireType value of {expectedType} is expected by this codec. {this}"); /// public override string ToString() { #if NET6_0_OR_GREATER var builder = new DefaultInterpolatedStringHandler(0, 0); builder.AppendLiteral("["); builder.AppendFormatted(WireType); if (HasFieldId) { builder.AppendLiteral(", IdDelta:"); builder.AppendFormatted(FieldIdDelta); } if (IsSchemaTypeValid) { builder.AppendLiteral(", SchemaType:"); builder.AppendFormatted(SchemaType); } if (HasExtendedSchemaType) { builder.AppendLiteral(", RuntimeType:"); builder.AppendFormatted(FieldType); } if (Tag.HasExtendedWireType) { builder.AppendLiteral(": "); builder.AppendFormatted(ExtendedWireType); } builder.AppendLiteral("]"); return builder.ToStringAndClear(); #else var builder = new StringBuilder(); builder.Append('['); builder.Append(WireType); if (HasFieldId) { builder.Append(", IdDelta:"); builder.Append(FieldIdDelta); } if (IsSchemaTypeValid) { builder.Append(", SchemaType:"); builder.Append(SchemaType); } if (HasExtendedSchemaType) { builder.Append(", RuntimeType:"); builder.Append(FieldType); } if (WireType == WireType.Extended) { builder.Append(": "); builder.Append(ExtendedWireType); } builder.Append(']'); return builder.ToString(); #endif } } } ================================================ FILE: src/Orleans.Serialization/WireProtocol/SchemaType.cs ================================================ namespace Orleans.Serialization.WireProtocol { /// /// Identifies the runtime type (schema type) of a field. /// public enum SchemaType : uint { /// /// Indicates that the runtime type is the exact type expected by the current schema. /// Expected = 0b00 << 3, /// /// Indicates that the runtime type is an instance of a well-known type. Followed by a VarInt type id. /// WellKnown = 0b01 << 3, /// /// Indicates that the runtime type is encoded as a named type. Followed by an encoded type name. /// Encoded = 0b10 << 3, /// /// Indicates that the runtime type is a type which was previously specified. Followed by a VarInt indicating which previous type is being reused. /// Referenced = 0b11 << 3, } } ================================================ FILE: src/Orleans.Serialization/WireProtocol/Tag.cs ================================================ using System.Runtime.CompilerServices; namespace Orleans.Serialization.WireProtocol { /// /// A serialization tag, which is always exactly a single byte. This acts as a part of the field header for all serialized fields. /// /// /// The typical form for a tag byte is [W W W] [S S] [F F F], where each is a bit. /// W is a , S is a bit, and F is a field identifier bit. /// public struct Tag { /// /// The wire type mask. /// public const byte WireTypeMask = 0b1110_0000; // The first 3 bits are dedicated to the wire type. /// /// The schema type mask. /// public const byte SchemaTypeMask = 0b0001_1000; // The next 2 bits are dedicated to the schema type specifier, if the schema type is expected. /// /// The field identifier mask. /// public const byte FieldIdMask = 0b0000_0111; // The final 3 bits are used for the field id delta, if the delta is expected. /// /// The field identifier complete mask. /// public const byte FieldIdCompleteMask = FieldIdMask; /// /// The extended wire type mask. /// public const byte ExtendedWireTypeMask = 0b0001_1000; /// /// The maximum embedded field identifier delta. /// public const int MaxEmbeddedFieldIdDelta = 6; private uint _tag; /// /// Initializes a new instance of the struct. /// /// The tag byte. public Tag(byte tag) => _tag = tag; internal Tag(uint tag) => _tag = tag; /// /// Performs an implicit conversion from to . /// /// The tag. /// The result of the conversion. public static implicit operator Tag(byte tag) => new(tag); /// /// Performs an implicit conversion from to . /// /// The tag. /// The result of the conversion. public static implicit operator byte(Tag tag) => (byte)tag._tag; /// /// Gets or sets the wire type of the data following this tag. /// public WireType WireType { readonly get => (WireType)(_tag & WireTypeMask); [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _tag = (_tag & ~(uint)WireTypeMask) | ((uint)value & WireTypeMask); } /// /// Gets a value indicating whether this instance has an extended wire type. /// /// if this instance has an extended wire type; otherwise, . public readonly bool HasExtendedWireType { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _tag >= (byte)WireType.Extended; //(this.tag & (byte) WireType.Extended) == (byte) WireType.Extended; } /// /// Gets or sets the extended wire type of the data following this tag. /// public ExtendedWireType ExtendedWireType { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get => (ExtendedWireType)(_tag & ExtendedWireTypeMask); [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _tag = (_tag & ~(uint)ExtendedWireTypeMask) | ((uint)value & ExtendedWireTypeMask); } internal readonly bool IsEndBaseFields => _tag == ((byte)WireType.Extended | (byte)ExtendedWireType.EndBaseFields); internal readonly bool IsEndObject => _tag == ((byte)WireType.Extended | (byte)ExtendedWireType.EndTagDelimited); /// /// Gets or sets the schema type. /// public SchemaType SchemaType { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get => (SchemaType)(_tag & SchemaTypeMask); [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _tag = (_tag & ~(uint)SchemaTypeMask) | ((uint)value & SchemaTypeMask); } /// /// Gets a value indicating whether the property is valid. /// /// if the is valid, otherwise. public readonly bool IsSchemaTypeValid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !HasExtendedWireType; //(this.tag & (byte) WireType.Extended) != (byte) WireType.Extended; } /// /// Returns the of the field represented by this tag. /// /// /// If is , this value is not a complete field id delta. /// public uint FieldIdDelta { [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly get => _tag & FieldIdMask; [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _tag = (_tag & ~(uint)FieldIdMask) | (value & FieldIdMask); } /// /// Invalidates . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetFieldIdInvalid() => _tag |= FieldIdCompleteMask; /// /// Gets a value indicating whether the property is valid. /// /// /// if the represents a complete id, if more data is required. /// /// /// If all bits are set in the field id portion of the tag, this field id is not valid and this tag must be followed by a field id. /// Therefore, field ids 0-6 can be represented without additional bytes. /// public readonly bool IsFieldIdValid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_tag & FieldIdCompleteMask) != FieldIdCompleteMask && !HasExtendedWireType; } /// /// Gets a value indicating whether the tag is followed by an extended field id. /// public readonly bool HasExtendedFieldId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_tag & FieldIdCompleteMask) == FieldIdCompleteMask && !HasExtendedWireType; } } } ================================================ FILE: src/Orleans.Serialization/WireProtocol/WireType.cs ================================================ namespace Orleans.Serialization.WireProtocol { /// /// Represents a 3-bit wire type, shifted into position /// public enum WireType : uint { /// /// A variable-length integer vlaue. /// /// /// Followed by a variable-length integer. /// VarInt = 0b000 << 5, /// /// A compound value comprised of a collection of tag-delimited fields. /// /// /// Followed by field specifiers, then an tag with as the extended wire type. /// TagDelimited = 0b001 << 5, /// /// A length-prefixed value. /// /// /// Followed by VarInt length representing the number of bytes which follow. /// LengthPrefixed = 0b010 << 5, /// /// A 32-bit value. /// /// /// Followed by 4 bytes. /// Fixed32 = 0b011 << 5, /// /// A 64-bit value. /// /// /// Followed by 8 bytes. /// Fixed64 = 0b100 << 5, /// /// A reference to a previously encoded value. /// /// /// Followed by 8 bytes. /// Reference = 0b110 << 5, // Followed by a VarInt reference to a previously defined object. Note that the SchemaType and type specification must still be included. Extended = 0b111 << 5, // This is a control tag. The schema type and embedded field id are invalid. The remaining 5 bits are used for control information. } } ================================================ FILE: src/Orleans.Serialization.Abstractions/Annotations.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; namespace Orleans { /// /// When applied to a type, specifies that the type is intended to be serialized and that serialization code should be generated for the type. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] public sealed class GenerateSerializerAttribute : Attribute { /// /// Get or sets if primary constructor parameters should automatically be included as Serializable fields. /// Defaults to for types, otherwise. /// public bool IncludePrimaryConstructorParameters { get; init; } /// /// Get or sets when Orleans should auto-assign field ids. The default behavior is to not auto-assign field ids. /// public GenerateFieldIds GenerateFieldIds { get; init; } = GenerateFieldIds.None; } /// /// When applied to an interface, specifies that support code should be generated to allow remoting of interface calls. /// /// [AttributeUsage(AttributeTargets.Interface, AllowMultiple = true)] public sealed class GenerateMethodSerializersAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The proxy base type. /// if set to , this is an extension interface. public GenerateMethodSerializersAttribute(Type proxyBase, bool isExtension = false) { ProxyBase = proxyBase; IsExtension = isExtension; } /// /// Gets the base type which the source generator will use for generated proxy classes. /// /// The proxy base type. public Type ProxyBase { get; } /// /// Gets a value indicating whether this interface is an extension interface. /// /// true if this instance is extension; otherwise, false. public bool IsExtension { get; } } /// /// Applied to method attributes on invokable interfaces to specify the name of the method on the base type to call when submitting a request. /// [AttributeUsage(AttributeTargets.Class)] public sealed class InvokeMethodNameAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// Name of the invoke method. public InvokeMethodNameAttribute(string invokeMethodName) { InvokeMethodName = invokeMethodName; } /// /// Gets the name of the invoke method. /// /// The name of the invoke method. public string InvokeMethodName { get; } } /// /// Applied to proxy base types and to attribute types used on invokable interface methods to specify the base type for the invokable object which represents a method call. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class DefaultInvokeMethodNameAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// Type of the return. /// Name of the method. public DefaultInvokeMethodNameAttribute(Type returnType, string methodName) { ReturnType = returnType; MethodName = methodName; } /// /// Gets the interface method return type which this attribute applies to. /// public Type ReturnType { get; } /// /// Gets the name of the method on the proxy base type to call when methods are invoked. /// public string MethodName { get; } } /// /// Applied to interface method attribute types to specify a method to be called on invokable objects which are created when invoking that interface method. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class InvokableCustomInitializerAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// Name of the method. /// The method argument value. public InvokableCustomInitializerAttribute(string methodName, object methodArgumentValue) { MethodName = methodName; MethodArgumentValue = methodArgumentValue; AttributeArgumentIndex = -1; } /// /// Initializes a new instance of the class. /// /// Name of the method. public InvokableCustomInitializerAttribute(string methodName) { MethodName = methodName; MethodArgumentValue = null; AttributeArgumentIndex = 0; } /// /// Gets the name of the method. /// public string MethodName { get; } /// /// Gets or sets the index of the attribute argument to propagate to the custom initializer method. /// public int AttributeArgumentIndex { get; init; } /// /// Gets or sets the name of the attribute argument to propagate to the custom initializer method. /// public int AttributeArgumentName { get; init; } /// /// Gets or sets the value to pass to the custom initializer method. /// public object MethodArgumentValue { get; } } /// /// Applied to proxy base types and to attribute types used on invokable interface methods to specify the base type for the invokable object which represents a method call. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class DefaultInvokableBaseTypeAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The return type of methods which this attribute applies to. /// Type of the invokable base. public DefaultInvokableBaseTypeAttribute(Type returnType, Type invokableBaseType) { ReturnType = returnType; InvokableBaseType = invokableBaseType; } /// /// Gets the return type of methods which this attribute applies to. /// public Type ReturnType { get; } /// /// Gets the base type for the invokable object class which will be used to represent method calls to methods which this attribute applies to. /// public Type InvokableBaseType { get; } /// /// Gets or sets the name of the method on the proxy object which is used to invoke this method. /// public string ProxyInvokeMethodName { get; init; } } /// /// Applied to attribute types used on invokable interface methods to specify the base type for the invokable object which represents a method call. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] public sealed class InvokableBaseTypeAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The proxy base class. /// The return type of methods which this attribute applies to. /// Type of the invokable base. public InvokableBaseTypeAttribute(Type proxyBaseClass, Type returnType, Type invokableBaseType) { ProxyBaseClass = proxyBaseClass; ReturnType = returnType; InvokableBaseType = invokableBaseType; } /// /// Gets the proxy base class which this attribute applies to. /// public Type ProxyBaseClass { get; } /// /// Gets the method return type which this attribute applies to. /// public Type ReturnType { get; } /// /// Gets the base type to use for the generated invokable object. /// public Type InvokableBaseType { get; } /// /// Gets or sets the name of the method on the proxy object to invoke when this method is called. /// public string ProxyInvokeMethodName { get; init; } } /// /// Applied to method attributes on invokable interfaces to specify the name of the method to call to get a completion source which is submitted to the submit method and eventually returned to the caller. /// [AttributeUsage(AttributeTargets.Class)] public sealed class GetCompletionSourceMethodNameAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The name of the method used to get a completion source for requests submitted to the runtime. public GetCompletionSourceMethodNameAttribute(string methodName) { MethodName = methodName; } /// /// Gets the name of the method used to get a completion source for requests submitted to the runtime. /// public string MethodName { get; } } /// /// Specifies the unique identity of a member. /// /// /// Every serializable member in a type which has applied to it must have one attribute applied with a unique value. /// /// [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method)] public sealed class IdAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The identifier. public IdAttribute(uint id) { Id = id; } /// /// Gets the identifier. /// /// The identifier. public uint Id { get; } } /// /// Specifies the constructor the serializer should use when creating new instances from serialized data. /// /// /// At most one constructor can be annotated with this attribute. If multiple constructors are annotated, the presence of this attribute is ignored. /// /// [AttributeUsage(AttributeTargets.Constructor)] [Obsolete("Use GeneratedActivatorConstructorAttribute instead. This attribute is not recognized by Orleans.")] public sealed class OrleansConstructorAttribute : ActivatorUtilitiesConstructorAttribute { /// /// Initializes a new instance of the class. /// public OrleansConstructorAttribute() { } } /// /// When applied to a type or method, specifies a well-known name which can be used to identify that type or method. /// /// /// In the case of a type, the alias must be globally unique. In the case of a method, the alias must be unique to the declaring type. /// /// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method, AllowMultiple = true)] public sealed class AliasAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The alias. /// /// In the case of a type, the alias must be globally unique. In the case of a method, the alias must be unique to the declaring type. /// public AliasAttribute(string alias) { Alias = alias; } /// /// Gets the alias. /// /// /// In the case of a type, the alias must be globally unique. In the case of a method, the alias must be unique to the declaring type. /// public string Alias { get; } } /// /// When applied to a type, indicates that the type should be encoded as a relation from a specified type. /// /// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = true)] public sealed class CompoundTypeAliasAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// The alias components, each of which must be a or a . public CompoundTypeAliasAttribute(params object[] components) { Components = components; } /// /// Gets the alias components. /// public object[] Components { get; } } /// /// When applied to a type, indicates that the type is a provider and that it should be automatically registered. /// /// The provider name, for example, "AzureTableStorage". /// The kind of provider, for example, "Clustering", "Reminders". /// The intended target of the provider, for example, "Server", "Client". /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class RegisterProviderAttribute( string name, string kind, string target, #if NET5_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] #endif Type type) : Attribute { /// /// Gets the provider name, for example, "AzureTableStorage". /// public string Name { get; } = name; /// /// Gets the kind of the provider, for example, "Clustering", "Reminders". /// public string Kind { get; } = kind; /// /// Gets the type used to configure the provider, for example, "Server", "Client". /// public string Target { get; } = target; /// /// Gets the type used to configure the provider. /// public Type Type { get; } = type; } /// /// When applied to a type, indicates that the type is a serializer and that it should be automatically registered. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class RegisterSerializerAttribute : Attribute { } /// /// When applied to a type, indicates that the type is an activator and that it should be automatically registered. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class RegisterActivatorAttribute : Attribute { } /// /// When applied to a type, indicates that the type is an copier and that it should be automatically registered. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class RegisterCopierAttribute : Attribute { } /// /// When applied to a type, indicates that the type is a converter and that it should be automatically registered. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class RegisterConverterAttribute : Attribute { } /// /// When applied to a type, indicates that the type should be activated using a registered activator rather than via its constructor or another mechanism. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class UseActivatorAttribute : Attribute { } /// /// When applied to a type, indicates that generated serializers for the type should not track references to the type. /// /// /// Reference tracking allows a serializable type to participate in a cyclic object graph. /// [AttributeUsage(AttributeTargets.Class)] public sealed class SuppressReferenceTrackingAttribute : Attribute { } /// /// When applied to a type, indicates that generated serializers for the type should avoid serializing members if the member value is equal to its default value. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class OmitDefaultMemberValuesAttribute : Attribute { } /// /// Indicates that the type, type member, parameter, or return value which it is applied to should be treated as immutable and therefore that defensive copies are never required. /// When applied to non-sealed classes, derived types are not guaranteed to be immutable. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] public sealed class ImmutableAttribute : Attribute { } /// /// Indicates that the specific type is invisible for serialization purposes. /// Usable only on abstract types with no serialized fields and effectively removes it from the inheritance hierarchy. /// Adding/removing this attribute from a type will cause serialization protocol level incompatibility (like type hierarchy changes). /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class SerializerTransparentAttribute : Attribute { } /// /// Specifies an assembly to be added as an application part. /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class ApplicationPartAttribute : Attribute { /// /// Initializes a new instance of . /// /// The assembly name. public ApplicationPartAttribute(string assemblyName) { AssemblyName = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName)); } /// /// Gets the assembly name. /// public string AssemblyName { get; } } /// /// Specifies a type to be instantiated and invoked when performing serialization operations on instances of the type which this attribute is attached to. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public sealed class SerializationCallbacksAttribute : Attribute { /// /// Instantiates a new instance. /// /// The type of the object used to invoke serialization hooks. public SerializationCallbacksAttribute(Type hookType) { HookType = hookType; } /// /// The type of the hook class. /// /// /// This value is used to get the hooks implementation from an . /// public Type HookType { get; } } /// /// When applied to a constructor, indicates that generated activator implementations should use that constructor when activating instances. /// /// /// This attribute can be used to call constructors which require injected dependencies. /// [AttributeUsage(AttributeTargets.Constructor)] public sealed class GeneratedActivatorConstructorAttribute : ActivatorUtilitiesConstructorAttribute { /// /// Initializes a new instance of the class. /// public GeneratedActivatorConstructorAttribute() { } } /// /// Indicates that the source generator should also inspect and generate code for the assembly containing the specified type. /// /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class GenerateCodeForDeclaringAssemblyAttribute : Attribute { /// /// Initializes a new instance of the class. /// /// Any type in the assembly which the source generator should inspect and generate source for. public GenerateCodeForDeclaringAssemblyAttribute(Type type) { Type = type; } /// /// Gets a type in the assembly which the source generator should inspect and generate source for. /// /// The type. public Type Type { get; } } /// /// Specifies the response timeout for the interface method which it is specified on. /// [AttributeUsage(AttributeTargets.Method)] public sealed class ResponseTimeoutAttribute : Attribute { /// /// Specifies the response timeout for the interface method which it is specified on. /// /// The response timeout, using syntax. public ResponseTimeoutAttribute(string timeout) => Timeout = TimeSpan.Parse(timeout); /// /// Gets or sets the response timeout for this method. /// public TimeSpan? Timeout { get; init; } } /// /// Functionality for converting between two types. /// public interface IConverter where TSurrogate : struct { /// /// Converts a surrogate value to the value type. /// /// The surrogate. /// The value. TValue ConvertFromSurrogate(in TSurrogate surrogate); /// /// Converts a value to the value type. /// /// The value. /// The surrogate. TSurrogate ConvertToSurrogate(in TValue value); } /// /// Functionality for populating one type from another. /// public interface IPopulator where TSurrogate : struct where TValue : class { /// /// Populates with values from . /// /// The surrogate. /// The value. void Populate(in TSurrogate surrogate, TValue value); } } namespace Orleans.Invocation { /// /// Applied to invokable base types (see TaskRequest) to indicate that instances of derived types should be returned directly from generated proxy methods rather than being passed to /// the runtime for invocation. This is used to support calling patterns other than request-response, such as streaming. /// [AttributeUsage(AttributeTargets.Class)] public sealed class ReturnValueProxyAttribute : Attribute { public ReturnValueProxyAttribute(string initializerMethodName) { InitializerMethodName = initializerMethodName; } /// /// The name of the method to /// public string InitializerMethodName { get; } } } ================================================ FILE: src/Orleans.Serialization.Abstractions/FrameworkPartAttribute.cs ================================================ using System; using System.ComponentModel; namespace Orleans.Metadata; /// /// Specifies that an assembly does not contain application code. /// [AttributeUsage(AttributeTargets.Assembly)] [EditorBrowsable(EditorBrowsableState.Never)] public sealed class FrameworkPartAttribute : Attribute { } ================================================ FILE: src/Orleans.Serialization.Abstractions/GenerateFieldIds.cs ================================================ namespace Orleans; /// /// This enum provides options for controlling the field id generation logic. /// public enum GenerateFieldIds { /// /// Only members explicitly annotated with a field id will be serialized. This is the default. /// None, /// /// Field ids will be automatically assigned to eligible public properties. To qualify, a property must have an accessible getter, and either an accessible setter or a corresponding constructor parameter. /// /// /// The presence of an explicit field id annotation on any member of a type will automatically disable automatic field id generation for that type. /// PublicProperties } ================================================ FILE: src/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.csproj ================================================ Microsoft.Orleans.Serialization.Abstractions Microsoft Orleans Serialization Abstractions Serialization abstractions for Microsoft Orleans $(DefaultTargetFrameworks);$(CompatibilityTargetFrameworks) true Orleans false README.md ================================================ FILE: src/Orleans.Serialization.Abstractions/Properties/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } ================================================ FILE: src/Orleans.Serialization.Abstractions/README.md ================================================ # Microsoft Orleans Serialization Abstractions ## Introduction Orleans Serialization Abstractions package provides the core interfaces and attributes needed for Orleans serialization. This package contains the definitions used for serialization but not the serialization implementation itself. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.Abstractions ``` This package is automatically included when you reference the Orleans Serialization package or Orleans SDK. ## Example ```csharp using Orleans.Serialization; // Define a serializable class [GenerateSerializer] public class MyData { [Id(0)] public string Name { get; set; } [Id(1)] public int Age { get; set; } [Id(2)] public List Tags { get; set; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Serialization in Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [Orleans type serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization-attributes) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.FSharp/FSharpCodecs.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization { /// /// Serializer for /// [RegisterSerializer] public sealed class FSharpUnitCodec : IFieldCodec { public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, Unit value) where TBufferWriter : IBufferWriter => ReferenceCodec.WriteNullReference(ref writer, fieldIdDelta); public Unit ReadValue(ref Reader reader, Field field) { field.EnsureWireType(WireType.Reference); ReferenceCodec.MarkValueField(reader.Session); var reference = reader.ReadVarUInt32(); if (reference != 0) throw new ReferenceNotFoundException(typeof(Unit), reference); return null; } } /// /// Copier for /// [RegisterCopier] public sealed class FSharpUnitCopier : ShallowCopier; /// /// Serializer for . /// /// The underlying type. [RegisterSerializer] public sealed class FSharpOptionCodec : IFieldCodec> { private readonly Type CodecType = typeof(FSharpOption); private readonly Type CodecFieldType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// public FSharpOptionCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpOption value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) return; writer.WriteStartObject(fieldIdDelta, expectedType, CodecType); if (FSharpOption.get_IsSome(value)) { _fieldCodec.WriteField(ref writer, 0, CodecFieldType, value.Value); } writer.WriteEndObject(); } /// public FSharpOption ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) return ReferenceCodec.ReadReference, TInput>(ref reader, field); field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); var result = FSharpOption.None; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: result = _fieldCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } /// /// Copier implementation for . /// /// The underlying value type of the option type. [RegisterCopier] public sealed class FSharpOptionCopier : IDeepCopier> { private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The value copier. public FSharpOptionCopier(IDeepCopier valueCopier) { _valueCopier = OrleansGeneratedCodeHelper.UnwrapService(this, valueCopier); } /// public FSharpOption DeepCopy(FSharpOption input, CopyContext context) { if (input is null || FSharpOption.get_IsNone(input)) { return input; } if (context.TryGetCopy>(input, out var result)) { return result; } result = _valueCopier.DeepCopy(input.Value, context); context.RecordCopy(input, result); return result; } } /// /// Serializer for . /// /// [RegisterSerializer] public class FSharpValueOptionCodec : IFieldCodec> { private readonly IFieldCodec _valueCodec; /// /// Initializes a new instance of the class. /// /// The item codec. public FSharpValueOptionCodec(IFieldCodec item1Codec) { _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); } /// void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpValueOption value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpValueOption), WireType.TagDelimited); if (value.IsSome) { _valueCodec.WriteField(ref writer, 0, typeof(T), value.Value); } writer.WriteEndObject(); } /// FSharpValueOption IFieldCodec>.ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); var result = FSharpValueOption.None; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: result = _valueCodec.ReadValue(ref reader, header); break; default: reader.ConsumeUnknownField(header); break; } } return result; } } /// /// Copier for . /// /// The underlying value type. [RegisterCopier] public sealed class FSharpValueOptionCopier : IDeepCopier> { private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The value copier. public FSharpValueOptionCopier(IDeepCopier valueCopier) { _valueCopier = OrleansGeneratedCodeHelper.UnwrapService(this, valueCopier); } /// public FSharpValueOption DeepCopy(FSharpValueOption input, CopyContext context) { if (input.IsNone) { return input; } else { return FSharpValueOption.Some(_valueCopier.DeepCopy(input.Value, context)); } } } /// /// Serializer for . /// [RegisterSerializer] public class FSharpChoiceCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; /// /// Initializes a new instance of the class. /// /// The codec for . /// The codec for . public FSharpChoiceCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); } /// void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpChoice value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpChoice), WireType.TagDelimited); switch (value) { case FSharpChoice.Choice1Of2 c1: _item1Codec.WriteField(ref writer, 1, ElementType1, c1.Item); break; case FSharpChoice.Choice2Of2 c2: _item2Codec.WriteField(ref writer, 2, ElementType2, c2.Item); break; } writer.WriteEndObject(); } FSharpChoice IFieldCodec>.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); FSharpChoice result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpChoice.NewChoice1Of2(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpChoice.NewChoice2Of2(_item2Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } [RegisterCopier] public class FSharpChoiceCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; public FSharpChoiceCopier(IDeepCopier copier1, IDeepCopier copier2) { _copier1 = copier1; _copier2 = copier2; } public FSharpChoice DeepCopy(FSharpChoice input, CopyContext context) { if (context.TryGetCopy(input, out FSharpChoice result)) { return result; } result = input switch { FSharpChoice.Choice1Of2 c1 => FSharpChoice.NewChoice1Of2(_copier1.DeepCopy(c1.Item, context)), FSharpChoice.Choice2Of2 c2 => FSharpChoice.NewChoice2Of2(_copier2.DeepCopy(c2.Item, context)), _ => throw new NotSupportedException($"Type {input.GetType()} is not supported"), }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpChoiceCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; public FSharpChoiceCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); } void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpChoice value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpChoice), WireType.TagDelimited); switch (value) { case FSharpChoice.Choice1Of3 c1: _item1Codec.WriteField(ref writer, 1, ElementType1, c1.Item); break; case FSharpChoice.Choice2Of3 c2: _item2Codec.WriteField(ref writer, 2, ElementType2, c2.Item); break; case FSharpChoice.Choice3Of3 c3: _item3Codec.WriteField(ref writer, 3, ElementType3, c3.Item); break; } writer.WriteEndObject(); } FSharpChoice IFieldCodec>.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); FSharpChoice result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpChoice.NewChoice1Of3(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpChoice.NewChoice2Of3(_item2Codec.ReadValue(ref reader, header)); break; case 3: result = FSharpChoice.NewChoice3Of3(_item3Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } [RegisterCopier] public class FSharpChoiceCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; public FSharpChoiceCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3) { _copier1 = copier1; _copier2 = copier2; _copier3 = copier3; } public FSharpChoice DeepCopy(FSharpChoice input, CopyContext context) { if (context.TryGetCopy(input, out FSharpChoice result)) { return result; } result = input switch { FSharpChoice.Choice1Of3 c1 => FSharpChoice.NewChoice1Of3(_copier1.DeepCopy(c1.Item, context)), FSharpChoice.Choice2Of3 c2 => FSharpChoice.NewChoice2Of3(_copier2.DeepCopy(c2.Item, context)), FSharpChoice.Choice3Of3 c3 => FSharpChoice.NewChoice3Of3(_copier3.DeepCopy(c3.Item, context)), _ => throw new NotSupportedException($"Type {input.GetType()} is not supported"), }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpChoiceCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; public FSharpChoiceCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); } void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpChoice value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpChoice), WireType.TagDelimited); switch (value) { case FSharpChoice.Choice1Of4 c1: _item1Codec.WriteField(ref writer, 1, ElementType1, c1.Item); break; case FSharpChoice.Choice2Of4 c2: _item2Codec.WriteField(ref writer, 2, ElementType2, c2.Item); break; case FSharpChoice.Choice3Of4 c3: _item3Codec.WriteField(ref writer, 3, ElementType3, c3.Item); break; case FSharpChoice.Choice4Of4 c4: _item4Codec.WriteField(ref writer, 4, ElementType4, c4.Item); break; } writer.WriteEndObject(); } FSharpChoice IFieldCodec>.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); FSharpChoice result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpChoice.NewChoice1Of4(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpChoice.NewChoice2Of4(_item2Codec.ReadValue(ref reader, header)); break; case 3: result = FSharpChoice.NewChoice3Of4(_item3Codec.ReadValue(ref reader, header)); break; case 4: result = FSharpChoice.NewChoice4Of4(_item4Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } [RegisterCopier] public class FSharpChoiceCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; public FSharpChoiceCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4) { _copier1 = copier1; _copier2 = copier2; _copier3 = copier3; _copier4 = copier4; } public FSharpChoice DeepCopy(FSharpChoice input, CopyContext context) { if (context.TryGetCopy(input, out FSharpChoice result)) { return result; } result = input switch { FSharpChoice.Choice1Of4 c1 => FSharpChoice.NewChoice1Of4(_copier1.DeepCopy(c1.Item, context)), FSharpChoice.Choice2Of4 c2 => FSharpChoice.NewChoice2Of4(_copier2.DeepCopy(c2.Item, context)), FSharpChoice.Choice3Of4 c3 => FSharpChoice.NewChoice3Of4(_copier3.DeepCopy(c3.Item, context)), FSharpChoice.Choice4Of4 c4 => FSharpChoice.NewChoice4Of4(_copier4.DeepCopy(c4.Item, context)), _ => throw new NotSupportedException($"Type {input.GetType()} is not supported"), }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpChoiceCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; public FSharpChoiceCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); } void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpChoice value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpChoice), WireType.TagDelimited); switch (value) { case FSharpChoice.Choice1Of5 c1: _item1Codec.WriteField(ref writer, 1, ElementType1, c1.Item); break; case FSharpChoice.Choice2Of5 c2: _item2Codec.WriteField(ref writer, 2, ElementType2, c2.Item); break; case FSharpChoice.Choice3Of5 c3: _item3Codec.WriteField(ref writer, 3, ElementType3, c3.Item); break; case FSharpChoice.Choice4Of5 c4: _item4Codec.WriteField(ref writer, 4, ElementType4, c4.Item); break; case FSharpChoice.Choice5Of5 c5: _item5Codec.WriteField(ref writer, 5, ElementType5, c5.Item); break; } writer.WriteEndObject(); } FSharpChoice IFieldCodec>.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); FSharpChoice result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpChoice.NewChoice1Of5(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpChoice.NewChoice2Of5(_item2Codec.ReadValue(ref reader, header)); break; case 3: result = FSharpChoice.NewChoice3Of5(_item3Codec.ReadValue(ref reader, header)); break; case 4: result = FSharpChoice.NewChoice4Of5(_item4Codec.ReadValue(ref reader, header)); break; case 5: result = FSharpChoice.NewChoice5Of5(_item5Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } [RegisterCopier] public class FSharpChoiceCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; public FSharpChoiceCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5) { _copier1 = copier1; _copier2 = copier2; _copier3 = copier3; _copier4 = copier4; _copier5 = copier5; } public FSharpChoice DeepCopy(FSharpChoice input, CopyContext context) { if (context.TryGetCopy(input, out FSharpChoice result)) { return result; } result = input switch { FSharpChoice.Choice1Of5 c1 => FSharpChoice.NewChoice1Of5(_copier1.DeepCopy(c1.Item, context)), FSharpChoice.Choice2Of5 c2 => FSharpChoice.NewChoice2Of5(_copier2.DeepCopy(c2.Item, context)), FSharpChoice.Choice3Of5 c3 => FSharpChoice.NewChoice3Of5(_copier3.DeepCopy(c3.Item, context)), FSharpChoice.Choice4Of5 c4 => FSharpChoice.NewChoice4Of5(_copier4.DeepCopy(c4.Item, context)), FSharpChoice.Choice5Of5 c5 => FSharpChoice.NewChoice5Of5(_copier5.DeepCopy(c5.Item, context)), _ => throw new NotSupportedException($"Type {input.GetType()} is not supported"), }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpChoiceCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T1); private readonly Type ElementType2 = typeof(T2); private readonly Type ElementType3 = typeof(T3); private readonly Type ElementType4 = typeof(T4); private readonly Type ElementType5 = typeof(T5); private readonly Type ElementType6 = typeof(T6); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; private readonly IFieldCodec _item3Codec; private readonly IFieldCodec _item4Codec; private readonly IFieldCodec _item5Codec; private readonly IFieldCodec _item6Codec; public FSharpChoiceCodec( IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); _item3Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item3Codec); _item4Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item4Codec); _item5Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item5Codec); _item6Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item6Codec); } void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpChoice value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpChoice), WireType.TagDelimited); switch (value) { case FSharpChoice.Choice1Of6 c1: _item1Codec.WriteField(ref writer, 1, ElementType1, c1.Item); break; case FSharpChoice.Choice2Of6 c2: _item2Codec.WriteField(ref writer, 2, ElementType2, c2.Item); break; case FSharpChoice.Choice3Of6 c3: _item3Codec.WriteField(ref writer, 3, ElementType3, c3.Item); break; case FSharpChoice.Choice4Of6 c4: _item4Codec.WriteField(ref writer, 4, ElementType4, c4.Item); break; case FSharpChoice.Choice5Of6 c5: _item5Codec.WriteField(ref writer, 5, ElementType5, c5.Item); break; case FSharpChoice.Choice6Of6 c6: _item6Codec.WriteField(ref writer, 6, ElementType6, c6.Item); break; } writer.WriteEndObject(); } FSharpChoice IFieldCodec>.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); FSharpChoice result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpChoice.NewChoice1Of6(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpChoice.NewChoice2Of6(_item2Codec.ReadValue(ref reader, header)); break; case 3: result = FSharpChoice.NewChoice3Of6(_item3Codec.ReadValue(ref reader, header)); break; case 4: result = FSharpChoice.NewChoice4Of6(_item4Codec.ReadValue(ref reader, header)); break; case 5: result = FSharpChoice.NewChoice5Of6(_item5Codec.ReadValue(ref reader, header)); break; case 6: result = FSharpChoice.NewChoice6Of6(_item6Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } } [RegisterCopier] public class FSharpChoiceCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; private readonly IDeepCopier _copier3; private readonly IDeepCopier _copier4; private readonly IDeepCopier _copier5; private readonly IDeepCopier _copier6; public FSharpChoiceCopier( IDeepCopier copier1, IDeepCopier copier2, IDeepCopier copier3, IDeepCopier copier4, IDeepCopier copier5, IDeepCopier copier6) { _copier1 = copier1; _copier2 = copier2; _copier3 = copier3; _copier4 = copier4; _copier5 = copier5; _copier6 = copier6; } public FSharpChoice DeepCopy(FSharpChoice input, CopyContext context) { if (context.TryGetCopy(input, out FSharpChoice result)) { return result; } result = input switch { FSharpChoice.Choice1Of6 c1 => FSharpChoice.NewChoice1Of6(_copier1.DeepCopy(c1.Item, context)), FSharpChoice.Choice2Of6 c2 => FSharpChoice.NewChoice2Of6(_copier2.DeepCopy(c2.Item, context)), FSharpChoice.Choice3Of6 c3 => FSharpChoice.NewChoice3Of6(_copier3.DeepCopy(c3.Item, context)), FSharpChoice.Choice4Of6 c4 => FSharpChoice.NewChoice4Of6(_copier4.DeepCopy(c4.Item, context)), FSharpChoice.Choice5Of6 c5 => FSharpChoice.NewChoice5Of6(_copier5.DeepCopy(c5.Item, context)), FSharpChoice.Choice6Of6 c6 => FSharpChoice.NewChoice6Of6(_copier6.DeepCopy(c6.Item, context)), _ => throw new NotSupportedException($"Type {input.GetType()} is not supported"), }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpRefCodec : GeneralizedReferenceTypeSurrogateCodec, FSharpRefSurrogate> { public FSharpRefCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } public override FSharpRef ConvertFromSurrogate(ref FSharpRefSurrogate surrogate) { return new FSharpRef(surrogate.Value); } public override void ConvertToSurrogate(FSharpRef value, ref FSharpRefSurrogate surrogate) { surrogate.Value = value.Value; } } [GenerateSerializer] public struct FSharpRefSurrogate { [Id(0)] public T Value { get; set; } } [RegisterCopier] public class FSharpRefCopier : IDeepCopier> { private readonly IDeepCopier _copier; public FSharpRefCopier(IDeepCopier copier) => _copier = copier; public FSharpRef DeepCopy(FSharpRef input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } result = input switch { not null => new FSharpRef(_copier.DeepCopy(input.Value, context)), null => null }; context.RecordCopy(input, result); return result; } } [RegisterSerializer] public class FSharpListCodec : GeneralizedReferenceTypeSurrogateCodec, FSharpListSurrogate> { public FSharpListCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } public override FSharpList ConvertFromSurrogate(ref FSharpListSurrogate surrogate) { if (surrogate.Value is null) return null; return ListModule.OfSeq(surrogate.Value); } public override void ConvertToSurrogate(FSharpList value, ref FSharpListSurrogate surrogate) { if (value is null) return; surrogate.Value = new(ListModule.ToSeq(value)); } } [GenerateSerializer] public struct FSharpListSurrogate { [Id(0)] public List Value { get; set; } } [RegisterCopier] public class FSharpListCopier : IDeepCopier> { private readonly IDeepCopier _copier; public FSharpListCopier(IDeepCopier copier) => _copier = copier; public FSharpList DeepCopy(FSharpList input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } result = ListModule.OfSeq(CopyElements(input, context)); context.RecordCopy(input, result); return result; IEnumerable CopyElements(FSharpList list, CopyContext context) { foreach (var element in list) { yield return _copier.DeepCopy(element, context); } } } } [RegisterSerializer] public class FSharpSetCodec : GeneralizedReferenceTypeSurrogateCodec, FSharpSetSurrogate> { public FSharpSetCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } public override FSharpSet ConvertFromSurrogate(ref FSharpSetSurrogate surrogate) { if (surrogate.Value is null) return null; return new FSharpSet(surrogate.Value); } public override void ConvertToSurrogate(FSharpSet value, ref FSharpSetSurrogate surrogate) { if (value is null) return; surrogate.Value = value.ToList(); } } [GenerateSerializer] public struct FSharpSetSurrogate { [Id(0)] public List Value { get; set; } } [RegisterCopier] public class FSharpSetCopier : IDeepCopier> { private readonly IDeepCopier _copier; public FSharpSetCopier(IDeepCopier copier) => _copier = copier; public FSharpSet DeepCopy(FSharpSet input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } result = SetModule.OfSeq(CopyElements(input, context)); context.RecordCopy(input, result); return result; IEnumerable CopyElements(FSharpSet vals, CopyContext context) { foreach (var element in vals) { yield return _copier.DeepCopy(element, context); } } } } [RegisterSerializer] public class FSharpMapCodec : GeneralizedReferenceTypeSurrogateCodec, FSharpMapSurrogate> { public FSharpMapCodec(IValueSerializer> surrogateSerializer) : base(surrogateSerializer) { } public override FSharpMap ConvertFromSurrogate(ref FSharpMapSurrogate surrogate) { if (surrogate.Value is null) return null; return new FSharpMap(surrogate.Value); } public override void ConvertToSurrogate(FSharpMap value, ref FSharpMapSurrogate surrogate) { if (value is null) return; surrogate.Value = new(value.Count); surrogate.Value.AddRange(MapModule.ToSeq(value)); } } [GenerateSerializer] public struct FSharpMapSurrogate { [Id(0)] public List> Value { get; set; } } [RegisterCopier] public class FSharpMapCopier : IDeepCopier> { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; public FSharpMapCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } public FSharpMap DeepCopy(FSharpMap input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } result = MapModule.OfSeq(CopyElements(input, context)); context.RecordCopy(input, result); return result; IEnumerable> CopyElements(FSharpMap vals, CopyContext context) { foreach (var element in vals) { yield return Tuple.Create(_keyCopier.DeepCopy(element.Key, context), _valueCopier.DeepCopy(element.Value, context)); } } } } [RegisterSerializer] public class FSharpResultCodec : IFieldCodec>, IDerivedTypeCodec { private readonly Type ElementType1 = typeof(T); private readonly Type ElementType2 = typeof(TError); private readonly IFieldCodec _item1Codec; private readonly IFieldCodec _item2Codec; public FSharpResultCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { _item1Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item1Codec); _item2Codec = OrleansGeneratedCodeHelper.UnwrapService(this, item2Codec); } void IFieldCodec>.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, FSharpResult value) { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(FSharpResult), WireType.TagDelimited); if (value.IsError) { _item2Codec.WriteField(ref writer, 2, ElementType2, value.ErrorValue); } else { _item1Codec.WriteField(ref writer, 1, ElementType1, value.ResultValue); } writer.WriteEndObject(); } FSharpResult IFieldCodec>.ReadValue(ref Reader reader, Field field) { field.EnsureWireTypeTagDelimited(); ReferenceCodec.MarkValueField(reader.Session); FSharpResult result = default; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 1: result = FSharpResult.NewOk(_item1Codec.ReadValue(ref reader, header)); break; case 2: result = FSharpResult.NewError(_item2Codec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } return result; } } [RegisterCopier] public class FSharpResultCopier : IDeepCopier>, IDerivedTypeCopier { private readonly IDeepCopier _copier1; private readonly IDeepCopier _copier2; public FSharpResultCopier(IDeepCopier copier1, IDeepCopier copier2) { _copier1 = copier1; _copier2 = copier2; } public FSharpResult DeepCopy(FSharpResult input, CopyContext context) { if (input.IsError) { return FSharpResult.NewError(_copier2.DeepCopy(input.ErrorValue, context)); } else { return FSharpResult.NewOk(_copier1.DeepCopy(input.ResultValue, context)); } } } } ================================================ FILE: src/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.csproj ================================================ README.md Microsoft.Orleans.Serialization.FSharp $(DefaultTargetFrameworks);netstandard2.1 F# core type support for Orleans.Serialization true false ================================================ FILE: src/Orleans.Serialization.FSharp/README.md ================================================ # Microsoft Orleans Serialization for F# ## Introduction Microsoft Orleans Serialization for F# provides serialization support for F# specific types in Microsoft Orleans. This package enables seamless integration of F# types like discriminated unions, records, and other F# specific constructs with Orleans serialization system. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.FSharp ``` ## Example - Configuring F# Serialization ```fsharp open Microsoft.Extensions.Hosting open Orleans.Hosting let builder = Host.CreateApplicationBuilder(args) .UseOrleans(fun siloBuilder -> siloBuilder .UseLocalhostClustering() // F# serialization is automatically configured when the package is referenced |> ignore) // Run the host await builder.RunAsync() ``` ## Example - Using F# Types with Orleans ```fsharp // Define F# discriminated union and record types [] type UserRole = | [] Admin | [] Moderator | [] User of level:int [] type UserRecord = { [] Id: string [] Name: string [] Role: UserRole [] Tags: string list } // Define a grain interface type IFSharpGrain = inherit Orleans.IGrainWithStringKey abstract member GetUser: unit -> Task abstract member UpdateUser: UserRecord -> Task // Grain implementation type FSharpGrain() = inherit Orleans.Grain() let mutable userData = Unchecked.defaultof interface IFSharpGrain with member this.GetUser() = Task.FromResult(userData) member this.UpdateUser(user) = userData <- user Task.CompletedTask ``` ## Example - Client Code Using F# Grain ```fsharp // Client-side code let client = clientBuilder.Build() let grain = client.GetGrain("user1") // Create an F# record with discriminated union let user = { Id = "user1" Name = "F# User" Role = UserRole.Admin Tags = ["functional"; "programming"] } // Call the grain with F# types grain.UpdateUser(user) |> Async.AwaitTask |> ignore let retrievedUser = grain.GetUser() |> Async.AwaitTask |> Async.RunSynchronously ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [F# Documentation](https://learn.microsoft.com/en-us/dotnet/fsharp/) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.MemoryPack/MemoryPackCodec.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; using MemoryPack; using Microsoft.Extensions.Options; using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// A serialization codec which uses . /// /// /// MemoryPack codec performs slightly worse than default Orleans serializer, if performance is critical for your application, consider using default serialization. /// [Alias(WellKnownAlias)] public class MemoryPackCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter { private static readonly ConcurrentDictionary SupportedTypes = new(); private static readonly Type SelfType = typeof(MemoryPackCodec); private readonly ICodecSelector[] _serializableTypeSelectors; private readonly ICopierSelector[] _copyableTypeSelectors; private readonly MemoryPackCodecOptions _options; /// /// The well-known type alias for this codec. /// public const string WellKnownAlias = "memorypack"; /// /// Initializes a new instance of the class. /// /// /// Filters used to indicate which types should be serialized by this codec. /// Filters used to indicate which types should be copied by this codec. /// The MemoryPack codec options. public MemoryPackCodec( IEnumerable serializableTypeSelectors, IEnumerable copyableTypeSelectors, IOptions options) { _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _options = options.Value; } /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } // The schema type when serializing the field is the type of the codec. writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited); // Write the type name ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType()); var bufferWriter = new BufferWriterBox(new()); try { MemoryPackSerializer.Serialize(value.GetType(), bufferWriter, value, _options.SerializerOptions); ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed); writer.WriteVarUInt32((uint)bufferWriter.Value.Length); bufferWriter.Value.CopyTo(ref writer); } finally { bufferWriter.Value.Dispose(); } writer.WriteEndObject(); } /// object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); object result = null; Type type = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: ReferenceCodec.MarkValueField(reader.Session); type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 1: if (type is null) { ThrowTypeFieldMissing(); } ReferenceCodec.MarkValueField(reader.Session); var length = reader.ReadVarUInt32(); var bufferWriter = new BufferWriterBox(new()); try { reader.ReadBytes(ref bufferWriter, (int)length); result = MemoryPackSerializer.Deserialize(type, bufferWriter.Value.AsReadOnlySequence(), _options.SerializerOptions); } finally { bufferWriter.Value.Dispose(); } break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } /// bool IGeneralizedCodec.IsSupportedType(Type type) { if (type == SelfType) { return true; } if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _serializableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsSerializableType?.Invoke(type) is bool value) { return value; } return IsMemoryPackContract(type); } /// object IDeepCopier.DeepCopy(object input, CopyContext context) { if (context.TryGetCopy(input, out object result)) { return result; } var bufferWriter = new BufferWriterBox(new()); try { MemoryPackSerializer.Serialize(input.GetType(), bufferWriter, input, _options.SerializerOptions); var sequence = bufferWriter.Value.AsReadOnlySequence(); result = MemoryPackSerializer.Deserialize(input.GetType(), sequence, _options.SerializerOptions); } finally { bufferWriter.Value.Dispose(); } context.RecordCopy(input, result); return result; } /// bool IGeneralizedCopier.IsSupportedType(Type type) { if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _copyableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsCopyableType?.Invoke(type) is bool value) { return value; } return IsMemoryPackContract(type); } /// bool? ITypeFilter.IsTypeAllowed(Type type) => (((IGeneralizedCopier)this).IsSupportedType(type) || ((IGeneralizedCodec)this).IsSupportedType(type)) ? true : null; private static bool IsMemoryPackContract(Type type) { if (SupportedTypes.TryGetValue(type, out bool isMemoryPackContract)) { return isMemoryPackContract; } isMemoryPackContract = type.GetCustomAttribute() is not null; SupportedTypes.TryAdd(type, isMemoryPackContract); return isMemoryPackContract; } private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field."); } ================================================ FILE: src/Orleans.Serialization.MemoryPack/MemoryPackCodecOptions.cs ================================================ using System; using MemoryPack; namespace Orleans.Serialization; /// /// Options for . /// public class MemoryPackCodecOptions { /// /// Gets or sets the . /// public MemoryPackSerializerOptions SerializerOptions { get; set; } = MemoryPackSerializerOptions.Default; /// /// Gets or sets a delegate used to determine if a type is supported by the MemoryPack serializer for serialization and deserialization. /// public Func IsSerializableType { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the MemoryPack serializer for copying. /// public Func IsCopyableType { get; set; } } ================================================ FILE: src/Orleans.Serialization.MemoryPack/Orleans.Serialization.MemoryPack.csproj ================================================  README.md Microsoft.Orleans.Serialization.MemoryPack $(DefaultTargetFrameworks);netstandard2.1 MemoryPack integration for Orleans.Serialization true false ================================================ FILE: src/Orleans.Serialization.MemoryPack/README.md ================================================ # Microsoft Orleans Serialization for MemoryPack ## Introduction Microsoft Orleans Serialization for MemoryPack provides MemoryPack serialization support for Microsoft Orleans using the MemoryPack format. This high-performance binary serialization format is ideal for scenarios requiring efficient serialization and deserialization. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.MemoryPack ``` ## Example - Configuring MemoryPack Serialization ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Serialization; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure MemoryPack as a serializer .AddSerializer(serializerBuilder => serializerBuilder.AddMemoryPackSerializer()); }); // Run the host await builder.RunAsync(); ``` ## Example - Using MemoryPack with a Custom Type ```csharp using Orleans; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using MemoryPack; namespace ExampleGrains; // Define a class with MemoryPack attributes [MemoryPackable] public partial class MyMemoryPackClass { public string Name { get; set; } public int Age { get; set; } public List Tags { get; set; } } // You can use it directly in your grain interfaces and implementation public interface IMyGrain : IGrainWithStringKey { Task GetData(); Task SetData(MyMemoryPackClass data); } public class MyGrain : Grain, IMyGrain { private MyMemoryPackClass _data; public Task GetData() { return Task.FromResult(_data); } public Task SetData(MyMemoryPackClass data) { _data = data; return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [MemoryPack for C#](https://github.com/Cysharp/MemoryPack) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.MemoryPack/SerializationHostingExtensions.cs ================================================ using System; using MemoryPack; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities.Internal; namespace Orleans.Serialization; /// /// Extension method for . /// public static class SerializationHostingExtensions { private static readonly ServiceDescriptor ServiceDescriptor = new(typeof(MemoryPackCodec), typeof(MemoryPackCodec)); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// The MemoryPack serializer options. public static ISerializerBuilder AddMemoryPackSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable = null, Func isCopyable = null, MemoryPackSerializerOptions memoryPackSerializerOptions = null) { return serializerBuilder.AddMemoryPackSerializer( isSerializable, isCopyable, optionsBuilder => optionsBuilder.Configure(options => { if (memoryPackSerializerOptions is not null) { options.SerializerOptions = memoryPackSerializerOptions; } }) ); } /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// A delegate used to configure the options for the MemoryPack codec. public static ISerializerBuilder AddMemoryPackSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable, Func isCopyable, Action> configureOptions = null) { var services = serializerBuilder.Services; if (configureOptions != null) { configureOptions(services.AddOptions()); } if (isSerializable != null) { services.AddSingleton(new DelegateCodecSelector { CodecName = MemoryPackCodec.WellKnownAlias, IsSupportedTypeDelegate = isSerializable }); } if (isCopyable != null) { services.AddSingleton(new DelegateCopierSelector { CopierName = MemoryPackCodec.WellKnownAlias, IsSupportedTypeDelegate = isCopyable }); } if (!services.Contains(ServiceDescriptor)) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); services.AddFromExisting(); serializerBuilder.Configure(options => options.WellKnownTypeAliases[MemoryPackCodec.WellKnownAlias] = typeof(MemoryPackCodec)); } return serializerBuilder; } } ================================================ FILE: src/Orleans.Serialization.MessagePack/MessagePackCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using MessagePack; using Microsoft.Extensions.Options; using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// A serialization codec which uses . /// /// /// MessagePack codec performs slightly worse than default Orleans serializer, if performance is critical for your application, consider using default serialization. /// [Alias(WellKnownAlias)] public class MessagePackCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter { private static readonly ConcurrentDictionary SupportedTypes = new(); private static readonly Type SelfType = typeof(MessagePackCodec); private readonly ICodecSelector[] _serializableTypeSelectors; private readonly ICopierSelector[] _copyableTypeSelectors; private readonly MessagePackCodecOptions _options; /// /// The well-known type alias for this codec. /// public const string WellKnownAlias = "msgpack"; /// /// Initializes a new instance of the class. /// /// /// Filters used to indicate which types should be serialized by this codec. /// Filters used to indicate which types should be copied by this codec. /// The MessagePack codec options. public MessagePackCodec( IEnumerable serializableTypeSelectors, IEnumerable copyableTypeSelectors, IOptions options) { _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _options = options.Value; } /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } // The schema type when serializing the field is the type of the codec. writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited); // Write the type name ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType()); var bufferWriter = new BufferWriterBox(new()); try { var msgPackWriter = new MessagePackWriter(bufferWriter); MessagePackSerializer.Serialize(value.GetType(), ref msgPackWriter, value, _options.SerializerOptions); msgPackWriter.Flush(); ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed); writer.WriteVarUInt32((uint)bufferWriter.Value.Length); bufferWriter.Value.CopyTo(ref writer); } finally { bufferWriter.Value.Dispose(); } writer.WriteEndObject(); } /// object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); object result = null; Type type = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: ReferenceCodec.MarkValueField(reader.Session); type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 1: if (type is null) { ThrowTypeFieldMissing(); } ReferenceCodec.MarkValueField(reader.Session); var length = reader.ReadVarUInt32(); var bufferWriter = new BufferWriterBox(new()); try { reader.ReadBytes(ref bufferWriter, (int)length); result = MessagePackSerializer.Deserialize(type, bufferWriter.Value.AsReadOnlySequence(), _options.SerializerOptions); } finally { bufferWriter.Value.Dispose(); } break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } /// bool IGeneralizedCodec.IsSupportedType(Type type) { if (type == SelfType) { return true; } if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _serializableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsSerializableType?.Invoke(type) is bool value) { return value; } return IsMessagePackContract(type, _options.AllowDataContractAttributes); } /// object IDeepCopier.DeepCopy(object input, CopyContext context) { if (context.TryGetCopy(input, out object result)) { return result; } var bufferWriter = new BufferWriterBox(new()); try { var msgPackWriter = new MessagePackWriter(bufferWriter); MessagePackSerializer.Serialize(input.GetType(), ref msgPackWriter, input, _options.SerializerOptions); msgPackWriter.Flush(); var sequence = bufferWriter.Value.AsReadOnlySequence(); result = MessagePackSerializer.Deserialize(input.GetType(), sequence, _options.SerializerOptions); } finally { bufferWriter.Value.Dispose(); } context.RecordCopy(input, result); return result; } /// bool IGeneralizedCopier.IsSupportedType(Type type) { if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _copyableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsCopyableType?.Invoke(type) is bool value) { return value; } return IsMessagePackContract(type, _options.AllowDataContractAttributes); } /// bool? ITypeFilter.IsTypeAllowed(Type type) => (((IGeneralizedCopier)this).IsSupportedType(type) || ((IGeneralizedCodec)this).IsSupportedType(type)) ? true : null; private static bool IsMessagePackContract(Type type, bool allowDataContractAttribute) { if (SupportedTypes.TryGetValue(type, out bool isMsgPackContract)) { return isMsgPackContract; } isMsgPackContract = type.GetCustomAttribute() is not null; if (!isMsgPackContract && allowDataContractAttribute) { isMsgPackContract = type.GetCustomAttribute() is DataContractAttribute; } SupportedTypes.TryAdd(type, isMsgPackContract); return isMsgPackContract; } private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field."); } ================================================ FILE: src/Orleans.Serialization.MessagePack/MessagePackCodecOptions.cs ================================================ using System; using System.Runtime.Serialization; using MessagePack; namespace Orleans.Serialization; /// /// Options for . /// public class MessagePackCodecOptions { /// /// Gets or sets the . /// public MessagePackSerializerOptions SerializerOptions { get; set; } = MessagePackSerializerOptions.Standard; /// /// Get or sets flag that allows the use of marked contracts for MessagePackSerializer. /// public bool AllowDataContractAttributes { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the MessagePack serializer for serialization and deserialization. /// public Func IsSerializableType { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the MessagePack serializer for copying. /// public Func IsCopyableType { get; set; } } ================================================ FILE: src/Orleans.Serialization.MessagePack/Orleans.Serialization.MessagePack.csproj ================================================ README.md Microsoft.Orleans.Serialization.MessagePack $(DefaultTargetFrameworks);netstandard2.1 MessagePack integration for Orleans.Serialization true false ================================================ FILE: src/Orleans.Serialization.MessagePack/README.md ================================================ # Microsoft Orleans Serialization for MessagePack ## Introduction Microsoft Orleans Serialization for MessagePack provides MessagePack serialization support for Microsoft Orleans using the MessagePack format. This high-performance binary serialization format is ideal for scenarios requiring efficient serialization and deserialization. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.MessagePack ``` ## Example - Configuring MessagePack Serialization ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Serialization; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure MessagePack as a serializer .AddSerializer(serializerBuilder => serializerBuilder.AddMessagePackSerializer()); }); // Run the host await builder.RunAsync(); ``` ## Example - Using MessagePack with a Custom Type ```csharp using Orleans; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Configuration; using Orleans.Serialization.Serializers; using MessagePack; namespace ExampleGrains; // Define a class with MessagePack attributes [MessagePackObject] public class MyMessagePackClass { [Key(0)] public string Name { get; set; } [Key(1)] public int Age { get; set; } [Key(2)] public List Tags { get; set; } } // You can use it directly in your grain interfaces and implementation public interface IMyGrain : IGrainWithStringKey { Task GetData(); Task SetData(MyMessagePackClass data); } public class MyGrain : Grain, IMyGrain { private MyMessagePackClass _data; public Task GetData() { return Task.FromResult(_data); } public Task SetData(MyMessagePackClass data) { _data = data; return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [MessagePack for C#](https://github.com/neuecc/MessagePack-CSharp) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.MessagePack/SerializationHostingExtensions.cs ================================================ using System; using MessagePack; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities.Internal; namespace Orleans.Serialization; /// /// Extension method for . /// public static class SerializationHostingExtensions { private static readonly ServiceDescriptor ServiceDescriptor = new (typeof(MessagePackCodec), typeof(MessagePackCodec)); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// The MessagePack serializer options. public static ISerializerBuilder AddMessagePackSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable = null, Func isCopyable = null, MessagePackSerializerOptions messagePackSerializerOptions = null) { return serializerBuilder.AddMessagePackSerializer( isSerializable, isCopyable, optionsBuilder => optionsBuilder.Configure(options => { if (messagePackSerializerOptions is not null) { options.SerializerOptions = messagePackSerializerOptions; } }) ); } /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// A delegate used to configure the options for the MessagePack codec. public static ISerializerBuilder AddMessagePackSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable, Func isCopyable, Action> configureOptions = null) { var services = serializerBuilder.Services; if (configureOptions != null) { configureOptions(services.AddOptions()); } if (isSerializable != null) { services.AddSingleton(new DelegateCodecSelector { CodecName = MessagePackCodec.WellKnownAlias, IsSupportedTypeDelegate = isSerializable }); } if (isCopyable != null) { services.AddSingleton(new DelegateCopierSelector { CopierName = MessagePackCodec.WellKnownAlias, IsSupportedTypeDelegate = isCopyable }); } if (!services.Contains(ServiceDescriptor)) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); services.AddFromExisting(); serializerBuilder.Configure(options => options.WellKnownTypeAliases[MessagePackCodec.WellKnownAlias] = typeof(MessagePackCodec)); } return serializerBuilder; } } ================================================ FILE: src/Orleans.Serialization.NewtonsoftJson/NewtonsoftJsonCodec.cs ================================================ using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Metadata; using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; [Alias(WellKnownAlias)] public class NewtonsoftJsonCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter { private static readonly Type SelfType = typeof(NewtonsoftJsonCodec); private readonly NewtonsoftJsonCodecOptions _options; private readonly ICodecSelector[] _serializableTypeSelectors; private readonly ICopierSelector[] _copyableTypeSelectors; private readonly JsonSerializer _serializer; /// /// The well-known type alias for this codec. /// public const string WellKnownAlias = "json.net"; /// /// Initializes a new instance of the class. /// /// Filters used to indicate which types should be serialized by this codec. /// Filters used to indicate which types should be copied by this codec. /// The JSON codec options. public NewtonsoftJsonCodec( IEnumerable serializableTypeSelectors, IEnumerable copyableTypeSelectors, IOptions options) { _options = options.Value; _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _serializer = JsonSerializer.Create(_options.SerializerSettings); } void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } // The schema type when serializing the field is the type of the codec. // In practice it could be any unique type as long as this codec is registered as the handler. // By checking against the codec type in IsSupportedType, the codec could also just be registered as an IGenericCodec. // Note that the codec is responsible for serializing the type of the value itself. writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited); // Write the type name ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType()); // Write the serialized payload var serializedValue = JsonConvert.SerializeObject(value, _options.SerializerSettings); StringCodec.WriteField(ref writer, 1, serializedValue); writer.WriteEndObject(); } object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); object result = null; Type type = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: ReferenceCodec.MarkValueField(reader.Session); type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 1: if (type is null) { ThrowTypeFieldMissing(); } // To possibly improve efficiency, this could be converted to read a ReadOnlySequence instead of a byte array. var serializedValue = StringCodec.ReadValue(ref reader, header); result = JsonConvert.DeserializeObject(serializedValue, type, _options.SerializerSettings); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; default: reader.ConsumeUnknownField(header); break; } } return result; } bool IGeneralizedCodec.IsSupportedType(Type type) { if (type == SelfType) { return true; } if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } if (IsNativelySupportedType(type)) { return true; } foreach (var selector in _serializableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsSerializableType?.Invoke(type) is bool value) { return value; } return false; } /// object IDeepCopier.DeepCopy(object input, CopyContext context) { if (context.TryGetCopy(input, out object result)) return result; var stream = PooledBufferStream.Rent(); try { var type = input.GetType(); var streamWriter = new StreamWriter(stream); using (var textWriter = new JsonTextWriter(streamWriter) { CloseOutput = false }) _serializer.Serialize(textWriter, input, type); streamWriter.Flush(); stream.Position = 0; var streamReader = new StreamReader(stream); using var jsonReader = new JsonTextReader(streamReader); result = _serializer.Deserialize(jsonReader, type); } finally { PooledBufferStream.Return(stream); } context.RecordCopy(input, result); return result; } /// bool IGeneralizedCopier.IsSupportedType(Type type) { if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } if (IsNativelySupportedType(type)) { return true; } foreach (var selector in _copyableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsCopyableType?.Invoke(type) is bool value) { return value; } return false; } private static bool IsNativelySupportedType(Type type) { return type == typeof(JObject) || type == typeof(JArray) || type == typeof(JProperty) || type == typeof(JRaw) || type == typeof(JValue) || type == typeof(JConstructor) || typeof(JContainer).IsAssignableFrom(type) || typeof(JToken).IsAssignableFrom(type); } private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field."); private bool IsSupportedType(Type type) => ((IGeneralizedCodec)this).IsSupportedType(type) || ((IGeneralizedCopier)this).IsSupportedType(type); bool? ITypeFilter.IsTypeAllowed(Type type) => IsSupportedType(type) ? true : null; } ================================================ FILE: src/Orleans.Serialization.NewtonsoftJson/NewtonsoftJsonCodecOptions.cs ================================================ using Newtonsoft.Json; using System; namespace Orleans.Serialization; /// /// Options for . /// public class NewtonsoftJsonCodecOptions { /// /// Gets or sets the . /// public JsonSerializerSettings SerializerSettings { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the JSON serializer for serialization and deserialization. /// public Func IsSerializableType { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the JSON serializer for copying. /// public Func IsCopyableType { get; set; } } ================================================ FILE: src/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.csproj ================================================ README.md Microsoft.Orleans.Serialization.NewtonsoftJson $(DefaultTargetFrameworks);netstandard2.1 Newtonsoft.Json integration for Orleans.Serialization true false ================================================ FILE: src/Orleans.Serialization.NewtonsoftJson/README.md ================================================ # Microsoft Orleans Serialization for Newtonsoft.Json ## Introduction Microsoft Orleans Serialization for Newtonsoft.Json provides JSON serialization support for Microsoft Orleans using the popular Newtonsoft.Json library. This allows you to use the comprehensive JSON features of Newtonsoft.Json within your Orleans application. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.NewtonsoftJson ``` ## Example - Configuring Newtonsoft.Json Serialization ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Serialization; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Newtonsoft.Json as a serializer .AddSerializer(serializerBuilder => serializerBuilder.AddNewtonsoftJsonSerializer(type => type.Namespace.StartsWith("ExampleGrains"))); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Newtonsoft.Json with a Custom Type ```csharp using Orleans; using Newtonsoft.Json; namespace ExampleGrains; // Define a class with Newtonsoft.Json attributes public class MyJsonClass { [JsonProperty("full_name")] public string Name { get; set; } [JsonProperty("age")] public int Age { get; set; } [JsonProperty("tags")] public List Tags { get; set; } [JsonIgnore] public string SecretData { get; set; } } // You can use it directly in your grain interfaces and implementation public interface IMyGrain : IGrainWithStringKey { Task GetData(); Task SetData(MyJsonClass data); } public class MyGrain : Grain, IMyGrain { private MyJsonClass _data; public Task GetData() { return Task.FromResult(_data); } public Task SetData(MyJsonClass data) { _data = data; return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [Newtonsoft.Json Documentation](https://www.newtonsoft.com/json/help/html/Introduction.htm) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.NewtonsoftJson/SerializationHostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities.Internal; using System; namespace Orleans.Serialization; /// /// Extension method for . /// public static class SerializationHostingExtensions { private static readonly ServiceDescriptor ServiceDescriptor = new (typeof(NewtonsoftJsonCodec), typeof(NewtonsoftJsonCodec)); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized and copied by this codec. /// The JSON serializer settings. public static ISerializerBuilder AddNewtonsoftJsonSerializer( this ISerializerBuilder serializerBuilder, Func isSupported, JsonSerializerSettings jsonSerializerSettings = null) => serializerBuilder.AddNewtonsoftJsonSerializer( isSupported, optionsBuilder => optionsBuilder.Configure(options => { if (jsonSerializerSettings is not null) { options.SerializerSettings = jsonSerializerSettings; } })); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized and copied by this codec. /// A delegate used to configure the options for the JSON serializer. public static ISerializerBuilder AddNewtonsoftJsonSerializer( this ISerializerBuilder serializerBuilder, Func isSupported, Action> configureOptions) => serializerBuilder.AddNewtonsoftJsonSerializer( isSupported, isSupported, configureOptions); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// A delegate used to configure the options for the JSON serializer. public static ISerializerBuilder AddNewtonsoftJsonSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable, Func isCopyable, Action> configureOptions) { var services = serializerBuilder.Services; if (configureOptions != null) { configureOptions(services.AddOptions()); } if (isSerializable != null) { services.AddSingleton(new DelegateCodecSelector { CodecName = NewtonsoftJsonCodec.WellKnownAlias, IsSupportedTypeDelegate = isSerializable }); } if (isCopyable != null) { services.AddSingleton(new DelegateCopierSelector { CopierName = NewtonsoftJsonCodec.WellKnownAlias, IsSupportedTypeDelegate = isCopyable }); } if (!services.Contains(ServiceDescriptor)) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); services.AddFromExisting(); serializerBuilder.Configure(options => options.WellKnownTypeAliases[NewtonsoftJsonCodec.WellKnownAlias] = typeof(NewtonsoftJsonCodec)); } return serializerBuilder; } } ================================================ FILE: src/Orleans.Serialization.SystemTextJson/JsonCodec.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Options; using Orleans.Metadata; using Orleans.Serialization.Buffers; using Orleans.Serialization.Buffers.Adaptors; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// A serialization codec which uses . /// [Alias(WellKnownAlias)] public class JsonCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter { private static readonly Type SelfType = typeof(JsonCodec); private readonly ICodecSelector[] _serializableTypeSelectors; private readonly ICopierSelector[] _copyableTypeSelectors; private readonly JsonCodecOptions _options; /// /// The well-known type alias for this codec. /// public const string WellKnownAlias = "json"; /// /// Initializes a new instance of the class. /// /// Filters used to indicate which types should be serialized by this codec. /// Filters used to indicate which types should be copied by this codec. /// The JSON codec options. public JsonCodec( IEnumerable serializableTypeSelectors, IEnumerable copyableTypeSelectors, IOptions options) { _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _options = options.Value; } /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } // The schema type when serializing the field is the type of the codec. // In practice it could be any unique type as long as this codec is registered as the handler. // By checking against the codec type in IsSupportedType, the codec could also just be registered as an IGenericCodec. // Note that the codec is responsible for serializing the type of the value itself. writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited); // Write the type name ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType()); // Write the serialized payload // Note that the Utf8JsonWriter and PooledBuffer could be pooled as long as they're correctly // reset at the end of each use. var bufferWriter = new BufferWriterBox(new PooledBuffer()); try { var jsonWriter = new Utf8JsonWriter(bufferWriter, _options.WriterOptions); JsonSerializer.Serialize(jsonWriter, value, _options.SerializerOptions); jsonWriter.Flush(); ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed); writer.WriteVarUInt32((uint)bufferWriter.Value.Length); bufferWriter.Value.CopyTo(ref writer); } finally { bufferWriter.Value.Dispose(); } writer.WriteEndObject(); } /// object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); object result = null; Type type = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: ReferenceCodec.MarkValueField(reader.Session); type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 1: if (type is null) { ThrowTypeFieldMissing(); } ReferenceCodec.MarkValueField(reader.Session); var length = reader.ReadVarUInt32(); // To possibly improve efficiency, this could be converted to read a ReadOnlySequence instead of a byte array. var tempBuffer = new PooledBuffer(); try { reader.ReadBytes(ref tempBuffer, (int)length); var sequence = tempBuffer.AsReadOnlySequence(); var jsonReader = new Utf8JsonReader(sequence, _options.ReaderOptions); if (typeof(JsonNode).IsAssignableFrom(type)) { result = JsonNode.Parse(ref jsonReader); } else { result = JsonSerializer.Deserialize(ref jsonReader, type, _options.SerializerOptions); } } finally { tempBuffer.Dispose(); } break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } /// bool IGeneralizedCodec.IsSupportedType(Type type) { if (type == SelfType) { return true; } if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } if (IsNativelySupportedType(type)) { return true; } foreach (var selector in _serializableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsSerializableType?.Invoke(type) is bool value) { return value; } return false; } private static bool IsNativelySupportedType(Type type) { // Add types natively supported by System.Text.Json // From https://github.com/dotnet/runtime/blob/2c4d0df3b146f8322f676b83a53ca21a065bdfc7/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs#L1792-L1797 return type == typeof(JsonElement) || type == typeof(JsonDocument) || type == typeof(JsonArray) || type == typeof(JsonObject) || type == typeof(JsonValue) || typeof(JsonNode).IsAssignableFrom(type); } /// object IDeepCopier.DeepCopy(object input, CopyContext context) { if (context.TryGetCopy(input, out object result)) { return result; } if (input is JsonNode jsonNode) { var copy = jsonNode.DeepClone(); context.RecordCopy(input, copy); return copy; } var bufferWriter = new BufferWriterBox(new PooledBuffer()); try { var jsonWriter = new Utf8JsonWriter(bufferWriter, _options.WriterOptions); JsonSerializer.Serialize(jsonWriter, input, _options.SerializerOptions); jsonWriter.Flush(); var sequence = bufferWriter.Value.AsReadOnlySequence(); var jsonReader = new Utf8JsonReader(sequence, _options.ReaderOptions); result = JsonSerializer.Deserialize(ref jsonReader, input.GetType(), _options.SerializerOptions); } finally { bufferWriter.Value.Dispose(); } context.RecordCopy(input, result); return result; } /// bool IGeneralizedCopier.IsSupportedType(Type type) { if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } if (IsNativelySupportedType(type)) { return true; } foreach (var selector in _copyableTypeSelectors) { if (selector.IsSupportedType(type)) { return true; } } if (_options.IsCopyableType?.Invoke(type) is bool value) { return value; } return false; } /// bool? ITypeFilter.IsTypeAllowed(Type type) => (((IGeneralizedCopier)this).IsSupportedType(type) || ((IGeneralizedCodec)this).IsSupportedType(type)) ? true : null; private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field."); } ================================================ FILE: src/Orleans.Serialization.SystemTextJson/JsonCodecOptions.cs ================================================ using System; using System.Text.Json; namespace Orleans.Serialization; /// /// Options for . /// public class JsonCodecOptions { /// /// Gets or sets the . /// public JsonSerializerOptions SerializerOptions { get; set; } = new(); /// /// Gets or sets the . /// public JsonReaderOptions ReaderOptions { get; set; } /// /// Gets or sets the . /// public JsonWriterOptions WriterOptions { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the JSON serializer for serialization and deserialization. /// public Func IsSerializableType { get; set; } /// /// Gets or sets a delegate used to determine if a type is supported by the JSON serializer for copying. /// public Func IsCopyableType { get; set; } } ================================================ FILE: src/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.csproj ================================================ README.md Microsoft.Orleans.Serialization.SystemTextJson $(DefaultTargetFrameworks);netstandard2.1 System.Text.Json integration for Orleans.Serialization true false ================================================ FILE: src/Orleans.Serialization.SystemTextJson/README.md ================================================ # Microsoft Orleans Serialization for System.Text.Json ## Introduction Microsoft Orleans Serialization for System.Text.Json provides JSON serialization support for Microsoft Orleans using the modern System.Text.Json library. This allows you to use the high-performance JSON serialization capabilities from the .NET runtime within your Orleans application. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.SystemTextJson ``` ## Example - Configuring System.Text.Json Serialization ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Serialization; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure System.Text.Json as a serializer .AddSerializer(serializerBuilder => serializerBuilder.AddSystemTextJsonSerializer(type => type.Namespace.StartsWith("ExampleGrains"))); }); // Run the host await builder.RunAsync(); ``` ## Example - Using System.Text.Json with a Custom Type ```csharp using Orleans; using System.Text.Json.Serialization; namespace ExampleGrains; // Define a class with System.Text.Json attributes public class MyJsonClass { [JsonPropertyName("full_name")] public string Name { get; set; } [JsonPropertyName("age")] public int Age { get; set; } [JsonPropertyName("tags")] public List Tags { get; set; } [JsonIgnore] public string SecretData { get; set; } } // You can use it directly in your grain interfaces and implementation public interface IMyGrain : IGrainWithStringKey { Task GetData(); Task SetData(MyJsonClass data); } public class MyGrain : Grain, IMyGrain { private MyJsonClass _data; public Task GetData() { return Task.FromResult(_data); } public Task SetData(MyJsonClass data) { _data = data; return Task.CompletedTask; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [System.Text.Json Documentation](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.SystemTextJson/SerializationHostingExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities.Internal; using System; using System.Text.Json; namespace Orleans.Serialization; /// /// Extension method for . /// public static class SerializationHostingExtensions { private static readonly ServiceDescriptor ServiceDescriptor = new (typeof(JsonCodec), typeof(JsonCodec)); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized and copied by this codec. /// The JSON serializer options. public static ISerializerBuilder AddJsonSerializer( this ISerializerBuilder serializerBuilder, Func isSupported, JsonSerializerOptions jsonSerializerOptions = null) => serializerBuilder.AddJsonSerializer( isSupported, isSupported, optionsBuilder => optionsBuilder.Configure(options => { if (jsonSerializerOptions is not null) { options.SerializerOptions = jsonSerializerOptions; } })); /// /// Adds support for serializing and deserializing values using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. /// A delegate used to configure the options for the JSON serializer. public static ISerializerBuilder AddJsonSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable, Func isCopyable, Action> configureOptions = null) { var services = serializerBuilder.Services; if (configureOptions != null) { configureOptions(services.AddOptions()); } if (isSerializable != null) { services.AddSingleton(new DelegateCodecSelector { CodecName = JsonCodec.WellKnownAlias, IsSupportedTypeDelegate = isSerializable }); } if (isCopyable != null) { services.AddSingleton(new DelegateCopierSelector { CopierName = JsonCodec.WellKnownAlias, IsSupportedTypeDelegate = isCopyable }); } if (!services.Contains(ServiceDescriptor)) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); services.AddFromExisting(); serializerBuilder.Configure(options => options.WellKnownTypeAliases[JsonCodec.WellKnownAlias] = typeof(JsonCodec)); } return serializerBuilder; } } ================================================ FILE: src/Orleans.Serialization.TestKit/BufferTestHelper.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Orleans.Serialization.TestKit { /// /// Helper for testing buffer types. /// [ExcludeFromCodeCoverage] internal static class BufferTestHelper { public static IBufferTestSerializer[] GetTestSerializers(IServiceProvider serviceProvider, int[] maxSizes) { var result = new List(); foreach (var size in maxSizes) { result.Add(ActivatorUtilities.CreateInstance(serviceProvider, new MultiSegmentBufferWriterTester.Options { MaxAllocationSize = size })); } result.Add(ActivatorUtilities.CreateInstance(serviceProvider)); result.Add(ActivatorUtilities.CreateInstance(serviceProvider)); return result.ToArray(); } public interface IBufferTestSerializer { IOutputBuffer Serialize(TValue input); void Deserialize(ReadOnlySequence buffer, out TValue output); } [ExcludeFromCodeCoverage] private abstract class BufferTester : IBufferTestSerializer where TBufferWriter : IBufferWriter, IOutputBuffer { private readonly SerializerSessionPool _sessionPool; private readonly Serializer _serializer; protected BufferTester(IServiceProvider serviceProvider) { _sessionPool = serviceProvider.GetRequiredService(); _serializer = serviceProvider.GetRequiredService>(); } protected abstract TBufferWriter CreateBufferWriter(); public IOutputBuffer Serialize(TValue input) { using var session = _sessionPool.GetSession(); var writer = Writer.Create(CreateBufferWriter(), session); _serializer.Serialize(input, ref writer); return writer.Output; } public void Deserialize(ReadOnlySequence buffer, out TValue output) { using var session = _sessionPool.GetSession(); var reader = Reader.Create(buffer, session); output = _serializer.Deserialize(ref reader); } } [ExcludeFromCodeCoverage] private class MultiSegmentBufferWriterTester : BufferTester { private readonly Options _options; public MultiSegmentBufferWriterTester(IServiceProvider serviceProvider, Options options) : base(serviceProvider) { _options = options; } public class Options { public int MaxAllocationSize { get; set; } } protected override TestMultiSegmentBufferWriter CreateBufferWriter() => new(_options.MaxAllocationSize); public override string ToString() => $"{nameof(TestMultiSegmentBufferWriter)} {nameof(_options.MaxAllocationSize)}: {_options.MaxAllocationSize}"; } [ExcludeFromCodeCoverage] private class StructBufferWriterTester : BufferTester { protected override TestBufferWriterStruct CreateBufferWriter() => new(new byte[102400]); public StructBufferWriterTester(IServiceProvider serviceProvider) : base(serviceProvider) { } public override string ToString() => $"{nameof(TestBufferWriterStruct)}"; } private struct PooledOutputBuffer : IBufferWriter, IOutputBuffer, IDisposable { private PooledBuffer _buffer; public PooledOutputBuffer() { _buffer = new(); } public void Advance(int count) => _buffer.Advance(count); public void Dispose() => _buffer.Dispose(); public Memory GetMemory(int sizeHint = 0) => _buffer.GetMemory(sizeHint); public ReadOnlySequence GetReadOnlySequence(int maxSegmentSize) => _buffer.AsReadOnlySequence(); public Span GetSpan(int sizeHint = 0) => _buffer.GetSpan(sizeHint); } [ExcludeFromCodeCoverage] private class PooledBufferWriterTester : BufferTester { public PooledBufferWriterTester(IServiceProvider serviceProvider) : base(serviceProvider) { } protected override PooledOutputBuffer CreateBufferWriter() => new(); public override string ToString() => $"{nameof(PooledBufferWriterTester)}"; } } } ================================================ FILE: src/Orleans.Serialization.TestKit/CopierTester.cs ================================================ using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Xunit; using Xunit.Abstractions; using System.Linq; namespace Orleans.Serialization.TestKit { /// /// Test methods for copiers. /// [Trait("Category", "BVT")] [ExcludeFromCodeCoverage] public abstract class CopierTester where TCopier : class, IDeepCopier { private readonly IServiceProvider _serviceProvider; private readonly CodecProvider _codecProvider; /// /// Initializes a new instance. /// protected CopierTester(ITestOutputHelper output) { #if NET6_0_OR_GREATER var seed = Random.Shared.Next(); #else var seed = new Random().Next(); #endif output.WriteLine($"Random seed: {seed}"); Random = new(seed); var services = new ServiceCollection(); _ = services.AddSerializer(builder => builder.Configure(config => config.Copiers.Add(typeof(TCopier)))); if (!typeof(TCopier).IsAbstract && !typeof(TCopier).IsInterface) { _ = services.AddSingleton(); } _ = services.AddSerializer(Configure); _serviceProvider = services.BuildServiceProvider(); _codecProvider = _serviceProvider.GetRequiredService(); } /// /// Gets the random number generator. /// protected Random Random { get; } /// /// Gets the service provider. /// protected IServiceProvider ServiceProvider => _serviceProvider; /// /// Gets a value indicating whether the type copied by this codec is immutable. /// protected virtual bool IsImmutable => false; /// /// Gets a value indicating whether the type copied by this codec is pooled. /// protected virtual bool IsPooled => false; /// /// Configures the serializer. /// protected virtual void Configure(ISerializerBuilder builder) { } /// /// Creates a copier instance for testing. /// protected virtual TCopier CreateCopier() => _serviceProvider.GetRequiredService(); /// /// Creates a value to copy. /// protected abstract TValue CreateValue(); /// /// Gets an array of test values. /// protected abstract TValue[] TestValues { get; } /// /// Compares two values and returns if they are equal, or if they are not equal. /// protected virtual bool Equals(TValue left, TValue right) => EqualityComparer.Default.Equals(left, right); /// /// Gets a value provider delegate. /// protected virtual Action> ValueProvider { get; } /// /// Checks if copied values are equal. /// [Fact] public void CopiedValuesAreEqual() { var copier = CreateCopier(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var output = copier.DeepCopy(original, new CopyContext(_codecProvider, _ => { })); var isEqual = Equals(original, output); Assert.True( isEqual, isEqual ? string.Empty : $"Copy value \"{output}\" must equal original value \"{original}\""); } } /// /// Checks if references are added to the copy context. /// [Fact] public void ReferencesAreAddedToCopyContext() { if (typeof(TValue).IsValueType || IsPooled) { return; } var value = CreateValue(); var array = new TValue[] { value, value }; var arrayCopier = _serviceProvider.GetRequiredService>(); var arrayCopy = arrayCopier.Copy(array); Assert.Same(arrayCopy[0], arrayCopy[1]); if (IsImmutable) { Assert.Same(value, arrayCopy[0]); } else { Assert.NotSame(value, arrayCopy[0]); } } /// /// Checks if strongly-typed tuples containing the field type can be copied. /// [Fact] public void CanCopyTupleViaSerializer() { var copier = _serviceProvider.GetRequiredService>(); var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString()); var copy = copier.Copy(original); var isEqual = Equals(original.Item1, copy.Item1); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 1, \"{copy}\", must equal original value, \"{original}\""); isEqual = Equals(original.Item2, copy.Item2); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 2, \"{copy}\", must equal original value, \"{original}\""); isEqual = Equals(original.Item3, copy.Item3); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 3, \"{copy}\", must equal original value, \"{original}\""); isEqual = Equals(original.Item4, copy.Item4); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 4, \"{copy}\", must equal original value, \"{original}\""); } /// /// Checks if object-typed tuples containing the field type can be copied. /// [Fact] public void CanCopyUntypedTupleViaSerializer() { var copier = _serviceProvider.GetRequiredService>(); var value = ((IEnumerable)TestValues).Reverse().Concat(new[] { CreateValue(), CreateValue() }).Take(2).ToArray(); var original = (Guid.NewGuid().ToString(), (object)value[0], (object)value[1], Guid.NewGuid().ToString()); var copy = copier.Copy(original); var isEqual = Equals(original.Item1, copy.Item1); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 1, \"{copy.Item1}\", must equal original value, \"{original.Item1}\""); isEqual = Equals((TValue)original.Item2, (TValue)copy.Item2); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 2, \"{copy.Item2}\", must equal original value, \"{original.Item2}\""); isEqual = Equals((TValue)original.Item3, (TValue)copy.Item3); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 3, \"{copy.Item3}\", must equal original value, \"{original.Item3}\""); isEqual = Equals(original.Item4, copy.Item4); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value for item 4, \"{copy.Item4}\", must equal original value, \"{original.Item4}\""); } /// /// Checks if values can be round-tripped when used as an element in a strongly-typed list. /// [Fact] public void CanCopyCollectionViaSerializer() { var copier = _serviceProvider.GetRequiredService>>(); var original = new List(); original.AddRange(TestValues); for (var i = 0; i < 5; i++) { original.Add(CreateValue()); } var copy = copier.Copy(original); Assert.Equal(original.Count, copy.Count); for (var i = 0; i < original.Count; ++i) { var isEqual = Equals(original[i], copy[i]); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\""); } } /// /// Checks if values can be round-tripped when used as an element in a list of object. /// [Fact] public void CanCopyCollectionViaUntypedSerializer() { var copier = _serviceProvider.GetRequiredService>>(); var original = new List(); foreach (var value in TestValues) { original.Add(value); } for (var i = 0; i < 5; i++) { original.Add(CreateValue()); } var copy = copier.Copy(original); Assert.Equal(original.Count, copy.Count); for (var i = 0; i < original.Count; ++i) { var isEqual = Equals((TValue)original[i], (TValue)copy[i]); Assert.True( isEqual, isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\""); } } } } ================================================ FILE: src/Orleans.Serialization.TestKit/FieldCodecTester.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.Session; using Orleans.Serialization.Utilities; using Microsoft.Extensions.DependencyInjection; using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Pipelines; using System.Linq; using System.Text; using Xunit; using Orleans.Serialization.Serializers; using Xunit.Abstractions; using Orleans.Serialization.GeneratedCodeHelpers; namespace Orleans.Serialization.TestKit { /// /// Methods for testing field codecs. /// [Trait("Category", "BVT")] [ExcludeFromCodeCoverage] public abstract class FieldCodecTester : IDisposable where TCodec : class, IFieldCodec { private readonly IServiceProvider _serviceProvider; private readonly SerializerSessionPool _sessionPool; /// /// Initializes a new instance of the class. /// protected FieldCodecTester(ITestOutputHelper output) { #if NET6_0_OR_GREATER var seed = Random.Shared.Next(); #else var seed = new Random().Next(); #endif output.WriteLine($"Random seed: {seed}"); Random = new(seed); var services = new ServiceCollection(); _ = services.AddSerializer(builder => builder.Configure(config => config.FieldCodecs.Add(typeof(TCodec)))); if (!typeof(TCodec).IsAbstract && !typeof(TCodec).IsInterface) { _ = services.AddSingleton(); } _ = services.AddSerializer(Configure); _serviceProvider = services.BuildServiceProvider(); _sessionPool = _serviceProvider.GetService(); } /// /// Gets the random number generator. /// protected Random Random { get; } /// /// Gets the service provider. /// protected IServiceProvider ServiceProvider => _serviceProvider; /// /// Gets the session pool. /// protected SerializerSessionPool SessionPool => _sessionPool; /// /// Gets the maximum segment sizes for buffer testing. /// protected virtual int[] MaxSegmentSizes => [16]; /// /// Configures the serializer. /// protected virtual void Configure(ISerializerBuilder builder) { } /// /// Creates a codec. /// protected virtual TCodec CreateCodec() => _serviceProvider.GetRequiredService(); /// /// Creates a value. /// protected abstract TValue CreateValue(); /// /// Gets test values. /// protected abstract TValue[] TestValues { get; } /// /// Compares two values for equality. /// protected virtual bool Equals(TValue left, TValue right) => EqualityComparer.Default.Equals(left, right); /// /// Gets a value provider delegate. /// protected virtual Action> ValueProvider { get; } /// void IDisposable.Dispose() => (_serviceProvider as IDisposable)?.Dispose(); protected virtual TValue GetWriteCopy(TValue input) => input; /// /// Checks whether the codec correctly advances the reference counter when writing to a stream and reading from a stream. /// [Fact] public void CorrectlyAdvancesReferenceCounterStream() { var stream = new MemoryStream(); using var writerSession = _sessionPool.GetSession(); using var readerSession = _sessionPool.GetSession(); var writer = Writer.Create(stream, writerSession); var writerCodec = CreateCodec(); // Write the field. This should involve marking at least one reference in the session. Assert.Equal(0, writer.Position); foreach (var value in TestValues) { var beforeReference = writer.Session.ReferencedObjects.CurrentReferenceId; writerCodec.WriteField(ref writer, 0, typeof(TValue), value); Assert.True(writer.Position > 0); writer.Commit(); var afterReference = writer.Session.ReferencedObjects.CurrentReferenceId; Assert.True(beforeReference < afterReference, $"Writing a field should result in at least one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); if (value is null) { Assert.True(beforeReference + 1 == afterReference, $"Writing a null field should result in exactly one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); } stream.Flush(); stream.Position = 0; var reader = Reader.Create(stream, readerSession); var previousPos = reader.Position; Assert.Equal(0, previousPos); var readerCodec = CreateCodec(); var readField = reader.ReadFieldHeader(); Assert.True(reader.Position > previousPos); previousPos = reader.Position; beforeReference = reader.Session.ReferencedObjects.CurrentReferenceId; var readValue = readerCodec.ReadValue(ref reader, readField); Assert.True(reader.Position > previousPos); afterReference = reader.Session.ReferencedObjects.CurrentReferenceId; Assert.True(beforeReference < afterReference, $"Reading a field should result in at least one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); if (readValue is null) { Assert.True(beforeReference + 1 == afterReference, $"Reading a null field should result in at exactly one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); } } } /// /// Checks whether the codec correctly advances the reference counter when writing to a pipe and reading from a pipe. /// [Fact] public void CorrectlyAdvancesReferenceCounter() { var pipe = new Pipe(); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(pipe.Writer, writerSession); var writerCodec = CreateCodec(); var beforeReference = writer.Session.ReferencedObjects.CurrentReferenceId; // Write the field. This should involve marking at least one reference in the session. Assert.Equal(0, writer.Position); writerCodec.WriteField(ref writer, 0, typeof(TValue), CreateValue()); Assert.True(writer.Position > 0); writer.Commit(); var afterReference = writer.Session.ReferencedObjects.CurrentReferenceId; Assert.True(beforeReference < afterReference, $"Writing a field should result in at least one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); pipe.Writer.Complete(); _ = pipe.Reader.TryRead(out var readResult); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var previousPos = reader.Position; Assert.Equal(0, previousPos); var readerCodec = CreateCodec(); var readField = reader.ReadFieldHeader(); Assert.True(reader.Position > previousPos); previousPos = reader.Position; beforeReference = reader.Session.ReferencedObjects.CurrentReferenceId; _ = readerCodec.ReadValue(ref reader, readField); Assert.True(reader.Position > previousPos); pipe.Reader.AdvanceTo(readResult.Buffer.End); pipe.Reader.Complete(); afterReference = reader.Session.ReferencedObjects.CurrentReferenceId; Assert.True(beforeReference < afterReference, $"Reading a field should result in at least one reference being marked in the session. Before: {beforeReference}, After: {afterReference}"); } /// /// Checks whether the codec correctly round-trips values when using a pooled stream. /// [Fact] public void CanRoundTripViaSerializer_StreamPooled() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var toWrite = GetWriteCopy(original); var buffer = new MemoryStream(); var writer = Writer.CreatePooled(buffer, _sessionPool.GetSession()); serializer.Serialize(toWrite, ref writer); buffer.Flush(); writer.Dispose(); buffer.Position = 0; var reader = Reader.Create(buffer, _sessionPool.GetSession()); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } } /// /// Checks whether the codec correctly round-trips values when writing to a span. /// [Fact] public void CanRoundTripViaSerializer_Span() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var buffer = new byte[8096].AsSpan(); var toWrite = GetWriteCopy(original); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(toWrite, ref writer); var reader = Reader.Create(buffer, _sessionPool.GetSession()); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } } /// /// Checks whether the codec correctly round-trips values when writing to an array. /// [Fact] public void CanRoundTripViaSerializer_Array() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var buffer = new byte[8096]; var toWrite = GetWriteCopy(original); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(toWrite, ref writer); var reader = Reader.Create(buffer, _sessionPool.GetSession()); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } } /// /// Checks whether the codec correctly round-trips values when writing to a memory slice. /// [Fact] public void CanRoundTripViaSerializer_Memory() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var buffer = (new byte[8096]).AsMemory(); var toWrite = GetWriteCopy(original); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(toWrite, ref writer); var reader = Reader.Create(buffer, _sessionPool.GetSession()); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } } /// /// Checks whether the codec correctly round-trips values when writing to a memory stream. /// [Fact] public void CanRoundTripViaSerializer_MemoryStream() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var toWrite = GetWriteCopy(original); var buffer = new MemoryStream(); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(buffer, writerSession); serializer.Serialize(toWrite, ref writer); writer.Commit(); buffer.Flush(); buffer.SetLength(buffer.Position); buffer.Position = 0; using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(buffer, readerSession); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } } /// /// Checks whether the codec correctly round-trips values when reading byte-by-byte, simulating fragmented reads. /// [Fact] public void CanRoundTripViaSerializer_ReadByteByByte() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var toWrite = GetWriteCopy(original); var buffer = new TestMultiSegmentBufferWriter(maxAllocationSize: 1024); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(buffer, writerSession); serializer.Serialize(toWrite, ref writer); writer.Commit(); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(buffer.GetReadOnlySequence(maxSegmentSize: 1), readerSession); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } } /// /// Checks whether the codec produces a valid bit stream. /// [Fact] public void ProducesValidBitStream() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var value in TestValues) { Test(value); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue value) { var array = serializer.SerializeToArray(value); var session = _sessionPool.GetSession(); var reader = Reader.Create(array, session); var formatted = new StringBuilder(); try { BitStreamFormatter.Format(ref reader, formatted); Assert.True(formatted.ToString() is string { Length: > 0 }); } catch (Exception exception) { Assert.True(false, $"Formatting failed with exception: {exception} and partial result: \"{formatted}\""); } } } /// /// Checks whether various buffer writers produce bit-wise identical results. /// [Fact] public void WritersProduceSameResults() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { byte[] expected; { var buffer = new TestMultiSegmentBufferWriter(1024); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); expected = buffer.GetReadOnlySequence(0).ToArray(); } { var buffer = new MemoryStream(); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); buffer.Flush(); buffer.SetLength(buffer.Position); buffer.Position = 0; var result = buffer.ToArray(); Assert.Equal(expected, result); } { var writer = Writer.CreatePooled(_sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); var result = writer.Output.ToArray(); Assert.Equal(expected, result); } var bytes = new byte[10240]; { var buffer = bytes.AsMemory(); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); var result = buffer[..writer.Output.BytesWritten].ToArray(); Assert.Equal(expected, result); } bytes.AsSpan().Clear(); { var buffer = bytes.AsSpan(); var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); var result = buffer[..writer.Output.BytesWritten].ToArray(); Assert.Equal(expected, result); } bytes.AsSpan().Clear(); { var buffer = bytes; var writer = Writer.Create(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); var result = buffer.AsSpan(0, writer.Output.BytesWritten).ToArray(); Assert.Equal(expected, result); } bytes.AsSpan().Clear(); { var buffer = new MemoryStream(bytes); var writer = Writer.CreatePooled(buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); buffer.Flush(); buffer.SetLength(buffer.Position); buffer.Position = 0; var result = buffer.ToArray(); writer.Dispose(); Assert.Equal(expected, result); } bytes.AsSpan().Clear(); { var buffer = new MemoryStream(bytes); var writer = Writer.Create((Stream)buffer, _sessionPool.GetSession()); serializer.Serialize(GetWriteCopy(original), ref writer); buffer.Flush(); buffer.SetLength(buffer.Position); buffer.Position = 0; var result = buffer.ToArray(); Assert.Equal(expected, result); } } } /// /// Checks whether a strongly typed collection of values can be round-tripped. /// [Fact] public void CanRoundTripCollectionViaSerializer() { var serializer = _serviceProvider.GetRequiredService>>(); var original = new List(); var originalCopy = new List(); original.AddRange(TestValues); foreach (var value in original) { originalCopy.Add(GetWriteCopy(value)); } for (var i = 0; i < 5; i++) { var o = CreateValue(); var c = GetWriteCopy(o); original.Add(o); originalCopy.Add(c); } using var writerSession = _sessionPool.GetSession(); var writer = Writer.CreatePooled(writerSession); serializer.Serialize(originalCopy, ref writer); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(writer.Output, readerSession); var deserialized = serializer.Deserialize(ref reader); Assert.Equal(original.Count, deserialized.Count); for (var i = 0; i < original.Count; ++i) { var isEqual = Equals(original[i], deserialized[i]); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value at index {i}, \"{deserialized[i]}\", must equal original value, \"{original[i]}\""); } Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } /// /// Checks whether a strongly typed collection of values can be round-tripped. /// [Fact] public void CanRoundTripWeaklyTypedCollectionViaSerializer() { var serializer = _serviceProvider.GetRequiredService>>(); var original = new List(); var originalCopy = new List(); foreach (var value in TestValues) { var o = value; var c = GetWriteCopy(o); original.Add(o); originalCopy.Add(c); } for (var i = 0; i < 5; i++) { var o = CreateValue(); var c = GetWriteCopy(o); original.Add(o); originalCopy.Add(c); } using var writerSession = _sessionPool.GetSession(); var writer = Writer.CreatePooled(writerSession); serializer.Serialize(originalCopy, ref writer); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(writer.Output, readerSession); var deserialized = serializer.Deserialize(ref reader); Assert.Equal(original.Count, deserialized.Count); for (var i = 0; i < original.Count; ++i) { var left = (TValue)original[i]; var right = (TValue)deserialized[i]; var isEqual = Equals(left, right); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value at index {i}, \"{right}\", must equal original value, \"{left}\""); } Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } /// /// Checks if values can be round-tripped when used as a field in a tuple. /// [Fact] public void CanRoundTripTupleViaSerializer() { var serializer = _serviceProvider.GetRequiredService>(); var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString()); var originalCopy = (original.Item1, GetWriteCopy(original.Item2), GetWriteCopy(original.Item3), original.Item4); using var writerSession = _sessionPool.GetSession(); var writer = Writer.CreatePooled(writerSession); serializer.Serialize(originalCopy, ref writer); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(writer.Output, readerSession); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original.Item1, deserialized.Item1); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value for item 1, \"{deserialized.Item1}\", must equal original value, \"{original.Item1}\""); isEqual = Equals(original.Item2, deserialized.Item2); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value for item 2, \"{deserialized.Item2}\", must equal original value, \"{original.Item2}\""); isEqual = Equals(original.Item3, deserialized.Item3); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value for item 3, \"{deserialized.Item3}\", must equal original value, \"{original.Item3}\""); isEqual = Equals(original.Item4, deserialized.Item4); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value for item 4, \"{deserialized.Item4}\", must equal original value, \"{original.Item4}\""); Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } /// /// Checks if values can be round-tripped through when using as the type parameter. /// [Fact] public void CanRoundTripViaSerializer() { var serializer = _serviceProvider.GetRequiredService>(); foreach (var original in TestValues) { Test(original); } if (ValueProvider is { } valueProvider) { valueProvider(Test); } void Test(TValue original) { var toWrite = GetWriteCopy(original); var buffer = new TestMultiSegmentBufferWriter(1024); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(buffer, writerSession); serializer.Serialize(toWrite, ref writer); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(buffer.GetReadOnlySequence(0), readerSession); var deserialized = serializer.Deserialize(ref reader); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } } /// /// Checks if values can be round-tripped through when using as the type parameter. /// [Fact] public void CanRoundTripViaObjectSerializer() { var serializer = _serviceProvider.GetRequiredService>(); var buffer = new byte[10240]; foreach (var original in TestValues) { buffer.AsSpan().Clear(); var toWrite = GetWriteCopy(original); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(buffer, writerSession); serializer.Serialize(toWrite, ref writer); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(buffer, readerSession); var deserializedObject = serializer.Deserialize(ref reader); if (original != null && !typeof(TValue).IsEnum) { var deserialized = Assert.IsAssignableFrom(deserializedObject); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } else if (typeof(TValue).IsEnum) { var deserialized = (TValue)deserializedObject; var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); } else { Assert.Null(deserializedObject); } Assert.Equal(writer.Position, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } } /// /// Checks if round-tripped values are equal. /// [Fact] public void RoundTrippedValuesEqual() => TestRoundTrippedValue(CreateValue()); /// /// Checks if round-tripped default values are equal. /// [Fact] public void CanRoundTripDefaultValueViaCodec() => TestRoundTrippedValue(default); /// /// Checks if values can be skipped over. /// [Fact] public void CanSkipValue() => CanBeSkipped(default); /// /// Checks if default values can be skipped over. /// [Fact] public void CanSkipDefaultValue() => CanBeSkipped(default); /// /// Checks if buffers are handled correctly. /// [Fact] public void CorrectlyHandlesBuffers() { var testers = BufferTestHelper.GetTestSerializers(_serviceProvider, MaxSegmentSizes); foreach (var tester in testers) { foreach (var maxSegmentSize in MaxSegmentSizes) { foreach (var value in TestValues) { var toWrite = GetWriteCopy(value); var buffer = tester.Serialize(toWrite); var sequence = buffer.GetReadOnlySequence(maxSegmentSize); tester.Deserialize(sequence, out var output); var bufferWriterType = tester.GetType().BaseType?.GenericTypeArguments[1]; var isEqual = Equals(value, output); Assert.True(isEqual, isEqual ? string.Empty : $"Deserialized value {output} must be equal to serialized value {value}. " + $"IBufferWriter<> type: {bufferWriterType}, Max Read Segment Size: {maxSegmentSize}. " + $"Buffer: 0x{string.Join(" ", sequence.ToArray().Select(b => $"{b:X2}"))}"); } } } } private void CanBeSkipped(TValue original) { var pipe = new Pipe(); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(pipe.Writer, writerSession); var writerCodec = CreateCodec(); var toWrite = GetWriteCopy(original); writerCodec.WriteField(ref writer, 0, typeof(TValue), toWrite); var expectedLength = writer.Position; writer.Commit(); _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); pipe.Writer.Complete(); _ = pipe.Reader.TryRead(out var readResult); { using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var readField = reader.ReadFieldHeader(); reader.SkipField(readField); Assert.Equal(expectedLength, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } { var codec = new SkipFieldCodec(); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var readField = reader.ReadFieldHeader(); var shouldBeNull = codec.ReadValue(ref reader, readField); Assert.Null(shouldBeNull); Assert.Equal(expectedLength, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } { using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var readField = reader.ReadFieldHeader(); reader.ConsumeUnknownField(readField); Assert.Equal(expectedLength, reader.Position); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } pipe.Reader.AdvanceTo(readResult.Buffer.End); pipe.Reader.Complete(); } private void TestRoundTrippedValue(TValue original) { var pipe = new Pipe(); using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(pipe.Writer, writerSession); var writerCodec = CreateCodec(); writerCodec.WriteField(ref writer, 0, typeof(TValue), GetWriteCopy(original)); writer.Commit(); _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); pipe.Writer.Complete(); _ = pipe.Reader.TryRead(out var readResult); using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var readerCodec = CreateCodec(); var readField = reader.ReadFieldHeader(); var deserialized = readerCodec.ReadValue(ref reader, readField); pipe.Reader.AdvanceTo(readResult.Buffer.End); pipe.Reader.Complete(); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } /// /// Round-trips a value through the codec. /// protected T RoundTripThroughCodec(T original) { T result; using (var readerSession = SessionPool.GetSession()) using (var writeSession = SessionPool.GetSession()) { var writer = Writer.CreatePooled(writeSession); try { var codec = ServiceProvider.GetRequiredService().GetCodec(); codec.WriteField( ref writer, 0, null, original); writer.Commit(); var output = writer.Output.AsReadOnlySequence(); var reader = Reader.Create(output, readerSession); var previousPos = reader.Position; var initialHeader = reader.ReadFieldHeader(); Assert.True(reader.Position > previousPos); result = codec.ReadValue(ref reader, initialHeader); } finally { writer.Dispose(); } } return result; } /// /// Round-trips a value through an untyped serializer. /// protected object RoundTripThroughUntypedSerializer(object original, out string formattedBitStream) { object result; using (var readerSession = SessionPool.GetSession()) using (var writeSession = SessionPool.GetSession()) { var writer = Writer.CreatePooled(writeSession); try { var serializer = ServiceProvider.GetService>(); serializer.Serialize(original, ref writer); using var analyzerSession = SessionPool.GetSession(); var output = writer.Output.Slice(); formattedBitStream = BitStreamFormatter.Format(output, analyzerSession); var reader = Reader.Create(output, readerSession); result = serializer.Deserialize(ref reader); } finally { writer.Dispose(); } } return result; } } } ================================================ FILE: src/Orleans.Serialization.TestKit/IOutputBuffer.cs ================================================ using System.Buffers; namespace Orleans.Serialization.TestKit { public interface IOutputBuffer { ReadOnlySequence GetReadOnlySequence(int maxSegmentSize); } } ================================================ FILE: src/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.csproj ================================================ README.md Microsoft.Orleans.Serialization.TestKit $(DefaultTargetFrameworks);netstandard2.1 Test kit for projects using Orleans.Serialization false false ================================================ FILE: src/Orleans.Serialization.TestKit/README.md ================================================ # Microsoft Orleans Serialization Test Kit ## Introduction Microsoft Orleans Serialization Test Kit provides tools and utilities to help test serialization functionality in Orleans applications. This package simplifies writing tests that verify serialization and deserialization of your custom types work correctly. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.TestKit ``` You'll typically add this package to a test project. ## Example - Testing Serialization ```csharp using Orleans.Serialization.TestKit; using Xunit.Abstractions; public class TimeSpanTests(ITestOutputHelper output) : FieldCodecTester(output) { protected override TimeSpan CreateValue() => TimeSpan.FromMilliseconds(Guid.NewGuid().GetHashCode()); protected override TimeSpan[] TestValues => [TimeSpan.MinValue, TimeSpan.MaxValue, TimeSpan.Zero, TimeSpan.FromSeconds(12345)]; protected override Action> ValueProvider => Gen.TimeSpan.ToValueProvider(); } public class TimeSpanCopierTests(ITestOutputHelper output) : CopierTester>(output) { protected override TimeSpan CreateValue() => TimeSpan.FromMilliseconds(Guid.NewGuid().GetHashCode()); protected override TimeSpan[] TestValues => [TimeSpan.MinValue, TimeSpan.MaxValue, TimeSpan.Zero, TimeSpan.FromSeconds(12345)]; protected override Action> ValueProvider => Gen.TimeSpan.ToValueProvider(); } public class DateTimeOffsetTests(ITestOutputHelper output) : FieldCodecTester(output) { protected override DateTimeOffset CreateValue() => DateTime.UtcNow; protected override DateTimeOffset[] TestValues => [ DateTimeOffset.MinValue, DateTimeOffset.MaxValue, new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0), TimeSpan.FromHours(11.5)), new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0), TimeSpan.FromHours(-11.5)), ]; protected override Action> ValueProvider => Gen.DateTimeOffset.ToValueProvider(); } public class DateTimeOffsetCopierTests(ITestOutputHelper output) : CopierTester>(output) { protected override DateTimeOffset CreateValue() => DateTime.UtcNow; protected override DateTimeOffset[] TestValues => [ DateTimeOffset.MinValue, DateTimeOffset.MaxValue, new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0), TimeSpan.FromHours(11.5)), new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0), TimeSpan.FromHours(-11.5)), ]; protected override Action> ValueProvider => Gen.DateTimeOffset.ToValueProvider(); } ``` ## Additional Testing Features The TestKit provides several utilities for testing serialization and allows you to focus on testing specific serialization components: ```csharp // Using a specific serializer var specificSerializer = services.GetRequiredService>(); byte[] bytes = specificSerializer.SerializeToArray(original); var deserialized = specificSerializer.Deserialize(bytes); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) - [Testing Orleans Applications](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/testing) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Serialization.TestKit/ReadOnlySequenceHelper.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Orleans.Serialization.TestKit { [ExcludeFromCodeCoverage] public static class ReadOnlySequenceHelper { public static IEnumerable Batch(this IEnumerable sequence, int batchSize) { var batch = new List(batchSize); foreach (var item in sequence) { batch.Add(item); if (batch.Count >= batchSize) { yield return batch.ToArray(); batch = new List(batchSize); } } if (batch.Count > 0) { yield return batch.ToArray(); } } public static ReadOnlySequence ToReadOnlySequence(this IEnumerable buffers) => CreateReadOnlySequence(buffers.ToArray()); public static ReadOnlySequence ToReadOnlySequence(this IEnumerable> buffers) => ReadOnlyBufferSegment.Create(buffers); public static ReadOnlySequence CreateReadOnlySequence(params byte[][] buffers) { if (buffers.Length == 1) { return new ReadOnlySequence(buffers[0]); } var list = new List>(); foreach (var buffer in buffers) { list.Add(buffer); } return ToReadOnlySequence(list); } private class ReadOnlyBufferSegment : ReadOnlySequenceSegment { public static ReadOnlySequence Create(IEnumerable> buffers) { ReadOnlyBufferSegment segment = null; ReadOnlyBufferSegment first = null; foreach (var buffer in buffers) { var newSegment = new ReadOnlyBufferSegment { Memory = buffer, }; if (segment != null) { segment.Next = newSegment; newSegment.RunningIndex = segment.RunningIndex + segment.Memory.Length; } else { first = newSegment; } segment = newSegment; } if (first is null) { first = segment = new ReadOnlyBufferSegment(); } return new ReadOnlySequence(first, 0, segment, segment.Memory.Length); } } } } ================================================ FILE: src/Orleans.Serialization.TestKit/TestBufferWriterStruct.cs ================================================ using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; namespace Orleans.Serialization.TestKit { [ExcludeFromCodeCoverage] public struct TestBufferWriterStruct : IBufferWriter, IOutputBuffer { private readonly byte[] _buffer; private int _written; public TestBufferWriterStruct(byte[] buffer) { _buffer = buffer; _written = 0; } public void Advance(int bytes) => _written += bytes; [Pure] public readonly Memory GetMemory(int sizeHint = 0) => _buffer.AsMemory()[_written..]; [Pure] public readonly Span GetSpan(int sizeHint) => _buffer.AsSpan()[_written..]; [Pure] public readonly ReadOnlySequence GetReadOnlySequence(int maxSegmentSize) => _buffer.Take(_written).Batch(maxSegmentSize).ToReadOnlySequence(); } } ================================================ FILE: src/Orleans.Serialization.TestKit/TestMultiSegmentBufferWriter.cs ================================================ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; namespace Orleans.Serialization.TestKit { [ExcludeFromCodeCoverage] public class TestMultiSegmentBufferWriter : IBufferWriter, IOutputBuffer { private readonly List _committed = new(); private readonly int _maxAllocationSize; private byte[] _current = Array.Empty(); public TestMultiSegmentBufferWriter(int maxAllocationSize) { _maxAllocationSize = maxAllocationSize; } public void Advance(int bytes) { if (bytes == 0) { return; } _committed.Add(_current.AsSpan(0, bytes).ToArray()); _current = Array.Empty(); } public Memory GetMemory(int sizeHint = 0) { if (sizeHint == 0) { sizeHint = _current.Length + 1; } if (sizeHint < _current.Length) { throw new InvalidOperationException("Attempted to allocate a new buffer when the existing buffer has sufficient free space."); } var newBuffer = new byte[Math.Min(sizeHint, _maxAllocationSize)]; _current.CopyTo(newBuffer.AsSpan()); _current = newBuffer; return _current; } public Span GetSpan(int sizeHint) { if (sizeHint == 0) { sizeHint = _current.Length + 1; } if (sizeHint < _current.Length) { throw new InvalidOperationException("Attempted to allocate a new buffer when the existing buffer has sufficient free space."); } var newBuffer = new byte[Math.Min(sizeHint, _maxAllocationSize)]; _current.CopyTo(newBuffer.AsSpan()); _current = newBuffer; return _current; } [Pure] public ReadOnlySequence GetReadOnlySequence(int maxSegmentSize) => _committed.SelectMany(b => b).Batch(maxSegmentSize).ToReadOnlySequence(); public ReadOnlySequence PeekAllBuffers() => _committed.Concat(new[] { _current }).ToReadOnlySequence(); } } ================================================ FILE: src/Orleans.Serialization.TestKit/ValueTypeFieldCodecTester.cs ================================================ using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Microsoft.Extensions.DependencyInjection; using System.IO.Pipelines; using Xunit; using Orleans.Serialization.Serializers; using Xunit.Abstractions; namespace Orleans.Serialization.TestKit { public abstract class ValueTypeFieldCodecTester : FieldCodecTester where TField : struct where TCodec : class, IFieldCodec { protected ValueTypeFieldCodecTester(ITestOutputHelper output) : base(output) { } [Fact] public void ValueSerializerRoundTrip() { var serializer = ServiceProvider.GetRequiredService>(); foreach (var value in TestValues) { var valueCopy = value; var serialized = serializer.SerializeToArray(ref valueCopy); var deserializedValue = default(TField); serializer.Deserialize(serialized, ref deserializedValue); Assert.Equal(value, deserializedValue); } } [Fact] public void DirectAccessValueSerializerRoundTrip() { foreach (var value in TestValues) { var valueLocal = value; TestRoundTrippedValueViaValueSerializer(ref valueLocal); } } private void TestRoundTrippedValueViaValueSerializer(ref TField original) { var codecProvider = ServiceProvider.GetRequiredService(); var serializer = codecProvider.GetValueSerializer(); var pipe = new Pipe(); using var writerSession = SessionPool.GetSession(); var writer = Writer.Create(pipe.Writer, writerSession); var writerCodec = serializer; writerCodec.Serialize(ref writer, ref original); writer.WriteEndObject(); writer.Commit(); _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); pipe.Writer.Complete(); _ = pipe.Reader.TryRead(out var readResult); using var readerSession = SessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var readerCodec = serializer; TField deserialized = default; readerCodec.Deserialize(ref reader, ref deserialized); pipe.Reader.AdvanceTo(readResult.Buffer.End); pipe.Reader.Complete(); var isEqual = Equals(original, deserialized); Assert.True( isEqual, isEqual ? string.Empty : $"Deserialized value \"{deserialized}\" must equal original value \"{original}\""); Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId); } } } ================================================ FILE: src/Orleans.Server/Orleans.Server.csproj ================================================ Microsoft.Orleans.Server Microsoft Orleans Server Libraries Collection of Microsoft Orleans libraries and files needed on the server. false true false false $(DefaultTargetFrameworks) README.md ================================================ FILE: src/Orleans.Server/README.md ================================================ # Microsoft Orleans Server ## Introduction Microsoft Orleans Server is a metapackage that includes all the necessary components to run an Orleans silo (server). It simplifies the process of setting up an Orleans server by providing a single package reference rather than requiring you to reference multiple packages individually. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Server ``` ## Example - Creating an Orleans Silo Host ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; // Define a grain interface namespace MyGrainNamespace; public interface IMyGrain : IGrainWithStringKey { Task DoSomething(); } // Implement the grain interface public class MyGrain : Grain, IMyGrain { public Task DoSomething() { return Task.FromResult("Done something!"); } } // Create the host var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering(); }); // Start the host var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("my-grain-id"); var result = await grain.DoSomething(); // Print the result Console.WriteLine($"Result: {result}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans server (silo) configuration](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) - [Hosting Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/host/generic-host) - [Grain persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.Streaming/Common/EventSequenceToken.cs ================================================ using System; using System.Globalization; using Orleans.Streams; using Newtonsoft.Json; namespace Orleans.Providers.Streams.Common { /// /// Stream sequence token that tracks sequence number and event index /// [Serializable] [GenerateSerializer] public class EventSequenceToken : StreamSequenceToken { /// /// Gets the number of event batches in stream prior to this event batch /// [Id(0)] [JsonProperty] public override long SequenceNumber { get; protected set; } /// /// Gets the number of events in batch prior to this event /// [Id(1)] [JsonProperty] public override int EventIndex { get; protected set; } /// /// Initializes a new instance of the class. /// /// The sequence number. public EventSequenceToken(long sequenceNumber) { SequenceNumber = sequenceNumber; EventIndex = 0; } /// /// Initializes a new instance of the class. /// /// The sequence number. /// The event index, for events which are part of a batch. public EventSequenceToken(long sequenceNumber, int eventIndex) { SequenceNumber = sequenceNumber; EventIndex = eventIndex; } /// /// Initializes a new instance of the class. /// /// /// This constructor is exposed for serializer use only. /// [JsonConstructor] public EventSequenceToken() { } /// /// Creates a sequence token for a specific event in the current batch. /// /// The event index, for events which are part of a batch. /// The sequence token. public EventSequenceToken CreateSequenceTokenForEvent(int eventInd) { return new EventSequenceToken(SequenceNumber, eventInd); } /// public override bool Equals(object obj) { return Equals(obj as EventSequenceToken); } /// public override bool Equals(StreamSequenceToken other) { var token = other as EventSequenceToken; return token != null && (token.SequenceNumber == SequenceNumber && token.EventIndex == EventIndex); } /// public override int CompareTo(StreamSequenceToken other) { if (other == null) return 1; var token = other as EventSequenceToken; if (token == null) throw new ArgumentOutOfRangeException(nameof(other)); int difference = SequenceNumber.CompareTo(token.SequenceNumber); return difference != 0 ? difference : EventIndex.CompareTo(token.EventIndex); } /// public override int GetHashCode() => HashCode.Combine(SequenceNumber, EventIndex); /// public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "[EventSequenceToken: SeqNum={0}, EventIndex={1}]", SequenceNumber, EventIndex); } } } ================================================ FILE: src/Orleans.Streaming/Common/EventSequenceTokenV2.cs ================================================ using System; using System.Globalization; using Newtonsoft.Json; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// Stream sequence token that tracks sequence number and event index /// [Serializable] [GenerateSerializer] public class EventSequenceTokenV2 : StreamSequenceToken { /// /// Gets the number of event batches in stream prior to this event batch /// [Id(0)] [JsonProperty] public override long SequenceNumber { get; protected set; } /// /// Gets the number of events in batch prior to this event /// [Id(1)] [JsonProperty] public override int EventIndex { get; protected set; } /// /// Initializes a new instance of the class. /// /// The sequence number. public EventSequenceTokenV2(long seqNumber) { SequenceNumber = seqNumber; EventIndex = 0; } /// /// Initializes a new instance of the class. /// /// The sequence number. /// The event index, for events which are part of a batch of events. public EventSequenceTokenV2(long seqNumber, int eventInd) { SequenceNumber = seqNumber; EventIndex = eventInd; } /// /// Initializes a new instance of the class. /// /// /// This constructor is for serializer use only. /// public EventSequenceTokenV2() { } /// /// Creates a sequence token for a specific event in the current batch /// /// The event index. /// A new sequence token. public EventSequenceTokenV2 CreateSequenceTokenForEvent(int eventInd) { return new EventSequenceTokenV2(SequenceNumber, eventInd); } /// public override bool Equals(object obj) { return Equals(obj as EventSequenceTokenV2); } /// public override bool Equals(StreamSequenceToken other) { var token = other as EventSequenceTokenV2; return token != null && (token.SequenceNumber == SequenceNumber && token.EventIndex == EventIndex); } /// public override int CompareTo(StreamSequenceToken other) { if (other == null) return 1; var token = other as EventSequenceTokenV2; if (token == null) throw new ArgumentOutOfRangeException(nameof(other)); int difference = SequenceNumber.CompareTo(token.SequenceNumber); return difference != 0 ? difference : EventIndex.CompareTo(token.EventIndex); } /// public override int GetHashCode() => HashCode.Combine(SequenceNumber, EventIndex); /// public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "[EventSequenceTokenV2: SeqNum={0}, EventIndex={1}]", SequenceNumber, EventIndex); } } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/DefaultBlockPoolMonitor.cs ================================================ using Orleans.Runtime; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; namespace Orleans.Providers.Streams.Common { /// /// Block pool monitor used as a default option in GeneratorStreamProvider and MemoryStreamProvider. /// public class DefaultBlockPoolMonitor : IBlockPoolMonitor { protected KeyValuePair[] _dimensions; private readonly ObservableCounter _totalMemoryCounter; private readonly ObservableCounter _availableMemoryCounter; private readonly ObservableCounter _claimedMemoryCounter; private readonly ObservableCounter _releasedMemoryCounter; private readonly ObservableCounter _allocatedMemoryCounter; private long _totalMemory; private long _availableMemory; private long _claimedMemory; private long _releasedMemory; private long _allocatedMemory; /// /// Initializes a new instance of the class. /// protected DefaultBlockPoolMonitor(KeyValuePair[] dimensions) { _dimensions = dimensions; _totalMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_BLOCK_POOL_TOTAL_MEMORY, GetTotalMemory, unit: "bytes"); _availableMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_BLOCK_POOL_AVAILABLE_MEMORY, GetAvailableMemory, unit: "bytes"); _claimedMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_BLOCK_POOL_CLAIMED_MEMORY, GetClaimedMemory, unit: "bytes"); _releasedMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_BLOCK_POOL_RELEASED_MEMORY, GetReleasedMemory, unit: "bytes"); _allocatedMemoryCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_BLOCK_POOL_ALLOCATED_MEMORY, GetAllocatedMemory, unit: "bytes"); } /// /// Initializes a new instance of the class. /// /// The dimensions. public DefaultBlockPoolMonitor(BlockPoolMonitorDimensions dimensions) : this(new KeyValuePair[] { new ("BlockPoolId", dimensions.BlockPoolId) }) { } private Measurement GetTotalMemory() => new(_totalMemory, _dimensions); private Measurement GetAvailableMemory() => new(_availableMemory, _dimensions); private Measurement GetClaimedMemory() => new(_claimedMemory, _dimensions); private Measurement GetReleasedMemory() => new(_releasedMemory, _dimensions); private Measurement GetAllocatedMemory() => new(_allocatedMemory, _dimensions); /// public void Report(long totalMemoryInByte, long availableMemoryInByte, long claimedMemoryInByte) { _totalMemory = totalMemoryInByte; _availableMemory = availableMemoryInByte; _claimedMemory = claimedMemoryInByte; } /// public void TrackMemoryReleased(long releasedMemoryInByte) { Interlocked.Add(ref _releasedMemory, releasedMemoryInByte); } /// public void TrackMemoryAllocated(long allocatedMemoryInByte) { Interlocked.Add(ref _allocatedMemory, allocatedMemoryInByte); } } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/DefaultCacheMonitor.cs ================================================ using Orleans.Runtime; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; namespace Orleans.Providers.Streams.Common { /// /// cache monitor used as a default option in GeneratorStreamprovider and MemoryStreamProvider /// public class DefaultCacheMonitor : ICacheMonitor { private readonly KeyValuePair[] _dimensions; private readonly ObservableCounter _queueCacheSizeCounter; private readonly ObservableCounter _queueCacheMessagesAddedCounter; private readonly ObservableCounter _queueCacheMessagesPurgedCounter; private readonly ObservableCounter _queueCacheMemoryAllocatedCounter; private readonly ObservableCounter _queueCacheMemoryReleasedCounter; private readonly ObservableGauge _oldestMessageReadEnqueueTimeToNowCounter; private readonly ObservableGauge _newestMessageReadEnqueueTimeToNowCounter; private readonly ObservableGauge _currentPressureCounter; private readonly ObservableGauge _underPressureCounter; private readonly ObservableGauge _pressureContributionCounter; private readonly ObservableCounter _queueCacheMessageCountCounter; private readonly ConcurrentDictionary _pressureMonitors = new(); private long _messagesPurged; private long _messagesAdded; private long _memoryReleased; private long _memoryAllocated; private long _totalCacheSize; private long _messageCount; private ValueStopwatch _oldestMessageDequeueAgo; private ValueStopwatch _oldestToNewestAge; /// /// Initializes a new instance of the class. /// protected DefaultCacheMonitor(KeyValuePair[] dimensions) { _dimensions = dimensions; _queueCacheSizeCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_SIZE, () => new(_totalCacheSize, _dimensions), unit: "bytes"); _queueCacheMessageCountCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_LENGTH, () => new(_messageCount, _dimensions), unit: "messages"); _queueCacheMessagesAddedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_MESSAGES_ADDED, () => new(_messagesAdded, _dimensions)); _queueCacheMessagesPurgedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_MESSAGES_PURGED, () => new(_messagesPurged, _dimensions)); _queueCacheMemoryAllocatedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_MEMORY_ALLOCATED, () => new(_memoryAllocated, _dimensions)); _queueCacheMemoryReleasedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_CACHE_MEMORY_RELEASED, () => new(_memoryReleased, _dimensions)); _oldestMessageReadEnqueueTimeToNowCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_CACHE_OLDEST_TO_NEWEST_DURATION, GetOldestToNewestAge); _newestMessageReadEnqueueTimeToNowCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_CACHE_OLDEST_AGE, GetOldestAge); _currentPressureCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_CACHE_PRESSURE, () => GetPressureMonitorMeasurement(monitor => monitor.CurrentPressure)); _underPressureCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_CACHE_UNDER_PRESSURE, () => GetPressureMonitorMeasurement(monitor => monitor.UnderPressure)); _pressureContributionCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_CACHE_PRESSURE_CONTRIBUTION_COUNT, () => GetPressureMonitorMeasurement(monitor => monitor.PressureContributionCount)); IEnumerable> GetPressureMonitorMeasurement(Func selector) where T : struct { foreach (var monitor in _pressureMonitors) { yield return new Measurement(selector(monitor.Value), monitor.Value.Dimensions); } } } /// /// Initializes a new instance of the class. /// /// The dimensions. public DefaultCacheMonitor(CacheMonitorDimensions dimensions) : this(new KeyValuePair[] { new("QueueId", dimensions.QueueId) }) { } private Measurement GetOldestToNewestAge() => new(_oldestToNewestAge.ElapsedTicks, _dimensions); private Measurement GetOldestAge() => new(_oldestMessageDequeueAgo.ElapsedTicks, _dimensions); /// public void TrackCachePressureMonitorStatusChange( string pressureMonitorType, bool underPressure, double? cachePressureContributionCount, double? currentPressure, double? flowControlThreshold) { var monitor = _pressureMonitors.GetOrAdd(pressureMonitorType, static (key, dimensions) => new PressureMonitorStatistics(key, dimensions), _dimensions); monitor.UnderPressure = underPressure ? 1 : 0; if (cachePressureContributionCount.HasValue) { monitor.PressureContributionCount = cachePressureContributionCount.Value; } if (currentPressure.HasValue) { monitor.CurrentPressure = currentPressure.Value; } } /// public void ReportCacheSize(long totalCacheSizeInByte) => _totalCacheSize = totalCacheSizeInByte; /// public void ReportMessageStatistics(DateTime? oldestMessageEnqueueTimeUtc, DateTime? oldestMessageDequeueTimeUtc, DateTime? newestMessageEnqueueTimeUtc, long totalMessageCount) { if (oldestMessageEnqueueTimeUtc.HasValue && newestMessageEnqueueTimeUtc.HasValue) { _oldestToNewestAge = ValueStopwatch.StartNew(newestMessageEnqueueTimeUtc.Value - oldestMessageEnqueueTimeUtc.Value); } if (oldestMessageDequeueTimeUtc.HasValue) { _oldestMessageDequeueAgo = ValueStopwatch.StartNew(DateTime.UtcNow - oldestMessageDequeueTimeUtc.Value); } _messageCount = totalMessageCount; } /// public void TrackMemoryAllocated(int memoryInByte) => Interlocked.Add(ref _memoryAllocated, memoryInByte); /// public void TrackMemoryReleased(int memoryInByte) => Interlocked.Add(ref _memoryReleased, memoryInByte); /// public void TrackMessagesAdded(long messageAdded) => Interlocked.Add(ref _messagesAdded, messageAdded); /// public void TrackMessagesPurged(long messagePurged) => Interlocked.Add(ref _messagesPurged, messagePurged); private sealed class PressureMonitorStatistics { public PressureMonitorStatistics(string type, KeyValuePair[] dimensions) { Dimensions = new KeyValuePair[dimensions.Length + 1]; dimensions.CopyTo(Dimensions, 0); Dimensions[^1] = new KeyValuePair("PressureMonitorType", type); } public KeyValuePair[] Dimensions { get; } public double PressureContributionCount { get; set; } public double CurrentPressure { get; set; } public int UnderPressure { get; set; } } } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/DefaultQueueAdapterReceiverMonitor.cs ================================================ using Orleans.Runtime; using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; namespace Orleans.Providers.Streams.Common { /// /// Queue adapter receiver monitor used as a default option in GeneratorStreamprovider and MemoryStreamProvider /// public class DefaultQueueAdapterReceiverMonitor : IQueueAdapterReceiverMonitor { private readonly Counter _initializationFailureCounter; private readonly Counter _initializationCallTimeCounter; private readonly Counter _initializationExceptionCounter; private readonly Counter _readFailureCounter; private readonly Counter _readCallTimeCounter; private readonly Counter _readExceptionCounter; private readonly Counter _shutdownFailureCounter; private readonly Counter _shutdownCallTimeCounter; private readonly Counter _shutdownExceptionCounter; private readonly ObservableCounter _messagesReceivedCounter; private readonly ObservableGauge _oldestMessageReadEnqueueTimeToNowCounter; private readonly ObservableGauge _newestMessageReadEnqueueTimeToNowCounter; private readonly KeyValuePair[] _dimensions; private ValueStopwatch _oldestMessageReadEnqueueAge; private ValueStopwatch _newestMessageReadEnqueueAge; private long _messagesReceived; protected DefaultQueueAdapterReceiverMonitor(KeyValuePair[] dimensions) { _dimensions = dimensions; _initializationFailureCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_INITIALIZATION_FAILURES); _initializationCallTimeCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_INITIALIZATION_DURATION); _initializationExceptionCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_INITIALIZATION_EXCEPTIONS); _readFailureCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_READ_FAILURES); _readCallTimeCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_READ_DURATION); _readExceptionCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_READ_EXCEPTIONS); _shutdownFailureCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_SHUTDOWN_FAILURES); _shutdownCallTimeCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_SHUTDOWN_DURATION); _shutdownExceptionCounter = Instruments.Meter.CreateCounter(InstrumentNames.STREAMS_QUEUE_SHUTDOWN_EXCEPTIONS); _messagesReceivedCounter = Instruments.Meter.CreateObservableCounter(InstrumentNames.STREAMS_QUEUE_MESSAGES_RECEIVED, GetMessagesReceivedCount); _oldestMessageReadEnqueueTimeToNowCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_OLDEST_MESSAGE_ENQUEUE_AGE, GetOldestMessageReadEnqueueAge); _newestMessageReadEnqueueTimeToNowCounter = Instruments.Meter.CreateObservableGauge(InstrumentNames.STREAMS_QUEUE_NEWEST_MESSAGE_ENQUEUE_AGE, GetNewestMessageReadEnqueueAge); } /// /// Initializes a new instance of the class. /// /// The dimensions. public DefaultQueueAdapterReceiverMonitor(ReceiverMonitorDimensions dimensions) : this(new KeyValuePair[] { new("QueueId", dimensions.QueueId) }) { } private Measurement GetOldestMessageReadEnqueueAge() => new(_oldestMessageReadEnqueueAge.ElapsedTicks, _dimensions); private Measurement GetNewestMessageReadEnqueueAge() => new(_newestMessageReadEnqueueAge.ElapsedTicks, _dimensions); private Measurement GetMessagesReceivedCount() => new(_messagesReceived, _dimensions); /// public void TrackInitialization(bool success, TimeSpan callTime, Exception exception) { _initializationFailureCounter.Add(success ? 0 : 1, _dimensions); _initializationCallTimeCounter.Add(callTime.Ticks, _dimensions); _initializationExceptionCounter.Add(exception is null ? 0 : 1, _dimensions); } /// public void TrackRead(bool success, TimeSpan callTime, Exception exception) { _readFailureCounter.Add(success ? 0 : 1, _dimensions); _readCallTimeCounter.Add(callTime.Ticks, _dimensions); _readExceptionCounter.Add(exception is null ? 0 : 1, _dimensions); } /// public void TrackMessagesReceived(long count, DateTime? oldestMessageEnqueueTimeUtc, DateTime? newestMessageEnqueueTimeUtc) { var now = DateTime.UtcNow; Interlocked.Add(ref _messagesReceived, count); if (oldestMessageEnqueueTimeUtc.HasValue) { var delta = now - oldestMessageEnqueueTimeUtc.Value; _oldestMessageReadEnqueueAge = ValueStopwatch.StartNew(delta); } if (newestMessageEnqueueTimeUtc.HasValue) { var delta = now - newestMessageEnqueueTimeUtc.Value; _newestMessageReadEnqueueAge = ValueStopwatch.StartNew(delta); } } /// public void TrackShutdown(bool success, TimeSpan callTime, Exception exception) { _shutdownFailureCounter.Add(success ? 0 : 1, _dimensions); _shutdownCallTimeCounter.Add(callTime.Ticks, _dimensions); _shutdownExceptionCounter.Add(exception is null ? 0 : 1, _dimensions); } } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/IBlockPoolMonitor.cs ================================================ namespace Orleans.Providers.Streams.Common { /// /// Monitor track block pool related metrics. Block pool is used in cache system for memory management /// public interface IBlockPoolMonitor { /// /// Called when memory is newly allocated by the cache. /// /// The allocated memory, in bytes. void TrackMemoryAllocated(long allocatedMemoryInBytes); /// /// Called when memory is released by the cache. /// /// The released memory, in bytes. void TrackMemoryReleased(long releasedMemoryInBytes); /// /// Periodically report block pool status /// /// Total memory this block pool allocated. /// Memory which is available for allocating to caches. /// Memory in use by caches. void Report(long totalSizeInByte, long availableMemoryInByte, long claimedMemoryInByte); } /// /// ObjectPoolMonitor report metrics for ObjectPool, which are based on object count. BlockPoolMonitor report metrics for BlockPool, which are based on memory size. /// These two monitor converge in orleans cache infrastructure, where ObjectPool is used as block pool to allocate memory, where each object represent a block of memory /// which has a size. ObjectPoolMonitorBridge is the bridge between these two monitors in cache infrastructure. When ObjectPoolMonitor is reporting a metric, /// the user configured BlockPoolMonitor will call its counterpart method and reporting metric based on the math: memoryInByte = objectCount*objectSizeInByte /// public class ObjectPoolMonitorBridge : IObjectPoolMonitor { private readonly IBlockPoolMonitor blockPoolMonitor; private readonly int blockSizeInBytes; /// /// Initializes a new instance of the class. /// /// The block pool monitor. /// The block size in bytes. public ObjectPoolMonitorBridge(IBlockPoolMonitor blockPoolMonitor, int blockSizeInBytes) { this.blockPoolMonitor = blockPoolMonitor; this.blockSizeInBytes = blockSizeInBytes; } /// public void TrackObjectAllocated() { long memoryAllocatedInByte = blockSizeInBytes; this.blockPoolMonitor.TrackMemoryAllocated(memoryAllocatedInByte); } /// public void TrackObjectReleased() { long memoryReleasedInByte = blockSizeInBytes; this.blockPoolMonitor.TrackMemoryReleased(memoryReleasedInByte); } /// public void Report(long totalObjects, long availableObjects, long claimedObjects) { var totalMemoryInByte = totalObjects * this.blockSizeInBytes; var availableMemoryInByte = availableObjects * this.blockSizeInBytes; var claimedMemoryInByte = claimedObjects * this.blockSizeInBytes; this.blockPoolMonitor.Report(totalMemoryInByte, availableMemoryInByte, claimedMemoryInByte); } } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/ICacheMonitor.cs ================================================ using System; namespace Orleans.Providers.Streams.Common { /// /// Responsible for monitoring cache related metrics. /// public interface ICacheMonitor { /// /// Called when the cache pressure monitor encounter a status change. /// /// Type of the pressure monitor. /// if set to , the cache is under pressure. /// The cache pressure contribution count. /// The current pressure. /// The flow control threshold. void TrackCachePressureMonitorStatusChange(string pressureMonitorType, bool underPressure, double? cachePressureContributionCount, double? currentPressure, double? flowControlThreshold); /// /// Called when messages are added to the cache. /// /// The number of messages added. void TrackMessagesAdded(long messagesAdded); /// /// Called when messages are purged from the cache. /// /// The number of messages purged. void TrackMessagesPurged(long messagesPurged); /// /// Called when new memory is allocated by the cache. /// /// The memory in bytes. void TrackMemoryAllocated(int memoryInBytes); /// /// Called when memory returned to block pool. /// /// The memory in bytes. void TrackMemoryReleased(int memoryInBytes); /// /// Called to report cache status metrics. /// /// The time in UTC when the oldest message was enqueued to the queue. /// The time in UTC when the oldest message was read from the queue and put in the cache. /// The time in UTC when the newest message was enqueued to the queue. /// The total message count. void ReportMessageStatistics(DateTime? oldestMessageEnqueueTimeUtc, DateTime? oldestMessageDequeueTimeUtc, DateTime? newestMessageEnqueueTimeUtc, long totalMessageCount); /// /// Called to report the total cache size. /// /// The total cache size in bytes. void ReportCacheSize(long totalCacheSizeInBytes); } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/IObjectPoolMonitor.cs ================================================ namespace Orleans.Providers.Streams.Common { /// /// Monitor track object pool related metrics /// public interface IObjectPoolMonitor { /// /// Called every time when an object is allocated. /// void TrackObjectAllocated(); /// /// Called every time an object was released back to the pool. /// void TrackObjectReleased(); /// /// Called to report object pool status. /// /// Total size of object pool. /// Count for objects in the pool which is available for allocating. /// Count for objects which are claimed, hence not available. void Report(long totalObjects, long availableObjects, long claimedObjects); } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/IQueueAdapterReceiverMonitor.cs ================================================ using System; namespace Orleans.Providers.Streams.Common { /// /// Responsible for monitoring receiver performance metrics. /// public interface IQueueAdapterReceiverMonitor { /// /// Track attempts to initialize the receiver. /// /// True if read succeeded, false if read failed. /// Init operation time. /// Exception caught if initialize fail. void TrackInitialization(bool success, TimeSpan callTime, Exception exception); /// /// Track attempts to read from the partition. Tracked per partition read operation. /// /// True if read succeeded, false if read failed. /// Time spent in read operation. /// The exception caught if read failed. void TrackRead(bool success, TimeSpan callTime, Exception exception); /// /// Tracks messages read and time taken per successful read. Tracked per successful partition read operation. /// /// Messages read. /// The oldest message enqueue time (UTC). /// The newest message enqueue time (UTC). void TrackMessagesReceived(long count, DateTime? oldestMessageEnqueueTimeUtc, DateTime? newestMessageEnqueueTimeUtc); /// /// Track attempts to shutdown the receiver. /// /// True if read succeeded, false if read failed. /// Shutdown operation time. /// Exception caught if shutdown fail. void TrackShutdown(bool success, TimeSpan callTime, Exception exception); } } ================================================ FILE: src/Orleans.Streaming/Common/Monitors/MonitorAggregationDimensions.cs ================================================ namespace Orleans.Providers.Streams.Common { /// /// Aggregation dimensions for receiver monitor. /// public class ReceiverMonitorDimensions { /// /// Initializes a new instance of the class. /// /// The queue identifier. public ReceiverMonitorDimensions(string queueId) { this.QueueId = queueId; } /// /// Initializes a new instance of the class. /// public ReceiverMonitorDimensions() { } /// /// Gets the queue identifier. /// public string QueueId { get; set; } } /// /// Aggregation dimensions for cache monitor. /// public class CacheMonitorDimensions : ReceiverMonitorDimensions { /// /// Initializes a new instance of the class. /// /// The queue identifier. /// The block pool identifier. public CacheMonitorDimensions(string queueId, string blockPoolId) :base(queueId) { this.BlockPoolId = blockPoolId; } /// /// Gets or sets the block pool identifier. /// /// The block pool identifier. public string BlockPoolId { get; set; } } /// /// Aggregation dimensions for block pool monitors. /// public class BlockPoolMonitorDimensions { /// /// Initializes a new instance of the class. /// /// The block pool identifier. public BlockPoolMonitorDimensions(string blockPoolId) { this.BlockPoolId = blockPoolId; } /// /// Gets or sets the block pool identifier. /// /// The block pool identifier. public string BlockPoolId { get; set; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/CachedMessage.cs ================================================ using System; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// This is a tightly packed cached structure containing a queue message. /// It should only contain value types. /// public struct CachedMessage { /// /// Identity of the stream this message is a part of. /// public StreamId StreamId; /// /// Sequence number. Position of event in queue. /// public long SequenceNumber; /// /// Event index. Index in batch. /// public int EventIndex; /// /// Time event was written to the queuing system. /// public DateTime EnqueueTimeUtc; /// /// Time event was read from the queuing system into this cache. /// public DateTime DequeueTimeUtc; /// /// Segment containing the serialized event data. /// public ArraySegment Segment; } /// /// Extensions for . /// public static class CachedMessageExtensions { /// /// Compares the specified cached message. /// /// The cached message. /// The token. /// A value indicating the relative order of the token to the cached message. public static int Compare(this ref CachedMessage cachedMessage, StreamSequenceToken token) { return cachedMessage.SequenceNumber != token.SequenceNumber ? (int)(cachedMessage.SequenceNumber - token.SequenceNumber) : cachedMessage.EventIndex - token.EventIndex; } /// /// Compares the stream identifier of a cached message. /// /// The cached message. /// The stream identifier. /// if streamId is equal to the value; otherwise . public static bool CompareStreamId(this ref CachedMessage cachedMessage, StreamId streamId) => cachedMessage.StreamId.Equals(streamId); } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/CachedMessageBlock.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// CachedMessageBlock is a block of tightly packed structures containing tracking data for cached messages. This data is /// tightly packed to reduced GC pressure. The tracking data is used by the queue cache to walk the cache serving ordered /// queue messages by stream. /// public class CachedMessageBlock : PooledResource { private const int OneKb = 1024; private const int DefaultCachedMessagesPerBlock = 16 * OneKb; // 16kb private readonly CachedMessage[] cachedMessages; private readonly int blockSize; private int writeIndex; private int readIndex; /// /// Linked list node, so this message block can be kept in a linked list. /// public LinkedListNode Node { get; private set; } /// /// Gets a value indicating whether more messages can be added to the block. /// public bool HasCapacity => writeIndex < blockSize; /// /// Gets a value indicating whether this block is empty. /// public bool IsEmpty => readIndex >= writeIndex; /// /// Gets the index of most recent message added to the block. /// public int NewestMessageIndex => writeIndex - 1; /// /// Gets the index of the oldest message in this block. /// public int OldestMessageIndex => readIndex; /// /// Gets the oldest message in the block. /// public CachedMessage OldestMessage => cachedMessages[OldestMessageIndex]; /// /// Gets the newest message in this block. /// public CachedMessage NewestMessage => cachedMessages[NewestMessageIndex]; /// /// Gets the number of messages in this block. /// public int ItemCount { get { int count = writeIndex - readIndex; return count >= 0 ? count : 0; } } /// /// Block of cached messages. /// /// The block size, expressed as a number of messages. public CachedMessageBlock(int blockSize = DefaultCachedMessagesPerBlock) { this.blockSize = blockSize; cachedMessages = new CachedMessage[blockSize]; writeIndex = 0; readIndex = 0; Node = new LinkedListNode(this); } /// /// Removes a message from the start of the block (oldest data). /// /// if there are more items remaining; otherwise . public bool Remove() { if (readIndex < writeIndex) { readIndex++; return true; } return false; } /// /// Add a message from the queue to the block. /// Converts the queue message to a cached message and stores it at the end of the block. /// /// /// The message to add to this block. /// public void Add(CachedMessage message) { if (!HasCapacity) { throw new InvalidOperationException("Block is full"); } int index = writeIndex++; cachedMessages[index] = message; } /// /// Access the cached message at the provided index. /// /// The index to access. /// The message at the specified index. public CachedMessage this[int index] { get { if (index >= writeIndex || index < readIndex) { throw new ArgumentOutOfRangeException(nameof(index)); } return cachedMessages[index]; } } /// /// Gets the sequence token of the cached message a the provided index /// /// The index of the message to access. /// The data adapter. /// The sequence token. public StreamSequenceToken GetSequenceToken(int index, ICacheDataAdapter dataAdapter) { if (index >= writeIndex || index < readIndex) { throw new ArgumentOutOfRangeException(nameof(index)); } return dataAdapter.GetSequenceToken(ref cachedMessages[index]); } /// /// Gets the sequence token of the newest message in this block /// /// The data adapter. /// The sequence token of the newest message in this block. public StreamSequenceToken GetNewestSequenceToken(ICacheDataAdapter dataAdapter) { return GetSequenceToken(NewestMessageIndex, dataAdapter); } /// /// Gets the sequence token of the oldest message in this block /// /// The data adapter. /// The sequence token of the oldest message in this block. public StreamSequenceToken GetOldestSequenceToken(ICacheDataAdapter dataAdapter) { return GetSequenceToken(OldestMessageIndex, dataAdapter); } /// /// Gets the index of the first message in this block that has a sequence token at or before the provided token /// /// The sequence token. /// The index of the first message in this block that has a sequence token equal to or before the provided token. public int GetIndexOfFirstMessageLessThanOrEqualTo(StreamSequenceToken token) { for (int i = writeIndex - 1; i >= readIndex; i--) { if (cachedMessages[i].Compare(token) <= 0) { return i; } } throw new ArgumentOutOfRangeException(nameof(token)); } /// /// Tries to find the first message in the block that is part of the provided stream. /// /// The stream identifier. /// The data adapter. /// The index. /// if the message was found, otherwise. public bool TryFindFirstMessage(StreamId streamId, ICacheDataAdapter dataAdapter, out int index) { return TryFindNextMessage(readIndex, streamId, dataAdapter, out index); } /// /// Tries to get the next message from the provided stream, starting at the start index. /// /// The start index. /// The stream identifier. /// The data adapter. /// The index. /// if the message was found, otherwise. public bool TryFindNextMessage(int start, StreamId streamId, ICacheDataAdapter dataAdapter, out int index) { if (start < readIndex) { throw new ArgumentOutOfRangeException(nameof(start)); } for (int i = start; i < writeIndex; i++) { if (cachedMessages[i].CompareStreamId(streamId)) { index = i; return true; } } index = writeIndex - 1; return false; } /// public override void OnResetState() { writeIndex = 0; readIndex = 0; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/CachedMessagePool.cs ================================================ namespace Orleans.Providers.Streams.Common { /// /// Pool of tightly packed cached messages that are kept in large blocks to reduce GC pressure. /// internal class CachedMessagePool { private readonly IObjectPool messagePool; private CachedMessageBlock currentMessageBlock; /// /// Allocates a pool of cached message blocks. /// /// The cache data adapter. public CachedMessagePool(ICacheDataAdapter cacheDataAdapter) { messagePool = new ObjectPool( () => new CachedMessageBlock()); } /// /// Allocates a message in a block and returns the block the message is in. /// /// The cached message block which the message was allocated in. public CachedMessageBlock AllocateMessage(CachedMessage message) { CachedMessageBlock returnBlock = currentMessageBlock ?? (currentMessageBlock = messagePool.Allocate()); returnBlock.Add(message); // blocks at capacity are eligable for purge, so we don't want to be holding on to them. if (!currentMessageBlock.HasCapacity) { currentMessageBlock = messagePool.Allocate(); } return returnBlock; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/ChronologicalEvictionStrategy.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; namespace Orleans.Providers.Streams.Common { /// /// Eviction strategy that evicts data based off of age. /// public partial class ChronologicalEvictionStrategy : IEvictionStrategy { private readonly ILogger logger; private readonly TimePurgePredicate timePurge; /// /// Buffers which are currently in use in the cache /// Protected for test purposes /// protected readonly Queue inUseBuffers; private FixedSizeBuffer currentBuffer; private readonly ICacheMonitor cacheMonitor; private readonly PeriodicAction periodicMonitoring; private long cacheSizeInByte; /// /// Initializes a new instance of the class. /// /// The logger. /// The time-based purge predicate. /// The cache monitor. /// "Interval to write periodic statistics. Only triggered for active caches. public ChronologicalEvictionStrategy(ILogger logger, TimePurgePredicate timePurage, ICacheMonitor cacheMonitor, TimeSpan? monitorWriteInterval) { if (logger == null) throw new ArgumentException(nameof(logger)); if (timePurage == null) throw new ArgumentException(nameof(timePurage)); this.logger = logger; this.timePurge = timePurage; this.inUseBuffers = new Queue(); // monitoring this.cacheMonitor = cacheMonitor; if (this.cacheMonitor != null && monitorWriteInterval.HasValue) { this.periodicMonitoring = new PeriodicAction(monitorWriteInterval.Value, this.ReportCacheSize); } this.cacheSizeInByte = 0; } private void ReportCacheSize() { this.cacheMonitor.ReportCacheSize(this.cacheSizeInByte); } /// public IPurgeObservable PurgeObservable { private get; set; } /// public Action OnPurged { get; set; } /// public void OnBlockAllocated(FixedSizeBuffer newBlock) { if (this.PurgeObservable.IsEmpty && this.currentBuffer != null && this.inUseBuffers.Contains(this.currentBuffer) && this.inUseBuffers.Count == 1) { this.inUseBuffers.Dequeue().Dispose(); } this.inUseBuffers.Enqueue(newBlock); this.currentBuffer = newBlock; //report metrics this.cacheSizeInByte += newBlock.SizeInByte; this.cacheMonitor?.TrackMemoryAllocated(newBlock.SizeInByte); } /// public void PerformPurge(DateTime nowUtc) { PerformPurgeInternal(nowUtc); this.periodicMonitoring?.TryAction(nowUtc); } /// /// Given a cached message, indicates whether it should be purged from the cache. /// /// The cached message. /// The newest cached message. /// The current time (UTC). /// if the message should be purged, otherwise. protected virtual bool ShouldPurge(ref CachedMessage cachedMessage, ref CachedMessage newestCachedMessage, DateTime nowUtc) { TimeSpan timeInCache = nowUtc - cachedMessage.DequeueTimeUtc; // age of message relative to the most recent event in the cache. TimeSpan relativeAge = newestCachedMessage.EnqueueTimeUtc - cachedMessage.EnqueueTimeUtc; return timePurge.ShouldPurgeFromTime(timeInCache, relativeAge); } private void PerformPurgeInternal(DateTime nowUtc) { //if the cache is empty, then nothing to purge, return if (this.PurgeObservable.IsEmpty) return; int itemsPurged = 0; CachedMessage neweswtMessageInCache = this.PurgeObservable.Newest.Value; CachedMessage? lastMessagePurged = null; while (!this.PurgeObservable.IsEmpty) { var oldestMessageInCache = this.PurgeObservable.Oldest.Value; if (!ShouldPurge(ref oldestMessageInCache, ref neweswtMessageInCache, nowUtc)) { break; } lastMessagePurged = oldestMessageInCache; itemsPurged++; this.PurgeObservable.RemoveOldestMessage(); } //if nothing got purged, return if (itemsPurged == 0) return; //items got purged, time to conduct follow up actions this.cacheMonitor?.TrackMessagesPurged(itemsPurged); OnPurged?.Invoke(lastMessagePurged, this.PurgeObservable.Newest); FreePurgedBuffers(lastMessagePurged, this.PurgeObservable.Oldest); ReportPurge(this.logger, this.PurgeObservable, itemsPurged); } private void FreePurgedBuffers(CachedMessage? lastMessagePurged, CachedMessage? oldestMessageInCache) { if (this.inUseBuffers.Count <= 0 || !lastMessagePurged.HasValue) return; int memoryReleasedInByte = 0; object IdOfLastPurgedBufferId = lastMessagePurged?.Segment.Array; // IdOfLastBufferInCache will be null if cache is empty after purge object IdOfLastBufferInCacheId = oldestMessageInCache?.Segment.Array; //all buffers older than LastPurgedBuffer should be purged while (this.inUseBuffers.Peek().Id != IdOfLastPurgedBufferId) { var purgedBuffer = this.inUseBuffers.Dequeue(); memoryReleasedInByte += purgedBuffer.SizeInByte; purgedBuffer.Dispose(); } // if last purged message does not share buffer with remaining messages in cache and cache is not empty //then last purged buffer should be purged too if (IdOfLastBufferInCacheId != null && IdOfLastPurgedBufferId != IdOfLastBufferInCacheId) { var purgedBuffer = this.inUseBuffers.Dequeue(); memoryReleasedInByte += purgedBuffer.SizeInByte; purgedBuffer.Dispose(); } //report metrics if (memoryReleasedInByte > 0) { this.cacheSizeInByte -= memoryReleasedInByte; this.cacheMonitor?.TrackMemoryReleased(memoryReleasedInByte); } } /// /// Logs cache purge activity /// /// The logger. /// The purge observable. /// The items purged. private static void ReportPurge(ILogger logger, IPurgeObservable purgeObservable, int itemsPurged) { if (!logger.IsEnabled(LogLevel.Debug)) return; int itemCountAfterPurge = purgeObservable.ItemCount; var itemCountBeforePurge = itemCountAfterPurge + itemsPurged; if (itemCountAfterPurge == 0) { LogBlockPurgedCacheEmpty(logger); } else { LogBlockPurged(logger, itemCountBeforePurge - itemCountAfterPurge, itemCountAfterPurge); } } [LoggerMessage( Level = LogLevel.Debug, Message = "BlockPurged: cache empty" )] private static partial void LogBlockPurgedCacheEmpty(ILogger logger); [LoggerMessage( Level = LogLevel.Debug, Message = "BlockPurged: PurgeCount: {PurgeCount}, CacheSize: {ItemCountAfterPurge}" )] private static partial void LogBlockPurged(ILogger logger, int purgeCount, int itemCountAfterPurge); } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/FixedSizeBuffer.cs ================================================ using System; namespace Orleans.Providers.Streams.Common { /// /// Manages a contiguous block of memory. /// Calls purge action with itself as the purge request when it's signaled to purge. /// public class FixedSizeBuffer : PooledResource { private readonly byte[] buffer; private int count; /// /// Buffer size in bytes. /// public readonly int SizeInByte; /// /// Unique identifier of this buffer. /// public object Id => buffer; /// /// Manages access to a fixed size byte buffer. /// /// The block size, in bytes. public FixedSizeBuffer(int blockSizeInByte) { if (blockSizeInByte < 0) { throw new ArgumentOutOfRangeException(nameof(blockSizeInByte), "blockSize must be positive value."); } count = 0; this.SizeInByte = blockSizeInByte; buffer = new byte[this.SizeInByte]; } /// /// Try to get a segment with a buffer of the specified size from this block. /// Fail if there is not enough space available /// /// The size. /// The segment. /// if the segment was retrieved; otherwise . public bool TryGetSegment(int size, out ArraySegment value) { value = default; if (size > this.SizeInByte - count) { return false; } value = new ArraySegment(buffer, count, size); count += size; return true; } /// public override void OnResetState() { count = 0; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/ICacheDataAdapter.cs ================================================ using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// Pooled queue cache stores data in tightly packed structures that need to be transformed to various /// other formats quickly. Since the data formats may change by queue type and data format, /// this interface allows adapter developers to build custom data transforms appropriate for /// the various types of queue data. /// public interface ICacheDataAdapter { /// /// Converts a cached message to a batch container for delivery /// /// The cached message. /// The batch container. IBatchContainer GetBatchContainer(ref CachedMessage cachedMessage); /// /// Gets the stream sequence token from a cached message. /// /// The cached message. /// The sequence token. StreamSequenceToken GetSequenceToken(ref CachedMessage cachedMessage); } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/IEvictionStrategy.cs ================================================ using System; namespace Orleans.Providers.Streams.Common { /// /// Eviction strategy for the PooledQueueCache /// public interface IEvictionStrategy { /// /// Gets the , which is implemented by the cache to do purge related actions and invoked by the eviction strategy. /// IPurgeObservable PurgeObservable { set; } /// /// Gets or sets the method which will be called when purge is finished. /// Action OnPurged { get; set; } /// /// Method which should be called when pulling agent try to do a purge on the cache /// /// The current time (UTC) void PerformPurge(DateTime utcNow); /// /// Method which should be called when data adapter allocated a new block /// /// The new block. void OnBlockAllocated(FixedSizeBuffer newBlock); } /// /// Functionality for purge-related actions. /// public interface IPurgeObservable { /// /// Removes oldest message in the cache. /// void RemoveOldestMessage(); /// /// Gets the newest message in the cache. /// CachedMessage? Newest { get; } /// /// Gets the oldest message in the cache. /// CachedMessage? Oldest { get; } /// /// Gets the message count. /// int ItemCount { get; } /// /// Gets a value indicating whether this instance is empty. /// bool IsEmpty { get; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/IObjectPool.cs ================================================ using System; using System.Threading; namespace Orleans.Providers.Streams.Common { /// /// Simple object pool Interface. /// Objects allocated should be returned to the pool when disposed. /// /// public interface IObjectPool where T : IDisposable { /// /// Allocates a pooled resource /// /// T Allocate(); /// /// Returns a resource to the pool /// /// void Free(T resource); } /// /// Utility class to support pooled objects by allowing them to track the pool they came from and return to it when disposed /// /// public abstract class PooledResource : IDisposable where T : PooledResource, IDisposable { private IObjectPool pool; /// /// Gets the pool to return this resource to upon disposal. /// A pool must set this property upon resource allocation. /// public IObjectPool Pool { set { pool = value; } } /// /// If this object is to be used in a fixed size object pool, this call should be /// overridden with the purge implementation that returns the object to the pool. /// public virtual void SignalPurge() { Dispose(); } /// /// Returns item to pool. /// public void Dispose() { IObjectPool localPool = Interlocked.Exchange(ref pool, null); if (localPool != null) { OnResetState(); localPool.Free((T)this); } } /// /// Notifies the object that it has been purged, so it can reset itself to /// the state of a newly allocated object. /// public virtual void OnResetState() { } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/ObjectPool.cs ================================================ using System; using System.Threading; using System.Collections.Concurrent; namespace Orleans.Providers.Streams.Common { /// /// Simple object pool that uses a stack to store available objects. /// /// public class ObjectPool : IObjectPool where T : PooledResource { private const int DefaultPoolCapacity = 1 << 10; // 1k private readonly ConcurrentStack pool; private readonly Func factoryFunc; private long totalObjects; /// /// monitor to report statistics for current object pool /// private readonly IObjectPoolMonitor monitor; private readonly PeriodicAction periodicMonitoring; /// /// Simple object pool /// /// Function used to create new resources of type T /// monitor to report statistics for object pool /// public ObjectPool(Func factoryFunc, IObjectPoolMonitor monitor = null, TimeSpan? monitorWriteInterval = null) { if (factoryFunc == null) { throw new ArgumentNullException(nameof(factoryFunc)); } this.factoryFunc = factoryFunc; pool = new ConcurrentStack(); // monitoring this.monitor = monitor; if (this.monitor != null && monitorWriteInterval.HasValue) { this.periodicMonitoring = new PeriodicAction(monitorWriteInterval.Value, this.ReportObjectPoolStatistics); } this.totalObjects = 0; } /// /// Allocates a pooled resource /// /// public virtual T Allocate() { T resource; //if couldn't pop a resource from the pool, create a new resource using factoryFunc from outside of the pool if (!pool.TryPop(out resource)) { resource = factoryFunc(); Interlocked.Increment(ref this.totalObjects); } this.monitor?.TrackObjectAllocated(); this.periodicMonitoring?.TryAction(DateTime.UtcNow); resource.Pool = this; return resource; } /// /// Returns a resource to the pool /// /// public virtual void Free(T resource) { this.monitor?.TrackObjectReleased(); this.periodicMonitoring?.TryAction(DateTime.UtcNow); pool.Push(resource); } private void ReportObjectPoolStatistics() { var availableObjects = this.pool.Count; long claimedObjects = this.totalObjects - availableObjects; this.monitor.Report(this.totalObjects, availableObjects, claimedObjects); } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/PooledQueueCache.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// The PooledQueueCache is a cache that is intended to serve as a message cache in an IQueueCache. /// It is capable of storing large numbers of messages (gigs worth of messages) for extended periods /// of time (minutes to indefinite), while incurring a minimal performance hit due to garbage collection. /// This pooled cache allocates memory and never releases it. It keeps freed resources available in pools /// that remain in application use through the life of the service. This means these objects go to gen2, /// are compacted, and then stay there. This is relatively cheap, as the only cost they now incur is /// the cost of checking to see if they should be freed in each collection cycle. Since this cache uses /// small numbers of large objects with relatively simple object graphs, they are less costly to check /// then large numbers of smaller objects with more complex object graphs. /// For performance reasons this cache is designed to more closely align with queue specific data. This is, /// in part, why, unlike the SimpleQueueCache, this cache does not implement IQueueCache. It is intended /// to be used in queue specific implementations of IQueueCache. /// public class PooledQueueCache: IPurgeObservable { // linked list of message bocks. First is newest. private readonly LinkedList messageBlocks; private readonly CachedMessagePool pool; private readonly ICacheDataAdapter cacheDataAdapter; private readonly ILogger logger; private readonly ICacheMonitor cacheMonitor; private readonly TimeSpan purgeMetadataInterval; private readonly PeriodicAction periodicMonitoring; private readonly PeriodicAction periodicMetadaPurging; private readonly Dictionary lastPurgedToken = new Dictionary(); /// /// Gets the cached message most recently added. /// public CachedMessage? Newest { get { if (IsEmpty) return null; return messageBlocks.First.Value.NewestMessage; } } /// /// Gets the oldest message in cache. /// public CachedMessage? Oldest { get { if (IsEmpty) return null; return messageBlocks.Last.Value.OldestMessage; } } /// /// Gets the cached message count. /// public int ItemCount { get; private set; } /// /// Pooled queue cache is a cache of message that obtains resource from a pool /// /// The cache data adapter. /// The logger. /// The cache monitor. /// The cache monitor write interval. Only triggered for active caches. /// The interval after which to purge cache metadata. public PooledQueueCache( ICacheDataAdapter cacheDataAdapter, ILogger logger, ICacheMonitor cacheMonitor, TimeSpan? cacheMonitorWriteInterval, TimeSpan? purgeMetadataInterval = null) { this.cacheDataAdapter = cacheDataAdapter ?? throw new ArgumentNullException(nameof(cacheDataAdapter)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.ItemCount = 0; pool = new CachedMessagePool(cacheDataAdapter); messageBlocks = new LinkedList(); this.cacheMonitor = cacheMonitor; if (this.cacheMonitor != null && cacheMonitorWriteInterval.HasValue) { this.periodicMonitoring = new PeriodicAction(cacheMonitorWriteInterval.Value, this.ReportCacheMessageStatistics); } if (purgeMetadataInterval.HasValue) { this.purgeMetadataInterval = purgeMetadataInterval.Value; this.periodicMetadaPurging = new PeriodicAction(purgeMetadataInterval.Value.Divide(5), this.PurgeMetadata); } } /// /// Indicates whether the cache is empty /// public bool IsEmpty => messageBlocks.Count == 0 || (messageBlocks.Count == 1 && messageBlocks.First.Value.IsEmpty); /// /// Acquires a cursor to enumerate through the messages in the cache at the provided sequenceToken, /// filtered on the specified stream. /// /// stream identity /// /// public object GetCursor(StreamId streamId, StreamSequenceToken sequenceToken) { var cursor = new Cursor(streamId); SetCursor(cursor, sequenceToken); return cursor; } private void ReportCacheMessageStatistics() { if (this.IsEmpty) { this.cacheMonitor.ReportMessageStatistics(null, null, null, this.ItemCount); } else { var newestMessage = this.Newest.Value; var oldestMessage = this.Oldest.Value; var newestMessageEnqueueTime = newestMessage.EnqueueTimeUtc; var oldestMessageEnqueueTime = oldestMessage.EnqueueTimeUtc; var oldestMessageDequeueTime = oldestMessage.DequeueTimeUtc; this.cacheMonitor.ReportMessageStatistics(oldestMessageEnqueueTime, oldestMessageDequeueTime, newestMessageEnqueueTime, this.ItemCount); } } private void PurgeMetadata() { var now = DateTime.UtcNow; // Get all keys older than this.purgeMetadataInterval foreach (var kvp in this.lastPurgedToken) { if (kvp.Value.TimeStamp + this.purgeMetadataInterval < now) { lastPurgedToken.Remove(kvp.Key); } } } private void TrackAndPurgeMetadata(CachedMessage messageToRemove) { // If tracking of evicted message metadata is disabled, do nothing if (this.periodicMetadaPurging == null) return; var now = DateTime.UtcNow; var streamId = messageToRemove.StreamId; var token = this.cacheDataAdapter.GetSequenceToken(ref messageToRemove); this.lastPurgedToken[streamId] = (now, token); this.periodicMetadaPurging.TryAction(now); } private void SetCursor(Cursor cursor, StreamSequenceToken sequenceToken) { // If nothing in cache, unset token, and wait for more data. if (messageBlocks.Count == 0) { cursor.State = CursorStates.Unset; cursor.SequenceToken = sequenceToken; return; } LinkedListNode newestBlock = messageBlocks.First; // if sequenceToken is null, iterate from newest message in cache if (sequenceToken == null) { cursor.State = CursorStates.Idle; cursor.CurrentBlock = newestBlock; cursor.Index = newestBlock.Value.NewestMessageIndex; cursor.SequenceToken = newestBlock.Value.GetNewestSequenceToken(cacheDataAdapter); return; } // If sequenceToken is too new to be in cache, unset token, and wait for more data. CachedMessage newestMessage = newestBlock.Value.NewestMessage; if (newestMessage.Compare(sequenceToken) < 0) { cursor.State = CursorStates.Unset; cursor.SequenceToken = sequenceToken; return; } // Check to see if sequenceToken is too old to be in cache var oldestBlock = messageBlocks.Last; var oldestMessage = oldestBlock.Value.OldestMessage; if (oldestMessage.Compare(sequenceToken) > 0) { // Check if we missed an event since we last purged the cache if (this.lastPurgedToken.TryGetValue(cursor.StreamId, out var entry) && sequenceToken.CompareTo(entry.Token) >= 0) { // If the token is more recent than the last purged token, then we didn't lose anything. Start from the oldest message in cache cursor.State = CursorStates.Set; cursor.CurrentBlock = oldestBlock; cursor.Index = oldestBlock.Value.OldestMessageIndex; cursor.SequenceToken = oldestBlock.Value.GetOldestSequenceToken(cacheDataAdapter); return; } else { throw new QueueCacheMissException(sequenceToken, messageBlocks.Last.Value.GetOldestSequenceToken(cacheDataAdapter), messageBlocks.First.Value.GetNewestSequenceToken(cacheDataAdapter)); } } // Find block containing sequence number, starting from the newest and working back to oldest LinkedListNode node = messageBlocks.First; while (true) { CachedMessage oldestMessageInBlock = node.Value.OldestMessage; if (oldestMessageInBlock.Compare(sequenceToken) <= 0) { break; } node = node.Next; } // return cursor from start. cursor.CurrentBlock = node; cursor.Index = node.Value.GetIndexOfFirstMessageLessThanOrEqualTo(sequenceToken); // if cursor has been idle, move to next message after message specified by sequenceToken if(cursor.State == CursorStates.Idle) { // if there are more messages in this block, move to next message if (!cursor.IsNewestInBlock) { cursor.Index++; } // if this is the newest message in this block, move to oldest message in newer block else if (node.Previous != null) { cursor.CurrentBlock = node.Previous; cursor.Index = cursor.CurrentBlock.Value.OldestMessageIndex; } else { cursor.State = CursorStates.Idle; return; } } cursor.SequenceToken = cursor.CurrentBlock.Value.GetSequenceToken(cursor.Index, cacheDataAdapter); cursor.State = CursorStates.Set; } /// /// Acquires the next message in the cache at the provided cursor /// /// /// /// public bool TryGetNextMessage(object cursorObj, out IBatchContainer message) { message = null; if (cursorObj == null) { throw new ArgumentNullException(nameof(cursorObj)); } var cursor = cursorObj as Cursor; if (cursor == null) { throw new ArgumentOutOfRangeException(nameof(cursorObj), "Cursor is bad"); } if (cursor.State != CursorStates.Set) { SetCursor(cursor, cursor.SequenceToken); if (cursor.State != CursorStates.Set) { return false; } } // has this message been purged CachedMessage oldestMessage = messageBlocks.Last.Value.OldestMessage; if (oldestMessage.Compare(cursor.SequenceToken) > 0) { throw new QueueCacheMissException(cursor.SequenceToken, messageBlocks.Last.Value.GetOldestSequenceToken(cacheDataAdapter), messageBlocks.First.Value.GetNewestSequenceToken(cacheDataAdapter)); } // Iterate forward (in time) in the cache until we find a message on the stream or run out of cached messages. // Note that we get the message from the current cursor location, then move it forward. This means that if we return true, the cursor // will point to the next message after the one we're returning. while (cursor.State == CursorStates.Set) { CachedMessage currentMessage = cursor.Message; // Have we caught up to the newest event, if so set cursor to idle. if (cursor.CurrentBlock == messageBlocks.First && cursor.IsNewestInBlock) { cursor.State = CursorStates.Idle; cursor.SequenceToken = messageBlocks.First.Value.GetNewestSequenceToken(cacheDataAdapter); } else // move to next { int index; if (cursor.IsNewestInBlock) { cursor.CurrentBlock = cursor.CurrentBlock.Previous; cursor.CurrentBlock.Value.TryFindFirstMessage(cursor.StreamId, this.cacheDataAdapter, out index); } else { cursor.CurrentBlock.Value.TryFindNextMessage(cursor.Index + 1, cursor.StreamId, this.cacheDataAdapter, out index); } cursor.Index = index; } // check if this message is in the cursor's stream if (currentMessage.CompareStreamId(cursor.StreamId)) { message = cacheDataAdapter.GetBatchContainer(ref currentMessage); cursor.SequenceToken = cursor.CurrentBlock.Value.GetSequenceToken(cursor.Index, cacheDataAdapter); return true; } } return false; } /// /// Add a list of queue message to the cache /// /// /// /// public void Add(List messages, DateTime dequeueTime) { foreach (var message in messages) { this.Add(message); } this.cacheMonitor?.TrackMessagesAdded(messages.Count); periodicMonitoring?.TryAction(dequeueTime); } private void Add(CachedMessage message) { // allocate message from pool CachedMessageBlock block = pool.AllocateMessage(message); // If new block, add message block to linked list if (block != messageBlocks.FirstOrDefault()) messageBlocks.AddFirst(block.Node); ItemCount++; } /// /// Remove oldest message in the cache, remove oldest block too if the block is empty /// public void RemoveOldestMessage() { TrackAndPurgeMetadata(this.messageBlocks.Last.Value.OldestMessage); this.messageBlocks.Last.Value.Remove(); this.ItemCount--; CachedMessageBlock lastCachedMessageBlock = this.messageBlocks.Last.Value; // if block is currently empty, but all capacity has been exausted, remove if (lastCachedMessageBlock.IsEmpty && !lastCachedMessageBlock.HasCapacity) { lastCachedMessageBlock.Dispose(); this.messageBlocks.RemoveLast(); } } private enum CursorStates { Unset, // Not yet set, or points to some data in the future. Set, // Points to a message in the cache Idle, // Has iterated over all relevant events in the cache and is waiting for more data on the stream. } private class Cursor { public readonly StreamId StreamId; public Cursor(StreamId streamId) { StreamId = streamId; State = CursorStates.Unset; } public CursorStates State; // current sequence token public StreamSequenceToken SequenceToken; // reference into cache public LinkedListNode CurrentBlock; public int Index; // utilities public bool IsNewestInBlock => Index == CurrentBlock.Value.NewestMessageIndex; public CachedMessage Message => CurrentBlock.Value[Index]; } } } ================================================ FILE: src/Orleans.Streaming/Common/PooledCache/TimePurgePredicate.cs ================================================ using System; namespace Orleans.Providers.Streams.Common { /// /// Determines if data should be purged based off time. /// public class TimePurgePredicate { private readonly TimeSpan minTimeInCache; private readonly TimeSpan maxRelativeMessageAge; /// /// Initializes a new instance of the class. /// /// The minimum time data should be kept in cache, unless purged due to data size. /// The maximum age of data to keep in the cache. public TimePurgePredicate(TimeSpan minTimeInCache, TimeSpan maxRelativeMessageAge) { this.minTimeInCache = minTimeInCache; this.maxRelativeMessageAge = maxRelativeMessageAge; } /// /// Checks to see if the message should be purged. /// Message should be purged if its relative age is greater than maxRelativeMessageAge and has been in the cache longer than the minTimeInCache. /// /// The amount of time message has been in this cache /// The age of message relative to the most recent events read /// if the message should be purged; otherwise . public virtual bool ShouldPurgeFromTime(TimeSpan timeInCache, TimeSpan relativeAge) { // if time in cache exceeds the minimum and age of data is greater than max allowed, purge return timeInCache > minTimeInCache && relativeAge > maxRelativeMessageAge; } } } ================================================ FILE: src/Orleans.Streaming/Common/RecoverableStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Hosting { /// /// Silo-specific configuration builder for recoverable streams. /// public interface ISiloRecoverableStreamConfigurator : ISiloPersistentStreamConfigurator {} /// /// Extension methods for . /// public static class SiloRecoverableStreamConfiguratorExtensions { /// /// Configures statistics options for a reliable stream provider. /// /// The configuration builder. /// The configuration delegate. public static void ConfigureStatistics(this ISiloRecoverableStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } /// /// Configures cache eviction options for a reliable stream provider. /// /// The configuration builder. /// The configuration delegate. public static void ConfigureCacheEviction(this ISiloRecoverableStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } } /// /// Configures reliable streams. /// public class SiloRecoverableStreamConfigurator : SiloPersistentStreamConfigurator, ISiloRecoverableStreamConfigurator { /// /// Initializes a new instance of the class. /// /// The stream provider name. /// The configuration delegate. /// The adapter factory. public SiloRecoverableStreamConfigurator( string name, Action> configureDelegate, Func adapterFactory) : base(name, configureDelegate, adapterFactory) { this.ConfigureDelegate(services => services .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name)); } } } ================================================ FILE: src/Orleans.Streaming/Common/RecoverableStreamOptions.cs ================================================ using System; using Orleans.Streams; namespace Orleans.Configuration { /// /// Configuration options for stream cache eviction. /// public class StreamCacheEvictionOptions { /// /// Gets or sets the minimum time a message will stay in cache before it is available for time based purge. /// public TimeSpan DataMinTimeInCache { get; set; } = DefaultDataMinTimeInCache; /// /// The default value for . /// public static readonly TimeSpan DefaultDataMinTimeInCache = TimeSpan.FromMinutes(5); /// /// Gets or sets the difference in time between the newest and oldest messages in the cache. Any messages older than this will be purged from the cache. /// public TimeSpan DataMaxAgeInCache { get; set; } = DefaultDataMaxAgeInCache; /// /// The default value for /// public static readonly TimeSpan DefaultDataMaxAgeInCache = TimeSpan.FromMinutes(30); /// /// Gets or sets the minimum time that message metadata () will stay in cache before it is available for time based purge. /// Used to avoid cache miss if the full message was purged. /// Set to to disable this tracking. /// public TimeSpan? MetadataMinTimeInCache { get; set; } = DefaultMetadataMinTimeInCache; /// /// The default value for . /// public static readonly TimeSpan DefaultMetadataMinTimeInCache = DefaultDataMinTimeInCache.Multiply(2); } /// /// Configuration options for stream statistics. /// public class StreamStatisticOptions { /// /// Gets or sets the statistic monitor write interval. /// Statistics generation is triggered by activity. Interval will be ignored when streams are inactive. /// public TimeSpan StatisticMonitorWriteInterval { get; set; } = DefaultStatisticMonitorWriteInterval; /// /// The default value for . /// public static readonly TimeSpan DefaultStatisticMonitorWriteInterval = TimeSpan.FromMinutes(5); } } ================================================ FILE: src/Orleans.Streaming/Common/SegmentBuilder.cs ================================================ using System; using System.Runtime.InteropServices; namespace Orleans.Providers.Streams.Common { /// /// Utility class for encoding data into an ArraySegment. /// public static class SegmentBuilder { /// /// Calculates how much space will be needed to append the provided bytes into the segment. /// public static int CalculateAppendSize(ReadOnlySpan memory) => memory.Length + sizeof(int); /// /// Calculates how much space will be needed to append the provided string into the segment. /// /// /// public static int CalculateAppendSize(string str) => str is null ? sizeof(int) : str.Length * sizeof(char) + sizeof(int); /// /// Appends a of bytes to the end of the segment /// /// /// /// public static void Append(ArraySegment segment, ref int writerOffset, ReadOnlySpan bytes) { if (segment.Array == null) { throw new ArgumentNullException(nameof(segment)); } var length = bytes.Length; MemoryMarshal.Write(segment.AsSpan(writerOffset), in length); writerOffset += sizeof(int); if (bytes.Length > 0) { bytes.CopyTo(segment.AsSpan(writerOffset)); writerOffset += bytes.Length; } } /// /// Appends a string to the end of the segment /// /// /// /// public static void Append(ArraySegment segment, ref int writerOffset, string str) { if (segment.Array == null) { throw new ArgumentNullException(nameof(segment)); } if (str == null) { var length = -1; MemoryMarshal.Write(segment.AsSpan(writerOffset), in length); writerOffset += sizeof(int); } else { Append(segment, ref writerOffset, MemoryMarshal.AsBytes(str.AsSpan())); } } /// /// Reads the next item in the segment as a byte array. For performance, this is returned as a sub-segment of the original segment. /// /// public static ArraySegment ReadNextBytes(ArraySegment segment, ref int readerOffset) { if (segment.Array == null) { throw new ArgumentNullException(nameof(segment)); } int size = BitConverter.ToInt32(segment.Array, segment.Offset + readerOffset); readerOffset += sizeof(int); var seg = new ArraySegment(segment.Array, segment.Offset + readerOffset, size); readerOffset += size; return seg; } /// /// Reads the next item in the segment as a string. /// /// public static string ReadNextString(ArraySegment segment, ref int readerOffset) { if (segment.Array == null) { throw new ArgumentNullException(nameof(segment)); } int size = BitConverter.ToInt32(segment.Array, segment.Offset + readerOffset); readerOffset += sizeof(int); if (size < 0) { return null; } if (size == 0) { return string.Empty; } var chars = segment.AsSpan(readerOffset, size); readerOffset += size; return new string(MemoryMarshal.Cast(chars)); } } } ================================================ FILE: src/Orleans.Streaming/Common/SimpleCache/SimpleQueueAdapterCache.cs ================================================ using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// Adapter for simple queue caches. /// public class SimpleQueueAdapterCache : IQueueAdapterCache { /// /// Cache size property name for configuration /// public const string CacheSizePropertyName = "CacheSize"; private readonly int cacheSize; private readonly string providerName; private readonly ILoggerFactory loggerFactory; /// /// Adapter for simple queue caches. /// /// The options. /// The stream provider name. /// The logger factory. public SimpleQueueAdapterCache(SimpleQueueCacheOptions options, string providerName, ILoggerFactory loggerFactory) { this.cacheSize = options.CacheSize; this.loggerFactory = loggerFactory; this.providerName = providerName; } /// public IQueueCache CreateQueueCache(QueueId queueId) { return new SimpleQueueCache(cacheSize, this.loggerFactory.CreateLogger($"{typeof(SimpleQueueCache).FullName}.{providerName}.{queueId}")); } } } ================================================ FILE: src/Orleans.Streaming/Common/SimpleCache/SimpleQueueCache.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { internal class CacheBucket { // For backpressure detection we maintain a histogram of 10 buckets. // Every bucket records how many items are in the cache in that bucket // and how many cursors are poinmting to an item in that bucket. // We update the NumCurrentItems when we add and remove cache item (potentially opening or removing a bucket) // We update NumCurrentCursors every time we move a cursor // If the first (most outdated bucket) has at least one cursor pointing to it, we say we are under back pressure (in a full cache). internal int NumCurrentItems { get; private set; } internal int NumCurrentCursors { get; private set; } internal void UpdateNumItems(int val) { NumCurrentItems = NumCurrentItems + val; } internal void UpdateNumCursors(int val) { NumCurrentCursors = NumCurrentCursors + val; } } internal class SimpleQueueCacheItem { internal IBatchContainer Batch; internal bool DeliveryFailure; internal StreamSequenceToken SequenceToken; internal CacheBucket CacheBucket; } /// /// A queue cache that keeps items in memory. /// public partial class SimpleQueueCache : IQueueCache { private readonly LinkedList cachedMessages; private readonly int maxCacheSize; private readonly ILogger logger; private readonly List cacheCursorHistogram; // for backpressure detection private const int NUM_CACHE_HISTOGRAM_BUCKETS = 10; private readonly int CACHE_HISTOGRAM_MAX_BUCKET_SIZE; /// /// Gets the number of items in the cache. /// public int Size => cachedMessages.Count; /// public int GetMaxAddCount() { return CACHE_HISTOGRAM_MAX_BUCKET_SIZE; } /// /// Initializes a new instance of the class. /// /// Size of the cache. /// The logger. public SimpleQueueCache(int cacheSize, ILogger logger) { cachedMessages = new LinkedList(); maxCacheSize = cacheSize; this.logger = logger; cacheCursorHistogram = new List(); CACHE_HISTOGRAM_MAX_BUCKET_SIZE = Math.Max(cacheSize / NUM_CACHE_HISTOGRAM_BUCKETS, 1); // we have 10 buckets } /// public virtual bool IsUnderPressure() { return cacheCursorHistogram.Count >= NUM_CACHE_HISTOGRAM_BUCKETS; } /// public virtual bool TryPurgeFromCache(out IList purgedItems) { purgedItems = null; if (cachedMessages.Count == 0) return false; // empty cache if (cacheCursorHistogram.Count == 0) return false; // no cursors yet - zero consumers basically yet. if (cacheCursorHistogram[0].NumCurrentCursors > 0) return false; // consumers are still active in the oldest bucket - fast path var allItems = new List(); while (cacheCursorHistogram.Count > 0 && cacheCursorHistogram[0].NumCurrentCursors == 0) { List items = DrainBucket(cacheCursorHistogram[0]); allItems.AddRange(items); cacheCursorHistogram.RemoveAt(0); // remove the last bucket } purgedItems = allItems; LogDebugTryPurgeFromCache(allItems.Count); return true; } private List DrainBucket(CacheBucket bucket) { var itemsToRelease = new List(bucket.NumCurrentItems); // walk all items in the cache starting from last // and remove from the cache the oness that reside in the given bucket until we jump to a next bucket while (bucket.NumCurrentItems > 0) { SimpleQueueCacheItem item = cachedMessages.Last.Value; if (item.CacheBucket.Equals(bucket)) { if (!item.DeliveryFailure) { itemsToRelease.Add(item.Batch); } bucket.UpdateNumItems(-1); cachedMessages.RemoveLast(); } else { // this item already points into the next bucket, so stop. break; } } return itemsToRelease; } /// public virtual void AddToCache(IList msgs) { if (msgs == null) throw new ArgumentNullException(nameof(msgs)); LogDebugAddToCache(msgs.Count); foreach (var message in msgs) { Add(message, message.SequenceToken); } } /// public virtual IQueueCacheCursor GetCacheCursor(StreamId streamId, StreamSequenceToken token) { var cursor = new SimpleQueueCacheCursor(this, streamId, logger); InitializeCursor(cursor, token); return cursor; } internal void InitializeCursor(SimpleQueueCacheCursor cursor, StreamSequenceToken sequenceToken) { LogDebugInitializeCursor(cursor, sequenceToken); // Nothing in cache, unset token, and wait for more data. if (cachedMessages.Count == 0) { UnsetCursor(cursor, sequenceToken); return; } // if no token is provided, set token to item at end of cache sequenceToken = sequenceToken ?? cachedMessages.First?.Value?.SequenceToken; // If sequenceToken is too new to be in cache, unset token, and wait for more data. if (sequenceToken.Newer(cachedMessages.First.Value.SequenceToken)) { UnsetCursor(cursor, sequenceToken); return; } LinkedListNode lastMessage = cachedMessages.Last; // Check to see if offset is too old to be in cache if (sequenceToken.Older(lastMessage.Value.SequenceToken)) { throw new QueueCacheMissException(sequenceToken, cachedMessages.Last.Value.SequenceToken, cachedMessages.First.Value.SequenceToken); } // Now the requested sequenceToken is set and is also within the limits of the cache. // Find first message at or below offset // Events are ordered from newest to oldest, so iterate from start of list until we hit a node at a previous offset, or the end. LinkedListNode node = cachedMessages.First; while (node != null && node.Value.SequenceToken.Newer(sequenceToken)) { // did we get to the end? if (node.Next == null) // node is the last message break; // if sequenceId is between the two, take the higher if (node.Next.Value.SequenceToken.Older(sequenceToken)) break; node = node.Next; } // return cursor from start. SetCursor(cursor, node); } internal void RefreshCursor(SimpleQueueCacheCursor cursor, StreamSequenceToken sequenceToken) { LogDebugRefreshCursor(cursor, sequenceToken); // set if unset if (!cursor.IsSet) { InitializeCursor(cursor, cursor.SequenceToken ?? sequenceToken); } } /// /// Acquires the next message in the cache at the provided cursor /// /// /// /// internal bool TryGetNextMessage(SimpleQueueCacheCursor cursor, out IBatchContainer batch) { LogDebugTryGetNextMessage(cursor); batch = null; if (!cursor.IsSet) return false; // If we are at the end of the cache unset cursor and move offset one forward if (cursor.Element == cachedMessages.First) { UnsetCursor(cursor, null); } else // Advance to next: { AdvanceCursor(cursor, cursor.Element.Previous); } batch = cursor.Element?.Value.Batch; return (batch != null); } private void AdvanceCursor(SimpleQueueCacheCursor cursor, LinkedListNode item) { LogDebugUpdateCursor(cursor, item.Value.Batch); cursor.Element.Value.CacheBucket.UpdateNumCursors(-1); // remove from prev bucket cursor.Set(item); cursor.Element.Value.CacheBucket.UpdateNumCursors(1); // add to next bucket } internal void SetCursor(SimpleQueueCacheCursor cursor, LinkedListNode item) { LogDebugSetCursor(cursor, item.Value.Batch); cursor.Set(item); cursor.Element.Value.CacheBucket.UpdateNumCursors(1); // add to next bucket } internal void UnsetCursor(SimpleQueueCacheCursor cursor, StreamSequenceToken token) { LogDebugUnsetCursor(cursor); if (cursor.IsSet) { cursor.Element.Value.CacheBucket.UpdateNumCursors(-1); } cursor.UnSet(token); } private void Add(IBatchContainer batch, StreamSequenceToken sequenceToken) { if (batch == null) throw new ArgumentNullException(nameof(batch)); // this should never happen, but just in case if (Size >= maxCacheSize) throw new CacheFullException(); CacheBucket cacheBucket; if (cacheCursorHistogram.Count == 0) { cacheBucket = new CacheBucket(); cacheCursorHistogram.Add(cacheBucket); } else { cacheBucket = cacheCursorHistogram[cacheCursorHistogram.Count - 1]; // last one } if (cacheBucket.NumCurrentItems == CACHE_HISTOGRAM_MAX_BUCKET_SIZE) // last bucket is full, open a new one { cacheBucket = new CacheBucket(); cacheCursorHistogram.Add(cacheBucket); } // Add message to linked list var item = new SimpleQueueCacheItem { Batch = batch, SequenceToken = sequenceToken, CacheBucket = cacheBucket }; cachedMessages.AddFirst(new LinkedListNode(item)); cacheBucket.UpdateNumItems(1); } [LoggerMessage( Level = LogLevel.Debug, Message = "TryPurgeFromCache: purged {PurgedItemsCount} items from cache." )] private partial void LogDebugTryPurgeFromCache(int purgedItemsCount); [LoggerMessage( Level = LogLevel.Debug, Message = "AddToCache: added {ItemCount} items to cache." )] private partial void LogDebugAddToCache(int itemCount); [LoggerMessage( Level = LogLevel.Debug, Message = "InitializeCursor: {Cursor} to sequenceToken {SequenceToken}" )] private partial void LogDebugInitializeCursor(SimpleQueueCacheCursor cursor, StreamSequenceToken sequenceToken); [LoggerMessage( Level = LogLevel.Debug, Message = "RefreshCursor: {RefreshCursor} to sequenceToken {SequenceToken}" )] private partial void LogDebugRefreshCursor(SimpleQueueCacheCursor refreshCursor, StreamSequenceToken sequenceToken); [LoggerMessage( Level = LogLevel.Debug, Message = "TryGetNextMessage: {Cursor}" )] private partial void LogDebugTryGetNextMessage(SimpleQueueCacheCursor cursor); [LoggerMessage( Level = LogLevel.Debug, Message = "UpdateCursor: {Cursor} to item {Item}" )] private partial void LogDebugUpdateCursor(SimpleQueueCacheCursor cursor, IBatchContainer item); [LoggerMessage( Level = LogLevel.Debug, Message = "SetCursor: {Cursor} to item {Item}" )] private partial void LogDebugSetCursor(SimpleQueueCacheCursor cursor, IBatchContainer item); [LoggerMessage( Level = LogLevel.Debug, Message = "UnsetCursor: {Cursor}" )] private partial void LogDebugUnsetCursor(SimpleQueueCacheCursor cursor); } } ================================================ FILE: src/Orleans.Streaming/Common/SimpleCache/SimpleQueueCacheCursor.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Common { /// /// Cursor into a simple queue cache. /// public partial class SimpleQueueCacheCursor : IQueueCacheCursor { private readonly StreamId streamId; private readonly SimpleQueueCache cache; private readonly ILogger logger; private IBatchContainer current; // this is a pointer to the current element in the cache. It is what will be returned by GetCurrent(). // This is also a pointer to the current element in the cache. It differs from current, in // that current is just the batch, and is null before the first call to MoveNext after // construction. (Or after refreshing if we had previously run out of batches). Upon MoveNext // being called in that situation, current gets set to the batch included in Element. That is // needed to implement the Enumerator pattern properly, since in that pattern MoveNext gets called // before the first access of (Get)Current. internal LinkedListNode Element { get; private set; } internal StreamSequenceToken SequenceToken { get; private set; } internal bool IsSet => Element != null; internal void Set(LinkedListNode item) { if (item == null) throw new NullReferenceException(nameof(item)); Element = item; SequenceToken = item.Value.SequenceToken; } internal void UnSet(StreamSequenceToken token) { Element = null; SequenceToken = token; } /// /// Cursor into a simple queue cache /// /// The cache instance. /// The stream identifier. /// The logger. public SimpleQueueCacheCursor(SimpleQueueCache cache, StreamId streamId, ILogger logger) { if (cache == null) { throw new ArgumentNullException(nameof(cache)); } this.cache = cache; this.streamId = streamId; this.logger = logger; current = null; LogDebugNewCursor(streamId); } /// public virtual IBatchContainer GetCurrent(out Exception exception) { LogDebugGetCurrent(current); exception = null; return current; } /// public virtual bool MoveNext() { if (current == null && IsSet && IsInStream(Element.Value.Batch)) { current = Element.Value.Batch; return true; } IBatchContainer next; while (cache.TryGetNextMessage(this, out next)) { if(IsInStream(next)) break; } current = next; if (!IsInStream(next)) return false; return true; } /// public virtual void Refresh(StreamSequenceToken sequenceToken) { if (!IsSet) { cache.RefreshCursor(this, sequenceToken); } } /// public void RecordDeliveryFailure() { if (IsSet && current != null) { Element.Value.DeliveryFailure = true; } } private bool IsInStream(IBatchContainer batchContainer) { return batchContainer != null && batchContainer.StreamId.Equals(this.streamId); } /// public void Dispose() { Dispose(true); } /// /// Clean up cache data when done /// /// if the instance is being disposed; if it is being called from a finalizer. protected virtual void Dispose(bool disposing) { if (disposing) { cache.UnsetCursor(this, null); current = null; } } /// public override string ToString() { return $""; } [LoggerMessage( Level = LogLevel.Debug, Message = "SimpleQueueCacheCursor New Cursor for {StreamId}" )] private partial void LogDebugNewCursor(StreamId streamId); [LoggerMessage( Level = LogLevel.Debug, Message = "SimpleQueueCacheCursor.GetCurrent: {Current}" )] private partial void LogDebugGetCurrent(IBatchContainer current); } } ================================================ FILE: src/Orleans.Streaming/Common/SimpleCache/SimpleQueueCacheOptions.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Configuration { /// /// Configuration options for the simple queue cache. /// public class SimpleQueueCacheOptions { /// /// Gets or sets the size of the cache. /// /// The size of the cache. public int CacheSize { get; set; } = DEFAULT_CACHE_SIZE; /// /// The default value of . /// public const int DEFAULT_CACHE_SIZE = 4096; } /// /// Validates . /// public class SimpleQueueCacheOptionsValidator : IConfigurationValidator { private readonly SimpleQueueCacheOptions options; private readonly string name; /// /// Initializes a new instance of the class. /// /// The options. /// The name. private SimpleQueueCacheOptionsValidator(SimpleQueueCacheOptions options, string name) { this.options = options; this.name = name; } /// public void ValidateConfiguration() { if(options.CacheSize <= 0) throw new OrleansConfigurationException($"{nameof(SimpleQueueCacheOptions)} on stream provider {this.name} is invalid. {nameof(SimpleQueueCacheOptions.CacheSize)} must be larger than zero"); } /// /// Creates a new instance. /// /// The services. /// The provider name. /// A new instance. public static IConfigurationValidator Create(IServiceProvider services, string name) { SimpleQueueCacheOptions queueCacheOptions = services.GetOptionsByName(name); return new SimpleQueueCacheOptionsValidator(queueCacheOptions, name); } } } ================================================ FILE: src/Orleans.Streaming/Core/DefaultStreamIdMapper.cs ================================================ using System; using System.Buffers.Text; using Orleans.Metadata; using Orleans.Runtime; #nullable enable namespace Orleans.Streams { /// /// The default implementation. /// public sealed class DefaultStreamIdMapper : IStreamIdMapper { /// /// The name of this stream identity mapper. /// public const string Name = "default"; /// public IdSpan GetGrainKeyId(GrainBindings grainBindings, StreamId streamId) { string? keyType = null; bool includeNamespaceInGrainId = false; foreach (var grainBinding in grainBindings.Bindings) { if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type) || !string.Equals(type, WellKnownGrainTypeProperties.StreamBindingTypeValue, StringComparison.Ordinal)) { continue; } if (grainBinding.TryGetValue(WellKnownGrainTypeProperties.LegacyGrainKeyType, out keyType)) { if (grainBinding.TryGetValue(WellKnownGrainTypeProperties.StreamBindingIncludeNamespaceKey, out var value) && string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) { includeNamespaceInGrainId = true; } } } return keyType switch { nameof(Guid) => GetGuidKey(streamId, includeNamespaceInGrainId), nameof(Int64) => GetIntegerKey(streamId, includeNamespaceInGrainId), _ => streamId.GetKeyIdSpan(), // null or string }; } private static IdSpan GetGuidKey(StreamId streamId, bool includeNamespaceInGrainId) { var key = streamId.Key.Span; if (!Utf8Parser.TryParse(key, out Guid guidKey, out var len, 'N') || len < key.Length) throw new ArgumentException(nameof(streamId)); if (!includeNamespaceInGrainId) return streamId.GetKeyIdSpan(); var ns = streamId.Namespace.Span; return ns.IsEmpty ? streamId.GetKeyIdSpan() : GrainIdKeyExtensions.CreateGuidKey(guidKey, ns); } private static IdSpan GetIntegerKey(StreamId streamId, bool includeNamespaceInGrainId) { var key = streamId.Key.Span; if (!Utf8Parser.TryParse(key, out long intKey, out var len) || len < key.Length) throw new ArgumentException(nameof(streamId)); return includeNamespaceInGrainId ? GrainIdKeyExtensions.CreateIntegerKey(intKey, streamId.Namespace.Span) : GrainIdKeyExtensions.CreateIntegerKey(intKey); } } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncBatchObservable.cs ================================================ #nullable enable using System.Threading.Tasks; namespace Orleans.Streams { /// /// This interface generalizes the IAsyncObserver interface to allow production and consumption of batches of items. /// /// Note that this interface is implemented by item consumers and invoked (used) by item producers. /// This means that the consumer endpoint of a stream implements this interface. /// /// /// The type of object consumed by the observer. public interface IAsyncBatchObservable { /// /// Subscribe a consumer to this batch observable. /// /// The asynchronous batch observer to subscribe. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// Task> SubscribeAsync(IAsyncBatchObserver observer); /// /// Subscribe a consumer to this batch observable. /// /// The asynchronous batch observer to subscribe. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// Task> SubscribeAsync(IAsyncBatchObserver observer, StreamSequenceToken? token); } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncBatchObserver.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Represents a stream item within a sequence. /// /// The item type. public class SequentialItem { /// /// Initializes a new instance of the class. /// /// The item. /// The token. public SequentialItem(T item, StreamSequenceToken token) { this.Item = item; this.Token = token; } /// /// Gets the item. /// /// The item. public T Item { get; } /// /// Gets the token. /// /// The token. public StreamSequenceToken Token { get; } } /// /// This interface generalizes the IAsyncObserver interface to allow production and consumption of batches of items. /// /// Note that this interface is implemented by item consumers and invoked (used) by item producers. /// This means that the consumer endpoint of a stream implements this interface. /// /// /// The type of object consumed by the observer. public interface IAsyncBatchObserver { /// /// Passes the next batch of items to the consumer. /// /// The Task returned from this method should be completed when the items' processing has been /// sufficiently processed by the consumer to meet any behavioral guarantees. /// /// /// When the consumer is the (producer endpoint of) a stream, the Task is completed when the stream implementation /// has accepted responsibility for the items and is assured of meeting its delivery guarantees. /// For instance, a stream based on a durable queue would complete the Task when the items have been durably saved. /// A stream that provides best-effort at most once delivery would return a Task that is already complete. /// /// /// When the producer is the (consumer endpoint of) a stream, the Task should be completed by the consumer code /// when it has accepted responsibility for the items. /// In particular, if the stream provider guarantees at-least-once delivery, then the items should not be considered /// delivered until the Task returned by the consumer has been completed. /// /// /// The item to be passed. /// A Task that is completed when the item has been accepted. Task OnNextAsync(IList> items); /// /// Notifies the consumer that the stream was completed. /// /// The Task returned from this method should be completed when the consumer is done processing the stream closure. /// /// /// A Task that is completed when the stream-complete operation has been accepted. Task OnCompletedAsync() => Task.CompletedTask; /// /// Notifies the consumer that the stream had an error. /// /// The Task returned from this method should be completed when the consumer is done processing the stream closure. /// /// /// An Exception that describes the error that occurred on the stream. /// A Task that is completed when the close has been accepted. Task OnErrorAsync(Exception ex); } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncBatchProducer.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// This interface generalizes the IAsyncObserver interface to allow production of batches of items. /// /// Note that this interface is invoked (used) by item producers. /// /// /// The type of object consumed by the observer. public interface IAsyncBatchProducer : IAsyncObserver { /// /// Passes the next batch of items to the consumer. /// /// The Task returned from this method should be completed when all items in the batch have been /// sufficiently processed by the consumer to meet any behavioral guarantees. /// /// /// That is, the semantics of the returned Task is the same as for , /// extended for all items in the batch. /// /// /// The items to be passed. /// The stream sequence token of this item. /// A Task that is completed when the batch has been accepted. Task OnNextBatchAsync(IEnumerable batch, StreamSequenceToken token = null); } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncObservable.cs ================================================ #nullable enable using System.Threading.Tasks; namespace Orleans.Streams { /// /// This interface generalizes the standard .NET IObserveable interface to allow asynchronous consumption of items. /// Asynchronous here means that the consumer can process items asynchronously and signal item completion to the /// producer by completing the returned Task. /// /// Note that this interface is invoked (used) by item consumers and implemented by item producers. /// This means that the producer endpoint of a stream implements this interface. /// /// /// The type of object produced by the observable. public interface IAsyncObservable { /// /// Subscribe a consumer to this observable. /// /// The asynchronous observer to subscribe. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// Task> SubscribeAsync(IAsyncObserver observer); /// /// Subscribe a consumer to this observable. /// /// The asynchronous observer to subscribe. /// The stream sequence to be used as an offset to start the subscription from. /// Data object that will be passed in to the filter. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// Task> SubscribeAsync(IAsyncObserver observer, StreamSequenceToken? token, string? filterData = null); } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncObserver.cs ================================================ #nullable enable using System; using System.Threading.Tasks; namespace Orleans.Streams { /// /// This interface generalizes the standard .NET IObserver interface to allow asynchronous production of items. /// /// Note that this interface is implemented by item consumers and invoked (used) by item producers. /// This means that the consumer endpoint of a stream implements this interface. /// /// /// The type of object consumed by the observer. public interface IAsyncObserver { /// /// Passes the next item to the consumer. /// /// The Task returned from this method should be completed when the item's processing has been /// sufficiently processed by the consumer to meet any behavioral guarantees. /// /// /// When the consumer is the (producer endpoint of) a stream, the Task is completed when the stream implementation /// has accepted responsibility for the item and is assured of meeting its delivery guarantees. /// For instance, a stream based on a durable queue would complete the Task when the item has been durably saved. /// A stream that provides best-effort at most once delivery would return a Task that is already complete. /// /// /// When the producer is the (consumer endpoint of) a stream, the Task should be completed by the consumer code /// when it has accepted responsibility for the item. /// In particular, if the stream provider guarantees at-least-once delivery, then the item should not be considered /// delivered until the Task returned by the consumer has been completed. /// /// /// The item to be passed. /// The stream sequence token of this item. /// A Task that is completed when the item has been accepted. Task OnNextAsync(T item, StreamSequenceToken? token = null); /// /// Notifies the consumer that the stream was completed. /// /// The Task returned from this method should be completed when the consumer is done processing the stream closure. /// /// /// A Task that is completed when the stream-complete operation has been accepted. Task OnCompletedAsync() => Task.CompletedTask; /// /// Notifies the consumer that the stream had an error. /// /// The Task returned from this method should be completed when the consumer is done processing the stream closure. /// /// /// An Exception that describes the error that occurred on the stream. /// A Task that is completed when the close has been accepted. Task OnErrorAsync(Exception ex); } } ================================================ FILE: src/Orleans.Streaming/Core/IAsyncStream.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// This interface represents an object that serves as a distributed rendezvous between producers and consumers. /// It is similar to a Reactive Framework Subject and implements /// IObserver nor IObservable interfaces. /// /// The type of object that flows through the stream. public interface IAsyncStream : IAsyncStream, IEquatable>, IComparable>, // comparison IAsyncObservable, IAsyncBatchObservable, // observables IAsyncBatchProducer // observers { /// /// Retrieves a list of all active subscriptions created by the caller for this stream. /// /// Task>> GetAllSubscriptionHandles(); } /// /// This interface represents an object that serves as a distributed rendezvous between producers and consumers. /// It is similar to a Reactive Framework Subject and implements /// IObserver nor IObservable interfaces. /// public interface IAsyncStream { /// /// Gets a value indicating whether this is a rewindable stream - supports subscribing from previous point in time. /// /// True if this is a rewindable stream, false otherwise. bool IsRewindable { get; } /// /// Gets the name of the provider. /// /// The name of the provider. string ProviderName { get; } /// /// Gets the stream identifier. /// /// The stream identifier. StreamId StreamId { get; } } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamIdMapper.cs ================================================ using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.Streams { /// /// Common interface for components that map a to a /// public interface IStreamIdMapper { /// /// Gets the which maps to the provided /// /// The grain bindings. /// The stream identifier. /// The component. IdSpan GetGrainKeyId(GrainBindings grainBindings, StreamId streamId); } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamIdentity.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Streams { /// /// Uniquely identifies a stream. /// /// /// Use instead, where possible. /// public interface IStreamIdentity { /// /// Gets the unique identifier. /// /// The unique identifier. Guid Guid { get; } /// /// Gets the namespace. /// /// The namespace. string Namespace { get; } } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamSubscriptionHandleFactory.cs ================================================ using Orleans.Runtime; namespace Orleans.Streams.Core { /// /// Functionality for creating a stream subscription handle for a particular stream and subscription. /// public interface IStreamSubscriptionHandleFactory { /// /// Gets the stream identifier. /// /// The stream identifier. StreamId StreamId { get; } /// /// Gets the name of the provider. /// /// The name of the provider. string ProviderName { get; } /// /// Gets the subscription identifier. /// /// The subscription identifier. GuidId SubscriptionId { get; } /// /// Creates a stream subscription handle for the stream and subscription identified by this instance. /// /// The stream element type. /// The new stream subscription handle. StreamSubscriptionHandle Create(); } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamSubscriptionManager.cs ================================================ using Orleans.Runtime; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams.Core { /// /// Functionality for managing stream subscriptions. /// public interface IStreamSubscriptionManager { /// /// Subscribes the specified grain to a stream. /// /// Name of the stream provider. /// The stream identifier. /// The grain reference. /// The stream subscription. Task AddSubscription(string streamProviderName, StreamId streamId, GrainReference grainRef); /// /// Unsubscribes a grain from a stream. /// /// Name of the stream provider. /// The stream identifier. /// The subscription identifier. /// A representing the operation. Task RemoveSubscription(string streamProviderName, StreamId streamId, Guid subscriptionId); /// /// Gets the subscriptions for a stream. /// /// Name of the stream provider. /// The stream identifier. /// The subscriptions. Task> GetSubscriptions(string streamProviderName, StreamId streamId); } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamSubscriptionManagerAdmin.cs ================================================ namespace Orleans.Streams.Core { /// /// Functionality for retrieving a instance. /// public interface IStreamSubscriptionManagerAdmin { /// /// Gets the stream subscription manager. /// /// Type of the manager. /// The . IStreamSubscriptionManager GetStreamSubscriptionManager(string managerType); } /// /// Constants for . /// public static class StreamSubscriptionManagerType { /// /// The explicit subscription manager. /// public const string ExplicitSubscribeOnly = "ExplicitSubscribeOnly"; } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamSubscriptionManagerRetriever.cs ================================================ namespace Orleans.Streams.Core { /// /// Provides functionality for retrieving an instance. /// public interface IStreamSubscriptionManagerRetriever { /// /// Gets the stream subscription manager. /// /// The . IStreamSubscriptionManager GetStreamSubscriptionManager(); } } ================================================ FILE: src/Orleans.Streaming/Core/IStreamSubscriptionObserver.cs ================================================ using System.Threading.Tasks; namespace Orleans.Streams.Core { /// /// When implemented by a grain, notifies the grain of any new or resuming subscriptions. /// public interface IStreamSubscriptionObserver { /// /// Called when this grain receives a message for a stream which it has not yet explicitly subscribed to or resumed. /// /// The handle factory. /// A representing the operation. Task OnSubscribed(IStreamSubscriptionHandleFactory handleFactory); } } ================================================ FILE: src/Orleans.Streaming/Core/ImplicitConsumerGrainExtensions.cs ================================================ namespace Orleans.Streams { /// /// Extension methods for grains implicitly subscribed to streams. /// public static class ImplicitConsumerGrainExtensions { /// /// Constructs of the stream that the grain is implicitly subscribed to. /// /// The implicitly subscribed grain. /// The stream identity (key + namespace). public static StreamIdentity GetImplicitStreamIdentity(this IGrainWithGuidCompoundKey grain) { string keyExtension; var key = grain.GetPrimaryKey(out keyExtension); return new StreamIdentity(key, keyExtension); } } } ================================================ FILE: src/Orleans.Streaming/Core/StreamIdentity.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { /// /// Stream identity contains the public stream information use to uniquely identify a stream. /// Stream identities are only unique per stream provider. /// /// /// Use where possible, instead. /// [Serializable, GenerateSerializer, Immutable] public sealed class StreamIdentity : IStreamIdentity { /// /// Initializes a new instance of the class. /// /// The stream unique identifier. /// The stream namespace. public StreamIdentity(Guid streamGuid, string streamNamespace) { Guid = streamGuid; Namespace = streamNamespace; } /// /// Gets the stream identifier. /// [Id(0)] public Guid Guid { get; } /// /// Gets the stream namespace. /// [Id(1)] public string Namespace { get; } /// public override bool Equals(object obj) => obj is StreamIdentity identity && this.Guid.Equals(identity.Guid) && this.Namespace == identity.Namespace; /// public override int GetHashCode() => HashCode.Combine(Guid, Namespace); } } ================================================ FILE: src/Orleans.Streaming/Core/StreamSequenceToken.cs ================================================ using System; namespace Orleans.Streams { /// /// Handle representing stream sequence number/token. /// Consumer may subscribe to the stream while specifying the start of the subscription sequence token. /// That means that the stream infrastructure will deliver stream events starting from this sequence token. /// [Serializable] [GenerateSerializer] public abstract class StreamSequenceToken : IEquatable, IComparable { /// /// Gets the number of event batches in stream prior to this event batch /// public abstract long SequenceNumber { get; protected set; } /// /// Gets the number of events in batch prior to this event /// public abstract int EventIndex { get; protected set; } /// public abstract bool Equals(StreamSequenceToken other); /// public abstract int CompareTo(StreamSequenceToken other); } /// /// Utilities for comparing instances. /// public static class StreamSequenceTokenUtilities { /// /// Returns if the first token is newer than the second token. /// /// The first token /// The second token. /// if the first token is newer than the second token, otherwise. static public bool Newer(this StreamSequenceToken me, StreamSequenceToken other) { return me.CompareTo(other) > 0; } /// /// Returns if the first token is older than the second token. /// /// The first token /// The second token. /// if the first token is older than the second token, otherwise. static public bool Older(this StreamSequenceToken me, StreamSequenceToken other) { return me.CompareTo(other) < 0; } } } ================================================ FILE: src/Orleans.Streaming/Core/StreamSubscription.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Streams.Core { /// /// Represents a subscription to a stream. /// [Serializable, GenerateSerializer, Immutable] public sealed class StreamSubscription { /// /// Initializes a new instance of the class. /// /// The subscription identifier. /// Name of the stream provider. /// The stream identifier. /// The grain identifier. public StreamSubscription(Guid subscriptionId, string streamProviderName, StreamId streamId, GrainId grainId) { this.SubscriptionId = subscriptionId; this.StreamProviderName = streamProviderName; this.StreamId = streamId; this.GrainId = grainId; } /// /// Gets or sets the subscription identifier. /// /// The subscription identifier. [Id(0)] public Guid SubscriptionId { get; } /// /// Gets or sets the name of the stream provider. /// /// The name of the stream provider. [Id(1)] public string StreamProviderName { get; } /// /// Gets or sets the stream identifier. /// /// The stream identifier. [Id(2)] public StreamId StreamId { get; } /// /// Gets or sets the grain identifier. /// /// The grain identifier. [Id(3)] public GrainId GrainId { get; } } } ================================================ FILE: src/Orleans.Streaming/Core/StreamSubscriptionHandle.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// Handle representing this subscription. /// Consumer may serialize and store the handle in order to unsubscribe later, for example /// in another activation on this grain. /// [Serializable] [GenerateSerializer] public abstract class StreamSubscriptionHandle : IEquatable> { /// /// Gets the stream identifier. /// /// The stream identifier. public abstract StreamId StreamId { get; } /// /// Gets the name of the provider. /// /// The name of the provider. public abstract string ProviderName { get; } /// /// Gets the unique identifier for this StreamSubscriptionHandle /// public abstract Guid HandleId { get; } /// /// Unsubscribe a stream consumer from this observable. /// /// /// A representing the operation. /// public abstract Task UnsubscribeAsync(); /// /// Resumed consumption from a subscription to a stream. /// /// The observer object. /// The stream sequence to be used as an offset to start the subscription from. /// /// The new stream subscription handle. /// public abstract Task> ResumeAsync(IAsyncObserver observer, StreamSequenceToken token = null); /// /// Resume batch consumption from a subscription to a stream. /// /// The batcj bserver object. /// The stream sequence to be used as an offset to start the subscription from. /// /// The new stream subscription handle. /// public abstract Task> ResumeAsync(IAsyncBatchObserver observer, StreamSequenceToken token = null); /// public abstract bool Equals(StreamSubscriptionHandle other); } } ================================================ FILE: src/Orleans.Streaming/Core/StreamSubscriptionManager.cs ================================================ using Orleans.Runtime; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Streams.Core { internal class StreamSubscriptionManager: IStreamSubscriptionManager { private readonly string type; private readonly IStreamPubSub streamPubSub; public StreamSubscriptionManager(IStreamPubSub streamPubSub, string managerType) { this.streamPubSub = streamPubSub; this.type = managerType; } public async Task AddSubscription(string streamProviderName, StreamId streamId, GrainReference grainRef) { var consumer = grainRef.GrainId; var internalStreamId = new QualifiedStreamId(streamProviderName, streamId); var subscriptionId = streamPubSub.CreateSubscriptionId(internalStreamId, consumer); await streamPubSub.RegisterConsumer(subscriptionId, internalStreamId, consumer, null); var newSub = new StreamSubscription(subscriptionId.Guid, streamProviderName, streamId, grainRef.GrainId); return newSub; } public async Task RemoveSubscription(string streamProviderName, StreamId streamId, Guid subscriptionId) { var internalStreamId = new QualifiedStreamId(streamProviderName, streamId); await streamPubSub.UnregisterConsumer(GuidId.GetGuidId(subscriptionId), internalStreamId); } public Task> GetSubscriptions(string streamProviderName, StreamId streamId) { var internalStreamId = new QualifiedStreamId(streamProviderName, streamId); return streamPubSub.GetAllSubscriptions(internalStreamId).ContinueWith(subs => subs.Result.AsEnumerable()); } } } ================================================ FILE: src/Orleans.Streaming/Core/StreamSubscriptionManagerAdmin.cs ================================================ using System.Collections.Generic; namespace Orleans.Streams.Core { internal class StreamSubscriptionManagerAdmin : IStreamSubscriptionManagerAdmin { private readonly StreamSubscriptionManager _explicitStreamSubscriptionManager; public StreamSubscriptionManagerAdmin(IStreamProviderRuntime providerRuntime) { // using ExplicitGrainBasedAndImplicit pubsub here, so if StreamSubscriptionManager.Add(Remove)Subscription called on a implicit subscribed // consumer grain, its subscription will be handled by ImplicitPubsub, and will not be messed into GrainBasedPubsub _explicitStreamSubscriptionManager = new StreamSubscriptionManager(providerRuntime.PubSub(StreamPubSubType.ExplicitGrainBasedAndImplicit), StreamSubscriptionManagerType.ExplicitSubscribeOnly); } public IStreamSubscriptionManager GetStreamSubscriptionManager(string managerType) { return managerType switch { StreamSubscriptionManagerType.ExplicitSubscribeOnly => _explicitStreamSubscriptionManager, _ => throw new KeyNotFoundException($"Cannot find StreamSubscriptionManager with type {managerType}.") }; } } } ================================================ FILE: src/Orleans.Streaming/Extensions/AsyncBatchObservableExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Extension methods for . /// public static class AsyncBatchObservableExtensions { private static readonly Func DefaultOnError = _ => Task.CompletedTask; private static readonly Func DefaultOnCompleted = () => Task.CompletedTask; /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncBatchObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncBatchObserver.OnNextAsync. /// Delegate that is called for IAsyncBatchObserver.OnErrorAsync. /// Delegate that is called for IAsyncBatchObserver.OnCompletedAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncBatchObservable obs, Func>, Task> onNextAsync, Func onErrorAsync, Func onCompletedAsync) { var genericObserver = new GenericAsyncBatchObserver(onNextAsync, onErrorAsync, onCompletedAsync); return obs.SubscribeAsync(genericObserver); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncBatchObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncBatchObserver.OnNextAsync. /// Delegate that is called for IAsyncBatchObserver.OnErrorAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncBatchObservable obs, Func>, Task> onNextAsync, Func onErrorAsync) { return obs.SubscribeAsync(onNextAsync, onErrorAsync, DefaultOnCompleted); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncBatchObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncBatchObserver.OnNextAsync. /// Delegate that is called for IAsyncBatchObserver.OnCompletedAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncBatchObservable obs, Func>, Task> onNextAsync, Func onCompletedAsync) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, onCompletedAsync); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncBatchObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncBatchObserver.OnNextAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncBatchObservable obs, Func>, Task> onNextAsync) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, DefaultOnCompleted); } } } ================================================ FILE: src/Orleans.Streaming/Extensions/AsyncObservableExtensions.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Extension methods for . /// public static class AsyncObservableExtensions { private static readonly Func DefaultOnError = _ => Task.CompletedTask; private static readonly Func DefaultOnCompleted = () => Task.CompletedTask; /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onErrorAsync, Func onCompletedAsync) { var genericObserver = new GenericAsyncObserver(onNextAsync, onErrorAsync, onCompletedAsync); return obs.SubscribeAsync(genericObserver); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onErrorAsync) { return obs.SubscribeAsync(onNextAsync, onErrorAsync, DefaultOnCompleted); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onCompletedAsync) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, onCompletedAsync); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, DefaultOnCompleted); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onErrorAsync, Func onCompletedAsync, StreamSequenceToken token) { var genericObserver = new GenericAsyncObserver(onNextAsync, onErrorAsync, onCompletedAsync); return obs.SubscribeAsync(genericObserver, token); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onErrorAsync, StreamSequenceToken token) { return obs.SubscribeAsync(onNextAsync, onErrorAsync, DefaultOnCompleted, token); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, Func onCompletedAsync, StreamSequenceToken token) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, onCompletedAsync, token); } /// /// Subscribe a consumer to this observable using delegates. /// This method is a helper for the IAsyncObservable.SubscribeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The Observable object. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. public static Task> SubscribeAsync(this IAsyncObservable obs, Func onNextAsync, StreamSequenceToken token) { return obs.SubscribeAsync(onNextAsync, DefaultOnError, DefaultOnCompleted, token); } } } ================================================ FILE: src/Orleans.Streaming/Extensions/GenericAsyncObserver.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Class used by the IAsyncObservable extension methods to allow observation via delegate. /// /// The type of object produced by the observable. internal class GenericAsyncObserver : IAsyncObserver { private readonly Func onNextAsync; private readonly Func onErrorAsync; private readonly Func onCompletedAsync; public GenericAsyncObserver(Func onNextAsync, Func onErrorAsync, Func onCompletedAsync) { if (onNextAsync == null) throw new ArgumentNullException(nameof(onNextAsync)); if (onErrorAsync == null) throw new ArgumentNullException(nameof(onErrorAsync)); if (onCompletedAsync == null) throw new ArgumentNullException(nameof(onCompletedAsync)); this.onNextAsync = onNextAsync; this.onErrorAsync = onErrorAsync; this.onCompletedAsync = onCompletedAsync; } public Task OnNextAsync(T item, StreamSequenceToken token = null) { return onNextAsync(item, token); } public Task OnCompletedAsync() { return onCompletedAsync(); } public Task OnErrorAsync(Exception ex) { return onErrorAsync(ex); } } } ================================================ FILE: src/Orleans.Streaming/Extensions/GenericBatchAsyncObserver.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Class used by the IAsyncBatchObservable extension methods to allow observation via delegate. /// /// The type of object produced by the observable. internal class GenericAsyncBatchObserver : IAsyncBatchObserver { private readonly Func>, Task> onNextAsync; private readonly Func onErrorAsync; private readonly Func onCompletedAsync; public GenericAsyncBatchObserver(Func>, Task> onNextAsync, Func onErrorAsync, Func onCompletedAsync) { this.onNextAsync = onNextAsync ?? throw new ArgumentNullException(nameof(onNextAsync)); this.onErrorAsync = onErrorAsync ?? throw new ArgumentNullException(nameof(onErrorAsync)); this.onCompletedAsync = onCompletedAsync ?? throw new ArgumentNullException(nameof(onCompletedAsync)); } public Task OnNextAsync(IList> items) { return this.onNextAsync(items); } public Task OnCompletedAsync() { return this.onCompletedAsync(); } public Task OnErrorAsync(Exception ex) { return this.onErrorAsync(ex); } } } ================================================ FILE: src/Orleans.Streaming/Extensions/StreamSubscriptionHandleExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Extension methods for . /// public static class StreamSubscriptionHandleExtensions { private static readonly Func DefaultOnError = _ => Task.CompletedTask; private static readonly Func DefaultOnCompleted = () => Task.CompletedTask; /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func onNextAsync, Func onErrorAsync, Func onCompletedAsync, StreamSequenceToken token = null) { var genericObserver = new GenericAsyncObserver(onNextAsync, onErrorAsync, onCompletedAsync); return handle.ResumeAsync(genericObserver, token); } /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func onNextAsync, Func onErrorAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, onErrorAsync, DefaultOnCompleted, token); } /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func onNextAsync, Func onCompletedAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, DefaultOnError, onCompletedAsync, token); } /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func onNextAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, DefaultOnError, DefaultOnCompleted, token); } /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func>, Task> onNextAsync, Func onErrorAsync, Func onCompletedAsync, StreamSequenceToken token = null) { var genericObserver = new GenericAsyncBatchObserver(onNextAsync, onErrorAsync, onCompletedAsync); return handle.ResumeAsync(genericObserver, token); } /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnErrorAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func>, Task> onNextAsync, Func onErrorAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, onErrorAsync, DefaultOnCompleted, token); } /// /// Resumes consumption of a stream using delegates. /// This method is a helper for the StreamSubscriptionHandle.ResumeAsync allowing the subscribing class to inline the /// handler methods instead of requiring an instance of IAsyncBatchObserver. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// Delegate that is called for IAsyncObserver.OnCompletedAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func>, Task> onNextAsync, Func onCompletedAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, DefaultOnError, onCompletedAsync, token); } /// /// Thrown if the supplied stream filter function is not suitable. /// Usually this is because it is not a static method. /// /// The type of object produced by the observable. /// The subscription handle. /// Delegate that is called for IAsyncObserver.OnNextAsync. /// The stream sequence to be used as an offset to start the subscription from. /// A promise for a StreamSubscriptionHandle that represents the subscription. /// The consumer may unsubscribe by using this handle. /// The subscription remains active for as long as it is not explicitly unsubscribed. /// public static Task> ResumeAsync(this StreamSubscriptionHandle handle, Func>, Task> onNextAsync, StreamSequenceToken token = null) { return handle.ResumeAsync(onNextAsync, DefaultOnError, DefaultOnCompleted, token); } } } ================================================ FILE: src/Orleans.Streaming/Filtering/IStreamFilter.cs ================================================ using Orleans.Runtime; namespace Orleans.Streams.Filtering { /// /// Functionality for filtering streams. /// public interface IStreamFilter { /// /// Returns a value indicating if the specified stream item should be delivered. /// /// The stream identifier. /// The stream item. /// The filter data. /// if the stream item should be delivered, otherwise. bool ShouldDeliver(StreamId streamId, object item, string filterData); } internal sealed class NoOpStreamFilter : IStreamFilter { public bool ShouldDeliver(StreamId streamId, object item, string filterData) => true; } } ================================================ FILE: src/Orleans.Streaming/Generator/GeneratorAdapterFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Generator { /// /// Stream generator commands /// public enum StreamGeneratorCommand { /// /// Command to configure the generator /// Configure = PersistentStreamProviderCommand.AdapterFactoryCommandStartRange } /// /// Adapter factory for stream generator stream provider. /// This factory acts as the adapter and the adapter factory. It creates receivers that use configurable generator /// to generate event streams, rather than reading them from storage. /// public partial class GeneratorAdapterFactory : IQueueAdapterFactory, IQueueAdapter, IQueueAdapterCache, IControllable { /// /// Configuration property name for generator configuration type /// private readonly HashRingStreamQueueMapperOptions queueMapperOptions; private readonly StreamStatisticOptions statisticOptions; private readonly IServiceProvider serviceProvider; private readonly Serialization.Serializer serializer; private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; private IStreamGeneratorConfig generatorConfig; private IStreamQueueMapper streamQueueMapper; private IStreamFailureHandler streamFailureHandler; private ConcurrentDictionary receivers; private IObjectPool bufferPool; private BlockPoolMonitorDimensions blockPoolMonitorDimensions; /// public bool IsRewindable => true; /// public StreamProviderDirection Direction => StreamProviderDirection.ReadOnly; /// public string Name { get; } /// /// Create a cache monitor to report cache related metrics /// Return a ICacheMonitor /// protected Func CacheMonitorFactory; /// /// Create a block pool monitor to monitor block pool related metrics /// Return a IBlockPoolMonitor /// protected Func BlockPoolMonitorFactory; /// /// Create a monitor to monitor QueueAdapterReceiver related metrics /// Return a IQueueAdapterReceiverMonitor /// protected Func ReceiverMonitorFactory; public GeneratorAdapterFactory( string providerName, HashRingStreamQueueMapperOptions queueMapperOptions, StreamStatisticOptions statisticOptions, IServiceProvider serviceProvider, Serialization.Serializer serializer, ILoggerFactory loggerFactory) { this.Name = providerName; this.queueMapperOptions = queueMapperOptions ?? throw new ArgumentNullException(nameof(queueMapperOptions)); this.statisticOptions = statisticOptions ?? throw new ArgumentNullException(nameof(statisticOptions)); this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); this.serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.logger = loggerFactory.CreateLogger(); } /// /// Initializes the factory. /// public void Init() { this.receivers = new ConcurrentDictionary(); if (CacheMonitorFactory == null) this.CacheMonitorFactory = (dimensions) => new DefaultCacheMonitor(dimensions); if (this.BlockPoolMonitorFactory == null) this.BlockPoolMonitorFactory = (dimensions) => new DefaultBlockPoolMonitor(dimensions); if (this.ReceiverMonitorFactory == null) this.ReceiverMonitorFactory = (dimensions) => new DefaultQueueAdapterReceiverMonitor(dimensions); generatorConfig = this.serviceProvider.GetKeyedService(this.Name); if(generatorConfig == null) { LogInfoNoGeneratorConfigurationFound(this.Name); } } private void CreateBufferPoolIfNotCreatedYet() { if (this.bufferPool == null) { // 1 meg block size pool this.blockPoolMonitorDimensions = new BlockPoolMonitorDimensions($"BlockPool-{Guid.NewGuid()}"); var oneMb = 1 << 20; var objectPoolMonitor = new ObjectPoolMonitorBridge(this.BlockPoolMonitorFactory(blockPoolMonitorDimensions), oneMb); this.bufferPool = new ObjectPool(() => new FixedSizeBuffer(oneMb), objectPoolMonitor, this.statisticOptions.StatisticMonitorWriteInterval); } } /// public Task CreateAdapter() { return Task.FromResult(this); } /// public IQueueAdapterCache GetQueueAdapterCache() { return this; } /// public IStreamQueueMapper GetStreamQueueMapper() { return streamQueueMapper ?? (streamQueueMapper = new HashRingBasedStreamQueueMapper(this.queueMapperOptions, this.Name)); } /// public Task GetDeliveryFailureHandler(QueueId queueId) { return Task.FromResult(streamFailureHandler ?? (streamFailureHandler = new NoOpStreamDeliveryFailureHandler())); } /// public Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { return Task.CompletedTask; } /// public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { if (!receivers.TryGetValue(queueId, out var receiver)) { var dimensions = new ReceiverMonitorDimensions(queueId.ToString()); var receiverMonitor = this.ReceiverMonitorFactory(dimensions); receiver = receivers.GetOrAdd(queueId, new Receiver(receiverMonitor)); } SetGeneratorOnReceiver(receiver); return receiver; } /// public Task ExecuteCommand(int command, object arg) { if (arg == null) { throw new ArgumentNullException(nameof(arg)); } generatorConfig = arg as IStreamGeneratorConfig; if (generatorConfig == null) { throw new ArgumentOutOfRangeException(nameof(arg), "Arg must by of type IStreamGeneratorConfig"); } // update generator on receivers foreach (var receiver in receivers) { SetGeneratorOnReceiver(receiver.Value); } return Task.FromResult(true); } private class Receiver : IQueueAdapterReceiver { private const int MaxDelayMs = 20; private readonly IQueueAdapterReceiverMonitor receiverMonitor; public IStreamGenerator QueueGenerator { private get; set; } public Receiver(IQueueAdapterReceiverMonitor receiverMonitor) { this.receiverMonitor = receiverMonitor; } public Task Initialize(TimeSpan timeout) { this.receiverMonitor?.TrackInitialization(true, TimeSpan.MinValue, null); return Task.CompletedTask; } public async Task> GetQueueMessagesAsync(int maxCount) { var watch = Stopwatch.StartNew(); await Task.Delay(Random.Shared.Next(1,MaxDelayMs)); List batches; if (QueueGenerator == null || !QueueGenerator.TryReadEvents(DateTime.UtcNow, maxCount, out batches)) { return new List(); } watch.Stop(); this.receiverMonitor?.TrackRead(true, watch.Elapsed, null); if (batches.Count > 0) { var oldestMessage = batches[0] as GeneratedBatchContainer; var newestMessage = batches[batches.Count - 1] as GeneratedBatchContainer; this.receiverMonitor?.TrackMessagesReceived(batches.Count, oldestMessage?.EnqueueTimeUtc, newestMessage?.EnqueueTimeUtc); } return batches; } public Task MessagesDeliveredAsync(IList messages) { return Task.CompletedTask; } public Task Shutdown(TimeSpan timeout) { this.receiverMonitor?.TrackShutdown(true, TimeSpan.MinValue, null); return Task.CompletedTask; } } private void SetGeneratorOnReceiver(Receiver receiver) { // if we don't have generator configuration, don't set generator if (generatorConfig == null) { return; } var generator = (IStreamGenerator)(serviceProvider?.GetService(generatorConfig.StreamGeneratorType) ?? Activator.CreateInstance(generatorConfig.StreamGeneratorType)); if (generator == null) { throw new OrleansException($"StreamGenerator type not supported: {generatorConfig.StreamGeneratorType}"); } generator.Configure(serviceProvider, generatorConfig); receiver.QueueGenerator = generator; } /// public IQueueCache CreateQueueCache(QueueId queueId) { //move block pool creation from init method to here, to avoid unnecessary block pool creation when stream provider is initialized in client side. CreateBufferPoolIfNotCreatedYet(); var dimensions = new CacheMonitorDimensions(queueId.ToString(), this.blockPoolMonitorDimensions.BlockPoolId); var cacheMonitor = this.CacheMonitorFactory(dimensions); return new GeneratorPooledCache( bufferPool, this.loggerFactory.CreateLogger($"{typeof(GeneratorPooledCache).FullName}.{this.Name}.{queueId}"), serializer, cacheMonitor, this.statisticOptions.StatisticMonitorWriteInterval); } /// /// Creates a new instance. /// /// The services. /// The provider name. /// The newly created instance. public static GeneratorAdapterFactory Create(IServiceProvider services, string name) { var queueMapperOptions = services.GetOptionsByName(name); var statisticOptions = services.GetOptionsByName(name); var factory = ActivatorUtilities.CreateInstance(services, name, queueMapperOptions, statisticOptions); factory.Init(); return factory; } // "No generator configuration found for stream provider {StreamProvider}. Inactive until provided with configuration by command." [LoggerMessage( Level = LogLevel.Information, Message = "No generator configuration found for stream provider {StreamProvider}. Inactive until provided with configuration by command." )] private partial void LogInfoNoGeneratorConfigurationFound(string streamProvider); } } ================================================ FILE: src/Orleans.Streaming/Generator/GeneratorPooledCache.cs ================================================ using System; using System.Collections.Generic; using Orleans.Providers.Streams.Common; using Orleans.Streams; using System.Linq; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.Providers.Streams.Generator { /// /// Pooled cache for generator stream provider. /// public class GeneratorPooledCache : IQueueCache, ICacheDataAdapter { private readonly IObjectPool bufferPool; private readonly Serialization.Serializer serializer; private readonly IEvictionStrategy evictionStrategy; private readonly PooledQueueCache cache; private FixedSizeBuffer currentBuffer; /// /// Pooled cache for generator stream provider. /// /// The buffer pool. /// The logger. /// The serializer. /// The cache monitor. /// The monitor write interval. Only triggered for active caches public GeneratorPooledCache(IObjectPool bufferPool, ILogger logger, Serialization.Serializer serializer, ICacheMonitor cacheMonitor, TimeSpan? monitorWriteInterval) { this.bufferPool = bufferPool; this.serializer = serializer; cache = new PooledQueueCache(this, logger, cacheMonitor, monitorWriteInterval); TimePurgePredicate purgePredicate = new TimePurgePredicate(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10)); this.evictionStrategy = new ChronologicalEvictionStrategy(logger, purgePredicate, cacheMonitor, monitorWriteInterval) {PurgeObservable = cache}; } /// public IBatchContainer GetBatchContainer(ref CachedMessage cachedMessage) { //Deserialize payload int readOffset = 0; ArraySegment payload = SegmentBuilder.ReadNextBytes(cachedMessage.Segment, ref readOffset); object payloadObject = this.serializer.Deserialize(payload); return new GeneratedBatchContainer(cachedMessage.StreamId, payloadObject, new EventSequenceTokenV2(cachedMessage.SequenceNumber)); } /// public StreamSequenceToken GetSequenceToken(ref CachedMessage cachedMessage) { return new EventSequenceTokenV2(cachedMessage.SequenceNumber); } private CachedMessage QueueMessageToCachedMessage(GeneratedBatchContainer queueMessage, DateTime dequeueTimeUtc) { StreamPosition streamPosition = GetStreamPosition(queueMessage); return new CachedMessage() { StreamId = streamPosition.StreamId, SequenceNumber = queueMessage.RealToken.SequenceNumber, EnqueueTimeUtc = queueMessage.EnqueueTimeUtc, DequeueTimeUtc = dequeueTimeUtc, Segment = SerializeMessageIntoPooledSegment(queueMessage) }; } // Placed object message payload into a segment from a buffer pool. When this get's too big, older blocks will be purged private ArraySegment SerializeMessageIntoPooledSegment(GeneratedBatchContainer queueMessage) { byte[] serializedPayload = this.serializer.SerializeToArray(queueMessage.Payload); // get size of namespace, offset, partitionkey, properties, and payload int size = SegmentBuilder.CalculateAppendSize(serializedPayload); // get segment ArraySegment segment; if (currentBuffer == null || !currentBuffer.TryGetSegment(size, out segment)) { // no block or block full, get new block and try again currentBuffer = bufferPool.Allocate(); //call EvictionStrategy's OnBlockAllocated method this.evictionStrategy.OnBlockAllocated(currentBuffer); // if this fails with clean block, then requested size is too big if (!currentBuffer.TryGetSegment(size, out segment)) { string errmsg = $"Message size is to big. MessageSize: {size}"; throw new ArgumentOutOfRangeException(nameof(queueMessage), errmsg); } } // encode namespace, offset, partitionkey, properties and payload into segment int writeOffset = 0; SegmentBuilder.Append(segment, ref writeOffset, serializedPayload); return segment; } private static StreamPosition GetStreamPosition(GeneratedBatchContainer queueMessage) { return new StreamPosition(queueMessage.StreamId, queueMessage.RealToken); } private class Cursor : IQueueCacheCursor { private readonly PooledQueueCache cache; private readonly object cursor; private IBatchContainer current; public Cursor(PooledQueueCache cache, StreamId streamId, StreamSequenceToken token) { this.cache = cache; cursor = cache.GetCursor(streamId, token); } public void Dispose() { } public IBatchContainer GetCurrent(out Exception exception) { exception = null; return current; } public bool MoveNext() { IBatchContainer next; if (!cache.TryGetNextMessage(cursor, out next)) { return false; } current = next; return true; } public void Refresh(StreamSequenceToken token) { } public void RecordDeliveryFailure() { } } /// public int GetMaxAddCount() { return 100; } /// public void AddToCache(IList messages) { DateTime utcNow = DateTime.UtcNow; List generatedMessages = messages .Cast() .Select(batch => QueueMessageToCachedMessage(batch, utcNow)) .ToList(); cache.Add(generatedMessages, utcNow); } /// public bool TryPurgeFromCache(out IList purgedItems) { purgedItems = null; this.evictionStrategy.PerformPurge(DateTime.UtcNow); return false; } /// public IQueueCacheCursor GetCacheCursor(StreamId streamId, StreamSequenceToken token) { return new Cursor(cache, streamId, token); } /// public bool IsUnderPressure() { return false; } } } ================================================ FILE: src/Orleans.Streaming/Generator/Generators/GeneratedBatchContainer.cs ================================================ using System; using System.Collections.Generic; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Generator { /// /// implementation for generated event payloads. /// [GenerateSerializer] public sealed class GeneratedBatchContainer : IBatchContainer { /// [Id(0)] public StreamId StreamId { get; } /// public StreamSequenceToken SequenceToken => RealToken; /// /// Gets the real token. /// /// The real token. [Id(1)] public EventSequenceTokenV2 RealToken { get; } /// /// Gets the enqueue time (UTC). /// /// The enqueue time (UTC). [Id(2)] public DateTime EnqueueTimeUtc { get; } /// /// Gets the payload. /// /// The payload. [Id(3)] public object Payload { get; } /// /// Initializes a new instance of the class. /// /// The stream identifier. /// The payload. /// The token. public GeneratedBatchContainer(StreamId streamId, object payload, EventSequenceTokenV2 token) { StreamId = streamId; EnqueueTimeUtc = DateTime.UtcNow; this.Payload = payload; this.RealToken = token; } /// public IEnumerable> GetEvents() { return new[] { Tuple.Create((T)Payload, SequenceToken) }; } /// public bool ImportRequestContext() { return false; } } } ================================================ FILE: src/Orleans.Streaming/Generator/Generators/GeneratedEvent.cs ================================================ using System; namespace Orleans.Providers.Streams.Generator { /// /// Event use in generated streams /// [Serializable] [GenerateSerializer] public sealed class GeneratedEvent { /// /// Generated event type /// public enum GeneratedEventType { /// /// Filler event /// Fill, /// /// Event that should trigger reporting /// Report } /// /// Gets or sets the event type. /// [Id(0)] public GeneratedEventType EventType { get; set; } /// /// Gets or sets the payload. /// [Id(1)] public int[] Payload { get; set; } } } ================================================ FILE: src/Orleans.Streaming/Generator/Generators/SimpleGenerator.cs ================================================ using System; using System.Collections.Generic; using Orleans.Hosting; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers.Streams.Generator { /// /// Simple Generator /// Generates a single stream of a configurable number of events. One event per poll. /// internal class SimpleGenerator : IStreamGenerator { private SimpleGeneratorOptions options; private StreamId streamId; private int sequenceId; /// public void Configure(IServiceProvider serviceProvider, IStreamGeneratorConfig generatorConfig) { var cfg = generatorConfig as SimpleGeneratorOptions; if (cfg == null) { throw new ArgumentOutOfRangeException(nameof(generatorConfig)); } options = cfg; sequenceId = 0; streamId = StreamId.Create(options.StreamNamespace, Guid.NewGuid()); } /// /// Until we've generated the configured number of events, return a single generated event /// public bool TryReadEvents(DateTime utcNow, int maxCount, out List events) { events = new List(); if (sequenceId >= this.options.EventsInStream) { return false; } for(int i=0; i< maxCount; i++) { if (!TryGenerateBatch(out GeneratedBatchContainer batch)) break; events.Add(batch); } return true; } private bool TryGenerateBatch(out GeneratedBatchContainer batch) { batch = null; if (sequenceId >= this.options.EventsInStream) { return false; } sequenceId++; var evt = new GeneratedEvent { // If this is the last event generated, mark it as such, so test grains know to report results. EventType = (sequenceId != this.options.EventsInStream) ? GeneratedEvent.GeneratedEventType.Fill : GeneratedEvent.GeneratedEventType.Report }; batch = new GeneratedBatchContainer(streamId, evt, new EventSequenceTokenV2(sequenceId)); return true; } } } ================================================ FILE: src/Orleans.Streaming/Generator/Generators/SimpleGeneratorConfig.cs ================================================ using System; using Orleans.Providers.Streams.Generator; namespace Orleans.Hosting { /// /// Simple generator configuration class. /// This class is used to configure a generator stream provider to generate streams using the SimpleGenerator /// [Serializable] [GenerateSerializer] public sealed class SimpleGeneratorOptions : IStreamGeneratorConfig { /// /// Gets or sets the stream namespace. /// /// The stream namespace. [Id(0)] public string StreamNamespace { get; set; } /// /// Gets the stream generator type /// /// The type of the stream generator. public Type StreamGeneratorType => typeof (SimpleGenerator); /// /// Gets or sets the number of events to generate. /// /// The number of events to generate. [Id(1)] public int EventsInStream { get; set; } = DEFAULT_EVENTS_IN_STREAM; /// /// The default number of events to generate. /// public const int DEFAULT_EVENTS_IN_STREAM = 100; } } ================================================ FILE: src/Orleans.Streaming/Generator/IStreamGenerator.cs ================================================ using System; using System.Collections.Generic; using Orleans.Streams; namespace Orleans.Providers.Streams.Generator { /// /// Interface of generators used by the GeneratorStreamProvider. Any method of generating events /// must conform to this interface to be used by the GeneratorStreamProvider. /// public interface IStreamGenerator { /// /// Tries to get an event, if the generator is configured to generate any at this time /// /// The current UTC time. /// The maximum number of events to read. /// The events. /// if events were read, otherwise. bool TryReadEvents(DateTime utcNow, int maxCount, out List events); /// /// Configures the stream generator. /// /// The service provider. /// The generator configuration. void Configure(IServiceProvider serviceProvider, IStreamGeneratorConfig generatorConfig); } /// /// Interface of configuration for generators used by the GeneratorStreamProvider. This interface covers /// the minimal set of information the stream provider needs to configure a generator to generate data. Generators should /// add any additional configuration information needed to it's implementation of this interface. /// public interface IStreamGeneratorConfig { /// /// Gets the stream generator type /// Type StreamGeneratorType { get; } } } ================================================ FILE: src/Orleans.Streaming/GrainStreamingExtensions.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Streams; namespace Orleans { /// /// Extension methods for accessing stream providers from a or implementation. /// public static class GrainStreamingExtensions { /// /// Gets the stream provider with the specified . /// /// The grain. /// The provider name. /// The stream provider. public static IStreamProvider GetStreamProvider(this Grain grain, string name) => GetStreamProvider((IGrainBase)grain, name); /// /// Gets the stream provider with the specified . /// /// The grain. /// The provider name. /// The stream provider. public static IStreamProvider GetStreamProvider(this IGrainBase grain, string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } try { return grain.GrainContext.ActivationServices.GetRequiredKeyedService(name); } catch (InvalidOperationException ex) { // We used to throw KeyNotFoundException before, keep it like this for backward compatibility throw new KeyNotFoundException($"Stream provider '{name}' not found", ex); } } } /// /// Extension methods for accessing stream providers from a client. /// public static class ClientStreamingExtensions { /// /// Gets the stream provider with the specified . /// /// The client. /// The provider name. /// The stream provider. public static IStreamProvider GetStreamProvider(this IClusterClient client, string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } try { return client.ServiceProvider.GetRequiredKeyedService(name); } catch (InvalidOperationException ex) { // We used to throw KeyNotFoundException before, keep it like this for backward compatibility throw new KeyNotFoundException($"Stream provider '{name}' not found", ex); } } } } ================================================ FILE: src/Orleans.Streaming/Hosting/ClientBuilderStreamingExtensions.cs ================================================ using System; using Orleans.Providers; using Orleans.Streams; namespace Orleans.Hosting { public static class ClientBuilderStreamingExtensions { /// /// Adds support for streaming to this client. /// /// The builder. /// The client builder. public static IClientBuilder AddStreaming(this IClientBuilder builder) => builder.ConfigureServices(services => services.AddClientStreaming()); /// /// Adds a new in-memory stream provider to the client, using the default message serializer /// (). /// /// The builder. /// The stream provider name. /// The configuration delegate. /// The client builder. public static IClientBuilder AddMemoryStreams( this IClientBuilder builder, string name, Action configure = null) { return AddMemoryStreams(builder, name, configure); } /// /// Adds a new in-memory stream provider to the client. /// /// The type of the t serializer. /// The builder. /// The stream provider name. /// The configuration delegate. /// The client builder. public static IClientBuilder AddMemoryStreams( this IClientBuilder builder, string name, Action configure = null) where TSerializer : class, IMemoryMessageBodySerializer { //the constructor wire up DI with all default components of the streams , so need to be called regardless of configureStream null or not var memoryStreamConfigurator = new ClusterClientMemoryStreamConfigurator(name, builder); configure?.Invoke(memoryStreamConfigurator); return builder; } /// /// Adds a new persistent streams provider to the client. /// /// The builder. /// The stream provider name. /// The adapter factory. /// The configuration delegate. /// The client builder. public static IClientBuilder AddPersistentStreams( this IClientBuilder builder, string name, Func adapterFactory, Action configureStream) { //the constructor wire up DI with all default components of the streams , so need to be called regardless of configureStream null or not var streamConfigurator = new ClusterClientPersistentStreamConfigurator(name, builder, adapterFactory); configureStream?.Invoke(streamConfigurator); return builder; } } } ================================================ FILE: src/Orleans.Streaming/Hosting/ClusterClientPersistentStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Streams; namespace Orleans.Hosting { /// /// Configuration builder for persistent streams. /// public interface IPersistentStreamConfigurator : INamedServiceConfigurator { } /// /// Extension methods for . /// public static class PersistentStreamConfiguratorExtensions { /// /// Configures the stream pub/sub type. /// /// The configuration builder. /// The stream pub/sub type to use. public static void ConfigureStreamPubSub(this IPersistentStreamConfigurator configurator, StreamPubSubType pubsubType = StreamPubSubOptions.DEFAULT_STREAM_PUBSUB_TYPE) { configurator.Configure(ob => ob.Configure(options => options.PubSubType = pubsubType)); } } /// /// Client-specific configuration builder for persistent stream. /// public interface IClusterClientPersistentStreamConfigurator : IPersistentStreamConfigurator { } /// /// Extension methods for . /// public static class ClusterClientPersistentStreamConfiguratorExtensions { /// /// Configures the . /// /// The configuration builder. /// The configuration delegate. public static void ConfigureLifecycle(this IClusterClientPersistentStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } } /// /// Client-side configuration provider for persistent streams. /// public class ClusterClientPersistentStreamConfigurator : NamedServiceConfigurator, IClusterClientPersistentStreamConfigurator { /// /// Initializes a new instance of the class. /// /// The stream provider name. /// The client builder. /// The adapter factory. public ClusterClientPersistentStreamConfigurator(string name, IClientBuilder clientBuilder, Func adapterFactory) : base(name, configureDelegate => clientBuilder.ConfigureServices(configureDelegate)) { clientBuilder.AddStreaming(); this.ConfigureComponent(PersistentStreamProvider.Create); this.ConfigureDelegate(services => services.AddSingleton(sp => PersistentStreamProvider.ParticipateIn(sp, this.Name))); this.ConfigureComponent(adapterFactory); } } } ================================================ FILE: src/Orleans.Streaming/Hosting/SiloBuilderMemoryStreamExtensions.cs ================================================ using System; using Orleans.Providers; namespace Orleans.Hosting { /// /// extension methods for configuring in-memory streams. /// public static class SiloBuilderMemoryStreamExtensions { /// /// Configure silo to use memory streams, using the default message serializer /// (). /// /// using the default built-in serializer /// The builder. /// The stream provider name. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryStreams(this ISiloBuilder builder, string name, Action configure = null) { return AddMemoryStreams(builder, name, configure); } /// /// Configure silo to use memory streams. /// /// The message serializer type, which must implement . /// The builder. /// The stream provider name. /// The configuration delegate. /// The silo builder. public static ISiloBuilder AddMemoryStreams(this ISiloBuilder builder, string name, Action configure = null) where TSerializer : class, IMemoryMessageBodySerializer { //the constructor wire up DI with all default components of the streams , so need to be called regardless of configureStream null or not var memoryStreamConfiguretor = new SiloMemoryStreamConfigurator(name, configureDelegate => builder.ConfigureServices(configureDelegate) ); configure?.Invoke(memoryStreamConfiguretor); return builder; } } } ================================================ FILE: src/Orleans.Streaming/Hosting/SiloBuilderStreamingExtensions.cs ================================================ using System; using Orleans.Streams; using Orleans.Streams.Filtering; namespace Orleans.Hosting { /// /// Extension methods for configuring streaming on silos. /// public static class SiloBuilderStreamingExtensions { /// /// Add support for streaming to this application. /// /// The builder. /// The silo builder. public static ISiloBuilder AddStreaming(this ISiloBuilder builder) => builder.ConfigureServices(services => services.AddSiloStreaming()); /// /// Configures the silo to use persistent streams. /// /// The builder. /// The provider name. /// The provider adapter factory. /// The stream provider configuration delegate. /// The silo builder. public static ISiloBuilder AddPersistentStreams( this ISiloBuilder builder, string name, Func adapterFactory, Action configureStream) { //the constructor wire up DI with all default components of the streams , so need to be called regardless of configureStream null or not var streamConfigurator = new SiloPersistentStreamConfigurator(name, configureDelegate => builder.ConfigureServices(configureDelegate), adapterFactory); configureStream?.Invoke(streamConfigurator); return builder; } /// /// Adds a stream filter. /// /// The stream filter type. /// The builder. /// The stream filter name. /// The silo builder. public static ISiloBuilder AddStreamFilter(this ISiloBuilder builder, string name) where T : class, IStreamFilter { return builder.ConfigureServices(svc => svc.AddStreamFilter(name)); } /// /// Adds a stream filter. /// /// The stream filter type. /// The builder. /// The stream filter name. /// The client builder. public static IClientBuilder AddStreamFilter(this IClientBuilder builder, string name) where T : class, IStreamFilter { return builder.ConfigureServices(svc => svc.AddStreamFilter(name)); } } } ================================================ FILE: src/Orleans.Streaming/Hosting/StreamingServiceCollectionExtensions.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration.Internal; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Providers; using Orleans.Serialization; using Orleans.Streaming.JsonConverters; using Orleans.Streams; using Orleans.Streams.Core; using Orleans.Streams.Filtering; namespace Orleans.Hosting { /// /// Extension methods for configuring streaming on silos. /// public static class StreamingServiceCollectionExtensions { /// /// Add support for streaming to this silo. /// /// The services. public static void AddSiloStreaming(this IServiceCollection services) { if (services.Any(service => service.ServiceType.Equals(typeof(SiloStreamProviderRuntime)))) { return; } services.AddSingleton(); services.AddSingleton(); services.AddFromExisting(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddKeyedSingleton(DefaultStreamIdMapper.Name); services.AddKeyedTransient(typeof(IStreamConsumerExtension), (sp, _) => { var runtime = sp.GetRequiredService(); var grainContextAccessor = sp.GetRequiredService(); return new StreamConsumerExtension(runtime, grainContextAccessor.GrainContext?.GrainInstance as IStreamSubscriptionObserver); }); services.AddSingleton(sp => new StreamSubscriptionManagerAdmin(sp.GetRequiredService())); services.AddTransient(); services.AddSingleton, StreamingConverterConfigurator>(); // One stream directory per activation services.AddScoped(); } /// /// Add support for streaming to this client. /// /// The services. public static void AddClientStreaming(this IServiceCollection services) { if (services.Any(service => service.ServiceType.Equals(typeof(ClientStreamingProviderRuntime)))) { return; } services.AddSingleton(); services.AddFromExisting(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddKeyedSingleton(DefaultStreamIdMapper.Name); services.AddFromExisting, ClientStreamingProviderRuntime>(); services.AddSingleton, StreamingConverterConfigurator>(); } /// /// Adds a stream filter. /// /// The stream filter type. /// The service collection. /// The stream filter name. /// The service collection. public static IServiceCollection AddStreamFilter(this IServiceCollection services, string name) where T : class, IStreamFilter { return services.AddKeyedSingleton(name); } } } ================================================ FILE: src/Orleans.Streaming/ISiloPersistentStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime.Providers; using Orleans.Streams; namespace Orleans.Hosting { /// /// Functionality for configuring persistent streams. /// public interface ISiloPersistentStreamConfigurator : IPersistentStreamConfigurator { } /// /// Extensions for . /// public static class SiloPersistentStreamConfiguratorExtensions { /// /// Configures the pulling agent. /// /// The configuration builder. /// The configuration delegate. public static void ConfigurePullingAgent(this ISiloPersistentStreamConfigurator configurator, Action> configureOptions = null) { configurator.Configure(configureOptions); } /// /// Configures the lifecycle. /// /// The configuration builder. /// The configuration delegate. public static void ConfigureLifecycle(this ISiloPersistentStreamConfigurator configurator, Action> configureOptions) { configurator.Configure(configureOptions); } /// /// Configures partition balancing. /// /// The configuration builder. /// The partition balancer factory. public static void ConfigurePartitionBalancing(this ISiloPersistentStreamConfigurator configurator, Func factory) { configurator.ConfigureComponent(factory); } /// /// Configures the pulling agents' message delivery backoff provider. /// /// The configuration builder. /// The message delivery backoff factory. public static void ConfigureBackoffProvider(this ISiloPersistentStreamConfigurator configurator, Func factory) { configurator.ConfigureComponent(factory); } /// /// Configures the pulling agents' queue reader backoff provider. /// /// The configuration builder. /// The queue reader backoff factory. public static void ConfigureBackoffProvider(this ISiloPersistentStreamConfigurator configurator, Func factory) { configurator.ConfigureComponent(factory); } /// /// Configures partition balancing. /// /// The partition balancer options. /// The configuration builder. /// The partition balancer factory. /// The configuration delegate. public static void ConfigurePartitionBalancing( this ISiloPersistentStreamConfigurator configurator, Func factory, Action> configureOptions) where TOptions : class, new() { configurator.ConfigureComponent(factory, configureOptions); } } } ================================================ FILE: src/Orleans.Streaming/Internal/IInternalAsyncObservable.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { internal interface IInternalAsyncObservable : IAsyncObservable, IAsyncBatchObservable { Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncObserver observer, StreamSequenceToken token = null); Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncBatchObserver observer, StreamSequenceToken token = null); Task UnsubscribeAsync(StreamSubscriptionHandle handle); Task>> GetAllSubscriptions(); Task Cleanup(); } internal interface IInternalAsyncBatchObserver : IAsyncBatchProducer { Task Cleanup(); } } ================================================ FILE: src/Orleans.Streaming/Internal/IInternalStreamProvider.cs ================================================ namespace Orleans.Streams { internal interface IInternalStreamProvider { IInternalAsyncBatchObserver GetProducerInterface(IAsyncStream streamId); IInternalAsyncObservable GetConsumerInterface(IAsyncStream streamId); } } ================================================ FILE: src/Orleans.Streaming/Internal/IStreamControl.cs ================================================ using System.Threading.Tasks; namespace Orleans.Streams { /// /// Stream control interface to allow stream runtime to perform management operations on streams /// without needing to worry about concrete generic types used by this stream /// internal interface IStreamControl { /// /// Perform cleanup functions for this stream. /// /// Completion promise for the cleanup operations for this stream. Task Cleanup(bool cleanupProducers, bool cleanupConsumers); } } ================================================ FILE: src/Orleans.Streaming/Internal/IStreamGrainExtensions.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans.Streams { // This is the extension interface for stream consumers internal interface IStreamConsumerExtension : IGrainExtension { Task DeliverImmutable(GuidId subscriptionId, QualifiedStreamId streamId, [Immutable] object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken); Task DeliverMutable(GuidId subscriptionId, QualifiedStreamId streamId, object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken); Task DeliverBatch(GuidId subscriptionId, QualifiedStreamId streamId, [Immutable] IBatchContainer item, StreamHandshakeToken handshakeToken); Task CompleteStream(GuidId subscriptionId); Task ErrorInStream(GuidId subscriptionId, Exception exc); Task GetSequenceToken(GuidId subscriptionId); } // This is the extension interface for stream producers internal interface IStreamProducerExtension : IGrainExtension { [AlwaysInterleave] Task AddSubscriber(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData); [AlwaysInterleave] Task RemoveSubscriber(GuidId subscriptionId, QualifiedStreamId streamId); } } ================================================ FILE: src/Orleans.Streaming/Internal/PeriodicAction.cs ================================================ using System; namespace Orleans { internal class PeriodicAction { private readonly Action action; private readonly TimeSpan period; private DateTime nextUtc; public PeriodicAction(TimeSpan period, Action action, DateTime? start = null) { this.period = period; this.nextUtc = start ?? DateTime.UtcNow + period; this.action = action; } public bool TryAction(DateTime nowUtc) { if (nowUtc < this.nextUtc) return false; this.nextUtc = nowUtc + this.period; this.action(); return true; } } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamConsumer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal partial class StreamConsumer : IInternalAsyncObservable { internal bool IsRewindable { get; private set; } private readonly StreamImpl stream; private readonly string streamProviderName; [NonSerialized] private readonly IStreamProviderRuntime providerRuntime; [NonSerialized] private readonly IStreamPubSub pubSub; private StreamConsumerExtension myExtension; private IStreamConsumerExtension myGrainReference; [NonSerialized] private readonly AsyncLock bindExtLock; [NonSerialized] private readonly ILogger logger; public StreamConsumer( StreamImpl stream, string streamProviderName, IStreamProviderRuntime runtime, IStreamPubSub pubSub, ILogger logger, bool isRewindable) { if (stream == null) throw new ArgumentNullException(nameof(stream)); if (runtime == null) throw new ArgumentNullException(nameof(runtime)); if (pubSub == null) throw new ArgumentNullException(nameof(pubSub)); if (logger == null) throw new ArgumentNullException(nameof(logger)); this.logger = logger; this.stream = stream; this.streamProviderName = streamProviderName; this.providerRuntime = runtime; this.pubSub = pubSub; this.IsRewindable = isRewindable; this.myExtension = null; this.myGrainReference = null; this.bindExtLock = new AsyncLock(); } public Task> SubscribeAsync(IAsyncObserver observer) { return SubscribeAsyncImpl(observer, null, null); } public Task> SubscribeAsync( IAsyncObserver observer, StreamSequenceToken token, string filterData = null) { return SubscribeAsyncImpl(observer, null, token, filterData); } public Task> SubscribeAsync(IAsyncBatchObserver batchObserver) { return SubscribeAsyncImpl(null, batchObserver, null); } public Task> SubscribeAsync(IAsyncBatchObserver batchObserver, StreamSequenceToken token) { return SubscribeAsyncImpl(null, batchObserver, token); } private async Task> SubscribeAsyncImpl( IAsyncObserver observer, IAsyncBatchObserver batchObserver, StreamSequenceToken token, string filterData = null) { if (token != null && !IsRewindable) throw new ArgumentNullException(nameof(token), "Passing a non-null token to a non-rewindable IAsyncObservable."); if (observer is GrainReference) throw new ArgumentException("On-behalf subscription via grain references is not supported. Only passing of object references is allowed.", nameof(observer)); if (batchObserver is GrainReference) throw new ArgumentException("On-behalf subscription via grain references is not supported. Only passing of object references is allowed.", nameof(batchObserver)); using var _ = RequestContext.SuppressCallChainReentrancy(); LogDebugSubscribeToken(token); await BindExtensionLazy(); LogDebugSubscribeRendezvous(pubSub, myGrainReference, token); GuidId subscriptionId = pubSub.CreateSubscriptionId(stream.InternalStreamId, myGrainReference.GetGrainId()); // Optimistic Concurrency: // In general, we should first register the subsription with the pubsub (pubSub.RegisterConsumer) // and only if it succeeds store it locally (myExtension.SetObserver). // Basicaly, those 2 operations should be done as one atomic transaction - either both or none and isolated from concurrent reads. // BUT: there is a distributed race here: the first msg may arrive before the call is awaited // (since the pubsub notifies the producer that may immideately produce) // and will thus not find the subriptionHandle in the extension, basically violating "isolation". // Therefore, we employ Optimistic Concurrency Control here to guarantee isolation: // we optimisticaly store subscriptionId in the handle first before calling pubSub.RegisterConsumer // and undo it in the case of failure. // There is no problem with that we call myExtension.SetObserver too early before the handle is registered in pub sub, // since this subscriptionId is unique (random Guid) and no one knows it anyway, unless successfully subscribed in the pubsub. var subriptionHandle = myExtension.SetObserver(subscriptionId, stream, observer, batchObserver, token, filterData); try { await pubSub.RegisterConsumer(subscriptionId, stream.InternalStreamId, myGrainReference.GetGrainId(), filterData); return subriptionHandle; } catch (Exception) { // Undo the previous call myExtension.SetObserver. myExtension.RemoveObserver(subscriptionId); throw; } } public Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncObserver observer, StreamSequenceToken token = null) { return ResumeAsyncImpl(handle, observer, null, token); } public Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncBatchObserver batchObserver, StreamSequenceToken token = null) { return ResumeAsyncImpl(handle, null, batchObserver, token); } private async Task> ResumeAsyncImpl( StreamSubscriptionHandle handle, IAsyncObserver observer, IAsyncBatchObserver batchObserver, StreamSequenceToken token = null) { using var _ = RequestContext.SuppressCallChainReentrancy(); StreamSubscriptionHandleImpl oldHandleImpl = CheckHandleValidity(handle); if (token != null && !IsRewindable) throw new ArgumentNullException(nameof(token), "Passing a non-null token to a non-rewindable IAsyncObservable."); LogDebugResumeToken(token); await BindExtensionLazy(); LogDebugResumeRendezvous(pubSub, myGrainReference, token); StreamSubscriptionHandle newHandle = myExtension.SetObserver(oldHandleImpl.SubscriptionId, stream, observer, batchObserver, token, null); // On failure caller should be able to retry using the original handle, so invalidate old handle only if everything succeeded. oldHandleImpl.Invalidate(); return newHandle; } public async Task UnsubscribeAsync(StreamSubscriptionHandle handle) { using var _ = RequestContext.SuppressCallChainReentrancy(); await BindExtensionLazy(); StreamSubscriptionHandleImpl handleImpl = CheckHandleValidity(handle); LogDebugUnsubscribe(handle); myExtension.RemoveObserver(handleImpl.SubscriptionId); // UnregisterConsumer from pubsub even if does not have this handle locally, to allow UnsubscribeAsync retries. LogDebugUnsubscribeRendezvous(pubSub, myGrainReference); await pubSub.UnregisterConsumer(handleImpl.SubscriptionId, stream.InternalStreamId); handleImpl.Invalidate(); } public async Task>> GetAllSubscriptions() { using var _ = RequestContext.SuppressCallChainReentrancy(); await BindExtensionLazy(); List subscriptions= await pubSub.GetAllSubscriptions(stream.InternalStreamId, myGrainReference.GetGrainId()); return subscriptions.Select(sub => new StreamSubscriptionHandleImpl(GuidId.GetGuidId(sub.SubscriptionId), stream)) .ToList>(); } public async Task Cleanup() { using var _ = RequestContext.SuppressCallChainReentrancy(); LogDebugCleanup(); if (myExtension == null) return; var allHandles = myExtension.GetAllStreamHandles(); var tasks = new List(); foreach (var handle in allHandles) { myExtension.RemoveObserver(handle.SubscriptionId); tasks.Add(pubSub.UnregisterConsumer(handle.SubscriptionId, stream.InternalStreamId)); } try { await Task.WhenAll(tasks); } catch (Exception exc) { LogWarningUnregisterConsumer(exc); } myExtension = null; } // Used in test. internal bool InternalRemoveObserver(StreamSubscriptionHandle handle) { return myExtension != null && myExtension.RemoveObserver(((StreamSubscriptionHandleImpl)handle).SubscriptionId); } internal Task DiagGetConsumerObserversCount() { return Task.FromResult(myExtension.DiagCountStreamObservers(stream.InternalStreamId)); } private async Task BindExtensionLazy() { if (myExtension == null) { using (await bindExtLock.LockAsync()) { if (myExtension == null) { LogDebugBindExtensionLazy(providerRuntime); (myExtension, myGrainReference) = providerRuntime.BindExtension(() => new StreamConsumerExtension(providerRuntime)); LogDebugBindExtension(myExtension, myGrainReference); } } } } private StreamSubscriptionHandleImpl CheckHandleValidity(StreamSubscriptionHandle handle) { if (handle == null) throw new ArgumentNullException(nameof(handle)); if (!handle.StreamId.Equals(stream.StreamId)) throw new ArgumentException("Handle is not for this stream.", nameof(handle)); var handleImpl = handle as StreamSubscriptionHandleImpl; if (handleImpl == null) throw new ArgumentException("Handle type not supported.", nameof(handle)); if (!handleImpl.IsValid) throw new ArgumentException("Handle is no longer valid. It has been used to unsubscribe or resume.", nameof(handle)); return handleImpl; } [LoggerMessage( Level = LogLevel.Debug, Message = "Subscribe Token={Token}" )] private partial void LogDebugSubscribeToken(StreamSequenceToken token); [LoggerMessage( Level = LogLevel.Debug, Message = "Subscribe - Connecting to Rendezvous {PubSub} My GrainRef={GrainReference} Token={Token}" )] private partial void LogDebugSubscribeRendezvous(IStreamPubSub pubSub, IStreamConsumerExtension grainReference, StreamSequenceToken token); [LoggerMessage( Level = LogLevel.Debug, Message = "Resume Token={Token}" )] private partial void LogDebugResumeToken(StreamSequenceToken token); [LoggerMessage( Level = LogLevel.Debug, Message = "Resume - Connecting to Rendezvous {PubSub} My GrainRef={GrainReference} Token={Token}" )] private partial void LogDebugResumeRendezvous(IStreamPubSub pubSub, IStreamConsumerExtension grainReference, StreamSequenceToken token); [LoggerMessage( Level = LogLevel.Debug, Message = "Unsubscribe StreamSubscriptionHandle={Handle}" )] private partial void LogDebugUnsubscribe(StreamSubscriptionHandle handle); [LoggerMessage( Level = LogLevel.Debug, Message = "Unsubscribe - Disconnecting from Rendezvous {PubSub} My GrainRef={GrainReference}" )] private partial void LogDebugUnsubscribeRendezvous(IStreamPubSub pubSub, IStreamConsumerExtension grainReference); [LoggerMessage( Level = LogLevel.Debug, Message = "Cleanup() called" )] private partial void LogDebugCleanup(); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.StreamProvider_ConsumerFailedToUnregister, Message = "Ignoring unhandled exception during PubSub.UnregisterConsumer" )] private partial void LogWarningUnregisterConsumer(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "BindExtensionLazy - Binding local extension to stream runtime={ProviderRuntime}" )] private partial void LogDebugBindExtensionLazy(IStreamProviderRuntime providerRuntime); [LoggerMessage( Level = LogLevel.Debug, Message = "BindExtensionLazy - Connected Extension={Extension} GrainRef={GrainReference}" )] private partial void LogDebugBindExtension(IStreamConsumerExtension extension, IStreamConsumerExtension grainReference); } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamConsumerExtension.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal interface IStreamSubscriptionHandle { Task DeliverItem(object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken); Task DeliverBatch(IBatchContainer item, StreamHandshakeToken handshakeToken); Task CompleteStream(); Task ErrorInStream(Exception exc); StreamHandshakeToken GetSequenceToken(); } /// /// The extension multiplexes all stream related messages to this grain between different streams and their stream observers. /// /// On the silo, we have one extension object per activation and this extension multiplexes all streams on this activation /// (streams of all types and ids: different stream ids and different stream providers). /// On the client, we have one extension per stream (we bind an extension for every StreamConsumer, therefore every stream has its own extension). /// [Serializable] [GenerateSerializer] internal sealed partial class StreamConsumerExtension : IStreamConsumerExtension { [Id(0)] private readonly IStreamProviderRuntime providerRuntime; [Id(1)] private readonly ConcurrentDictionary allStreamObservers = new(); // map to different ObserversCollection of different Ts. [Id(2)] private readonly ILogger logger; private const int MAXIMUM_ITEM_STRING_LOG_LENGTH = 128; // if this extension is attached to a cosnumer grain which implements IOnSubscriptionActioner, // then this will be not null, otherwise, it will be null [NonSerialized] private readonly IStreamSubscriptionObserver streamSubscriptionObserver; internal StreamConsumerExtension(IStreamProviderRuntime providerRt, IStreamSubscriptionObserver streamSubscriptionObserver = null) { this.streamSubscriptionObserver = streamSubscriptionObserver; providerRuntime = providerRt; logger = providerRt.ServiceProvider.GetRequiredService>(); } internal StreamSubscriptionHandleImpl SetObserver( GuidId subscriptionId, StreamImpl stream, IAsyncObserver observer, IAsyncBatchObserver batchObserver, StreamSequenceToken token, string filterData) { if (null == stream) throw new ArgumentNullException(nameof(stream)); try { LogDebugAddObserver(providerRuntime.ExecutingEntityIdentity(), stream.InternalStreamId); // Note: The caller [StreamConsumer] already handles locking for Add/Remove operations, so we don't need to repeat here. var handle = new StreamSubscriptionHandleImpl(subscriptionId, observer, batchObserver, stream, token, filterData); allStreamObservers[subscriptionId] = handle; return handle; } catch (Exception exc) { LogErrorAddObserver(providerRuntime.ExecutingEntityIdentity(), stream.InternalStreamId, exc); throw; } } public bool RemoveObserver(GuidId subscriptionId) { return allStreamObservers.TryRemove(subscriptionId, out _); } public Task DeliverImmutable(GuidId subscriptionId, QualifiedStreamId streamId, object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken) { return DeliverMutable(subscriptionId, streamId, item, currentToken, handshakeToken); } public async Task DeliverMutable(GuidId subscriptionId, QualifiedStreamId streamId, object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken) { LogTraceDeliverItem(new(item), subscriptionId); IStreamSubscriptionHandle observer; if (allStreamObservers.TryGetValue(subscriptionId, out observer)) { return await observer.DeliverItem(item, currentToken, handshakeToken); } else if(this.streamSubscriptionObserver != null) { var streamProvider = this.providerRuntime.ServiceProvider.GetKeyedService(streamId.ProviderName); if(streamProvider != null) { var subscriptionHandlerFactory = new StreamSubscriptionHandlerFactory(streamProvider, streamId, streamId.ProviderName, subscriptionId); await this.streamSubscriptionObserver.OnSubscribed(subscriptionHandlerFactory); //check if an observer were attached after handling the new subscription, deliver on it if attached if (allStreamObservers.TryGetValue(subscriptionId, out observer)) { return await observer.DeliverItem(item, currentToken, handshakeToken); } } } LogWarningNoStreamForItem( providerRuntime.ExecutingEntityIdentity(), subscriptionId); // We got an item when we don't think we're the subscriber. This is a normal race condition. // We can drop the item on the floor, or pass it to the rendezvous, or ... return default; } public async Task DeliverBatch(GuidId subscriptionId, QualifiedStreamId streamId, IBatchContainer batch, StreamHandshakeToken handshakeToken) { LogTraceDeliverBatch(batch, subscriptionId); IStreamSubscriptionHandle observer; if (allStreamObservers.TryGetValue(subscriptionId, out observer)) { return await observer.DeliverBatch(batch, handshakeToken); } else if(this.streamSubscriptionObserver != null) { var streamProvider = this.providerRuntime.ServiceProvider.GetKeyedService(streamId.ProviderName); if (streamProvider != null) { var subscriptionHandlerFactory = new StreamSubscriptionHandlerFactory(streamProvider, streamId, streamId.ProviderName, subscriptionId); await this.streamSubscriptionObserver.OnSubscribed(subscriptionHandlerFactory); // check if an observer were attached after handling the new subscription, deliver on it if attached if (allStreamObservers.TryGetValue(subscriptionId, out observer)) { return await observer.DeliverBatch(batch, handshakeToken); } } } LogWarningNoStreamForBatch( providerRuntime.ExecutingEntityIdentity(), subscriptionId); // We got an item when we don't think we're the subscriber. This is a normal race condition. // We can drop the item on the floor, or pass it to the rendezvous, or ... return default; } public Task CompleteStream(GuidId subscriptionId) { LogTraceCompleteStream(subscriptionId); IStreamSubscriptionHandle observer; if (allStreamObservers.TryGetValue(subscriptionId, out observer)) return observer.CompleteStream(); LogWarningNoStreamForComplete( providerRuntime.ExecutingEntityIdentity(), subscriptionId); // We got an item when we don't think we're the subscriber. This is a normal race condition. // We can drop the item on the floor, or pass it to the rendezvous, or ... return Task.CompletedTask; } public Task ErrorInStream(GuidId subscriptionId, Exception exc) { LogTraceErrorInStream(subscriptionId, exc); IStreamSubscriptionHandle observer; if (allStreamObservers.TryGetValue(subscriptionId, out observer)) return observer.ErrorInStream(exc); LogWarningNoStreamForError( providerRuntime.ExecutingEntityIdentity(), subscriptionId, exc); // We got an item when we don't think we're the subscriber. This is a normal race condition. // We can drop the item on the floor, or pass it to the rendezvous, or ... return Task.CompletedTask; } public Task GetSequenceToken(GuidId subscriptionId) { IStreamSubscriptionHandle observer; return Task.FromResult(allStreamObservers.TryGetValue(subscriptionId, out observer) ? observer.GetSequenceToken() : null); } internal int DiagCountStreamObservers(QualifiedStreamId streamId) { return allStreamObservers.Count(o => o.Value is StreamSubscriptionHandleImpl i && i.SameStreamId(streamId)); } internal List> GetAllStreamHandles() { var ls = new List>(); foreach (var o in allStreamObservers) { if (o.Value is StreamSubscriptionHandleImpl i) ls.Add(i); } return ls; } [LoggerMessage( Level = LogLevel.Debug, Message = "{Grain} AddObserver for stream {StreamId}")] private partial void LogDebugAddObserver(string grain, StreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.StreamProvider_AddObserverException, Message = "{Grain} StreamConsumerExtension.AddObserver({StreamId}) caught exception." )] private partial void LogErrorAddObserver(string grain, StreamId streamId, Exception exception); private struct ItemWithMaxLength(object item) { public override string ToString() { var itemString = item.ToString(); return (itemString.Length > MAXIMUM_ITEM_STRING_LOG_LENGTH) ? itemString[..MAXIMUM_ITEM_STRING_LOG_LENGTH] + "..." : itemString; } } [LoggerMessage( Level = LogLevel.Trace, Message = "DeliverItem {Item} for subscription {Subscription}")] private partial void LogTraceDeliverItem(ItemWithMaxLength item, GuidId subscription); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.StreamProvider_NoStreamForItem, Message = "{Grain} got an item for subscription {Subscription}, but I don't have any subscriber for that stream. Dropping on the floor.")] private partial void LogWarningNoStreamForItem(string grain, GuidId subscription); [LoggerMessage( Level = LogLevel.Trace, Message = "DeliverBatch {Batch} for subscription {Subscription}")] private partial void LogTraceDeliverBatch(IBatchContainer batch, GuidId subscription); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.StreamProvider_NoStreamForBatch, Message = "{Grain} got an item for subscription {Subscription}, but I don't have any subscriber for that stream. Dropping on the floor.")] private partial void LogWarningNoStreamForBatch(string grain, GuidId subscription); [LoggerMessage( Level = LogLevel.Trace, Message = "CompleteStream for subscription {SubscriptionId}")] private partial void LogTraceCompleteStream(GuidId subscriptionId); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.StreamProvider_NoStreamForItem, Message = "{Grain} got a Complete for subscription {Subscription}, but I don't have any subscriber for that stream. Dropping on the floor.")] private partial void LogWarningNoStreamForComplete(string grain, GuidId subscription); [LoggerMessage( Level = LogLevel.Trace, Message = "Error in stream for subscription {Subscription}")] private partial void LogTraceErrorInStream(GuidId subscription, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.StreamProvider_NoStreamForItem, Message = "{Grain} got an error for subscription {Subscription}, but I don't have any subscriber for that stream. Dropping on the floor.")] private partial void LogWarningNoStreamForError(string grain, GuidId subscription, Exception exception); } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamDirectory.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// Stores all streams associated with a specific silo /// internal class StreamDirectory : IAsyncDisposable { private readonly ConcurrentDictionary allStreams = new ConcurrentDictionary(); internal IAsyncStream GetOrAddStream(QualifiedStreamId streamId, Func> streamCreator) { var stream = allStreams.GetOrAdd(streamId, (_, streamCreator) => streamCreator(), streamCreator); var streamOfT = stream as IAsyncStream; if (streamOfT == null) { throw new Runtime.OrleansException($"Stream type mismatch. A stream can only support a single type of data. The generic type of the stream requested ({typeof(T)}) does not match the previously requested type ({stream.GetType().GetGenericArguments().FirstOrDefault()})."); } return streamOfT; } internal async Task Cleanup(bool cleanupProducers, bool cleanupConsumers) { if (StreamResourceTestControl.TestOnlySuppressStreamCleanupOnDeactivate) { return; } var promises = new List(); List streamIds = GetUsedStreamIds(); foreach (QualifiedStreamId s in streamIds) { IStreamControl streamControl = GetStreamControl(s); if (streamControl != null) promises.Add(streamControl.Cleanup(cleanupProducers, cleanupConsumers)); } await Task.WhenAll(promises); } internal void Clear() { // This is a quick temporary solution to unblock testing for resource leakages for streams. allStreams.Clear(); } private IStreamControl GetStreamControl(QualifiedStreamId streamId) { object streamObj; bool ok = allStreams.TryGetValue(streamId, out streamObj); return ok ? streamObj as IStreamControl : null; } private List GetUsedStreamIds() { return allStreams.Select(kv => kv.Key).ToList(); } public async ValueTask DisposeAsync() => await this.Cleanup(cleanupProducers: true, cleanupConsumers: false).ConfigureAwait(false); } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamHandshakeToken.cs ================================================ using System; namespace Orleans.Streams { [Serializable] [GenerateSerializer] internal abstract class StreamHandshakeToken : IEquatable { [Id(0)] public StreamSequenceToken Token { get; private set; } public static StreamHandshakeToken CreateStartToken(StreamSequenceToken token) { if (token == null) return default; return new StartToken {Token = token}; } public static StreamHandshakeToken CreateDeliveyToken(StreamSequenceToken token) { if (token == null) return default; return new DeliveryToken {Token = token}; } public bool Equals(StreamHandshakeToken other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; if (other.GetType() != GetType()) return false; return Equals(Token, other.Token); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((StreamHandshakeToken)obj); } public override int GetHashCode() => HashCode.Combine(GetType(), Token); } [Serializable] [GenerateSerializer] internal sealed class StartToken : StreamHandshakeToken { } [Serializable] [GenerateSerializer] internal sealed class DeliveryToken : StreamHandshakeToken { } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamImpl.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Serialization; namespace Orleans.Streams { [Serializable] [Immutable] [GenerateSerializer] [SerializationCallbacks(typeof(OnDeserializedCallbacks))] internal sealed class StreamImpl : IAsyncStream, IStreamControl, IOnDeserialized { [Id(0)] private readonly QualifiedStreamId streamId; [Id(1)] private readonly bool isRewindable; [NonSerialized] private IInternalStreamProvider? provider; [NonSerialized] private volatile IInternalAsyncBatchObserver? producerInterface; [NonSerialized] private volatile IInternalAsyncObservable? consumerInterface; #if NET9_0_OR_GREATER [NonSerialized] private readonly Lock initLock = new(); #else [NonSerialized] private readonly object initLock = new(); #endif [NonSerialized] private IRuntimeClient? runtimeClient; internal QualifiedStreamId InternalStreamId { get { return streamId; } } public StreamId StreamId => streamId; public bool IsRewindable => isRewindable; public string ProviderName => streamId.ProviderName; // Constructor for Orleans serialization, otherwise initLock is null public StreamImpl() { } public StreamImpl(QualifiedStreamId streamId, IInternalStreamProvider provider, bool isRewindable, IRuntimeClient runtimeClient) { this.streamId = streamId; this.provider = provider ?? throw new ArgumentNullException(nameof(provider)); this.runtimeClient = runtimeClient ?? throw new ArgumentNullException(nameof(runtimeClient)); producerInterface = null; consumerInterface = null; this.isRewindable = isRewindable; } public Task> SubscribeAsync(IAsyncObserver observer) { return GetConsumerInterface().SubscribeAsync(observer, null); } public Task> SubscribeAsync(IAsyncObserver observer, StreamSequenceToken? token, string? filterData = null) { return GetConsumerInterface().SubscribeAsync(observer, token, filterData); } public Task> SubscribeAsync(IAsyncBatchObserver batchObserver) { return GetConsumerInterface().SubscribeAsync(batchObserver); } public Task> SubscribeAsync(IAsyncBatchObserver batchObserver, StreamSequenceToken? token) { return GetConsumerInterface().SubscribeAsync(batchObserver, token); } public async Task Cleanup(bool cleanupProducers, bool cleanupConsumers) { // Cleanup producers if (cleanupProducers && producerInterface != null) { await producerInterface.Cleanup(); producerInterface = null; } // Cleanup consumers if (cleanupConsumers && consumerInterface != null) { await consumerInterface.Cleanup(); consumerInterface = null; } } public Task OnNextAsync(T item, StreamSequenceToken? token = null) { return GetProducerInterface().OnNextAsync(item, token); } public Task OnNextBatchAsync(IEnumerable batch, StreamSequenceToken token) { return GetProducerInterface().OnNextBatchAsync(batch, token); } public Task OnCompletedAsync() { IInternalAsyncBatchObserver producerInterface = GetProducerInterface(); return producerInterface.OnCompletedAsync(); } public Task OnErrorAsync(Exception ex) { IInternalAsyncBatchObserver producerInterface = GetProducerInterface(); return producerInterface.OnErrorAsync(ex); } internal Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncObserver observer, StreamSequenceToken token) { return GetConsumerInterface().ResumeAsync(handle, observer, token); } internal Task> ResumeAsync( StreamSubscriptionHandle handle, IAsyncBatchObserver observer, StreamSequenceToken token) { return GetConsumerInterface().ResumeAsync(handle, observer, token); } public Task>> GetAllSubscriptionHandles() { return GetConsumerInterface().GetAllSubscriptions(); } internal Task UnsubscribeAsync(StreamSubscriptionHandle handle) { return GetConsumerInterface().UnsubscribeAsync(handle); } internal IInternalAsyncBatchObserver GetProducerInterface() { if (producerInterface != null) return producerInterface; lock (initLock) { if (producerInterface != null) return producerInterface; if (provider == null) provider = GetStreamProvider(); producerInterface = provider!.GetProducerInterface(this); } return producerInterface; } internal IInternalAsyncObservable GetConsumerInterface() { if (consumerInterface == null) { lock (initLock) { if (consumerInterface == null) { if (provider == null) provider = GetStreamProvider(); consumerInterface = provider!.GetConsumerInterface(this); } } } return consumerInterface; } private IInternalStreamProvider? GetStreamProvider() { return this.runtimeClient?.ServiceProvider.GetRequiredKeyedService(streamId.ProviderName) as IInternalStreamProvider; } public int CompareTo(IAsyncStream? other) { var o = other as StreamImpl; return o == null ? 1 : streamId.CompareTo(o.streamId); } public bool Equals(IAsyncStream? other) { var o = other as StreamImpl; return o != null && streamId.Equals(o.streamId); } public override bool Equals(object? obj) { var o = obj as StreamImpl; return o != null && streamId.Equals(o.streamId); } public override int GetHashCode() { return streamId.GetHashCode(); } public override string ToString() { return streamId.ToString(); } void IOnDeserialized.OnDeserialized(DeserializationContext context) { this.runtimeClient = context?.RuntimeClient as IRuntimeClient; } } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamSubscriptionHandleImpl.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using Orleans.Runtime; namespace Orleans.Streams { [Serializable] [GenerateSerializer] internal class StreamSubscriptionHandleImpl : StreamSubscriptionHandle, IStreamSubscriptionHandle { [Id(0)] [JsonProperty] private StreamImpl streamImpl; [Id(1)] [JsonProperty] private readonly string filterData; [Id(2)] [JsonProperty] private readonly GuidId subscriptionId; [Id(3)] private readonly bool isRewindable; [NonSerialized] private IAsyncObserver observer; [NonSerialized] private IAsyncBatchObserver batchObserver; [NonSerialized] private StreamHandshakeToken expectedToken; internal bool IsValid { get { return streamImpl != null; } } internal GuidId SubscriptionId { get { return subscriptionId; } } internal bool IsRewindable { get { return isRewindable; } } public override string ProviderName { get { return this.streamImpl.ProviderName; } } public override StreamId StreamId { get { return streamImpl.StreamId; } } public override Guid HandleId { get { return subscriptionId.Guid; } } [JsonConstructor] public StreamSubscriptionHandleImpl(GuidId subscriptionId, StreamImpl streamImpl, string filterData) : this(subscriptionId, null, null, streamImpl, null, filterData) { } public StreamSubscriptionHandleImpl(GuidId subscriptionId, StreamImpl streamImpl) : this(subscriptionId, null, null, streamImpl, null, null) { } public StreamSubscriptionHandleImpl( GuidId subscriptionId, IAsyncObserver observer, IAsyncBatchObserver batchObserver, StreamImpl streamImpl, StreamSequenceToken token, string filterData) { this.subscriptionId = subscriptionId ?? throw new ArgumentNullException(nameof(subscriptionId)); this.observer = observer; this.batchObserver = batchObserver; this.streamImpl = streamImpl ?? throw new ArgumentNullException(nameof(streamImpl)); this.filterData = filterData; this.isRewindable = streamImpl.IsRewindable; if (IsRewindable) { expectedToken = StreamHandshakeToken.CreateStartToken(token); } } public void Invalidate() { this.streamImpl = null; this.observer = null; this.batchObserver = null; } public StreamHandshakeToken GetSequenceToken() { return this.expectedToken; } public override Task UnsubscribeAsync() { if (!IsValid) throw new InvalidOperationException("Handle is no longer valid. It has been used to unsubscribe or resume."); return this.streamImpl.UnsubscribeAsync(this); } public override Task> ResumeAsync(IAsyncObserver obs, StreamSequenceToken token = null) { if (!IsValid) throw new InvalidOperationException("Handle is no longer valid. It has been used to unsubscribe or resume."); return this.streamImpl.ResumeAsync(this, obs, token); } public override Task> ResumeAsync(IAsyncBatchObserver observer, StreamSequenceToken token = null) { if (!IsValid) throw new InvalidOperationException("Handle is no longer valid. It has been used to unsubscribe or resume."); return this.streamImpl.ResumeAsync(this, observer, token); } public async Task DeliverBatch(IBatchContainer batch, StreamHandshakeToken handshakeToken) { // we validate expectedToken only for ordered (rewindable) streams if (this.expectedToken != null) { if (!this.expectedToken.Equals(handshakeToken)) return this.expectedToken; // Check if this even already has been delivered if (IsRewindable) { var currentToken = StreamHandshakeToken.CreateDeliveyToken(batch.SequenceToken); if (this.expectedToken.Equals(currentToken)) return this.expectedToken; } } if (batch is IBatchContainerBatch) { var batchContainerBatch = batch as IBatchContainerBatch; await NextBatch(batchContainerBatch); } else { if (this.observer != null) { foreach (var itemTuple in batch.GetEvents()) { await NextItem(itemTuple.Item1, itemTuple.Item2); } } else { await NextItems(batch.GetEvents()); } } if (IsRewindable) { this.expectedToken = StreamHandshakeToken.CreateDeliveyToken(batch.SequenceToken); } return null; } public async Task DeliverItem(object item, StreamSequenceToken currentToken, StreamHandshakeToken handshakeToken) { if (this.expectedToken != null) { if (!this.expectedToken.Equals(handshakeToken)) return this.expectedToken; // Check if this even already has been delivered if (IsRewindable) { if (this.expectedToken.Equals(currentToken)) return this.expectedToken; } } T typedItem; try { typedItem = (T)item; } catch (InvalidCastException) { // We got an illegal item on the stream -- close it with a Cast exception throw new InvalidCastException("Received an item of type " + item.GetType().Name + ", expected " + typeof(T).FullName); } await ((this.observer != null) ? NextItem(typedItem, currentToken) : NextItems(new[] { Tuple.Create(typedItem, currentToken) })); // check again, in case the expectedToken was changed indiretly via ResumeAsync() if (this.expectedToken != null) { if (!this.expectedToken.Equals(handshakeToken)) return this.expectedToken; } if (IsRewindable) { this.expectedToken = StreamHandshakeToken.CreateDeliveyToken(currentToken); } return null; } public async Task NextBatch(IBatchContainerBatch batchContainerBatch) { if (this.observer != null) { foreach (var batchContainer in batchContainerBatch.BatchContainers) { bool isRequestContextSet = batchContainer.ImportRequestContext(); try { foreach (var itemTuple in batchContainer.GetEvents()) { await NextItem(itemTuple.Item1, itemTuple.Item2); } } finally { if (isRequestContextSet) { RequestContext.Clear(); } } } } else { await NextItems(batchContainerBatch.BatchContainers.SelectMany(batch => batch.GetEvents())); } } private Task NextItem(T item, StreamSequenceToken token) { // This method could potentially be invoked after Dispose() has been called, // so we have to ignore the request or we risk breaking unit tests AQ_01 - AQ_04. if (this.observer == null || !IsValid) return Task.CompletedTask; return this.observer.OnNextAsync(item, token); } private Task NextItems(IEnumerable> items) { // This method could potentially be invoked after Dispose() has been called, // so we have to ignore the request or we risk breaking unit tests AQ_01 - AQ_04. if (this.batchObserver == null || !IsValid) return Task.CompletedTask; IList> batch = items .Select(item => new SequentialItem(item.Item1, item.Item2)) .ToList(); return batch.Count != 0 ? this.batchObserver.OnNextAsync(batch) : Task.CompletedTask; } public Task CompleteStream() { return this.observer is null ? this.batchObserver is null ? Task.CompletedTask : this.batchObserver.OnCompletedAsync() : this.observer.OnCompletedAsync(); } public Task ErrorInStream(Exception ex) { return this.observer is null ? this.batchObserver is null ? Task.CompletedTask : this.batchObserver.OnErrorAsync(ex) : this.observer.OnErrorAsync(ex); } internal bool SameStreamId(QualifiedStreamId streamId) { return IsValid && streamImpl.InternalStreamId.Equals(streamId); } public override bool Equals(StreamSubscriptionHandle other) { var o = other as StreamSubscriptionHandleImpl; return o != null && SubscriptionId.Equals(o.SubscriptionId); } public override bool Equals(object obj) { return Equals(obj as StreamSubscriptionHandle); } public override int GetHashCode() { return SubscriptionId.GetHashCode(); } public override string ToString() { return string.Format("StreamSubscriptionHandleImpl:Stream={0},HandleId={1}", IsValid ? streamImpl.InternalStreamId.ToString() : "null", HandleId); } } } ================================================ FILE: src/Orleans.Streaming/Internal/StreamSubsriptionHandlerFactory.cs ================================================ using Orleans.Runtime; using System; using Orleans.Streams.Core; namespace Orleans.Streams { /// /// Factory for creating instances. /// public class StreamSubscriptionHandlerFactory : IStreamSubscriptionHandleFactory { private readonly IStreamProvider streamProvider; /// public StreamId StreamId { get; } /// public string ProviderName { get; } /// public GuidId SubscriptionId { get; } /// /// Initializes a new instance of the class. /// /// /// The stream provider. /// /// /// The stream identity. /// /// /// The stream provider name. /// /// /// The subscription identity. /// public StreamSubscriptionHandlerFactory(IStreamProvider streamProvider, StreamId streamId, string providerName, GuidId subscriptionId) { this.streamProvider = streamProvider ?? throw new ArgumentNullException(nameof(streamProvider)); this.StreamId = streamId; this.ProviderName = providerName; this.SubscriptionId = subscriptionId; } /// public StreamSubscriptionHandle Create() { var stream = this.streamProvider.GetStream(StreamId) as StreamImpl; return new StreamSubscriptionHandleImpl(SubscriptionId, stream); } } } ================================================ FILE: src/Orleans.Streaming/InternalStreamId.cs ================================================ using System; using System.Runtime.Serialization; #nullable enable namespace Orleans.Runtime { [Immutable] [Serializable] [GenerateSerializer] public readonly struct QualifiedStreamId : IEquatable, IComparable, ISerializable, ISpanFormattable { [Id(0)] public readonly StreamId StreamId; [Id(1)] public readonly string ProviderName; public QualifiedStreamId(string providerName, StreamId streamId) { ProviderName = providerName; StreamId = streamId; } private QualifiedStreamId(SerializationInfo info, StreamingContext context) { ProviderName = info.GetString("pvn")!; StreamId = (StreamId)info.GetValue("sid", typeof(StreamId))!; } public static implicit operator StreamId(QualifiedStreamId internalStreamId) => internalStreamId.StreamId; public bool Equals(QualifiedStreamId other) => StreamId.Equals(other) && ProviderName.Equals(other.ProviderName); public override bool Equals(object? obj) => obj is QualifiedStreamId other ? this.Equals(other) : false; public static bool operator ==(QualifiedStreamId s1, QualifiedStreamId s2) => s1.Equals(s2); public static bool operator !=(QualifiedStreamId s1, QualifiedStreamId s2) => !s2.Equals(s1); public int CompareTo(QualifiedStreamId other) => StreamId.CompareTo(other.StreamId); public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("pvn", ProviderName); info.AddValue("sid", StreamId, typeof(StreamId)); } public override int GetHashCode() => HashCode.Combine(ProviderName, StreamId); public override string ToString() => $"{ProviderName}/{StreamId}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => destination.TryWrite($"{ProviderName}/{StreamId}", out charsWritten); internal string? GetNamespace() => StreamId.GetNamespace(); } } ================================================ FILE: src/Orleans.Streaming/JsonConverters/StreamImplConverter.cs ================================================ #nullable enable using System; using System.Runtime.Serialization; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Streaming.JsonConverters { internal class StreamImplConverter : JsonConverter { private readonly IRuntimeClient _runtimeClient; public StreamImplConverter(IRuntimeClient runtimeClient) { _runtimeClient = runtimeClient; } public override bool CanConvert(Type objectType) => objectType.IsGenericType && (objectType.GetGenericTypeDefinition() == typeof(StreamImpl<>) || objectType.GetGenericTypeDefinition() == typeof(IAsyncStream<>)); public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var jo = JObject.Load(reader); if (jo != null) { var itemType = objectType.GetGenericArguments()[0]; var fullType = typeof(StreamImpl<>).MakeGenericType(itemType); var streamId = jo["streamId"]?.ToObject(); var providerName = jo["providerName"]?.Value(); var isRewindable = jo["isRewindable"]?.ToObject(); if (streamId.HasValue && isRewindable.HasValue && !string.IsNullOrWhiteSpace(providerName)) { var provider = _runtimeClient.ServiceProvider.GetRequiredKeyedService(providerName) as IInternalStreamProvider; return Activator.CreateInstance(fullType, new QualifiedStreamId(providerName, streamId.Value), provider, isRewindable.Value, _runtimeClient); } } throw new SerializationException(); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { writer.WriteStartObject(); if (value is IAsyncStream target) { writer.WritePropertyName("streamId"); serializer.Serialize(writer, target.StreamId); writer.WritePropertyName("providerName"); serializer.Serialize(writer, target.ProviderName); writer.WritePropertyName("isRewindable"); writer.WriteValue(target.IsRewindable); } writer.WriteEndObject(); } } } ================================================ FILE: src/Orleans.Streaming/JsonConverters/StreamingConverterConfigurator.cs ================================================ #nullable enable using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Serialization; namespace Orleans.Streaming.JsonConverters { internal class StreamingConverterConfigurator : IPostConfigureOptions { private readonly IRuntimeClient _runtimeClient; public StreamingConverterConfigurator(IRuntimeClient runtimeClient) { _runtimeClient = runtimeClient; } public void PostConfigure(string? name, OrleansJsonSerializerOptions options) { options.JsonSerializerSettings.Converters.Add(new StreamImplConverter(_runtimeClient)); } } } ================================================ FILE: src/Orleans.Streaming/LoadShedQueueFlowController.cs ================================================ using System; using Orleans.Configuration; using Orleans.Statistics; namespace Orleans.Streams { /// /// Flow control triggered by silo load shedding. /// This is an all-or-nothing trigger which will request , or 0. /// public class LoadShedQueueFlowController : IQueueFlowController { private readonly LoadSheddingOptions options; private readonly double loadSheddingLimit; private readonly IEnvironmentStatisticsProvider environmentStatisticsProvider; /// /// Creates a flow controller triggered when the CPU reaches a percentage of the cluster load shedding limit. /// This is intended to reduce queue read rate prior to causing the silo to shed load. /// Note: Triggered only when load shedding is enabled. /// /// The silo statistics options. /// Percentage of load shed limit which triggers a reduction of queue read rate. /// The silo environment statistics. /// The flow controller. public static IQueueFlowController CreateAsPercentOfLoadSheddingLimit(LoadSheddingOptions options, IEnvironmentStatisticsProvider environmentStatisticsProvider, int percentOfSiloSheddingLimit = LoadSheddingOptions.DefaultCpuThreshold) { if (percentOfSiloSheddingLimit < 0.0 || percentOfSiloSheddingLimit > 100.0) throw new ArgumentOutOfRangeException(nameof(percentOfSiloSheddingLimit), "Percent value must be between 0-100"); // Start shedding before silo reaches shedding limit. return new LoadShedQueueFlowController((int)(options.CpuThreshold * (percentOfSiloSheddingLimit / 100.0)), options, environmentStatisticsProvider); } /// /// Creates a flow controller triggered when the CPU reaches the specified limit. /// Note: Triggered only when load shedding is enabled. /// /// Percentage of CPU which triggers queue read rate reduction /// The silo statistics options. /// The silo environment statistics. /// The flow controller. public static IQueueFlowController CreateAsPercentageOfCPU(int loadSheddingLimit, LoadSheddingOptions options, IEnvironmentStatisticsProvider environmentStatisticsProvider) { if (loadSheddingLimit < 0 || loadSheddingLimit > 100) throw new ArgumentOutOfRangeException(nameof(loadSheddingLimit), "Value must be between 0-100"); return new LoadShedQueueFlowController(loadSheddingLimit, options, environmentStatisticsProvider); } private LoadShedQueueFlowController(int loadSheddingLimit, LoadSheddingOptions options, IEnvironmentStatisticsProvider environmentStatisticsProvider) { this.options = options; if (loadSheddingLimit < 0 || loadSheddingLimit > 100) throw new ArgumentOutOfRangeException(nameof(loadSheddingLimit), "Value must be between 0-100"); this.loadSheddingLimit = loadSheddingLimit != 0 ? loadSheddingLimit : int.MaxValue; this.environmentStatisticsProvider = environmentStatisticsProvider; } /// public int GetMaxAddCount() { return options.LoadSheddingEnabled && environmentStatisticsProvider.GetEnvironmentStatistics().FilteredCpuUsagePercentage > loadSheddingLimit ? 0 : int.MaxValue; } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/IMemoryStreamQueueGrain.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Providers { /// /// Interface for In-memory stream queue grain. /// public interface IMemoryStreamQueueGrain : IGrainWithGuidKey { /// /// Enqueues an event. /// /// The data. /// A representing the operation. Task Enqueue(MemoryMessageData data); /// /// Dequeues up to events. /// /// /// The maximum number of events to dequeue. /// /// A representing the operation. Task> Dequeue(int maxCount); } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryAdapterFactory.cs ================================================ using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO.Hashing; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers { /// /// Adapter factory for in memory stream provider. /// This factory acts as the adapter and the adapter factory. The events are stored in an in-memory grain that /// behaves as an event queue, this provider adapter is primarily used for testing /// public partial class MemoryAdapterFactory : IQueueAdapterFactory, IQueueAdapter, IQueueAdapterCache where TSerializer : class, IMemoryMessageBodySerializer { private readonly StreamCacheEvictionOptions cacheOptions; private readonly StreamStatisticOptions statisticOptions; private readonly HashRingStreamQueueMapperOptions queueMapperOptions; private readonly IGrainFactory grainFactory; private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; private readonly TSerializer serializer; private readonly ulong _nameHash; private IStreamQueueMapper streamQueueMapper; private ConcurrentDictionary queueGrains; private IObjectPool bufferPool; private BlockPoolMonitorDimensions blockPoolMonitorDimensions; private IStreamFailureHandler streamFailureHandler; private TimePurgePredicate purgePredicate; /// public string Name { get; } /// public bool IsRewindable => true; /// public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; /// /// Creates a failure handler for a partition. /// protected Func> StreamFailureHandlerFactory { get; set; } /// /// Create a cache monitor to report cache related metrics /// Return a ICacheMonitor /// protected Func CacheMonitorFactory; /// /// Create a block pool monitor to monitor block pool related metrics /// Return a IBlockPoolMonitor /// protected Func BlockPoolMonitorFactory; /// /// Create a monitor to monitor QueueAdapterReceiver related metrics /// Return a IQueueAdapterReceiverMonitor /// protected Func ReceiverMonitorFactory; public MemoryAdapterFactory( string providerName, StreamCacheEvictionOptions cacheOptions, StreamStatisticOptions statisticOptions, HashRingStreamQueueMapperOptions queueMapperOptions, IServiceProvider serviceProvider, IGrainFactory grainFactory, ILoggerFactory loggerFactory) { this.Name = providerName; this.queueMapperOptions = queueMapperOptions ?? throw new ArgumentNullException(nameof(queueMapperOptions)); this.cacheOptions = cacheOptions ?? throw new ArgumentNullException(nameof(cacheOptions)); this.statisticOptions = statisticOptions ?? throw new ArgumentException(nameof(statisticOptions)); this.grainFactory = grainFactory ?? throw new ArgumentNullException(nameof(grainFactory)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.logger = loggerFactory.CreateLogger>>(); this.serializer = MemoryMessageBodySerializerFactory.GetOrCreateSerializer(serviceProvider); var nameBytes = BitConverter.IsLittleEndian ? MemoryMarshal.AsBytes(Name.AsSpan()) : Encoding.Unicode.GetBytes(Name); XxHash64.Hash(nameBytes, MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref _nameHash, 1))); } /// /// Initializes this instance. /// public void Init() { this.queueGrains = new ConcurrentDictionary(); if (CacheMonitorFactory == null) this.CacheMonitorFactory = (dimensions) => new DefaultCacheMonitor(dimensions); if (this.BlockPoolMonitorFactory == null) this.BlockPoolMonitorFactory = (dimensions) => new DefaultBlockPoolMonitor(dimensions); if (this.ReceiverMonitorFactory == null) this.ReceiverMonitorFactory = (dimensions) => new DefaultQueueAdapterReceiverMonitor(dimensions); this.purgePredicate = new TimePurgePredicate(this.cacheOptions.DataMinTimeInCache, this.cacheOptions.DataMaxAgeInCache); this.streamQueueMapper = new HashRingBasedStreamQueueMapper(this.queueMapperOptions, this.Name); } private void CreateBufferPoolIfNotCreatedYet() { if (this.bufferPool == null) { // 1 meg block size pool this.blockPoolMonitorDimensions = new BlockPoolMonitorDimensions($"BlockPool-{Guid.NewGuid()}"); var oneMb = 1 << 20; var objectPoolMonitor = new ObjectPoolMonitorBridge(this.BlockPoolMonitorFactory(blockPoolMonitorDimensions), oneMb); this.bufferPool = new ObjectPool(() => new FixedSizeBuffer(oneMb), objectPoolMonitor, this.statisticOptions.StatisticMonitorWriteInterval); } } /// public Task CreateAdapter() { return Task.FromResult(this); } /// public IQueueAdapterCache GetQueueAdapterCache() { return this; } /// public IStreamQueueMapper GetStreamQueueMapper() { return streamQueueMapper; } /// public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { var dimensions = new ReceiverMonitorDimensions(queueId.ToString()); var receiverLogger = this.loggerFactory.CreateLogger($"{typeof(MemoryAdapterReceiver).FullName}.{this.Name}.{queueId}"); var receiverMonitor = this.ReceiverMonitorFactory(dimensions); IQueueAdapterReceiver receiver = new MemoryAdapterReceiver(GetQueueGrain(queueId), receiverLogger, this.serializer, receiverMonitor); return receiver; } /// public async Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { try { var queueId = streamQueueMapper.GetQueueForStream(streamId); ArraySegment bodyBytes = serializer.Serialize(new MemoryMessageBody(events.Cast(), requestContext)); var messageData = MemoryMessageData.Create(streamId, bodyBytes); IMemoryStreamQueueGrain queueGrain = GetQueueGrain(queueId); await queueGrain.Enqueue(messageData); } catch (Exception exc) { LogErrorQueueMessageBatchAsync(exc); throw; } } /// public IQueueCache CreateQueueCache(QueueId queueId) { //move block pool creation from init method to here, to avoid unnecessary block pool creation when stream provider is initialized in client side. CreateBufferPoolIfNotCreatedYet(); var logger = this.loggerFactory.CreateLogger($"{typeof(MemoryPooledCache).FullName}.{this.Name}.{queueId}"); var monitor = this.CacheMonitorFactory(new CacheMonitorDimensions(queueId.ToString(), this.blockPoolMonitorDimensions.BlockPoolId)); return new MemoryPooledCache(bufferPool, purgePredicate, logger, this.serializer, monitor, this.statisticOptions.StatisticMonitorWriteInterval, this.cacheOptions.MetadataMinTimeInCache); } /// public Task GetDeliveryFailureHandler(QueueId queueId) { return Task.FromResult(streamFailureHandler ?? (streamFailureHandler = new NoOpStreamDeliveryFailureHandler())); } /// /// Generate a deterministic Guid from a queue Id. /// private Guid GenerateDeterministicGuid(QueueId queueId) { Span bytes = stackalloc byte[16]; MemoryMarshal.Write(bytes, in _nameHash); BinaryPrimitives.WriteUInt32LittleEndian(bytes[8..], queueId.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[12..], queueId.GetNumericId()); return new(bytes); } /// /// Get a MemoryStreamQueueGrain instance by queue Id. /// private IMemoryStreamQueueGrain GetQueueGrain(QueueId queueId) { return queueGrains.GetOrAdd(queueId, (id, arg) => arg.grainFactory.GetGrain(arg.instance.GenerateDeterministicGuid(id)), (instance: this, grainFactory)); } /// /// Creates a new instance. /// /// The services. /// The provider name. /// A mew instance. public static MemoryAdapterFactory Create(IServiceProvider services, string name) { var cachePurgeOptions = services.GetOptionsByName(name); var statisticOptions = services.GetOptionsByName(name); var queueMapperOptions = services.GetOptionsByName(name); var factory = ActivatorUtilities.CreateInstance>(services, name, cachePurgeOptions, statisticOptions, queueMapperOptions); factory.Init(); return factory; } [LoggerMessage( EventId = (int)ProviderErrorCode.MemoryStreamProviderBase_QueueMessageBatchAsync, Level = LogLevel.Error, Message = "Exception thrown in MemoryAdapterFactory.QueueMessageBatchAsync." )] private partial void LogErrorQueueMessageBatchAsync(Exception exception); } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryAdapterReceiver.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Providers.Streams.Common; using Orleans.Streams; namespace Orleans.Providers { internal partial class MemoryAdapterReceiver : IQueueAdapterReceiver where TSerializer : class, IMemoryMessageBodySerializer { private readonly IMemoryStreamQueueGrain queueGrain; private readonly List awaitingTasks; private readonly ILogger logger; private readonly TSerializer serializer; private readonly IQueueAdapterReceiverMonitor receiverMonitor; public MemoryAdapterReceiver(IMemoryStreamQueueGrain queueGrain, ILogger logger, TSerializer serializer, IQueueAdapterReceiverMonitor receiverMonitor) { this.queueGrain = queueGrain; this.logger = logger; this.serializer = serializer; awaitingTasks = new List(); this.receiverMonitor = receiverMonitor; } public Task Initialize(TimeSpan timeout) { this.receiverMonitor?.TrackInitialization(true, TimeSpan.MinValue, null); return Task.CompletedTask; } public async Task> GetQueueMessagesAsync(int maxCount) { var watch = Stopwatch.StartNew(); List batches; Task> task = null; try { task = queueGrain.Dequeue(maxCount); awaitingTasks.Add(task); var eventData = await task; batches = eventData.Select(data => new MemoryBatchContainer(data, this.serializer)).ToList(); watch.Stop(); this.receiverMonitor?.TrackRead(true, watch.Elapsed, null); if (eventData.Count > 0) { var oldestMessage = eventData[0]; var newestMessage = eventData[eventData.Count - 1]; this.receiverMonitor?.TrackMessagesReceived(eventData.Count, oldestMessage.EnqueueTimeUtc, newestMessage.EnqueueTimeUtc); } } catch (Exception exc) { LogErrorGetQueueMessagesAsync(exc); watch.Stop(); this.receiverMonitor?.TrackRead(true, watch.Elapsed, exc); throw; } finally { awaitingTasks.Remove(task); } return batches; } public Task MessagesDeliveredAsync(IList messages) { return Task.CompletedTask; } public async Task Shutdown(TimeSpan timeout) { var watch = Stopwatch.StartNew(); try { if (awaitingTasks.Count != 0) { await Task.WhenAll(awaitingTasks); } watch.Stop(); this.receiverMonitor?.TrackShutdown(true, watch.Elapsed, null); } catch (Exception ex) { watch.Stop(); this.receiverMonitor?.TrackShutdown(false, watch.Elapsed, ex); } } [LoggerMessage( EventId = (int)ProviderErrorCode.MemoryStreamProviderBase_GetQueueMessagesAsync, Level = LogLevel.Error, Message = "Exception thrown in MemoryAdapterFactory.GetQueueMessagesAsync." )] private partial void LogErrorGetQueueMessagesAsync(Exception exception); } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryBatchContainer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; namespace Orleans.Providers { [Serializable] [GenerateSerializer] [SerializationCallbacks(typeof(OnDeserializedCallbacks))] internal sealed class MemoryBatchContainer : IBatchContainer, IOnDeserialized where TSerializer : class, IMemoryMessageBodySerializer { [NonSerialized] private TSerializer serializer; [Id(0)] private readonly EventSequenceToken realToken; public StreamId StreamId => MessageData.StreamId; public StreamSequenceToken SequenceToken => realToken; [Id(1)] public MemoryMessageData MessageData { get; set; } public long SequenceNumber => realToken.SequenceNumber; // Payload is local cache of deserialized payloadBytes. Should never be serialized as part of batch container. During batch container serialization raw payloadBytes will always be used. [NonSerialized] private MemoryMessageBody payload; private MemoryMessageBody Payload() { return payload ?? (payload = serializer.Deserialize(MessageData.Payload)); } public MemoryBatchContainer(MemoryMessageData messageData, TSerializer serializer) { this.serializer = serializer; MessageData = messageData; realToken = new EventSequenceToken(messageData.SequenceNumber); } public IEnumerable> GetEvents() { return Payload().Events.Cast().Select((e, i) => Tuple.Create(e, realToken.CreateSequenceTokenForEvent(i))); } public bool ImportRequestContext() { var context = Payload().RequestContext; if (context != null) { RequestContextExtensions.Import(context); return true; } return false; } void IOnDeserialized.OnDeserialized(DeserializationContext context) { this.serializer = MemoryMessageBodySerializerFactory.GetOrCreateSerializer(context.ServiceProvider); } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryMessageBody.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; namespace Orleans.Providers { /// /// Implementations of this interface are responsible for serializing MemoryMessageBody objects /// public interface IMemoryMessageBodySerializer { /// /// Serialize to an array segment of bytes. /// /// The body. /// The serialized data. ArraySegment Serialize(MemoryMessageBody body); /// /// Deserialize an array segment into a /// /// The body bytes. /// The deserialized message body. MemoryMessageBody Deserialize(ArraySegment bodyBytes); } /// /// Default implementation. /// [Serializable, GenerateSerializer, Immutable] [SerializationCallbacks(typeof(Runtime.OnDeserializedCallbacks))] public sealed class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer, IOnDeserialized { [NonSerialized] private Serializer serializer; /// /// Initializes a new instance of the class. /// /// The serializer. public DefaultMemoryMessageBodySerializer(Serializer serializer) { this.serializer = serializer; } /// public ArraySegment Serialize(MemoryMessageBody body) { return new ArraySegment(serializer.SerializeToArray(body)); } /// public MemoryMessageBody Deserialize(ArraySegment bodyBytes) { return serializer.Deserialize(bodyBytes.ToArray()); } /// void IOnDeserialized.OnDeserialized(DeserializationContext context) { this.serializer = context.ServiceProvider.GetRequiredService>(); } } /// /// Message body used by the in-memory stream provider. /// [Serializable] [GenerateSerializer] public sealed class MemoryMessageBody { /// /// Initializes a new instance of the class. /// /// Events that are part of this message. /// Context in which this message was sent. public MemoryMessageBody(IEnumerable events, Dictionary requestContext) { if (events == null) throw new ArgumentNullException(nameof(events)); Events = events.ToList(); RequestContext = requestContext; } /// /// Gets the events in the message. /// [Id(0)] public List Events { get; } /// /// Gets the message request context. /// [Id(1)] public Dictionary RequestContext { get; } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryMessageBodySerializerFactory.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Providers { internal static class MemoryMessageBodySerializerFactory where TSerializer : class, IMemoryMessageBodySerializer { private static readonly Lazy ObjectFactory = new Lazy( () => ActivatorUtilities.CreateFactory( typeof(TSerializer), Type.EmptyTypes)); public static TSerializer GetOrCreateSerializer(IServiceProvider serviceProvider) { return serviceProvider.GetService() ?? (TSerializer) ObjectFactory.Value(serviceProvider, null); } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryMessageData.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Providers { /// /// Represents the event sent and received from an In-Memory queue grain. /// [Serializable] [GenerateSerializer] public struct MemoryMessageData { /// /// The stream identifier. /// [Id(0)] public StreamId StreamId; /// /// The position of the event in the stream. /// [Id(1)] public long SequenceNumber; /// /// The time this message was read from the message queue. /// [Id(2)] public DateTime DequeueTimeUtc; /// /// The time message was written to the message queue. /// [Id(3)] public DateTime EnqueueTimeUtc; /// /// The serialized event data. /// [Id(4)] public ArraySegment Payload; internal static MemoryMessageData Create(StreamId streamId, ArraySegment arraySegment) { return new MemoryMessageData { StreamId = streamId, EnqueueTimeUtc = DateTime.UtcNow, Payload = arraySegment }; } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryPooledCache.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Providers { /// /// Pooled cache for memory stream provider /// public class MemoryPooledCache : IQueueCache, ICacheDataAdapter where TSerializer : class, IMemoryMessageBodySerializer { private readonly IObjectPool bufferPool; private readonly TSerializer serializer; private readonly IEvictionStrategy evictionStrategy; private readonly PooledQueueCache cache; private FixedSizeBuffer currentBuffer; /// /// Pooled cache for memory stream provider. /// /// The buffer pool. /// The purge predicate. /// The logger. /// The serializer. /// The cache monitor. /// The monitor write interval. public MemoryPooledCache( IObjectPool bufferPool, TimePurgePredicate purgePredicate, ILogger logger, TSerializer serializer, ICacheMonitor cacheMonitor, TimeSpan? monitorWriteInterval, TimeSpan? purgeMetadataInterval) { this.bufferPool = bufferPool; this.serializer = serializer; this.cache = new PooledQueueCache(this, logger, cacheMonitor, monitorWriteInterval, purgeMetadataInterval); this.evictionStrategy = new ChronologicalEvictionStrategy(logger, purgePredicate, cacheMonitor, monitorWriteInterval) {PurgeObservable = cache}; } private CachedMessage QueueMessageToCachedMessage(MemoryMessageData queueMessage, DateTime dequeueTimeUtc) { StreamPosition streamPosition = GetStreamPosition(queueMessage); return new CachedMessage() { StreamId = streamPosition.StreamId, SequenceNumber = queueMessage.SequenceNumber, EnqueueTimeUtc = queueMessage.EnqueueTimeUtc, DequeueTimeUtc = dequeueTimeUtc, Segment = SerializeMessageIntoPooledSegment(queueMessage) }; } // Placed object message payload into a segment from a buffer pool. When this get's too big, older blocks will be purged private ArraySegment SerializeMessageIntoPooledSegment(MemoryMessageData queueMessage) { // serialize payload int size = SegmentBuilder.CalculateAppendSize(queueMessage.Payload); // get segment from current block ArraySegment segment; if (currentBuffer == null || !currentBuffer.TryGetSegment(size, out segment)) { // no block or block full, get new block and try again currentBuffer = bufferPool.Allocate(); //call EvictionStrategy's OnBlockAllocated method this.evictionStrategy.OnBlockAllocated(currentBuffer); // if this fails with clean block, then requested size is too big if (!currentBuffer.TryGetSegment(size, out segment)) { string errmsg = string.Format(CultureInfo.InvariantCulture, "Message size is too big. MessageSize: {0}", size); throw new ArgumentOutOfRangeException(nameof(queueMessage), errmsg); } } // encode namespace, offset, partitionkey, properties and payload into segment int writeOffset = 0; SegmentBuilder.Append(segment, ref writeOffset, queueMessage.Payload); return segment; } private static StreamPosition GetStreamPosition(MemoryMessageData queueMessage) { return new StreamPosition(queueMessage.StreamId, new EventSequenceTokenV2(queueMessage.SequenceNumber)); } private class Cursor : IQueueCacheCursor { private readonly PooledQueueCache cache; private readonly object cursor; private IBatchContainer current; public Cursor(PooledQueueCache cache, StreamId streamId, StreamSequenceToken token) { this.cache = cache; cursor = cache.GetCursor(streamId, token); } public void Dispose() { } public IBatchContainer GetCurrent(out Exception exception) { exception = null; return current; } public bool MoveNext() { IBatchContainer next; if (!cache.TryGetNextMessage(cursor, out next)) { return false; } current = next; return true; } public void Refresh(StreamSequenceToken token) { } public void RecordDeliveryFailure() { } } /// public int GetMaxAddCount() { return 100; } /// public void AddToCache(IList messages) { DateTime utcNow = DateTime.UtcNow; List memoryMessages = messages .Cast>() .Select(container => container.MessageData) .Select(batch => QueueMessageToCachedMessage(batch, utcNow)) .ToList(); cache.Add(memoryMessages, DateTime.UtcNow); } /// public bool TryPurgeFromCache(out IList purgedItems) { purgedItems = null; this.evictionStrategy.PerformPurge(DateTime.UtcNow); return false; } /// public IQueueCacheCursor GetCacheCursor(StreamId streamId, StreamSequenceToken token) { return new Cursor(cache, streamId, token); } /// public bool IsUnderPressure() { return false; } /// public IBatchContainer GetBatchContainer(ref CachedMessage cachedMessage) { //Deserialize payload int readOffset = 0; ArraySegment payload = SegmentBuilder.ReadNextBytes(cachedMessage.Segment, ref readOffset); MemoryMessageData message = MemoryMessageData.Create(cachedMessage.StreamId, new ArraySegment(payload.ToArray())); message.SequenceNumber = cachedMessage.SequenceNumber; return new MemoryBatchContainer(message, this.serializer); } /// public StreamSequenceToken GetSequenceToken(ref CachedMessage cachedMessage) { return new EventSequenceToken(cachedMessage.SequenceNumber); } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryStreamBuilder.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Providers; namespace Orleans.Hosting { /// /// Configuration builder for memory streams. /// public interface IMemoryStreamConfigurator : INamedServiceConfigurator { } /// /// Configuration extensions for memory streams. /// public static class MemoryStreamConfiguratorExtensions { /// /// Configures partitioning for memory streams. /// /// The configuration builder. /// The number of queues. public static void ConfigurePartitioning(this IMemoryStreamConfigurator configurator, int numOfQueues = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { configurator.Configure(ob => ob.Configure(options => options.TotalQueueCount = numOfQueues)); } } /// /// Silo-specific configuration builder for memory streams. /// public interface ISiloMemoryStreamConfigurator : IMemoryStreamConfigurator, ISiloRecoverableStreamConfigurator { } /// /// Configures memory streams. /// /// The message body serializer type, which must implement . public class SiloMemoryStreamConfigurator : SiloRecoverableStreamConfigurator, ISiloMemoryStreamConfigurator where TSerializer : class, IMemoryMessageBodySerializer { /// /// Initializes a new instance of the class. /// /// The stream provider name. /// The services configuration delegate. public SiloMemoryStreamConfigurator( string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, MemoryAdapterFactory.Create) { this.ConfigureDelegate(services => services.ConfigureNamedOptionForLogging(name)); } } /// /// Client-specific configuration builder for memory streams. /// public interface IClusterClientMemoryStreamConfigurator : IMemoryStreamConfigurator, IClusterClientPersistentStreamConfigurator { } /// /// Configures memory streams. /// /// The message body serializer type, which must implement . public class ClusterClientMemoryStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientMemoryStreamConfigurator where TSerializer : class, IMemoryMessageBodySerializer { /// /// Initializes a new instance of the class. /// /// The stream provider name. /// The builder. public ClusterClientMemoryStreamConfigurator(string name, IClientBuilder builder) : base(name, builder, MemoryAdapterFactory.Create) { } } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryStreamProviderBuilder.cs ================================================ using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Providers; [assembly: RegisterProvider("Memory", "Streaming", "Client", typeof(MemoryStreamProviderBuilder))] [assembly: RegisterProvider("Memory", "Streaming", "Silo", typeof(MemoryStreamProviderBuilder))] namespace Orleans.Providers; internal sealed class MemoryStreamProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddMemoryStreams(name); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddMemoryStreams(name); } } ================================================ FILE: src/Orleans.Streaming/MemoryStreams/MemoryStreamQueueGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Providers { /// /// Memory stream queue grain. This grain works as a storage queue of event data. Enqueue and Dequeue operations are supported. /// the max event count sets the max storage limit to the queue. /// public class MemoryStreamQueueGrain : Grain, IMemoryStreamQueueGrain, IGrainMigrationParticipant { private Queue _eventQueue = new Queue(); private long sequenceNumber = DateTime.UtcNow.Ticks; /// /// The maximum event count. /// private const int MaxEventCount = 16384; /// /// Enqueues an event data. If the current total count reaches the max limit. throws an exception. /// /// /// public Task Enqueue(MemoryMessageData data) { if (_eventQueue.Count >= MaxEventCount) { throw new InvalidOperationException($"Can not enqueue since the count has reached its maximum of {MaxEventCount}"); } data.SequenceNumber = sequenceNumber++; _eventQueue.Enqueue(data); return Task.CompletedTask; } /// /// Dequeues up to a max amount of maxCount event data from the queue. /// /// /// public Task> Dequeue(int maxCount) { List list = new List(); for (int i = 0; i < maxCount && _eventQueue.Count > 0; ++i) { list.Add(_eventQueue.Dequeue()); } return Task.FromResult(list); } void IGrainMigrationParticipant.OnDehydrate(IDehydrationContext dehydrationContext) { dehydrationContext.TryAddValue("queue", _eventQueue); } void IGrainMigrationParticipant.OnRehydrate(IRehydrationContext rehydrationContext) { if (rehydrationContext.TryGetValue("queue", out Queue value)) { _eventQueue = value; } } } } ================================================ FILE: src/Orleans.Streaming/Orleans.Streaming.csproj ================================================ Microsoft.Orleans.Streaming Microsoft Orleans Streaming Library Streaming library for Microsoft Orleans used both on the client and server. $(DefaultTargetFrameworks) true false ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IDeploymentConfiguration.cs ================================================ using System.Collections.Generic; namespace Orleans.Streams { /// /// Interface for accessing the deployment configuration. /// public interface IDeploymentConfiguration { /// /// Get the silo instance names for all configured silos. /// /// The list of silo names. IList GetAllSiloNames(); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IPersistentStreamPullingAgent.cs ================================================ using System.Threading.Tasks; using Orleans.Providers.Streams.Common; namespace Orleans.Streams { internal interface IPersistentStreamPullingAgent : ISystemTarget, IStreamProducerExtension { Task Initialize(); Task Shutdown(); } internal interface IPersistentStreamPullingManager : ISystemTarget { Task Initialize(); Task Stop(); Task StartAgents(); Task StopAgents(); Task ExecuteCommand(PersistentStreamProviderCommand command, object arg); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IQueueDataAdapter.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { /// /// Converts event data to queue message /// public interface IQueueDataAdapter { /// /// Creates a cloud queue message from stream event data. /// /// The stream event type. /// The stream identifier. /// The events. /// The token. /// The request context. /// A new queue message. TQueueMessage ToQueueMessage(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext); } /// /// Converts event data to and from queue message /// /// The type of the queue message. /// The type of the message batch. public interface IQueueDataAdapter : IQueueDataAdapter { /// /// Creates a batch container from a cloud queue message /// /// The queue message. /// The sequence identifier. /// The message batch. TMessageBatch FromQueueMessage(TQueueMessage queueMessage, long sequenceId); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IStreamFailureHandler.cs ================================================ using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// Functionality for handling stream failures. /// public interface IStreamFailureHandler { /// /// Gets a value indicating whether the subscription should fault when there is an error. /// /// if the subscription should fault when there is an error; otherwise, . bool ShouldFaultSubsriptionOnError { get; } /// /// Called once all measures to deliver an event to a consumer have been exhausted. /// /// The subscription identifier. /// Name of the stream provider. /// The stream identity. /// The sequence token. /// A representing the operation. Task OnDeliveryFailure(GuidId subscriptionId, string streamProviderName, StreamId streamIdentity, StreamSequenceToken sequenceToken); /// /// Should be called when establishing a subscription failed. /// /// The subscription identifier. /// Name of the stream provider. /// The stream identity. /// The sequence token. /// A representing the operation. Task OnSubscriptionFailure(GuidId subscriptionId, string streamProviderName, StreamId streamIdentity, StreamSequenceToken sequenceToken); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IStreamQueueBalancer.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// The stream queue balancer is responsible for load balancing queues across all other related queue balancers. It /// notifies any listeners (IStreamQueueBalanceListener) of changes to the distribution of queues. /// Method GetMyQueues, SubscribeToQueueDistributionChangeEvents, and UnSubscribeFromQueueDistributionChangeEvents will /// likely be called in the IStreamQueueBalanceListener's thread so they need to be thread safe /// public interface IStreamQueueBalancer { /// /// Initializes this instance. /// /// The queue mapper. /// A representing the operation. Task Initialize(IStreamQueueMapper queueMapper); /// /// Shutdown the queue balancer. /// /// A representing the operation. Task Shutdown(); /// /// Retrieves the latest queue distribution for this balancer. /// /// Queue allocated to this balancer. IEnumerable GetMyQueues(); /// /// Subscribes to receive queue distribution change notifications /// /// An observer interface to receive queue distribution change notifications. /// A value indicating whether subscription succeeded or not. bool SubscribeToQueueDistributionChangeEvents(IStreamQueueBalanceListener observer); /// /// Unsubscribes from receiving queue distribution notifications. /// /// An observer interface to receive queue distribution change notifications. /// A value indicating whether teh unsubscription succeeded or not bool UnSubscribeFromQueueDistributionChangeEvents(IStreamQueueBalanceListener observer); } /// /// The stream queue balancer listener receives notifications from a stream queue balancer (IStreamQueueBalancer) /// indicating that the balance of queues has changed. /// It should be implemented by components interested in stream queue load balancing. /// When change notification is received, listener should request updated list of queues from the queue balancer. /// public interface IStreamQueueBalanceListener { /// /// Called when adapter queue responsibility changes. /// /// A representing the operation. Task QueueDistributionChangeNotification(); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/IStreamQueueCheckpointer.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Factory for creating instances. /// public interface IStreamQueueCheckpointerFactory { /// /// Creates a stream checkpointer for the specified partition. /// /// The partition. /// The stream checkpointer. Task> Create(string partition); } /// /// Functionality for checkpointing a stream. /// /// The checkpoint type. public interface IStreamQueueCheckpointer { /// /// Gets a value indicating whether a checkpoint exists. /// /// if checkpoint exists; otherwise, . bool CheckpointExists { get; } /// /// Loads the checkpoint. /// /// The checkpoint. Task Load(); /// /// Updates the checkpoint. /// /// The offset. /// The current UTC time. void Update(TCheckpoint offset, DateTime utcNow); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/NoOpStreamFailureHandler.cs ================================================ using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// which does nothing in response to failures. /// public class NoOpStreamDeliveryFailureHandler : IStreamFailureHandler { /// /// Initializes a new instance of the class. /// public NoOpStreamDeliveryFailureHandler() : this(false) { } /// /// Initializes a new instance of the class. /// /// The value used for . public NoOpStreamDeliveryFailureHandler(bool faultOnError) { ShouldFaultSubsriptionOnError = faultOnError; } /// public bool ShouldFaultSubsriptionOnError { get; } /// public Task OnDeliveryFailure(GuidId subscriptionId, string streamProviderName, StreamId streamId, StreamSequenceToken sequenceToken) { return Task.CompletedTask; } /// public Task OnSubscriptionFailure(GuidId subscriptionId, string streamProviderName, StreamId streamId, StreamSequenceToken sequenceToken) { return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/Options/PersistentStreamProviderOptions.cs ================================================ using System; using Orleans.Streams; namespace Orleans.Configuration { /// /// Options for managing stream system lifecycle. /// public class StreamLifecycleOptions { /// /// Identifies well-known points in the lifecycle of the streaming system. /// [Serializable] public enum RunState { /// /// Not running. /// None, /// /// Streaming has initialized. /// Initialized, /// /// The agents have started. /// AgentsStarted, /// /// The agents have stopped. /// AgentsStopped, } /// /// If set to , stream pulling agents will be started during initialization. /// public RunState StartupState { get; set; } = DEFAULT_STARTUP_STATE; public const RunState DEFAULT_STARTUP_STATE = RunState.AgentsStarted; /// /// Gets or sets the lifecycle stage at which to initialize the stream runtime. /// /// The initialization stage. public int InitStage { get; set; } = DEFAULT_INIT_STAGE; public const int DEFAULT_INIT_STAGE = ServiceLifecycleStage.ApplicationServices; /// /// Gets or sets the lifecycle stage at which to start the stream runtime. /// /// The startup stage. public int StartStage { get; set; } = DEFAULT_START_STAGE; public const int DEFAULT_START_STAGE = ServiceLifecycleStage.Active; } /// /// Options for configuring stream pub/sub. /// public class StreamPubSubOptions { /// /// Gets or sets the pub sub type. /// /// The type of the pub sub. public StreamPubSubType PubSubType { get; set; } = DEFAULT_STREAM_PUBSUB_TYPE; public const StreamPubSubType DEFAULT_STREAM_PUBSUB_TYPE = StreamPubSubType.ExplicitGrainBasedAndImplicit; } /// /// Options for stream pulling agents. /// public class StreamPullingAgentOptions { /// /// Gets or sets the size of each batch container batch. /// /// The size of each batch container batch. public int BatchContainerBatchSize { get; set; } = DEFAULT_BATCH_CONTAINER_BATCH_SIZE; /// /// The default batch container batch size. /// public static readonly int DEFAULT_BATCH_CONTAINER_BATCH_SIZE = 1; /// /// Gets or sets the period between polling for queue messages. /// public TimeSpan GetQueueMsgsTimerPeriod { get; set; } = DEFAULT_GET_QUEUE_MESSAGES_TIMER_PERIOD; /// /// The default period between polling for queue messages. /// public static readonly TimeSpan DEFAULT_GET_QUEUE_MESSAGES_TIMER_PERIOD = TimeSpan.FromMilliseconds(100); /// /// Gets or sets the queue initialization timeout. /// /// The queue initialization timeout. public TimeSpan InitQueueTimeout { get; set; } = DEFAULT_INIT_QUEUE_TIMEOUT; /// /// The default queue initialization timeout /// public static readonly TimeSpan DEFAULT_INIT_QUEUE_TIMEOUT = TimeSpan.FromSeconds(5); /// /// Gets or sets the maximum event delivery time. /// /// The maximum event delivery time. public TimeSpan MaxEventDeliveryTime { get; set; } = DEFAULT_MAX_EVENT_DELIVERY_TIME; /// /// The default maximum event delivery time. /// public static readonly TimeSpan DEFAULT_MAX_EVENT_DELIVERY_TIME = TimeSpan.FromMinutes(1); /// /// Gets or sets the stream inactivity period. /// /// The stream inactivity period. public TimeSpan StreamInactivityPeriod { get; set; } = DEFAULT_STREAM_INACTIVITY_PERIOD; /// /// The default stream inactivity period. /// public static readonly TimeSpan DEFAULT_STREAM_INACTIVITY_PERIOD = TimeSpan.FromMinutes(30); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/PersistentStreamProducer.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Runtime; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using Orleans.Serialization; namespace Orleans.Streams { internal partial class PersistentStreamProducer : IInternalAsyncBatchObserver { private readonly StreamImpl stream; private readonly IQueueAdapter queueAdapter; private readonly DeepCopier deepCopier; internal bool IsRewindable { get; private set; } internal PersistentStreamProducer(StreamImpl stream, IStreamProviderRuntime providerUtilities, IQueueAdapter queueAdapter, bool isRewindable, DeepCopier deepCopier) { this.stream = stream; this.queueAdapter = queueAdapter; this.deepCopier = deepCopier; IsRewindable = isRewindable; var logger = providerUtilities.ServiceProvider.GetRequiredService>>(); LogCreatedPersistentStreamProducer(logger, stream, typeof(T), this.queueAdapter.Name); } public Task OnNextAsync(T item, StreamSequenceToken token) { return this.queueAdapter.QueueMessageAsync(this.stream.StreamId, item, token, RequestContextExtensions.Export(this.deepCopier)); } public Task OnNextBatchAsync(IEnumerable batch, StreamSequenceToken token) { return this.queueAdapter.QueueMessageBatchAsync(this.stream.StreamId, batch, token, RequestContextExtensions.Export(this.deepCopier)); } public Task OnCompletedAsync() { // Maybe send a close message to the rendezvous? throw new NotImplementedException("OnCompletedAsync is not implemented for now."); } public Task OnErrorAsync(Exception ex) { // Maybe send a close message to the rendezvous? throw new NotImplementedException("OnErrorAsync is not implemented for now."); } public Task Cleanup() { return Task.CompletedTask; } [LoggerMessage( Level = LogLevel.Debug, Message = "Created PersistentStreamProducer for stream {StreamId}, of type {ElementType}, and with Adapter: {QueueAdapterName}." )] private static partial void LogCreatedPersistentStreamProducer(ILogger logger, StreamImpl streamId, Type elementType, string queueAdapterName); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/PersistentStreamProvider.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Streams; using Orleans.Streams.Core; namespace Orleans.Providers.Streams.Common { /// /// Commands which can be handled by the . /// /// [Serializable] public enum PersistentStreamProviderCommand { None, /// /// Starts the agents. /// StartAgents, /// /// Stops the agents. /// StopAgents, /// /// Retrieves agent state. /// GetAgentsState, /// /// Gets the number of running agents. /// GetNumberRunningAgents, /// /// The command start range for custom adapters. /// AdapterCommandStartRange = 10000, /// /// The command end range for custom adapters. /// AdapterCommandEndRange = AdapterCommandStartRange + 9999, /// /// The command start range for custom adapter factories. /// AdapterFactoryCommandStartRange = AdapterCommandEndRange + 1, /// /// The command end range for custom adapter factories. /// AdapterFactoryCommandEndRange = AdapterFactoryCommandStartRange + 9999, } /// /// Persistent stream provider that uses an adapter for persistence /// public partial class PersistentStreamProvider : IStreamProvider, IInternalStreamProvider, IControllable, IStreamSubscriptionManagerRetriever, ILifecycleParticipant { private readonly ILogger logger; private readonly IStreamProviderRuntime runtime; private readonly DeepCopier deepCopier; private readonly IRuntimeClient runtimeClient; private readonly ProviderStateManager stateManager = new ProviderStateManager(); private IQueueAdapterFactory adapterFactory; private IQueueAdapter queueAdapter; private IPersistentStreamPullingManager pullingAgentManager; private IStreamSubscriptionManager streamSubscriptionManager; private readonly StreamPubSubOptions pubsubOptions; private readonly StreamLifecycleOptions lifeCycleOptions; public string Name { get; private set; } public bool IsRewindable { get { return queueAdapter.IsRewindable; } } public PersistentStreamProvider( string name, StreamPubSubOptions pubsubOptions, StreamLifecycleOptions lifeCycleOptions, IProviderRuntime runtime, DeepCopier deepCopier, ILogger logger) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); if (runtime == null) throw new ArgumentNullException(nameof(runtime)); this.pubsubOptions = pubsubOptions ?? throw new ArgumentNullException(nameof(pubsubOptions)); this.Name = name; this.lifeCycleOptions = lifeCycleOptions ?? throw new ArgumentNullException(nameof(lifeCycleOptions)); this.runtime = runtime.ServiceProvider.GetRequiredService(); this.runtimeClient = runtime.ServiceProvider.GetRequiredService(); this.deepCopier = deepCopier ?? throw new ArgumentNullException(nameof(deepCopier)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } private async Task Init(CancellationToken token) { if(!this.stateManager.PresetState(ProviderState.Initialized)) return; this.adapterFactory = this.runtime.ServiceProvider.GetRequiredKeyedService(this.Name); this.queueAdapter = await adapterFactory.CreateAdapter(); if (this.pubsubOptions.PubSubType == StreamPubSubType.ExplicitGrainBasedAndImplicit || this.pubsubOptions.PubSubType == StreamPubSubType.ExplicitGrainBasedOnly) { this.streamSubscriptionManager = this.runtime.ServiceProvider .GetService().GetStreamSubscriptionManager(StreamSubscriptionManagerType.ExplicitSubscribeOnly); } this.stateManager.CommitState(); } private async Task Start(CancellationToken token) { if (!this.stateManager.PresetState(ProviderState.Started)) return; if (this.queueAdapter.Direction.Equals(StreamProviderDirection.ReadOnly) || this.queueAdapter.Direction.Equals(StreamProviderDirection.ReadWrite)) { var siloRuntime = this.runtime as ISiloSideStreamProviderRuntime; if (siloRuntime != null) { this.pullingAgentManager = await siloRuntime.InitializePullingAgents(this.Name, this.adapterFactory, this.queueAdapter); // TODO: No support yet for DeliveryDisabled, only Stopped and Started if (this.lifeCycleOptions.StartupState == StreamLifecycleOptions.RunState.AgentsStarted) await pullingAgentManager.StartAgents(); } } stateManager.CommitState(); } public IStreamSubscriptionManager GetStreamSubscriptionManager() { return this.streamSubscriptionManager; } private async Task Close(CancellationToken token) { if (!stateManager.PresetState(ProviderState.Closed)) return; var manager = this.pullingAgentManager; if (manager != null) { await manager.Stop(); } stateManager.CommitState(); } public IAsyncStream GetStream(StreamId streamId) { var id = new QualifiedStreamId(Name, streamId); return this.runtime.GetStreamDirectory().GetOrAddStream( id, () => new StreamImpl(id, this, IsRewindable, this.runtimeClient)); } IInternalAsyncBatchObserver IInternalStreamProvider.GetProducerInterface(IAsyncStream stream) { if (queueAdapter.Direction == StreamProviderDirection.ReadOnly) { throw new InvalidOperationException($"Stream provider {queueAdapter.Name} is ReadOnly."); } return new PersistentStreamProducer((StreamImpl)stream, this.runtime, queueAdapter, IsRewindable, this.deepCopier); } IInternalAsyncObservable IInternalStreamProvider.GetConsumerInterface(IAsyncStream streamId) { return GetConsumerInterfaceImpl(streamId); } private IInternalAsyncObservable GetConsumerInterfaceImpl(IAsyncStream stream) { return new StreamConsumer((StreamImpl)stream, Name, this.runtime, this.runtime.PubSub(this.pubsubOptions.PubSubType), this.logger, IsRewindable); } public Task ExecuteCommand(int command, object arg) { if (command >= (int)PersistentStreamProviderCommand.AdapterCommandStartRange && command <= (int)PersistentStreamProviderCommand.AdapterCommandEndRange && queueAdapter is IControllable) { return ((IControllable)queueAdapter).ExecuteCommand(command, arg); } if (command >= (int)PersistentStreamProviderCommand.AdapterFactoryCommandStartRange && command <= (int)PersistentStreamProviderCommand.AdapterFactoryCommandEndRange && adapterFactory is IControllable) { return ((IControllable)adapterFactory).ExecuteCommand(command, arg); } if (pullingAgentManager != null) { return pullingAgentManager.ExecuteCommand((PersistentStreamProviderCommand)command, arg); } LogWarningGotCommand( (PersistentStreamProviderCommand)command, arg); throw new ArgumentException("PullingAgentManager is not initialized yet."); } public void Participate(ILifecycleObservable lifecycle) { lifecycle.Subscribe(OptionFormattingUtilities.Name(this.Name), this.lifeCycleOptions.InitStage, Init); lifecycle.Subscribe(OptionFormattingUtilities.Name(this.Name), this.lifeCycleOptions.StartStage, Start, Close); } public static IStreamProvider Create(IServiceProvider services, string name) { var pubsubOptions = services.GetRequiredService>().Get(name); var initOptions = services.GetRequiredService>().Get(name); return ActivatorUtilities.CreateInstance(services, name, pubsubOptions, initOptions); } public static ILifecycleParticipant ParticipateIn(IServiceProvider serviceProvider, string name) where TLifecycle : ILifecycleObservable { var provider = (PersistentStreamProvider)serviceProvider.GetRequiredKeyedService(name); return provider.ParticipateIn(); } [LoggerMessage( Level = LogLevel.Warning, Message = "Got command {Command} with arg {Argument}, but PullingAgentManager is not initialized yet. Ignoring the command." )] private partial void LogWarningGotCommand(PersistentStreamProviderCommand command, object argument); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/PersistentStreamPullingAgent.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Internal; using Orleans.Runtime; using Orleans.Runtime.Internal; using Orleans.Streams.Filtering; namespace Orleans.Streams { internal sealed partial class PersistentStreamPullingAgent : SystemTarget, IPersistentStreamPullingAgent { private const int ReadLoopRetryMax = 6; private const int StreamInactivityCheckFrequency = 10; private readonly IBackoffProvider deliveryBackoffProvider; private readonly IBackoffProvider queueReaderBackoffProvider; private readonly string streamProviderName; private readonly IStreamPubSub pubSub; private readonly IStreamFilter streamFilter; private readonly Dictionary pubSubCache; private readonly StreamPullingAgentOptions options; private readonly ILogger logger; private readonly IQueueAdapterCache queueAdapterCache; private readonly IQueueAdapter queueAdapter; private readonly IStreamFailureHandler streamFailureHandler; internal readonly QueueId QueueId; private int numMessages; private IQueueCache queueCache; private IQueueAdapterReceiver receiver; private DateTime lastTimeCleanedPubSubCache; private IGrainTimer timer; private Task receiverInitTask; private bool IsShutdown => timer == null; private string StatisticUniquePostfix => $"{streamProviderName}.{QueueId}"; internal PersistentStreamPullingAgent( SystemTargetGrainId id, string strProviderName, IStreamPubSub streamPubSub, IStreamFilter streamFilter, QueueId queueId, StreamPullingAgentOptions options, IQueueAdapter queueAdapter, IQueueAdapterCache queueAdapterCache, IStreamFailureHandler streamFailureHandler, IBackoffProvider deliveryBackoffProvider, IBackoffProvider queueReaderBackoffProvider, SystemTargetShared shared) : base(id, shared) { if (strProviderName == null) throw new ArgumentNullException("runtime", "PersistentStreamPullingAgent: strProviderName should not be null"); QueueId = queueId; streamProviderName = strProviderName; pubSub = streamPubSub; this.streamFilter = streamFilter; pubSubCache = new Dictionary(); this.options = options; this.queueAdapter = queueAdapter ?? throw new ArgumentNullException(nameof(queueAdapter)); this.streamFailureHandler = streamFailureHandler ?? throw new ArgumentNullException(nameof(streamFailureHandler)); this.queueAdapterCache = queueAdapterCache; this.deliveryBackoffProvider = deliveryBackoffProvider; this.queueReaderBackoffProvider = queueReaderBackoffProvider; numMessages = 0; logger = shared.LoggerFactory.CreateLogger($"{this.GetType().Namespace}.{streamProviderName}"); LogInfoCreated(GetType().Name, GrainId, strProviderName, Silo, new(QueueId)); shared.ActivationDirectory.RecordNewTarget(this); } /// /// Take responsibility for a new queues that was assigned to me via a new range. /// We first store the new queue in our internal data structure, try to initialize it and start a pumping timer. /// ERROR HANDLING: /// The responsibility to handle initialization and shutdown failures is inside the INewQueueAdapterReceiver code. /// The agent will call Initialize once and log an error. It will not call initialize again. /// The receiver itself may attempt later to recover from this error and do initialization again. /// The agent will assume initialization has succeeded and will subsequently start calling pumping receive. /// Same applies to shutdown. /// /// public Task Initialize() { LogInfoInit(GetType().Name, GrainId, Silo, new(QueueId)); lastTimeCleanedPubSubCache = DateTime.UtcNow; try { if (queueAdapterCache != null) { using var _ = new ExecutionContextSuppressor(); queueCache = queueAdapterCache.CreateQueueCache(QueueId); } } catch (Exception exc) { LogErrorCreatingQueueCache(exc); throw; } try { using var _ = new ExecutionContextSuppressor(); receiver = queueAdapter.CreateReceiver(QueueId); } catch (Exception exc) { LogErrorCreatingReceiver(exc); throw; } try { using var _ = new ExecutionContextSuppressor(); receiverInitTask = OrleansTaskExtentions.SafeExecute(() => receiver.Initialize(this.options.InitQueueTimeout)) .LogException(logger, ErrorCode.PersistentStreamPullingAgent_03, $"QueueAdapterReceiver {QueueId:H} failed to Initialize."); receiverInitTask.Ignore(); } catch (Exception exception) { LogErrorReceiverInit(new(QueueId), exception); // Just ignore this exception and proceed as if Initialize has succeeded. // We already logged individual exceptions for individual calls to Initialize. No need to log again. } // Setup a reader for a new receiver. // Even if the receiver failed to initialize, treat it as OK and start pumping it. It's receiver responsibility to retry initialization. var randomTimerOffset = RandomTimeSpan.Next(this.options.GetQueueMsgsTimerPeriod); timer = RegisterGrainTimer(AsyncTimerCallback, QueueId, randomTimerOffset, this.options.GetQueueMsgsTimerPeriod); StreamInstruments.RegisterPersistentStreamPubSubCacheSizeObserve(() => new Measurement(pubSubCache.Count, new KeyValuePair("name", StatisticUniquePostfix))); LogInfoTakingQueue(new(QueueId)); return Task.CompletedTask; } public async Task Shutdown() { // Stop pulling from queues that are not in my range anymore. LogInfoShutdown(GetType().Name, new(QueueId)); var asyncTimer = timer; timer = null; asyncTimer.Dispose(); this.queueCache = null; Task localReceiverInitTask = receiverInitTask; if (localReceiverInitTask != null) { await localReceiverInitTask.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); receiverInitTask = null; } try { IQueueAdapterReceiver localReceiver = this.receiver; this.receiver = null; if (localReceiver != null) { var task = OrleansTaskExtentions.SafeExecute(() => localReceiver.Shutdown(this.options.InitQueueTimeout)); task = task.LogException(logger, ErrorCode.PersistentStreamPullingAgent_07, $"QueueAdapterReceiver {QueueId} failed to Shutdown."); await task; } } catch { // Just ignore this exception and proceed as if Shutdown has succeeded. // We already logged individual exceptions for individual calls to Shutdown. No need to log again. } var unregisterTasks = new List(); foreach (var tuple in pubSubCache) { tuple.Value.DisposeAll(logger); var streamId = tuple.Key; LogInfoUnregisterProducer(streamId); unregisterTasks.Add(pubSub.UnregisterProducer(streamId, GrainId)); } try { await Task.WhenAll(unregisterTasks); } catch (Exception exc) { LogWarningUnregisterProducer(exc); } pubSubCache.Clear(); } public Task AddSubscriber( GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData) { LogDebugAddSubscriber(streamId, streamConsumer); // cannot await here because explicit consumers trigger this call, so it could cause a deadlock. AddSubscriber_Impl(subscriptionId, streamId, streamConsumer, filterData, null) .LogException(logger, ErrorCode.PersistentStreamPullingAgent_26, $"Failed to add subscription for stream {streamId}.") .Ignore(); return Task.CompletedTask; } // Called by rendezvous when new remote subscriber subscribes to this stream. private async Task AddSubscriber_Impl( GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData, StreamSequenceToken cacheToken) { if (IsShutdown) return; StreamConsumerCollection streamDataCollection; if (!pubSubCache.TryGetValue(streamId, out streamDataCollection)) { // If stream is not in pubsub cache, then we've received no events on this stream, and will aquire the subscriptions from pubsub when we do. return; } StreamConsumerData data; if (!streamDataCollection.TryGetConsumer(subscriptionId, out data)) { var consumerReference = this.RuntimeClient.InternalGrainFactory .GetGrain(streamConsumer) .AsReference(); data = streamDataCollection.AddConsumer(subscriptionId, streamId, consumerReference, filterData); } if (await DoHandshakeWithConsumer(data, cacheToken)) { data.PendingStartToken = null; data.IsRegistered = true; if (data.State == StreamConsumerDataState.Inactive) RunConsumerCursor(data).Ignore(); // Start delivering events if not actively doing so } } private async Task DoHandshakeWithConsumer( StreamConsumerData consumerData, StreamSequenceToken cacheToken) { StreamHandshakeToken requestedHandshakeToken = null; // if not cache, then we can't get cursor and there is no reason to ask consumer for token. if (queueCache != null) { Exception exceptionOccured = null; try { requestedHandshakeToken = await AsyncExecutorWithRetries.ExecuteWithRetries( i => consumerData.StreamConsumer.GetSequenceToken(consumerData.SubscriptionId), AsyncExecutorWithRetries.INFINITE_RETRIES, // Do not retry if the agent is shutting down, or if the exception is ClientNotAvailableException (exception, i) => exception is not ClientNotAvailableException && !IsShutdown, this.options.MaxEventDeliveryTime, deliveryBackoffProvider); var requestedToken = requestedHandshakeToken?.Token; if (requestedToken != null) { consumerData.SafeDisposeCursor(logger); consumerData.Cursor = queueCache.GetCacheCursor(consumerData.StreamId, requestedToken); } else { var registrationToken = cacheToken ?? consumerData.PendingStartToken; if (consumerData.Cursor == null) // if the consumer did not ask for a specific token and we already have a cursor, just keep using it. consumerData.Cursor = queueCache.GetCacheCursor(consumerData.StreamId, registrationToken); } } catch (Exception exception) { exceptionOccured = exception; } if (exceptionOccured != null) { // If we are shutting down, ignore the error if (IsShutdown) return false; bool faultedSubscription = await ErrorProtocol(consumerData, exceptionOccured, false, null, requestedHandshakeToken?.Token); if (faultedSubscription) return false; } } consumerData.LastToken = requestedHandshakeToken; // use what ever the consumer asked for as LastToken for next handshake (even if he asked for null). // if we don't yet have a cursor (had errors in the handshake or data not available exc), get a cursor at the event that triggered that consumer subscription. if (consumerData.Cursor == null && queueCache != null) { try { var registrationToken = cacheToken ?? consumerData.PendingStartToken; consumerData.Cursor = queueCache.GetCacheCursor(consumerData.StreamId, registrationToken); } catch (Exception) { consumerData.Cursor = queueCache.GetCacheCursor(consumerData.StreamId, null); // just in case last GetCacheCursor failed. } } return true; } public Task RemoveSubscriber(GuidId subscriptionId, QualifiedStreamId streamId) { RemoveSubscriber_Impl(subscriptionId, streamId); return Task.CompletedTask; } public void RemoveSubscriber_Impl(GuidId subscriptionId, QualifiedStreamId streamId) { if (IsShutdown) return; StreamConsumerCollection streamData; if (!pubSubCache.TryGetValue(streamId, out streamData)) return; // remove consumer bool removed = streamData.RemoveConsumer(subscriptionId, logger); if (removed) LogDebugRemovedConsumer(subscriptionId, streamId); if (streamData.Count == 0) pubSubCache.Remove(streamId); } private Task AsyncTimerCallback(QueueId queueId, CancellationToken cancellationToken) { using var _ = new ExecutionContextSuppressor(); return PumpQueue(queueId, cancellationToken); } private async Task PumpQueue(QueueId queueId, CancellationToken cancellationToken) { try { Task localReceiverInitTask = receiverInitTask; if (localReceiverInitTask != null) { await localReceiverInitTask; receiverInitTask = null; } if (IsShutdown || cancellationToken.IsCancellationRequested) return; // timer was already removed, last tick // loop through the queue until it is empty. while (!IsShutdown && !cancellationToken.IsCancellationRequested) // timer will be set to null when we are asked to shutdown. { int maxCacheAddCount = queueCache?.GetMaxAddCount() ?? QueueAdapterConstants.UNLIMITED_GET_QUEUE_MSG; if (maxCacheAddCount != QueueAdapterConstants.UNLIMITED_GET_QUEUE_MSG && maxCacheAddCount <= 0) return; // If read succeeds and there is more data, we continue reading. // If read succeeds and there is no more data, we break out of loop // If read fails, we retry 6 more times, with backoff policy. // we log each failure as warnings. After 6 times retry if still fail, we break out of loop and log an error bool moreData = await AsyncExecutorWithRetries.ExecuteWithRetries( i => ReadFromQueue(queueId, receiver, maxCacheAddCount), ReadLoopRetryMax, ReadLoopRetryExceptionFilter, Timeout.InfiniteTimeSpan, queueReaderBackoffProvider, cancellationToken: cancellationToken); if (!moreData) return; } } catch (Exception exc) { receiverInitTask = null; LogErrorGivingUpReading(new(queueId), ReadLoopRetryMax, exc); } bool ReadLoopRetryExceptionFilter(Exception e, int retryCounter) { LogErrorRetrying(retryCounter, new(queueId), e); return !cancellationToken.IsCancellationRequested && !IsShutdown; } } /// /// Read from queue. /// Returns true, if data was read, false if it was not /// /// /// /// /// private async Task ReadFromQueue(QueueId myQueueId, IQueueAdapterReceiver rcvr, int maxCacheAddCount) { if (rcvr == null) { return false; } var now = DateTime.UtcNow; // Try to cleanup the pubsub cache at the cadence of 10 times in the configurable StreamInactivityPeriod. if ((now - lastTimeCleanedPubSubCache) >= this.options.StreamInactivityPeriod.Divide(StreamInactivityCheckFrequency)) { lastTimeCleanedPubSubCache = now; CleanupPubSubCache(now); } if (queueCache != null) { IList purgedItems; if (queueCache.TryPurgeFromCache(out purgedItems)) { try { await rcvr.MessagesDeliveredAsync(purgedItems); } catch (Exception exc) { LogWarningMessagesDeliveredAsync(new(myQueueId), exc); } } } if (queueCache != null && queueCache.IsUnderPressure()) { // Under back pressure. Exit the loop. Will attempt again in the next timer callback. LogInfoStreamCacheUnderPressure(); return false; } // Retrieve one multiBatch from the queue. Every multiBatch has an IEnumerable of IBatchContainers, each IBatchContainer may have multiple events. IList multiBatch = await rcvr.GetQueueMessagesAsync(maxCacheAddCount); if (multiBatch == null || multiBatch.Count == 0) return false; // queue is empty. Exit the loop. Will attempt again in the next timer callback. queueCache?.AddToCache(multiBatch); numMessages += multiBatch.Count; StreamInstruments.PersistentStreamReadMessages.Add(multiBatch.Count); LogTraceGotMessages(multiBatch.Count, new(myQueueId), numMessages); foreach (var group in multiBatch .Where(m => m != null) .GroupBy(container => container.StreamId)) { var streamId = new QualifiedStreamId(queueAdapter.Name, group.Key); StreamSequenceToken startToken = group.First().SequenceToken; StreamConsumerCollection streamData; if (pubSubCache.TryGetValue(streamId, out streamData)) { streamData.RefreshActivity(now); StartInactiveCursors(streamData, startToken); } else { RegisterStream(streamId, startToken, now).Ignore(); // if this is a new stream register as producer of stream in pub sub system } } return true; } private void CleanupPubSubCache(DateTime now) { foreach (var tuple in pubSubCache) { if (tuple.Value.IsInactive(now, options.StreamInactivityPeriod)) { pubSubCache.Remove(tuple.Key); tuple.Value.DisposeAll(logger); } } } private async Task RegisterStream(QualifiedStreamId streamId, StreamSequenceToken firstToken, DateTime now) { var streamData = new StreamConsumerCollection(now); pubSubCache.Add(streamId, streamData); // Create a fake cursor to point into a cache. // That way we will not purge the event from the cache, until we talk to pub sub. // This will help ensure the "casual consistency" between pre-existing subscripton (of a potentially new already subscribed consumer) // and later production. var pinCursor = queueCache?.GetCacheCursor(streamId, firstToken); try { await RegisterAsStreamProducer(streamId, firstToken); streamData.StreamRegistered = true; } finally { // Cleanup the fake pinning cursor. pinCursor?.Dispose(); } } private void StartInactiveCursors(StreamConsumerCollection streamData, StreamSequenceToken startToken) { foreach (StreamConsumerData consumerData in streamData.AllConsumers()) { // Some consumer might not be fully registered yet if (consumerData.IsRegistered) { consumerData.Cursor?.Refresh(startToken); if (consumerData.State == StreamConsumerDataState.Inactive) { // wake up inactive consumers RunConsumerCursor(consumerData).Ignore(); } } else { if (consumerData.PendingStartToken is null || startToken.Older(consumerData.PendingStartToken)) { consumerData.PendingStartToken = startToken; } LogDebugPulledNewMessages(consumerData.StreamId); } } } private async Task RunConsumerCursor(StreamConsumerData consumerData) { try { // double check in case of interleaving if (consumerData.State == StreamConsumerDataState.Active || consumerData.Cursor == null) return; consumerData.State = StreamConsumerDataState.Active; while (consumerData.Cursor != null) { IBatchContainer batch = null; Exception exceptionOccured = null; try { batch = GetBatchForConsumer(consumerData.Cursor, consumerData.StreamId, consumerData.FilterData); if (batch == null) { break; } } catch (Exception exc) { exceptionOccured = exc; consumerData.SafeDisposeCursor(logger); consumerData.Cursor = queueCache.GetCacheCursor(consumerData.StreamId, null); } if (batch != null) { if (!ShouldDeliverBatch(consumerData.StreamId, batch, consumerData.FilterData)) continue; } try { StreamInstruments.PersistentStreamSentMessages.Add(1); if (batch != null) { StreamHandshakeToken newToken = await AsyncExecutorWithRetries.ExecuteWithRetries( i => DeliverBatchToConsumer(consumerData, batch), AsyncExecutorWithRetries.INFINITE_RETRIES, // Do not retry if the agent is shutting down, or if the exception is ClientNotAvailableException (exception, i) => exception is not ClientNotAvailableException && !IsShutdown, this.options.MaxEventDeliveryTime, deliveryBackoffProvider); if (newToken != null) { consumerData.LastToken = newToken; IQueueCacheCursor newCursor = queueCache.GetCacheCursor(consumerData.StreamId, newToken.Token); // The handshake token points to an already processed event, we need to advance the cursor to // the next event. newCursor.MoveNext(); consumerData.SafeDisposeCursor(logger); consumerData.Cursor = newCursor; } } } catch (Exception exc) { consumerData.Cursor?.RecordDeliveryFailure(); LogErrorDeliveringMessages(consumerData.StreamId, exc); exceptionOccured = exc is ClientNotAvailableException ? exc : new StreamEventDeliveryFailureException(consumerData.StreamId); } // if we failed to deliver a batch if (exceptionOccured != null) { bool faultedSubscription = await ErrorProtocol(consumerData, exceptionOccured, true, batch, batch?.SequenceToken); if (faultedSubscription) return; } } consumerData.State = StreamConsumerDataState.Inactive; } catch (Exception exc) { // RunConsumerCursor is fired with .Ignore so we should log if anything goes wrong, because there is no one to catch the exception LogErrorRunConsumerCursor(exc); consumerData.State = StreamConsumerDataState.Inactive; throw; } } private IBatchContainer GetBatchForConsumer(IQueueCacheCursor cursor, StreamId streamId, string filterData) { if (this.options.BatchContainerBatchSize <= 1) { if (!cursor.MoveNext()) { return null; } return cursor.GetCurrent(out _); } else if (this.options.BatchContainerBatchSize > 1) { int i = 0; var batchContainers = new List(); while (i < this.options.BatchContainerBatchSize) { if (!cursor.MoveNext()) { break; } var batchContainer = cursor.GetCurrent(out _); if (!ShouldDeliverBatch(streamId, batchContainer, filterData)) continue; batchContainers.Add(batchContainer); i++; } if (i == 0) { return null; } return new BatchContainerBatch(batchContainers); } return null; } private async Task DeliverBatchToConsumer(StreamConsumerData consumerData, IBatchContainer batch) { try { StreamHandshakeToken newToken = await ContextualizedDeliverBatchToConsumer(consumerData, batch); consumerData.LastToken = StreamHandshakeToken.CreateDeliveyToken(batch.SequenceToken); // this is the currently delivered token return newToken; } catch (Exception ex) { LogWarningFailedToDeliverMessage(consumerData.SubscriptionId, consumerData.StreamId, ex); throw; } } /// /// Add call context for batch delivery call, then clear context immediately, without giving up turn. /// private static Task ContextualizedDeliverBatchToConsumer(StreamConsumerData consumerData, IBatchContainer batch) { bool isRequestContextSet = batch.ImportRequestContext(); try { return consumerData.StreamConsumer.DeliverBatch(consumerData.SubscriptionId, consumerData.StreamId, batch, consumerData.LastToken); } finally { if (isRequestContextSet) { // clear RequestContext before await! RequestContext.Clear(); } } } private static async Task DeliverErrorToConsumer(StreamConsumerData consumerData, Exception exc, IBatchContainer batch) { Task errorDeliveryTask; bool isRequestContextSet = batch != null && batch.ImportRequestContext(); try { errorDeliveryTask = consumerData.StreamConsumer.ErrorInStream(consumerData.SubscriptionId, exc); } finally { if (isRequestContextSet) { RequestContext.Clear(); // clear RequestContext before await! } } await errorDeliveryTask; } private async Task ErrorProtocol(StreamConsumerData consumerData, Exception exceptionOccured, bool isDeliveryError, IBatchContainer batch, StreamSequenceToken token) { // for loss of client, we just remove the subscription if (exceptionOccured is ClientNotAvailableException) { LogWarningConsumerIsDead(consumerData.StreamConsumer, consumerData.StreamId); pubSub.UnregisterConsumer(consumerData.SubscriptionId, consumerData.StreamId).Ignore(); return true; } // notify consumer about the error or that the data is not available. await DeliverErrorToConsumer(consumerData, exceptionOccured, batch).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); // record that there was a delivery failure if (isDeliveryError) { await streamFailureHandler.OnDeliveryFailure( consumerData.SubscriptionId, streamProviderName, consumerData.StreamId, token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); } else { await streamFailureHandler.OnSubscriptionFailure( consumerData.SubscriptionId, streamProviderName, consumerData.StreamId, token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); } // if configured to fault on delivery failure and this is not an implicit subscription, fault and remove the subscription if (streamFailureHandler.ShouldFaultSubsriptionOnError && !SubscriptionMarker.IsImplicitSubscription(consumerData.SubscriptionId.Guid)) { try { // notify consumer of faulted subscription, if we can. await DeliverErrorToConsumer( consumerData, new FaultedSubscriptionException(consumerData.SubscriptionId, consumerData.StreamId), batch).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); // mark subscription as faulted. await pubSub.FaultSubscription(consumerData.StreamId, consumerData.SubscriptionId); } finally { // remove subscription RemoveSubscriber_Impl(consumerData.SubscriptionId, consumerData.StreamId); } return true; } return false; } private static async Task> PubsubRegisterProducer(IStreamPubSub pubSub, QualifiedStreamId streamId, GrainId meAsStreamProducer, ILogger logger) { try { var streamData = await pubSub.RegisterProducer(streamId, meAsStreamProducer); return streamData; } catch (Exception e) { LogErrorRegisterAsStreamProducer(logger, e); throw; } } private async Task RegisterAsStreamProducer(QualifiedStreamId streamId, StreamSequenceToken streamStartToken) { try { if (pubSub == null) throw new NullReferenceException("Found pubSub reference not set up correctly in RetrieveNewStream"); ISet streamData = null; await AsyncExecutorWithRetries.ExecuteWithRetries( async i => { streamData = await PubsubRegisterProducer(pubSub, streamId, GrainId, logger); }, AsyncExecutorWithRetries.INFINITE_RETRIES, (exception, i) => !IsShutdown, Timeout.InfiniteTimeSpan, deliveryBackoffProvider); LogDebugGotBackSubscribers(streamData.Count, streamId); var addSubscriptionTasks = new List(streamData.Count); foreach (PubSubSubscriptionState item in streamData) { addSubscriptionTasks.Add(AddSubscriber_Impl(item.SubscriptionId, item.Stream, item.Consumer, item.FilterData, streamStartToken)); } await Task.WhenAll(addSubscriptionTasks); } catch (Exception exc) { // RegisterAsStreamProducer is fired with .Ignore so we should log if anything goes wrong, because there is no one to catch the exception LogErrorIgnoredRegisterAsStreamProducer(exc); throw; } } private bool ShouldDeliverBatch(StreamId streamId, IBatchContainer batchContainer, string filterData) { if (this.streamFilter is NoOpStreamFilter) return true; try { foreach (var evt in batchContainer.GetEvents()) { if (this.streamFilter.ShouldDeliver(streamId, evt.Item1, filterData)) return true; } return false; } catch (Exception exc) { LogWarningFilterEvaluation(streamFilter.GetType().Name, filterData, streamId, exc); } return true; } private readonly struct QueueIdLogRecord(QueueId queueId) { public override string ToString() => queueId.ToStringWithHashCode(); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_01, Message = "Created {Name} {Id} for Stream Provider {StreamProvider} on silo {Silo} for Queue {Queue}." )] private partial void LogInfoCreated(string name, GrainId id, string streamProvider, SiloAddress silo, QueueIdLogRecord queue); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_02, Message = "Init of {Name} {Id} on silo {Silo} for queue {Queue}." )] private partial void LogInfoInit(string name, GrainId id, SiloAddress silo, QueueIdLogRecord queue); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_23, Message = "Exception while calling IQueueAdapterCache.CreateQueueCache." )] private partial void LogErrorCreatingQueueCache(Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_02, Message = "Exception while calling IQueueAdapter.CreateNewReceiver." )] private partial void LogErrorCreatingReceiver(Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_03, Message = "QueueAdapterReceiver {QueueId} failed to Initialize." )] private partial void LogErrorReceiverInit(QueueIdLogRecord queueId, Exception exception); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_04, Message = "Taking queue {Queue} under my responsibility." )] private partial void LogInfoTakingQueue(QueueIdLogRecord queue); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_05, Message = "Shutdown of {Name} responsible for queue: {Queue}" )] private partial void LogInfoShutdown(string name, QueueIdLogRecord queue); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_06, Message = "Unregister PersistentStreamPullingAgent Producer for stream {StreamId}." )] private partial void LogInfoUnregisterProducer(QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.PersistentStreamPullingAgent_07, Message = "Failed to unregister myself as stream producer to some streams that used to be in my responsibility." )] private partial void LogWarningUnregisterProducer(Exception exception); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.PersistentStreamPullingAgent_09, Message = "AddSubscriber: Stream={StreamId} Subscriber={SubscriberId}." )] private partial void LogDebugAddSubscriber(QualifiedStreamId streamId, GrainId subscriberId); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.PersistentStreamPullingAgent_10, Message = "Removed consumer: subscription {SubscriptionId}, for stream {StreamId}." )] private partial void LogDebugRemovedConsumer(GuidId subscriptionId, QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_12, Message = "Giving up reading from queue {QueueId} after retry attempts {ReadLoopRetryMax}" )] private partial void LogErrorGivingUpReading(QueueIdLogRecord queueId, int readLoopRetryMax, Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_12, Message = "Exception while retrying the {RetryCounter}th time reading from queue {QueueId}" )] private partial void LogErrorRetrying(int retryCounter, QueueIdLogRecord queueId, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.PersistentStreamPullingAgent_27, Message = "Exception calling MessagesDeliveredAsync on queue {MyQueueId}. Ignoring." )] private partial void LogWarningMessagesDeliveredAsync(QueueIdLogRecord myQueueId, Exception exception); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingAgent_24, Message = "Stream cache is under pressure. Backing off." )] private partial void LogInfoStreamCacheUnderPressure(); [LoggerMessage( Level = LogLevel.Trace, EventId = (int)ErrorCode.PersistentStreamPullingAgent_11, Message = "Got {ReceivedCount} messages from queue {Queue}. So far {MessageCount} messages from this queue." )] private partial void LogTraceGotMessages(int receivedCount, QueueIdLogRecord queue, int messageCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Pulled new messages in stream {StreamId} from the queue, but the subscriber isn't fully registered yet. The pulling agent will start deliver on this stream after registration is complete." )] private partial void LogDebugPulledNewMessages(QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_14, Message = "Exception while trying to deliver msgs to stream {StreamId} in PersistentStreamPullingAgentGrain.RunConsumerCursor" )] private partial void LogErrorDeliveringMessages(QualifiedStreamId streamId, Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_15, Message = "Ignored RunConsumerCursor error" )] private partial void LogErrorRunConsumerCursor(Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to deliver message to consumer on {SubscriptionId} for stream {StreamId}, may retry." )] private partial void LogWarningFailedToDeliverMessage(GuidId subscriptionId, QualifiedStreamId streamId, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.Stream_ConsumerIsDead, Message = "Consumer {Consumer} on stream {StreamId} is no longer active - permanently removing Consumer." )] private partial void LogWarningConsumerIsDead(IStreamConsumerExtension consumer, QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_17, Message = "RegisterAsStreamProducer failed" )] private static partial void LogErrorRegisterAsStreamProducer(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Debug, EventId = (int)ErrorCode.PersistentStreamPullingAgent_16, Message = "Got back {Count} subscribers for stream {StreamId}." )] private partial void LogDebugGotBackSubscribers(int count, QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingAgent_17, Message = "Ignored RegisterAsStreamProducer error" )] private partial void LogErrorIgnoredRegisterAsStreamProducer(Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.PersistentStreamPullingAgent_13, Message = "Ignoring exception while trying to evaluate subscription filter '{Filter}' with data '{FilterData}' on stream {StreamId}" )] private partial void LogWarningFilterEvaluation(string filter, string filterData, StreamId streamId, Exception exception); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/PersistentStreamPullingManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Configuration; using RunState = Orleans.Configuration.StreamLifecycleOptions.RunState; using Orleans.Internal; using System.Threading; using Orleans.Streams.Filtering; using Orleans.Runtime.Scheduler; using System.Diagnostics.Metrics; namespace Orleans.Streams { internal sealed partial class PersistentStreamPullingManager : SystemTarget, IPersistentStreamPullingManager, IStreamQueueBalanceListener { private static readonly TimeSpan QUEUES_PRINT_PERIOD = TimeSpan.FromMinutes(5); private readonly Dictionary queuesToAgentsMap; private readonly Dictionary deactivatedAgents = new(); private readonly string streamProviderName; private readonly IStreamPubSub pubSub; private readonly SystemTargetShared _systemTargetShared; private readonly StreamPullingAgentOptions options; private readonly AsyncSerialExecutor nonReentrancyGuarantor; // for non-reentrant execution of queue change notifications. private readonly ILogger logger; private int latestRingNotificationSequenceNumber; private int latestCommandNumber; private readonly IQueueAdapter queueAdapter; private readonly IBackoffProvider _deliveryBackoffProvider; private readonly IBackoffProvider _queueReaderBackoffProvider; private readonly IQueueAdapterCache queueAdapterCache; private IStreamQueueBalancer queueBalancer; private readonly IStreamFilter streamFilter; private readonly IQueueAdapterFactory adapterFactory; private RunState managerState; private IDisposable queuePrintTimer; private int nextAgentId; private int NumberRunningAgents { get { return queuesToAgentsMap.Count; } } internal PersistentStreamPullingManager( SystemTargetGrainId managerId, string strProviderName, IStreamPubSub streamPubSub, IQueueAdapterFactory adapterFactory, IStreamQueueBalancer streamQueueBalancer, IStreamFilter streamFilter, StreamPullingAgentOptions options, IQueueAdapter queueAdapter, IBackoffProvider deliveryBackoffProvider, IBackoffProvider queueReaderBackoffProvider, SystemTargetShared shared) : base(managerId, shared) { if (string.IsNullOrWhiteSpace(strProviderName)) { throw new ArgumentNullException(nameof(strProviderName)); } if (streamPubSub == null) { throw new ArgumentNullException(nameof(streamPubSub), "StreamPubSub reference should not be null"); } if (streamQueueBalancer == null) { throw new ArgumentNullException(nameof(streamQueueBalancer), "IStreamQueueBalancer streamQueueBalancer reference should not be null"); } queuesToAgentsMap = new Dictionary(); streamProviderName = strProviderName; pubSub = streamPubSub; this.options = options; nonReentrancyGuarantor = new AsyncSerialExecutor(); latestRingNotificationSequenceNumber = 0; latestCommandNumber = 0; queueBalancer = streamQueueBalancer; this.streamFilter = streamFilter; this.adapterFactory = adapterFactory; this.queueAdapter = queueAdapter ?? throw new ArgumentNullException(nameof(queueAdapter)); _deliveryBackoffProvider = deliveryBackoffProvider; _queueReaderBackoffProvider = queueReaderBackoffProvider; _systemTargetShared = shared; queueAdapterCache = adapterFactory.GetQueueAdapterCache(); logger = shared.LoggerFactory.CreateLogger($"{GetType().FullName}.{streamProviderName}"); LogInfoCreated(GetType().Name, streamProviderName); StreamInstruments.RegisterPersistentStreamPullingAgentsObserve(() => new Measurement(queuesToAgentsMap.Count, new KeyValuePair("name", streamProviderName))); shared.ActivationDirectory.RecordNewTarget(this); } public async Task Initialize() { LogInfoInit(); await this.queueBalancer.Initialize(this.adapterFactory.GetStreamQueueMapper()); queueBalancer.SubscribeToQueueDistributionChangeEvents(this); List myQueues = queueBalancer.GetMyQueues().ToList(); LogInfoInitialized(myQueues.Count, new(myQueues)); queuePrintTimer = this.RegisterTimer(AsyncTimerCallback, null, QUEUES_PRINT_PERIOD, QUEUES_PRINT_PERIOD); managerState = RunState.Initialized; } public async Task Stop() { await StopAgents(); if (queuePrintTimer != null) { queuePrintTimer.Dispose(); this.queuePrintTimer = null; } await this.queueBalancer.Shutdown(); this.queueBalancer = null; } public async Task StartAgents() { managerState = RunState.AgentsStarted; List myQueues = queueBalancer.GetMyQueues().ToList(); LogInfoStarting(myQueues.Count, new(myQueues)); await AddNewQueues(myQueues, true); LogInfoStarted(); } public async Task StopAgents() { managerState = RunState.AgentsStopped; List queuesToRemove = queuesToAgentsMap.Keys.ToList(); LogInfoStopping(queuesToRemove.Count, new(queuesToRemove)); await RemoveQueues(queuesToRemove); LogInfoStopped(); } /// /// Actions to take when the queue distribution changes due to a failure or a join. /// Since this pulling manager is system target and queue distribution change notifications /// are delivered to it as grain method calls, notifications are not reentrant. To simplify /// notification handling we execute them serially, in a non-reentrant way. We also suppress /// and don't execute an older notification if a newer one was already delivered. /// public Task QueueDistributionChangeNotification() { return this.RunOrQueueTask(() => this.HandleQueueDistributionChangeNotification()); } public Task HandleQueueDistributionChangeNotification() { latestRingNotificationSequenceNumber++; int notificationSeqNumber = latestRingNotificationSequenceNumber; LogInfoGotQueueChangeNotification(notificationSeqNumber, managerState); if (managerState == RunState.AgentsStopped) { return Task.CompletedTask; // if agents not running, no need to rebalance the queues among them. } return nonReentrancyGuarantor.AddNext(() => { // skip execution of an older/previous notification since already got a newer range update notification. if (notificationSeqNumber < latestRingNotificationSequenceNumber) { LogInfoSkipQueueChangeNotification(notificationSeqNumber, latestRingNotificationSequenceNumber); return Task.CompletedTask; } if (managerState == RunState.AgentsStopped) { return Task.CompletedTask; // if agents not running, no need to rebalance the queues among them. } return QueueDistributionChangeNotification(notificationSeqNumber); }); } private async Task QueueDistributionChangeNotification(int notificationSeqNumber) { HashSet currentQueues = queueBalancer.GetMyQueues().ToSet(); LogInfoExecutingQueueChangeNotification( notificationSeqNumber, currentQueues.Count, new(currentQueues)); try { Task t1 = AddNewQueues(currentQueues, false); List queuesToRemove = queuesToAgentsMap.Keys.Where(queueId => !currentQueues.Contains(queueId)).ToList(); Task t2 = RemoveQueues(queuesToRemove); await Task.WhenAll(t1, t2); } finally { LogInfoDoneExecutingQueueChangeNotification( notificationSeqNumber, NumberRunningAgents, new(queuesToAgentsMap.Keys)); } } /// /// Take responsibility for a set of new queues that were assigned to me via a new range. /// We first create one pulling agent for every new queue and store them in our internal data structure, then try to initialize the agents. /// ERROR HANDLING: /// The responsibility to handle initialization and shutdown failures is inside the Agents code. /// The manager will call Initialize once and log an error. It will not call initialize again and will assume initialization has succeeded. /// Same applies to shutdown. /// /// /// /// private async Task AddNewQueues(IEnumerable myQueues, bool failOnInit) { // Create agents for queues in range that we don't yet have. // First create them and store in local queuesToAgentsMap. // Only after that Initialize them all. var agents = new List(); foreach (var queueId in myQueues) { if (queuesToAgentsMap.ContainsKey(queueId)) { continue; } else if (deactivatedAgents.Remove(queueId, out var agent)) { queuesToAgentsMap[queueId] = agent; agents.Add(agent); } else { // Create a new agent. try { var agentIdNumber = Interlocked.Increment(ref nextAgentId); var agentId = SystemTargetGrainId.Create(Constants.StreamPullingAgentType, this.Silo, $"{streamProviderName}_{agentIdNumber}_{queueId:H}"); IStreamFailureHandler deliveryFailureHandler = await adapterFactory.GetDeliveryFailureHandler(queueId); agent = new PersistentStreamPullingAgent( agentId, streamProviderName, pubSub, streamFilter, queueId, this.options, queueAdapter, queueAdapterCache, deliveryFailureHandler, _deliveryBackoffProvider, _queueReaderBackoffProvider, _systemTargetShared); queuesToAgentsMap.Add(queueId, agent); agents.Add(agent); } catch (Exception exc) { LogErrorCreatingAgent(exc); // What should we do? This error is not recoverable and considered a bug. But we don't want to bring the silo down. // If this is when silo is starting and agent is initializing, fail the silo startup. Otherwise, just swallow to limit impact on other receivers. if (failOnInit) throw; } } } try { var initTasks = new List(); foreach (var agent in agents) { initTasks.Add(InitAgent(agent)); } await Task.WhenAll(initTasks); } catch { // Just ignore this exception and proceed as if Initialize has succeeded. // We already logged individual exceptions for individual calls to Initialize. No need to log again. } if (agents.Count > 0) { LogInfoAddedQueues( agents.Count, new(agents), NumberRunningAgents, new(queuesToAgentsMap.Keys)); } } private async Task InitAgent(PersistentStreamPullingAgent agent) { // Init the agent only after it was registered locally. var agentGrainRef = agent.AsReference(); // Need to call it as a grain reference. try { await agentGrainRef.Initialize(); } catch (Exception exc) { LogErrorInitializingAgent(agent.QueueId, exc); } } private async Task RemoveQueues(List queuesToRemove) { if (queuesToRemove.Count == 0) { return; } // Stop the agents that for queues that are not in my range anymore. LogInfoRemovingAgents(queuesToRemove.Count, new(queuesToRemove)); var agents = new List(queuesToRemove.Count); var removeTasks = new List(); foreach (var queueId in queuesToRemove) { if (!queuesToAgentsMap.Remove(queueId, out var agent)) { continue; } agents.Add(agent); deactivatedAgents[queueId] = agent; var agentGrainRef = agent.AsReference(); var task = OrleansTaskExtentions.SafeExecute(agentGrainRef.Shutdown); task = task.LogException(logger, ErrorCode.PersistentStreamPullingManager_11, $"PersistentStreamPullingAgent {agent.QueueId} failed to Shutdown."); removeTasks.Add(task); } try { await Task.WhenAll(removeTasks); } catch { // Just ignore this exception and proceed as if Initialize has succeeded. // We already logged individual exceptions for individual calls to Shutdown. No need to log again. } if (agents.Count > 0) { LogInfoRemovedQueues( agents.Count, new(agents), NumberRunningAgents, new(queuesToAgentsMap.Keys)); } } public async Task ExecuteCommand(PersistentStreamProviderCommand command, object arg) { latestCommandNumber++; int commandSeqNumber = latestCommandNumber; try { LogInfoGotCommand( command, arg != null ? " with arg " + arg : string.Empty, commandSeqNumber, managerState); switch (command) { case PersistentStreamProviderCommand.StartAgents: case PersistentStreamProviderCommand.StopAgents: await QueueCommandForExecution(command, commandSeqNumber); return null; case PersistentStreamProviderCommand.GetAgentsState: return managerState; case PersistentStreamProviderCommand.GetNumberRunningAgents: return NumberRunningAgents; default: throw new OrleansException($"PullingAgentManager does not support command {command}."); } } finally { LogInfoDoneExecutingCommand( command, commandSeqNumber, managerState, NumberRunningAgents); } } // Start and Stop commands are composite commands that take multiple turns. // Ee don't wnat them to interleave with other concurrent Start/Stop commands, as well as not with QueueDistributionChangeNotification. // Therefore, we serialize them all via the same nonReentrancyGuarantor. private Task QueueCommandForExecution(PersistentStreamProviderCommand command, int commandSeqNumber) { return nonReentrancyGuarantor.AddNext(() => { // skip execution of an older/previous command since already got a newer command. if (commandSeqNumber < latestCommandNumber) { LogInfoSkipCommandExecution( commandSeqNumber, latestCommandNumber); return Task.CompletedTask; } switch (command) { case PersistentStreamProviderCommand.StartAgents: return StartAgents(); case PersistentStreamProviderCommand.StopAgents: return StopAgents(); default: throw new OrleansException($"PullingAgentManager got unsupported command {command}"); } }); } private static string PrintQueues(ICollection myQueues) => Utils.EnumerableToString(myQueues); // Just print our queue assignment periodicaly, for easy monitoring. private Task AsyncTimerCallback(object state) { LogInfoPeriodicPrint(NumberRunningAgents, streamProviderName, new(queuesToAgentsMap.Keys)); return Task.CompletedTask; } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_01, Message = "Created {Name} for Stream Provider {StreamProvider}." )] private partial void LogInfoCreated(string name, string streamProvider); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_02, Message = "Init." )] private partial void LogInfoInit(); private readonly struct QueueIdsLogRecord(ICollection queues) { public override string ToString() => PrintQueues(queues); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_03, Message = "Initialize: I am now responsible for {QueueCount} queues: {Queues}." )] private partial void LogInfoInitialized(int queueCount, QueueIdsLogRecord queues); // [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_Starting, Message = "Starting agents for {QueueCount} queues: {Queues}." )] private partial void LogInfoStarting(int queueCount, QueueIdsLogRecord queues); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_Started, Message = "Started agents." )] private partial void LogInfoStarted(); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_Stopping, Message = "Stopping agents for {RemovedCount} queues: {RemovedQueues}." )] private partial void LogInfoStopping(int removedCount, QueueIdsLogRecord removedQueues); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_Stopped, Message = "Stopped agents." )] private partial void LogInfoStopped(); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_04, Message = "Got QueueChangeNotification number {NotificationSequenceNumber} from the queue balancer. managerState = {ManagerState}." )] private partial void LogInfoGotQueueChangeNotification(int notificationSequenceNumber, RunState managerState); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_05, Message = "Skipping execution of QueueChangeNotification number {NotificationSequenceNumber} from the queue allocator since already received a later notification (already have notification number {LatestNotificationNumber})." )] private partial void LogInfoSkipQueueChangeNotification(int notificationSequenceNumber, int latestNotificationNumber); // "Executing QueueChangeNotification number {NotificationSequenceNumber}. Queue balancer says I should now own {QueueCount} queues: {Queues}" [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_06, Message = "Executing QueueChangeNotification number {NotificationSequenceNumber}. Queue balancer says I should now own {QueueCount} queues: {Queues}." )] private partial void LogInfoExecutingQueueChangeNotification(int notificationSequenceNumber, int queueCount, QueueIdsLogRecord queues); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_16, Message = "Done Executing QueueChangeNotification number {NotificationSequenceNumber}. I now own {QueueCount} queues: {Queues}." )] private partial void LogInfoDoneExecutingQueueChangeNotification(int notificationSequenceNumber, int queueCount, QueueIdsLogRecord queues); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingManager_07, Message = "Exception while creating PersistentStreamPullingAgent." )] private partial void LogErrorCreatingAgent(Exception exc); private readonly struct AgentsLogRecord(List agents) { public override string ToString() => Utils.EnumerableToString(agents, agent => agent.QueueId.ToString()); } [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_08, Message = "Added {AddedCount} new queues: {AddedQueues}. Now own total of {QueueCount} queues: {Queues}." )] private partial void LogInfoAddedQueues(int addedCount, AgentsLogRecord addedQueues, int queueCount, QueueIdsLogRecord queues); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.PersistentStreamPullingManager_09, Message = "PersistentStreamPullingAgent {QueueId} failed to Initialize." )] private partial void LogErrorInitializingAgent(QueueId queueId, Exception exc); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_10, Message = "About to remove {RemovedCount} agents from my responsibility: {RemovedQueues}." )] private partial void LogInfoRemovingAgents(int removedCount, QueueIdsLogRecord removedQueues); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_10, Message = "Removed {RemovedCount} queues: {RemovedQueues}. Now own total of {QueueCount} queues: {Queues}." )] private partial void LogInfoRemovedQueues(int removedCount, AgentsLogRecord removedQueues, int queueCount, QueueIdsLogRecord queues); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_13, Message = "Got command {Command}{ArgString}: commandSeqNumber = {CommandSequenceNumber}, managerState = {ManagerState}." )] private partial void LogInfoGotCommand(PersistentStreamProviderCommand command, string argString, int commandSequenceNumber, RunState managerState); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_15, Message = "Done executing command {Command}: commandSeqNumber = {CommandSequenceNumber}, managerState = {ManagerState}, num running agents = {NumRunningAgents}." )] private partial void LogInfoDoneExecutingCommand(PersistentStreamProviderCommand command, int commandSequenceNumber, RunState managerState, int numRunningAgents); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_15, Message = "Skipping execution of command number {CommandNumber} since already received a later command (already have command number {LatestCommandNumber})." )] private partial void LogInfoSkipCommandExecution(int commandNumber, int latestCommandNumber); [LoggerMessage( Level = LogLevel.Information, EventId = (int)ErrorCode.PersistentStreamPullingManager_PeriodicPrint, Message = "I am responsible for a total of {QueueCount} queues on stream provider {StreamProviderName}: {Queues}." )] private partial void LogInfoPeriodicPrint(int queueCount, string streamProviderName, QueueIdsLogRecord queues); } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/QueueStreamDataStructures.cs ================================================ using System; using Microsoft.Extensions.Logging; using Orleans.Runtime; #nullable enable namespace Orleans.Streams { [Serializable] internal enum StreamConsumerDataState { Active, // Indicates that events are activly being delivered to this consumer. Inactive, // Indicates that events are not activly being delivered to this consumers. If adapter produces any events on this consumers stream, the agent will need begin delivering events } [Serializable] [GenerateSerializer] internal sealed class StreamConsumerData { [Id(0)] public GuidId SubscriptionId; [Id(1)] public QualifiedStreamId StreamId; [Id(2)] public IStreamConsumerExtension StreamConsumer; [Id(3)] public StreamConsumerDataState State = StreamConsumerDataState.Inactive; [Id(4)] public IQueueCacheCursor? Cursor; [Id(5)] public StreamHandshakeToken? LastToken; [Id(6)] public string FilterData; [NonSerialized] public bool IsRegistered = false; [NonSerialized] public StreamSequenceToken? PendingStartToken; public StreamConsumerData(GuidId subscriptionId, QualifiedStreamId streamId, IStreamConsumerExtension streamConsumer, string filterData) { SubscriptionId = subscriptionId; StreamId = streamId; StreamConsumer = streamConsumer; FilterData = filterData; } internal void SafeDisposeCursor(ILogger logger) { if (Cursor is { } cursor) { Cursor = null; // kill cursor activity and ensure it does not start again on this consumer data. try { cursor.Dispose(); } catch (Exception ex) { string? caller = null; try { caller = $"Cursor.Dispose on stream {StreamId}, StreamConsumer {StreamConsumer} has thrown exception."; } catch { } Utils.LogIgnoredException(logger, ex, caller); } } } } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/StreamConsumerCollection.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.Streams { [Serializable] [GenerateSerializer] internal sealed class StreamConsumerCollection { [Id(0)] private readonly Dictionary queueData; // map of consumers for one stream: from Guid ConsumerId to StreamConsumerData [Id(1)] private DateTime lastActivityTime; [Id(2)] public bool StreamRegistered { get; set; } public StreamConsumerCollection(DateTime now) { queueData = new Dictionary(); lastActivityTime = now; } public StreamConsumerData AddConsumer(GuidId subscriptionId, QualifiedStreamId streamId, IStreamConsumerExtension streamConsumer, string filterData) { var consumerData = new StreamConsumerData(subscriptionId, streamId, streamConsumer, filterData); queueData.Add(subscriptionId, consumerData); lastActivityTime = DateTime.UtcNow; return consumerData; } public bool RemoveConsumer(GuidId subscriptionId, ILogger logger) { if (!queueData.Remove(subscriptionId, out var consumer)) return false; consumer.SafeDisposeCursor(logger); return true; } public bool Contains(GuidId subscriptionId) { return queueData.ContainsKey(subscriptionId); } public bool TryGetConsumer(GuidId subscriptionId, out StreamConsumerData data) { return queueData.TryGetValue(subscriptionId, out data); } public IEnumerable AllConsumers() { return queueData.Values; } public void DisposeAll(ILogger logger) { foreach (StreamConsumerData consumer in queueData.Values) { consumer.SafeDisposeCursor(logger); } queueData.Clear(); } public int Count { get { return queueData.Count; } } public void RefreshActivity(DateTime now) { lastActivityTime = now; } public bool IsInactive(DateTime now, TimeSpan inactivityPeriod) { // Consider stream inactive (with all its consumers) from the pulling agent perspective if: // 1) There were no new events received for that stream in the last inactivityPeriod // 2) All consumer for that stream are currently inactive (that is, all cursors are inactive) - // meaning there is nothing for those consumers in the adapter cache. if (now - lastActivityTime < inactivityPeriod) return false; return !queueData.Values.Any(data => data.State.Equals(StreamConsumerDataState.Active)); } } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/StreamEventDeliveryFailureException.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Streams { /// /// This exception indicates that a stream event was not successfully delivered to the consumer. /// [Serializable] [GenerateSerializer] public sealed class StreamEventDeliveryFailureException : OrleansException { private const string ErrorStringFormat = "Stream provider failed to deliver an event. StreamProvider:{0} Stream:{1}"; /// /// Initializes a new instance of the class. /// public StreamEventDeliveryFailureException() { } /// /// Initializes a new instance of the class. /// /// The message. public StreamEventDeliveryFailureException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The stream identifier. internal StreamEventDeliveryFailureException(QualifiedStreamId streamId) : base(string.Format(ErrorStringFormat, streamId.GetNamespace(), streamId.StreamId)) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public StreamEventDeliveryFailureException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] public StreamEventDeliveryFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Streaming/PersistentStreams/StreamPosition.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Streams { /// /// Stream position uniquely identifies the position of an event in a stream. /// If acquiring a stream position for a batch of events, the stream position will be of the first event in the batch. /// public class StreamPosition { /// /// Stream position consists of the stream identity and the sequence token /// /// The stream identity. /// The stream sequence token. public StreamPosition(StreamId streamId, StreamSequenceToken sequenceToken) { if (sequenceToken == null) { throw new ArgumentNullException(nameof(sequenceToken)); } StreamId = streamId; SequenceToken = sequenceToken; } /// /// Gets the identity of the stream /// public StreamId StreamId { get; } /// /// Gets the position in the stream /// public StreamSequenceToken SequenceToken { get; } } } ================================================ FILE: src/Orleans.Streaming/Predicates/AllStreamNamespacesPredicate.cs ================================================ namespace Orleans.Streams { /// /// A stream namespace predicate which matches all namespaces. /// internal class AllStreamNamespacesPredicate : IStreamNamespacePredicate { /// public string PredicatePattern => "*"; /// public bool IsMatch(string streamNamespace) { return true; } } } ================================================ FILE: src/Orleans.Streaming/Predicates/ExactMatchStreamNamespacePredicate.cs ================================================ using System; namespace Orleans.Streams { /// /// Stream namespace predicate which matches exactly one, specified /// [Serializable, GenerateSerializer, Immutable] internal sealed class ExactMatchStreamNamespacePredicate : IStreamNamespacePredicate { internal const string Prefix = "namespace:"; [Id(0)] private readonly string targetStreamNamespace; /// /// Initializes a new instance of the class. /// /// The target stream namespace. public ExactMatchStreamNamespacePredicate(string targetStreamNamespace) { this.targetStreamNamespace = targetStreamNamespace; } /// public string PredicatePattern => $"{Prefix}{this.targetStreamNamespace}"; /// public bool IsMatch(string streamNamespace) { return string.Equals(targetStreamNamespace, streamNamespace?.Trim()); } } } ================================================ FILE: src/Orleans.Streaming/Predicates/IStreamNamespacePredicate.cs ================================================ namespace Orleans.Streams { /// /// Stream namespace predicate used for filtering implicit subscriptions using /// . /// /// All implementations must be serializable. public interface IStreamNamespacePredicate { /// /// Defines if the consumer grain should subscribe to the specified namespace. /// /// The target stream namespace to check. /// true, if the grain should subscribe to the specified namespace; false, otherwise. /// bool IsMatch(string streamNamespace); /// /// Gets a pattern to describe this predicate. This value is passed to instances of to recreate this predicate. /// string PredicatePattern { get; } } /// /// Converts predicate pattern strings to instances. /// /// public interface IStreamNamespacePredicateProvider { /// /// Get the predicate matching the provided pattern. Returns if this provider cannot match the predicate. /// bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate); } } ================================================ FILE: src/Orleans.Streaming/Predicates/RegexStreamNamespacePredicate.cs ================================================ using System; using System.Text.RegularExpressions; namespace Orleans.Streams { /// /// implementation allowing to filter stream namespaces by regular /// expression. /// public class RegexStreamNamespacePredicate : IStreamNamespacePredicate { internal const string Prefix = "regex:"; private readonly Regex regex; /// /// Returns a pattern used to describe this instance. The pattern will be parsed by an instance on each node. /// public string PredicatePattern => $"{Prefix}{regex}"; /// /// Creates an instance of with the specified regular expression. /// /// The stream namespace regular expression. public RegexStreamNamespacePredicate(string regex) { ArgumentNullException.ThrowIfNull(regex); this.regex = new Regex(regex, RegexOptions.Compiled); } /// public bool IsMatch(string streamNameSpace) { return regex.IsMatch(streamNameSpace); } } } ================================================ FILE: src/Orleans.Streaming/Predicates/StreamSubscriptionAttributes.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Orleans.Metadata; using Orleans.Runtime; using Orleans.Streams; namespace Orleans { /// /// The [Orleans.ImplicitStreamSubscription] attribute is used to mark grains as implicit stream subscriptions. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ImplicitStreamSubscriptionAttribute : Attribute, IGrainBindingsProviderAttribute { /// /// Gets the stream namespace filter predicate. /// public IStreamNamespacePredicate Predicate { get; } /// /// Gets the name of the stream identifier mapper. /// /// The name of the stream identifier mapper. /// /// This value is the name used to resolve the registered in the dependency injection container. /// public string StreamIdMapper { get; init; } /// /// Used to subscribe to all stream namespaces. /// public ImplicitStreamSubscriptionAttribute() { Predicate = new AllStreamNamespacesPredicate(); } /// /// Used to subscribe to the specified stream namespace. /// /// The stream namespace to subscribe. /// The name of the stream identity mapper. public ImplicitStreamSubscriptionAttribute(string streamNamespace, string streamIdMapper = null) { Predicate = new ExactMatchStreamNamespacePredicate(streamNamespace.Trim()); StreamIdMapper = streamIdMapper; } /// /// Allows to pass an arbitrary predicate type to filter stream namespaces to subscribe. The predicate type /// must have a constructor without parameters. /// /// The stream namespace predicate type. /// The name of the stream identity mapper. public ImplicitStreamSubscriptionAttribute(Type predicateType, string streamIdMapper = null) { Predicate = (IStreamNamespacePredicate) Activator.CreateInstance(predicateType); StreamIdMapper = streamIdMapper; } /// /// Allows to pass an instance of the stream namespace predicate. To be used mainly as an extensibility point /// via inheriting attributes. /// /// The stream namespace predicate. /// The name of the stream identity mapper. public ImplicitStreamSubscriptionAttribute(IStreamNamespacePredicate predicate, string streamIdMapper = null) { Predicate = predicate; StreamIdMapper = streamIdMapper; } /// public IEnumerable> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType) { var binding = new Dictionary { [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.StreamBindingTypeValue, [WellKnownGrainTypeProperties.StreamBindingPatternKey] = this.Predicate.PredicatePattern, [WellKnownGrainTypeProperties.StreamIdMapperKey] = this.StreamIdMapper, }; if (LegacyGrainId.IsLegacyGrainType(grainClass)) { string keyType; if (typeof(IGrainWithGuidKey).IsAssignableFrom(grainClass) || typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(grainClass)) keyType = nameof(Guid); else if (typeof(IGrainWithIntegerKey).IsAssignableFrom(grainClass) || typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(grainClass)) keyType = nameof(Int64); else // fallback to string keyType = nameof(String); binding[WellKnownGrainTypeProperties.LegacyGrainKeyType] = keyType; } if (LegacyGrainId.IsLegacyKeyExtGrainType(grainClass)) { binding[WellKnownGrainTypeProperties.StreamBindingIncludeNamespaceKey] = "true"; } yield return binding; } } /// /// The [Orleans.RegexImplicitStreamSubscription] attribute is used to mark grains as implicit stream /// subscriptions by filtering stream namespaces to subscribe using a regular expression. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class RegexImplicitStreamSubscriptionAttribute : ImplicitStreamSubscriptionAttribute { /// /// Allows to pass a regular expression to filter stream namespaces to subscribe to. /// /// The stream namespace regular expression filter. public RegexImplicitStreamSubscriptionAttribute([StringSyntax(StringSyntaxAttribute.Regex)] string pattern) : base(new RegexStreamNamespacePredicate(pattern)) { } } } ================================================ FILE: src/Orleans.Streaming/ProviderErrorCode.cs ================================================ namespace Orleans.Providers { internal enum ProviderErrorCode { ProvidersBase = 200000, MemoryStreamProviderBase = ProvidersBase + 400, MemoryStreamProviderBase_QueueMessageBatchAsync = MemoryStreamProviderBase + 1, MemoryStreamProviderBase_GetQueueMessagesAsync = MemoryStreamProviderBase + 2, } } ================================================ FILE: src/Orleans.Streaming/Providers/ClientStreamingProviderRuntime.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Streams; using Microsoft.Extensions.Logging; namespace Orleans.Providers { internal class ClientStreamingProviderRuntime : IStreamProviderRuntime, ILifecycleParticipant { private readonly IStreamPubSub grainBasedPubSub; private readonly IStreamPubSub implicitPubSub; private readonly IStreamPubSub combinedGrainBasedAndImplicitPubSub; private StreamDirectory streamDirectory; private readonly IInternalGrainFactory grainFactory; private readonly ImplicitStreamSubscriberTable implicitSubscriberTable; private readonly ClientGrainContext clientContext; private readonly IRuntimeClient runtimeClient; public ClientStreamingProviderRuntime( IInternalGrainFactory grainFactory, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ImplicitStreamSubscriberTable implicitSubscriberTable, ClientGrainContext clientContext) { this.grainFactory = grainFactory; this.ServiceProvider = serviceProvider; this.implicitSubscriberTable = implicitSubscriberTable; this.clientContext = clientContext; this.runtimeClient = serviceProvider.GetService(); grainBasedPubSub = new GrainBasedPubSubRuntime(GrainFactory); var tmp = new ImplicitStreamPubSub(this.grainFactory, this.implicitSubscriberTable); implicitPubSub = tmp; combinedGrainBasedAndImplicitPubSub = new StreamPubSubImpl(grainBasedPubSub, tmp); streamDirectory = new StreamDirectory(); } public IGrainFactory GrainFactory => this.grainFactory; public IServiceProvider ServiceProvider { get; } public StreamDirectory GetStreamDirectory() { return streamDirectory; } public async Task Reset(bool cleanup = true) { if (streamDirectory != null) { var tmp = streamDirectory; streamDirectory = null; // null streamDirectory now, just to make sure we call cleanup only once, in all cases. if (cleanup) { await tmp.Cleanup(true, true); } } } public string ExecutingEntityIdentity() { return this.runtimeClient.CurrentActivationIdentity; } public (TExtension, TExtensionInterface) BindExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { return this.clientContext.GetOrSetExtension(newExtensionFunc); } public IStreamPubSub PubSub(StreamPubSubType pubSubType) { switch (pubSubType) { case StreamPubSubType.ExplicitGrainBasedAndImplicit: return combinedGrainBasedAndImplicitPubSub; case StreamPubSubType.ExplicitGrainBasedOnly: return grainBasedPubSub; case StreamPubSubType.ImplicitOnly: return implicitPubSub; default: return null; } } public void Participate(IClusterClientLifecycle lifecycle) { lifecycle.Subscribe(ServiceLifecycleStage.RuntimeInitialize, ct => Task.CompletedTask, async ct => await this.Reset(!ct.IsCancellationRequested)); } } } ================================================ FILE: src/Orleans.Streaming/Providers/IBackoffProviders.cs ================================================ using Orleans.Internal; using Orleans.Streams; namespace Orleans.Runtime.Providers; /// /// Functionality for determining how long the will wait between successive attempts to deliver a message. /// public interface IMessageDeliveryBackoffProvider : IBackoffProvider { } /// /// Functionality for determining how long the will wait between successive attempts to read a message from a queue. /// public interface IQueueReaderBackoffProvider : IBackoffProvider { } ================================================ FILE: src/Orleans.Streaming/Providers/IStreamProvider.cs ================================================ using Orleans.Runtime; using System; namespace Orleans.Streams { /// /// Functionality for providing streams to consumers and producers. /// public interface IStreamProvider { /// /// Gets the name of the stream provider. /// /// The name. string Name { get; } /// /// Gets the stream with the specified identity. /// /// The stream element type. /// The stream identifier. /// The stream. IAsyncStream GetStream(StreamId streamId); /// /// Gets a value indicating whether this is a rewindable provider - supports creating rewindable streams /// (streams that allow subscribing from previous point in time). /// /// if this is a rewindable provider, otherwise. bool IsRewindable { get; } } /// /// Extensions for . /// public static class StreamProviderExtensions { /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, Guid id) => streamProvider.GetStream(StreamId.Create(null, id)); /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The namespace. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, Guid id) => streamProvider.GetStream(StreamId.Create(ns, id)); /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, string id) => streamProvider.GetStream(StreamId.Create(null, id)); /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The namespace. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, string id) => streamProvider.GetStream(StreamId.Create(ns, id)); /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, long id) => streamProvider.GetStream(StreamId.Create(null, id)); /// /// Gets the stream with the specified identity and namespace. /// /// The stream element type. /// The stream provider. /// The namespace. /// The identifier. /// The stream. public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, long id) => streamProvider.GetStream(StreamId.Create(ns, id)); } } ================================================ FILE: src/Orleans.Streaming/Providers/IStreamProviderRuntime.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Providers; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { /// /// Provider-facing interface for manager of streaming providers /// internal interface IStreamProviderRuntime : IProviderRuntime { /// /// Retrieves the opaque identity of currently executing grain or client object. /// /// Exposed for logging purposes. string ExecutingEntityIdentity(); /// /// Returns the stream directory. /// /// The stream directory. StreamDirectory GetStreamDirectory(); /// /// A Pub Sub runtime interface. /// /// IStreamPubSub PubSub(StreamPubSubType pubSubType); } /// /// Provider-facing interface for manager of streaming providers /// internal interface ISiloSideStreamProviderRuntime : IStreamProviderRuntime { /// Start the pulling agents for a given persistent stream provider. Task InitializePullingAgents( string streamProviderName, IQueueAdapterFactory adapterFactory, IQueueAdapter queueAdapter); } /// /// Identifies the publish/subscribe system types which stream providers can use. /// public enum StreamPubSubType { /// /// Explicit and implicit pub/sub. /// ExplicitGrainBasedAndImplicit, /// /// Explicit pub/sub. /// ExplicitGrainBasedOnly, /// /// Implicit pub/sub. /// ImplicitOnly, } public interface IStreamPubSub // Compare with: IPubSubRendezvousGrain { Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer); Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer); Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData); Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId); Task ProducerCount(QualifiedStreamId streamId); Task ConsumerCount(QualifiedStreamId streamId); Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer = default); GuidId CreateSubscriptionId(QualifiedStreamId streamId, GrainId streamConsumer); Task FaultSubscription(QualifiedStreamId streamId, GuidId subscriptionId); } } ================================================ FILE: src/Orleans.Streaming/Providers/ProviderStartException.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Streams { /// /// Exception thrown whenever a provider has failed to be started. /// [Serializable] [GenerateSerializer] public sealed class ProviderStartException : OrleansException { /// /// Initializes a new instance of the class. /// public ProviderStartException() { } /// /// Initializes a new instance of the class. /// /// The message. public ProviderStartException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public ProviderStartException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private ProviderStartException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Streaming/Providers/SiloStreamProviderRuntime.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Runtime.ConsistentRing; using Orleans.Streams; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.Streams.Filtering; using Orleans.Internal; namespace Orleans.Runtime.Providers { internal partial class SiloStreamProviderRuntime : ISiloSideStreamProviderRuntime { private readonly IConsistentRingProvider consistentRingProvider; private readonly InsideRuntimeClient runtimeClient; private readonly IStreamPubSub grainBasedPubSub; private readonly IStreamPubSub implictPubSub; private readonly IStreamPubSub combinedGrainBasedAndImplicitPubSub; private readonly ILoggerFactory loggerFactory; private readonly ILocalSiloDetails siloDetails; private readonly IGrainContextAccessor grainContextAccessor; private readonly ILogger logger; private readonly StreamDirectory hostedClientStreamDirectory = new StreamDirectory(); public IGrainFactory GrainFactory => this.runtimeClient.InternalGrainFactory; public IServiceProvider ServiceProvider => this.runtimeClient.ServiceProvider; public SiloStreamProviderRuntime( IConsistentRingProvider consistentRingProvider, InsideRuntimeClient runtimeClient, ImplicitStreamSubscriberTable implicitStreamSubscriberTable, ILoggerFactory loggerFactory, ILocalSiloDetails siloDetails, IGrainContextAccessor grainContextAccessor) { this.loggerFactory = loggerFactory; this.siloDetails = siloDetails; this.grainContextAccessor = grainContextAccessor; this.consistentRingProvider = consistentRingProvider; this.runtimeClient = runtimeClient; this.logger = this.loggerFactory.CreateLogger(); this.grainBasedPubSub = new GrainBasedPubSubRuntime(this.GrainFactory); var tmp = new ImplicitStreamPubSub(this.runtimeClient.InternalGrainFactory, implicitStreamSubscriberTable); this.implictPubSub = tmp; this.combinedGrainBasedAndImplicitPubSub = new StreamPubSubImpl(this.grainBasedPubSub, tmp); } public IStreamPubSub PubSub(StreamPubSubType pubSubType) { switch (pubSubType) { case StreamPubSubType.ExplicitGrainBasedAndImplicit: return combinedGrainBasedAndImplicitPubSub; case StreamPubSubType.ExplicitGrainBasedOnly: return grainBasedPubSub; case StreamPubSubType.ImplicitOnly: return implictPubSub; default: return null; } } public async Task InitializePullingAgents( string streamProviderName, IQueueAdapterFactory adapterFactory, IQueueAdapter queueAdapter) { IStreamQueueBalancer queueBalancer = CreateQueueBalancer(streamProviderName); (var deliveryProvider, var queueReaderProvider) = CreateBackoffProviders(streamProviderName); var managerId = SystemTargetGrainId.Create(Constants.StreamPullingAgentManagerType, this.siloDetails.SiloAddress, streamProviderName); var pubsubOptions = this.ServiceProvider.GetOptionsByName(streamProviderName); var pullingAgentOptions = this.ServiceProvider.GetOptionsByName(streamProviderName); var filter = this.ServiceProvider.GetKeyedService(streamProviderName) ?? new NoOpStreamFilter(); var manager = new PersistentStreamPullingManager( managerId, streamProviderName, this.PubSub(pubsubOptions.PubSubType), adapterFactory, queueBalancer, filter, pullingAgentOptions, queueAdapter, deliveryProvider, queueReaderProvider, ServiceProvider.GetRequiredService()); // Init the manager only after it was registered locally. var pullingAgentManager = manager.AsReference(); // Need to call it as a grain reference though. await pullingAgentManager.Initialize(); return pullingAgentManager; } private (IBackoffProvider, IBackoffProvider) CreateBackoffProviders(string streamProviderName) { var deliveryProvider = (IBackoffProvider)ServiceProvider.GetKeyedService(streamProviderName) ?? new ExponentialBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(1)); var queueReaderProvider = (IBackoffProvider)ServiceProvider.GetKeyedService(streamProviderName) ?? new ExponentialBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(1)); return new(deliveryProvider, queueReaderProvider); } private IStreamQueueBalancer CreateQueueBalancer(string streamProviderName) { try { var balancer = this.ServiceProvider.GetKeyedService(streamProviderName) ??this.ServiceProvider.GetService(); if (balancer == null) throw new ArgumentOutOfRangeException("balancerType", $"Cannot create stream queue balancer for StreamProvider: {streamProviderName}.Please configure your stream provider with a queue balancer."); LogInfoSuccessfullyCreatedQueueBalancer(balancer.GetType(), streamProviderName); return balancer; } catch (Exception e) { string error = $"Cannot create stream queue balancer for StreamProvider: {streamProviderName}, Exception: {e}. Please configure your stream provider with a queue balancer."; throw new ArgumentOutOfRangeException("balancerType", error); } } /// public string ExecutingEntityIdentity() => runtimeClient.CurrentActivationIdentity; /// public StreamDirectory GetStreamDirectory() { if (RuntimeContext.Current is { } activation) { var directory = activation.GetComponent(); if (directory is null) { directory = activation.ActivationServices.GetRequiredService(); activation.SetComponent(directory); } return directory; } return this.hostedClientStreamDirectory; } public (TExtension, TExtensionInterface) BindExtension(Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { if (this.grainContextAccessor.GrainContext is ActivationData activationData && activationData.IsStatelessWorker) { throw new InvalidOperationException($"The extension { typeof(TExtension) } cannot be bound to a Stateless Worker."); } return this.grainContextAccessor.GrainContext.GetComponent().GetOrSetExtension(newExtensionFunc); } [LoggerMessage( Level = LogLevel.Information, Message = "Successfully created queue balancer of type {BalancerType} for stream provider {StreamProviderName}" )] private partial void LogInfoSuccessfullyCreatedQueueBalancer(Type balancerType, string streamProviderName); } } ================================================ FILE: src/Orleans.Streaming/Providers/StreamProviderDirection.cs ================================================ namespace Orleans.Streams { /// /// Identifies whether a stream provider is read-only, read-write, or write-only. /// public enum StreamProviderDirection { /// /// None. /// None, /// /// This provider can receive messages but cannot send them. /// ReadOnly, /// /// This provider can send messages but cannot receive them. /// WriteOnly, /// /// This provider can both send and receive messages. /// ReadWrite } } ================================================ FILE: src/Orleans.Streaming/PubSub/DefaultStreamNamespacePredicateProvider.cs ================================================ using System; using Orleans.Serialization.TypeSystem; namespace Orleans.Streams { /// /// Default implementation of for internally supported stream predicates. /// public class DefaultStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider { /// public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate) { switch (predicatePattern) { case "*": predicate = new AllStreamNamespacesPredicate(); return true; case var regex when regex.StartsWith(RegexStreamNamespacePredicate.Prefix, StringComparison.Ordinal): predicate = new RegexStreamNamespacePredicate(regex[RegexStreamNamespacePredicate.Prefix.Length..]); return true; case var ns when ns.StartsWith(ExactMatchStreamNamespacePredicate.Prefix, StringComparison.Ordinal): predicate = new ExactMatchStreamNamespacePredicate(ns[ExactMatchStreamNamespacePredicate.Prefix.Length..]); return true; } predicate = null; return false; } } /// /// Stream namespace predicate provider which supports objects which can be constructed and optionally accept a string as a constructor argument. /// public class ConstructorStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider { /// /// The prefix used to identify this predicate provider. /// public const string Prefix = "ctor"; /// /// Formats a stream namespace predicate which indicates a concrete type to be constructed, along with an optional argument. /// public static string FormatPattern(Type predicateType, string constructorArgument) { if (constructorArgument is null) { return $"{Prefix}:{RuntimeTypeNameFormatter.Format(predicateType)}"; } return $"{Prefix}:{RuntimeTypeNameFormatter.Format(predicateType)}:{constructorArgument}"; } /// public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate) { if (!predicatePattern.StartsWith(Prefix, StringComparison.Ordinal)) { predicate = null; return false; } var start = Prefix.Length + 1; string typeName; string arg; var index = predicatePattern.IndexOf(':', start); if (index < 0) { typeName = predicatePattern[start..]; arg = null; } else { typeName = predicatePattern[start..index]; arg = predicatePattern[(index + 1)..]; } var type = Type.GetType(typeName, throwOnError: true); if (string.IsNullOrEmpty(arg)) { predicate = (IStreamNamespacePredicate)Activator.CreateInstance(type); } else { predicate = (IStreamNamespacePredicate)Activator.CreateInstance(type, arg); } return true; } } } ================================================ FILE: src/Orleans.Streaming/PubSub/FaultedSubscriptionException.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Streams { /// /// This exception indicates that an error has occurred on a stream subscription that has placed the subscription into /// a faulted state. Work on faulted subscriptions should be abandoned. /// [Serializable] [GenerateSerializer] public sealed class FaultedSubscriptionException : OrleansException { private const string ErrorStringFormat = "Subscription is in a Faulted state. Subscription:{0}, Stream:{1}"; /// /// Initializes a new instance of the class. /// public FaultedSubscriptionException() { } /// /// Initializes a new instance of the class. /// /// The message. public FaultedSubscriptionException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The subscription identifier. /// The stream identifier. internal FaultedSubscriptionException(GuidId subscriptionId, QualifiedStreamId streamId) : base(string.Format(ErrorStringFormat, subscriptionId.Guid, streamId)) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public FaultedSubscriptionException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private FaultedSubscriptionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Streaming/PubSub/GrainBasedPubSubRuntime.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal class GrainBasedPubSubRuntime : IStreamPubSub { private readonly IGrainFactory grainFactory; public GrainBasedPubSubRuntime(IGrainFactory grainFactory) { this.grainFactory = grainFactory; } public Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.RegisterProducer(streamId, streamProducer); } public Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.UnregisterProducer(streamId, streamProducer); } public Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.RegisterConsumer(subscriptionId, streamId, streamConsumer, filterData); } public Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.UnregisterConsumer(subscriptionId, streamId); } public Task ProducerCount(QualifiedStreamId streamId) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.ProducerCount(streamId); } public Task ConsumerCount(QualifiedStreamId streamId) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.ConsumerCount(streamId); } public Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer = default) { var streamRendezvous = GetRendezvousGrain(streamId); return streamRendezvous.GetAllSubscriptions(streamId, streamConsumer); } private IPubSubRendezvousGrain GetRendezvousGrain(QualifiedStreamId streamId) { return grainFactory.GetGrain(streamId.ToString()); } public GuidId CreateSubscriptionId(QualifiedStreamId streamId, GrainId streamConsumer) { Guid subscriptionId = SubscriptionMarker.MarkAsExplicitSubscriptionId(Guid.NewGuid()); return GuidId.GetGuidId(subscriptionId); } public async Task FaultSubscription(QualifiedStreamId streamId, GuidId subscriptionId) { var streamRendezvous = GetRendezvousGrain(streamId); await streamRendezvous.FaultSubscription(subscriptionId); return true; } } } ================================================ FILE: src/Orleans.Streaming/PubSub/IPubSubRendezvousGrain.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal interface IPubSubRendezvousGrain : IGrainWithStringKey { Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer); Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer); Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData); Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId); Task ProducerCount(QualifiedStreamId streamId); Task ConsumerCount(QualifiedStreamId streamId); Task DiagGetConsumers(QualifiedStreamId streamId); Task Validate(); Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer = default); Task FaultSubscription(GuidId subscriptionId); } } ================================================ FILE: src/Orleans.Streaming/PubSub/ImplicitStreamPubSub.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal class ImplicitStreamPubSub : IStreamPubSub { private readonly IInternalGrainFactory grainFactory; private readonly ImplicitStreamSubscriberTable implicitTable; public ImplicitStreamPubSub(IInternalGrainFactory grainFactory, ImplicitStreamSubscriberTable implicitPubSubTable) { if (implicitPubSubTable == null) { throw new ArgumentNullException(nameof(implicitPubSubTable)); } this.grainFactory = grainFactory; this.implicitTable = implicitPubSubTable; } public Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { ISet result = new HashSet(); if (!ImplicitStreamSubscriberTable.IsImplicitSubscribeEligibleNameSpace(streamId.GetNamespace())) return Task.FromResult(result); IDictionary implicitSubscriptions = implicitTable.GetImplicitSubscribers(streamId, this.grainFactory); foreach (var kvp in implicitSubscriptions) { GuidId subscriptionId = GuidId.GetGuidId(kvp.Key); result.Add(new PubSubSubscriptionState(subscriptionId, streamId, kvp.Value)); } return Task.FromResult(result); } public Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { return Task.CompletedTask; } public Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData) { // TODO BPETIT filter data? if (!IsImplicitSubscriber(streamConsumer, streamId)) { throw new ArgumentOutOfRangeException(streamId.ToString(), "Only implicit subscriptions are supported."); } return Task.CompletedTask; } public Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId) { if (!IsImplicitSubscriber(subscriptionId, streamId)) { throw new ArgumentOutOfRangeException(streamId.ToString(), "Only implicit subscriptions are supported."); } return Task.CompletedTask; } public Task ProducerCount(QualifiedStreamId streamId) { return Task.FromResult(0); } public Task ConsumerCount(QualifiedStreamId streamId) { return Task.FromResult(0); } public Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer = default) { if (!ImplicitStreamSubscriberTable.IsImplicitSubscribeEligibleNameSpace(streamId.GetNamespace())) return Task.FromResult(new List()); if (streamConsumer != default) { var subscriptionId = CreateSubscriptionId(streamId, streamConsumer); return Task.FromResult(new List { new StreamSubscription(subscriptionId.Guid, streamId.ProviderName, streamId, streamConsumer) }); } else { var implicitConsumers = this.implicitTable.GetImplicitSubscribers(streamId, grainFactory); var subscriptions = implicitConsumers.Select(consumer => { var grainId = consumer.Value; var subId = consumer.Key; return new StreamSubscription(subId, streamId.ProviderName, streamId, grainId); }).ToList(); return Task.FromResult(subscriptions); } } internal bool IsImplicitSubscriber(GrainId grainId, QualifiedStreamId streamId) { return implicitTable.IsImplicitSubscriber(grainId, streamId); } internal bool IsImplicitSubscriber(GuidId subscriptionId, QualifiedStreamId streamId) { return SubscriptionMarker.IsImplicitSubscription(subscriptionId.Guid); } public GuidId CreateSubscriptionId(QualifiedStreamId streamId, GrainId grainId) { Guid subscriptionGuid; if (!implicitTable.TryGetImplicitSubscriptionGuid(grainId, streamId, out subscriptionGuid)) { throw new ArgumentOutOfRangeException(streamId.ToString(), "Only implicit subscriptions are supported."); } return GuidId.GetGuidId(subscriptionGuid); } public Task FaultSubscription(QualifiedStreamId streamId, GuidId subscriptionId) { return Task.FromResult(false); } } } ================================================ FILE: src/Orleans.Streaming/PubSub/ImplicitStreamSubscriberTable.cs ================================================ using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Orleans.Metadata; using Orleans.Runtime; namespace Orleans.Streams { internal class ImplicitStreamSubscriberTable { #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly GrainBindingsResolver _bindings; private readonly IStreamNamespacePredicateProvider[] _providers; private readonly IServiceProvider _serviceProvider; private Cache _cache; public ImplicitStreamSubscriberTable( GrainBindingsResolver bindings, IEnumerable providers, IServiceProvider serviceProvider) { _bindings = bindings; var initialBindings = bindings.GetAllBindings(); _providers = providers.ToArray(); _serviceProvider = serviceProvider; _cache = BuildCache(initialBindings.Version, initialBindings.Bindings); } private Cache GetCache() { var cache = _cache; var bindings = _bindings.GetAllBindings(); if (bindings.Version == cache.Version) { return cache; } lock (_lockObj) { bindings = _bindings.GetAllBindings(); if (bindings.Version == cache.Version) { return cache; } return _cache = BuildCache(bindings.Version, bindings.Bindings); } } private Cache BuildCache(MajorMinorVersion version, ImmutableDictionary bindings) { var newPredicates = new List(); foreach (var binding in bindings.Values) { foreach (var grainBinding in binding.Bindings) { if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type) || type != WellKnownGrainTypeProperties.StreamBindingTypeValue) { continue; } if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.StreamBindingPatternKey, out var pattern)) { throw new KeyNotFoundException( $"Stream binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.StreamBindingPatternKey}\" value"); } IStreamNamespacePredicate predicate = null; foreach (var provider in _providers) { if (provider.TryGetPredicate(pattern, out predicate)) break; } if (predicate is null) { throw new KeyNotFoundException( $"Could not find an {nameof(IStreamNamespacePredicate)} for the pattern \"{pattern}\"." + $" Ensure that a corresponding {nameof(IStreamNamespacePredicateProvider)} is registered"); } if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.StreamIdMapperKey, out var mapperName)) { throw new KeyNotFoundException( $"Stream binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.StreamIdMapperKey}\" value"); } var streamIdMapper = _serviceProvider.GetKeyedService(string.IsNullOrWhiteSpace(mapperName) ? DefaultStreamIdMapper.Name : mapperName); var subscriber = new StreamSubscriber(binding, streamIdMapper); newPredicates.Add(new StreamSubscriberPredicate(subscriber, predicate)); } } return new Cache(version, newPredicates); } /// /// Retrieve a map of implicit subscriptionsIds to implicit subscribers, given a stream ID. This method throws an exception if there's no namespace associated with the stream ID. /// /// A stream ID. /// The grain factory used to get consumer references. /// A set of GrainId that are implicitly subscribed grains. They are expected to support the streaming consumer extension. /// The stream ID doesn't have an associated namespace. /// Internal invariant violation. internal Dictionary GetImplicitSubscribers(QualifiedStreamId streamId, IInternalGrainFactory grainFactory) { var streamNamespace = streamId.GetNamespace(); if (!IsImplicitSubscribeEligibleNameSpace(streamNamespace)) { throw new ArgumentException("The stream ID doesn't have an associated namespace.", nameof(streamId)); } var entries = GetOrAddImplicitSubscribers(streamNamespace); var result = new Dictionary(); foreach (var entry in entries) { var grainId = entry.GetGrainId(streamId); Guid subscriptionGuid = MakeSubscriptionGuid(entry.GrainType, streamId); CollectionsMarshal.GetValueRefOrAddDefault(result, subscriptionGuid, out var duplicate) = grainId; if (duplicate) { throw new InvalidOperationException( $"Internal invariant violation: generated duplicate subscriber reference: {grainId}, subscriptionId: {subscriptionGuid}"); } } return result; } private HashSet GetOrAddImplicitSubscribers(string streamNamespace) { var cache = GetCache(); if (cache.Namespaces.TryGetValue(streamNamespace, out var result)) { return result; } return cache.Namespaces.GetOrAdd(streamNamespace, FindImplicitSubscribers(streamNamespace, cache.Predicates)); } /// /// Determines whether the specified grain is an implicit subscriber of a given stream. /// /// The grain identifier. /// The stream identifier. /// true if the grain id describes an implicit subscriber of the stream described by the stream id. internal bool IsImplicitSubscriber(GrainId grainId, QualifiedStreamId streamId) { var streamNamespace = streamId.GetNamespace(); if (!IsImplicitSubscribeEligibleNameSpace(streamNamespace)) { return false; } foreach (var entry in GetOrAddImplicitSubscribers(streamNamespace)) { if (entry.GrainType == grainId.Type) return true; } return false; } /// /// Try to get the implicit subscriptionId. /// If an implicit subscription exists, return a subscription Id that is unique per grain type, grainId, namespace combination. /// /// /// /// /// internal bool TryGetImplicitSubscriptionGuid(GrainId grainId, QualifiedStreamId streamId, out Guid subscriptionId) { if (!IsImplicitSubscriber(grainId, streamId)) { subscriptionId = default; return false; } subscriptionId = MakeSubscriptionGuid(grainId.Type, streamId); return true; } /// /// Create a subscriptionId that is unique per grainId, grainType, namespace combination. /// private Guid MakeSubscriptionGuid(GrainType grainType, QualifiedStreamId streamId) { Span bytes = stackalloc byte[16]; BinaryPrimitives.WriteUInt32LittleEndian(bytes, grainType.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[4..], streamId.StreamId.GetUniformHashCode()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[8..], streamId.StreamId.GetKeyIndex()); BinaryPrimitives.WriteUInt32LittleEndian(bytes[12..], StableHash.ComputeHash(streamId.ProviderName)); return SubscriptionMarker.MarkAsImplictSubscriptionId(new(bytes)); } internal static bool IsImplicitSubscribeEligibleNameSpace(string streamNameSpace) { return !string.IsNullOrWhiteSpace(streamNameSpace); } /// /// Finds all implicit subscribers for the given stream namespace. /// private static HashSet FindImplicitSubscribers(string streamNamespace, List predicates) { var result = new HashSet(); foreach (var predicate in predicates) { if (predicate.Predicate.IsMatch(streamNamespace)) { result.Add(predicate.Subscriber); } } return result; } private class StreamSubscriberPredicate { public StreamSubscriberPredicate(StreamSubscriber subscriber, IStreamNamespacePredicate predicate) { this.Subscriber = subscriber; this.Predicate = predicate; } public StreamSubscriber Subscriber { get; } public IStreamNamespacePredicate Predicate { get; } } private sealed class StreamSubscriber : IEquatable { public StreamSubscriber(GrainBindings grainBindings, IStreamIdMapper streamIdMapper) { this.GrainBindings = grainBindings; this.streamIdMapper = streamIdMapper; } public GrainType GrainType => this.GrainBindings.GrainType; private GrainBindings GrainBindings { get; } private IStreamIdMapper streamIdMapper { get; } public override bool Equals(object obj) => Equals(obj as StreamSubscriber); public bool Equals(StreamSubscriber other) => other != null && GrainType.Equals(other.GrainType); public override int GetHashCode() => GrainType.GetHashCode(); internal GrainId GetGrainId(QualifiedStreamId streamId) { var grainKeyId = this.streamIdMapper.GetGrainKeyId(this.GrainBindings, streamId); return GrainId.Create(this.GrainType, grainKeyId); } } private class Cache { public Cache(MajorMinorVersion version, List predicates) { this.Version = version; this.Predicates = predicates; this.Namespaces = new ConcurrentDictionary>(); } public MajorMinorVersion Version { get; } public ConcurrentDictionary> Namespaces { get; } public List Predicates { get; } } } } ================================================ FILE: src/Orleans.Streaming/PubSub/PubSubPublisherState.cs ================================================ using System; using Newtonsoft.Json; using Orleans.Runtime; namespace Orleans.Streams { [Serializable] [JsonObject(MemberSerialization.OptIn)] [GenerateSerializer] internal sealed class PubSubPublisherState : IEquatable { // IMPORTANT!!!!! // These fields have to be public non-readonly for JsonSerialization to work! // Implement ISerializable if changing any of them to readonly [JsonProperty] [Id(0)] public QualifiedStreamId Stream; [JsonProperty] [Id(1)] public GrainId Producer; // the field needs to be of a public type, otherwise we will not generate an Orleans serializer for that class. // This constructor has to be public for JsonSerialization to work! // Implement ISerializable if changing it to non-public public PubSubPublisherState(QualifiedStreamId streamId, GrainId streamProducer) { Stream = streamId; Producer = streamProducer; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; // Note: Can't use the 'as' operator on PubSubPublisherState because it is a struct. return obj is PubSubPublisherState && Equals((PubSubPublisherState)obj); } public bool Equals(PubSubPublisherState other) { // Note: PubSubPublisherState is a struct, so 'other' can never be null. return Equals(other.Stream, other.Producer); } public bool Equals(QualifiedStreamId streamId, GrainId streamProducer) { if (Stream == default) return false; return Stream.Equals(streamId) && Producer.Equals(streamProducer); } public static bool operator ==(PubSubPublisherState left, PubSubPublisherState right) { return left.Equals(right); } public static bool operator !=(PubSubPublisherState left, PubSubPublisherState right) { return !left.Equals(right); } public override int GetHashCode() => HashCode.Combine(Stream, Producer); public override string ToString() { return string.Format("PubSubPublisherState:StreamId={0},Producer={1}.", Stream, Producer); } } } ================================================ FILE: src/Orleans.Streaming/PubSub/PubSubRendezvousGrain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Core; using Orleans.Providers; using Orleans.Runtime; using Orleans.Serialization.Serializers; using Orleans.Storage; using Orleans.Streams.Core; #nullable enable namespace Orleans.Streams { internal sealed partial class PubSubGrainStateStorageFactory { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; public PubSubGrainStateStorageFactory(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) { _serviceProvider = serviceProvider; _logger = loggerFactory.CreateLogger(); } public StateStorageBridge GetStorage(PubSubRendezvousGrain grain) { var span = grain.GrainId.Key.AsSpan(); var i = span.IndexOf((byte)'/'); if (i < 0) { throw new ArgumentException($"Unable to parse \"{grain.GrainId.Key}\" as a stream id"); } var providerName = Encoding.UTF8.GetString(span[..i]); LogDebugTryingToFindStorageProvider(providerName); var storage = _serviceProvider.GetKeyedService(providerName); if (storage == null) { LogDebugFallbackToStorageProvider(ProviderConstants.DEFAULT_PUBSUB_PROVIDER_NAME); storage = _serviceProvider.GetRequiredKeyedService(ProviderConstants.DEFAULT_PUBSUB_PROVIDER_NAME); } return new(nameof(PubSubRendezvousGrain), grain.GrainContext, storage); } [LoggerMessage( Level = LogLevel.Debug, Message = "Trying to find storage provider {ProviderName}" )] private partial void LogDebugTryingToFindStorageProvider(string providerName); [LoggerMessage( Level = LogLevel.Debug, Message = "Fallback to storage provider {ProviderName}" )] private partial void LogDebugFallbackToStorageProvider(string providerName); } [Serializable] [GenerateSerializer] internal sealed class PubSubGrainState { [Id(0)] public HashSet Producers { get; set; } = new HashSet(); [Id(1)] public HashSet Consumers { get; set; } = new HashSet(); } [GrainType("pubsubrendezvous")] internal sealed partial class PubSubRendezvousGrain : Grain, IPubSubRendezvousGrain, IGrainMigrationParticipant { private readonly ILogger _logger; private const bool DEBUG_PUB_SUB = false; private readonly PubSubGrainStateStorageFactory _storageFactory; private readonly StateStorageBridge _storage; private PubSubGrainState State => _storage.State; public PubSubRendezvousGrain(PubSubGrainStateStorageFactory storageFactory, ILogger logger) { _storageFactory = storageFactory; _logger = logger; _storage = _storageFactory.GetStorage(this); } public override async Task OnActivateAsync(CancellationToken cancellationToken) { await ReadStateAsync(); LogPubSubCounts("OnActivateAsync"); } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { LogPubSubCounts("OnDeactivateAsync"); return Task.CompletedTask; } public async Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { StreamInstruments.PubSubProducersAdded.Add(1); try { var publisherState = new PubSubPublisherState(streamId, streamProducer); State.Producers.Add(publisherState); LogPubSubCounts("RegisterProducer {0}", streamProducer); await WriteStateAsync(); StreamInstruments.PubSubProducersTotal.Add(1); } catch (Exception exc) { LogErrorRegisterProducer(streamId, streamProducer, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } return State.Consumers.Where(c => !c.IsFaulted).ToSet(); } public async Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { StreamInstruments.PubSubProducersRemoved.Add(1); try { int numRemoved = State.Producers.RemoveWhere(s => s.Equals(streamId, streamProducer)); LogPubSubCounts("UnregisterProducer {0} NumRemoved={1}", streamProducer, numRemoved); if (numRemoved > 0) { Task updateStorageTask = State.Producers.Count == 0 && State.Consumers.Count == 0 ? ClearStateAsync() //State contains no producers or consumers, remove it from storage : WriteStateAsync(); await updateStorageTask; } StreamInstruments.PubSubProducersTotal.Add(-numRemoved); } catch (Exception exc) { LogErrorUnregisterProducer(streamId, streamProducer, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } if (State.Producers.Count == 0 && State.Consumers.Count == 0) { DeactivateOnIdle(); // No producers or consumers left now, so flag ourselves to expedite Deactivation } } public async Task RegisterConsumer( GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData) { StreamInstruments.PubSubConsumersAdded.Add(1); var pubSubState = State.Consumers.FirstOrDefault(s => s.Equals(subscriptionId)); if (pubSubState != null && pubSubState.IsFaulted) throw new FaultedSubscriptionException(subscriptionId, streamId); try { if (pubSubState == null) { pubSubState = new PubSubSubscriptionState(subscriptionId, streamId, streamConsumer); State.Consumers.Add(pubSubState); } if (!string.IsNullOrWhiteSpace(filterData)) pubSubState.AddFilter(filterData); LogPubSubCounts("RegisterConsumer {0}", streamConsumer); await WriteStateAsync(); StreamInstruments.PubSubConsumersTotal.Add(1); } catch (Exception exc) { LogErrorRegisterConsumer(streamId, subscriptionId, streamConsumer, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } int numProducers = State.Producers.Count; if (numProducers <= 0) return; LogDebugNotifyProducersOfNewConsumer(numProducers, streamConsumer, new(State.Producers)); // Notify producers about a new streamConsumer. var tasks = new List(); var producers = State.Producers.ToList(); int initialProducerCount = producers.Count; try { foreach (PubSubPublisherState producerState in producers) { tasks.Add(ExecuteProducerTask(producerState, p => p.AddSubscriber(subscriptionId, streamId, streamConsumer, filterData))); } Exception? exception = null; try { await Task.WhenAll(tasks); } catch (Exception exc) { exception = exc; } // if the number of producers has been changed, resave state. if (State.Producers.Count != initialProducerCount) { await WriteStateAsync(); StreamInstruments.PubSubConsumersTotal.Add(-(initialProducerCount - State.Producers.Count)); } if (exception != null) { ExceptionDispatchInfo.Capture(exception).Throw(); } } catch (Exception exc) { LogErrorRegisterConsumerFailed( streamId, subscriptionId, streamConsumer, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } } private void RemoveProducer(PubSubPublisherState producer) { LogWarningProducerIsDead(producer, producer.Stream); State.Producers.Remove(producer); } public async Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId) { StreamInstruments.PubSubConsumersRemoved.Add(1); try { int numRemoved = State.Consumers.RemoveWhere(c => c.Equals(subscriptionId)); LogPubSubCounts("UnregisterSubscription {0} NumRemoved={1}", subscriptionId, numRemoved); if (await TryClearState()) { // If state was cleared expedite Deactivation DeactivateOnIdle(); } else { if (numRemoved != 0) { await WriteStateAsync(); } await NotifyProducersOfRemovedSubscription(subscriptionId, streamId); } StreamInstruments.PubSubConsumersTotal.Add(-numRemoved); } catch (Exception exc) { LogErrorUnregisterConsumer(streamId, subscriptionId, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } } public Task ProducerCount(QualifiedStreamId streamId) { return Task.FromResult(State.Producers.Count); } public Task ConsumerCount(QualifiedStreamId streamId) { return Task.FromResult(GetConsumersForStream(streamId).Length); } public Task DiagGetConsumers(QualifiedStreamId streamId) { return Task.FromResult(GetConsumersForStream(streamId)); } private PubSubSubscriptionState[] GetConsumersForStream(QualifiedStreamId streamId) { return State.Consumers.Where(c => !c.IsFaulted && c.Stream.Equals(streamId)).ToArray(); } private void LogPubSubCounts(string fmt, params object[] args) { if (_logger.IsEnabled(LogLevel.Debug) || DEBUG_PUB_SUB) { int numProducers = 0; int numConsumers = 0; if (State?.Producers != null) numProducers = State.Producers.Count; if (State?.Consumers != null) numConsumers = State.Consumers.Count; string when = args != null && args.Length != 0 ? string.Format(fmt, args) : fmt; _logger.LogDebug("{When}. Now have total of {ProducerCount} producers and {ConsumerCount} consumers. All Consumers = {Consumers}, All Producers = {Producers}", when, numProducers, numConsumers, Utils.EnumerableToString(State?.Consumers), Utils.EnumerableToString(State?.Producers)); } } // Check that what we have cached locally matches what is in the persistent table. public async Task Validate() { var captureProducers = State.Producers; var captureConsumers = State.Consumers; await ReadStateAsync(); if (captureProducers.Count != State.Producers.Count) { throw new OrleansException( $"State mismatch between PubSubRendezvousGrain and its persistent state. captureProducers.Count={captureProducers.Count}, State.Producers.Count={State.Producers.Count}"); } if (captureProducers.Any(producer => !State.Producers.Contains(producer))) { throw new OrleansException( $"State mismatch between PubSubRendezvousGrain and its persistent state. captureProducers={Utils.EnumerableToString(captureProducers)}, State.Producers={Utils.EnumerableToString(State.Producers)}"); } if (captureConsumers.Count != State.Consumers.Count) { LogPubSubCounts("Validate: Consumer count mismatch"); throw new OrleansException( $"State mismatch between PubSubRendezvousGrain and its persistent state. captureConsumers.Count={captureConsumers.Count}, State.Consumers.Count={State.Consumers.Count}"); } if (captureConsumers.Any(consumer => !State.Consumers.Contains(consumer))) { throw new OrleansException( $"State mismatch between PubSubRendezvousGrain and its persistent state. captureConsumers={Utils.EnumerableToString(captureConsumers)}, State.Consumers={Utils.EnumerableToString(State.Consumers)}"); } } public Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer) { if (streamConsumer != default) { List subscriptions = State.Consumers.Where(c => !c.IsFaulted && c.Consumer.Equals(streamConsumer)) .Select( c => new StreamSubscription(c.SubscriptionId.Guid, streamId.ProviderName, streamId, streamConsumer)).ToList(); return Task.FromResult(subscriptions); } else { List subscriptions = State.Consumers.Where(c => !c.IsFaulted) .Select( c => new StreamSubscription(c.SubscriptionId.Guid, streamId.ProviderName, streamId, c.Consumer)).ToList(); return Task.FromResult(subscriptions); } } public async Task FaultSubscription(GuidId subscriptionId) { var pubSubState = State.Consumers.FirstOrDefault(s => s.Equals(subscriptionId)); if (pubSubState == null) { return; } try { pubSubState.Fault(); LogDebugSettingSubscriptionToFaulted(subscriptionId); await WriteStateAsync(); await NotifyProducersOfRemovedSubscription(pubSubState.SubscriptionId, pubSubState.Stream); } catch (Exception exc) { LogErrorSetSubscriptionToFaulted(subscriptionId, exc); // Corrupted state, deactivate grain. DeactivateOnIdle(); throw; } } private async Task NotifyProducersOfRemovedSubscription(GuidId subscriptionId, QualifiedStreamId streamId) { int numProducersBeforeNotify = State.Producers.Count; if (numProducersBeforeNotify > 0) { LogDebugNotifyProducersOfRemovedConsumer(numProducersBeforeNotify); // Notify producers about unregistered consumer. List tasks = State.Producers .Select(producerState => ExecuteProducerTask(producerState, p => p.RemoveSubscriber(subscriptionId, streamId))) .ToList(); await Task.WhenAll(tasks); //if producers got removed if (State.Producers.Count < numProducersBeforeNotify) await WriteStateAsync(); } } /// /// Try clear state will only clear the state if there are no producers or consumers. /// /// private async Task TryClearState() { if (State.Producers.Count == 0 && State.Consumers.Count == 0) // + we already know that numProducers == 0 from previous if-clause { await ClearStateAsync(); //State contains no producers or consumers, remove it from storage return true; } return false; } private async Task ExecuteProducerTask(PubSubPublisherState producer, Func producerTask) { try { var extension = GrainFactory.GetGrain(producer.Producer); await producerTask(extension); } catch (GrainExtensionNotInstalledException) { RemoveProducer(producer); } catch (ClientNotAvailableException) { RemoveProducer(producer); } catch (OrleansMessageRejectionException) { // if producer is a system target on and unavailable silo, remove it. if (producer.Producer.IsSystemTarget()) { RemoveProducer(producer); } else // otherwise, throw { throw; } } } private Task ReadStateAsync() => _storage.ReadStateAsync(); private Task WriteStateAsync() => _storage.WriteStateAsync(); private Task ClearStateAsync() => _storage.ClearStateAsync(); void IGrainMigrationParticipant.OnDehydrate(IDehydrationContext dehydrationContext) => _storage.OnDehydrate(dehydrationContext); void IGrainMigrationParticipant.OnRehydrate(IRehydrationContext rehydrationContext) => _storage.OnRehydrate(rehydrationContext); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_RegisterProducerFailed, Message = "Failed to register a stream producer. Stream: {StreamId}, Producer: {StreamProducer}" )] private partial void LogErrorRegisterProducer(QualifiedStreamId streamId, GrainId streamProducer, Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_UnregisterProducerFailed, Message = "Failed to unregister a stream producer. Stream: {StreamId}, Producer: {StreamProducer}" )] private partial void LogErrorUnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer, Exception exception); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_RegisterConsumerFailed, Message = "Failed to register a stream consumer. Stream: {StreamId}, SubscriptionId {SubscriptionId}, Consumer: {StreamConsumer}" )] private partial void LogErrorRegisterConsumer(QualifiedStreamId streamId, GuidId subscriptionId, GrainId streamConsumer, Exception exception); private readonly struct ProducersLogRecord(HashSet producers) { public override string ToString() => Utils.EnumerableToString(producers); } [LoggerMessage( Level = LogLevel.Debug, Message = "Notifying {ProducerCount} existing producer(s) about new consumer {Consumer}. Producers={Producers}" )] private partial void LogDebugNotifyProducersOfNewConsumer(int producerCount, GrainId consumer, ProducersLogRecord producers); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_RegisterConsumerFailed, Message = "Failed to update producers while registering a stream consumer. Stream: {StreamId}, SubscriptionId {SubscriptionId}, Consumer: {StreamConsumer}" )] private partial void LogErrorRegisterConsumerFailed(QualifiedStreamId streamId, GuidId subscriptionId, GrainId streamConsumer, Exception exception); [LoggerMessage( Level = LogLevel.Warning, EventId = (int)ErrorCode.Stream_ProducerIsDead, Message = "Producer {Producer} on stream {StreamId} is no longer active - permanently removing producer." )] private partial void LogWarningProducerIsDead(PubSubPublisherState producer, QualifiedStreamId streamId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_UnregisterConsumerFailed, Message = "Failed to unregister a stream consumer. Stream: {StreamId}, SubscriptionId {SubscriptionId}" )] private partial void LogErrorUnregisterConsumer(QualifiedStreamId streamId, GuidId subscriptionId, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Setting subscription {SubscriptionId} to a faulted state." )] private partial void LogDebugSettingSubscriptionToFaulted(GuidId subscriptionId); [LoggerMessage( Level = LogLevel.Error, EventId = (int)ErrorCode.Stream_SetSubscriptionToFaultedFailed, Message = "Failed to set subscription state to faulted. SubscriptionId {SubscriptionId}" )] private partial void LogErrorSetSubscriptionToFaulted(GuidId subscriptionId, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Notifying {ProducerCountBeforeNotify} existing producers about unregistered consumer." )] private partial void LogDebugNotifyProducersOfRemovedConsumer(int producerCountBeforeNotify); } } ================================================ FILE: src/Orleans.Streaming/PubSub/PubSubSubscriptionState.cs ================================================ using System; using Newtonsoft.Json; using Orleans.Runtime; namespace Orleans.Streams { [Serializable] [JsonObject(MemberSerialization.OptIn)] [GenerateSerializer] public sealed class PubSubSubscriptionState : IEquatable { public enum SubscriptionStates { Active, Faulted, } // IMPORTANT!!!!! // These fields have to be public non-readonly for JSonSerialization to work! // Implement ISerializable if changing any of them to readonly [JsonProperty] [Id(0)] public GuidId SubscriptionId; [JsonProperty] [Id(1)] public QualifiedStreamId Stream; [JsonProperty] [Id(2)] public GrainId Consumer; // the field needs to be of a public type, otherwise we will not generate an Orleans serializer for that class. [JsonProperty] [Id(3)] public string FilterData; // Serialized func info [JsonProperty] [Id(4)] public SubscriptionStates state; [JsonIgnore] public bool IsFaulted { get { return state == SubscriptionStates.Faulted; } } // This constructor has to be public for JSonSerialization to work! // Implement ISerializable if changing it to non-public public PubSubSubscriptionState( GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer) { SubscriptionId = subscriptionId; Stream = streamId; Consumer = streamConsumer; state = SubscriptionStates.Active; } public void AddFilter(string filterData) { this.FilterData = filterData; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; // Note: Can't use the 'as' operator on PubSubSubscriptionState because it is a struct. return obj is PubSubSubscriptionState && Equals((PubSubSubscriptionState) obj); } public bool Equals(PubSubSubscriptionState other) { if ((object)other == null) return false; // Note: PubSubSubscriptionState is a struct, so 'other' can never be null. return Equals(other.SubscriptionId); } public bool Equals(GuidId subscriptionId) { if (ReferenceEquals(null, subscriptionId)) return false; return SubscriptionId.Equals(subscriptionId); } public override int GetHashCode() { return SubscriptionId.GetHashCode(); } public static bool operator ==(PubSubSubscriptionState left, PubSubSubscriptionState right) { if ((object)left == null && (object)right == null) return true; if ((object)left != null) { return left.Equals(right); } return false; } public static bool operator !=(PubSubSubscriptionState left, PubSubSubscriptionState right) { return !(left == right); } public override string ToString() { return string.Format("PubSubSubscriptionState:SubscriptionId={0},StreamId={1},Consumer={2}.", SubscriptionId, Stream, Consumer); } public void Fault() { state = SubscriptionStates.Faulted; } } } ================================================ FILE: src/Orleans.Streaming/PubSub/StreamPubSubImpl.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { internal class StreamPubSubImpl : IStreamPubSub { private readonly IStreamPubSub explicitPubSub; private readonly ImplicitStreamPubSub implicitPubSub; public StreamPubSubImpl(IStreamPubSub explicitPubSub, ImplicitStreamPubSub implicitPubSub) { if (explicitPubSub == null) { throw new ArgumentNullException(nameof(explicitPubSub)); } if (implicitPubSub == null) { throw new ArgumentNullException(nameof(implicitPubSub)); } this.explicitPubSub = explicitPubSub; this.implicitPubSub = implicitPubSub; } public async Task> RegisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { ISet explicitRes = await explicitPubSub.RegisterProducer(streamId, streamProducer); ISet implicitRes = await implicitPubSub.RegisterProducer(streamId, streamProducer); explicitRes.UnionWith(implicitRes); return explicitRes; } public Task UnregisterProducer(QualifiedStreamId streamId, GrainId streamProducer) { return explicitPubSub.UnregisterProducer(streamId, streamProducer); } public Task RegisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId, GrainId streamConsumer, string filterData) { return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId) ? implicitPubSub.RegisterConsumer(subscriptionId, streamId, streamConsumer, filterData) : explicitPubSub.RegisterConsumer(subscriptionId, streamId, streamConsumer, filterData); } public Task UnregisterConsumer(GuidId subscriptionId, QualifiedStreamId streamId) { return implicitPubSub.IsImplicitSubscriber(subscriptionId, streamId) ? implicitPubSub.UnregisterConsumer(subscriptionId, streamId) : explicitPubSub.UnregisterConsumer(subscriptionId, streamId); } public Task ProducerCount(QualifiedStreamId streamId) { return explicitPubSub.ProducerCount(streamId); } public Task ConsumerCount(QualifiedStreamId streamId) { return explicitPubSub.ConsumerCount(streamId); } public async Task> GetAllSubscriptions(QualifiedStreamId streamId, GrainId streamConsumer) { if (streamConsumer != default) { return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId) ? await implicitPubSub.GetAllSubscriptions(streamId, streamConsumer) : await explicitPubSub.GetAllSubscriptions(streamId, streamConsumer); } else { var implicitSubs = await implicitPubSub.GetAllSubscriptions(streamId); var explicitSubs = await explicitPubSub.GetAllSubscriptions(streamId); return implicitSubs.Concat(explicitSubs).ToList(); } } public GuidId CreateSubscriptionId(QualifiedStreamId streamId, GrainId streamConsumer) { return implicitPubSub.IsImplicitSubscriber(streamConsumer, streamId) ? implicitPubSub.CreateSubscriptionId(streamId, streamConsumer) : explicitPubSub.CreateSubscriptionId(streamId, streamConsumer); } public Task FaultSubscription(QualifiedStreamId streamId, GuidId subscriptionId) { return implicitPubSub.IsImplicitSubscriber(subscriptionId, streamId) ? implicitPubSub.FaultSubscription(streamId, subscriptionId) : explicitPubSub.FaultSubscription(streamId, subscriptionId); } } } ================================================ FILE: src/Orleans.Streaming/PubSub/StreamSubscriptionManagerExtensions.cs ================================================ using Orleans.Streams.Core; using System; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams.PubSub { /// /// Extension methods for . /// public static class StreamSubscriptionManagerExtensions { /// /// Subscribes the specified grain to the specified stream. /// /// The grain interface type. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain to subscribe. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, GrainId grainId) where TGrainInterface : IGrainWithGuidKey { var grainRef = grainFactory.GetGrain(grainId) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Subscribes the specified grain to the specified stream. /// /// An interface which the grain is the primary implementation of. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain's primary key. /// The grain class name prefix. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey { var grainRef = grainFactory.GetGrain(primaryKey, grainClassNamePrefix) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Subscribes the specified grain to the specified stream. /// /// An interface which the grain is the primary implementation of. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain's primary key. /// The grain class name prefix. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey { var grainRef = grainFactory.GetGrain(primaryKey, grainClassNamePrefix) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Subscribes the specified grain to the specified stream. /// /// An interface which the grain is the primary implementation of. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain's primary key. /// The grain class name prefix. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey { var grainRef = grainFactory.GetGrain(primaryKey, grainClassNamePrefix) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Subscribes the specified grain to the specified stream. /// /// An interface which the grain is the primary implementation of. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain's primary key. /// The grain's key extension. /// The grain class name prefix. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, Guid primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey { var grainRef = grainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Subscribes the specified grain to the specified stream. /// /// An interface which the grain is the primary implementation of. /// The manager. /// The grain factory. /// The stream identifier. /// Name of the stream provider. /// The grain's primary key. /// The grain's key extension. /// The grain class name prefix. /// The newly added subscription. public static Task AddSubscription( this IStreamSubscriptionManager manager, IGrainFactory grainFactory, StreamId streamId, string streamProviderName, long primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey { var grainRef = grainFactory.GetGrain(primaryKey, keyExtension, grainClassNamePrefix) as GrainReference; return manager.AddSubscription(streamProviderName, streamId, grainRef); } /// /// Returns the for the provided stream provider. /// /// The stream provider. /// The manager. /// if the stream subscription manager could be retrieved, otherwise. public static bool TryGetStreamSubscriptionManager(this IStreamProvider streamProvider, out IStreamSubscriptionManager manager) { manager = null; if (streamProvider is IStreamSubscriptionManagerRetriever) { var streamSubManagerRetriever = streamProvider as IStreamSubscriptionManagerRetriever; manager = streamSubManagerRetriever.GetStreamSubscriptionManager(); //implicit only stream provider don't have a subscription manager configured //so manager can be null; return manager != null; } return false; } } } ================================================ FILE: src/Orleans.Streaming/PubSub/SubscriptionMarker.cs ================================================ using System; namespace Orleans.Streams { /// /// Mark a subscriptionId as either an implicit subscription Id, or an explicit subscription Id. /// high bit of last byte in guild is the subscription type flag. /// 1: implicit subscription /// 0: explicit subscription /// internal static class SubscriptionMarker { internal static Guid MarkAsExplicitSubscriptionId(Guid subscriptionGuid) { return MarkSubscriptionGuid(subscriptionGuid, false); } internal static Guid MarkAsImplictSubscriptionId(Guid subscriptionGuid) { return MarkSubscriptionGuid(subscriptionGuid, true); } internal static bool IsImplicitSubscription(Guid subscriptionGuid) { Span guidBytes = stackalloc byte[16]; subscriptionGuid.TryWriteBytes(guidBytes); // return true if high bit of last byte is set return (guidBytes[15] & 0x80) != 0; } private static Guid MarkSubscriptionGuid(Guid subscriptionGuid, bool isImplicitSubscription) { Span guidBytes = stackalloc byte[16]; subscriptionGuid.TryWriteBytes(guidBytes); if (isImplicitSubscription) { // set high bit of last byte guidBytes[15] |= 0x80; } else { // clear high bit of last byte guidBytes[15] &= 0x7f; } return new Guid(guidBytes); } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/AggregatedQueueFlowController.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans.Streams { /// /// A which aggregates multiple other values. /// public class AggregatedQueueFlowController : List, IQueueFlowController { private readonly int defaultMaxAddCount; /// /// Initializes a new instance of the class. /// /// The default maximum add count, see . public AggregatedQueueFlowController(int defaultMaxAddCount) { this.defaultMaxAddCount = defaultMaxAddCount; } /// public int GetMaxAddCount() { return this.Aggregate(defaultMaxAddCount, (count, fc) => Math.Min(count, fc.GetMaxAddCount())); } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/BatchContainerBatch.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Runtime; namespace Orleans.Streams { /// /// A batch of batch containers, that if configured (see StreamPullingAgentOptions), will be the data pulled by the /// PersistenStreamPullingAgent from it's underlying cache /// [GenerateSerializer] public sealed class BatchContainerBatch : IBatchContainerBatch { /// /// Gets the stream identifier for the stream this batch is part of. /// Derived from the first batch container in the batch. /// [Id(0)] public StreamId StreamId { get; } /// /// Gets the stream Sequence Token for the start of this batch. /// Derived from the first batch container in the batch. /// [Id(1)] public StreamSequenceToken SequenceToken { get; } /// /// Gets the batch containers comprising this batch /// [Id(2)] public List BatchContainers { get; } public BatchContainerBatch(List batchContainers) { if ((batchContainers == null) || !batchContainers.Any()) { throw new ArgumentNullException(nameof(batchContainers)); } this.BatchContainers = batchContainers; var containerDelegate = this.BatchContainers[0]; this.SequenceToken = containerDelegate.SequenceToken; this.StreamId = containerDelegate.StreamId; } /// public IEnumerable> GetEvents() { return this.BatchContainers.SelectMany(batchContainer => batchContainer.GetEvents()); } /// public bool ImportRequestContext() { return this.BatchContainers[0].ImportRequestContext(); } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/DataNotAvailableException.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Runtime; namespace Orleans.Streams { /// /// Exception indicates that the requested data is not available. /// [Serializable] [GenerateSerializer] public class DataNotAvailableException : OrleansException { /// /// Initializes a new instance of the class. /// public DataNotAvailableException() : this("Data not found") { } /// /// Initializes a new instance of the class. /// /// The message. public DataNotAvailableException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner. public DataNotAvailableException(string message, Exception inner) : base(message, inner) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] protected DataNotAvailableException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Indicates that the queue message cache is full. /// [Serializable] [GenerateSerializer] public sealed class CacheFullException : OrleansException { /// /// Initializes a new instance of the class. /// public CacheFullException() : this("Queue message cache is full") { } /// /// Initializes a new instance of the class. /// /// The message. public CacheFullException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner. public CacheFullException(string message, Exception inner) : base(message, inner) { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private CacheFullException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/HashRing.cs ================================================ using System; using Orleans.Runtime; namespace Orleans.Streams; internal readonly struct HashRing { private readonly QueueId[] _ring; public HashRing(QueueId[] ring) { Array.Sort(ring, (x, y) => x.GetUniformHashCode().CompareTo(y.GetUniformHashCode())); _ring = ring; } public QueueId[] GetAllRingMembers() => _ring; public QueueId CalculateResponsible(uint uniformHashCode) { // use clockwise ... current code in membershipOracle.CalculateTargetSilo() does counter-clockwise ... var index = _ring.AsSpan().BinarySearch(new Searcher(uniformHashCode)); if (index < 0) { index = ~index; // if not found in traversal, then first element should be returned (we are on a ring) if (index == _ring.Length) index = 0; } return _ring[index]; } private readonly struct Searcher : IComparable { private readonly uint _value; public Searcher(uint value) => _value = value; public int CompareTo(QueueId other) => _value.CompareTo(other.GetUniformHashCode()); } public override string ToString() => $"All QueueIds:{Environment.NewLine}{(Utils.EnumerableToString(_ring, elem => $"{elem}/x{elem.GetUniformHashCode():X8}", Environment.NewLine, false))}"; } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/HashRingBasedStreamQueueMapper.cs ================================================ using System; using System.Collections.Generic; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.Streams { /// /// A and hence which balances queues by mapping them onto a hash ring consisting of silos. /// public class HashRingBasedStreamQueueMapper : IConsistentRingStreamQueueMapper { private readonly HashRing hashRing; /// /// Initializes a new instance of the class. /// /// The options. /// The queue name prefix. public HashRingBasedStreamQueueMapper(HashRingStreamQueueMapperOptions options, string queueNamePrefix) : this(options.TotalQueueCount, queueNamePrefix) { } internal HashRingBasedStreamQueueMapper(int numQueues, string queueNamePrefix) { if (numQueues < 1) throw new ArgumentException("TotalQueueCount must be at least 1"); var queueIds = new QueueId[numQueues]; if (numQueues == 1) { queueIds[0] = QueueId.GetQueueId(queueNamePrefix, 0, 0); } else { uint portion = checked((uint)(RangeFactory.RING_SIZE / numQueues + 1)); for (uint i = 0; i < numQueues; i++) { uint uniformHashCode = checked(portion * i); queueIds[i] = QueueId.GetQueueId(queueNamePrefix, i, uniformHashCode); } } this.hashRing = new(queueIds); } /// public IEnumerable GetQueuesForRange(IRingRange range) { var ls = new List(); foreach (QueueId queueId in hashRing.GetAllRingMembers()) { if (range.InRange(queueId.GetUniformHashCode())) { ls.Add(queueId); } } return ls; } /// public IEnumerable GetAllQueues() => hashRing.GetAllRingMembers(); /// public QueueId GetQueueForStream(StreamId streamId) => hashRing.CalculateResponsible((uint)streamId.GetHashCode()); /// public override string ToString() => hashRing.ToString(); } /// /// Queue mapper that tracks which partition was mapped to which QueueId /// public sealed class HashRingBasedPartitionedStreamQueueMapper : HashRingBasedStreamQueueMapper { private readonly Dictionary _partitions; /// /// Queue mapper that tracks which partition was mapped to which QueueId /// /// List of partitions /// Prefix for QueueIds. Must be unique per stream provider public HashRingBasedPartitionedStreamQueueMapper(IReadOnlyList partitionIds, string queueNamePrefix) : base(partitionIds.Count, queueNamePrefix) { var queues = (QueueId[])GetAllQueues(); _partitions = new(queues.Length); for (var i = 0; i < queues.Length; i++) _partitions.Add(queues[i], partitionIds[i]); } /// /// Gets the partition by QueueId /// /// /// public string QueueToPartition(QueueId queue) => _partitions.TryGetValue(queue, out var p) ? p : throw new ArgumentOutOfRangeException($"Queue {queue:H}"); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/HashRingStreamQueueMapperOptions.cs ================================================ using Orleans.Streams; namespace Orleans.Configuration { /// /// Options for . /// public class HashRingStreamQueueMapperOptions { /// /// Gets or sets the total queue count. /// /// The total queue count. public int TotalQueueCount { get; set; } = DEFAULT_NUM_QUEUES; /// /// The default number queues, which should be a power of two. /// public const int DEFAULT_NUM_QUEUES = 8; } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IBatchContainer.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { /// /// Each queue message is allowed to be a heterogeneous, ordered set of events. /// contains these events and allows users to query the batch for a specific type of event. /// public interface IBatchContainer { /// /// Ges the stream identifier for the stream this batch is part of. /// StreamId StreamId { get; } /// /// Gets events of a specific type from the batch. /// /// /// IEnumerable> GetEvents(); /// /// Ges the stream sequence token for the start of this batch. /// StreamSequenceToken SequenceToken { get; } /// /// Gives an opportunity to to set any data in the before this is sent to consumers. /// It can be the data that was set at the time event was generated and enqueued into the persistent provider or any other data. /// /// if the was indeed modified, otherwise. bool ImportRequestContext(); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IBatchContainerBatch.cs ================================================ using System.Collections.Generic; namespace Orleans.Streams { /// /// A batch of queue messages (see IBatchContainer for description of batch contents) /// public interface IBatchContainerBatch : IBatchContainer { /// /// Gets the batch containers comprising this batch /// List BatchContainers { get; } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IConsistentRingStreamQueueMapper.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { /// /// The stream queue mapper is responsible for mapping ring ranges from the load balancing ring provider to stream queues. /// Implementation must be thread safe. /// public interface IConsistentRingStreamQueueMapper : IStreamQueueMapper { /// /// Gets the queues which map to the specified range. /// /// The range. /// The queues which map to the specified range. IEnumerable GetQueuesForRange(IRingRange range); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueAdapter.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Streams { /// /// Stream queue storage adapter. This is an abstraction layer that hides the implementation details of the underlying queuing system. /// public interface IQueueAdapter { /// /// Gets the name of the adapter. Primarily for logging purposes /// string Name { get; } /// /// Writes a set of events to the queue as a single batch associated with the provided streamId. /// /// The queue element type. /// The stream identifier. /// The events. /// The token. /// The request context. /// Task. Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext); /// /// Creates a queue receiver for the specified queueId /// /// The queue identifier. /// The receiver. IQueueAdapterReceiver CreateReceiver(QueueId queueId); /// /// Gets a value indicating whether this is a rewindable stream adapter - supports subscribing from previous point in time. /// /// True if this is a rewindable stream adapter, false otherwise. bool IsRewindable { get; } /// /// Gets the direction of this queue adapter: , , or . /// /// The direction in which this adapter provides data. StreamProviderDirection Direction { get; } } /// /// Extension methods for /// public static class QueueAdapterExtensions { /// /// Writes a set of events to the queue as a single batch associated with the provided . /// /// The queue element type. /// The adapter. /// The stream identifier. /// The event. /// The token. /// The request context. /// A representing the operation. public static Task QueueMessageAsync(this IQueueAdapter adapter, StreamId streamId, T evt, StreamSequenceToken token, Dictionary requestContext) { return adapter.QueueMessageBatchAsync(streamId, new[] { evt }, token, requestContext); } } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueAdapterCache.cs ================================================ namespace Orleans.Streams { /// /// Functionality for creating an for a given queue. /// public interface IQueueAdapterCache { /// /// Create a cache for a given queue id /// /// The queue id. /// The queue cache.. IQueueCache CreateQueueCache(QueueId queueId); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueAdapterFactory.cs ================================================ using System.Threading.Tasks; namespace Orleans.Streams { /// /// Adapter factory. This should create an adapter from the stream provider configuration /// public interface IQueueAdapterFactory { /// /// Creates a queue adapter. /// /// The queue adapter Task CreateAdapter(); /// /// Creates queue message cache adapter. /// /// The queue adapter cache. IQueueAdapterCache GetQueueAdapterCache(); /// /// Creates a queue mapper. /// /// The queue mapper. IStreamQueueMapper GetStreamQueueMapper(); /// /// Acquire delivery failure handler for a queue /// /// The queue identifier. /// The stream failure handler. Task GetDeliveryFailureHandler(QueueId queueId); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueAdapterReceiver.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Streams { /// /// Receives batches of messages from a single partition of a message queue. /// public interface IQueueAdapterReceiver { /// /// Initializes this receiver. /// /// A representing the operation. Task Initialize(TimeSpan timeout); /// /// Retrieves batches from a message queue. /// /// /// The maximum number of message batches to retrieve. /// /// The message batches. Task> GetQueueMessagesAsync(int maxCount); /// /// Notifies the adapter receiver that the messages were delivered to all consumers, /// so the receiver can take an appropriate action (e.g., delete the messages from a message queue). /// /// /// The message batches. /// /// A representing the operation. Task MessagesDeliveredAsync(IList messages); /// /// Receiver is no longer used. Shutdown and clean up. /// /// A representing the operation. Task Shutdown(TimeSpan timeout); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueCache.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { public interface IQueueCache : IQueueFlowController { /// /// Adds messages to the cache. /// /// The message batches. void AddToCache(IList messages); /// /// Requests that the cache purge any items that can be purged. /// /// The purged items. /// if items were successfully purged from the cache., otherwise. bool TryPurgeFromCache(out IList purgedItems); /// /// Acquire a stream message cursor. This can be used to retrieve messages from the /// cache starting at the location indicated by the provided token. /// /// The stream identifier. /// The token. /// The queue cache cursor. IQueueCacheCursor GetCacheCursor(StreamId streamId, StreamSequenceToken token); /// /// Returns if this cache is under pressure, otherwise. /// /// if this cache is under pressure; otherwise, . bool IsUnderPressure(); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueCacheCursor.cs ================================================ using System; namespace Orleans.Streams { /// /// Enumerates the messages in a stream. /// public interface IQueueCacheCursor : IDisposable { /// /// Get the current value. /// /// The resulting exception. /// /// Returns the current batch container. /// If null then the stream has completed or there was a stream error. /// If there was a stream error, an error exception will be provided in the output. /// IBatchContainer GetCurrent(out Exception exception); /// /// Move to next message in the stream. /// If it returns false, there are no more messages. The enumerator is still /// valid however and can be called again when more data has come in on this /// stream. /// /// if there are more items, otherwise bool MoveNext(); /// /// Refreshes the cache cursor. Called when new data is added into a cache. /// /// The token. void Refresh(StreamSequenceToken token); /// /// Records that delivery of the current event has failed /// void RecordDeliveryFailure(); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IQueueFlowController.cs ================================================ namespace Orleans.Streams { /// /// Functionality for controlling the flow of retrieved queue items. /// public interface IQueueFlowController { /// /// Gets the maximum number of items that can be added. /// /// /// The maximum number of items that can be added. /// int GetMaxAddCount(); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/IStreamQueueMapper.cs ================================================ using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Streams { /// /// The stream queue mapper returns a list of all queues and is also responsible for mapping streams to queues. /// Implementation must be thread safe. /// public interface IStreamQueueMapper { /// /// Gets all queues. /// /// All queues. IEnumerable GetAllQueues(); /// /// Gets the queue for the specified stream. /// /// The stream identifier. /// The queue responsible for the specified stream. QueueId GetQueueForStream(StreamId streamId); } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/QueueAdapterConstants.cs ================================================ namespace Orleans.Streams { /// /// Constants for queue adapters. /// public static class QueueAdapterConstants { /// /// The value used to indicate an unlimited number of messages can be retrieved, when returned by . /// public const int UNLIMITED_GET_QUEUE_MSG = -1; } } ================================================ FILE: src/Orleans.Streaming/QueueAdapters/QueueCacheMissException.cs ================================================ using System; using System.Globalization; using System.Runtime.Serialization; namespace Orleans.Streams { /// /// Exception indicates that the requested message is not in the queue cache. /// [Serializable] [GenerateSerializer] public sealed class QueueCacheMissException : DataNotAvailableException { private const string MESSAGE_FORMAT = "Item not found in cache. Requested: {0}, Low: {1}, High: {2}"; /// /// Gets the requested sequence token. /// /// The requested sequence token. [Id(0)] public string Requested { get; private set; } /// /// Gets the earliest available sequence token. /// [Id(1)] public string Low { get; private set; } /// /// Gets the latest available sequence token. /// [Id(2)] public string High { get; private set; } /// /// Initializes a new instance of the class. /// public QueueCacheMissException() : this("Item no longer in cache") { } /// /// Initializes a new instance of the class. /// /// The message. public QueueCacheMissException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public QueueCacheMissException(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the class. /// /// The requested sequence token. /// The earliest available sequence token. /// The latest available sequence token. public QueueCacheMissException(StreamSequenceToken requested, StreamSequenceToken low, StreamSequenceToken high) : this(requested.ToString(), low.ToString(), high.ToString()) { } /// /// Initializes a new instance of the class. /// /// The requested sequence token. /// The earliest available sequence token. /// The latest available sequence token. public QueueCacheMissException(string requested, string low, string high) : this(string.Format(CultureInfo.InvariantCulture, MESSAGE_FORMAT, requested, low, high)) { Requested = requested; Low = low; High = high; } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private QueueCacheMissException(SerializationInfo info, StreamingContext context) : base(info, context) { Requested = info.GetString("Requested"); Low = info.GetString("Low"); High = info.GetString("High"); } /// [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Requested", Requested); info.AddValue("Low", Low); info.AddValue("High", High); base.GetObjectData(info, context); } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/BestFitBalancer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans.Streams { /// /// Best fit balancer keeps each active bucket responsible for its ideal set of resources, and redistributes /// resources from inactive buckets evenly over active buckets. If there are large numbers of inactive buckets, /// this can lead to quite a bit of shuffling of resources from inactive buckets as buckets come back online. /// Requirements: /// - Even distribution of resources across buckets /// - Must be consistent results for same inputs regardless of input order. /// - Minimize movement of resources when rebalancing from changes in active buckets. /// - Must be deterministic independent of previous distribution state. /// Algorithm: /// - On creation generate an ideal distribution of resources across all buckets, that is, each bucket has no more than 1 resource more /// than any other bucket. /// - When requesting new resource distribution for a list of active buckets: /// 1) Initialize the new distribution of each active bucket with the ideal resources for that bucket. This prevents /// these resources from ever being assigned to another bucket unless a bucket becomes inactive. /// 2) Build a list of inactive buckets. /// 3) For each inactive bucket, add its ideal resource allocation to the list of resources to be reallocated. /// 4) Order the active buckets by the number of resources allocated to each and begin assigning them more resources /// from the list of resources to be reallocated. /// i) Continue iterating over the active buckets assigning resources until there are no more resources that need /// reallocated. /// /// Type of bucket upon which resources will be distributed among /// Type of resources being distributed internal class BestFitBalancer where TBucket : IEquatable, IComparable where TResource : IEquatable, IComparable { private readonly Dictionary> idealDistribution; public Dictionary> IdealDistribution { get { return idealDistribution; } } /// /// Constructor. /// Initializes an ideal distribution to be used to aid in resource to bucket affinity. /// /// Buckets among which to distribute resources. /// Resources to be distributed. public BestFitBalancer(IEnumerable buckets, IEnumerable resources) { if (buckets == null) { throw new ArgumentNullException(nameof(buckets)); } if (resources == null) { throw new ArgumentNullException(nameof(resources)); } idealDistribution = BuildIdealDistribution(buckets, resources); } /// /// Gets a distribution for the active buckets. /// Any active buckets keep their ideal distribution. Resources from inactive buckets are redistributed evenly /// among the active buckets, starting with those with the fewest allocated resources. /// /// currently active buckets /// public Dictionary> GetDistribution(IEnumerable activeBuckets) { if (activeBuckets == null) { throw new ArgumentNullException(nameof(activeBuckets)); } // sanitize active buckets. Remove duplicates, ensure all buckets are valid HashSet activeBucketsSet = new HashSet(activeBuckets); foreach (var bucket in activeBucketsSet) { if (!idealDistribution.ContainsKey(bucket)) { throw new ArgumentOutOfRangeException(nameof(activeBuckets), string.Format("Active buckets contain a bucket {0} not in the master list.", bucket)); } } var newDistribution = new Dictionary>(); // if no buckets, return empty resource distribution if (activeBucketsSet.Count == 0) { return newDistribution; } // setup ideal distribution for active buckets and build list of all resources that need redistributed from inactive buckets var resourcesToRedistribute = new List(); foreach (var kv in idealDistribution) { if (activeBucketsSet.Contains(kv.Key)) newDistribution.Add(kv.Key, kv.Value); else resourcesToRedistribute.AddRange(kv.Value); } // redistribute remaining resources across the resource lists of the active buckets, resource lists with the fewest reasources first IOrderedEnumerable> sortedResourceLists = newDistribution.Values.OrderBy(resources => resources.Count); IEnumerator> resourceListenumerator = sortedResourceLists.GetEnumerator(); foreach (TResource resource in resourcesToRedistribute) { // if we reach the end, start over if (!resourceListenumerator.MoveNext()) { resourceListenumerator = sortedResourceLists.GetEnumerator(); resourceListenumerator.MoveNext(); } resourceListenumerator.Current.Add(resource); } return newDistribution; } /// /// Distribute resources evenly among buckets in a deterministic way. /// - Must distribute resources evenly regardless off order of inputs. /// /// Buckets among which to distribute resources. /// Resources to be distributed. /// Dictionary of resources evenly distributed among the buckets private static Dictionary> BuildIdealDistribution(IEnumerable buckets, IEnumerable resources) { var idealDistribution = new Dictionary>(); // Sanitize buckets. Remove duplicates and sort List bucketList = buckets.Distinct().ToList(); if (bucketList.Count == 0) { return idealDistribution; } bucketList.Sort(); // Sanitize resources. Removed duplicates and sort List resourceList = resources.Distinct().ToList(); resourceList.Sort(); // Distribute resources evenly among buckets var upperResourceCountPerBucket = (int)Math.Ceiling((double)resourceList.Count / bucketList.Count); var lowerResourceCountPerBucket = upperResourceCountPerBucket - 1; List.Enumerator resourceEnumerator = resourceList.GetEnumerator(); int bucketsToFillWithUpperResource = resourceList.Count % bucketList.Count; // a bucketsToFillWithUpperResource of 0 indicates resources are evenly devisible, so fill them all with upper resource count if (bucketsToFillWithUpperResource == 0) { bucketsToFillWithUpperResource = bucketList.Count; } int bucketsFilledCount = 0; foreach (TBucket bucket in bucketList) { // if we've filled the first bucketsToFillWithUpperResource buckets with upperResourceCountPerBucket // resources, fill the rest with lowerResourceCountPerBucket int resourcesToAddToBucket = bucketsFilledCount < bucketsToFillWithUpperResource ? upperResourceCountPerBucket : lowerResourceCountPerBucket; var bucketResources = new List(); idealDistribution.Add(bucket, bucketResources); while (resourceEnumerator.MoveNext()) { bucketResources.Add(resourceEnumerator.Current); if (bucketResources.Count >= resourcesToAddToBucket) { break; } } bucketsFilledCount++; } return idealDistribution; } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/ConsistentRingQueueBalancer.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Runtime.ConsistentRing; namespace Orleans.Streams { internal class ConsistentRingQueueBalancer : QueueBalancerBase, IStreamQueueBalancer, IRingRangeListener { private IConsistentRingStreamQueueMapper _streamQueueMapper; private IRingRange _myRange; public static IStreamQueueBalancer Create(IServiceProvider services, string name) { return ActivatorUtilities.CreateInstance(services); } public ConsistentRingQueueBalancer(IConsistentRingProvider consistentRingProvider, ILoggerFactory loggerFactory, IServiceProvider services, ILogger logger) : base(services, logger) { if (consistentRingProvider == null) { throw new ArgumentNullException("streamProviderRuntime"); } _myRange = consistentRingProvider.GetMyRange(); consistentRingProvider.SubscribeToRangeChangeEvents(this); } public override Task Initialize(IStreamQueueMapper queueMapper) { if (queueMapper == null) { throw new ArgumentNullException(nameof(queueMapper)); } if (queueMapper is not IConsistentRingStreamQueueMapper streamQueueMapper) { throw new ArgumentException("IStreamQueueMapper for ConsistentRingQueueBalancer should implement IConsistentRingStreamQueueMapper", nameof(queueMapper)); } _streamQueueMapper = streamQueueMapper; return base.Initialize(queueMapper); } public override IEnumerable GetMyQueues() { return _streamQueueMapper.GetQueuesForRange(_myRange); } protected override void OnClusterMembershipChange(HashSet activeSilos) { } public void RangeChangeNotification(IRingRange old, IRingRange now, bool increased) { _myRange = now; base.NotifyListeners().Ignore(); } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/DeploymentBasedQueueBalancer.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Runtime; using Microsoft.Extensions.Options; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Orleans.Streams { /// /// DeploymentBasedQueueBalancer is a stream queue balancer that uses deployment information to /// help balance queue distribution. /// DeploymentBasedQueueBalancer uses the deployment configuration to determine how many silos /// to expect and uses a silo status oracle to determine which of the silos are available. With /// this information it tries to balance the queues using a best fit resource balancing algorithm. /// public class DeploymentBasedQueueBalancer : QueueBalancerBase, IStreamQueueBalancer { private readonly ISiloStatusOracle siloStatusOracle; private readonly IDeploymentConfiguration deploymentConfig; private readonly DeploymentBasedQueueBalancerOptions options; private readonly ConcurrentDictionary immatureSilos; private List allQueues; private bool isStarting; public DeploymentBasedQueueBalancer( ISiloStatusOracle siloStatusOracle, IDeploymentConfiguration deploymentConfig, DeploymentBasedQueueBalancerOptions options, IServiceProvider services, ILogger logger) : base (services, logger) { this.siloStatusOracle = siloStatusOracle ?? throw new ArgumentNullException(nameof(siloStatusOracle)); this.deploymentConfig = deploymentConfig ?? throw new ArgumentNullException(nameof(deploymentConfig)); this.options = options; isStarting = true; // record all already active silos as already mature. // Even if they are not yet, they will be mature by the time I mature myself (after I become !isStarting). immatureSilos = new ConcurrentDictionary( from s in siloStatusOracle.GetApproximateSiloStatuses(true).Keys where !s.Equals(siloStatusOracle.SiloAddress) select new KeyValuePair(s, false)); } public static IStreamQueueBalancer Create(IServiceProvider services, string name, IDeploymentConfiguration deploymentConfiguration) { var options = services.GetRequiredService>().Get(name); return ActivatorUtilities.CreateInstance(services, options, deploymentConfiguration); } public override Task Initialize(IStreamQueueMapper queueMapper) { if (queueMapper == null) { throw new ArgumentNullException(nameof(queueMapper)); } this.allQueues = queueMapper.GetAllQueues().ToList(); NotifyAfterStart().Ignore(); return base.Initialize(queueMapper); } private async Task NotifyAfterStart() { await Task.Delay(this.options.SiloMaturityPeriod); isStarting = false; await NotifyListeners(); } private async Task RecordImmatureSilo(SiloAddress updatedSilo) { immatureSilos[updatedSilo] = true; // record as immature await Task.Delay(this.options.SiloMaturityPeriod); immatureSilos[updatedSilo] = false; // record as mature } public override IEnumerable GetMyQueues() { BestFitBalancer balancer = GetBalancer(); bool useIdealDistribution = this.options.IsFixed || isStarting; Dictionary> distribution = useIdealDistribution ? balancer.IdealDistribution : balancer.GetDistribution(GetActiveSilos(siloStatusOracle, immatureSilos)); List myQueues; if (distribution.TryGetValue(siloStatusOracle.SiloName, out myQueues)) { if (!useIdealDistribution) { HashSet queuesOfImmatureSilos = GetQueuesOfImmatureSilos(siloStatusOracle, immatureSilos, balancer.IdealDistribution); // filter queues that belong to immature silos myQueues.RemoveAll(queue => queuesOfImmatureSilos.Contains(queue)); } return myQueues; } return Enumerable.Empty(); } private static List GetActiveSilos(ISiloStatusOracle siloStatusOracle, ConcurrentDictionary immatureSilos) { var activeSiloNames = new List(); foreach (var kvp in siloStatusOracle.GetApproximateSiloStatuses(true)) { bool immatureBit; if (!(immatureSilos.TryGetValue(kvp.Key, out immatureBit) && immatureBit)) // if not immature now or any more { string siloName; if (siloStatusOracle.TryGetSiloName(kvp.Key, out siloName)) { activeSiloNames.Add(siloName); } } } return activeSiloNames; } /// /// Checks to see if deployment configuration has changed, by adding or removing silos. /// If so, it updates the list of all silo names and creates a new resource balancer. /// This should occur rarely. /// private BestFitBalancer GetBalancer() { var allSiloNames = deploymentConfig.GetAllSiloNames(); // rebuild balancer with new list of instance names return new BestFitBalancer(allSiloNames, allQueues); } private static HashSet GetQueuesOfImmatureSilos(ISiloStatusOracle siloStatusOracle, ConcurrentDictionary immatureSilos, Dictionary> idealDistribution) { HashSet queuesOfImmatureSilos = new HashSet(); foreach (var silo in immatureSilos.Where(s => s.Value)) // take only those from immature set that have their immature status bit set { string siloName; if (siloStatusOracle.TryGetSiloName(silo.Key, out siloName)) { List queues; if (idealDistribution.TryGetValue(siloName, out queues)) { queuesOfImmatureSilos.UnionWith(queues); } } } return queuesOfImmatureSilos; } protected override void OnClusterMembershipChange(HashSet activeSilos) { SignalClusterChange(activeSilos).Ignore(); } private async Task SignalClusterChange(HashSet activeSilos) { List tasks = new List(); // look at all currently active silos not including myself foreach (var silo in activeSilos) { if (!silo.Equals(siloStatusOracle.SiloAddress) && !immatureSilos.ContainsKey(silo)) { tasks.Add(RecordImmatureSilo(silo)); } } if (!isStarting) { // notify, uncoditionaly, and deal with changes in GetMyQueues() await NotifyListeners(); } if (tasks.Count > 0) { await Task.WhenAll(tasks); await this.NotifyListeners(); // notify, uncoditionaly, and deal with changes it in GetMyQueues() } } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/DeploymentBasedQueueBalancerOptions.cs ================================================ using System; using Orleans.Streams; namespace Orleans.Configuration { /// /// Options for . /// public class DeploymentBasedQueueBalancerOptions { /// /// Gets or sets the silo maturity period, which is the period of time to allow a silo to remain active for before rebalancing queues. /// /// The silo maturity period. public TimeSpan SiloMaturityPeriod { get; set; } = DEFAULT_SILO_MATURITY_PERIOD; /// /// The default silo maturity period. /// public static readonly TimeSpan DEFAULT_SILO_MATURITY_PERIOD = TimeSpan.FromMinutes(2); /// /// Gets or sets a value indicating whether to presume a static (fixed) cluster. /// public bool IsFixed { get; set; } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/IResourceSelector.cs ================================================ using System.Collections.Generic; namespace Orleans.Streams { /// /// IResourceSelector selects a certain amount of resources from a resource list /// /// internal interface IResourceSelector { /// /// Number of resources /// int Count { get; } /// /// Try to select certain count of resources from resource list, which doesn't overlap with existing selection /// /// /// /// List NextSelection(int newSelectionCount, List existingSelection); } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/LeaseBasedQueueBalancer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Configuration; using Orleans.LeaseProviders; using Orleans.Runtime; using Orleans.Runtime.Internal; namespace Orleans.Streams; /// /// LeaseBasedQueueBalancer. This balancer supports queue balancing in cluster auto-scale scenarios, /// unexpected server failure scenarios, and tries to support ideal distribution as much as possible. /// /// /// Initializes a new instance of the class. /// /// The name. /// The options. /// The lease provider. /// The services. /// The logger factory. public partial class LeaseBasedQueueBalancer( string name, LeaseBasedQueueBalancerOptions options, ILeaseProvider leaseProvider, IServiceProvider services, ILoggerFactory loggerFactory, TimeProvider timeProvider) : QueueBalancerBase(services, loggerFactory.CreateLogger($"{typeof(LeaseBasedQueueBalancer).FullName}.{name}")), IStreamQueueBalancer { private sealed class AcquiredQueue(int order, QueueId queueId, AcquiredLease lease) { public int LeaseOrder { get; set; } = order; public QueueId QueueId { get; set; } = queueId; public AcquiredLease AcquiredLease { get; set; } = lease; } private readonly LeaseBasedQueueBalancerOptions _options = options; private readonly ILeaseProvider _leaseProvider = leaseProvider; private readonly AsyncSerialExecutor _executor = new(); private readonly List _myQueues = []; private readonly PeriodicTimer _leaseMaintenanceTimer = new(Timeout.InfiniteTimeSpan, timeProvider); private readonly PeriodicTimer _leaseAcquisitionTimer = new(Timeout.InfiniteTimeSpan, timeProvider); private Task _leaseMaintenanceTimerTask = Task.CompletedTask; private Task _leaseAcquisitionTimerTask = Task.CompletedTask; private RoundRobinSelector _queueSelector; private int _allQueuesCount; private int _responsibility; private int _leaseOrder; /// /// Creates a new instance. /// /// The services. /// The name. /// The new instance. public static IStreamQueueBalancer Create(IServiceProvider services, string name) { var options = services.GetOptionsByName(name); var leaseProvider = services.GetKeyedService(name) ?? services.GetService() ?? throw new InvalidOperationException($"No lease provider found for queue balancer '{name}'. Register an implementation of {nameof(ILeaseProvider)}."); return ActivatorUtilities.CreateInstance(services, name, options, leaseProvider); } /// public override async Task Initialize(IStreamQueueMapper queueMapper) { if (Cancellation.IsCancellationRequested) { throw new InvalidOperationException("Cannot initialize a terminated balancer."); } ArgumentNullException.ThrowIfNull(queueMapper); var allQueues = queueMapper.GetAllQueues().ToList(); _allQueuesCount = allQueues.Count; // Selector default to round robin selector now, but we can make a further change to make selector configurable if needed. Selector algorithm could // be affecting queue balancing stabilization time in cluster initializing and auto-scaling _queueSelector = new RoundRobinSelector(allQueues); await base.Initialize(queueMapper); StartMaintenanceTasks(); void StartMaintenanceTasks() { using var _ = new ExecutionContextSuppressor(); _leaseAcquisitionTimerTask = PeriodicallyAcquireLeasesToMeetResponsibility(); _leaseMaintenanceTimerTask = PeriodicallyMaintainLeases(); } } /// public override async Task Shutdown() { if (Cancellation.IsCancellationRequested) return; // Stop acquiring and renewing leases. _leaseMaintenanceTimer.Dispose(); _leaseAcquisitionTimer.Dispose(); await Task.WhenAll(_leaseMaintenanceTimerTask, _leaseAcquisitionTimerTask); // Release all owned leases. var shutdownTask = _executor.AddNext(async () => { _responsibility = 0; await ReleaseLeasesToMeetResponsibility(); }); // Signal shutdown. await base.Shutdown(); } /// public override IEnumerable GetMyQueues() { if (Cancellation.IsCancellationRequested) { throw new InvalidOperationException("Cannot acquire queues from a terminated balancer."); } return _myQueues.Select(queue => queue.QueueId); } private async Task PeriodicallyMaintainLeases() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); while (await _leaseMaintenanceTimer.WaitForNextTickAsync()) { try { await _executor.AddNext(MaintainLeases); } catch (Exception ex) { Logger.LogError(ex, "Error maintaining leases."); } } async Task MaintainLeases() { if (Cancellation.IsCancellationRequested) return; var oldQueues = new HashSet(_myQueues.Select(queue => queue.QueueId)); try { bool allLeasesRenewed = await RenewLeases(); // If we lost some leases during renew after leaseAcquisitionTimer stopped, restart it. if (!allLeasesRenewed) { // Make the acquisition timer fire immediately. _leaseAcquisitionTimer.Period = TimeSpan.FromMilliseconds(1); } } finally { await NotifyOnChange(oldQueues); } } } private async Task PeriodicallyAcquireLeasesToMeetResponsibility() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); while (await _leaseAcquisitionTimer.WaitForNextTickAsync()) { // Set the period for the next round. // It may be mutated by another method, but not concurrently. _leaseAcquisitionTimer.Period = _options.LeaseAcquisitionPeriod; try { await _executor.AddNext(AcquireLeasesToMeetResponsibility); } catch (Exception ex) { Logger.LogError(ex, "Error acquiring leases."); } } } private async Task AcquireLeasesToMeetResponsibility() { if (Cancellation.IsCancellationRequested) return; var oldQueues = new HashSet(_myQueues.Select(queue => queue.QueueId)); try { if (_myQueues.Count < _responsibility) { await AcquireLeasesToMeetExpectation(_responsibility, _options.LeaseLength.Divide(10)); } else if (_myQueues.Count > _responsibility) { await ReleaseLeasesToMeetResponsibility(); } } finally { await NotifyOnChange(oldQueues); if (_myQueues.Count == _responsibility) { // Stop the acquisition timer. _leaseAcquisitionTimer.Period = Timeout.InfiniteTimeSpan; } } } private async Task ReleaseLeasesToMeetResponsibility() { if (Cancellation.IsCancellationRequested) return; LogTraceReleaseLeasesToMeetResponsibility(Logger, _myQueues.Count, _responsibility); var queueCountToRelease = _myQueues.Count - _responsibility; if (queueCountToRelease <= 0) { return; } // Remove oldest acquired queues first, this provides max recovery time for the queues being moved. AcquiredLease[] queuesToGiveUp = _myQueues .OrderBy(queue => queue.LeaseOrder) .Take(queueCountToRelease) .Select(queue => queue.AcquiredLease) .ToArray(); // Remove queues from list even if release fails, since we can let the lease expire. // TODO: mark for removal instead so we don't renew, and only remove leases that have not expired. - jbragg for (int index = _myQueues.Count - 1; index >= 0; index--) { if (queuesToGiveUp.Contains(_myQueues[index].AcquiredLease)) { _myQueues.RemoveAt(index); } } await _leaseProvider.Release(_options.LeaseCategory, queuesToGiveUp); // Remove queuesToGiveUp from myQueue list after the balancer released the leases on them. LogDebugReleasedLeases(Logger, queueCountToRelease, _myQueues.Count, _responsibility); } private async Task AcquireLeasesToMeetExpectation(int expectedTotalLeaseCount, TimeSpan timeout) { if (Cancellation.IsCancellationRequested) return; LogTraceAcquireLeasesToMeetExpectation(Logger, _myQueues.Count, expectedTotalLeaseCount); var leasesToAcquire = expectedTotalLeaseCount - _myQueues.Count; if (leasesToAcquire <= 0) { return; } // tracks how many remaining possible leases there are. var possibleLeaseCount = _queueSelector.Count - _myQueues.Count; LogDebugHoldingLeased(Logger, _myQueues.Count, leasesToAcquire, expectedTotalLeaseCount, possibleLeaseCount); // Try to acquire leases until we have no more to acquire or no more possible var sw = ValueStopwatch.StartNew(); while (!Cancellation.IsCancellationRequested && leasesToAcquire > 0 && possibleLeaseCount > 0) { // Select new queues to acquire List expectedQueues = _queueSelector.NextSelection(leasesToAcquire, _myQueues.Select(queue => queue.QueueId).ToList()); // Build lease request from each queue LeaseRequest[] leaseRequests = expectedQueues .Select(queue => new LeaseRequest(queue.ToString(), _options.LeaseLength)) .ToArray(); // Add successfully acquired queue to myQueues list AcquireLeaseResult[] results = await _leaseProvider.Acquire(_options.LeaseCategory, leaseRequests); for (var i = 0; i < results.Length; i++) { AcquireLeaseResult result = results[i]; switch (result.StatusCode) { case ResponseCode.OK: { _myQueues.Add(new AcquiredQueue(_leaseOrder++, expectedQueues[i], result.AcquiredLease)); break; } case ResponseCode.TransientFailure: { LogWarningFailedToAcquireLeaseTransient(Logger, result.FailureException, result.AcquiredLease.ResourceKey); break; } // This is expected much of the time. case ResponseCode.LeaseNotAvailable: { LogDebugFailedToAcquireLeaseNotAvailable(Logger, result.FailureException, result.AcquiredLease.ResourceKey, result.StatusCode); break; } // An acquire call should not return this code, so log as error case ResponseCode.InvalidToken: { LogErrorFailedToAcquireLeaseInvalidToken(Logger, result.FailureException, result.AcquiredLease.ResourceKey); break; } default: { LogErrorUnexpectedAcquireLease(Logger, result.FailureException, result.AcquiredLease.ResourceKey, result.StatusCode); break; } } } possibleLeaseCount -= expectedQueues.Count; leasesToAcquire = expectedTotalLeaseCount - _myQueues.Count; LogDebugHoldingLeased(Logger, _myQueues.Count, leasesToAcquire, expectedTotalLeaseCount, possibleLeaseCount); if (sw.Elapsed > timeout) { // blown our allotted time, try again next period break; } } LogDebugHoldingLeases(Logger, _myQueues.Count, _responsibility); } /// /// Renew leases /// /// bool - false if we failed to renew all leases private async Task RenewLeases() { bool allRenewed = true; if (Cancellation.IsCancellationRequested) return false; LogTraceRenewLeases(Logger, _myQueues.Count); if (_myQueues.Count <= 0) { return allRenewed; } var results = await _leaseProvider.Renew(_options.LeaseCategory, _myQueues.Select(queue => queue.AcquiredLease).ToArray()); // Update myQueues list with successfully renewed leases. for (var i = results.Length - 1; i >= 0; i--) { AcquireLeaseResult result = results[i]; switch (result.StatusCode) { case ResponseCode.OK: { _myQueues[i].AcquiredLease = result.AcquiredLease; break; } case ResponseCode.TransientFailure: { _myQueues.RemoveAt(i); allRenewed = false; LogWarningFailedToRenewLeaseTransient(Logger, result.FailureException, result.AcquiredLease.ResourceKey); break; } // These can occur if lease has expired and/or someone else has taken it. case ResponseCode.InvalidToken: case ResponseCode.LeaseNotAvailable: { _myQueues.RemoveAt(i); allRenewed = false; LogWarningFailedToRenewLeaseReason(Logger, result.FailureException, result.AcquiredLease.ResourceKey, result.StatusCode); break; } default: { _myQueues.RemoveAt(i); allRenewed = false; LogErrorUnexpectedRenewLease(Logger, result.FailureException, result.AcquiredLease.ResourceKey, result.StatusCode); break; } } } LogDebugRenewedLeases(Logger, _myQueues.Count); return allRenewed; } private Task NotifyOnChange(HashSet oldQueues) { if (Cancellation.IsCancellationRequested) return Task.CompletedTask; var newQueues = new HashSet(_myQueues.Select(queue => queue.QueueId)); // If queue changed, notify listeners. return !oldQueues.SetEquals(newQueues) ? NotifyListeners() : Task.CompletedTask; } /// protected override void OnClusterMembershipChange(HashSet activeSilos) { if (Cancellation.IsCancellationRequested) return; ScheduleUpdateResponsibilities(activeSilos).Ignore(); } private async Task ScheduleUpdateResponsibilities(HashSet activeSilos) { if (Cancellation.IsCancellationRequested) return; try { await _executor.AddNext(() => UpdateResponsibilities(activeSilos)); } catch (Exception ex) { Logger.LogError(ex, "Error updating lease responsibilities."); } } /// /// Checks to see if this balancer should be greedy, which means it attempts to grab one /// more queue than the non-greedy balancers. /// /// number of free queues, assuming all balancers meet their minimum responsibilities /// number of active silos hosting queues /// bool - true indicates that the balancer should try to acquire one /// more queue than the non-greedy balancers private bool ShouldBeGreedy(int overflow, HashSet activeSilos) { // If using multiple stream providers, this will select the same silos to be greedy for // all providers, aggravating imbalance as stream provider count increases. return activeSilos.OrderBy(silo => silo) .Take(overflow) .Contains(SiloAddress); } private async Task UpdateResponsibilities(HashSet activeSilos) { if (Cancellation.IsCancellationRequested) return; var activeSiloCount = Math.Max(1, activeSilos.Count); _responsibility = _allQueuesCount / activeSiloCount; var overflow = _allQueuesCount % activeSiloCount; if (overflow != 0 && ShouldBeGreedy(overflow, activeSilos)) { _responsibility++; } LogDebugUpdatingResponsibilities(Logger, _allQueuesCount, activeSiloCount, _responsibility, _myQueues.Count); if (_myQueues.Count < _responsibility && _leaseAcquisitionTimer.Period == Timeout.InfiniteTimeSpan) { // Ensure the acquisition timer is running. _leaseAcquisitionTimer.Period = _options.LeaseAcquisitionPeriod; } _leaseMaintenanceTimer.Period = _options.LeaseRenewPeriod; await AcquireLeasesToMeetResponsibility(); } [LoggerMessage( Level = LogLevel.Trace, Message = "ReleaseLeasesToMeetResponsibility. QueueCount: {QueueCount}, Responsibility: {Responsibility}" )] private static partial void LogTraceReleaseLeasesToMeetResponsibility(ILogger logger, int queueCount, int responsibility); [LoggerMessage( Level = LogLevel.Debug, Message = "Released leases for {QueueCount} queues. Holding leases for {HoldingQueueCount} of an expected {MinQueueCount} queues." )] private static partial void LogDebugReleasedLeases(ILogger logger, int queueCount, int holdingQueueCount, int minQueueCount); [LoggerMessage( Level = LogLevel.Trace, Message = "AcquireLeasesToMeetExpectation. QueueCount: {QueueCount}, ExpectedTotalLeaseCount: {ExpectedTotalLeaseCount}" )] private static partial void LogTraceAcquireLeasesToMeetExpectation(ILogger logger, int queueCount, int expectedTotalLeaseCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Holding leased for {QueueCount} queues. Trying to acquire {acquireQueueCount} queues to reach {TargetQueueCount} of a possible {PossibleLeaseCount}" )] private static partial void LogDebugHoldingLeased(ILogger logger, int queueCount, int acquireQueueCount, int targetQueueCount, int possibleLeaseCount); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to acquire lease {LeaseKey} due to transient error." )] private static partial void LogWarningFailedToAcquireLeaseTransient(ILogger logger, Exception exception, string leaseKey); [LoggerMessage( Level = LogLevel.Debug, Message = "Failed to acquire lease {LeaseKey} due to {Reason}." )] private static partial void LogDebugFailedToAcquireLeaseNotAvailable(ILogger logger, Exception exception, string leaseKey, ResponseCode reason); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to acquire acquire {LeaseKey} unexpected invalid token." )] private static partial void LogErrorFailedToAcquireLeaseInvalidToken(ILogger logger, Exception exception, string leaseKey); [LoggerMessage( Level = LogLevel.Error, Message = "Unexpected response to acquire request of lease {LeaseKey}. StatusCode {StatusCode}." )] private static partial void LogErrorUnexpectedAcquireLease(ILogger logger, Exception exception, string leaseKey, ResponseCode statusCode); [LoggerMessage( Level = LogLevel.Debug, Message = "Holding leased for {QueueCount} queues. Trying to acquire {acquireQueueCount} queues to reach {TargetQueueCount} of a possible {PossibleLeaseCount} lease" )] private static partial void LogDebugHoldingLeasedAgain(ILogger logger, int queueCount, int acquireQueueCount, int targetQueueCount, int possibleLeaseCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Holding leases for {QueueCount} of an expected {MinQueueCount} queues." )] private static partial void LogDebugHoldingLeases(ILogger logger, int queueCount, int minQueueCount); [LoggerMessage( Level = LogLevel.Trace, Message = "RenewLeases. QueueCount: {QueueCount}" )] private static partial void LogTraceRenewLeases(ILogger logger, int queueCount); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to renew lease {LeaseKey} due to transient error." )] private static partial void LogWarningFailedToRenewLeaseTransient(ILogger logger, Exception exception, string leaseKey); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to renew lease {LeaseKey} due to {Reason}." )] private static partial void LogWarningFailedToRenewLeaseReason(ILogger logger, Exception exception, string leaseKey, ResponseCode reason); [LoggerMessage( Level = LogLevel.Error, Message = "Unexpected response to renew of lease {LeaseKey}. StatusCode {StatusCode}." )] private static partial void LogErrorUnexpectedRenewLease(ILogger logger, Exception exception, string leaseKey, ResponseCode statusCode); [LoggerMessage( Level = LogLevel.Debug, Message = "Renewed leases for {QueueCount} queues." )] private static partial void LogDebugRenewedLeases(ILogger logger, int queueCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Updating Responsibilities for {QueueCount} queue over {SiloCount} silos. Need {MinQueueCount} queues, have {MyQueueCount}" )] private static partial void LogDebugUpdatingResponsibilities(ILogger logger, int queueCount, int siloCount, int minQueueCount, int myQueueCount); } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/LeaseBasedQueueBalancerOptions.cs ================================================ using System; namespace Orleans.Configuration { /// /// Config for LeaseBasedQueueBalancer. User need to configure this option in order to use LeaseBasedQueueBalancer in the /// stream provider. Per stream provider options can be configured as named options using the same name as the provider. /// public class LeaseBasedQueueBalancerOptions { /// /// Gets or sets the length of the lease. /// /// The length of the lease. public TimeSpan LeaseLength { get; set; } = DefaultLeaseLength; /// /// The default lease length. /// public static readonly TimeSpan DefaultLeaseLength = TimeSpan.FromSeconds(60); /// /// Gets or sets the lease renew period. /// /// The lease renew period. public TimeSpan LeaseRenewPeriod { get; set; } = DefaultLeaseRenewPeriod; /// /// The default lease renew period /// /// /// set to (/2 - 1) to allow time for at least 2 renew calls before we lose the lease. /// public static readonly TimeSpan DefaultLeaseRenewPeriod = TimeSpan.FromSeconds(29); /// /// Gets or sets how often balancer attempts to acquire leases. /// public TimeSpan LeaseAcquisitionPeriod { get; set; } = DefaultMinLeaseAcquisitionPeriod; /// /// Gets or sets how often balancer attempts to acquire leases. /// [Obsolete($"Use {nameof(LeaseAcquisitionPeriod)} instead.", error: true)] public TimeSpan LeaseAquisitionPeriod { get => LeaseAcquisitionPeriod; set => LeaseAcquisitionPeriod = value; } /// /// The default minimum lease acquisition period. /// public static readonly TimeSpan DefaultMinLeaseAcquisitionPeriod = TimeSpan.FromSeconds(30); /// /// The default minimum lease acquisition period. /// [Obsolete($"Use {nameof(DefaultMinLeaseAcquisitionPeriod)} instead.", error: true)] public static readonly TimeSpan DefaultMinLeaseAquisitionPeriod = DefaultMinLeaseAcquisitionPeriod; /// /// Gets or sets the lease category, allows for more fine grain partitioning of leases. /// public string LeaseCategory { get; set; } = DefaultLeaseCategory; /// /// The default lease category /// public const string DefaultLeaseCategory = "QueueBalancer"; } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/PersistentStreamConfiguratorExtension.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Streams; namespace Orleans.Hosting { /// /// Extension methods for . /// public static class SiloPersistentStreamConfiguratorExtension { /// /// Configures the stream provider to use the consistent ring queue balancer. /// /// The confiurator. public static void UseConsistentRingQueueBalancer(this ISiloPersistentStreamConfigurator configurator) { configurator.ConfigurePartitionBalancing(ConsistentRingQueueBalancer.Create); } /// /// Configures the stream provider to use the static cluster configuration deployment balancer. /// /// The configuration builder. /// The silo maturity period. public static void UseStaticClusterConfigDeploymentBalancer( this ISiloPersistentStreamConfigurator configurator, TimeSpan? siloMaturityPeriod = null) { configurator.ConfigurePartitionBalancing( (s, n) => DeploymentBasedQueueBalancer.Create(s, n, s.GetRequiredService>().Value), options => options.Configure(op => { op.IsFixed = true; if (siloMaturityPeriod.HasValue) op.SiloMaturityPeriod = siloMaturityPeriod.Value; })); } /// /// Configures the stream provider to use the dynamic cluster configuration deployment balancer. /// /// The configuration builder. /// The silo maturity period. public static void UseDynamicClusterConfigDeploymentBalancer( this ISiloPersistentStreamConfigurator configurator, TimeSpan? siloMaturityPeriod = null) { configurator.ConfigurePartitionBalancing( (s, n) => DeploymentBasedQueueBalancer.Create(s, n, s.GetRequiredService>().Value), options => options.Configure(op => { op.IsFixed = false; if (siloMaturityPeriod.HasValue) op.SiloMaturityPeriod = siloMaturityPeriod.Value; })); } /// /// Configures the stream provider to use the lease based queue balancer. /// /// The configuration builder. /// The configure options. public static void UseLeaseBasedQueueBalancer(this ISiloPersistentStreamConfigurator configurator, Action> configureOptions = null) { configurator.ConfigurePartitionBalancing((s, n) => LeaseBasedQueueBalancer.Create(s, n), configureOptions); } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/QueueBalancerBase.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Internal; using Orleans.Runtime; using Orleans.Runtime.Internal; namespace Orleans.Streams { /// /// Base class for StreamQueueBalancer /// public abstract partial class QueueBalancerBase : IStreamQueueBalancer { private readonly IAsyncEnumerable clusterMembershipUpdates; private readonly List queueBalanceListeners; private readonly CancellationTokenSource cts; private Task _listenForClusterChangesTask; protected CancellationToken Cancellation => this.cts.Token; protected SiloAddress SiloAddress { get; } protected ILogger Logger { get; } protected QueueBalancerBase(IServiceProvider sp, ILogger logger) : this(sp.GetRequiredService(), sp.GetRequiredService(), logger) { } /// /// This should be primary constructor once IAsyncEnumerable is released /// private QueueBalancerBase(IClusterMembershipService clusterMembership, ILocalSiloDetails localSiloDetails, ILogger logger) { this.clusterMembershipUpdates = clusterMembership.MembershipUpdates; this.SiloAddress = localSiloDetails.SiloAddress; this.Logger = logger; this.queueBalanceListeners = new List(); this.cts = new CancellationTokenSource(); } /// public abstract IEnumerable GetMyQueues(); /// public virtual Task Initialize(IStreamQueueMapper queueMapper) { using var _ = new ExecutionContextSuppressor(); _listenForClusterChangesTask = ListenForClusterChanges(); return Task.CompletedTask; } public virtual async Task Shutdown() { try { this.cts.Cancel(throwOnFirstException: false); } catch (Exception exc) { LogErrorSignalingShutdownToken(Logger, exc); } await _listenForClusterChangesTask.SuppressThrowing(); } /// public bool SubscribeToQueueDistributionChangeEvents(IStreamQueueBalanceListener observer) { ArgumentNullException.ThrowIfNull(observer); lock (this.queueBalanceListeners) { if (this.queueBalanceListeners.Contains(observer)) { return false; } this.queueBalanceListeners.Add(observer); return true; } } /// public bool UnSubscribeFromQueueDistributionChangeEvents(IStreamQueueBalanceListener observer) { ArgumentNullException.ThrowIfNull(observer); lock (this.queueBalanceListeners) { return this.queueBalanceListeners.Remove(observer); } } protected Task NotifyListeners() { if (this.Cancellation.IsCancellationRequested) return Task.CompletedTask; List queueBalanceListenersCopy; lock (queueBalanceListeners) { queueBalanceListenersCopy = queueBalanceListeners.ToList(); // make copy } return Task.WhenAll(queueBalanceListenersCopy.Select(listener => listener.QueueDistributionChangeNotification())); } private async Task ListenForClusterChanges() { await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); var current = new HashSet(); await foreach (var membershipSnapshot in this.clusterMembershipUpdates.WithCancellation(this.Cancellation)) { try { // Get active members. var update = new HashSet(membershipSnapshot.Members.Values .Where(member => member.Status == SiloStatus.Active) .Select(member => member.SiloAddress)); // If active list has changed, track new list and notify. if (!current.SetEquals(update)) { current = update; OnClusterMembershipChange(current); } } catch (Exception exception) { LogErrorProcessingClusterMembershipUpdate(Logger, exception); } } } protected abstract void OnClusterMembershipChange(HashSet activeSilos); [LoggerMessage( Level = LogLevel.Error, Message = "Error signaling shutdown token." )] private static partial void LogErrorSignalingShutdownToken(ILogger logger, Exception exception); [LoggerMessage( Level = LogLevel.Error, Message = "Error processing cluster membership update." )] private static partial void LogErrorProcessingClusterMembershipUpdate(ILogger logger, Exception exception); } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/RoundRobinSelector.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Orleans.Streams { /// /// Selector using round robin algorithm /// /// internal sealed class RoundRobinSelector : IResourceSelector { private readonly List resources; private int lastSelection; public RoundRobinSelector(IEnumerable resources) { // distinct randomly ordered readonly collection this.resources = resources.Distinct().OrderBy(_ => Random.Shared.Next()).ToList(); this.lastSelection = Random.Shared.Next(this.resources.Count); } public int Count => this.resources.Count; /// /// Try to select certain count of resources from resource list, which doesn't overlap with existing resources /// /// /// /// public List NextSelection(int newSelectionCount, List existingSelection) { var selection = new List(Math.Min(newSelectionCount, this.resources.Count)); int tries = 0; while (selection.Count < newSelectionCount && tries++ < this.resources.Count) { this.lastSelection = (++this.lastSelection) % (this.resources.Count); if (!existingSelection.Contains(this.resources[this.lastSelection])) selection.Add(this.resources[this.lastSelection]); } return selection; } } } ================================================ FILE: src/Orleans.Streaming/QueueBalancer/StaticClusterDeploymentConfiguration.cs ================================================ using System.Collections.Generic; using Orleans.Streams; namespace Orleans.Hosting { /// /// Deployment configuration that reads from orleans cluster configuration /// public class StaticClusterDeploymentOptions : IDeploymentConfiguration { /// /// Gets or sets the silo names. /// /// The silo names. public IList SiloNames { get; set; } = new List(); /// IList IDeploymentConfiguration.GetAllSiloNames() { return this.SiloNames; } } } ================================================ FILE: src/Orleans.Streaming/QueueId.cs ================================================ using System; #nullable enable namespace Orleans.Streams { /// /// Identifier of a durable queue. /// Used by Orleans streaming extensions. /// [Serializable] [Immutable] [GenerateSerializer] public readonly struct QueueId : IEquatable, IComparable, ISpanFormattable { [Id(0)] private readonly string queueNamePrefix; [Id(1)] private readonly uint queueId; [Id(2)] private readonly uint uniformHashCache; /// /// Initializes a new instance of the class. /// /// The queue prefix. /// The identifier. /// The hash. private QueueId(string queuePrefix, uint id, uint hash) { queueNamePrefix = queuePrefix ?? throw new ArgumentNullException(nameof(queuePrefix)); queueId = id; uniformHashCache = hash; } /// /// Gets the queue identifier. /// /// Name of the queue. /// The queue identifier. /// The hash. /// The queue identifier. public static QueueId GetQueueId(string queueName, uint queueId, uint hash) => new(queueName, queueId, hash); /// /// Gets the queue name prefix. /// /// The queue name prefix. public string GetStringNamePrefix() => queueNamePrefix; /// /// Gets the numeric identifier. /// /// The numeric identifier. public uint GetNumericId() => queueId; /// public uint GetUniformHashCode() => uniformHashCache; /// /// Gets a value indicating whether the instance is the default instance. /// public bool IsDefault => queueNamePrefix is null; /// public int CompareTo(QueueId other) { if (queueId != other.queueId) return queueId.CompareTo(other.queueId); var cmp = string.CompareOrdinal(queueNamePrefix, other.queueNamePrefix); if (cmp != 0) return cmp; return uniformHashCache.CompareTo(other.uniformHashCache); } /// public bool Equals(QueueId other) => queueId == other.queueId && uniformHashCache == other.uniformHashCache && queueNamePrefix == other.queueNamePrefix; /// public override bool Equals(object? obj) => obj is QueueId queueId && Equals(queueId); /// public override int GetHashCode() => HashCode.Combine(queueId, uniformHashCache, queueNamePrefix); public static bool operator ==(QueueId left, QueueId right) => left.Equals(right); public static bool operator !=(QueueId left, QueueId right) => !(left == right); /// public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { var len = queueNamePrefix.AsSpan().ToLowerInvariant(destination); if (len >= 0 && destination[len..].TryWrite($"-{queueId}", out var len2)) { len += len2; if (format.Length == 1 && format[0] == 'H') { if (!destination[len..].TryWrite($"-0x{uniformHashCache:X8}", out len2)) { charsWritten = 0; return false; } len += len2; } charsWritten = len; return true; } charsWritten = 0; return false; } /// /// Returns a string representation of this instance which includes its uniform hash code. /// /// A string representation of this instance which includes its uniform hash code. public string ToStringWithHashCode() => $"{this:H}"; } } ================================================ FILE: src/Orleans.Streaming/SiloPersistentStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Providers; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Storage; using Orleans.Streams; namespace Orleans.Hosting { /// /// Validates . /// public class PersistentStreamStorageConfigurationValidator : IConfigurationValidator { private readonly IServiceProvider services; private readonly string streamProviderName; /// /// Initializes a new instance of the class. /// /// The services. /// Name of the stream provider. private PersistentStreamStorageConfigurationValidator(IServiceProvider services, string streamProviderName) { this.services = services; this.streamProviderName = streamProviderName; } /// public void ValidateConfiguration() { var pubsubOptions = services.GetOptionsByName(this.streamProviderName); if (pubsubOptions.PubSubType == StreamPubSubType.ExplicitGrainBasedAndImplicit || pubsubOptions.PubSubType == StreamPubSubType.ExplicitGrainBasedOnly) { var pubsubStore = services.GetKeyedService(this.streamProviderName) ?? services.GetKeyedService(ProviderConstants.DEFAULT_PUBSUB_PROVIDER_NAME); if (pubsubStore == null) throw new OrleansConfigurationException( $" Streams with pubsub type {StreamPubSubType.ExplicitGrainBasedAndImplicit} and {StreamPubSubType.ExplicitGrainBasedOnly} requires a grain storage named " + $"{ProviderConstants.DEFAULT_PUBSUB_PROVIDER_NAME} or {this.streamProviderName} to be configured with silo. Please configure one for your stream {streamProviderName}."); } } /// /// Creates a new instance. /// /// The services. /// The name. /// The newly created instance. public static IConfigurationValidator Create(IServiceProvider services, string name) { return new PersistentStreamStorageConfigurationValidator(services, name); } } /// /// Configures persistent streams. /// public class SiloPersistentStreamConfigurator : NamedServiceConfigurator, ISiloPersistentStreamConfigurator { /// /// Initializes a new instance of the class. /// /// The stream provider name. /// The configuration delegate. /// The adapter factory. public SiloPersistentStreamConfigurator(string name, Action> configureDelegate, Func adapterFactory) : base(name, configureDelegate) { this.ConfigureDelegate(services => services.AddSiloStreaming()); this.ConfigureComponent(PersistentStreamProvider.Create); this.ConfigureComponent((s,n) => s.GetRequiredKeyedService(n) as IControllable); this.ConfigureDelegate(services => services.AddSingleton(sp => PersistentStreamProvider.ParticipateIn(sp, this.Name))); this.ConfigureComponent(adapterFactory); this.ConfigureDelegate(services => services.AddSingleton(sp => PersistentStreamStorageConfigurationValidator.Create(sp, this.Name))); } } } ================================================ FILE: src/Orleans.Streaming/StreamConsumerGrainContextAction.cs ================================================ using Orleans.Runtime; using Orleans.Streams.Core; namespace Orleans.Streams { /// /// Installs an extension on a for grains which implement . /// internal class StreamConsumerGrainContextAction : IConfigureGrainContext { private readonly IStreamProviderRuntime _streamProviderRuntime; /// /// Initializes a new instance of the class. /// /// The stream provider runtime. public StreamConsumerGrainContextAction(IStreamProviderRuntime streamProviderRuntime) { _streamProviderRuntime = streamProviderRuntime; } /// public void Configure(IGrainContext context) { if (context.GrainInstance is IStreamSubscriptionObserver observer) { InstallStreamConsumerExtension(context, observer as IStreamSubscriptionObserver); } } private void InstallStreamConsumerExtension(IGrainContext context, IStreamSubscriptionObserver observer) { _streamProviderRuntime.BindExtension(() => new StreamConsumerExtension(_streamProviderRuntime, observer)); } } } ================================================ FILE: src/Orleans.Streaming/StreamId.cs ================================================ using System; using System.Buffers.Text; using System.Diagnostics; using System.Runtime.Serialization; using System.Text; using Orleans.Streams; #nullable enable namespace Orleans.Runtime { /// /// Identifies a Stream within a provider /// [Immutable] [Serializable] [GenerateSerializer] public readonly struct StreamId : IEquatable, IComparable, ISerializable, ISpanFormattable { [Id(0)] private readonly byte[] fullKey; [Id(1)] private readonly ushort keyIndex; [Id(2)] private readonly int hash; /// /// Gets the full key. /// /// The full key. public ReadOnlyMemory FullKey => fullKey; /// /// Gets the namespace. /// /// The namespace. public ReadOnlyMemory Namespace => fullKey.AsMemory(0, this.keyIndex); /// /// Gets the key. /// /// The key. public ReadOnlyMemory Key => fullKey.AsMemory(this.keyIndex); private StreamId(byte[] fullKey, ushort keyIndex, int hash) { this.fullKey = fullKey; this.keyIndex = keyIndex; this.hash = hash; } internal StreamId(byte[] fullKey, ushort keyIndex) : this(fullKey, keyIndex, (int)StableHash.ComputeHash(fullKey)) { } private StreamId(SerializationInfo info, StreamingContext context) { fullKey = (byte[])info.GetValue("fk", typeof(byte[]))!; this.keyIndex = info.GetUInt16("ki"); this.hash = info.GetInt32("fh"); } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static StreamId Create(ReadOnlySpan ns, ReadOnlySpan key) { if (key.IsEmpty) throw new ArgumentNullException(nameof(key)); if (!ns.IsEmpty) { var fullKeysBytes = new byte[ns.Length + key.Length]; ns.CopyTo(fullKeysBytes.AsSpan()); key.CopyTo(fullKeysBytes.AsSpan(ns.Length)); return new(fullKeysBytes, (ushort)ns.Length); } else { return new(key.ToArray(), 0); } } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static StreamId Create(string ns, Guid key) { if (ns is null) { var buf = new byte[32]; Utf8Formatter.TryFormat(key, buf, out var len, 'N'); Debug.Assert(len == 32); return new StreamId(buf, 0); } else { var nsLen = Encoding.UTF8.GetByteCount(ns); var buf = new byte[nsLen + 32]; Encoding.UTF8.GetBytes(ns, 0, ns.Length, buf, 0); Utf8Formatter.TryFormat(key, buf.AsSpan(nsLen), out var len, 'N'); Debug.Assert(len == 32); return new StreamId(buf, (ushort)nsLen); } } /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static StreamId Create(string ns, long key) => Create(ns, key.ToString()); /// /// Initializes a new instance of the struct. /// /// The namespace. /// The key. public static StreamId Create(string ns, string key) { if (ns is null) return new StreamId(Encoding.UTF8.GetBytes(key), 0); var nsLen = Encoding.UTF8.GetByteCount(ns); var keyLen = Encoding.UTF8.GetByteCount(key); var buf = new byte[nsLen + keyLen]; Encoding.UTF8.GetBytes(ns, 0, ns.Length, buf, 0); Encoding.UTF8.GetBytes(key, 0, key.Length, buf, nsLen); return new StreamId(buf, (ushort)nsLen); } /// /// Initializes a new instance of the struct. /// /// The stream identity. public static StreamId Create(IStreamIdentity streamIdentity) => Create(streamIdentity.Namespace, streamIdentity.Guid); /// public int CompareTo(StreamId other) => fullKey.AsSpan().SequenceCompareTo(other.fullKey); /// public bool Equals(StreamId other) => fullKey.AsSpan().SequenceEqual(other.fullKey); /// public override bool Equals(object? obj) => obj is StreamId other ? this.Equals(other) : false; /// /// Compares two instances for equality. /// /// The first stream identity. /// The second stream identity. /// The result of the operator. public static bool operator ==(StreamId s1, StreamId s2) => s1.Equals(s2); /// /// Compares two instances for equality. /// /// The first stream identity. /// The second stream identity. /// The result of the operator. public static bool operator !=(StreamId s1, StreamId s2) => !s2.Equals(s1); /// public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("fk", fullKey); info.AddValue("ki", this.keyIndex); info.AddValue("fh", this.hash); } /// public override string ToString() => $"{this}"; string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { var len = Encoding.UTF8.GetCharCount(fullKey); if (keyIndex == 0) { if (destination.Length >= len + 5) { "null/".CopyTo(destination); charsWritten = Encoding.UTF8.GetChars(fullKey, destination[5..]) + 5; return true; } } else if (destination.Length > len) { len = Encoding.UTF8.GetChars(fullKey.AsSpan(0, keyIndex), destination); destination[len++] = '/'; charsWritten = Encoding.UTF8.GetChars(fullKey.AsSpan(keyIndex), destination[len..]) + len; return true; } charsWritten = 0; return false; } /// /// Parses a instance from a . /// /// The UTF-8 encoded value. /// The parsed stream identity. public static StreamId Parse(ReadOnlySpan value) { var i = value.IndexOf((byte)'/'); if (i < 0) { throw new ArgumentException($"Unable to parse \"{Encoding.UTF8.GetString(value)}\" as a stream id"); } return Create(value[..i], value[(i + 1)..]); } /// public override int GetHashCode() => this.hash; internal uint GetUniformHashCode() => (uint)hash; internal uint GetKeyIndex() => keyIndex; /// /// Returns the component of this instance as a string. /// /// The key component of this instance. public string GetKeyAsString() => Encoding.UTF8.GetString(fullKey, keyIndex, fullKey.Length - keyIndex); /// /// Returns the component of this instance as a string. /// /// The namespace component of this instance. public string? GetNamespace() => keyIndex == 0 ? null : Encoding.UTF8.GetString(fullKey, 0, keyIndex); internal IdSpan GetKeyIdSpan() => keyIndex == 0 ? IdSpan.UnsafeCreate(fullKey, hash) : new(fullKey.AsSpan(keyIndex).ToArray()); } } ================================================ FILE: src/Orleans.Streaming.Abstractions/Orleans.Streaming.Abstractions.csproj ================================================ Microsoft.Orleans.Streaming.Abstractions Microsoft Orleans Streaming Abstractions Streaming abstractions library for Microsoft Orleans $(DefaultTargetFrameworks) true Orleans false true ================================================ FILE: src/Orleans.Streaming.Abstractions/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; ================================================ FILE: src/Orleans.Streaming.NATS/Hosting/ClientBuilderExtensions.cs ================================================ using System; using Orleans.Hosting; using Orleans.Streaming.NATS.Hosting; namespace Orleans.Streaming.NATS.Hosting; public static class ClientBuilderExtensions { /// /// Configure cluster client to use NATS persistent streams with default settings /// public static IClientBuilder AddNatsStreams(this IClientBuilder builder, string name, Action configureOptions) { builder.AddNatsStreams(name, b => b.ConfigureNats(ob => ob.Configure(configureOptions))); return builder; } /// /// Configure cluster client to use NATS persistent streams. /// public static IClientBuilder AddNatsStreams(this IClientBuilder builder, string name, Action? configure) { var configurator = new ClusterClientNatsStreamConfigurator(name, builder); configure?.Invoke(configurator); return builder; } } ================================================ FILE: src/Orleans.Streaming.NATS/Hosting/NatsStreamConfigurator.cs ================================================ using System; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Configuration; namespace Orleans.Streaming.NATS.Hosting; public class SiloNatsStreamConfigurator : SiloPersistentStreamConfigurator { public SiloNatsStreamConfigurator(string name, Action> configureServicesDelegate) : base(name, configureServicesDelegate, NatsAdapterFactory.Create) { this.ConfigureDelegate(services => { services .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new NatsStreamOptionsValidator(sp.GetRequiredService>().Get(name), name)); }); } public SiloNatsStreamConfigurator ConfigureNats(Action> configureOptions) { this.Configure(configureOptions); return this; } public SiloNatsStreamConfigurator ConfigureCache(int cacheSize = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE) { this.Configure(ob => ob.Configure(options => options.CacheSize = cacheSize)); return this; } public SiloNatsStreamConfigurator ConfigurePartitioning( int numOfparitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = numOfparitions)); return this; } } public class ClusterClientNatsStreamConfigurator : ClusterClientPersistentStreamConfigurator { public ClusterClientNatsStreamConfigurator(string name, IClientBuilder builder) : base(name, builder, NatsAdapterFactory.Create) { builder .ConfigureServices(services => { services .ConfigureNamedOptionForLogging(name) .ConfigureNamedOptionForLogging(name) .AddTransient(sp => new NatsStreamOptionsValidator(sp.GetRequiredService>().Get(name), name)); }); } public ClusterClientNatsStreamConfigurator ConfigureNats(Action> configureOptions) { this.Configure(configureOptions); return this; } public ClusterClientNatsStreamConfigurator ConfigurePartitioning( int numOfparitions = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES) { this.Configure(ob => ob.Configure(options => options.TotalQueueCount = numOfparitions)); return this; } } ================================================ FILE: src/Orleans.Streaming.NATS/Hosting/SiloBuilderExtensions.cs ================================================ using System; using Orleans.Hosting; namespace Orleans.Streaming.NATS.Hosting; public static class SiloBuilderExtensions { /// /// Configure silo to use NATS persistent streams. /// public static ISiloBuilder AddNatsStreams(this ISiloBuilder builder, string name, Action configureOptions) { builder.AddNatsStreams(name, b => b.ConfigureNats(ob => ob.Configure(configureOptions))); return builder; } /// /// Configure silo to use NATS persistent streams. /// public static ISiloBuilder AddNatsStreams(this ISiloBuilder builder, string name, Action? configure) { var configurator = new SiloNatsStreamConfigurator(name, configureServicesDelegate => builder.ConfigureServices(configureServicesDelegate)); configure?.Invoke(configurator); return builder; } } ================================================ FILE: src/Orleans.Streaming.NATS/NatsOptions.cs ================================================ using System.Text.Json; using Orleans.Runtime; using NATS.Client.Core; namespace Orleans.Streaming.NATS; /// /// Configuration options for the NATS JetStream stream provider /// public class NatsOptions { /// /// The NATS JetStream stream name /// public string StreamName { get; set; } = default!; /// /// Configuration options for the NATS client. /// If not provided, a default client will be created with the name Orleans-{providerName} /// and will connect to the NATS server at localhost:4222 /// public NatsOpts? NatsClientOptions { get; set; } /// /// The maximum number of messages to fetch in a single batch. /// Defaults to 100. /// public int BatchSize { get; set; } = 100; /// /// The number of partitions in the stream. /// This determines the number of pooling agents that will be created on this Orleans Cluster. /// This is mapped to a deterministic partitioning scheme of the NATS JetStream stream. /// The partitions are mapped from "[Provider-Name].*.*" to "[Provider-Name].{{partition([PartitionCount],1,2)}}.{{wildcard(1)}}.{{wildcard(2)}}". /// For details on how partitioning works in NATS JetStream, see /// Defaults to 8. Increase it if you need more parallelism. /// /// Deterministic partition scheme is a NATS server construct. /// This provider when started at the first time will create the stream with the partition scheme. /// If you need to change the partition count, you need to modify the value of this property and, you need to manually modify it on NATS Server first since the provider will not make updates to the JetStream stream definition. /// /// public int PartitionCount { get; set; } = 8; /// /// The number of connections used to send stream messages to NATS JetStream. /// public int ProducerCount { get; set; } = 8; /// /// System.Text.Json serializer options to be used by the NATS provider. /// public JsonSerializerOptions? JsonSerializerOptions { get; set; } } public class NatsStreamOptionsValidator(NatsOptions options, string? name = null) : IConfigurationValidator { public void ValidateConfiguration() { if (string.IsNullOrWhiteSpace(options.StreamName)) { throw new OrleansConfigurationException( $"The {nameof(NatsOptions.StreamName)} is required for the NATS stream provider '{name}'."); } } } ================================================ FILE: src/Orleans.Streaming.NATS/Orleans.Streaming.NATS.csproj ================================================  Microsoft.Orleans.Streaming.NATS Microsoft Orleans NATS Streaming Provider Microsoft Orleans streaming provider backed by NATS $(PackageTags) NATS $(DefineConstants);ORLEANS_NATS $(DefaultTargetFrameworks) Orleans.Streaming.NATS Orleans.Streaming.NATS true enable $(VersionSuffix).alpha.1 alpha.1 README.md ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsAdapter.cs ================================================ using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Orleans.Streams; using Orleans.Runtime; using Orleans.Serialization; namespace Orleans.Streaming.NATS; internal sealed class NatsAdapter( string providerName, NatsOptions options, ILoggerFactory loggerFactory, Serializer serializer, NatsConnectionManager natsConnectionManager) : IQueueAdapter { public string Name => providerName; public bool IsRewindable => false; // We will make it rewindable later public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; public IQueueAdapterReceiver CreateReceiver(QueueId queueId) => NatsQueueAdapterReceiver.Create(providerName, loggerFactory, natsConnectionManager, queueId.GetNumericId(), options, serializer); public async Task QueueMessageBatchAsync(StreamId streamId, IEnumerable events, StreamSequenceToken token, Dictionary requestContext) { var batchContainer = new NatsBatchContainer(streamId, events.Cast().ToArray(), requestContext); var raw = serializer.GetSerializer().SerializeToArray(batchContainer); await natsConnectionManager.EnqueueMessage(new NatsStreamMessage { StreamId = streamId, Payload = raw, RequestContext = requestContext }); } } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsAdapterFactory.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using Orleans.Streams; using Orleans.Configuration; using Orleans.Serialization; using Orleans.Configuration.Overrides; using Orleans.Providers.Streams.Common; namespace Orleans.Streaming.NATS; internal class NatsAdapterFactory : IQueueAdapterFactory { private readonly string providerName; private readonly NatsOptions natsOptions; private readonly Serializer serializer; private readonly ILoggerFactory loggerFactory; private readonly HashRingBasedStreamQueueMapper streamQueueMapper; private readonly IQueueAdapterCache adapterCache; /// /// Application level failure handler override. /// protected Func> StreamFailureHandlerFactory { private get; set; } = default!; public NatsAdapterFactory( string name, NatsOptions natsOptions, HashRingStreamQueueMapperOptions queueMapperOptions, SimpleQueueCacheOptions cacheOptions, IOptions clusterOptions, Serializer serializer, ILoggerFactory loggerFactory) { this.providerName = name; this.natsOptions = natsOptions; this.serializer = serializer; this.loggerFactory = loggerFactory; streamQueueMapper = new HashRingBasedStreamQueueMapper(queueMapperOptions, this.providerName); adapterCache = new SimpleQueueAdapterCache(cacheOptions, this.providerName, this.loggerFactory); } /// Init the factory. public virtual void Init() { if (StreamFailureHandlerFactory == null) { StreamFailureHandlerFactory = qid => Task.FromResult(new NoOpStreamDeliveryFailureHandler()); } } /// Creates the NATS based adapter. public virtual async Task CreateAdapter() { var connectionManager = new NatsConnectionManager(this.providerName, this.loggerFactory, this.natsOptions); await connectionManager.Initialize(); var adapter = new NatsAdapter(this.providerName, this.natsOptions, this.loggerFactory, this.serializer, connectionManager); return adapter; } /// Creates the adapter cache. public virtual IQueueAdapterCache GetQueueAdapterCache() { return adapterCache; } /// Creates the factory stream queue mapper. public IStreamQueueMapper GetStreamQueueMapper() { return streamQueueMapper; } /// /// Creates a delivery failure handler for the specified queue. /// /// /// public Task GetDeliveryFailureHandler(QueueId queueId) { return StreamFailureHandlerFactory(queueId); } public static NatsAdapterFactory Create(IServiceProvider services, string name) { var natsOptions = services.GetOptionsByName(name); var cacheOptions = services.GetOptionsByName(name); var queueMapperOptions = services.GetOptionsByName(name); IOptions clusterOptions = services.GetProviderClusterOptions(name); var factory = ActivatorUtilities.CreateInstance(services, name, natsOptions, cacheOptions, queueMapperOptions, clusterOptions); factory.Init(); return factory; } } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsBatchContainer.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Text.Json.Serialization; using Orleans.Providers.Streams.Common; using Orleans.Runtime; using Orleans.Streams; namespace Orleans.Streaming.NATS; [Serializable] [GenerateSerializer] [method: JsonConstructor] internal class NatsBatchContainer( StreamId streamId, object[] events, Dictionary? requestContext, string? replyTo = null, EventSequenceTokenV2 sequenceToken = default!) : IBatchContainer { [Id(0)] [field: JsonPropertyName("sid")] public StreamId StreamId { get; } = streamId; [Id(1)] [field: JsonPropertyName("stk")] public StreamSequenceToken SequenceToken { get; set; } = sequenceToken; [Id(2)] [field: JsonPropertyName("evts")] public object[] Events { get; } = events; [Id(3)] [field: JsonPropertyName("ctx")] public Dictionary? RequestContext { get; } = requestContext; [Id(4)] [field: JsonPropertyName("rpt")] public string? ReplyTo { get; set; } = replyTo; public bool ImportRequestContext() { if (this.RequestContext is not null) { RequestContextExtensions.Import(this.RequestContext); return true; } return false; } public IEnumerable> GetEvents() => this.Events.OfType().Select((e, i) => Tuple.Create(e, this.SequenceToken)); public override string ToString() => $"[NatsBatchContainer:Stream={StreamId},#Items={Events.Length}]"; } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsConnectionManager.cs ================================================ using System; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NATS.Client.Core; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; using NATS.Client.Serializers.Json; namespace Orleans.Streaming.NATS; /// /// Wrapper around NATS and JetStream APIs /// internal sealed class NatsConnectionManager { private static readonly byte[] AckPayload = "+ACK"u8.ToArray(); private readonly string _providerName; private readonly NatsOpts _natsClientOptions; private readonly NatsConnection _natsConnection; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly NatsOptions _options; private readonly NatsJSContext[] _producerNatsContexts; private readonly NatsJSContext _natsContext; [GeneratedActivatorConstructor] public NatsConnectionManager(string providerName, ILoggerFactory loggerFactory, NatsOptions options) { this._providerName = providerName; this._loggerFactory = loggerFactory; this._logger = this._loggerFactory.CreateLogger(); this._options = options; this._options.JsonSerializerOptions ??= new(); this._options.JsonSerializerOptions.TypeInfoResolverChain.Add(NatsSerializerContext.Default); if (this._options.NatsClientOptions is null) { this._options.NatsClientOptions = NatsOpts.Default with { Name = $"Orleans-{this._providerName}", SerializerRegistry = new NatsJsonContextOptionsSerializerRegistry(this._options.JsonSerializerOptions) }; } else { this._options.NatsClientOptions = this._options.NatsClientOptions with { Name = string.IsNullOrWhiteSpace(this._options.NatsClientOptions.Name) ? $"Orleans-{this._providerName}" : this._options.NatsClientOptions.Name, SerializerRegistry = new NatsJsonContextOptionsSerializerRegistry(this._options.JsonSerializerOptions) }; } this._natsClientOptions = this._options.NatsClientOptions; this._natsConnection = new NatsConnection(this._natsClientOptions); this._natsContext = new NatsJSContext(this._natsConnection); this._producerNatsContexts = new NatsJSContext[this._options.ProducerCount]; for (var i = 0; i < this._options.ProducerCount; i++) { var producerOptions = this._natsClientOptions with { Name = $"Orleans-{this._providerName}-Producer-{i}" }; var producerConnection = new NatsConnection(producerOptions); this._producerNatsContexts[i] = new NatsJSContext(producerConnection); } } /// /// Initialize the connection to the NATS server and check if JetStream is available /// public async Task Initialize(CancellationToken cancellationToken = default) { try { await this._natsConnection.ConnectAsync(); if (this._natsConnection.ConnectionState != NatsConnectionState.Open) { this._logger.LogError("Unable to connect to NATS server {NatsServer}", this._natsClientOptions.Url); return; } if (!this._natsConnection.ServerInfo!.JetStreamAvailable) { this._logger.LogError( "Unable to use {NatsServer} for Orleans Stream Provider {ProviderName}: NATS JetStream is not available", this._natsClientOptions.Url, this._providerName); return; } foreach (var producerContext in this._producerNatsContexts) { await producerContext.Connection.ConnectAsync(); if (producerContext.Connection.ConnectionState != NatsConnectionState.Open) { this._logger.LogError("Unable to connect to NATS server {NatsServer}", producerContext.Connection.Opts.Url); return; } } this._logger.LogTrace("Connected to NATS server {NatsServer}", this._natsClientOptions.Url); try { var streamConfig = new StreamConfig(this._options.StreamName, [$"{this._providerName}.>"]) { Retention = StreamConfigRetention.Workqueue, SubjectTransform = new SubjectTransform { Src = $"{this._providerName}.*.*", Dest = @$"{this._providerName}.{{{{partition({this._options.PartitionCount},1,2)}}}}.{{{{wildcard(1)}}}}.{{{{wildcard(2)}}}}" } }; await this._natsContext.CreateStreamAsync(streamConfig, cancellationToken); } catch (NatsJSApiException e) when (e.Error.ErrCode == 10065) { // ignore, stream already exists } this._logger.LogTrace( "Initialized to NATS JetStream stream {Stream} on server {NatsServer}", this._options.StreamName, this._natsClientOptions.Url); } catch (Exception ex) { _logger.LogError(ex, "Error initializing NATS JetStream Connection Manager"); throw; } } /// /// Enqueue a message to NATS JetStream stream /// /// The message /// Cancellation token public async Task EnqueueMessage(NatsStreamMessage message, CancellationToken cancellationToken = default) { if (this._natsContext is null) { this._logger.LogError("Unable to enqueue message: NATS context is not initialized"); throw new InvalidOperationException("Unable to enqueue message: NATS context is not initialized"); } var ns = message.StreamId.Namespace.IsEmpty ? "null" : Encoding.UTF8.GetString(message.StreamId.Namespace.Span); var id = Encoding.UTF8.GetString(message.StreamId.Key.Span); var subject = $"{this._providerName}.{ns}.{id}"; var context = this._producerNatsContexts[Math.Abs(id.GetHashCode()) % this._producerNatsContexts.Length]; var ack = await context.TryPublishAsync( subject, message, this._natsClientOptions.SerializerRegistry.GetSerializer(), cancellationToken: cancellationToken); if (ack.Success) { _logger.LogTrace("Enqueued NATS message to subject {Subject}", subject); } else { this._logger.LogError(ack.Error, "Failed to enqueue NATS message to {Subject}", subject); } } /// /// Create a NATS JetStream consumer /// /// The partition number /// A wrapper to a durable NATS JetStream stream consumer public NatsStreamConsumer CreateConsumer(uint partition) => new(this._loggerFactory, this._natsContext, this._providerName, this._options.StreamName, partition, this._options.BatchSize, this._natsClientOptions.SerializerRegistry.GetDeserializer()); /// /// Acknowledge messages on a subject in a NATS JetStream stream /// /// The ReplyTo subject public async Task AcknowledgeMessages(string subject) { await this._natsConnection .PublishAsync(subject, AckPayload, serializer: NatsRawSerializer.Default); } } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsQueueAdapterReceiver.cs ================================================ using System; using System.Linq; using System.Buffers; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Orleans.Providers.Streams.Common; using Orleans.Streams; using Orleans.Serialization; namespace Orleans.Streaming.NATS; internal sealed class NatsQueueAdapterReceiver : IQueueAdapterReceiver { private readonly ILogger _logger; private readonly uint _partition; private readonly string _providerName; private readonly Serializer _serializer; private long lastReadMessage; private NatsConnectionManager? _nats; private NatsStreamConsumer? _consumer; private Task? _outstandingTask; public static IQueueAdapterReceiver Create(string providerName, ILoggerFactory loggerFactory, NatsConnectionManager connectionManager, uint partition, NatsOptions options, Serializer serializer) { ArgumentException.ThrowIfNullOrWhiteSpace(providerName); ArgumentNullException.ThrowIfNull(loggerFactory); ArgumentNullException.ThrowIfNull(options); return new NatsQueueAdapterReceiver(providerName, loggerFactory, partition, connectionManager, serializer); } private NatsQueueAdapterReceiver(string providerName, ILoggerFactory loggerFactory, uint partition, NatsConnectionManager nats, Serializer serializer) { ArgumentException.ThrowIfNullOrWhiteSpace(providerName); ArgumentNullException.ThrowIfNull(loggerFactory); ArgumentNullException.ThrowIfNull(nats); this._logger = loggerFactory.CreateLogger(); this._nats = nats; this._partition = partition; this._providerName = providerName; this._serializer = serializer; } public async Task Initialize(TimeSpan timeout) { // If it is null, then we are shutting down if (this._nats is null) return; using var cts = new CancellationTokenSource(timeout); this._consumer = this._nats.CreateConsumer(this._partition); if (this._consumer is null) { this._logger.LogError("Unable to create consumer for partition {Partition}", this._partition); return; } await this._consumer.Initialize(cts.Token); } public async Task Shutdown(TimeSpan timeout) { try { if (this._outstandingTask is not null) { await this._outstandingTask; } } finally { this._consumer = null; this._nats = null; } } public async Task> GetQueueMessagesAsync(int maxCount) { try { if (this._nats is null || this._consumer is null) { this._logger.LogWarning( "NATS provider is not initialized. Unable to get messages. If we are shutting down it is fine. Otherwise, we have a problem with initialization of the NATS stream provider {Provider} for partition {Partition}.", this._providerName, this._partition); return []; } var task = this._consumer.GetMessages(maxCount); this._outstandingTask = task; var (messages, messageCount) = await task; var containers = new List(); for (var i = 0; i < messageCount; i++) { var natsMessage = messages[i]; var container = this._serializer.Deserialize(natsMessage.Payload); container.SequenceToken = new EventSequenceTokenV2(lastReadMessage++); container.ReplyTo = natsMessage.ReplyTo; containers.Add(container); } ArrayPool.Shared.Return(messages); return containers; } finally { this._outstandingTask = null; } } public async Task MessagesDeliveredAsync(IList messages) { if (this._nats is null || this._consumer is null) { this._logger.LogWarning( "NATS provider is not initialized. Unable to deliver messages. If we are shutting down it is fine. Otherwise, we have a problem with initialization of the NATS stream provider {Provider} for partition {Partition}.", this._providerName, this._partition); return; } if (messages.Count == 0) return; var tasks = new List(); foreach (var message in messages) { if (message is NatsBatchContainer natsMessage && !string.IsNullOrWhiteSpace(natsMessage.ReplyTo)) { tasks.Add(this._nats.AcknowledgeMessages(natsMessage.ReplyTo)); } } await Task.WhenAll(tasks); } } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsStreamConsumer.cs ================================================ using System; using System.Buffers; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NATS.Client.Core; using NATS.Client.JetStream; using NATS.Client.JetStream.Models; namespace Orleans.Streaming.NATS; /// /// Wrapper around a NATS JetStream consumer /// internal sealed class NatsStreamConsumer( ILoggerFactory loggerFactory, NatsJSContext context, string provider, string stream, uint partition, int batchSize, INatsDeserialize serializer) { private readonly ILogger _logger = loggerFactory.CreateLogger(); private readonly ConsumerConfig _config = new($"orleans-{provider}-{stream}-{partition}") { FilterSubject = $"{provider}.{partition}.>", MaxBatch = batchSize, DeliverPolicy = ConsumerConfigDeliverPolicy.All, MaxAckPending = batchSize }; private INatsJSConsumer? _consumer; public async Task<(NatsStreamMessage[] Messages, int Count)> GetMessages(int messageCount = 0, CancellationToken cancellationToken = default) { if (this._consumer is null) { this._logger.LogError( "Internal NATS Consumer is not initialized. Provider: {Provider} | Stream: {Stream} | Partition: {Partition}.", provider, stream, partition); return ([], 0); } var batchCount = messageCount > 0 && messageCount < batchSize ? messageCount : batchSize; var messages = ArrayPool.Shared.Rent(batchCount); var i = 0; await foreach (var msg in this._consumer.FetchNoWaitAsync( new NatsJSFetchOpts { MaxMsgs = batchCount, Expires = TimeSpan.FromSeconds(10) }, serializer: serializer) .WithCancellation(cancellationToken)) { var streamMessage = msg.Data; if (streamMessage is null) { this._logger.LogWarning("Unable to deserialize NATS message for subject {Subject}. Ignoring...", msg.Subject); continue; } messages[i] = streamMessage; messages[i].ReplyTo = msg.ReplyTo; i++; } return (messages, i); } public async Task Initialize(CancellationToken cancellationToken = default) { var consumer = await context.CreateOrUpdateConsumerAsync(stream, this._config, cancellationToken); this._consumer = consumer; } } ================================================ FILE: src/Orleans.Streaming.NATS/Providers/NatsStreamMessage.cs ================================================ using System; using System.Collections.Generic; using System.Text.Json.Serialization; using Orleans.Runtime; namespace Orleans.Streaming.NATS; [Serializable] [GenerateSerializer] internal class NatsStreamMessage { [Id(0)] [JsonConverter(typeof(StreamIdJsonConverter))] [JsonPropertyName("sid")] public StreamId StreamId { get; set; } [Id(1)] [JsonPropertyName("ctx")] public Dictionary? RequestContext { get; set; } [Id(2)] [JsonPropertyName("p")] public required byte[] Payload { get; set; } [Id(3)] [JsonPropertyName("rpt")] public string? ReplyTo { get; set; } } [JsonSerializable(typeof(NatsStreamMessage))] [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, WriteIndented = false)] internal partial class NatsSerializerContext : JsonSerializerContext; ================================================ FILE: src/Orleans.Streaming.NATS/Providers/StreamIdJsonConverter.cs ================================================ using System; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Orleans.Runtime; namespace Orleans.Streaming.NATS; internal class StreamIdJsonConverter : JsonConverter { public override StreamId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.String) { throw new JsonException("StreamId is not a string"); } var str = reader.GetString(); if (string.IsNullOrWhiteSpace(str)) { throw new JsonException("StreamId is empty"); } return StreamId.Parse(Encoding.UTF8.GetBytes(str)); } public override void Write(Utf8JsonWriter writer, StreamId value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); } ================================================ FILE: src/Orleans.Streaming.NATS/README.md ================================================ # Microsoft Orleans Stream Provider for NATS ## Introduction Microsoft Orleans Stream Provider for NATS enables Orleans applications to leverage NATS JetStream for reliable, scalable event processing. This provider allows you to use NATS JetStream as a streaming backbone for your Orleans application to both produce and consume streams of events. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Streaming.NATS ``` ## Example - Configuring NATS Stream Provider ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Streaming.NATS.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure NATS JetStream as a stream provider .AddNatsStreams( "NatsStreamProvider", options => { options.StreamName = "orleans-stream"; // Optional: Configure NATS client options // options.NatsClientOptions = new NatsOpts { Url = "nats://localhost:4222" }; // Optional: Configure batch size (default: 100) // options.BatchSize = 100; // Optional: Configure partition count (default: 8) // options.PartitionCount = 8; }); }); // Run the host await builder.RunAsync(); ``` ## Example - Configuring NATS Streams on Client ```csharp using Microsoft.Extensions.Hosting; using Orleans.Streaming.NATS.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleansClient(clientBuilder => { clientBuilder .UseLocalhostClustering() .AddNatsStreams( "NatsStreamProvider", options => { options.StreamName = "orleans-stream"; }); }); await builder.RunAsync(); ``` ## Example - Using NATS Streams in a Grain ```csharp using Orleans; using Orleans.Streams; // Grain interface public interface IStreamProcessingGrain : IGrainWithGuidKey { Task StartProcessing(); Task SendEvent(MyEvent evt); } // Grain implementation public class StreamProcessingGrain : Grain, IStreamProcessingGrain { private IStreamProvider _streamProvider; private IAsyncStream _stream; private StreamSubscriptionHandle _subscription; public override async Task OnActivateAsync(CancellationToken cancellationToken) { // Get the stream provider _streamProvider = this.GetStreamProvider("NatsStreamProvider"); // Get a reference to a specific stream _stream = _streamProvider.GetStream( StreamId.Create("MyStreamNamespace", this.GetPrimaryKey())); await base.OnActivateAsync(cancellationToken); } public async Task StartProcessing() { // Subscribe to the stream to process events _subscription = await _stream.SubscribeAsync(OnNextAsync); } private Task OnNextAsync(MyEvent evt, StreamSequenceToken token) { Console.WriteLine($"Received event: {evt.Data}"); return Task.CompletedTask; } // Produce an event to the stream public Task SendEvent(MyEvent evt) { return _stream.OnNextAsync(evt); } } // Event class public class MyEvent { public string Data { get; set; } } ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Streams](https://learn.microsoft.com/en-us/dotnet/orleans/streaming/) - [NATS JetStream Documentation](https://docs.nats.io/nats-concepts/jetstream) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Orleans.TestingHost/ClientExtensions.cs ================================================ using Orleans.Runtime; using Orleans.Runtime.TestHooks; namespace Orleans.TestingHost { /// /// Extension methods for . /// internal static class ClientExtensions { /// /// Returns test hooks for the specified silo. /// /// The client. /// The silo. /// Test hooks for the specified silo. public static ITestHooks GetTestHooks(this IClusterClient client, SiloHandle silo) { // Use the siloAddress here, not the gateway address, since we may be targeting a silo on which we are not // connected to the gateway var internalClient = (IInternalClusterClient) client; return internalClient.GetSystemTarget(Constants.TestHooksSystemTargetType, silo.SiloAddress); } } } ================================================ FILE: src/Orleans.TestingHost/ConfigureDistributedGrainDirectory.cs ================================================ using Orleans.Hosting; namespace Orleans.TestingHost; internal class ConfigureDistributedGrainDirectory : ISiloConfigurator { #pragma warning disable ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. public void Configure(ISiloBuilder siloBuilder) => siloBuilder.AddDistributedGrainDirectory(); #pragma warning restore ORLEANSEXP003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } ================================================ FILE: src/Orleans.TestingHost/IClientBuilderConfigurator.cs ================================================ using Microsoft.Extensions.Configuration; using Orleans.Hosting; namespace Orleans.TestingHost { /// /// Allows implementations to configure the client builder when starting up each silo in the test cluster. /// public interface IClientBuilderConfigurator { /// /// Configures the client builder. /// /// The configuration. /// The client builder. void Configure(IConfiguration configuration, IClientBuilder clientBuilder); } } ================================================ FILE: src/Orleans.TestingHost/IHostConfigurator.cs ================================================ using Microsoft.Extensions.Hosting; namespace Orleans.TestingHost { /// /// Allows implementations to configure the host builder when starting up each silo in the test cluster. /// public interface IHostConfigurator { /// /// Configures the host builder. /// /// The host builder. void Configure(IHostBuilder hostBuilder); } } ================================================ FILE: src/Orleans.TestingHost/IPortAllocator.cs ================================================ using System; namespace Orleans.TestingHost { /// /// Functionality for finding unused ports. /// public interface ITestClusterPortAllocator : IDisposable { /// /// Allocates consecutive port pairs. /// /// The number of consecutive ports to allocate. /// Base ports for silo and gateway endpoints. ValueTuple AllocateConsecutivePortPairs(int numPorts); } } ================================================ FILE: src/Orleans.TestingHost/ISiloConfigurator.cs ================================================ using Orleans.Hosting; namespace Orleans.TestingHost { /// /// Allows implementations to configure the silo builder when starting up each silo in the test cluster. /// public interface ISiloConfigurator { /// /// Configures the silo builder. /// /// The silo builder. void Configure(ISiloBuilder siloBuilder); } } ================================================ FILE: src/Orleans.TestingHost/InMemoryTransport/InMemoryTransportConnection.cs ================================================ #nullable enable using System.Buffers; using System.IO.Pipelines; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Orleans.Networking.Shared; namespace Orleans.TestingHost.InMemoryTransport; internal class InMemoryTransportConnection : TransportConnection { private readonly CancellationTokenSource _connectionClosedTokenSource = new(); private readonly ILogger _logger; private bool _isClosed; private readonly TaskCompletionSource _waitForCloseTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); private InMemoryTransportConnection(MemoryPool memoryPool, ILogger logger, DuplexPipe.DuplexPipePair pair, EndPoint localEndPoint, EndPoint remoteEndPoint) { MemoryPool = memoryPool; _logger = logger; LocalEndPoint = localEndPoint; RemoteEndPoint = remoteEndPoint; Application = pair.Application; Transport = pair.Transport; ConnectionClosed = _connectionClosedTokenSource.Token; } public static InMemoryTransportConnection Create(MemoryPool memoryPool, ILogger logger, EndPoint localEndPoint, EndPoint remoteEndPoint) { var pair = DuplexPipe.CreateConnectionPair( new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, useSynchronizationContext: false), new PipeOptions(memoryPool, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false)); return new InMemoryTransportConnection(memoryPool, logger, pair, localEndPoint, remoteEndPoint); } public static InMemoryTransportConnection Create(MemoryPool memoryPool, ILogger logger, InMemoryTransportConnection other, EndPoint localEndPoint) { // Swap the application & tranport pipes since we're going in the other direction. var pair = new DuplexPipe.DuplexPipePair(transport: other.Application, application: other.Transport); var remoteEndPoint = other.LocalEndPoint; return new InMemoryTransportConnection(memoryPool, logger, pair, localEndPoint, remoteEndPoint); } public override MemoryPool MemoryPool { get; } public Task WaitForCloseTask => _waitForCloseTcs.Task; public override void Abort(ConnectionAbortedException? abortReason) { _logger.LogDebug(@"Connection id ""{ConnectionId}"" closing because: ""{Message}""", ConnectionId, abortReason?.Message); Transport.Input.CancelPendingRead(); Transport.Output.CancelPendingFlush(); OnClosed(); } public void OnClosed() { if (_isClosed) { return; } _isClosed = true; ThreadPool.UnsafeQueueUserWorkItem(state => { state._connectionClosedTokenSource.Cancel(); state._waitForCloseTcs.TrySetResult(true); }, this, preferLocal: false); } public override async ValueTask DisposeAsync() { Abort(null); await _waitForCloseTcs.Task; _connectionClosedTokenSource.Dispose(); } public override string ToString() => $"InMem({LocalEndPoint}<->{RemoteEndPoint})"; } ================================================ FILE: src/Orleans.TestingHost/InMemoryTransport/InMemoryTransportListenerFactory.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.Hosting; using Orleans.Networking.Shared; using Orleans.Runtime.Messaging; namespace Orleans.TestingHost.InMemoryTransport; internal static class InMemoryTransportExtensions { public static ISiloBuilder UseInMemoryConnectionTransport(this ISiloBuilder siloBuilder, InMemoryTransportConnectionHub hub) { siloBuilder.ConfigureServices(services => { services.AddKeyedSingleton(SiloConnectionFactory.ServicesKey, CreateInMemoryConnectionFactory(hub)); services.AddKeyedSingleton(SiloConnectionListener.ServicesKey, CreateInMemoryConnectionListenerFactory(hub)); services.AddKeyedSingleton(GatewayConnectionListener.ServicesKey, CreateInMemoryConnectionListenerFactory(hub)); }); return siloBuilder; } public static IClientBuilder UseInMemoryConnectionTransport(this IClientBuilder clientBuilder, InMemoryTransportConnectionHub hub) { clientBuilder.ConfigureServices(services => { services.AddKeyedSingleton(ClientOutboundConnectionFactory.ServicesKey, CreateInMemoryConnectionFactory(hub)); }); return clientBuilder; } private static Func CreateInMemoryConnectionFactory(InMemoryTransportConnectionHub hub) { return (IServiceProvider sp, object key) => { var loggerFactory = sp.GetRequiredService(); var sharedMemoryPool = sp.GetRequiredService(); return new InMemoryTransportConnectionFactory(hub, loggerFactory, sharedMemoryPool); }; } private static Func CreateInMemoryConnectionListenerFactory(InMemoryTransportConnectionHub hub) { return (IServiceProvider sp, object key) => { var loggerFactory = sp.GetRequiredService(); var sharedMemoryPool = sp.GetRequiredService(); return new InMemoryTransportListener(hub, loggerFactory, sharedMemoryPool); }; } } internal class InMemoryTransportListener : IConnectionListenerFactory, IConnectionListener { private readonly Channel<(InMemoryTransportConnection Connection, TaskCompletionSource ConnectionAcceptedTcs)> _acceptQueue = Channel.CreateUnbounded<(InMemoryTransportConnection, TaskCompletionSource)>(); private readonly InMemoryTransportConnectionHub _hub; private readonly ILoggerFactory _loggerFactory; private readonly SharedMemoryPool _memoryPool; private readonly CancellationTokenSource _disposedCts = new(); public InMemoryTransportListener(InMemoryTransportConnectionHub hub, ILoggerFactory loggerFactory, SharedMemoryPool memoryPool) { _hub = hub; _loggerFactory = loggerFactory; _memoryPool = memoryPool; } public CancellationToken OnDisposed => _disposedCts.Token; public EndPoint EndPoint { get; set; } public async Task ConnectAsync(InMemoryTransportConnection connection) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (_acceptQueue.Writer.TryWrite((connection, completion))) { var connected = await completion.Task; if (connected) { return; } } throw new ConnectionFailedException($"Unable to connect to {EndPoint} because its listener has terminated."); } public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { if (await _acceptQueue.Reader.WaitToReadAsync(cancellationToken)) { if (_acceptQueue.Reader.TryRead(out var item)) { var remoteConnectionContext = item.Connection; var localConnectionContext = InMemoryTransportConnection.Create( _memoryPool.Pool, _loggerFactory.CreateLogger(), other: remoteConnectionContext, localEndPoint: EndPoint); // Set the result to true to indicate that the connection was accepted. item.ConnectionAcceptedTcs.TrySetResult(true); return localConnectionContext; } } return null; } public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { EndPoint = endpoint; _hub.RegisterConnectionListenerFactory(endpoint, this); return new ValueTask(this); } public ValueTask DisposeAsync() { return UnbindAsync(default); } public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _acceptQueue.Writer.TryComplete(); while (_acceptQueue.Reader.TryRead(out var item)) { // Set the result to false to indicate that the listener has terminated. item.ConnectionAcceptedTcs.TrySetResult(false); } _disposedCts.Cancel(); return default; } } internal class InMemoryTransportConnectionHub { private readonly ConcurrentDictionary _listeners = new(); public static InMemoryTransportConnectionHub Instance { get; } = new(); public void RegisterConnectionListenerFactory(EndPoint endPoint, InMemoryTransportListener listener) { _listeners[endPoint] = listener; listener.OnDisposed.Register(() => { ((IDictionary)_listeners).Remove(new KeyValuePair(endPoint, listener)); }); } public InMemoryTransportListener GetConnectionListenerFactory(EndPoint endPoint) { _listeners.TryGetValue(endPoint, out var listener); return listener; } } internal class InMemoryTransportConnectionFactory : IConnectionFactory { private readonly InMemoryTransportConnectionHub _hub; private readonly ILoggerFactory _loggerFactory; private readonly SharedMemoryPool _memoryPool; private readonly IPEndPoint _localEndpoint; public InMemoryTransportConnectionFactory(InMemoryTransportConnectionHub hub, ILoggerFactory loggerFactory, SharedMemoryPool memoryPool) { _hub = hub; _loggerFactory = loggerFactory; _memoryPool = memoryPool; _localEndpoint = new IPEndPoint(IPAddress.Loopback, Random.Shared.Next(1024, ushort.MaxValue - 1024)); } public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var listener = _hub.GetConnectionListenerFactory(endpoint); if (listener is null) { throw new ConnectionFailedException($"Unable to connect to endpoint {endpoint} because no such endpoint is currently registered."); } var connectionContext = InMemoryTransportConnection.Create( _memoryPool.Pool, _loggerFactory.CreateLogger(), _localEndpoint, endpoint); await listener.ConnectAsync(connectionContext).WaitAsync(cancellationToken); return connectionContext; } } ================================================ FILE: src/Orleans.TestingHost/InProcTestCluster.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.TestingHost.Utils; using Microsoft.Extensions.Configuration; using Orleans.Configuration; using Microsoft.Extensions.Options; using Microsoft.Extensions.Hosting; using Orleans.TestingHost.InMemoryTransport; using System.Net; using Orleans.Statistics; using Orleans.TestingHost.InProcess; using Orleans.Runtime.Hosting; using Orleans.GrainDirectory; using Orleans.Messaging; using Orleans.Hosting; using Orleans.Runtime.TestHooks; using Orleans.Configuration.Internal; using Orleans.TestingHost.Logging; namespace Orleans.TestingHost; /// /// A host class for local testing with Orleans using in-process silos. /// public sealed class InProcessTestCluster : IDisposable, IAsyncDisposable { private readonly List _silos = []; private readonly StringBuilder _log = new(); private readonly InMemoryTransportConnectionHub _transportHub = new(); private readonly InProcessGrainDirectory _grainDirectory; private readonly InProcessMembershipTable _membershipTable; private bool _disposed; private int _startedInstances; /// /// Collection of all known silos. /// public ReadOnlyCollection Silos { get { lock (_silos) { return new List(_silos).AsReadOnly(); } } } /// /// Options used to configure the test cluster. /// /// This is the options you configured your test cluster with, or the default one. /// If the cluster is being configured via ClusterConfiguration, then this object may not reflect the true settings. /// public InProcessTestClusterOptions Options { get; } /// /// The internal client interface. /// internal IHost ClientHost { get; private set; } /// /// The internal client interface. /// internal IInternalClusterClient InternalClient => ClientHost?.Services.GetRequiredService(); /// /// The client. /// public IClusterClient Client => ClientHost?.Services.GetRequiredService(); /// /// The port allocator. /// public ITestClusterPortAllocator PortAllocator { get; } /// /// Configures the test cluster plus client in-process. /// public InProcessTestCluster( InProcessTestClusterOptions options, ITestClusterPortAllocator portAllocator) { Options = options; PortAllocator = portAllocator; _membershipTable = new(options.ClusterId); _grainDirectory = new(_membershipTable.GetSiloStatus); } /// /// Returns the associated with the given . /// /// The silo process to the the service provider for. /// If is one of the existing silos will be picked randomly. public IServiceProvider GetSiloServiceProvider(SiloAddress silo = null) { if (silo != null) { var handle = Silos.FirstOrDefault(x => x.SiloAddress.Equals(silo)); return handle != null ? handle.SiloHost.Services : throw new ArgumentException($"The provided silo address '{silo}' is unknown."); } else { var index = Random.Shared.Next(Silos.Count); return Silos[index].SiloHost.Services; } } /// /// Deploys the cluster using the specified configuration and starts the client in-process. /// public async Task DeployAsync() { if (_silos.Count > 0) throw new InvalidOperationException("Cluster host already deployed."); AppDomain.CurrentDomain.UnhandledException += ReportUnobservedException; try { string startMsg = "----------------------------- STARTING NEW UNIT TEST SILO HOST: " + GetType().FullName + " -------------------------------------"; WriteLog(startMsg); await InitializeAsync(); if (Options.InitializeClientOnDeploy) { await WaitForInitialStabilization(); } } catch (TimeoutException te) { FlushLogToConsole(); throw new TimeoutException("Timeout during test initialization", te); } catch (Exception ex) { await StopAllSilosAsync(); Exception baseExc = ex.GetBaseException(); FlushLogToConsole(); if (baseExc is TimeoutException) { throw new TimeoutException("Timeout during test initialization", ex); } // IMPORTANT: // Do NOT re-throw the original exception here, also not as an internal exception inside AggregateException // Due to the way MS tests works, if the original exception is an Orleans exception, // it's assembly might not be loaded yet in this phase of the test. // As a result, we will get "MSTest: Unit Test Adapter threw exception: Type is not resolved for member XXX" // and will loose the original exception. This makes debugging tests super hard! // The root cause has to do with us initializing our tests from Test constructor and not from TestInitialize method. // More details: http://dobrzanski.net/2010/09/20/mstest-unit-test-adapter-threw-exception-type-is-not-resolved-for-member/ //throw new Exception( // string.Format("Exception during test initialization: {0}", // LogFormatter.PrintException(baseExc))); throw; } } private async Task WaitForInitialStabilization() { // Poll each silo to check that it knows the expected number of active silos. // If any silo does not have the expected number of active silos in its cluster membership oracle, try again. // If the cluster membership has not stabilized after a certain period of time, give up and continue anyway. var totalWait = Stopwatch.StartNew(); while (true) { var silos = Silos; var expectedCount = silos.Count; var remainingSilos = expectedCount; foreach (var silo in silos) { var hooks = InternalClient.GetTestHooks(silo); var statuses = await hooks.GetApproximateSiloStatuses(); var activeCount = statuses.Count(s => s.Value == SiloStatus.Active); if (activeCount != expectedCount) break; remainingSilos--; } if (remainingSilos == 0) { totalWait.Stop(); break; } WriteLog($"{remainingSilos} silos do not have a consistent cluster view, waiting until stabilization."); await Task.Delay(TimeSpan.FromMilliseconds(100)); if (totalWait.Elapsed < TimeSpan.FromSeconds(60)) { WriteLog($"Warning! {remainingSilos} silos do not have a consistent cluster view after {totalWait.ElapsedMilliseconds}ms, continuing without stabilization."); break; } } } /// /// Get the list of current active silos. /// /// List of current silos. public IEnumerable GetActiveSilos() { var additional = new List(); lock (_silos) { additional.AddRange(_silos); } WriteLog("GetActiveSilos: {0} Silos={1}", additional.Count, Runtime.Utils.EnumerableToString(additional)); if (additional.Count > 0) foreach (var s in additional) if (s?.IsActive == true) yield return s; } /// /// Find the silo handle for the specified silo address. /// /// Silo address to be found. /// SiloHandle of the appropriate silo, or null if not found. public InProcessSiloHandle GetSiloForAddress(SiloAddress siloAddress) { var activeSilos = GetActiveSilos().ToList(); var ret = activeSilos.Find(s => s.SiloAddress.Equals(siloAddress)); return ret; } /// /// Wait for the silo liveness sub-system to detect and act on any recent cluster membership changes. /// /// Whether recent membership changes we done by graceful Stop. public async Task WaitForLivenessToStabilizeAsync(bool didKill = false) { var clusterMembershipOptions = Client.ServiceProvider.GetRequiredService>().Value; TimeSpan stabilizationTime = GetLivenessStabilizationTime(clusterMembershipOptions, didKill); WriteLog(Environment.NewLine + Environment.NewLine + "WaitForLivenessToStabilize is about to sleep for {0}", stabilizationTime); await Task.Delay(stabilizationTime); WriteLog("WaitForLivenessToStabilize is done sleeping"); } /// /// Get the timeout value to use to wait for the silo liveness sub-system to detect and act on any recent cluster membership changes. /// /// public static TimeSpan GetLivenessStabilizationTime(ClusterMembershipOptions clusterMembershipOptions, bool didKill = false) { TimeSpan stabilizationTime = TimeSpan.Zero; if (didKill) { // in case of hard kill (kill and not Stop), we should give silos time to detect failures first. stabilizationTime = TestingUtils.Multiply(clusterMembershipOptions.ProbeTimeout, clusterMembershipOptions.NumMissedProbesLimit); } if (clusterMembershipOptions.UseLivenessGossip) { stabilizationTime += TimeSpan.FromSeconds(5); } else { stabilizationTime += TestingUtils.Multiply(clusterMembershipOptions.TableRefreshTimeout, 2); } return stabilizationTime; } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. public InProcessSiloHandle StartAdditionalSilo() { return StartAdditionalSiloAsync().GetAwaiter().GetResult(); } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. public async Task StartAdditionalSiloAsync() { return (await StartSilosAsync(1)).Single(); } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. [Obsolete("Use overload which does not have a 'startAdditionalSiloOnNewPort' parameter.")] public InProcessSiloHandle StartAdditionalSilo(bool startAdditionalSiloOnNewPort) { return StartAdditionalSiloAsync(startAdditionalSiloOnNewPort).GetAwaiter().GetResult(); } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. public async Task StartAdditionalSiloAsync(bool startAdditionalSiloOnNewPort) { return (await StartSilosAsync(1)).Single(); } /// /// Start a number of additional silo, so that they join the existing cluster. /// /// Number of silos to start. /// /// List of SiloHandles for the newly started silos. [Obsolete("Use overload which does not have a 'startAdditionalSiloOnNewPort' parameter.")] public async Task> StartSilosAsync(int silosToStart, bool startAdditionalSiloOnNewPort) { return await StartSilosAsync(silosToStart); } /// /// Start a number of additional silo, so that they join the existing cluster. /// /// Number of silos to start. /// List of SiloHandles for the newly started silos. public async Task> StartSilosAsync(int silosToStart) { var instances = new List(); if (silosToStart > 0) { var siloStartTasks = Enumerable.Range(_startedInstances, silosToStart) .Select(instanceNumber => Task.Run(() => StartSiloAsync((short)instanceNumber, Options))).ToArray(); try { await Task.WhenAll(siloStartTasks); } catch (Exception) { lock (_silos) { _silos.AddRange(siloStartTasks.Where(t => t.Exception == null).Select(t => t.Result)); } throw; } instances.AddRange(siloStartTasks.Select(t => t.Result)); lock (_silos) { _silos.AddRange(instances); } } return instances; } /// /// Stop all silos. /// public async Task StopSilosAsync() { foreach (var instance in _silos.ToList()) { await StopSiloAsync(instance); } } /// /// Stop cluster client as an asynchronous operation. /// /// A representing the asynchronous operation. public async Task StopClusterClientAsync() { var client = ClientHost; try { if (client is not null) { await client.StopAsync().ConfigureAwait(false); } } catch (Exception exc) { WriteLog("Exception stopping client: {0}", exc); } finally { await DisposeAsync(client).ConfigureAwait(false); ClientHost = null; } } /// /// Stop all current silos. /// public void StopAllSilos() { StopAllSilosAsync().GetAwaiter().GetResult(); } /// /// Stop all current silos. /// public async Task StopAllSilosAsync() { await StopClusterClientAsync(); await StopSilosAsync(); AppDomain.CurrentDomain.UnhandledException -= ReportUnobservedException; } /// /// Do a semi-graceful Stop of the specified silo. /// /// Silo to be stopped. public async Task StopSiloAsync(InProcessSiloHandle instance) { if (instance != null) { await StopSiloAsync(instance, true); lock (_silos) { _silos.Remove(instance); } } } /// /// Do an immediate Kill of the specified silo. /// /// Silo to be killed. public async Task KillSiloAsync(InProcessSiloHandle instance) { if (instance != null) { // do NOT stop, just kill directly, to simulate crash. await StopSiloAsync(instance, false); lock (_silos) { _silos.Remove(instance); } } } /// /// Performs a hard kill on client. Client will not cleanup resources. /// public async Task KillClientAsync() { var client = ClientHost; if (client != null) { var cancelled = new CancellationTokenSource(); cancelled.Cancel(); try { await client.StopAsync(cancelled.Token).ConfigureAwait(false); } finally { await DisposeAsync(client); ClientHost = null; } } } /// /// Do a Stop or Kill of the specified silo, followed by a restart. /// /// Silo to be restarted. public async Task RestartSiloAsync(InProcessSiloHandle instance) { if (instance != null) { var instanceNumber = instance.InstanceNumber; await StopSiloAsync(instance); var newInstance = await StartSiloAsync(instanceNumber, Options); lock (_silos) { _silos.Add(newInstance); } return newInstance; } return null; } /// /// Restart a previously stopped. /// /// Silo to be restarted. public async Task RestartStoppedSecondarySiloAsync(string siloName) { if (siloName == null) throw new ArgumentNullException(nameof(siloName)); var siloHandle = Silos.Single(s => s.Name.Equals(siloName, StringComparison.Ordinal)); var newInstance = await StartSiloAsync(Silos.IndexOf(siloHandle), Options); lock (_silos) { _silos.Add(newInstance); } return newInstance; } /// /// Initialize the grain client. This should be already done by /// public async Task InitializeClientAsync() { WriteLog("Initializing Cluster Client"); if (ClientHost is not null) { await StopClusterClientAsync(); } var hostBuilder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings { EnvironmentName = Environments.Development, ApplicationName = "TestClusterClient", DisableDefaults = true, }); foreach (var hostDelegate in Options.ClientHostConfigurationDelegates) { hostDelegate(hostBuilder); } hostBuilder.UseOrleansClient(clientBuilder => { clientBuilder.Configure(o => { o.ClusterId = Options.ClusterId; o.ServiceId = Options.ServiceId; }); if (Options.UseTestClusterMembership) { clientBuilder.Services.AddSingleton(_membershipTable); } clientBuilder.UseInMemoryConnectionTransport(_transportHub); }); TryConfigureFileLogging(Options, hostBuilder.Services, "TestClusterClient"); ClientHost = hostBuilder.Build(); await ClientHost.StartAsync(); } private async Task InitializeAsync() { var silosToStart = Options.InitialSilosCount; if (silosToStart > 0) { await StartSilosAsync(silosToStart); } WriteLog("Done initializing cluster"); if (Options.InitializeClientOnDeploy) { await InitializeClientAsync(); } } public async Task CreateSiloAsync(InProcessTestSiloSpecificOptions siloOptions) { var host = await Task.Run(async () => { var siloName = siloOptions.SiloName; var appBuilder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings { ApplicationName = siloName, EnvironmentName = Environments.Development, DisableDefaults = true }); var services = appBuilder.Services; TryConfigureFileLogging(Options, services, siloName); if (Debugger.IsAttached) { // Test is running inside debugger - Make timeout ~= infinite services.Configure(op => op.ResponseTimeout = TimeSpan.FromMilliseconds(1000000)); } foreach (var hostDelegate in Options.SiloHostConfigurationDelegates) { hostDelegate(siloOptions, appBuilder); } appBuilder.UseOrleans(siloBuilder => { siloBuilder.Configure(o => { o.ClusterId = Options.ClusterId; o.ServiceId = Options.ServiceId; }); siloBuilder.Configure(o => { o.SiloName = siloOptions.SiloName; }); siloBuilder.Configure(o => { o.AdvertisedIPAddress = IPAddress.Loopback; o.SiloPort = siloOptions.SiloPort; o.GatewayPort = siloOptions.GatewayPort; }); siloBuilder.Services .Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); if (Options.UseTestClusterMembership) { services.AddSingleton(_membershipTable); siloBuilder.AddGrainDirectory(GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY, (_, _) => _grainDirectory); } siloBuilder.UseInMemoryConnectionTransport(_transportHub); services.AddSingleton(); services.AddSingleton(); if (!Options.UseRealEnvironmentStatistics) { services.AddFromExisting(); } }); var host = appBuilder.Build(); InitializeTestHooksSystemTarget(host); await host.StartAsync(); return host; }); return new InProcessSiloHandle { Name = siloOptions.SiloName, SiloHost = host, SiloAddress = host.Services.GetRequiredService().SiloAddress, GatewayAddress = host.Services.GetRequiredService().GatewayAddress, }; } /// /// Start a new silo in the target cluster /// /// The InProcessTestCluster in which the silo should be deployed /// The instance number to deploy /// The options to use. /// A handle to the silo deployed public static async Task StartSiloAsync(InProcessTestCluster cluster, int instanceNumber, InProcessTestClusterOptions clusterOptions) { if (cluster == null) throw new ArgumentNullException(nameof(cluster)); return await cluster.StartSiloAsync(instanceNumber, clusterOptions); } /// /// Start a new silo in the target cluster /// /// The InProcessTestCluster in which the silo should be deployed /// The instance number to deploy /// The options to use. /// Configuration overrides. /// Whether we start this silo on a new port, instead of the default one /// A handle to the silo deployed [Obsolete("Use the overload which does not have a 'startSiloOnNewPort' parameter.")] public static async Task StartSiloAsync(InProcessTestCluster cluster, int instanceNumber, InProcessTestClusterOptions clusterOptions, IReadOnlyList configurationOverrides, bool startSiloOnNewPort) { if (cluster == null) throw new ArgumentNullException(nameof(cluster)); return await cluster.StartSiloAsync(instanceNumber, clusterOptions, configurationOverrides, startSiloOnNewPort); } /// /// Starts a new silo. /// /// The instance number to deploy /// The options to use. /// A handle to the deployed silo. public async Task StartSiloAsync(int instanceNumber, InProcessTestClusterOptions clusterOptions) { var siloOptions = InProcessTestSiloSpecificOptions.Create(this, clusterOptions, instanceNumber, assignNewPort: true); var handle = await CreateSiloAsync(siloOptions); handle.InstanceNumber = (short)instanceNumber; Interlocked.Increment(ref _startedInstances); return handle; } /// /// Starts a new silo. /// /// The instance number to deploy /// The options to use. /// Configuration overrides. /// Whether we start this silo on a new port, instead of the default one /// A handle to the deployed silo. [Obsolete("Use the overload which does not have a 'startSiloOnNewPort' parameter.")] public async Task StartSiloAsync(int instanceNumber, InProcessTestClusterOptions clusterOptions, IReadOnlyList configurationOverrides, bool startSiloOnNewPort) { return await StartSiloAsync(instanceNumber, clusterOptions); } private async Task StopSiloAsync(InProcessSiloHandle instance, bool stopGracefully) { try { await instance.StopSiloAsync(stopGracefully).ConfigureAwait(false); } finally { await DisposeAsync(instance).ConfigureAwait(false); Interlocked.Decrement(ref _startedInstances); } } /// /// Gets the log. /// /// The log contents. public string GetLog() { return _log.ToString(); } private void ReportUnobservedException(object sender, UnhandledExceptionEventArgs eventArgs) { Exception exception = (Exception)eventArgs.ExceptionObject; WriteLog("Unobserved exception: {0}", exception); } private void WriteLog(string format, params object[] args) { _log.AppendFormat(format + Environment.NewLine, args); } private void FlushLogToConsole() { Console.WriteLine(GetLog()); } /// public async ValueTask DisposeAsync() { if (_disposed) { return; } await Task.Run(async () => { foreach (var handle in Silos) { await DisposeAsync(handle).ConfigureAwait(false); } await DisposeAsync(ClientHost).ConfigureAwait(false); ClientHost = null; PortAllocator?.Dispose(); }); _disposed = true; } /// public void Dispose() { if (_disposed) { return; } foreach (var handle in Silos) { handle.Dispose(); } ClientHost?.Dispose(); PortAllocator?.Dispose(); _disposed = true; } private static async Task DisposeAsync(IDisposable value) { if (value is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } else if (value is IDisposable disposable) { disposable.Dispose(); } } private static void TryConfigureFileLogging(InProcessTestClusterOptions options, IServiceCollection services, string name) { if (options.ConfigureFileLogging) { var fileName = TestingUtils.CreateTraceFileName(name, options.ClusterId); services.AddLogging(loggingBuilder => loggingBuilder.AddFile(fileName)); } } private static void InitializeTestHooksSystemTarget(IHost host) { _ = host.Services.GetRequiredService(); } } ================================================ FILE: src/Orleans.TestingHost/InProcTestClusterBuilder.cs ================================================ using System; using System.Globalization; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Runtime; namespace Orleans.TestingHost; /// Configuration builder for starting a . public sealed class InProcessTestClusterBuilder { /// /// Initializes a new instance of using the default options. /// public InProcessTestClusterBuilder() : this(2) { } /// /// Initializes a new instance of overriding the initial silos count. /// /// The number of initial silos to deploy. public InProcessTestClusterBuilder(short initialSilosCount) { Options = new InProcessTestClusterOptions { InitialSilosCount = initialSilosCount, ClusterId = CreateClusterId(), ServiceId = Guid.NewGuid().ToString("N"), UseTestClusterMembership = true, InitializeClientOnDeploy = true, ConfigureFileLogging = true, AssumeHomogenousSilosForTesting = true }; } /// /// Gets the options. /// /// The options. public InProcessTestClusterOptions Options { get; } /// /// The port allocator. /// public ITestClusterPortAllocator PortAllocator { get; } = new TestClusterPortAllocator(); /// /// Adds a delegate for configuring silo and client hosts. /// public InProcessTestClusterBuilder ConfigureHost(Action configureDelegate) { Options.SiloHostConfigurationDelegates.Add((_, hostBuilder) => configureDelegate(hostBuilder)); Options.ClientHostConfigurationDelegates.Add(configureDelegate); return this; } /// /// Adds a delegate to configure silos. /// /// The builder. public InProcessTestClusterBuilder ConfigureSilo(Action configureSiloDelegate) { Options.SiloHostConfigurationDelegates.Add((options, hostBuilder) => hostBuilder.UseOrleans(siloBuilder => configureSiloDelegate(options, siloBuilder))); return this; } /// /// Adds a delegate to configure silo hosts. /// /// The builder. public InProcessTestClusterBuilder ConfigureSiloHost(Action configureSiloHostDelegate) { Options.SiloHostConfigurationDelegates.Add(configureSiloHostDelegate); return this; } /// /// Adds a delegate to configure clients. /// /// The builder. public InProcessTestClusterBuilder ConfigureClient(Action configureClientDelegate) { Options.ClientHostConfigurationDelegates.Add(hostBuilder => hostBuilder.UseOrleansClient(clientBuilder => configureClientDelegate(clientBuilder))); return this; } /// /// Adds a delegate to configure clients hosts. /// /// The builder. public InProcessTestClusterBuilder ConfigureClientHost(Action configureHostDelegate) { Options.ClientHostConfigurationDelegates.Add(hostBuilder => configureHostDelegate(hostBuilder)); return this; } /// /// Builds this instance. /// /// InProcessTestCluster. public InProcessTestCluster Build() { var portAllocator = PortAllocator; ConfigureDefaultPorts(); var testCluster = new InProcessTestCluster(Options, portAllocator); return testCluster; } /// /// Creates a cluster identifier. /// /// A new cluster identifier. public static string CreateClusterId() { string prefix = "testcluster-"; int randomSuffix = Random.Shared.Next(1000); DateTime now = DateTime.UtcNow; string DateTimeFormat = @"yyyy-MM-dd\tHH-mm-ss"; return $"{prefix}{now.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}-{randomSuffix}"; } private void ConfigureDefaultPorts() { // Set base ports if none are currently set. (int baseSiloPort, int baseGatewayPort) = PortAllocator.AllocateConsecutivePortPairs(Options.InitialSilosCount + 3); if (Options.BaseSiloPort == 0) Options.BaseSiloPort = baseSiloPort; if (Options.BaseGatewayPort == 0) Options.BaseGatewayPort = baseGatewayPort; } internal class ConfigureStaticClusterDeploymentOptions : IHostConfigurator { public void Configure(IHostBuilder hostBuilder) { hostBuilder.ConfigureServices((context, services) => { var initialSilos = int.Parse(context.Configuration[nameof(InProcessTestClusterOptions.InitialSilosCount)]); var siloNames = Enumerable.Range(0, initialSilos).Select(GetSiloName).ToList(); services.Configure(options => options.SiloNames = siloNames); }); } private static string GetSiloName(int instanceNumber) { return instanceNumber == 0 ? Silo.PrimarySiloName : $"Secondary_{instanceNumber}"; } } } ================================================ FILE: src/Orleans.TestingHost/InProcTestClusterOptions.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; namespace Orleans.TestingHost; /// /// Configuration options for test clusters. /// public sealed class InProcessTestClusterOptions { /// /// Gets or sets the cluster identifier. /// /// /// The cluster identifier. public string ClusterId { get; set; } /// /// Gets or sets the service identifier. /// /// /// The service identifier. public string ServiceId { get; set; } /// /// Gets or sets the base silo port, which is the port for the first silo. Other silos will use subsequent ports. /// /// The base silo port. internal int BaseSiloPort { get; set; } /// /// Gets or sets the base gateway port, which is the gateway port for the first silo. Other silos will use subsequent ports. /// /// The base gateway port. internal int BaseGatewayPort { get; set; } /// /// Gets or sets a value indicating whether to use test cluster membership. /// /// if test cluster membership should be used; otherwise, . internal bool UseTestClusterMembership { get; set; } /// /// Gets or sets a value indicating whether to use the real environment statistics. /// public bool UseRealEnvironmentStatistics { get; set; } /// /// Gets or sets a value indicating whether to initialize the client immediately on deployment. /// /// if the client should be initialized immediately on deployment; otherwise, . public bool InitializeClientOnDeploy { get; set; } /// /// Gets or sets the initial silos count. /// /// The initial silos count. public short InitialSilosCount { get; set; } /// /// Gets or sets a value indicating whether to configure file logging. /// /// if file logging should be configured; otherwise, . public bool ConfigureFileLogging { get; set; } = true; /// /// Gets or sets a value indicating whether to assume homogeneous silos for testing purposes. /// /// if the cluster should assume homogeneous silos; otherwise, . public bool AssumeHomogenousSilosForTesting { get; set; } /// /// Gets or sets a value indicating whether each silo should host a gateway. /// /// if each silo should host a gateway; otherwise, . public bool GatewayPerSilo { get; set; } = true; /// /// Gets the silo host configuration delegates. /// /// The silo host configuration delegates. public List> SiloHostConfigurationDelegates { get; } = []; /// /// Gets the client host configuration delegates. /// /// The client host configuration delegates. public List> ClientHostConfigurationDelegates { get; } = []; } ================================================ FILE: src/Orleans.TestingHost/InProcTestSiloSpecificOptions.cs ================================================ namespace Orleans.TestingHost; /// /// Configuration overrides for individual silos. /// public sealed class InProcessTestSiloSpecificOptions { /// /// Gets or sets the silo port. /// /// The silo port. public int SiloPort { get; set; } /// /// Gets or sets the gateway port. /// /// The gateway port. public int GatewayPort { get; set; } /// /// Gets or sets the name of the silo. /// /// The name of the silo. public string SiloName { get; set; } /// /// Creates an instance of the class. /// /// The test cluster. /// The test cluster options. /// The instance number. /// if set to , assign a new port for the silo. /// The options. public static InProcessTestSiloSpecificOptions Create(InProcessTestCluster testCluster, InProcessTestClusterOptions testClusterOptions, int instanceNumber, bool assignNewPort = false) { var result = new InProcessTestSiloSpecificOptions { SiloName = $"Silo_{instanceNumber}", }; if (assignNewPort) { var (siloPort, gatewayPort) = testCluster.PortAllocator.AllocateConsecutivePortPairs(1); result.SiloPort = siloPort; result.GatewayPort = (instanceNumber == 0 || testClusterOptions.GatewayPerSilo) ? gatewayPort : 0; } else { result.SiloPort = testClusterOptions.BaseSiloPort + instanceNumber; result.GatewayPort = (instanceNumber == 0 || testClusterOptions.GatewayPerSilo) ? testClusterOptions.BaseGatewayPort + instanceNumber : 0; } return result; } } ================================================ FILE: src/Orleans.TestingHost/InProcess/InProcessGrainDirectory.cs ================================================ #nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.GrainDirectory; using Orleans.Runtime; namespace Orleans.TestingHost.InProcess; internal sealed class InProcessGrainDirectory(Func getSiloStatus) : IGrainDirectory { private readonly ConcurrentDictionary _entries = []; public Task Lookup(GrainId grainId) { if (_entries.TryGetValue(grainId, out var result) && !IsSiloDead(result)) { return Task.FromResult(result); } return Task.FromResult(null); } public Task Register(GrainAddress address, GrainAddress? previousAddress) { ArgumentNullException.ThrowIfNull(address); var result = _entries.AddOrUpdate( address.GrainId, static (grainId, state) => state.Address, static (grainId, existing, state) => { if (existing is null || state.PreviousAddress is { } prev && existing.Matches(prev) || state.Self.IsSiloDead(existing)) { return state.Address; } return existing; }, (Self: this, Address: address, PreviousAddress: previousAddress)); if (result is null || IsSiloDead(result)) { return Task.FromResult(null); } return Task.FromResult(result); } public Task Register(GrainAddress address) => Register(address, null); public Task Unregister(GrainAddress address) { if (!((IDictionary)_entries).Remove(KeyValuePair.Create(address.GrainId, address))) { if (_entries.TryGetValue(address.GrainId, out var existing) && (existing.Matches(address) || IsSiloDead(existing))) { ((IDictionary)_entries).Remove(KeyValuePair.Create(existing.GrainId, existing)); } } return Task.CompletedTask; } public Task UnregisterSilos(List siloAddresses) { foreach (var entry in _entries) { foreach (var silo in siloAddresses) { if (silo.Equals(entry.Value.SiloAddress)) { ((IDictionary)_entries).Remove(entry); } } } return Task.CompletedTask; } private bool IsSiloDead(GrainAddress existing) => existing.SiloAddress is not { } address || getSiloStatus(address) is SiloStatus.Dead or SiloStatus.None; } ================================================ FILE: src/Orleans.TestingHost/InProcess/InProcessMembershipTable.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Orleans.Runtime; using System.Globalization; using System.Threading.Tasks; using Orleans.Messaging; namespace Orleans.TestingHost.InProcess; /// /// An in-memory implementation of for testing purposes. /// internal sealed class InProcessMembershipTable(string clusterId) : IMembershipTable, IGatewayListProvider { private readonly Table _table = new(); private readonly string _clusterId = clusterId; public TimeSpan MaxStaleness => TimeSpan.Zero; public bool IsUpdatable => true; public Task InitializeMembershipTable(bool tryInitTableVersion) => Task.CompletedTask; public Task DeleteMembershipTableEntries(string clusterId) { if (string.Equals(_clusterId, clusterId, StringComparison.Ordinal)) { _table.Clear(); } return Task.CompletedTask; } public Task ReadRow(SiloAddress key) => Task.FromResult(_table.Read(key)); public Task ReadAll() => Task.FromResult(_table.ReadAll()); public Task InsertRow(MembershipEntry entry, TableVersion tableVersion) => Task.FromResult(_table.Insert(entry, tableVersion)); public Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) => Task.FromResult(_table.Update(entry, etag, tableVersion)); public Task UpdateIAmAlive(MembershipEntry entry) { _table.UpdateIAmAlive(entry); return Task.CompletedTask; } public Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { _table.CleanupDefunctSiloEntries(beforeDate); return Task.CompletedTask; } public Task InitializeGatewayListProvider() => Task.CompletedTask; public Task> GetGateways() { var table = _table.ReadAll(); var result = table.Members .Where(x => x.Item1.Status == SiloStatus.Active && x.Item1.ProxyPort != 0) .Select(x => { var entry = x.Item1; return SiloAddress.New(entry.SiloAddress.Endpoint.Address, entry.ProxyPort, entry.SiloAddress.Generation).ToGatewayUri(); }).ToList(); return Task.FromResult>(result); } public SiloStatus GetSiloStatus(SiloAddress address) => _table.GetSiloStatus(address); private sealed class Table { #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else private readonly object _lock = new(); #endif private readonly Dictionary _table = []; private TableVersion _tableVersion; private long _lastETagCounter; public Table() { _tableVersion = new TableVersion(0, NewETag()); } public SiloStatus GetSiloStatus(SiloAddress key) { lock (_lock) { return _table.TryGetValue(key, out var data) ? data.Entry.Status : SiloStatus.None; } } public MembershipTableData Read(SiloAddress key) { lock (_lock) { return _table.TryGetValue(key, out var data) ? new MembershipTableData(Tuple.Create(data.Entry.Copy(), data.ETag), _tableVersion) : new MembershipTableData(_tableVersion); } } public MembershipTableData ReadAll() { lock (_lock) { return new MembershipTableData(_table.Values.Select(data => Tuple.Create(data.Entry.Copy(), data.ETag)).ToList(), _tableVersion); } } public TableVersion ReadTableVersion() => _tableVersion; public bool Insert(MembershipEntry entry, TableVersion version) { lock (_lock) { if (_table.TryGetValue(entry.SiloAddress, out var data)) { return false; } if (!_tableVersion.VersionEtag.Equals(version.VersionEtag)) { return false; } _table[entry.SiloAddress] = (entry.Copy(), _lastETagCounter++.ToString(CultureInfo.InvariantCulture)); _tableVersion = new TableVersion(version.Version, NewETag()); return true; } } public bool Update(MembershipEntry entry, string etag, TableVersion version) { lock (_lock) { if (!_table.TryGetValue(entry.SiloAddress, out var data)) { return false; } if (!data.ETag.Equals(etag) || !_tableVersion.VersionEtag.Equals(version.VersionEtag)) { return false; } _table[entry.SiloAddress] = (entry.Copy(), _lastETagCounter++.ToString(CultureInfo.InvariantCulture)); _tableVersion = new TableVersion(version.Version, NewETag()); return true; } } public void UpdateIAmAlive(MembershipEntry entry) { lock (_lock) { if (!_table.TryGetValue(entry.SiloAddress, out var data)) { return; } data.Entry.IAmAliveTime = entry.IAmAliveTime; _table[entry.SiloAddress] = (data.Entry, NewETag()); } } public void CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { lock (_lock) { var entries = _table.Values.ToList(); foreach (var (entry, _) in entries) { if (entry.Status == SiloStatus.Dead && new DateTime(Math.Max(entry.IAmAliveTime.Ticks, entry.StartTime.Ticks), DateTimeKind.Utc) < beforeDate) { _table.Remove(entry.SiloAddress, out _); continue; } } } } internal void Clear() { lock (_lock) { _table.Clear(); } } public override string ToString() => $"Table = {ReadAll()}, ETagCounter={_lastETagCounter}"; private string NewETag() => _lastETagCounter++.ToString(CultureInfo.InvariantCulture); } } ================================================ FILE: src/Orleans.TestingHost/InProcessSiloHandle.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Runtime; namespace Orleans.TestingHost { /// /// Represents a handle to a silo that is deployed in the same process and AppDomain. /// public class InProcessSiloHandle : SiloHandle { private bool isActive = true; /// Gets a reference to the silo host. public IHost SiloHost { get; init; } /// /// Gets the silo's service provider. /// public IServiceProvider ServiceProvider => SiloHost.Services; /// public override bool IsActive => isActive; /// /// Create a silo handle. /// /// Name of the silo. /// The configuration. /// An optional delegate which is invoked just prior to building the host builder. /// The silo handle. public static async Task CreateAsync( string siloName, IConfiguration configuration, Action postConfigureHostBuilder = null) { var host = await Task.Run(async () => { var result = TestClusterHostFactory.CreateSiloHost(siloName, configuration, postConfigureHostBuilder); await result.StartAsync(); return result; }); var retValue = new InProcessSiloHandle { Name = siloName, SiloHost = host, SiloAddress = host.Services.GetRequiredService().SiloAddress, GatewayAddress = host.Services.GetRequiredService().GatewayAddress, }; return retValue; } /// public override async Task StopSiloAsync(bool stopGracefully) { using var cancellation = new CancellationTokenSource(); var ct = cancellation.Token; if (!stopGracefully) cancellation.Cancel(); await StopSiloAsync(ct); } /// public override async Task StopSiloAsync(CancellationToken ct) { if (!IsActive) return; try { await Task.Run(() => this.SiloHost.StopAsync(ct)); } catch (Exception exc) { WriteLog(exc); throw; } finally { this.isActive = false; } } /// protected override void Dispose(bool disposing) { if (!this.IsActive) return; if (disposing) { try { StopSiloAsync(true).GetAwaiter().GetResult(); } catch { } this.SiloHost?.Dispose(); } } /// public override async ValueTask DisposeAsync() { if (!this.IsActive) return; try { await StopSiloAsync(true).ConfigureAwait(false); } finally { if (this.SiloHost is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } } } private static void WriteLog(object value) { Console.WriteLine(value?.ToString()); } } } ================================================ FILE: src/Orleans.TestingHost/Logging/FileLogger.cs ================================================ using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using Microsoft.Extensions.Logging; using Orleans.Runtime; namespace Orleans.TestingHost.Logging { /// /// The log output which all share to log messages to /// public class FileLoggingOutput : IDisposable { private static readonly ConcurrentDictionary Instances = new(); private readonly TimeSpan flushInterval = Debugger.IsAttached ? TimeSpan.FromMilliseconds(10) : TimeSpan.FromSeconds(1); #if NET9_0_OR_GREATER private readonly Lock lockObj = new(); #else private readonly object lockObj = new(); #endif private readonly string logFileName; private DateTime lastFlush = DateTime.UtcNow; private StreamWriter logOutput; /// /// Initializes static members of the class. /// static FileLoggingOutput() { AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; static void CurrentDomain_ProcessExit(object sender, EventArgs args) { foreach (var instance in Instances) { instance.Key.Dispose(); } } } /// /// Initializes a new instance of the class. /// /// Name of the log file. public FileLoggingOutput(string fileName) { this.logFileName = fileName; logOutput = new StreamWriter(File.Open(fileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite), Encoding.UTF8); Instances[this] = null; } /// /// Logs a message. /// /// The type of . /// The log level. /// The event identifier. /// The state. /// The exception. /// The formatter. /// The category. public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter, string category) { var logMessage = FormatMessage(DateTime.UtcNow, logLevel, category, formatter(state, exception), exception, eventId); lock (this.lockObj) { if (this.logOutput == null) return; this.logOutput.WriteLine(logMessage); var now = DateTime.UtcNow; if (now - this.lastFlush > flushInterval) { this.lastFlush = now; this.logOutput.Flush(); } } } private static string FormatMessage( DateTime timestamp, LogLevel logLevel, string caller, string message, Exception exception, EventId errorCode) { if (logLevel == LogLevel.Error) message = "!!!!!!!!!! " + message; var exc = LogFormatter.PrintException(exception); var msg = string.Format("[{0} {1}\t{2}\t{3}\t{4}]\t{5}\t{6}", LogFormatter.PrintDate(timestamp), //0 Environment.CurrentManagedThreadId, //1 logLevel.ToString(), //2 errorCode.ToString(), //3 caller, //4 message, //5 exc); //6 return msg; } /// public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { try { lock (this.lockObj) { if (this.logOutput is StreamWriter output) { this.logOutput = null; _ = Instances.TryRemove(this, out _); // Dispose the output, which will flush all buffers. output.Dispose(); } } } catch (Exception exc) { var msg = string.Format("Ignoring error closing log file {0} - {1}", this.logFileName, LogFormatter.PrintException(exc)); Console.WriteLine(msg); } } } /// /// File logger, which logs messages to a file. /// public class FileLogger : ILogger { private readonly FileLoggingOutput output; private readonly string category; /// /// Initializes a new instance of the class. /// /// The output logger. /// The category. public FileLogger(FileLoggingOutput output, string category) { this.category = category; this.output = output; } /// public IDisposable BeginScope(TState state) { return NullScope.Instance; } /// public bool IsEnabled(LogLevel logLevel) { return true; } /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { this.output.Log(logLevel, eventId, state, exception, formatter, this.category); } private class NullScope : IDisposable { public static NullScope Instance { get; } = new NullScope(); private NullScope() { } public void Dispose() { } } } } ================================================ FILE: src/Orleans.TestingHost/Logging/FileLoggerProvider.cs ================================================ using System; using Microsoft.Extensions.Logging; namespace Orleans.TestingHost.Logging { /// /// which outputs to a log file. /// public class FileLoggerProvider : ILoggerProvider { private readonly FileLoggingOutput output; /// /// Initializes a new instance of the class. /// /// The log file path. public FileLoggerProvider(string filePath) { this.output = new FileLoggingOutput(filePath); } /// public ILogger CreateLogger(string categoryName) { return new FileLogger(this.output, categoryName); } /// public void Dispose() { this.output.Dispose(); } } /// /// Extension methods to configure ILoggingBuilder with FileLoggerProvider /// public static class FileLoggerProviderExtensions { /// /// Add to /// /// The logging builder. /// The log file path /// The logging builder. public static ILoggingBuilder AddFile( this ILoggingBuilder builder, string filePathName) { if (builder == null) throw new ArgumentNullException(nameof(builder)); builder.AddProvider(new FileLoggerProvider(filePathName)); return builder; } } } ================================================ FILE: src/Orleans.TestingHost/Orleans.TestingHost.csproj ================================================ Microsoft.Orleans.TestingHost Microsoft Orleans Testing Host Library Microsoft Orleans library for hosting a silo in a testing project. $(DefaultTargetFrameworks) true $(NoWarn);NU5104 ================================================ FILE: src/Orleans.TestingHost/SiloHandle.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.TestingHost { /// /// Represents a handle to a silo that is remotely deployed /// public abstract class SiloHandle : IDisposable, IAsyncDisposable { /// Get or set configuration of the cluster public TestClusterOptions ClusterOptions { get; set; } /// Gets or sets the instance number within the cluster. public short InstanceNumber { get; set; } /// Get or set the name of the silo public string Name { get; set; } /// Get or set the address of the silo public SiloAddress SiloAddress { get; set; } ///// Get the proxy address of the silo public SiloAddress GatewayAddress { get; set; } /// Gets whether the remote silo is expected to be active public abstract bool IsActive { get; } /// Stop the remote silo /// Specifies whether the silo should be stopped gracefully or abruptly. public abstract Task StopSiloAsync(bool stopGracefully); /// Stop the remote silo. This method cannot be use with AppDomain /// Specifies the cancellation token to use for the shutdown sequence public abstract Task StopSiloAsync(CancellationToken ct); /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. protected virtual void Dispose(bool disposing) { if (!this.IsActive) return; // Do not attempt to perform expensive blocking operations in the finalizer thread. // Concrete SiloHandle implementations can do have their own cleanup functionality if (disposing) { StopSiloAsync(true).GetAwaiter().GetResult(); } } /// public abstract ValueTask DisposeAsync(); /// ~SiloHandle() { Dispose(false); } /// public override string ToString() { return SiloAddress.ToString(); } } } ================================================ FILE: src/Orleans.TestingHost/StandaloneSiloHandle.cs ================================================ using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; namespace Orleans.TestingHost { /// /// A silo handle and factory which spawns a separate process for each silo. /// public class StandaloneSiloHandle : SiloHandle { private readonly StringBuilder _outputBuilder; private readonly TaskCompletionSource _startedEvent; private readonly TaskCompletionSource _outputCloseEvent; private readonly StringBuilder _errorBuilder; private readonly TaskCompletionSource _errorCloseEvent; private readonly EventHandler _processExitHandler; private bool isActive = true; private Task _runTask; /// /// The configuration key used to identify the process to launch. /// public const string ExecutablePathConfigKey = "ExecutablePath"; /// Gets a reference to the silo host. private Process Process { get; set; } /// public override bool IsActive => isActive; public StandaloneSiloHandle(string siloName, IConfiguration configuration, string executablePath) { if (string.IsNullOrWhiteSpace(executablePath) || !File.Exists(executablePath)) { throw new ArgumentException("Must provide at least one assembly path"); } Name = siloName; // If the debugger is attached to this process, give it an opportunity to attach to the remote process. if (Debugger.IsAttached) { configuration["AttachDebugger"] = "true"; } var serializedConfiguration = TestClusterHostFactory.SerializeConfiguration(configuration); Process = new Process(); Process.StartInfo = new ProcessStartInfo(executablePath) { ArgumentList = { Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture), serializedConfiguration }, CreateNoWindow = true, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, WorkingDirectory = new FileInfo(executablePath).Directory.FullName, UseShellExecute = false, }; _outputBuilder = new StringBuilder(); _outputCloseEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _startedEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Process.OutputDataReceived += (s, e) => { if (e.Data == null) { _outputCloseEvent.SetResult(true); } else { // Read standard output from the process for status updates. if (!_startedEvent.Task.IsCompleted) { if (e.Data.StartsWith(StandaloneSiloHost.SiloAddressLog, StringComparison.Ordinal)) { SiloAddress = Orleans.Runtime.SiloAddress.FromParsableString(e.Data[StandaloneSiloHost.SiloAddressLog.Length..]); } else if (e.Data.StartsWith(StandaloneSiloHost.GatewayAddressLog, StringComparison.Ordinal)) { GatewayAddress = Orleans.Runtime.SiloAddress.FromParsableString(e.Data[StandaloneSiloHost.GatewayAddressLog.Length..]); } else if (e.Data.StartsWith(StandaloneSiloHost.StartedLog, StringComparison.Ordinal)) { _startedEvent.TrySetResult(true); } } lock (_outputBuilder) { _outputBuilder.AppendLine(e.Data); } } }; _errorBuilder = new StringBuilder(); _errorCloseEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Process.ErrorDataReceived += (s, e) => { if (e.Data == null) { _errorCloseEvent.SetResult(true); } else { lock (_errorBuilder) { _errorBuilder.AppendLine(e.Data); } } }; var selfReference = new WeakReference(this); _processExitHandler = (o, e) => { if (selfReference.TryGetTarget(out var target)) { try { target.Process.Kill(entireProcessTree: true); } catch { } } }; AppDomain.CurrentDomain.ProcessExit += _processExitHandler; } /// /// Spawns a new process to host a silo, using the executable provided in the configuration's "ExecutablePath" property as the entry point. /// public static async Task Create( string siloName, IConfiguration configuration) { var executablePath = configuration[ExecutablePathConfigKey]; var result = new StandaloneSiloHandle(siloName, configuration, executablePath); await result.StartAsync(); return result; } /// /// Creates a delegate which spawns a silo in a new process, using the provided executable as the entry point for that silo. /// /// The entry point for spawned silos. public static Func> CreateDelegate(string executablePath) { return async (siloName, configuration) => { var result = new StandaloneSiloHandle(siloName, configuration, executablePath); await result.StartAsync(); return result; }; } /// /// Creates a delegate which spawns a silo in a new process, with the provided assembly (or its executable counterpart, if it is a library) being the entry point for that silo. /// /// The entry point for spawned silos. If the provided assembly is a library (dll), then its executable sibling assembly will be invoked instead. public static Func> CreateForAssembly(Assembly assembly) { var executablePath = assembly.Location; var originalFileInfo = new FileInfo(executablePath); if (!originalFileInfo.Exists) { throw new FileNotFoundException($"Cannot find assembly location for assembly {assembly}. Location property returned \"{executablePath}\""); } FileInfo target; if (string.Equals(".dll", originalFileInfo.Extension)) { if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { target = new FileInfo(Path.GetFileNameWithoutExtension(originalFileInfo.FullName) + ".exe"); } else { // On unix-like operating systems, executables generally do not have an extension. target = new FileInfo(Path.GetFileNameWithoutExtension(originalFileInfo.FullName)); } } else { target = originalFileInfo; } if (!target.Exists) { throw new FileNotFoundException($"Target assembly \"{target.FullName}\" does not exist"); } return CreateDelegate(target.FullName); } private async Task StartAsync() { try { if (!Process.Start()) { throw new InvalidOperationException("No process was started"); } } catch (Exception) { isActive = false; throw; } _runTask = Task.Run(async () => { try { Process.BeginOutputReadLine(); Process.BeginErrorReadLine(); var waitForExit = Task.Factory.StartNew( process => ((Process)process).WaitForExit(-1), Process, CancellationToken.None, TaskCreationOptions.LongRunning | TaskCreationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); await Task.WhenAll(waitForExit, _outputCloseEvent.Task, _errorCloseEvent.Task); if (!await waitForExit) { try { Process.Kill(); } catch { } } } catch (Exception exception) { _startedEvent.TrySetException(exception); } }); var task = await Task.WhenAny(_startedEvent.Task, _outputCloseEvent.Task, _errorCloseEvent.Task); if (!ReferenceEquals(task, _startedEvent.Task)) { string output; lock (_outputBuilder) { output = _outputBuilder.ToString(); } string error; lock (_errorBuilder) { error = _errorBuilder.ToString(); } throw new Exception($"Process failed to start correctly.\nOutput:\n{output}\nError:\n{error}"); } } /// public override async Task StopSiloAsync(bool stopGracefully) { using var cancellation = new CancellationTokenSource(); var ct = cancellation.Token; if (!stopGracefully) cancellation.Cancel(); await StopSiloAsync(ct); } /// public override async Task StopSiloAsync(CancellationToken ct) { if (!IsActive) return; try { if (ct.IsCancellationRequested) { this.Process?.Kill(); } else { using var registration = ct.Register(() => { var process = this.Process; if (process is not null) { process.Kill(); } }); await this.Process.StandardInput.WriteLineAsync(StandaloneSiloHost.ShutdownCommand); using var linkedCts = new CancellationTokenSource(); await Task.WhenAny(_runTask, Task.Delay(TimeSpan.FromMinutes(2), linkedCts.Token)); } } finally { this.isActive = false; } } /// protected override void Dispose(bool disposing) { if (!this.IsActive) return; if (disposing) { try { StopSiloAsync(true).GetAwaiter().GetResult(); } catch { } this.Process?.Dispose(); } AppDomain.CurrentDomain.ProcessExit -= _processExitHandler; } /// public override async ValueTask DisposeAsync() { if (!this.IsActive) return; await StopSiloAsync(true).ConfigureAwait(false); this.Process?.Dispose(); AppDomain.CurrentDomain.ProcessExit -= _processExitHandler; } } } ================================================ FILE: src/Orleans.TestingHost/StandaloneSiloHost.cs ================================================ using System; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.TestingHost { /// /// The entry point for standalone silo processes. See . /// public static class StandaloneSiloHost { public const string SiloAddressLog = "#### SILO "; public const string GatewayAddressLog = "#### GATEWAY "; public const string StartedLog = "#### STARTED"; public const string ShutdownCommand = "#### SHUTDOWN"; public static async Task Main(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Expected JSON-serialized configuration to be provided as an argument"); } var monitorProcessId = int.Parse(args[0], NumberStyles.Integer, CultureInfo.InvariantCulture); var serializedConfiguration = args[1]; var configuration = TestClusterHostFactory.DeserializeConfiguration(serializedConfiguration); if (string.Equals(configuration["AttachDebugger"], "true", StringComparison.OrdinalIgnoreCase)) { Debugger.Launch(); } var name = configuration["SiloName"]; using var host = TestClusterHostFactory.CreateSiloHost(name, configuration); try { var cts = new CancellationTokenSource(); Console.CancelKeyPress += (sender, eventArgs) => cts.Cancel(); ListenForShutdownCommand(cts); MonitorParentProcess(monitorProcessId); await host.StartAsync(cts.Token); // This is a special marker line. var localSiloDetails = (ILocalSiloDetails)host.Services.GetService(typeof(ILocalSiloDetails)); Console.WriteLine($"{SiloAddressLog}{localSiloDetails.SiloAddress.ToParsableString()}"); Console.WriteLine($"{GatewayAddressLog}{localSiloDetails.GatewayAddress.ToParsableString()}"); Console.WriteLine(StartedLog); await cts.Token.WhenCancelled(); await host.StopAsync(CancellationToken.None); } finally { if (host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { host.Dispose(); } } } private static void MonitorParentProcess(int monitorProcessId) { if (monitorProcessId > 0) { Console.WriteLine($"Monitoring parent process {monitorProcessId}"); Process.GetProcessById(monitorProcessId).Exited += (o, a) => { Console.Error.WriteLine($"Parent process {monitorProcessId} has exited"); Environment.Exit(0); }; _ = Task.Factory.StartNew(async _ => { try { while (true) { await Task.Delay(5000); if (!Array.Exists(Process.GetProcesses(), p => p.Id == monitorProcessId)) { Console.Error.WriteLine($"Parent process {monitorProcessId} has exited"); Environment.Exit(0); } } } catch { // Ignore all errors. } }, null, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); } } private static void ListenForShutdownCommand(CancellationTokenSource cts) { // Start a thread to monitor for the shutdown command from standard input. _ = Task.Factory.StartNew(_ => { try { while (true) { var text = Console.ReadLine(); if (string.Equals(text, ShutdownCommand, StringComparison.Ordinal)) { Console.WriteLine("Shutdown requested"); cts.Cancel(); return; } } } catch { // Ignore all errors. } }, null, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); } private static Task WhenCancelled(this CancellationToken token) { if (token.IsCancellationRequested) { return Task.CompletedTask; } var waitForCancellation = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); token.Register(obj => { var tcs = (TaskCompletionSource)obj; tcs.TrySetResult(null); }, waitForCancellation); return waitForCancellation.Task; } } } ================================================ FILE: src/Orleans.TestingHost/TestCluster.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.TestingHost.Utils; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Memory; using Orleans.Configuration; using Microsoft.Extensions.Options; using Microsoft.Extensions.Hosting; using Orleans.TestingHost.InMemoryTransport; using Orleans.TestingHost.UnixSocketTransport; using System.Net; using Orleans.Statistics; namespace Orleans.TestingHost { /// /// A host class for local testing with Orleans using in-process silos. /// Runs a Primary and optionally secondary silos in separate app domains, and client in the main app domain. /// Additional silos can also be started in-process on demand if required for particular test cases. /// /// /// Make sure that your test project references your test grains and test grain interfaces /// projects, and has CopyLocal=True set on those references [which should be the default]. /// public class TestCluster : IDisposable, IAsyncDisposable { private readonly List additionalSilos = new List(); private readonly TestClusterOptions options; private readonly StringBuilder log = new StringBuilder(); private readonly InMemoryTransportConnectionHub _transportHub = new(); private bool _disposed; private int startedInstances; /// /// Primary silo handle, if applicable. /// /// This handle is valid only when using Grain-based membership. public SiloHandle Primary { get; private set; } /// /// List of handles to the secondary silos. /// public IReadOnlyList SecondarySilos { get { lock (this.additionalSilos) { return new List(this.additionalSilos); } } } /// /// Collection of all known silos. /// public ReadOnlyCollection Silos { get { var result = new List(); if (this.Primary != null) { result.Add(this.Primary); } lock (this.additionalSilos) { result.AddRange(this.additionalSilos); } return result.AsReadOnly(); } } /// /// Options used to configure the test cluster. /// /// This is the options you configured your test cluster with, or the default one. /// If the cluster is being configured via ClusterConfiguration, then this object may not reflect the true settings. /// public TestClusterOptions Options => this.options; /// /// The internal client interface. /// internal IHost ClientHost { get; private set; } /// /// The internal client interface. /// internal IInternalClusterClient InternalClient => ClientHost?.Services.GetRequiredService(); /// /// The client. /// public IClusterClient Client => this.InternalClient; /// /// GrainFactory to use in the tests /// public IGrainFactory GrainFactory => this.Client; /// /// GrainFactory to use in the tests /// internal IInternalGrainFactory InternalGrainFactory => this.InternalClient; /// /// Client-side to use in the tests. /// public IServiceProvider ServiceProvider => this.Client.ServiceProvider; /// /// Delegate used to create and start an individual silo. /// public Func> CreateSiloAsync { private get; set; } /// /// The port allocator. /// public ITestClusterPortAllocator PortAllocator { get; } /// /// Configures the test cluster plus client in-process. /// public TestCluster( TestClusterOptions options, IReadOnlyList configurationSources, ITestClusterPortAllocator portAllocator) { this.options = options; this.ConfigurationSources = configurationSources.ToArray(); this.PortAllocator = portAllocator; this.CreateSiloAsync = DefaultCreateSiloAsync; } /// /// Returns the associated with the given . /// /// The silo process to the the service provider for. /// If is one of the existing silos will be picked randomly. public IServiceProvider GetSiloServiceProvider(SiloAddress silo = null) { if (silo != null) { var handle = Silos.FirstOrDefault(x => x.SiloAddress.Equals(silo)); return handle != null ? ((InProcessSiloHandle)handle).SiloHost.Services : throw new ArgumentException($"The provided silo address '{silo}' is unknown."); } else { var index = Random.Shared.Next(Silos.Count); return ((InProcessSiloHandle)Silos[index]).SiloHost.Services; } } /// /// Deploys the cluster using the specified configuration and starts the client in-process. /// It will start the number of silos defined in . /// public void Deploy() { this.DeployAsync().GetAwaiter().GetResult(); } /// /// Deploys the cluster using the specified configuration and starts the client in-process. /// public async Task DeployAsync() { if (this.Primary != null || this.additionalSilos.Count > 0) throw new InvalidOperationException("Cluster host already deployed."); AppDomain.CurrentDomain.UnhandledException += ReportUnobservedException; try { string startMsg = "----------------------------- STARTING NEW UNIT TEST SILO HOST: " + GetType().FullName + " -------------------------------------"; WriteLog(startMsg); await InitializeAsync(); if (this.options.InitializeClientOnDeploy) { await WaitForInitialStabilization(); } } catch (TimeoutException te) { FlushLogToConsole(); throw new TimeoutException("Timeout during test initialization", te); } catch (Exception ex) { await StopAllSilosAsync(); Exception baseExc = ex.GetBaseException(); FlushLogToConsole(); if (baseExc is TimeoutException) { throw new TimeoutException("Timeout during test initialization", ex); } // IMPORTANT: // Do NOT re-throw the original exception here, also not as an internal exception inside AggregateException // Due to the way MS tests works, if the original exception is an Orleans exception, // it's assembly might not be loaded yet in this phase of the test. // As a result, we will get "MSTest: Unit Test Adapter threw exception: Type is not resolved for member XXX" // and will loose the original exception. This makes debugging tests super hard! // The root cause has to do with us initializing our tests from Test constructor and not from TestInitialize method. // More details: http://dobrzanski.net/2010/09/20/mstest-unit-test-adapter-threw-exception-type-is-not-resolved-for-member/ //throw new Exception( // string.Format("Exception during test initialization: {0}", // LogFormatter.PrintException(baseExc))); throw; } } private async Task WaitForInitialStabilization() { // Poll each silo to check that it knows the expected number of active silos. // If any silo does not have the expected number of active silos in its cluster membership oracle, try again. // If the cluster membership has not stabilized after a certain period of time, give up and continue anyway. var totalWait = Stopwatch.StartNew(); while (true) { var silos = this.Silos; var expectedCount = silos.Count; var remainingSilos = expectedCount; foreach (var silo in silos) { var hooks = this.InternalClient.GetTestHooks(silo); var statuses = await hooks.GetApproximateSiloStatuses(); var activeCount = statuses.Count(s => s.Value == SiloStatus.Active); if (activeCount != expectedCount) break; remainingSilos--; } if (remainingSilos == 0) { totalWait.Stop(); break; } WriteLog($"{remainingSilos} silos do not have a consistent cluster view, waiting until stabilization."); await Task.Delay(TimeSpan.FromMilliseconds(100)); if (totalWait.Elapsed < TimeSpan.FromSeconds(60)) { WriteLog($"Warning! {remainingSilos} silos do not have a consistent cluster view after {totalWait.ElapsedMilliseconds}ms, continuing without stabilization."); break; } } } /// /// Get the list of current active silos. /// /// List of current silos. public IEnumerable GetActiveSilos() { var additional = new List(); lock (additionalSilos) { additional.AddRange(additionalSilos); } WriteLog("GetActiveSilos: Primary={0} + {1} Additional={2}", Primary, additional.Count, Runtime.Utils.EnumerableToString(additional)); if (Primary?.IsActive == true) yield return Primary; if (additional.Count > 0) foreach (var s in additional) if (s?.IsActive == true) yield return s; } /// /// Find the silo handle for the specified silo address. /// /// Silo address to be found. /// SiloHandle of the appropriate silo, or null if not found. public SiloHandle GetSiloForAddress(SiloAddress siloAddress) { var activeSilos = GetActiveSilos().ToList(); var ret = activeSilos.Find(s => s.SiloAddress.Equals(siloAddress)); return ret; } /// /// Wait for the silo liveness sub-system to detect and act on any recent cluster membership changes. /// /// Whether recent membership changes we done by graceful Stop. public async Task WaitForLivenessToStabilizeAsync(bool didKill = false) { var clusterMembershipOptions = this.ServiceProvider.GetRequiredService>().Value; TimeSpan stabilizationTime = GetLivenessStabilizationTime(clusterMembershipOptions, didKill); WriteLog(Environment.NewLine + Environment.NewLine + "WaitForLivenessToStabilize is about to sleep for {0}", stabilizationTime); await Task.Delay(stabilizationTime); WriteLog("WaitForLivenessToStabilize is done sleeping"); } /// /// Get the timeout value to use to wait for the silo liveness sub-system to detect and act on any recent cluster membership changes. /// /// public static TimeSpan GetLivenessStabilizationTime(ClusterMembershipOptions clusterMembershipOptions, bool didKill = false) { TimeSpan stabilizationTime = TimeSpan.Zero; if (didKill) { // in case of hard kill (kill and not Stop), we should give silos time to detect failures first. stabilizationTime = TestingUtils.Multiply(clusterMembershipOptions.ProbeTimeout, clusterMembershipOptions.NumMissedProbesLimit); } if (clusterMembershipOptions.UseLivenessGossip) { stabilizationTime += TimeSpan.FromSeconds(5); } else { stabilizationTime += TestingUtils.Multiply(clusterMembershipOptions.TableRefreshTimeout, 2); } return stabilizationTime; } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. public SiloHandle StartAdditionalSilo(bool startAdditionalSiloOnNewPort = false) { return StartAdditionalSiloAsync(startAdditionalSiloOnNewPort).GetAwaiter().GetResult(); } /// /// Start an additional silo, so that it joins the existing cluster. /// /// SiloHandle for the newly started silo. public async Task StartAdditionalSiloAsync(bool startAdditionalSiloOnNewPort = false) { return (await this.StartAdditionalSilosAsync(1, startAdditionalSiloOnNewPort)).Single(); } /// /// Start a number of additional silo, so that they join the existing cluster. /// /// Number of silos to start. /// /// List of SiloHandles for the newly started silos. public async Task> StartAdditionalSilosAsync(int silosToStart, bool startAdditionalSiloOnNewPort = false) { var instances = new List(); if (silosToStart > 0) { var siloStartTasks = Enumerable.Range(this.startedInstances, silosToStart) .Select(instanceNumber => Task.Run(() => StartSiloAsync((short)instanceNumber, this.options, startSiloOnNewPort: startAdditionalSiloOnNewPort))).ToArray(); try { await Task.WhenAll(siloStartTasks); } catch (Exception) { lock (additionalSilos) { this.additionalSilos.AddRange(siloStartTasks.Where(t => t.Exception == null).Select(t => t.Result)); } throw; } instances.AddRange(siloStartTasks.Select(t => t.Result)); lock (additionalSilos) { this.additionalSilos.AddRange(instances); } } return instances; } /// /// Stop any additional silos, not including the default Primary silo. /// public async Task StopSecondarySilosAsync() { foreach (var instance in this.additionalSilos.ToList()) { await StopSiloAsync(instance); } } /// /// Stops the default Primary silo. /// public async Task StopPrimarySiloAsync() { if (Primary == null) throw new InvalidOperationException("There is no primary silo"); await StopClusterClientAsync(); await StopSiloAsync(Primary); } /// /// Stop cluster client as an asynchronous operation. /// /// A representing the asynchronous operation. public async Task StopClusterClientAsync() { var client = this.ClientHost; try { if (client is not null) { await client.StopAsync().ConfigureAwait(false); } } catch (Exception exc) { WriteLog("Exception stopping client: {0}", exc); } finally { await DisposeAsync(client).ConfigureAwait(false); ClientHost = null; } } /// /// Stop all current silos. /// public void StopAllSilos() { StopAllSilosAsync().GetAwaiter().GetResult(); } /// /// Stop all current silos. /// public async Task StopAllSilosAsync() { await StopClusterClientAsync(); await StopSecondarySilosAsync(); if (Primary != null) { await StopPrimarySiloAsync(); } AppDomain.CurrentDomain.UnhandledException -= ReportUnobservedException; } /// /// Do a semi-graceful Stop of the specified silo. /// /// Silo to be stopped. public async Task StopSiloAsync(SiloHandle instance) { if (instance != null) { await StopSiloAsync(instance, true); if (Primary == instance) { Primary = null; } else { lock (additionalSilos) { additionalSilos.Remove(instance); } } } } /// /// Do an immediate Kill of the specified silo. /// /// Silo to be killed. public async Task KillSiloAsync(SiloHandle instance) { if (instance != null) { // do NOT stop, just kill directly, to simulate crash. await StopSiloAsync(instance, false); if (Primary == instance) { Primary = null; } else { lock (additionalSilos) { additionalSilos.Remove(instance); } } } } /// /// Performs a hard kill on client. Client will not cleanup resources. /// public async Task KillClientAsync() { var client = ClientHost; if (client != null) { using var cancelled = new CancellationTokenSource(); cancelled.Cancel(); try { await client.StopAsync(cancelled.Token).ConfigureAwait(false); } finally { await DisposeAsync(client); ClientHost = null; } } } /// /// Do a Stop or Kill of the specified silo, followed by a restart. /// /// Silo to be restarted. public async Task RestartSiloAsync(SiloHandle instance) { if (instance != null) { var instanceNumber = instance.InstanceNumber; var siloName = instance.Name; await StopSiloAsync(instance); var newInstance = await StartSiloAsync(instanceNumber, this.options); if (siloName == Silo.PrimarySiloName) { Primary = newInstance; } else { lock (additionalSilos) { additionalSilos.Add(newInstance); } } return newInstance; } return null; } /// /// Restart a previously stopped. /// /// Silo to be restarted. public async Task RestartStoppedSecondarySiloAsync(string siloName) { if (siloName == null) throw new ArgumentNullException(nameof(siloName)); var siloHandle = this.Silos.Single(s => s.Name.Equals(siloName, StringComparison.Ordinal)); var newInstance = await this.StartSiloAsync(this.Silos.IndexOf(siloHandle), this.options); lock (additionalSilos) { additionalSilos.Add(newInstance); } return newInstance; } /// /// Initialize the grain client. This should be already done by or /// public async Task InitializeClientAsync() { WriteLog("Initializing Cluster Client"); if (ClientHost is not null) { await StopClusterClientAsync(); } var configurationBuilder = new ConfigurationBuilder(); foreach (var source in ConfigurationSources) { configurationBuilder.Add(source); } if (options.UseTestClusterMembership) { var gateways = new List(); if (options.GatewayPerSilo) { gateways.AddRange(Enumerable.Range(options.BaseGatewayPort, options.InitialSilosCount).Select(port => new IPEndPoint(IPAddress.Loopback, port))); } else { gateways.Add(new IPEndPoint(IPAddress.Loopback, options.BaseGatewayPort)); } var clustering = new Dictionary(); var i = 0; foreach (var v in gateways) { clustering[$"Orleans:Clustering:Gateways:{i++}"] = v.ToString(); } clustering["Orleans:Clustering:ProviderType"] = "Development"; configurationBuilder.AddInMemoryCollection(clustering); } var configuration = configurationBuilder.Build(); this.ClientHost = TestClusterHostFactory.CreateClusterClient( "MainClient", configuration, hostBuilder => { hostBuilder.UseOrleansClient((context, clientBuilder) => { Enum.TryParse(context.Configuration[nameof(TestClusterOptions.ConnectionTransport)], out var transport); switch (transport) { case ConnectionTransportType.TcpSocket: break; case ConnectionTransportType.InMemory: clientBuilder.UseInMemoryConnectionTransport(_transportHub); break; case ConnectionTransportType.UnixSocket: clientBuilder.UseUnixSocketConnection(); break; default: throw new ArgumentException($"Unsupported {nameof(ConnectionTransportType)}: {transport}"); } }); }); await this.ClientHost.StartAsync(); } /// /// Gets the configuration sources. /// /// The configuration sources. public IReadOnlyList ConfigurationSources { get; } private async Task InitializeAsync() { short silosToStart = this.options.InitialSilosCount; if (this.options.UseTestClusterMembership) { this.Primary = await StartSiloAsync(this.startedInstances, this.options); silosToStart--; } if (silosToStart > 0) { await this.StartAdditionalSilosAsync(silosToStart); } WriteLog("Done initializing cluster"); if (this.options.InitializeClientOnDeploy) { await InitializeClientAsync(); } } /// /// Default value for , which creates a new silo handle. /// /// Name of the silo. /// The configuration. /// The silo handle. public async Task DefaultCreateSiloAsync(string siloName, IConfiguration configuration) { return await InProcessSiloHandle.CreateAsync(siloName, configuration, hostBuilder => { hostBuilder.UseOrleans((context, siloBuilder) => { Enum.TryParse(context.Configuration[nameof(TestClusterOptions.ConnectionTransport)], out var transport); switch (transport) { case ConnectionTransportType.TcpSocket: break; case ConnectionTransportType.InMemory: siloBuilder.UseInMemoryConnectionTransport(_transportHub); break; case ConnectionTransportType.UnixSocket: siloBuilder.UseUnixSocketConnection(); break; default: throw new ArgumentException($"Unsupported {nameof(ConnectionTransportType)}: {transport}"); } if (options.UseRealEnvironmentStatistics) { var descriptor = siloBuilder.Services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IEnvironmentStatisticsProvider)); if (descriptor != null) { siloBuilder.Services.Remove(descriptor); siloBuilder.Services.AddSingleton(); } } }); }); } /// /// Start a new silo in the target cluster /// /// The TestCluster in which the silo should be deployed /// The instance number to deploy /// The options to use. /// Configuration overrides. /// Whether we start this silo on a new port, instead of the default one /// A handle to the silo deployed public static async Task StartSiloAsync(TestCluster cluster, int instanceNumber, TestClusterOptions clusterOptions, IReadOnlyList configurationOverrides = null, bool startSiloOnNewPort = false) { if (cluster == null) throw new ArgumentNullException(nameof(cluster)); return await cluster.StartSiloAsync(instanceNumber, clusterOptions, configurationOverrides, startSiloOnNewPort); } /// /// Starts a new silo. /// /// The instance number to deploy /// The options to use. /// Configuration overrides. /// Whether we start this silo on a new port, instead of the default one /// A handle to the deployed silo. public async Task StartSiloAsync(int instanceNumber, TestClusterOptions clusterOptions, IReadOnlyList configurationOverrides = null, bool startSiloOnNewPort = false) { var configurationSources = this.ConfigurationSources.ToList(); // Add overrides. if (configurationOverrides != null) configurationSources.AddRange(configurationOverrides); var siloSpecificOptions = TestSiloSpecificOptions.Create(this, clusterOptions, instanceNumber, startSiloOnNewPort); configurationSources.Add(new MemoryConfigurationSource { InitialData = siloSpecificOptions.ToDictionary() }); var configurationBuilder = new ConfigurationBuilder(); foreach (var source in configurationSources) { configurationBuilder.Add(source); } var configuration = configurationBuilder.Build(); var handle = await this.CreateSiloAsync(siloSpecificOptions.SiloName, configuration); handle.InstanceNumber = (short)instanceNumber; Interlocked.Increment(ref this.startedInstances); return handle; } private async Task StopSiloAsync(SiloHandle instance, bool stopGracefully) { try { await instance.StopSiloAsync(stopGracefully).ConfigureAwait(false); } finally { await DisposeAsync(instance).ConfigureAwait(false); Interlocked.Decrement(ref this.startedInstances); } } /// /// Gets the log. /// /// The log contents. public string GetLog() { return this.log.ToString(); } private void ReportUnobservedException(object sender, UnhandledExceptionEventArgs eventArgs) { Exception exception = (Exception)eventArgs.ExceptionObject; this.WriteLog("Unobserved exception: {0}", exception); } private void WriteLog(string format, params object[] args) { log.AppendFormat(format + Environment.NewLine, args); } private void FlushLogToConsole() { Console.WriteLine(GetLog()); } /// public async ValueTask DisposeAsync() { if (_disposed) { return; } await Task.Run(async () => { await DisposeAsync(ClientHost).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); foreach (var handle in SecondarySilos) { await DisposeAsync(handle).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } if (Primary is not null) { await DisposeAsync(Primary).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } ClientHost = null; PortAllocator?.Dispose(); }); _disposed = true; } /// public void Dispose() { if (_disposed) { return; } DisposeAsync().AsTask().Wait(); } private static async Task DisposeAsync(IDisposable value) { if (value is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync().AsTask().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } else if (value is IDisposable disposable) { disposable.Dispose(); } } } } ================================================ FILE: src/Orleans.TestingHost/TestClusterBuilder.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Runtime; namespace Orleans.TestingHost { /// Configuration builder for starting a . public class TestClusterBuilder { private readonly List> configureHostConfigActions = new List>(); private readonly List configureBuilderActions = new List(); private Func> _createSiloAsync; /// /// Initializes a new instance of using the default options. /// public TestClusterBuilder() : this(2) { } /// /// Initializes a new instance of overriding the initial silos count. /// /// The number of initial silos to deploy. public TestClusterBuilder(short initialSilosCount) { this.Options = new TestClusterOptions { InitialSilosCount = initialSilosCount, ClusterId = CreateClusterId(), ServiceId = Guid.NewGuid().ToString("N"), UseTestClusterMembership = true, InitializeClientOnDeploy = true, ConfigureFileLogging = true, AssumeHomogenousSilosForTesting = true }; AddSiloBuilderConfigurator(); this.AddSiloBuilderConfigurator(); this.ConfigureBuilder(ConfigureDefaultPorts); } /// /// Gets or sets the port allocator used to allocate consecutive silo ports. /// public ITestClusterPortAllocator PortAllocator { get; set; } = new TestClusterPortAllocator(); /// /// Configuration values which will be provided to the silos and clients created by this builder. /// public Dictionary Properties { get; } = new Dictionary(); /// /// Gets the options. /// /// The options. public TestClusterOptions Options { get; } /// /// Gets or sets the delegate used to create and start an individual silo. /// public Func> CreateSiloAsync { private get => _createSiloAsync; set { _createSiloAsync = value; // The custom builder does not have access to the in-memory transport. Options.ConnectionTransport = ConnectionTransportType.TcpSocket; } } /// /// Adds a configuration delegate to the builder /// /// The configuration delegate. public TestClusterBuilder ConfigureBuilder(Action configureDelegate) { this.configureBuilderActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } /// /// Set up the configuration for the builder itself. This will be used as a base to initialize each silo host /// for use later in the build process. This can be called multiple times and the results will be additive. /// /// The delegate for configuring the that will be used /// to construct the for the host. /// The same instance of the host builder for chaining. public TestClusterBuilder ConfigureHostConfiguration(Action configureDelegate) { this.configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } /// /// Adds an implementation of or to configure silos created by the test cluster. /// /// The configurator type. /// The builder. public TestClusterBuilder AddSiloBuilderConfigurator() where T : new() { if (!typeof(ISiloConfigurator).IsAssignableFrom(typeof(T)) && !typeof(IHostConfigurator).IsAssignableFrom(typeof(T))) { throw new ArgumentException($"The type {typeof(T)} is not assignable to either {nameof(ISiloConfigurator)} or {nameof(IHostConfigurator)}"); } this.Options.SiloBuilderConfiguratorTypes.Add(typeof(T).AssemblyQualifiedName); return this; } /// /// Adds an implementation of or to configure the client created for the test cluster /// /// The client builder type /// The builder. public TestClusterBuilder AddClientBuilderConfigurator() where T : new() { if (!typeof(IClientBuilderConfigurator).IsAssignableFrom(typeof(T)) && !typeof(IHostConfigurator).IsAssignableFrom(typeof(T))) { throw new ArgumentException($"The type {typeof(T)} is not assignable to either {nameof(IClientBuilderConfigurator)} or {nameof(IHostConfigurator)}"); } this.Options.ClientBuilderConfiguratorTypes.Add(typeof(T).AssemblyQualifiedName); return this; } /// /// Builds this instance. /// /// TestCluster. public TestCluster Build() { var portAllocator = this.PortAllocator; var configBuilder = new ConfigurationBuilder(); foreach (var action in configureBuilderActions) { action(); } configBuilder.AddInMemoryCollection(this.Properties); configBuilder.AddInMemoryCollection(this.Options.ToDictionary()); foreach (var buildAction in this.configureHostConfigActions) { buildAction(configBuilder); } var configuration = configBuilder.Build(); var finalOptions = new TestClusterOptions(); configuration.Bind(finalOptions); // Since the ClusterId & ServiceId properties are nested under the 'Orleans' section in the config, // we bind the 'Orleans' section here to bind them to the options. configuration.GetSection("Orleans").Bind(finalOptions); var configSources = new ReadOnlyCollection(configBuilder.Sources); var testCluster = new TestCluster(finalOptions, configSources, portAllocator); if (CreateSiloAsync != null) testCluster.CreateSiloAsync = CreateSiloAsync; return testCluster; } /// /// Creates a cluster identifier. /// /// A new cluster identifier. public static string CreateClusterId() { string prefix = "testcluster-"; int randomSuffix = Random.Shared.Next(1000); DateTime now = DateTime.UtcNow; string DateTimeFormat = @"yyyy-MM-dd-HH-mm-ss"; return $"{prefix}{now.ToString(DateTimeFormat, CultureInfo.InvariantCulture)}-{randomSuffix}"; } private void ConfigureDefaultPorts() { // Set base ports if none are currently set. (int baseSiloPort, int baseGatewayPort) = this.PortAllocator.AllocateConsecutivePortPairs(this.Options.InitialSilosCount + 3); if (this.Options.BaseSiloPort == 0) this.Options.BaseSiloPort = baseSiloPort; if (this.Options.BaseGatewayPort == 0) this.Options.BaseGatewayPort = baseGatewayPort; } internal class ConfigureStaticClusterDeploymentOptions : IHostConfigurator { public void Configure(IHostBuilder hostBuilder) { hostBuilder.ConfigureServices((context, services) => { var initialSilos = int.Parse(context.Configuration[nameof(TestClusterOptions.InitialSilosCount)]); var siloNames = Enumerable.Range(0, initialSilos).Select(GetSiloName).ToList(); services.Configure(options => options.SiloNames = siloNames); }); } private static string GetSiloName(int instanceNumber) { return instanceNumber == 0 ? Silo.PrimarySiloName : $"Secondary_{instanceNumber}"; } } } } ================================================ FILE: src/Orleans.TestingHost/TestClusterExtensions.cs ================================================ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Orleans.Hosting; namespace Orleans.TestingHost { /// /// Extension methods for test clusters. /// public static class TestClusterExtensions { /// /// Gets the configuration from the specified host builder. /// /// /// The builder. /// public static IConfiguration GetConfiguration(this IHostBuilder builder) { if (builder.Properties.TryGetValue("Configuration", out var configObject) && configObject is IConfiguration config) { return config; } throw new InvalidOperationException( $"Expected configuration object in \"Configuration\" property of type {nameof(IConfiguration)} on {nameof(ISiloBuilder)}."); } /// /// Gets a configuration value. /// /// The host builder. /// The key. /// The configuration value. public static string GetConfigurationValue(this IHostBuilder hostBuilder, string key) { return hostBuilder.GetConfiguration()[key]; } /// /// Gets the test cluster options. /// /// The host builder. /// The test cluster options. public static TestClusterOptions GetTestClusterOptions(this IHostBuilder hostBuilder) { return hostBuilder.GetConfiguration().GetTestClusterOptions(); } /// /// Gets the test cluster options. /// /// The configuration. /// The test cluster options. public static TestClusterOptions GetTestClusterOptions(this IConfiguration config) { var result = new TestClusterOptions(); config.Bind(result); return result; } } } ================================================ FILE: src/Orleans.TestingHost/TestClusterHostFactory.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.Hosting; using Orleans.Messaging; using Orleans.Runtime; using Orleans.Runtime.MembershipService; using Orleans.Runtime.TestHooks; using Orleans.Statistics; using Orleans.TestingHost.Logging; using Orleans.TestingHost.Utils; namespace Orleans.TestingHost { /// /// Utility for creating silos given a name and collection of configuration sources. /// public class TestClusterHostFactory { /// /// Creates an returns a new silo. /// /// The silo name if it is not already specified in the configuration. /// The configuration. /// An optional delegate which can be used to configure the host builder just prior to a host being built. /// A new silo. public static IHost CreateSiloHost(string hostName, IConfiguration configuration, Action postConfigureHostBuilder = null) { string siloName = configuration["Name"] ?? hostName; var hostBuilder = new HostBuilder(); hostBuilder.UseEnvironment(Environments.Development); hostBuilder.Properties["Configuration"] = configuration; hostBuilder.ConfigureHostConfiguration(cb => cb.AddConfiguration(configuration)); hostBuilder.UseOrleans((ctx, siloBuilder) => { siloBuilder.Services .Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); }); ConfigureAppServices(configuration, hostBuilder); hostBuilder.ConfigureServices((context, services) => { services.AddSingleton(); services.AddFromExisting(); services.AddSingleton(); TryConfigureFileLogging(configuration, services, siloName); if (Debugger.IsAttached) { // Test is running inside debugger - Make timeout ~= infinite services.Configure(op => op.ResponseTimeout = TimeSpan.FromMilliseconds(1000000)); } }); postConfigureHostBuilder?.Invoke(hostBuilder); var host = hostBuilder.Build(); InitializeTestHooksSystemTarget(host); return host; } /// /// Creates the cluster client. /// /// Name of the host. /// The configuration. /// An optional delegate which can be used to configure the host builder just prior to a host being built. /// The cluster client host. public static IHost CreateClusterClient(string hostName, IConfiguration configuration, Action postConfigureHostBuilder = null) { var hostBuilder = new HostBuilder(); hostBuilder.UseEnvironment(Environments.Development); hostBuilder.Properties["Configuration"] = configuration; hostBuilder.ConfigureHostConfiguration(cb => cb.AddConfiguration(configuration)); hostBuilder.UseOrleansClient(); ConfigureClientAppServices(configuration, hostBuilder); hostBuilder .ConfigureServices(services => { //TryConfigureClientMembership(configuration, services); TryConfigureFileLogging(configuration, services, hostName); }); postConfigureHostBuilder?.Invoke(hostBuilder); var host = hostBuilder.Build(); return host; } /// /// Serializes configuration to a string. /// /// The configuration. /// The serialized configuration. public static string SerializeConfiguration(IConfiguration configuration) { var settings = new JsonSerializerSettings(); KeyValuePair[] enumerated = configuration.AsEnumerable().ToArray(); return JsonConvert.SerializeObject(enumerated, settings); } /// /// Deserializes a configuration string. /// /// The serialized sources. /// The deserialized configuration. public static IConfiguration DeserializeConfiguration(string serializedSources) { var settings = new JsonSerializerSettings(); var builder = new ConfigurationBuilder(); var enumerated = JsonConvert.DeserializeObject[]>(serializedSources, settings); builder.AddInMemoryCollection(enumerated); return builder.Build(); } private static void ConfigureAppServices(IConfiguration configuration, IHostBuilder hostBuilder) { var builderConfiguratorTypes = configuration.GetSection(nameof(TestClusterOptions.SiloBuilderConfiguratorTypes))?.Get(); if (builderConfiguratorTypes == null) return; foreach (var builderConfiguratorType in builderConfiguratorTypes) { if (!string.IsNullOrWhiteSpace(builderConfiguratorType)) { var configurator = Activator.CreateInstance(Type.GetType(builderConfiguratorType, true)); (configurator as IHostConfigurator)?.Configure(hostBuilder); hostBuilder.UseOrleans((ctx, siloBuilder) => (configurator as ISiloConfigurator)?.Configure(siloBuilder)); } } } private static void ConfigureClientAppServices(IConfiguration configuration, IHostBuilder hostBuilder) { var builderConfiguratorTypes = configuration.GetSection(nameof(TestClusterOptions.ClientBuilderConfiguratorTypes))?.Get(); if (builderConfiguratorTypes == null) return; foreach (var builderConfiguratorType in builderConfiguratorTypes) { if (!string.IsNullOrWhiteSpace(builderConfiguratorType)) { var builderConfigurator = Activator.CreateInstance(Type.GetType(builderConfiguratorType, true)); (builderConfigurator as IHostConfigurator)?.Configure(hostBuilder); if (builderConfigurator is IClientBuilderConfigurator clientBuilderConfigurator) { hostBuilder.UseOrleansClient(clientBuilder => clientBuilderConfigurator.Configure(configuration, clientBuilder)); } } } } private static void TryConfigureFileLogging(IConfiguration configuration, IServiceCollection services, string name) { bool.TryParse(configuration[nameof(TestClusterOptions.ConfigureFileLogging)], out bool configureFileLogging); if (configureFileLogging) { var fileName = TestingUtils.CreateTraceFileName(name, configuration["Orleans:ClusterId"]); services.AddLogging(loggingBuilder => loggingBuilder.AddFile(fileName)); } } private static void InitializeTestHooksSystemTarget(IHost host) { _ = host.Services.GetRequiredService(); } } } ================================================ FILE: src/Orleans.TestingHost/TestClusterOptions.cs ================================================ using System.Collections.Generic; using System.Net; using Orleans.Configuration; using Orleans.Runtime; namespace Orleans.TestingHost { /// /// Configuration options for test clusters. /// public class TestClusterOptions { /// /// Gets or sets the cluster identifier. /// /// /// The cluster identifier. public string ClusterId { get; set; } /// /// Gets or sets the service identifier. /// /// /// The service identifier. public string ServiceId { get; set; } /// /// Gets or sets the base silo port, which is the port for the first silo. Other silos will use subsequent ports. /// /// The base silo port. public int BaseSiloPort{ get; set; } /// /// Gets or sets the base gateway port, which is the gateway port for the first silo. Other silos will use subsequent ports. /// /// The base gateway port. public int BaseGatewayPort { get; set; } /// /// Gets or sets a value indicating whether to use test cluster membership. /// /// if test cluster membership should be used; otherwise, . public bool UseTestClusterMembership { get; set; } /// /// Gets or sets a value indicating whether to use the real environment statistics. /// public bool UseRealEnvironmentStatistics { get; set; } /// /// Gets or sets a value indicating whether to initialize the client immediately on deployment. /// /// if the client should be initialized immediately on deployment; otherwise, . public bool InitializeClientOnDeploy { get; set; } /// /// Gets or sets the initial silos count. /// /// The initial silos count. public short InitialSilosCount { get; set; } /// /// Gets or sets the application base directory. /// /// The application base directory. public string ApplicationBaseDirectory { get; set; } /// /// Gets or sets a value indicating whether to configure file logging. /// /// if file logging should be configured; otherwise, . public bool ConfigureFileLogging { get; set; } = true; /// /// Gets or sets a value indicating whether to assume homogeneous silos for testing purposes. /// /// if the cluster should assume homogeneous silos; otherwise, . public bool AssumeHomogenousSilosForTesting { get; set; } /// /// Gets or sets a value indicating whether each silo should host a gateway. /// /// if each silo should host a gateway; otherwise, . public bool GatewayPerSilo { get; set; } = true; /// /// Gets the silo builder configurator types. /// /// The silo builder configurator types. public List SiloBuilderConfiguratorTypes { get; } = new List(); /// /// Gets the client builder configurator types. /// /// The client builder configurator types. public List ClientBuilderConfiguratorTypes { get; } = new List(); /// /// Gets or sets a value indicating what transport to use for connecting silos and clients. /// /// /// Defaults to InMemory. /// public ConnectionTransportType ConnectionTransport { get; set; } = ConnectionTransportType.InMemory; /// /// Converts these options into a dictionary. /// /// The options dictionary. public Dictionary ToDictionary() { var result = new Dictionary { [$"Orleans:{nameof(ClusterId)}"] = this.ClusterId, [$"Orleans:{nameof(ServiceId)}"] = this.ServiceId, [nameof(BaseSiloPort)] = this.BaseSiloPort.ToString(), [nameof(BaseGatewayPort)] = this.BaseGatewayPort.ToString(), [nameof(UseTestClusterMembership)] = this.UseTestClusterMembership.ToString(), [nameof(UseRealEnvironmentStatistics)] = this.UseRealEnvironmentStatistics.ToString(), [nameof(InitializeClientOnDeploy)] = this.InitializeClientOnDeploy.ToString(), [nameof(InitialSilosCount)] = this.InitialSilosCount.ToString(), [nameof(ApplicationBaseDirectory)] = this.ApplicationBaseDirectory, [nameof(ConfigureFileLogging)] = this.ConfigureFileLogging.ToString(), [nameof(AssumeHomogenousSilosForTesting)] = this.AssumeHomogenousSilosForTesting.ToString(), [nameof(GatewayPerSilo)] = this.GatewayPerSilo.ToString(), [nameof(ConnectionTransport)] = this.ConnectionTransport.ToString(), }; if (UseTestClusterMembership) { result["Orleans:Clustering:ProviderType"] = "Development"; } result["UseRealEnvironmentStatistics"] = UseRealEnvironmentStatistics ? "True" : "False"; if (this.SiloBuilderConfiguratorTypes != null) { for (int i = 0; i < this.SiloBuilderConfiguratorTypes.Count; i++) { result[$"{nameof(SiloBuilderConfiguratorTypes)}:{i}"] = this.SiloBuilderConfiguratorTypes[i]; } } if (this.ClientBuilderConfiguratorTypes != null) { for (int i = 0; i < this.ClientBuilderConfiguratorTypes.Count; i++) { result[$"{nameof(ClientBuilderConfiguratorTypes)}:{i}"] = this.ClientBuilderConfiguratorTypes[i]; } } return result; } } /// /// Configuration overrides for individual silos. /// public class TestSiloSpecificOptions { /// /// Gets or sets the silo port. /// /// The silo port. public int SiloPort { get; set; } /// /// Gets or sets the gateway port. /// /// The gateway port. public int GatewayPort { get; set; } /// /// Gets or sets the name of the silo. /// /// The name of the silo. public string SiloName { get; set; } /// /// Gets or sets the primary silo port. /// /// The primary silo port. public IPEndPoint PrimarySiloEndPoint { get; set; } /// /// Creates an instance of the class. /// /// The test cluster. /// The test cluster options. /// The instance number. /// if set to , assign a new port for the silo. /// The options. public static TestSiloSpecificOptions Create(TestCluster testCluster, TestClusterOptions testClusterOptions, int instanceNumber, bool assignNewPort = false) { var result = new TestSiloSpecificOptions { SiloName = testClusterOptions.UseTestClusterMembership && instanceNumber == 0 ? Silo.PrimarySiloName : $"Secondary_{instanceNumber}", PrimarySiloEndPoint = testClusterOptions.UseTestClusterMembership ? new IPEndPoint(IPAddress.Loopback, testClusterOptions.BaseSiloPort) : null, }; if (assignNewPort) { var (siloPort, gatewayPort) = testCluster.PortAllocator.AllocateConsecutivePortPairs(1); result.SiloPort = siloPort; result.GatewayPort = (instanceNumber == 0 || testClusterOptions.GatewayPerSilo) ? gatewayPort : 0; } else { result.SiloPort = testClusterOptions.BaseSiloPort + instanceNumber; result.GatewayPort = (instanceNumber == 0 || testClusterOptions.GatewayPerSilo) ? testClusterOptions.BaseGatewayPort + instanceNumber : 0; } return result; } /// /// Converts these options into a dictionary. /// /// The options dictionary. public Dictionary ToDictionary() { var result = new Dictionary { [$"Orleans:Endpoints:AdvertisedIPAddress"] = IPAddress.Loopback.ToString(), [$"Orleans:Endpoints:{nameof(SiloPort)}"] = this.SiloPort.ToString(), [$"Orleans:EndPoints:{nameof(GatewayPort)}"] = this.GatewayPort.ToString(), ["Orleans:Name"] = this.SiloName, }; if (PrimarySiloEndPoint != null) { result[$"Orleans:Clustering:{nameof(PrimarySiloEndPoint)}"] = this.PrimarySiloEndPoint.ToString(); } return result; } } /// /// Describe a transport method /// public enum ConnectionTransportType { /// /// Uses real TCP socket. /// TcpSocket = 0, /// /// Uses in memory socket. /// InMemory = 1, /// /// Uses in Unix socket. /// UnixSocket = 2, } } ================================================ FILE: src/Orleans.TestingHost/TestClusterPortAllocator.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; namespace Orleans.TestingHost; /// /// Default implementation, which tries to allocate unused ports. /// public class TestClusterPortAllocator : ITestClusterPortAllocator { private bool _disposed; #if NET9_0_OR_GREATER private readonly Lock _lockObj = new(); #else private readonly object _lockObj = new(); #endif private readonly Dictionary _allocatedPorts = []; /// public (int, int) AllocateConsecutivePortPairs(int numPorts = 5) { // Evaluate current system tcp connections var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners(); // each returned port in the pair will have to have at least this amount of available ports following it return (GetAvailableConsecutiveServerPorts(tcpConnInfoArray, 22300, 30000, numPorts), GetAvailableConsecutiveServerPorts(tcpConnInfoArray, 40000, 50000, numPorts)); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } lock (_lockObj) { if (_disposed) { return; } foreach (var pair in _allocatedPorts) { MutexManager.Instance.SignalRelease(pair.Value); } _allocatedPorts.Clear(); _disposed = true; } } /// /// Finalizes an instance of the class. /// ~TestClusterPortAllocator() { Dispose(false); } private int GetAvailableConsecutiveServerPorts(IPEndPoint[] tcpConnInfoArray, int portStartRange, int portEndRange, int consecutivePortsToCheck) { const int MaxAttempts = 100; var allocations = new List<(int Port, string Mutex)>(); for (var attempts = 0; attempts < MaxAttempts; attempts++) { var basePort = Random.Shared.Next(portStartRange, portEndRange); // get ports in buckets, so we don't interfere with parallel runs of this same function basePort = basePort - basePort % consecutivePortsToCheck; var endPort = basePort + consecutivePortsToCheck; // make sure none of the ports in the sub range are in use if (tcpConnInfoArray.All(endpoint => endpoint.Port < basePort || endpoint.Port >= endPort)) { var portsAvailable = true; for (var i = 0; i < consecutivePortsToCheck; i++) { var port = basePort + i; try { using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); } catch (SocketException) { portsAvailable = false; break; } } if (!portsAvailable) { continue; } for (var i = 0; i < consecutivePortsToCheck; i++) { var port = basePort + i; var name = $"Global.TestCluster.{port.ToString(CultureInfo.InvariantCulture)}"; if (MutexManager.Instance.Acquire(name)) { allocations.Add((port, name)); } else { foreach (var allocation in allocations) { MutexManager.Instance.SignalRelease(allocation.Mutex); } allocations.Clear(); break; } } if (allocations.Count == 0) { // Try a different range. continue; } lock (_lockObj) { foreach (var allocation in allocations) { _allocatedPorts[allocation.Port] = allocation.Mutex; } } return basePort; } } throw new InvalidOperationException("Cannot find enough free ports to spin up a cluster"); } private class MutexManager { private readonly Dictionary _mutexes = []; private readonly BlockingCollection _workItems = []; private readonly Thread _thread; public static MutexManager Instance { get; } = new MutexManager(); private MutexManager() { _thread = new Thread(Run) { Name = "MutexManager.Worker", IsBackground = true, }; _thread.Start(); AppDomain.CurrentDomain.DomainUnload += this.OnAppDomainUnload; } private void OnAppDomainUnload(object sender, EventArgs e) => Shutdown(); private void Shutdown() { _workItems.CompleteAdding(); _thread.Join(); } public bool Acquire(string name) { var result = new [] { 0 }; var signal = new ManualResetEventSlim(initialState: false); _workItems.Add(() => { try { if (!_mutexes.TryGetValue(name, out var mutex)) { mutex = new Mutex(false, name); if (mutex.WaitOne(500)) { // Acquired _mutexes[name] = mutex; Interlocked.Increment(ref result[0]); return; } // Failed to acquire: the mutex is already held by another process. try { mutex.ReleaseMutex(); } finally { mutex.Close(); } } // Failed to acquire: the mutex is already held by this process. } finally { signal.Set(); } }); if (!signal.Wait(TimeSpan.FromSeconds(10))) { throw new TimeoutException("Timed out while waiting for MutexManager to acquire mutex."); } return result[0] == 1; } public void SignalRelease(string name) { if (_workItems.IsAddingCompleted) return; try { _workItems.Add(() => { if (_mutexes.Remove(name, out var value)) { value.ReleaseMutex(); value.Close(); } }); } catch { } } private void Run() { try { foreach (var action in _workItems.GetConsumingEnumerable()) { try { action(); } catch { } } } catch { } finally { foreach (var mutex in _mutexes.Values) { try { mutex.ReleaseMutex(); } catch { } finally { mutex.Close(); } } _mutexes.Clear(); } } } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/FaultInjectionStorageProvider.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Storage; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Orleans.TestingHost { /// /// Options for fault injection grain storage /// public class FaultInjectionGrainStorageOptions { /// /// The default latency. /// public static TimeSpan DEFAULT_LATENCY = TimeSpan.FromMilliseconds(10); /// /// Gets or sets the latency applied on storage operations. /// public TimeSpan Latency { get; set; } = DEFAULT_LATENCY; } /// /// Fault injection decorator for storage providers. This allows users to inject storage exceptions to test error handling scenarios. /// public class FaultInjectionGrainStorage : IGrainStorage, ILifecycleParticipant { private readonly IGrainStorage realStorageProvider; private readonly IGrainFactory grainFactory; private readonly ILogger logger; private readonly FaultInjectionGrainStorageOptions options; /// /// Default constructor which creates the decorated storage provider. /// /// The real storage provider. /// The storage provider name. /// The logger factory. /// The grain factory. /// The fault injection options. public FaultInjectionGrainStorage(IGrainStorage realStorageProvider, string name, ILoggerFactory loggerFactory, IGrainFactory grainFactory, FaultInjectionGrainStorageOptions faultInjectionOptions) { this.realStorageProvider = realStorageProvider; this.logger = loggerFactory.CreateLogger($"{this.GetType().FullName}.{name}"); this.grainFactory = grainFactory; this.options = faultInjectionOptions; } private Task InsertDelay() { return Task.Delay(this.options.Latency); } /// Faults if exception is provided, otherwise calls through to decorated storage provider. /// Completion promise for the Read operation on the specified grain. public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { IStorageFaultGrain faultGrain = grainFactory.GetGrain(grainType); try { await InsertDelay(); await faultGrain.OnRead(grainId); } catch (Exception) { logger.LogInformation( "Fault injected for ReadState for grain {GrainId} of type {GrainType}", grainId, grainType); throw; } logger.LogInformation( "ReadState for grain {GrainId} of type {GrainType}", grainId, grainType); await realStorageProvider.ReadStateAsync(grainType, grainId, grainState); } /// Faults if exception is provided, otherwise calls through to decorated storage provider. /// Completion promise for the Write operation on the specified grain. public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { IStorageFaultGrain faultGrain = grainFactory.GetGrain(grainType); try { await InsertDelay(); await faultGrain.OnWrite(grainId); } catch (Exception) { logger.LogInformation( "Fault injected for WriteState for grain {GrainId} of type {GrainType}", grainId, grainType); throw; } logger.LogInformation( "WriteState for grain {GrainId} of type {GrainType}", grainId, grainType); await realStorageProvider.WriteStateAsync(grainType, grainId, grainState); } /// Faults if exception is provided, otherwise calls through to decorated storage provider. /// Completion promise for the Delete operation on the specified grain. public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { IStorageFaultGrain faultGrain = grainFactory.GetGrain(grainType); try { await InsertDelay(); await faultGrain.OnClear(grainId); } catch (Exception) { logger.LogInformation( "Fault injected for ClearState for grain {GrainId} of type {GrainType}", grainId, grainType); throw; } logger.LogInformation( "ClearState for grain {GrainId} of type {GrainType}", grainId, grainType); await realStorageProvider.ClearStateAsync(grainType, grainId, grainState); } /// public void Participate(ISiloLifecycle lifecycle) { (realStorageProvider as ILifecycleParticipant)?.Participate(lifecycle); } } /// /// Factory to create FaultInjectionGrainStorage /// public static class FaultInjectionGrainStorageFactory { /// /// Creates a new instance. /// /// The services. /// The storage provider name. /// The injected grain storage factory. /// The new instance. public static IGrainStorage Create(IServiceProvider services, string name, Func injectedGrainStorageFactory) { return new FaultInjectionGrainStorage(injectedGrainStorageFactory(services,name), name, services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService>().Get(name)); } } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/FaultInjectionStorageServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Runtime; using Orleans.Storage; using Orleans.TestingHost; namespace Orleans.Hosting { /// /// Extension methods for . /// public static class FaultInjectionStorageServiceCollectionExtensions { /// /// Configures a silo to use . /// /// The services. /// The storage provider name. /// The memory storage configuration delegate. /// The fault injection provider configuration delegate. /// The service collection. public static IServiceCollection AddFaultInjectionMemoryStorage( this IServiceCollection services, string name, Action configureOptions, Action configureFaultInjectionOptions) { return services.AddFaultInjectionMemoryStorage(name, ob => ob.Configure(configureOptions), faultOb => faultOb.Configure(configureFaultInjectionOptions)); } /// /// Configures a silo to use . /// /// The services. /// The storage provider name. /// The memory storage configuration delegate. /// The fault injection provider configuration delegate. /// The service collection. public static IServiceCollection AddFaultInjectionMemoryStorage( this IServiceCollection services, string name, Action> configureOptions = null, Action> configureFaultInjectionOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); configureFaultInjectionOptions?.Invoke(services.AddOptions(name)); services.ConfigureNamedOptionForLogging(name); services.ConfigureNamedOptionForLogging(name); services.AddKeyedSingleton(name, (svc, n) => FaultInjectionGrainStorageFactory.Create(svc, n as string, MemoryGrainStorageFactory.Create)) .AddKeyedSingleton>(name, (s, n) => (ILifecycleParticipant)s.GetRequiredKeyedService(n)); return services; } } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/FaultyMemoryStorage.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; namespace Orleans.TestingHost { /// /// Extension methods for . /// public static class SiloBuilderExtensions { /// /// Configures a silo to use . /// /// The builder. /// The storage provider name. /// The memory storage configuration delegate. /// The fault injection provider configuration delegate. /// The silo builder public static ISiloBuilder AddFaultInjectionMemoryStorage( this ISiloBuilder builder, string name, Action configureOptions, Action configureFaultInjectionOptions) { return builder.ConfigureServices(services => services.AddFaultInjectionMemoryStorage(name, ob => ob.Configure(configureOptions), faultOb => faultOb.Configure(configureFaultInjectionOptions))); } /// /// Configures a silo to use . /// /// The builder. /// The storage provider name. /// The memory storage configuration delegate. /// The fault injection provider configuration delegate. /// The silo builder public static ISiloBuilder AddFaultInjectionMemoryStorage( this ISiloBuilder builder, string name, Action> configureOptions = null, Action> configureFaultInjectionOptions = null) { return builder.ConfigureServices(services => services.AddFaultInjectionMemoryStorage(name, configureOptions, configureFaultInjectionOptions)); } } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/IStorageFaultGrain.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.TestingHost { /// /// Grain that tracks storage exceptions to be injected. /// public interface IStorageFaultGrain : IGrainWithStringKey { /// /// Adds a storage exception to be thrown when the referenced grain reads state from a storage provider /// /// Task. Task AddFaultOnRead(GrainId grainId, Exception exception); /// /// Adds a storage exception to be thrown when the referenced grain writes state to a storage provider /// /// Task. Task AddFaultOnWrite(GrainId grainId, Exception exception); /// /// Adds a storage exception to be thrown when the referenced grain clears state in a storage provider /// /// Task. Task AddFaultOnClear(GrainId grainId, Exception exception); /// /// Throws a storage exception if one has been added for the grain reference for reading. /// Task OnRead(GrainId grainId); /// /// Throws a storage exception if one has been added for the grain reference for writing. /// Task OnWrite(GrainId grainId); /// /// Throws a storage exception if one has been added for the grain reference for clearing state. /// Task OnClear(GrainId grainId); } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/RandomlyInjectedStorageException.cs ================================================ using Orleans.Storage; using System; using System.Runtime.Serialization; namespace Orleans.TestingHost { /// /// Represents a randomly injected storage exception. /// [Serializable] [GenerateSerializer] public sealed class RandomlyInjectedStorageException : Exception { /// /// Initializes a new instance of the class. /// public RandomlyInjectedStorageException() : base("injected fault") { } /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. [Obsolete] private RandomlyInjectedStorageException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Represents a randomly injected . /// [Serializable] [GenerateSerializer] public sealed class RandomlyInjectedInconsistentStateException : InconsistentStateException { /// /// Initializes a new instance of the class. /// public RandomlyInjectedInconsistentStateException() : base("injected fault") { } /// /// Initializes a new instance of the class. /// /// The serialization info. /// The context. [Obsolete] private RandomlyInjectedInconsistentStateException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.TestingHost/TestStorageProviders/StorageFaultGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Microsoft.Extensions.DependencyInjection; using System.Threading; namespace Orleans.TestingHost { /// /// Grain that tracks storage exceptions to be injected. /// public class StorageFaultGrain : Grain, IStorageFaultGrain { private ILogger logger; private Dictionary readFaults; private Dictionary writeFaults; private Dictionary clearfaults; /// public override async Task OnActivateAsync(CancellationToken cancellationToken) { await base.OnActivateAsync(cancellationToken); logger = this.ServiceProvider.GetService().CreateLogger($"{typeof (StorageFaultGrain).FullName}-{IdentityString}-{RuntimeIdentity}"); readFaults = new(); writeFaults = new(); clearfaults = new(); logger.LogInformation("Activate."); } /// public Task AddFaultOnRead(GrainId grainId, Exception exception) { readFaults.Add(grainId, exception); logger.LogInformation("Added ReadState fault for {GrainId}.", GrainId); return Task.CompletedTask; } /// public Task AddFaultOnWrite(GrainId grainId, Exception exception) { writeFaults.Add(grainId, exception); logger.LogInformation("Added WriteState fault for {GrainId}.", GrainId); return Task.CompletedTask; } /// public Task AddFaultOnClear(GrainId grainId, Exception exception) { clearfaults.Add(grainId, exception); logger.LogInformation("Added ClearState fault for {GrainId}.", GrainId); return Task.CompletedTask; } /// public Task OnRead(GrainId grainId) { if (readFaults.Remove(grainId, out var exception)) { throw exception; } return Task.CompletedTask; } /// public Task OnWrite(GrainId grainId) { if (writeFaults.Remove(grainId, out var exception)) { throw exception; } return Task.CompletedTask; } /// public Task OnClear(GrainId grainId) { if (clearfaults.Remove(grainId, out var exception)) { throw exception; } return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.TestingHost/UnixSocketTransport/UnixSocketConnectionExtensions.cs ================================================ using System; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Runtime; using Orleans.Runtime.Messaging; namespace Orleans.TestingHost.UnixSocketTransport; public static class UnixSocketConnectionExtensions { public static ISiloBuilder UseUnixSocketConnection(this ISiloBuilder siloBuilder) { siloBuilder.ConfigureServices(services => { services.AddKeyedSingleton(SiloConnectionFactory.ServicesKey, CreateUnixSocketConnectionFactory()); services.AddKeyedSingleton(SiloConnectionListener.ServicesKey, CreateUnixSocketConnectionListenerFactory()); services.AddKeyedSingleton(GatewayConnectionListener.ServicesKey, CreateUnixSocketConnectionListenerFactory()); }); return siloBuilder; } public static IClientBuilder UseUnixSocketConnection(this IClientBuilder clientBuilder) { clientBuilder.ConfigureServices(services => { services.AddKeyedSingleton(ClientOutboundConnectionFactory.ServicesKey, CreateUnixSocketConnectionFactory()); }); return clientBuilder; } private static Func CreateUnixSocketConnectionFactory() { return (IServiceProvider sp, object key) => ActivatorUtilities.CreateInstance(sp); } private static Func CreateUnixSocketConnectionListenerFactory() { return (IServiceProvider sp, object key) => ActivatorUtilities.CreateInstance(sp); } } ================================================ FILE: src/Orleans.TestingHost/UnixSocketTransport/UnixSocketConnectionFactory.cs ================================================ using System.Buffers; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Networking.Shared; namespace Orleans.TestingHost.UnixSocketTransport; internal class UnixSocketConnectionFactory : IConnectionFactory { private readonly SocketsTrace trace; private readonly UnixSocketConnectionOptions socketConnectionOptions; private readonly SocketSchedulers schedulers; private readonly MemoryPool memoryPool; public UnixSocketConnectionFactory( ILoggerFactory loggerFactory, IOptions options, SocketSchedulers schedulers, SharedMemoryPool memoryPool) { var logger = loggerFactory.CreateLogger("Orleans.UnixSocket"); this.trace = new SocketsTrace(logger); this.socketConnectionOptions = options.Value; this.schedulers = schedulers; this.memoryPool = memoryPool.Pool; } public async ValueTask ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); var unixEndpoint = new UnixDomainSocketEndPoint(socketConnectionOptions.ConvertEndpointToPath(endpoint)); await socket.ConnectAsync(unixEndpoint); var scheduler = this.schedulers.GetScheduler(); var connection = new SocketConnection(socket, memoryPool, scheduler, trace); connection.Start(); return connection; } } ================================================ FILE: src/Orleans.TestingHost/UnixSocketTransport/UnixSocketConnectionListener.cs ================================================ using System; using System.Buffers; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Orleans.Networking.Shared; namespace Orleans.TestingHost.UnixSocketTransport; internal class UnixSocketConnectionListener : IConnectionListener { private readonly UnixDomainSocketEndPoint _unixEndpoint; private readonly EndPoint _endpoint; private readonly UnixSocketConnectionOptions _socketConnectionOptions; private readonly SocketsTrace _trace; private readonly SocketSchedulers _schedulers; private readonly MemoryPool _memoryPool; private Socket _listenSocket; public UnixSocketConnectionListener(UnixDomainSocketEndPoint unixEndpoint, EndPoint endpoint, UnixSocketConnectionOptions socketConnectionOptions, SocketsTrace trace, SocketSchedulers schedulers) { _unixEndpoint = unixEndpoint; _endpoint = endpoint; _socketConnectionOptions = socketConnectionOptions; _trace = trace; _schedulers = schedulers; _memoryPool = socketConnectionOptions.MemoryPoolFactory(); } public EndPoint EndPoint => _endpoint; public void Bind() { _listenSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); _listenSocket.Bind(_unixEndpoint); _listenSocket.Listen(512); } public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) { while (true) { try { var acceptSocket = await _listenSocket.AcceptAsync(); var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers.GetScheduler(), _trace); connection.Start(); return connection; } catch (ObjectDisposedException) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted) { // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done return null; } catch (SocketException) { // The connection got reset while it was in the backlog, so we try again. _trace.ConnectionReset(connectionId: "(null)"); } } } public ValueTask DisposeAsync() { _listenSocket?.Dispose(); return default; } public ValueTask UnbindAsync(CancellationToken cancellationToken = default) { _listenSocket?.Dispose(); _memoryPool?.Dispose(); return default; } } ================================================ FILE: src/Orleans.TestingHost/UnixSocketTransport/UnixSocketConnectionListenerFactory.cs ================================================ using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Networking.Shared; namespace Orleans.TestingHost.UnixSocketTransport; internal class UnixSocketConnectionListenerFactory : IConnectionListenerFactory { private readonly UnixSocketConnectionOptions socketConnectionOptions; private readonly SocketsTrace trace; private readonly SocketSchedulers schedulers; public UnixSocketConnectionListenerFactory( ILoggerFactory loggerFactory, IOptions socketConnectionOptions, SocketSchedulers schedulers) { this.socketConnectionOptions = socketConnectionOptions.Value; var logger = loggerFactory.CreateLogger("Orleans.UnixSockets"); this.trace = new SocketsTrace(logger); this.schedulers = schedulers; } public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var unixEndpoint = new UnixDomainSocketEndPoint(socketConnectionOptions.ConvertEndpointToPath(endpoint)); var listener = new UnixSocketConnectionListener(unixEndpoint, endpoint, this.socketConnectionOptions, this.trace, this.schedulers); listener.Bind(); return new ValueTask(listener); } } ================================================ FILE: src/Orleans.TestingHost/UnixSocketTransport/UnixSocketConnectionOptions.cs ================================================ using System; using System.Buffers; using System.IO; using System.Net; using System.Text.RegularExpressions; using Orleans.Networking.Shared; namespace Orleans.TestingHost.UnixSocketTransport; public partial class UnixSocketConnectionOptions { /// /// Get or sets to function used to get a filename given an endpoint /// public Func ConvertEndpointToPath { get; set; } = DefaultConvertEndpointToPath; /// /// Gets or sets the memory pool factory. /// internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); [GeneratedRegex("[^a-zA-Z0-9]")] private static partial Regex ConvertEndpointRegex(); private static string DefaultConvertEndpointToPath(EndPoint endPoint) => Path.Combine(Path.GetTempPath(), ConvertEndpointRegex().Replace(endPoint.ToString(), "_")); } ================================================ FILE: src/Orleans.TestingHost/Utils/AsyncResultHandle.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.TestingHost.Utils { /// /// This class is for internal testing use only. /// public class AsyncResultHandle { private bool done = false; private bool continueFlag = false; /// Reset the current result handle public virtual void Reset() { Exception = null; Result = null; done = false; continueFlag = false; } /// Get or set the Done flag public bool Done { get { return done; } set { done = value; } } /// Get or set the Continue flag public bool Continue { get { return continueFlag; } set { continueFlag = value; } } /// Get or set the exception of the result handle public Exception Exception { get; set; } /// Get or set the value of the result handle public object Result { get; set; } /// /// /// /// Returns true if operation completes before timeout public Task WaitForFinished(TimeSpan timeout) { return WaitFor(timeout, () => done); } /// /// /// /// Returns true if operation completes before timeout public Task WaitForContinue(TimeSpan timeout) { return WaitFor(timeout, () => continueFlag); } /// /// /// /// /// Returns true if operation completes before timeout public async Task WaitFor(TimeSpan timeout, Func checkFlag) { double remaining = timeout.TotalMilliseconds; while (!checkFlag()) { if (remaining < 0) { //throw new TimeoutException("Timeout waiting for result for " + timeout); return false; } await Task.Delay(TimeSpan.FromMilliseconds(200)); remaining -= 200; } return true; } } } ================================================ FILE: src/Orleans.TestingHost/Utils/StorageEmulator.cs ================================================ using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace Orleans.TestingHost.Utils { /// /// A wrapper on Azure Storage Emulator. /// /// It might be tricky to implement this as a IDisposable, isolated, autonomous instance, /// see at Use the Azure Storage Emulator for Development and Testing /// for pointers. public static class StorageEmulator { /// /// The storage emulator process name. One way to enumerate running process names is /// Get-Process | Format-Table Id, ProcessName -autosize. If there were multiple storage emulator /// processes running, they would named WASTOR~1, WASTOR~2, ... WASTOR~n. /// private static readonly string[] storageEmulatorProcessNames = new[] { "AzureStorageEmulator", // newest "Windows Azure Storage Emulator Service", // >= 2.7 "WAStorageEmulator", // < 2.7 }; //The file names aren't the same as process names. private static readonly string[] storageEmulatorFilenames = new[] { "AzureStorageEmulator.exe", // >= 2.7 "WAStorageEmulator.exe", // < 2.7 }; /// /// Is the storage emulator already started. /// /// if this instance is started; otherwise, . public static bool IsStarted() { return GetStorageEmulatorProcess(); } /// /// Checks if the storage emulator exists, i.e. is installed. /// /// if the storage emulator exists; otherwise, . public static bool Exists { get { return GetStorageEmulatorPath() != null; } } /// /// Storage Emulator help. /// /// Storage emulator help. public static string Help() { if (!IsStarted()) return "Error happened. Has StorageEmulator.Start() been called?"; try { //This process handle returns immediately. using(var process = Process.Start(CreateProcessArguments("help"))) { process.WaitForExit(); StringBuilder help = new(); while(!process.StandardOutput.EndOfStream) { help.Append(process.StandardOutput.ReadLine()); } return help.ToString(); } } catch (Exception exc) { return exc.ToString(); } } /// /// Tries to start the storage emulator. /// /// if the process was started successfully; otherwise. public static bool TryStart() { if (!StorageEmulator.Exists) return false; return Start(); } /// /// Starts the storage emulator if not already started. /// /// if the process was started successfully; otherwise. public static bool Start() { if (IsStarted()) return true; try { //This process handle returns immediately. using(var process = Process.Start(CreateProcessArguments("start"))) { if (process == null) return false; process.WaitForExit(); return process.ExitCode == 0; } } catch { return false; } } /// /// Stops the storage emulator if started. /// /// if the process was stopped successfully or was already stopped; otherwise. public static bool Stop() { if (!IsStarted()) return false; try { //This process handle returns immediately. using(var process = Process.Start(CreateProcessArguments("stop"))) { process.WaitForExit(); return process.ExitCode == 0; } } catch { return false; } } /// /// Creates a new ProcessStartInfo to be used as an argument /// to other operations in this class. /// /// The arguments. /// A new that has the given arguments. private static ProcessStartInfo CreateProcessArguments(string arguments) { #pragma warning disable CA1416 // Validate platform compatibility return new ProcessStartInfo(GetStorageEmulatorPath()) { WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = true, LoadUserProfile = true, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, Arguments = arguments }; #pragma warning restore CA1416 } /// /// Queries the storage emulator process from the system. /// /// if the storage emulator process was found, otherwise. private static bool GetStorageEmulatorProcess() { foreach (var name in storageEmulatorProcessNames) { var ps = Process.GetProcessesByName(name); if (ps.Length != 0) { foreach (var p in ps) p.Dispose(); return true; } } return false; } /// /// Returns a full path to the storage emulator executable, including the executable name and file extension. /// /// A full path to the storage emulator executable, or null if not found. private static string GetStorageEmulatorPath() { //Try to take the newest known emulator path. If it does not exist, try an older one. string exeBasePath = Path.Combine(GetProgramFilesBasePath(), @"Microsoft SDKs\Azure\Storage Emulator\"); return storageEmulatorFilenames .Select(filename => Path.Combine(exeBasePath, filename)) .FirstOrDefault(File.Exists); } /// /// Determines the Program Files base directory. /// /// The Program files base directory. private static string GetProgramFilesBasePath() { return Environment.Is64BitOperatingSystem ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); } } } ================================================ FILE: src/Orleans.TestingHost/Utils/TestingUtils.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.TestingHost.Logging; namespace Orleans.TestingHost.Utils { /// Collection of test utilities public static class TestingUtils { private static long uniquifier = Stopwatch.GetTimestamp(); /// /// Configure with a which logs to /// by default; /// /// The builder. /// The file path. public static void ConfigureDefaultLoggingBuilder(ILoggingBuilder builder, string filePath) { builder.AddFile(filePath); } /// /// Create trace file name for a specific node or client in a specific deployment /// /// Name of the node. /// The cluster identifier. /// The new trace file name. public static string CreateTraceFileName(string nodeName, string clusterId) { const string traceFileFolder = "logs"; if (!Directory.Exists(traceFileFolder)) { Directory.CreateDirectory(traceFileFolder); } var traceFileName = Path.Combine(traceFileFolder, $"{clusterId}_{Interlocked.Increment(ref uniquifier):X}_{nodeName}.log"); return traceFileName; } /// /// Create the default logger factory, which would configure logger factory with a that writes logs to and console. /// by default; /// /// The file path. /// ILoggerFactory. public static ILoggerFactory CreateDefaultLoggerFactory(string filePath) { return CreateDefaultLoggerFactory(filePath, new LoggerFilterOptions()); } /// /// Create the default logger factory, which would configure logger factory with a that writes logs to and console. /// by default; /// /// the logger file path /// log filters you want to configure your logging with /// public static ILoggerFactory CreateDefaultLoggerFactory(string filePath, LoggerFilterOptions filters) { var factory = new LoggerFactory(new List(), filters); factory.AddProvider(new FileLoggerProvider(filePath)); return factory; } /// Run the predicate until it succeed or times out /// The predicate to run /// The timeout value /// The time to delay next call upon failure /// True if the predicate succeed, false otherwise public static async Task WaitUntilAsync(Func> predicate, TimeSpan timeout, TimeSpan? delayOnFail = null) { delayOnFail = delayOnFail ?? TimeSpan.FromSeconds(1); var keepGoing = new[] { true }; async Task loop() { bool passed; do { // need to wait a bit to before re-checking the condition. await Task.Delay(delayOnFail.Value); passed = await predicate(false); } while (!passed && keepGoing[0]); if (!passed) await predicate(true); } var task = loop(); try { await Task.WhenAny(task, Task.Delay(timeout)); } finally { keepGoing[0] = false; } await task; } /// /// Multiply a timeout by a value /// /// The time. /// The value. /// The resulting time span value. public static TimeSpan Multiply(TimeSpan time, double value) { double ticksD = checked(time.Ticks * value); long ticks = checked((long)ticksD); return TimeSpan.FromTicks(ticks); } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/Extensions/TransactionalStateExtensions.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { public static class TransactionalStateExtensions { /// /// Performs an update operation, without returning any result. /// /// Transactional state to perform update upon. /// An action that updates the state. public static Task PerformUpdate(this ITransactionalState transactionalState, Action updateAction) where TState : class, new() { return transactionalState.PerformUpdate(state => { updateAction(state); return true; }); } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/INamedTransactionalStateStorageFactory.cs ================================================  namespace Orleans.Transactions.Abstractions { /// /// Factory which creates an ITransactionalStateStorage by name. /// public interface INamedTransactionalStateStorageFactory { /// /// Create an ITransactionalStateStorage by name. /// /// /// Name of transaction state storage to create. /// Name of transaction state. /// ITransactionalStateStorage, null if not found. ITransactionalStateStorage Create(string storageName, string stateName) where TState : class, new(); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionAgentStatistics.cs ================================================  namespace Orleans.Transactions.Abstractions { public interface ITransactionAgentStatistics { void TrackTransactionStarted(); long TransactionsStarted { get; } void TrackTransactionSucceeded(); long TransactionsSucceeded { get; } void TrackTransactionFailed(); long TransactionsFailed { get; } void TrackTransactionThrottled(); long TransactionsThrottled { get; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionCommitter.cs ================================================  using System; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { public interface ITransactionCommitOperation where TService : class { Task Commit(Guid transactionId, TService service); } public interface ITransactionCommitter where TService : class { Task OnCommit(ITransactionCommitOperation operation); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionCommitterConfiguration.cs ================================================  namespace Orleans.Transactions.Abstractions { public interface ITransactionCommitterConfiguration { string ServiceName { get; } string StorageName { get; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionCommitterFactory.cs ================================================ namespace Orleans.Transactions.Abstractions { public interface ITransactionCommitterFactory { ITransactionCommitter Create(ITransactionCommitterConfiguration config) where TService : class; } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionDataCopier.cs ================================================  namespace Orleans.Transactions.Abstractions { public interface ITransactionDataCopier { TData DeepCopy(TData original); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionManager.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { public interface ITransactionManager { /// /// Request sent by TA to TM. The TM responds after committing or aborting the transaction. /// /// the id of the transaction to prepare /// number of reads/writes performed on this participant by this transaction /// the commit timestamp for this transaction /// the participants who wrote during the transaction /// the total number of participants in the transaction /// the status of the transaction Task PrepareAndCommit(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, List writerResources, int totalParticipants); /// /// One-way message sent by a participant to the TM after it (successfully or unsuccessfully) prepares. /// /// The id of the transaction /// The commit timestamp of the transaction /// The participant sending the message /// The outcome of the prepare /// Task Prepared(Guid transactionId, DateTime timeStamp, ParticipantId resource, TransactionalStatus status); /// /// One-way message sent by participants to TM, to let TM know they are still waiting to hear about /// the fate of a transaction. /// /// The id of the transaction /// The commit timestamp of the transaction /// The participant sending the message Task Ping(Guid transactionId, DateTime timeStamp, ParticipantId resource); } /// /// Counts read and write accesses on a transaction participant. /// [GenerateSerializer] [Serializable] public struct AccessCounter { [Id(0)] public int Reads; [Id(1)] public int Writes; public static AccessCounter operator +(AccessCounter c1, AccessCounter c2) { return new AccessCounter { Reads = c1.Reads + c2.Reads, Writes = c1.Writes + c2.Writes }; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionManagerExtension.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans.Transactions.Abstractions { public interface ITransactionManagerExtension : IGrainExtension { [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] Task PrepareAndCommit(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp, List writeResources, int totalParticipants); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [OneWay] Task Prepared(string resourceId, Guid transactionId, DateTime timestamp, ParticipantId resource, TransactionalStatus status); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [OneWay] Task Ping(string resourceId, Guid transactionId, DateTime timeStamp, ParticipantId resource); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalResource.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { /// /// Interface that allows a component to be a transaction participant. /// public interface ITransactionalResource { /// /// Request sent by TA to all participants of a read-only transaction (one-phase commit). /// Participants respond after committing or aborting the read. /// /// the id of the transaction to prepare /// number of reads/writes performed on this participant by this transaction /// the commit timestamp for this transaction /// Task CommitReadOnly(Guid transactionId, AccessCounter accessCount, DateTime timeStamp); /// /// One-way message sent by TA to all participants except TM. /// /// the id of the transaction to prepare /// number of reads/writes performed on this participant by this transaction /// the commit timestamp for this transaction /// the transaction manager for this transaction /// Task Prepare(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager); /// /// One-way message sent by TA to participants to let them know a transaction has aborted. /// /// The id of the aborted transaction Task Abort(Guid transactionId); /// /// One-way message sent by TM to participants to let them know a transaction has aborted. /// /// The id of the aborted transaction /// The commit timestamp of the aborted transaction /// Reason for abort Task Cancel(Guid transactionId, DateTime timeStamp, TransactionalStatus status); /// /// Request sent by TM to participants to let them know a transaction has committed. /// Participants respond after cleaning up all prepare records. /// /// The id of the committed transaction /// The commit timestamp of the committed transaction Task Confirm(Guid transactionId, DateTime timeStamp); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalResourceExtension.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Runtime; namespace Orleans.Transactions.Abstractions { public interface ITransactionalResourceExtension : IGrainExtension { [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] Task CommitReadOnly(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] Task Abort(string resourceId, Guid transactionId); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] Task Cancel(string resourceId, Guid transactionId, DateTime timeStamp, TransactionalStatus status); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] Task Confirm(string resourceId, Guid transactionId, DateTime timeStamp); [AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [OneWay] Task Prepare(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalState.cs ================================================  using System; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { /// /// State that respects Orleans transaction semantics, and allows /// read/write locking /// /// The type of the state public interface ITransactionalState where TState : class, new() { /// /// Performs a read operation and returns the result, without modifying the state. /// /// The type of the return value /// A function that reads the state and returns the result. MUST NOT modify the state. Task PerformRead(Func readFunction); /// /// Performs an update operation and returns the result. /// /// The type of the return value /// A function that can read and update the state, and return a result Task PerformUpdate(Func updateFunction); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalStateConfiguration.cs ================================================  namespace Orleans.Transactions.Abstractions { public interface ITransactionalStateConfiguration { string StateName { get; } string StorageName { get; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalStateFactory.cs ================================================ namespace Orleans.Transactions.Abstractions { public class TransactionalStateConfiguration : ITransactionalStateConfiguration { private readonly string name; private readonly string storage; public TransactionalStateConfiguration(ITransactionalStateConfiguration config, ParticipantId.Role supportedRoles = ParticipantId.Role.Resource | ParticipantId.Role.Manager) { this.name = config.StateName; this.storage = config.StorageName; this.SupportedRoles = supportedRoles; } public string StateName => this.name; public string StorageName => this.storage; public ParticipantId.Role SupportedRoles { get; } } public interface ITransactionalStateFactory { ITransactionalState Create(TransactionalStateConfiguration config) where TState : class, new(); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalStateStorage.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Transactions.Abstractions { /// /// Storage interface for transactional state /// /// the type of the state public interface ITransactionalStateStorage where TState : class, new() { Task> Load(); Task Store( string expectedETag, TransactionalStateMetaData metadata, // a list of transactions to prepare. List> statesToPrepare, // if non-null, commit all pending transaction up to and including this sequence number. long? commitUpTo, // if non-null, abort all pending transactions with sequence numbers strictly larger than this one. long? abortAfter ); } [Serializable, GenerateSerializer, Immutable] public sealed class PendingTransactionState where TState : class, new() { /// /// Transactions are given dense local sequence numbers 1,2,3,4... /// If a new transaction is prepared with the same sequence number as a /// previously prepared transaction, it replaces it. /// [Id(0)] public long SequenceId { get; set; } /// /// A globally unique identifier of the transaction. /// [Id(1)] public string TransactionId { get; set; } /// /// The logical timestamp of the transaction. /// Timestamps are guaranteed to be monotonically increasing. /// [Id(2)] public DateTime TimeStamp { get; set; } /// /// The transaction manager that knows about the status of this prepared transaction, /// or null if this is the transaction manager. /// Used during recovery to inquire about the fate of the transaction. /// [Id(3)] public ParticipantId TransactionManager { get; set; } /// /// A snapshot of the state after this transaction executed /// [Id(4)] public TState State { get; set; } } [Serializable, GenerateSerializer, Immutable] public sealed class TransactionalStorageLoadResponse where TState : class, new() { public TransactionalStorageLoadResponse() : this(null, new TState(), 0, new TransactionalStateMetaData(), Array.Empty>()) { } public TransactionalStorageLoadResponse(string etag, TState committedState, long committedSequenceId, TransactionalStateMetaData metadata, IReadOnlyList> pendingStates) { this.ETag = etag; this.CommittedState = committedState; this.CommittedSequenceId = committedSequenceId; this.Metadata = metadata; this.PendingStates = pendingStates; } [Id(0)] public string ETag { get; set; } [Id(1)] public TState CommittedState { get; set; } /// /// The local sequence id of the last committed transaction, or zero if none /// [Id(2)] public long CommittedSequenceId { get; set; } /// /// Additional state maintained by the transaction algorithm, such as commit records /// [Id(3)] public TransactionalStateMetaData Metadata { get; set; } /// /// List of pending states, ordered by sequence id /// [Id(4)] public IReadOnlyList> PendingStates { get; set; } } /// /// Metadata is stored in storage, as a JSON object /// [GenerateSerializer] [Serializable] public sealed class TransactionalStateMetaData { [Id(0)] public DateTime TimeStamp { get; set; } = default; [Id(1)] public Dictionary CommitRecords { get; set; } = new Dictionary(); } [Serializable, GenerateSerializer, Immutable] public sealed class CommitRecord { [Id(0)] public DateTime Timestamp { get; set; } [Id(1)] public List WriteParticipants { get; set; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/ITransactionalStateStorageFactory.cs ================================================  using Orleans.Runtime; namespace Orleans.Transactions.Abstractions { public interface ITransactionalStateStorageFactory { ITransactionalStateStorage Create(string stateName, IGrainContext context) where TState : class, new(); } } ================================================ FILE: src/Orleans.Transactions/Abstractions/TransactionCommitterAttribute.cs ================================================ using System; namespace Orleans.Transactions.Abstractions { [AttributeUsage(AttributeTargets.Parameter)] public class TransactionCommitterAttribute : Attribute, IFacetMetadata, ITransactionCommitterConfiguration { public string ServiceName { get; } public string StorageName { get; } public TransactionCommitterAttribute(string serviceName, string storageName = null) { this.ServiceName = serviceName; this.StorageName = storageName; } } } ================================================ FILE: src/Orleans.Transactions/Abstractions/TransactionalStateAttribute.cs ================================================ using System; namespace Orleans.Transactions.Abstractions { [AttributeUsage(AttributeTargets.Parameter)] public class TransactionalStateAttribute : Attribute, IFacetMetadata, ITransactionalStateConfiguration { public string StateName { get; } public string StorageName { get; } public TransactionalStateAttribute(string stateName, string storageName = null) { this.StateName = stateName; this.StorageName = storageName; } } } ================================================ FILE: src/Orleans.Transactions/DisabledTransactionAgent.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Transactions { internal class DisabledTransactionAgent : ITransactionAgent { public Task Abort(TransactionInfo transactionInfo) { throw new OrleansTransactionsDisabledException(); } public Task<(TransactionalStatus Status, Exception exception)> Resolve(TransactionInfo transactionInfo) { throw new OrleansTransactionsDisabledException(); } public Task StartTransaction(bool readOnly, TimeSpan timeout) { throw new OrleansStartTransactionFailedException(new OrleansTransactionsDisabledException()); } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/ContextResourceFactoryExtensions.cs ================================================ using System; using System.Collections.Generic; using Orleans.Runtime; namespace Orleans.Transactions { internal class ResourceFactoryRegistry : Dictionary> { }; internal static class ContextResourceFactoryExtensions { public static void RegisterResourceFactory(this IGrainContext context, string name, Func factory) { ResourceFactoryRegistry registry = context.GetResourceFactoryRegistry(true); registry[name] = factory; } public static ResourceFactoryRegistry GetResourceFactoryRegistry(this IGrainContext context, bool createIfNotExists = false) { ResourceFactoryRegistry result = context.GetComponent>(); if (createIfNotExists && result == null) { result = new ResourceFactoryRegistry(); context.SetComponent(result); } return result; } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/ParticipantId.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { [Serializable, GenerateSerializer, Immutable] public readonly struct ParticipantId { public static readonly IEqualityComparer Comparer = new IdComparer(); [GenerateSerializer] [Flags] public enum Role { Resource = 1 << 0, Manager = 1 << 1, PriorityManager = 1 << 2 } [Id(0)] public string Name { get; } [Id(1)] public GrainReference Reference { get; } [Id(2)] public Role SupportedRoles { get; } public ParticipantId(string name, GrainReference reference, Role supportedRoles) { this.Name = name; this.Reference = reference; this.SupportedRoles = supportedRoles; } public override string ToString() { return $"ParticipantId.{Name}.{Reference}"; } [GenerateSerializer, Immutable] public sealed class IdComparer : IEqualityComparer { public bool Equals(ParticipantId x, ParticipantId y) { return string.CompareOrdinal(x.Name, y.Name) == 0 && Equals(x.Reference, y.Reference); } public int GetHashCode(ParticipantId obj) => HashCode.Combine(obj.Name, obj.Reference); } } public static class ParticipantRoleExtensions { public static bool SupportsRoles(this ParticipantId participant, ParticipantId.Role role) { return (participant.SupportedRoles & role) != 0; } public static bool IsResource(this ParticipantId participant) { return participant.SupportsRoles(ParticipantId.Role.Resource); } public static bool IsManager(this ParticipantId participant) { return participant.SupportsRoles(ParticipantId.Role.Manager); } public static bool IsPriorityManager(this ParticipantId participant) { return participant.SupportsRoles(ParticipantId.Role.PriorityManager); } public static IEnumerable> SelectResources(this IEnumerable> participants) { return participants.Where(p => p.Key.IsResource()); } public static IEnumerable> SelectManagers(this IEnumerable> participants) { return participants.Where(p => p.Key.IsManager()); } public static IEnumerable SelectPriorityManagers(this IEnumerable participants) { return participants.Where(p => p.IsPriorityManager()); } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionAgent.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using System.Linq; using Microsoft.Extensions.Logging; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { internal partial class TransactionAgent : ITransactionAgent { private readonly ILogger logger; private readonly Stopwatch stopwatch = Stopwatch.StartNew(); private readonly CausalClock clock; private readonly ITransactionAgentStatistics statistics; private readonly ITransactionOverloadDetector overloadDetector; public TransactionAgent(IClock clock, ILogger logger, ITransactionAgentStatistics statistics, ITransactionOverloadDetector overloadDetector) { this.clock = new CausalClock(clock); this.logger = logger; this.statistics = statistics; this.overloadDetector = overloadDetector; } public Task StartTransaction(bool readOnly, TimeSpan timeout) { if (overloadDetector.IsOverloaded()) { this.statistics.TrackTransactionThrottled(); throw new OrleansStartTransactionFailedException(new OrleansTransactionOverloadException()); } var guid = Guid.NewGuid(); DateTime ts = this.clock.UtcNow(); LogTraceStartTransaction(new(stopwatch), guid, new(ts)); this.statistics.TrackTransactionStarted(); return Task.FromResult(new TransactionInfo(guid, ts, ts, readOnly)); } public async Task<(TransactionalStatus, Exception)> Resolve(TransactionInfo transactionInfo) { transactionInfo.TimeStamp = this.clock.MergeUtcNow(transactionInfo.TimeStamp); LogTracePrepareTransaction(new(stopwatch), transactionInfo); if (transactionInfo.Participants.Count == 0) { this.statistics.TrackTransactionSucceeded(); return (TransactionalStatus.Ok, null); } KeyValuePair? manager; List writeParticipants; List> resources; CollateParticipants(transactionInfo.Participants, out writeParticipants, out resources, out manager); try { var (status, exception) = (writeParticipants == null) ? await CommitReadOnlyTransaction(transactionInfo, resources) : await CommitReadWriteTransaction(transactionInfo, writeParticipants, resources, manager.Value); if (status == TransactionalStatus.Ok) this.statistics.TrackTransactionSucceeded(); else this.statistics.TrackTransactionFailed(); return (status, exception); } catch (Exception) { this.statistics.TrackTransactionFailed(); throw; } } private async Task<(TransactionalStatus, Exception)> CommitReadOnlyTransaction(TransactionInfo transactionInfo, List> resources) { TransactionalStatus status = TransactionalStatus.Ok; Exception exception; var tasks = new List>(); try { foreach (KeyValuePair resource in resources) { tasks.Add(resource.Key.Reference.AsReference() .CommitReadOnly(resource.Key.Name, transactionInfo.TransactionId, resource.Value, transactionInfo.TimeStamp)); } // wait for all responses TransactionalStatus[] results = await Task.WhenAll(tasks); // examine the return status foreach (var s in results) { if (s != TransactionalStatus.Ok) { status = s; LogDebugPrepareTransactionFailure(new(stopwatch), transactionInfo.TransactionId, status); break; } } exception = null; } catch (TimeoutException ex) { LogDebugCommitReadOnlyTimeout(new(stopwatch), transactionInfo.TransactionId); status = TransactionalStatus.ParticipantResponseTimeout; exception = ex; } catch (Exception ex) { LogDebugCommitReadOnlyFailure(new(stopwatch), transactionInfo.TransactionId); LogWarnCommitReadOnlyFailure(transactionInfo.TransactionId, ex); status = TransactionalStatus.PresumedAbort; exception = ex; } if (status != TransactionalStatus.Ok) { try { await Task.WhenAll(resources.Select(r => r.Key.Reference.AsReference() .Abort(r.Key.Name, transactionInfo.TransactionId))); } catch (Exception ex) { LogDebugCommitReadOnlyFailureAborting(new(stopwatch), transactionInfo.TransactionId, ex); LogWarnFailAbortReadonlyTransaction(transactionInfo.TransactionId, ex); } } LogTraceFinishReadOnlyTransaction(new(stopwatch), transactionInfo.TransactionId); return (status, exception); } private async Task<(TransactionalStatus, Exception)> CommitReadWriteTransaction(TransactionInfo transactionInfo, List writeResources, List> resources, KeyValuePair manager) { TransactionalStatus status = TransactionalStatus.Ok; Exception exception; try { foreach (var p in resources) { if (p.Key.Equals(manager.Key)) continue; // one-way prepare message p.Key.Reference.AsReference() .Prepare(p.Key.Name, transactionInfo.TransactionId, p.Value, transactionInfo.TimeStamp, manager.Key) .Ignore(); } // wait for the TM to commit the transaction status = await manager.Key.Reference.AsReference() .PrepareAndCommit(manager.Key.Name, transactionInfo.TransactionId, manager.Value, transactionInfo.TimeStamp, writeResources, resources.Count); exception = null; } catch (TimeoutException ex) { LogDebugCommitReadWriteTimeout(new(stopwatch), transactionInfo.TransactionId); status = TransactionalStatus.TMResponseTimeout; exception = ex; } catch (Exception ex) { LogDebugCommitReadWriteFailure(new(stopwatch), transactionInfo.TransactionId); LogWarnCommitTransactionFailure(transactionInfo.TransactionId, ex); status = TransactionalStatus.PresumedAbort; exception = ex; } if (status != TransactionalStatus.Ok) { try { LogDebugCommitTransactionFailure(new(stopwatch), transactionInfo.TransactionId, status); // notify participants if (status.DefinitelyAborted()) { await Task.WhenAll(writeResources .Where(p => !p.Equals(manager.Key)) .Select(p => p.Reference.AsReference() .Cancel(p.Name, transactionInfo.TransactionId, transactionInfo.TimeStamp, status))); } } catch (Exception ex) { LogDebugCommitReadWriteFailureAborting(new(stopwatch), transactionInfo.TransactionId, ex); LogWarnFailAbortTransaction(transactionInfo.TransactionId, ex); } } LogTraceFinishTransaction(new(stopwatch), transactionInfo.TransactionId); return (status, exception); } public async Task Abort(TransactionInfo transactionInfo) { this.statistics.TrackTransactionFailed(); List participants = transactionInfo.Participants.Keys.ToList(); LogTraceAbortTransaction(transactionInfo, new(participants)); // send one-way abort messages to release the locks and roll back any updates await Task.WhenAll(participants.Select(p => p.Reference.AsReference() .Abort(p.Name, transactionInfo.TransactionId))); } private void CollateParticipants(Dictionary participants, out List writers, out List> resources, out KeyValuePair? manager) { writers = null; resources = null; manager = null; KeyValuePair? priorityManager = null; foreach (KeyValuePair participant in participants) { ParticipantId id = participant.Key; // priority manager if (id.IsPriorityManager()) { manager = priorityManager = (priorityManager == null) ? participant : throw new ArgumentOutOfRangeException(nameof(participants), "Only one priority transaction manager allowed in transaction"); } // resource if(id.IsResource()) { if(resources == null) { resources = new List>(); } resources.Add(participant); if(participant.Value.Writes > 0) { if (writers == null) { writers = new List(); } writers.Add(id); } } // manager if (manager == null && id.IsManager() && participant.Value.Writes > 0) { manager = participant; } } } private readonly struct StopwatchLogRecord(Stopwatch stopwatch) { public override string ToString() => stopwatch.Elapsed.TotalMilliseconds.ToString("f2"); } private readonly struct DateTimeLogRecord(DateTime ts) { public override string ToString() => ts.ToString("o"); } [LoggerMessage( Level = LogLevel.Trace, Message = "{TotalMilliseconds} start transaction {TransactionId} at {TimeStamp}" )] private partial void LogTraceStartTransaction(StopwatchLogRecord totalMilliseconds, Guid transactionId, DateTimeLogRecord timeStamp); [LoggerMessage( Level = LogLevel.Trace, Message = "{ElapsedMilliseconds} prepare {TransactionInfo}" )] private partial void LogTracePrepareTransaction(StopwatchLogRecord elapsedMilliseconds, TransactionInfo transactionInfo); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} fail {TransactionId} prepare response status={Status}" )] private partial void LogDebugPrepareTransactionFailure(StopwatchLogRecord totalMilliseconds, Guid transactionId, TransactionalStatus status); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} timeout {TransactionId} on CommitReadOnly" )] private partial void LogDebugCommitReadOnlyTimeout(StopwatchLogRecord totalMilliseconds, Guid transactionId); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} failure {TransactionId} CommitReadOnly" )] private partial void LogDebugCommitReadOnlyFailure(StopwatchLogRecord totalMilliseconds, Guid transactionId); [LoggerMessage( Level = LogLevel.Warning, Message = "Unknown error while commiting readonly transaction {TransactionId}" )] private partial void LogWarnCommitReadOnlyFailure(Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} failure aborting {TransactionId} CommitReadOnly" )] private partial void LogDebugCommitReadOnlyFailureAborting(StopwatchLogRecord totalMilliseconds, Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to abort readonly transaction {TransactionId}" )] private partial void LogWarnFailAbortReadonlyTransaction(Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "{ElapsedMilliseconds} finish (reads only) {TransactionId}" )] private partial void LogTraceFinishReadOnlyTransaction(StopwatchLogRecord elapsedMilliseconds, Guid transactionId); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} timeout {TransactionId} on CommitReadWriteTransaction" )] private partial void LogDebugCommitReadWriteTimeout(StopwatchLogRecord totalMilliseconds, Guid transactionId); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} failure {TransactionId} CommitReadWriteTransaction" )] private partial void LogDebugCommitReadWriteFailure(StopwatchLogRecord totalMilliseconds, Guid transactionId); [LoggerMessage( Level = LogLevel.Warning, Message = "Unknown error while committing transaction {TransactionId}" )] private partial void LogWarnCommitTransactionFailure(Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} failed {TransactionId} with status={Status}" )] private partial void LogDebugCommitTransactionFailure(StopwatchLogRecord totalMilliseconds, Guid transactionId, TransactionalStatus status); [LoggerMessage( Level = LogLevel.Debug, Message = "{TotalMilliseconds} failure aborting {TransactionId} CommitReadWriteTransaction" )] private partial void LogDebugCommitReadWriteFailureAborting(StopwatchLogRecord totalMilliseconds, Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to abort transaction {TransactionId}" )] private partial void LogWarnFailAbortTransaction(Guid transactionId, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "{TotalMilliseconds} finish {TransactionId}" )] private partial void LogTraceFinishTransaction(StopwatchLogRecord totalMilliseconds, Guid transactionId); private readonly struct ParticipantsLogRecord(List participants) { public override string ToString() => string.Join(",", participants.Select(p => p.ToString())); } [LoggerMessage( Level = LogLevel.Trace, Message = "Abort {TransactionInfo} {Participants}" )] private partial void LogTraceAbortTransaction(TransactionInfo transactionInfo, ParticipantsLogRecord participants); } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionAgentStatistics.cs ================================================ using System.Diagnostics.Metrics; using System.Threading; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionAgentStatistics : ITransactionAgentStatistics { private static readonly Meter Meter = new("Orleans"); private const string TRANSACTIONS_STARTED = "orleans-transactions-started"; private const string TRANSACTIONS_SUCCESSFUL = "orleans-transactions-successful"; private const string TRANSACTIONS_FAILED = "orleans-transactions-failed"; private const string TRANSACTIONS_THROTTLED = "orleans-transactions-throttled"; private readonly ObservableCounter _transactionsStartedCounter; private readonly ObservableCounter _transactionsSuccessfulCounter; private readonly ObservableCounter _transactionsFailedCounter; private readonly ObservableCounter _transactionsThrottledCounter; private long _transactionsStarted; private long _transactionsSucceeded; private long _transactionsFailed; private long _transactionsThrottled; public TransactionAgentStatistics() { _transactionsStartedCounter = Meter.CreateObservableCounter(TRANSACTIONS_STARTED, () => new(TransactionsStarted)); _transactionsSuccessfulCounter = Meter.CreateObservableCounter(TRANSACTIONS_SUCCESSFUL, () => new(TransactionsSucceeded)); _transactionsFailedCounter = Meter.CreateObservableCounter(TRANSACTIONS_FAILED, () => new(TransactionsFailed)); _transactionsThrottledCounter = Meter.CreateObservableCounter(TRANSACTIONS_THROTTLED, () => new(TransactionsThrottled)); } public long TransactionsStarted => _transactionsStarted; public long TransactionsSucceeded => _transactionsSucceeded; public long TransactionsFailed => _transactionsFailed; public long TransactionsThrottled => _transactionsThrottled; public void TrackTransactionStarted() { Interlocked.Increment(ref _transactionsStarted); } public void TrackTransactionSucceeded() { Interlocked.Increment(ref _transactionsSucceeded); } public void TrackTransactionFailed() { Interlocked.Increment(ref _transactionsFailed); } public void TrackTransactionThrottled() { Interlocked.Increment(ref _transactionsThrottled); } public static ITransactionAgentStatistics Copy(ITransactionAgentStatistics initialStatistics) { return new TransactionAgentStatistics { _transactionsStarted = initialStatistics.TransactionsStarted, _transactionsSucceeded = initialStatistics.TransactionsSucceeded, _transactionsFailed = initialStatistics.TransactionsFailed, _transactionsThrottled = initialStatistics.TransactionsThrottled }; } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionClient.cs ================================================ using System; using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Orleans.Serialization; namespace Orleans.Transactions; internal class TransactionClient : ITransactionClient { private readonly ITransactionAgent _transactionAgent; private readonly Serializer _serializer; public TransactionClient(ITransactionAgent transactionAgent, Serializer serializer) { _transactionAgent = transactionAgent; _serializer = serializer; } public async Task RunTransaction(TransactionOption transactionOption, Func transactionDelegate) { if (transactionDelegate is null) { throw new ArgumentNullException(nameof(transactionDelegate)); } await RunTransaction(transactionOption, async () => { await transactionDelegate(); return true; }); } public async Task RunTransaction(TransactionOption transactionOption, Func> transactionDelegate) { if (transactionDelegate is null) { throw new ArgumentNullException(nameof(transactionDelegate)); } // Pick up ambient transaction context var ambientTransactionInfo = TransactionContext.GetTransactionInfo(); if (ambientTransactionInfo is not null && transactionOption == TransactionOption.Suppress) { throw new NotSupportedException("Delegate cannot be executed within a transaction."); } if (ambientTransactionInfo is null && transactionOption == TransactionOption.Join) { throw new NotSupportedException("Delegate cannot be executed outside of a transaction."); } try { switch (transactionOption) { case TransactionOption.Create: await RunDelegateWithTransaction(null, transactionDelegate); break; case TransactionOption.Join: await RunDelegateWithTransaction(ambientTransactionInfo, transactionDelegate); break; case TransactionOption.CreateOrJoin: await RunDelegateWithTransaction(ambientTransactionInfo, transactionDelegate); break; case TransactionOption.Suppress: await RunDelegateWithSupressedTransaction(ambientTransactionInfo, transactionDelegate); break; case TransactionOption.Supported: await RunDelegateWithSupportedTransaction(ambientTransactionInfo, transactionDelegate); break; case TransactionOption.NotAllowed: await RunDelegateWithDisallowedTransaction(ambientTransactionInfo, transactionDelegate); break; default: throw new ArgumentOutOfRangeException(nameof(transactionOption), $"{transactionOption} is not supported"); } } finally { // Restore ambient transaction context, if any TransactionContext.SetTransactionInfo(ambientTransactionInfo); } } private static async Task RunDelegateWithDisallowedTransaction(TransactionInfo ambientTransactionInfo, Func> transactionDelegate) { if (ambientTransactionInfo is not null) { // No transaction is allowed within delegate throw new NotSupportedException("Delegate cannot be executed within a transaction."); } // Run delegate _ = await transactionDelegate(); } private static async Task RunDelegateWithSupportedTransaction(TransactionInfo ambientTransactionInfo, Func> transactionDelegate) { if (ambientTransactionInfo is null) { // Run delegate _ = await transactionDelegate(); } else { // Run delegate ambientTransactionInfo.TryToCommit = await transactionDelegate(); } } private static async Task RunDelegateWithSupressedTransaction(TransactionInfo ambientTransactionInfo, Func> transactionDelegate) { // Clear transaction context TransactionContext.Clear(); if (ambientTransactionInfo is null) { // Run delegate _ = await transactionDelegate(); } else { // Run delegate ambientTransactionInfo.TryToCommit = await transactionDelegate(); } } private async Task RunDelegateWithTransaction(TransactionInfo ambientTransactionInfo, Func> transactionDelegate) { TransactionInfo transactionInfo; if (ambientTransactionInfo is null) { // TODO: this should be a configurable parameter var transactionTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(30) : TimeSpan.FromSeconds(10); // Start transaction transactionInfo = await _transactionAgent.StartTransaction(readOnly: false, transactionTimeout); } else { // Fork ambient transaction transactionInfo = ambientTransactionInfo.Fork(); } // Set transaction context TransactionContext.SetTransactionInfo(transactionInfo); try { // Run delegate transactionInfo.TryToCommit = await transactionDelegate(); } catch (Exception exception) { // Record exception with transaction transactionInfo.RecordException(exception, _serializer); } // Gather pending actions into transaction transactionInfo.ReconcilePending(); if (ambientTransactionInfo is null) { // Finalize transaction since there is no ambient transaction to join await FinalizeTransaction(transactionInfo); } else { // Join transaction with ambient transaction ambientTransactionInfo.Join(transactionInfo); } } private async Task FinalizeTransaction(TransactionInfo transactionInfo) { // Prepare for exception, if any OrleansTransactionException transactionException; // Check if transaction is pending for abort transactionException = transactionInfo.MustAbort(_serializer); if (transactionException is not null || transactionInfo.TryToCommit is false) { // Transaction is pending for abort await _transactionAgent.Abort(transactionInfo); } else { // Try to resolve transaction var (status, exception) = await _transactionAgent.Resolve(transactionInfo); if (status != TransactionalStatus.Ok) { // Resolving transaction failed transactionException = status.ConvertToUserException(transactionInfo.Id, exception); ExceptionDispatchInfo.SetCurrentStackTrace(transactionException); } } if (transactionException != null) { // Transaction failed - bubble up exception ExceptionDispatchInfo.Throw(transactionException); } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionInfo.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using Orleans.Serialization; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { [GenerateSerializer] public sealed class TransactionInfo { public TransactionInfo() { this.Participants = new Dictionary(ParticipantId.Comparer); this.joined = new ConcurrentQueue(); } public TransactionInfo(Guid id, DateTime timeStamp, DateTime priority, bool readOnly = false) : this() { this.TransactionId = id; this.IsReadOnly = readOnly; this.TimeStamp = timeStamp; this.Priority = priority; } /// /// Constructor used when TransactionInfo is transferred to a request /// /// public TransactionInfo(TransactionInfo other) : this() { this.TransactionId = other.TransactionId; this.TryToCommit = other.TryToCommit; this.IsReadOnly = other.IsReadOnly; this.TimeStamp = other.TimeStamp; this.Priority = other.Priority; } public string Id => TransactionId.ToString(); [Id(0)] public Guid TransactionId { get; } [Id(1)] public DateTime TimeStamp { get; set; } [Id(2)] public DateTime Priority { get; set; } [Id(3)] public bool IsReadOnly { get; } [Id(4)] public byte[] OriginalException { get; set; } // counts how many writes were done per each accessed resource // zero means the resource was only read [Id(5)] public Dictionary Participants { get; } [Id(6)] public bool TryToCommit { get; internal set; } = true; [NonSerialized] public int PendingCalls; [NonSerialized] private readonly ConcurrentQueue joined; public TransactionInfo Fork() { Interlocked.Increment(ref PendingCalls); return new TransactionInfo(this); } public void Join(TransactionInfo x) { joined.Enqueue(x); } public OrleansTransactionAbortedException MustAbort(Serializer serializer) { if (OriginalException != null) { return serializer.Deserialize(OriginalException); } else if (PendingCalls != 0) { return new OrleansOrphanCallException(TransactionId.ToString(), PendingCalls); } else { return null; } } public void RecordException(Exception e, Serializer sm) { if (OriginalException == null) { var exception = (e as OrleansTransactionAbortedException) ?? new OrleansTransactionAbortedException(TransactionId.ToString(), e); OriginalException = sm.SerializeToArray(exception); } } /// /// Reconciles all pending calls that have join the transaction. /// /// true if there are no orphans, false otherwise public void ReconcilePending() { TransactionInfo transactionInfo; while (this.joined.TryDequeue(out transactionInfo)) { Union(transactionInfo); PendingCalls--; } } private void Union(TransactionInfo other) { if (OriginalException == null) { OriginalException = other.OriginalException; } // Take sum of write counts foreach (KeyValuePair participant in other.Participants) { if (!this.Participants.TryGetValue(participant.Key, out var existing)) { this.Participants[participant.Key] = participant.Value; } else { this.Participants[participant.Key] = existing + participant.Value; } } // take max of timestamp if (TimeStamp < other.TimeStamp) TimeStamp = other.TimeStamp; // take commit pending flag if (TryToCommit) TryToCommit = other.TryToCommit; } public void RecordRead(ParticipantId id, DateTime minTime) { this.Participants.TryGetValue(id, out AccessCounter count); count.Reads++; this.Participants[id] = count; if (minTime > TimeStamp) { TimeStamp = minTime; } } public void RecordWrite(ParticipantId id, DateTime minTime) { this.Participants.TryGetValue(id, out AccessCounter count); count.Writes++; this.Participants[id] = count; if (minTime > TimeStamp) { TimeStamp = minTime; } } /// /// For verbose tracing and debugging. /// public override string ToString() { return string.Join("", $"{TransactionId} {TimeStamp:o}", (IsReadOnly ? " RO" : ""), (TryToCommit ? " Committing" : ""), (OriginalException != null ? " Aborting" : ""), $" {{{string.Join(" ", this.Participants.Select(kvp => $"{kvp.Key}:{kvp.Value.Reads},{kvp.Value.Writes}"))}}}" ); } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionManagerExtension.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionManagerExtension : ITransactionManagerExtension { private readonly ResourceFactoryRegistry factories; private readonly Dictionary managers; public TransactionManagerExtension(IGrainContextAccessor contextAccessor) { this.factories = contextAccessor.GrainContext.GetResourceFactoryRegistry(); this.managers = new Dictionary(); } public Task Ping(string resourceId, Guid transactionId, DateTime timeStamp, ParticipantId resource) { return GetManager(resourceId).Ping(transactionId, timeStamp, resource); } public Task PrepareAndCommit(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp, List writeResources, int totalResources) { return GetManager(resourceId).PrepareAndCommit(transactionId, accessCount, timeStamp, writeResources, totalResources); } public Task Prepared(string resourceId, Guid transactionId, DateTime timestamp, ParticipantId resource, TransactionalStatus status) { return GetManager(resourceId).Prepared(transactionId, timestamp, resource, status); } private ITransactionManager GetManager(string resourceId) { if (!this.managers.TryGetValue(resourceId, out ITransactionManager manager)) { this.managers[resourceId] = manager = this.factories[resourceId].Invoke(); } return manager; } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionOverloadDetector.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Internal.Trasactions; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public interface ITransactionOverloadDetector { bool IsOverloaded(); } /// /// Options for load shedding based on transaction rate /// public class TransactionRateLoadSheddingOptions { /// /// whether to turn on transaction load shedding. Default to false; /// public bool Enabled { get; set; } /// /// Default load shedding limit /// public const double DEFAULT_LIMIT = 700; /// /// Load shedding limit for transaction /// public double Limit { get; set; } = DEFAULT_LIMIT; } public class TransactionOverloadDetector : ITransactionOverloadDetector { private readonly ITransactionAgentStatistics statistics; private readonly TransactionRateLoadSheddingOptions options; private readonly PeriodicAction monitor; private ITransactionAgentStatistics lastStatistics; private double transactionStartedPerSecond; private DateTime lastCheckTime; private static readonly TimeSpan MetricsCheck = TimeSpan.FromSeconds(15); public TransactionOverloadDetector(ITransactionAgentStatistics statistics, IOptions options) { this.statistics = statistics; this.options = options.Value; this.monitor = new PeriodicAction(MetricsCheck, this.RecordStatistics); this.lastStatistics = TransactionAgentStatistics.Copy(statistics); this.lastCheckTime = DateTime.UtcNow; } private void RecordStatistics() { ITransactionAgentStatistics current = TransactionAgentStatistics.Copy(this.statistics); DateTime now = DateTime.UtcNow; this.transactionStartedPerSecond = CalculateTps(this.lastStatistics.TransactionsStarted, this.lastCheckTime, current.TransactionsStarted, now); this.lastStatistics = current; this.lastCheckTime = now; } public bool IsOverloaded() { if (!this.options.Enabled) return false; DateTime now = DateTime.UtcNow; this.monitor.TryAction(now); double txPerSecondCurrently = CalculateTps(this.lastStatistics.TransactionsStarted, this.lastCheckTime, this.statistics.TransactionsStarted, now); //decaying utilization for tx per second var aggregratedTxPerSecond = (this.transactionStartedPerSecond + (2.0 * txPerSecondCurrently)) / 3.0; return aggregratedTxPerSecond > this.options.Limit; } private static double CalculateTps(long startCounter, DateTime startTimeUtc, long currentCounter, DateTime curentTimeUtc) { TimeSpan deltaTime = curentTimeUtc - startTimeUtc; long deltaCounter = currentCounter - startCounter; return (deltaTime.TotalMilliseconds < 1000) ? deltaCounter : (deltaCounter * 1000.0) / deltaTime.TotalMilliseconds; } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionRecord.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Transactions { /// /// Each participant plays a particular role in the commit protocol /// internal enum CommitRole { NotYetDetermined, // role is known only when prepare message is received from TA ReadOnly, // this participant has not written RemoteCommit, // this participant has written, but is not the TM LocalCommit, // this participant has written, and is the TM } /// /// Record that is kept for each transaction at each participant /// /// The type of state internal class TransactionRecord { public TransactionRecord() { } // a unique identifier for this transaction public Guid TransactionId; // the time at which this transaction was started on the TA public DateTime Priority; // a deadline for the transaction to complete successfully, set by the TA public DateTime Deadline; // the transaction timestamp as computed by the algorithm public DateTime Timestamp; // the number of reads and writes that this transaction has performed on this transactional participant public int NumberReads; public int NumberWrites; // the state for this transaction, and the sequence number of this state public TState State; public long SequenceNumber; public bool HasCopiedState; public void AddRead() { NumberReads++; } public void AddWrite() { NumberWrites++; } public CommitRole Role; // used for readonly and local commit public TaskCompletionSource PromiseForTA; // used for local and remote commit public ParticipantId TransactionManager; // used for local commit public List WriteParticipants; public int WaitCount; public DateTime WaitingSince; // used for remote commit public DateTime? LastSent; public bool PrepareIsPersisted; public TaskCompletionSource ConfirmationResponsePromise; /// /// Indicates whether a transaction record is ready to commit /// public bool ReadyToCommit { get { switch (Role) { case CommitRole.ReadOnly: return true; case CommitRole.LocalCommit: return WaitCount == 0; // received all "Prepared" messages case CommitRole.RemoteCommit: return (ConfirmationResponsePromise != null) // TM has sent confirm and is waiting for response || (NumberWrites == 0 && LastSent.HasValue); // this participant did not write and finished prepare default: throw new NotSupportedException($"{Role} is not a supported CommitRole."); } } } public bool IsReadOnly { get { switch (Role) { case CommitRole.ReadOnly: return true; case CommitRole.LocalCommit: return false; case CommitRole.RemoteCommit: return NumberWrites == 0; default: throw new NotSupportedException($"{Role} is not a supported CommitRole."); } } } public bool Batchable { get { switch (Role) { case CommitRole.ReadOnly: case CommitRole.LocalCommit: return true; case CommitRole.RemoteCommit: return NumberWrites == 0; default: throw new NotImplementedException(); } } } // formatted for debugging commit queue contents public override string ToString() { switch (Role) { case CommitRole.NotYetDetermined: return $"ND tid={TransactionId} v{SequenceNumber}"; case CommitRole.ReadOnly: return $"RE tid={TransactionId} v{SequenceNumber}"; case CommitRole.LocalCommit: return $"LCE tid={TransactionId} v{SequenceNumber} wc={WaitCount} rtb={ReadyToCommit}"; case CommitRole.RemoteCommit: return $"RCE tid={TransactionId} v{SequenceNumber} pip={PrepareIsPersisted} ls={LastSent.HasValue} ro={IsReadOnly} rtb={ReadyToCommit} tm={TransactionManager}"; default: throw new NotSupportedException($"{Role} is not a supported CommitRole."); } } } } ================================================ FILE: src/Orleans.Transactions/DistributedTM/TransactionalResourceExtension.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionalResourceExtension : ITransactionalResourceExtension { private readonly ResourceFactoryRegistry factories; private readonly Dictionary resources; public TransactionalResourceExtension(IGrainContextAccessor contextAccessor) { this.factories = contextAccessor.GrainContext.GetResourceFactoryRegistry(); this.resources = new Dictionary(); } public Task CommitReadOnly(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp) { return GetResource(resourceId).CommitReadOnly(transactionId, accessCount, timeStamp); } public Task Abort(string resourceId, Guid transactionId) { return GetResource(resourceId).Abort(transactionId); } public Task Cancel(string resourceId, Guid transactionId, DateTime timeStamp, TransactionalStatus status) { return GetResource(resourceId).Cancel(transactionId, timeStamp, status); } public Task Confirm(string resourceId, Guid transactionId, DateTime timeStamp) { return GetResource(resourceId).Confirm(transactionId, timeStamp); } public Task Prepare(string resourceId, Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager) { return GetResource(resourceId).Prepare(transactionId, accessCount, timeStamp, transactionManager); } private ITransactionalResource GetResource(string resourceId) { if (!this.resources.TryGetValue(resourceId, out ITransactionalResource resource)) { this.resources[resourceId] = resource = this.factories[resourceId].Invoke(); } return resource; } } } ================================================ FILE: src/Orleans.Transactions/ErrorCodes.cs ================================================  namespace Orleans.Transactions { /// /// Orleans Transactions error codes /// internal enum OrleansTransactionsErrorCode { /// /// Start of orleans transactions error codes /// OrleansTransactions = 1 << 17, // TODO - jbragg - add error codes for transaction errors } } ================================================ FILE: src/Orleans.Transactions/Hosting/ClientBuilderExtensions.cs ================================================ namespace Orleans.Hosting { public static class ClientBuilderExtensions { public static IClientBuilder UseTransactions(this IClientBuilder builder) => builder.ConfigureServices(services => services.UseTransactionsWithClient()); } } ================================================ FILE: src/Orleans.Transactions/Hosting/DefaultTransactionDataCopier.cs ================================================ using Orleans.Serialization; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class DefaultTransactionDataCopier : ITransactionDataCopier { private readonly DeepCopier deepCopier; public DefaultTransactionDataCopier(DeepCopier deepCopier) { this.deepCopier = deepCopier; } public TData DeepCopy(TData original) { return (TData)this.deepCopier.Copy(original); } } } ================================================ FILE: src/Orleans.Transactions/Hosting/SiloBuilderExtensions.cs ================================================ using Orleans.Transactions; using Orleans.Transactions.Abstractions; namespace Orleans.Hosting; public static class SiloBuilderExtensions { /// /// Configure cluster to use the distributed TM algorithm /// /// Silo host builder /// The silo builder. public static ISiloBuilder UseTransactions(this ISiloBuilder builder) { return builder.ConfigureServices(services => services.UseTransactionsWithSilo()) .AddGrainExtension() .AddGrainExtension(); } } ================================================ FILE: src/Orleans.Transactions/Hosting/TransactionCommitterAttributeMapper.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using System.Reflection; namespace Orleans.Transactions { internal class TransactionCommitterAttributeMapper : IAttributeToFactoryMapper { private static readonly MethodInfo create = typeof(ITransactionCommitterFactory).GetMethod("Create"); public Factory GetFactory(ParameterInfo parameter, TransactionCommitterAttribute attribute) { TransactionCommitterAttribute config = attribute; // use generic type args to define collection type. MethodInfo genericCreate = create.MakeGenericMethod(parameter.ParameterType.GetGenericArguments()); object[] args = new object[] { config }; return context => Create(context, genericCreate, args); } private static object Create(IGrainContext context, MethodInfo genericCreate, object[] args) { ITransactionCommitterFactory factory = context.ActivationServices.GetRequiredService(); return genericCreate.Invoke(factory, args); } } } ================================================ FILE: src/Orleans.Transactions/Hosting/TransactionalStateAttributeMapper.cs ================================================ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionalStateAttributeMapper : TransactionalStateAttributeMapper { protected override TransactionalStateConfiguration AttributeToConfig(TransactionalStateAttribute attribute) { return new TransactionalStateConfiguration(attribute); } } public abstract class TransactionalStateAttributeMapper : IAttributeToFactoryMapper where TAttribute : IFacetMetadata, ITransactionalStateConfiguration { private static readonly MethodInfo create = typeof(ITransactionalStateFactory).GetMethod("Create"); public Factory GetFactory(ParameterInfo parameter, TAttribute attribute) { TransactionalStateConfiguration config = AttributeToConfig(attribute); // use generic type args to define collection type. MethodInfo genericCreate = create.MakeGenericMethod(parameter.ParameterType.GetGenericArguments()); object[] args = new object[] { config }; return context => Create(context, genericCreate, args); } private object Create(IGrainContext context, MethodInfo genericCreate, object[] args) { ITransactionalStateFactory factory = context.ActivationServices.GetRequiredService(); return genericCreate.Invoke(factory, args); } protected abstract TransactionalStateConfiguration AttributeToConfig(TAttribute attribute); } } ================================================ FILE: src/Orleans.Transactions/Hosting/TransactionsServiceCollectionExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection.Extensions; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions; using Microsoft.Extensions.DependencyInjection; namespace Orleans.Hosting { /// /// extensions. /// public static class TransactionsServiceCollectionExtensions { internal static IServiceCollection UseTransactionsWithSilo(this IServiceCollection services) { services.AddTransactionsBaseline(); services.TryAddSingleton(typeof(ITransactionDataCopier<>), typeof(DefaultTransactionDataCopier<>)); services.AddSingleton, TransactionalStateAttributeMapper>(); services.TryAddTransient(); services.AddSingleton, TransactionCommitterAttributeMapper>(); services.TryAddTransient(); services.TryAddTransient(); services.AddTransient(typeof(ITransactionalState<>), typeof(TransactionalState<>)); return services; } internal static IServiceCollection UseTransactionsWithClient(this IServiceCollection services) => services.AddTransactionsBaseline(); internal static IServiceCollection AddTransactionsBaseline(this IServiceCollection services) { services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); return services; } } } ================================================ FILE: src/Orleans.Transactions/ITransactionAgent.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans.Transactions { /// /// The Transaction Agent it is used by the silo and activations to /// interact with the transactions system. /// /// /// There is one Transaction Agent per silo. /// TODO: does this belong in Runtime instead? /// public interface ITransactionAgent { /// /// Starts a new transaction /// /// Whether it is a read-only transaction /// Transaction is automatically aborted if it does not complete within this time /// Info of the new transaction Task StartTransaction(bool readOnly, TimeSpan timeout); /// /// Attempt to Resolve a transaction. Will commit or abort transaction /// /// transaction info /// null if the transaction committed successfully, or an exception otherwise. /// If the exception is OrleansTransactionInDoubtException, it means the outcome of the Commit cannot be determined; otherwise, /// the transaction is guaranteed to not have taken effect. Task<(TransactionalStatus Status, Exception exception)> Resolve(TransactionInfo transactionInfo); /// /// Abort a transaction. /// /// /// None. /// This method is exception-free Task Abort(TransactionInfo transactionInfo); } } ================================================ FILE: src/Orleans.Transactions/ITransactionClient.cs ================================================ using System; using System.Threading.Tasks; namespace Orleans; public interface ITransactionClient { /// /// Run transaction delegate /// /// /// /// /// Transaction always commit, unless an exception is thrown from the delegate and depending on Task RunTransaction(TransactionOption transactionOption, Func transactionDelegate); /// /// Run transaction delegate /// /// /// /// True if the transaction should commit Task RunTransaction(TransactionOption transactionOption, Func> transactionDelegate); } ================================================ FILE: src/Orleans.Transactions/Orleans.Transactions.csproj ================================================ Microsoft.Orleans.Transactions Microsoft Orleans Transactions support Core Transaction library of Microsoft Orleans used on the server. $(PackageTags) Transactions $(DefaultTargetFrameworks) Orleans.Transactions Orleans.Transactions true ================================================ FILE: src/Orleans.Transactions/OrleansTransactionException.cs ================================================ using Orleans.Runtime; using System; using System.Runtime.Serialization; namespace Orleans.Transactions { /// /// Base class for all transaction exceptions /// [Serializable] [GenerateSerializer] public class OrleansTransactionException : OrleansException { public OrleansTransactionException() : base("Orleans transaction error.") { } public OrleansTransactionException(string message) : base(message) { } public OrleansTransactionException(string message, Exception innerException) : base(message, innerException) { } [Obsolete] protected OrleansTransactionException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Orleans transactions are disabled. /// [Serializable] [GenerateSerializer] public sealed class OrleansTransactionsDisabledException : OrleansTransactionException { public OrleansTransactionsDisabledException() : base("Orleans transactions have not been enabled. Transactions are disabled by default and must be configured to be used.") { } [Obsolete] private OrleansTransactionsDisabledException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the runtime was unable to start a transaction. /// [Serializable] [GenerateSerializer] public sealed class OrleansStartTransactionFailedException : OrleansTransactionException { public OrleansStartTransactionFailedException(Exception innerException) : base("Failed to start transaction. Check InnerException for details", innerException) { } [Obsolete] private OrleansStartTransactionFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that transaction runtime is overloaded /// [Serializable] [GenerateSerializer] public sealed class OrleansTransactionOverloadException : OrleansTransactionException { public OrleansTransactionOverloadException() : base("Transaction is overloaded on current silo, please try again later.") { } } /// /// Signifies that the runtime is unable to determine whether a transaction /// has committed. /// [Serializable] [GenerateSerializer] public sealed class OrleansTransactionInDoubtException : OrleansTransactionException { [Id(0)] public string TransactionId { get; private set; } public OrleansTransactionInDoubtException(string transactionId) : base(string.Format("Transaction {0} is InDoubt", transactionId)) { this.TransactionId = transactionId; } public OrleansTransactionInDoubtException(string transactionId, Exception exc) : base(string.Format("Transaction {0} is InDoubt", transactionId), exc) { this.TransactionId = transactionId; } public OrleansTransactionInDoubtException(string transactionId, string msg, Exception innerException) : base(string.Format("Transaction {0} is InDoubt: {1}", transactionId, msg), innerException) { this.TransactionId = transactionId; } [Obsolete] private OrleansTransactionInDoubtException(SerializationInfo info, StreamingContext context) : base(info, context) { this.TransactionId = info.GetString(nameof(this.TransactionId)); } [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(this.TransactionId), this.TransactionId); } } /// /// Signifies that the executing transaction has aborted. /// [Serializable] [GenerateSerializer] public class OrleansTransactionAbortedException : OrleansTransactionException { /// /// The unique identifier of the aborted transaction. /// [Id(0)] public string TransactionId { get; private set; } public OrleansTransactionAbortedException(string transactionId, string msg, Exception innerException) : base(msg, innerException) { this.TransactionId = transactionId; } public OrleansTransactionAbortedException(string transactionId, string msg) : base(msg) { this.TransactionId = transactionId; } public OrleansTransactionAbortedException(string transactionId, Exception innerException) : base($"Transaction {transactionId} Aborted because of an unhandled exception in a grain method call. See InnerException for details.", innerException) { TransactionId = transactionId; } [Obsolete] protected OrleansTransactionAbortedException(SerializationInfo info, StreamingContext context) : base(info, context) { this.TransactionId = info.GetString(nameof(this.TransactionId)); } [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(this.TransactionId), this.TransactionId); } } /// /// Signifies that the executing transaction has aborted because a dependent transaction aborted. /// [Serializable] [GenerateSerializer] public sealed class OrleansCascadingAbortException : OrleansTransactionTransientFailureException { [Id(0)] public string DependentTransactionId { get; private set; } public OrleansCascadingAbortException(string transactionId, string dependentId) : base(transactionId, string.Format("Transaction {0} aborted because its dependent transaction {1} aborted", transactionId, dependentId)) { this.DependentTransactionId = dependentId; } public OrleansCascadingAbortException(string transactionId) : base(transactionId, string.Format("Transaction {0} aborted because a dependent transaction aborted", transactionId)) { } public OrleansCascadingAbortException(string transactionId, Exception innerException) : base(transactionId, string.Format("Transaction {0} aborted because a dependent transaction aborted", transactionId), innerException) { } [Obsolete] private OrleansCascadingAbortException(SerializationInfo info, StreamingContext context) : base(info, context) { this.DependentTransactionId = info.GetString(nameof(this.DependentTransactionId)); } [Obsolete] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(this.DependentTransactionId), this.DependentTransactionId); } } /// /// Signifies that the executing transaction has aborted because a method did not await all its pending calls. /// [Serializable] [GenerateSerializer] public sealed class OrleansOrphanCallException : OrleansTransactionAbortedException { public OrleansOrphanCallException(string transactionId, int pendingCalls) : base( transactionId, $"Transaction {transactionId} aborted because method did not await all its outstanding calls ({pendingCalls})") { } [Obsolete] private OrleansOrphanCallException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the executing read-only transaction has aborted because it attempted to write to a grain. /// [Serializable] [GenerateSerializer] public sealed class OrleansReadOnlyViolatedException : OrleansTransactionAbortedException { public OrleansReadOnlyViolatedException(string transactionId) : base(transactionId, string.Format("Transaction {0} aborted because it attempted to write a grain", transactionId)) { } [Obsolete] private OrleansReadOnlyViolatedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } [Serializable] [GenerateSerializer] public sealed class OrleansTransactionServiceNotAvailableException : OrleansTransactionException { public OrleansTransactionServiceNotAvailableException() : base("Transaction service not available") { } [Obsolete] private OrleansTransactionServiceNotAvailableException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the executing transaction has aborted because its execution lock was broken /// [Serializable] [GenerateSerializer] public sealed class OrleansBrokenTransactionLockException : OrleansTransactionTransientFailureException { public OrleansBrokenTransactionLockException(string transactionId, string situation) : base(transactionId, $"Transaction {transactionId} aborted because a broken lock was detected, {situation}") { } public OrleansBrokenTransactionLockException(string transactionId, string situation, Exception innerException) : base(transactionId, $"Transaction {transactionId} aborted because a broken lock was detected, {situation}", innerException) { } [Obsolete] private OrleansBrokenTransactionLockException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the executing transaction has aborted because it could not upgrade some lock /// [Serializable] [GenerateSerializer] public sealed class OrleansTransactionLockUpgradeException : OrleansTransactionTransientFailureException { public OrleansTransactionLockUpgradeException(string transactionId) : base(transactionId, $"Transaction {transactionId} Aborted because it could not upgrade a lock, because of a higher-priority conflicting transaction") { } [Obsolete] private OrleansTransactionLockUpgradeException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the executing transaction has aborted because the TM did not receive all prepared messages in time /// [Serializable] [GenerateSerializer] public sealed class OrleansTransactionPrepareTimeoutException : OrleansTransactionTransientFailureException { public OrleansTransactionPrepareTimeoutException(string transactionId, Exception innerException) : base(transactionId, $"Transaction {transactionId} Aborted because the prepare phase did not complete within the timeout limit", innerException) { } [Obsolete] private OrleansTransactionPrepareTimeoutException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Signifies that the executing transaction has aborted because some possibly transient problem, such as internal /// timeouts for locks or protocol responses, or speculation failures. /// [Serializable] [GenerateSerializer] public class OrleansTransactionTransientFailureException : OrleansTransactionAbortedException { public OrleansTransactionTransientFailureException(string transactionId, string msg, Exception innerException) : base(transactionId, msg, innerException) { } public OrleansTransactionTransientFailureException(string transactionId, string msg) : base(transactionId, msg) { } [Obsolete] protected OrleansTransactionTransientFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Transactions/State/ActivationLifetime.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Orleans.Runtime; namespace Orleans.Transactions.State { internal class ActivationLifetime : IActivationLifetime, ILifecycleObserver { private readonly CancellationTokenSource onDeactivating = new CancellationTokenSource(); private int pendingDeactivationLocks; public ActivationLifetime(IGrainContext activationContext) { activationContext.ObservableLifecycle.Subscribe(GrainLifecycleStage.First, this); activationContext.ObservableLifecycle.Subscribe(GrainLifecycleStage.Last, this); } public CancellationToken OnDeactivating => this.onDeactivating.Token; public Task OnStart(CancellationToken ct) => Task.CompletedTask; public Task OnStop(CancellationToken ct) { this.onDeactivating.Cancel(throwOnFirstException: false); if (!ct.IsCancellationRequested && pendingDeactivationLocks > 0) { return OnStopAsync(ct); } return Task.CompletedTask; } private async Task OnStopAsync(CancellationToken ct) { var startTime = DateTime.UtcNow; var maxTime = TimeSpan.FromSeconds(5); while (!ct.IsCancellationRequested && pendingDeactivationLocks > 0 && DateTime.UtcNow - startTime < maxTime) { await Task.Delay(TimeSpan.FromMilliseconds(10)); } } public IDisposable BlockDeactivation() => new BlockDeactivationDisposable(this); private class BlockDeactivationDisposable : IDisposable { private readonly ActivationLifetime owner; public BlockDeactivationDisposable(ActivationLifetime owner) { this.owner = owner; Interlocked.Increment(ref owner.pendingDeactivationLocks); } public void Dispose() { Interlocked.Decrement(ref owner.pendingDeactivationLocks); } } } } ================================================ FILE: src/Orleans.Transactions/State/ConfirmationWorker.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Timers.Internal; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.State { internal partial class ConfirmationWorker where TState : class, new() { private readonly TransactionalStateOptions options; private readonly ParticipantId me; private readonly BatchWorker storageWorker; private readonly Func> getStorageBatch; private readonly ILogger logger; private readonly ITimerManager timerManager; private readonly IActivationLifetime activationLifetime; private readonly HashSet pending; public ConfirmationWorker( IOptions options, ParticipantId me, BatchWorker storageWorker, Func> getStorageBatch, ILogger logger, ITimerManager timerManager, IActivationLifetime activationLifetime) { this.options = options.Value; this.me = me; this.storageWorker = storageWorker; this.getStorageBatch = getStorageBatch; this.logger = logger; this.timerManager = timerManager; this.activationLifetime = activationLifetime; this.pending = new HashSet(); } public void Add(Guid transactionId, DateTime timestamp, List participants) { if (!IsConfirmed(transactionId)) { this.pending.Add(transactionId); SendConfirmation(transactionId, timestamp, participants).Ignore(); } } public bool IsConfirmed(Guid transactionId) { return this.pending.Contains(transactionId); } private async Task SendConfirmation(Guid transactionId, DateTime timestamp, List participants) { await NotifyAll(transactionId, timestamp, participants); await Collect(transactionId); } private async Task NotifyAll(Guid transactionId, DateTime timestamp, List participants) { List confirmations = participants .Where(p => !p.Equals(this.me)) .Select(p => new Confirmation( p, transactionId, timestamp, () => p.Reference.AsReference() .Confirm(p.Name, transactionId, timestamp), this.logger)) .ToList(); if (confirmations.Count == 0) return; // attempts to confirm all, will retry every ConfirmationRetryDelay until all succeed var ct = this.activationLifetime.OnDeactivating; bool hasPendingConfirmations = true; while (!ct.IsCancellationRequested && hasPendingConfirmations) { using (this.activationLifetime.BlockDeactivation()) { var confirmationResults = await Task.WhenAll(confirmations.Select(c => c.Confirmed())); hasPendingConfirmations = false; foreach (var confirmed in confirmationResults) { if (!confirmed) { hasPendingConfirmations = true; await this.timerManager.Delay(this.options.ConfirmationRetryDelay, ct); break; } } } } } // retries collect until it succeeds private async Task Collect(Guid transactionId) { var ct = this.activationLifetime.OnDeactivating; while (!ct.IsCancellationRequested) { using (this.activationLifetime.BlockDeactivation()) { if (await TryCollect(transactionId)) break; await this.timerManager.Delay(this.options.ConfirmationRetryDelay, ct); } } } // attempt to clear transaction from commit log private async Task TryCollect(Guid transactionId) { try { var storeComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // Now we can remove the commit record. StorageBatch storageBatch = getStorageBatch(); storageBatch.Collect(transactionId); storageBatch.FollowUpAction(() => { LogTraceCollectionCompleted(transactionId); this.pending.Remove(transactionId); storeComplete.TrySetResult(true); }); storageWorker.Notify(); // wait for storage call, so we don't free spin return await storeComplete.Task; } catch(Exception ex) { LogWarnCollectingTransaction(transactionId, ex); } return false; } // Tracks the effort to notify a participant, will not call again once it succeeds. private struct Confirmation { private readonly ParticipantId participant; private readonly Guid transactionId; private readonly DateTime timestamp; private readonly Func call; private readonly ILogger logger; private Task pending; private bool complete; public Confirmation(ParticipantId paricipant, Guid transactionId, DateTime timestamp, Func call, ILogger logger) { this.participant = paricipant; this.transactionId = transactionId; this.timestamp = timestamp; this.call = call; this.logger = logger; this.pending = null; this.complete = false; } public async Task Confirmed() { if (this.complete) return this.complete; this.pending = this.pending ?? call(); try { await this.pending; this.complete = true; } catch (Exception ex) { this.pending = null; LogWarningConfirmationFailed(this.logger, this.transactionId, this.timestamp, this.participant, ex); } return this.complete; } } [LoggerMessage( Level = LogLevel.Trace, Message = "Collection completed. TransactionId:{TransactionId}" )] private partial void LogTraceCollectionCompleted(Guid transactionId); [LoggerMessage( Level = LogLevel.Warning, Message = "Error occured while cleaning up transaction {TransactionId} from commit log. Will retry." )] private partial void LogWarnCollectingTransaction(Guid transactionId, Exception ex); [LoggerMessage( Level = LogLevel.Warning, Message = "Confirmation of transaction {TransactionId} with timestamp {Timestamp} to participant {Participant} failed. Retrying" )] private static partial void LogWarningConfirmationFailed(ILogger logger, Guid transactionId, DateTime timestamp, ParticipantId participant, Exception ex); } } ================================================ FILE: src/Orleans.Transactions/State/IActivationLifetime.cs ================================================ using System; using System.Threading; namespace Orleans.Transactions.State { internal interface IActivationLifetime { CancellationToken OnDeactivating { get; } IDisposable BlockDeactivation(); } } ================================================ FILE: src/Orleans.Transactions/State/NamedTransactionalStateStorageFactory.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Storage; namespace Orleans.Transactions { public class NamedTransactionalStateStorageFactory : INamedTransactionalStateStorageFactory { private readonly IGrainContextAccessor contextAccessor; [Obsolete("Use the NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor) constructor.")] public NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : this(contextAccessor) { } public NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; } public ITransactionalStateStorage Create(string storageName, string stateName) where TState : class, new() { var currentContext = this.contextAccessor.GrainContext; // Try to get ITransactionalStateStorage from factory ITransactionalStateStorageFactory factory = string.IsNullOrEmpty(storageName) ? currentContext.ActivationServices.GetService() : currentContext.ActivationServices.GetKeyedService(storageName); if (factory != null) return factory.Create(stateName, currentContext); // Else try to get storage provider and wrap it IGrainStorage grainStorage = string.IsNullOrEmpty(storageName) ? currentContext.ActivationServices.GetService() : currentContext.ActivationServices.GetKeyedService(storageName); if (grainStorage != null) { return new TransactionalStateStorageProviderWrapper(grainStorage, stateName, currentContext); } throw (string.IsNullOrEmpty(storageName)) ? new InvalidOperationException($"No default {nameof(ITransactionalStateStorageFactory)} nor {nameof(IGrainStorage)} was found while attempting to create transactional state storage.") : new InvalidOperationException($"No {nameof(ITransactionalStateStorageFactory)} nor {nameof(IGrainStorage)} with the name {storageName} was found while attempting to create transactional state storage."); } } } ================================================ FILE: src/Orleans.Transactions/State/ReaderWriterLock.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.State { internal partial class ReadWriteLock where TState : class, new() { private readonly TransactionalStateOptions options; private readonly TransactionQueue queue; private readonly BatchWorker lockWorker; private readonly BatchWorker storageWorker; private readonly ILogger logger; private readonly IActivationLifetime activationLifetime; // the linked list of lock groups // the head is the group that is currently holding the lock private LockGroup currentGroup = null; // cache the last known minimum so we don't have to recompute it as much private DateTime cachedMin = DateTime.MaxValue; private Guid cachedMinId; // group of non-conflicting transactions collectively acquiring/releasing the lock private class LockGroup : Dictionary> { public int FillCount; public List Tasks; // the tasks for executing the waiting operations public LockGroup Next; // queued-up transactions waiting to acquire lock public DateTime? Deadline; public void Reset() { FillCount = 0; Tasks = null; Deadline = null; Clear(); } } public ReadWriteLock( IOptions options, TransactionQueue queue, BatchWorker storageWorker, ILogger logger, IActivationLifetime activationLifetime) { this.options = options.Value; this.queue = queue; this.storageWorker = storageWorker; this.logger = logger; this.activationLifetime = activationLifetime; this.lockWorker = new BatchWorkerFromDelegate(LockWork, this.activationLifetime.OnDeactivating); } public async Task EnterLock(Guid transactionId, DateTime priority, AccessCounter counter, bool isRead, Func task) { bool rollbacksOccurred = false; List cleanup = new List(); await this.queue.Ready(); // search active transactions if (Find(transactionId, isRead, out var group, out var record)) { // check if we lost some reads or writes already if (counter.Reads > record.NumberReads || counter.Writes > record.NumberWrites) { throw new OrleansBrokenTransactionLockException(transactionId.ToString(), "when re-entering lock"); } // check if the operation conflicts with other transactions in the group if (HasConflict(isRead, priority, transactionId, group, out var resolvable)) { if (!resolvable) { throw new OrleansTransactionLockUpgradeException(transactionId.ToString()); } else { // rollback all conflicts var conflicts = Conflicts(transactionId, group).ToList(); if (conflicts.Count > 0) { foreach (var r in conflicts) { cleanup.Add(Rollback(r, true)); rollbacksOccurred = true; } } } } } else { // check if we were supposed to already hold this lock if (counter.Reads + counter.Writes > 0) { throw new OrleansBrokenTransactionLockException(transactionId.ToString(), "when trying to re-enter lock"); } // update the lock deadline if (group == currentGroup) { group.Deadline = DateTime.UtcNow + this.options.LockTimeout; LogTraceSetLockExpiration(new(group.Deadline)); } // create a new record for this transaction record = new TransactionRecord() { TransactionId = transactionId, Priority = priority, Deadline = DateTime.UtcNow + this.options.LockAcquireTimeout }; group.Add(transactionId, record); group.FillCount++; if (group == currentGroup) LogTraceEnterLock(transactionId, group.FillCount); else LogTraceEnterLockQueue(transactionId, group.FillCount); } var result = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); void completion() { try { result.TrySetResult(task()); } catch (Exception exception) { result.TrySetException(exception); } } if (group != currentGroup) { // task will be executed once its group acquires the lock if (group.Tasks == null) group.Tasks = new List(); group.Tasks.Add(completion); } else { // execute task right now completion(); } if (isRead) { record.AddRead(); } else { record.AddWrite(); } if (rollbacksOccurred) { lockWorker.Notify(); } else if (group.Deadline.HasValue) { lockWorker.Notify(group.Deadline.Value); } await Task.WhenAll(cleanup); return await result.Task; } public async Task<(TransactionalStatus Status, TransactionRecord State)> ValidateLock(Guid transactionId, AccessCounter accessCount) { if (currentGroup == null || !currentGroup.TryGetValue(transactionId, out TransactionRecord record)) { return (TransactionalStatus.BrokenLock, new TransactionRecord()); } else if (record.NumberReads != accessCount.Reads || record.NumberWrites != accessCount.Writes) { await Rollback(transactionId, true); return (TransactionalStatus.LockValidationFailed, record); } else { return (TransactionalStatus.Ok, record); } } public void Notify() { this.lockWorker.Notify(); } public bool TryGetRecord(Guid transactionId, out TransactionRecord record) { return this.currentGroup.TryGetValue(transactionId, out record); } public Task AbortExecutingTransactions(Exception exception) { if (currentGroup != null) { Task[] pending = currentGroup.Select(g => BreakLock(g.Key, g.Value, exception)).ToArray(); currentGroup.Reset(); return Task.WhenAll(pending); } return Task.CompletedTask; } private Task BreakLock(Guid transactionId, TransactionRecord entry, Exception exception) { LogTraceBreakLock(transactionId); return this.queue.NotifyOfAbort(entry, TransactionalStatus.BrokenLock, exception); } public void AbortQueuedTransactions() { var pos = currentGroup?.Next; while (pos != null) { if (pos.Tasks != null) { foreach (var t in pos.Tasks) { // running the task will abort the transaction because it is not in currentGroup t(); } } pos.Clear(); pos = pos.Next; } if (currentGroup != null) currentGroup.Next = null; } public void Rollback(Guid guid) => currentGroup?.Remove(guid); public Task Rollback(Guid guid, bool notify) { // no-op if the transaction never happened or already rolled back if (currentGroup == null || !currentGroup.Remove(guid, out var record)) { return Task.CompletedTask; } // notify remote listeners return notify ? queue.NotifyOfAbort(record, TransactionalStatus.BrokenLock, exception: null) : Task.CompletedTask; } private async Task LockWork() { // Stop pumping lock work if this activation is stopping/stopped. if (this.activationLifetime.OnDeactivating.IsCancellationRequested) return; using (this.activationLifetime.BlockDeactivation()) { var now = DateTime.UtcNow; if (currentGroup != null) { // check if there are any group members that are ready to exit the lock if (currentGroup.Count > 0) { if (LockExits(out var single, out var multiple)) { if (single != null) { await this.queue.EnqueueCommit(single); } else if (multiple != null) { foreach (var r in multiple) { await this.queue.EnqueueCommit(r); } } lockWorker.Notify(); storageWorker.Notify(); } else if (currentGroup.Deadline.HasValue) { if (currentGroup.Deadline.Value < now) { // the lock group has timed out. TimeSpan late = now - currentGroup.Deadline.Value; LogTraceBreakLockTimeout(new(currentGroup.Keys), Math.Floor(late.TotalMilliseconds)); await AbortExecutingTransactions(exception: null); lockWorker.Notify(); } else { LogTraceRecheckLockExpiration(new(currentGroup.Deadline)); // check again when the group expires lockWorker.Notify(currentGroup.Deadline.Value); } } else { LogWarningDeadlineNotSet(new(currentGroup.Keys)); } } else { // the lock is empty, a new group can enter currentGroup = currentGroup.Next; if (currentGroup != null) { currentGroup.Deadline = now + this.options.LockTimeout; // discard expired waiters that have no chance to succeed // because they have been waiting for the lock for a longer timespan than the // total transaction timeout foreach (var kvp in currentGroup) { if (now > kvp.Value.Deadline) { currentGroup.Remove(kvp.Key); LogTraceExpireLockWaiter(kvp.Key); } } LogTraceLockGroupSize(currentGroup.Count, new(currentGroup.Deadline)); if (logger.IsEnabled(LogLevel.Trace)) { foreach (var kvp in currentGroup) LogTraceEnterLockKey(kvp.Key); } // execute all the read and update tasks if (currentGroup.Tasks != null) { foreach (var t in currentGroup.Tasks) { t(); } } lockWorker.Notify(); } } } } } private bool Find(Guid guid, bool isRead, out LockGroup group, out TransactionRecord record) { if (currentGroup == null) { group = currentGroup = new LockGroup(); record = null; return false; } else { group = null; var pos = currentGroup; while (true) { if (pos.TryGetValue(guid, out record)) { group = pos; return true; } // if we have not found a place to insert this op yet, and there is room, and no conflicts, use this one if (group == null && pos.FillCount < this.options.MaxLockGroupSize && !HasConflict(isRead, DateTime.MaxValue, guid, pos, out _)) { group = pos; } if (pos.Next == null) // we did not find this tx. { // add a new empty group to insert this tx, if we have not found one yet if (group == null) { group = pos.Next = new LockGroup(); } return false; } pos = pos.Next; } } } private static bool HasConflict(bool isRead, DateTime priority, Guid transactionId, LockGroup group, out bool resolvable) { bool foundResolvableConflicts = false; foreach (var kvp in group) { if (kvp.Key != transactionId) { if (isRead && kvp.Value.NumberWrites == 0) { continue; } else { if (priority > kvp.Value.Priority) { resolvable = false; return true; } else { foundResolvableConflicts = true; } } } } resolvable = foundResolvableConflicts; return foundResolvableConflicts; } private static IEnumerable Conflicts(Guid transactionId, LockGroup group) { foreach (var kvp in group) { if (kvp.Key != transactionId) { yield return kvp.Key; } } } private bool LockExits(out TransactionRecord single, out List> multiple) { single = null; multiple = null; // fast-path the one-element case if (currentGroup.Count == 1) { var kvp = currentGroup.First(); if (kvp.Value.Role == CommitRole.NotYetDetermined) // has not received commit from TA { return false; } else { single = kvp.Value; currentGroup.Remove(single.TransactionId); LogDebugExitLock(single.TransactionId, new(single.Timestamp)); return true; } } else { // find the current minimum, if we don't have a valid cache of it if (cachedMin == DateTime.MaxValue || !currentGroup.TryGetValue(cachedMinId, out var record) || record.Role != CommitRole.NotYetDetermined || record.Timestamp != cachedMin) { cachedMin = DateTime.MaxValue; foreach (var kvp in currentGroup) { if (kvp.Value.Role == CommitRole.NotYetDetermined) // has not received commit from TA { if (cachedMin > kvp.Value.Timestamp) { cachedMin = kvp.Value.Timestamp; cachedMinId = kvp.Key; } } } } // find released entries foreach (var kvp in currentGroup) { if (kvp.Value.Role != CommitRole.NotYetDetermined) // ready to commit { if (kvp.Value.Timestamp < cachedMin) { if (multiple == null) { multiple = new List>(); } multiple.Add(kvp.Value); } } } if (multiple == null) { return false; } else { multiple.Sort(Comparer); for (int i = 0; i < multiple.Count; i++) { currentGroup.Remove(multiple[i].TransactionId); LogDebugExitLockProgress(i, multiple.Count, multiple[i].TransactionId, new(multiple[i].Timestamp)); } return true; } } } private static int Comparer(TransactionRecord a, TransactionRecord b) { return a.Timestamp.CompareTo(b.Timestamp); } private readonly struct DateTimeLogRecord(DateTime? ts) { public override string ToString() => ts?.ToString("o") ?? "none"; } [LoggerMessage( Level = LogLevel.Trace, Message = "Set lock expiration at {Deadline}" )] private partial void LogTraceSetLockExpiration(DateTimeLogRecord deadline); [LoggerMessage( Level = LogLevel.Trace, Message = "Enter-lock {TransactionId} Fill count={FillCount}" )] private partial void LogTraceEnterLock(Guid transactionId, int fillCount); [LoggerMessage( Level = LogLevel.Trace, Message = "Enter-lock-queue {TransactionId} Fill count={FillCount}" )] private partial void LogTraceEnterLockQueue(Guid transactionId, int fillCount); [LoggerMessage( Level = LogLevel.Trace, Message = "Break-lock for transaction {TransactionId}" )] private partial void LogTraceBreakLock(Guid transactionId); private readonly struct TransactionIdsLogRecord(IEnumerable guids) { public override string ToString() => string.Join(",", guids); } [LoggerMessage( Level = LogLevel.Trace, Message = "Break-lock timeout for transactions {TransactionIds}. {Late}ms late" )] private partial void LogTraceBreakLockTimeout(TransactionIdsLogRecord transactionIds, double late); [LoggerMessage( Level = LogLevel.Trace, Message = "Recheck lock expiration at {Deadline}" )] private partial void LogTraceRecheckLockExpiration(DateTimeLogRecord deadline); [LoggerMessage( Level = LogLevel.Warning, Message = "Deadline not set for transactions {TransactionIds}" )] private partial void LogWarningDeadlineNotSet(TransactionIdsLogRecord transactionIds); [LoggerMessage( Level = LogLevel.Trace, Message = "Expire-lock-waiter {Key}" )] private partial void LogTraceExpireLockWaiter(Guid key); [LoggerMessage( Level = LogLevel.Trace, Message = "Lock group size={Count} deadline={Deadline}" )] private partial void LogTraceLockGroupSize(int count, DateTimeLogRecord deadline); // "Enter-lock {Key}" [LoggerMessage( Level = LogLevel.Trace, Message = "Enter-lock {Key}" )] private partial void LogTraceEnterLockKey(Guid key); [LoggerMessage( Level = LogLevel.Debug, Message = "Exit-lock {TransactionId} {Timestamp}" )] private partial void LogDebugExitLock(Guid transactionId, DateTimeLogRecord timestamp); [LoggerMessage( Level = LogLevel.Debug, Message = "Exit-lock ({Current}/{Count}) {TransactionId} {Timestamp}" )] private partial void LogDebugExitLockProgress(int current, int count, Guid transactionId, DateTimeLogRecord timestamp); } } ================================================ FILE: src/Orleans.Transactions/State/StorageBatch.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { /// /// Events streamed to storage. /// public interface ITransactionalStateStorageEvents where TState : class, new() { void Prepare(long sequenceNumber, Guid transactionId, DateTime timestamp, ParticipantId transactionManager, TState state); void Read(DateTime timestamp); void Cancel(long sequenceNumber); void Confirm(long sequenceNumber); void Commit(Guid transactionId, DateTime timestamp, List writeResources); void Collect(Guid transactionId); } /// /// Accumulates storage events, for submitting them to storage as a batch. /// /// internal class StorageBatch : ITransactionalStateStorageEvents where TState : class, new() { // watermarks for commit, prepare, abort private long confirmUpTo; private long cancelAbove; private readonly long cancelAboveStart; // prepare records private readonly SortedDictionary> prepares; // follow-up actions, to be executed after storing this batch private readonly List followUpActions; private readonly List>> storeConditions; // counters for each type of event private int total = 0; private int prepare = 0; private int read = 0; private int commit = 0; private int confirm = 0; private int collect = 0; private int cancel = 0; public TransactionalStateMetaData MetaData { get; private set; } public string ETag { get; set; } public int BatchSize => total; public override string ToString() { return $"batchsize={total} [{read}r {prepare}p {commit}c {confirm}cf {collect}cl {cancel}cc]"; } public StorageBatch(TransactionalStateMetaData metaData, string etag, long confirmUpTo, long cancelAbove) { this.MetaData = metaData ?? throw new ArgumentNullException(nameof(metaData)); this.ETag = etag; this.confirmUpTo = confirmUpTo; this.cancelAbove = cancelAbove; this.cancelAboveStart = cancelAbove; this.followUpActions = new List(); this.storeConditions = new List>>(); this.prepares = new SortedDictionary>(); } public StorageBatch(StorageBatch previous) : this(previous.MetaData, previous.ETag, previous.confirmUpTo, previous.cancelAbove) { } public StorageBatch(TransactionalStorageLoadResponse loadresponse) : this(loadresponse.Metadata, loadresponse.ETag, loadresponse.CommittedSequenceId, loadresponse.PendingStates.LastOrDefault()?.SequenceId ?? loadresponse.CommittedSequenceId) { } public async Task Store(ITransactionalStateStorage storage) { List> list = this.prepares.Values.ToList(); return await storage.Store(ETag, this.MetaData, list, (confirm > 0) ? confirmUpTo : (long?)null, (cancelAbove < cancelAboveStart) ? cancelAbove : (long?)null); } public void RunFollowUpActions() { foreach (var action in followUpActions) { action(); } } public void Read(DateTime timestamp) { read++; total++; if (MetaData.TimeStamp < timestamp) { MetaData.TimeStamp = timestamp; } } public void Prepare(long sequenceNumber, Guid transactionId, DateTime timestamp, ParticipantId transactionManager, TState state) { prepare++; total++; if (MetaData.TimeStamp < timestamp) MetaData.TimeStamp = timestamp; this.prepares[sequenceNumber] = new PendingTransactionState { SequenceId = sequenceNumber, TransactionId = transactionId.ToString(), TimeStamp = timestamp, TransactionManager = transactionManager, State = state }; if (cancelAbove < sequenceNumber) { cancelAbove = sequenceNumber; } } public void Cancel(long sequenceNumber) { cancel++; total++; this.prepares.Remove(sequenceNumber); if (cancelAbove > sequenceNumber - 1) { cancelAbove = sequenceNumber - 1; } } public void Confirm(long sequenceNumber) { confirm++; total++; confirmUpTo = sequenceNumber; // remove all redundant prepare records that are superseded by a later confirmed state while (true) { long? first = this.prepares.Values.FirstOrDefault()?.SequenceId; if (first.HasValue && first < confirmUpTo) { this.prepares.Remove(first.Value); } else { break; } } } public void Commit(Guid transactionId, DateTime timestamp, List WriteParticipants) { commit++; total++; MetaData.CommitRecords.Add(transactionId, new CommitRecord() { Timestamp = timestamp, WriteParticipants = WriteParticipants }); } public void Collect(Guid transactionId) { collect++; total++; MetaData.CommitRecords.Remove(transactionId); } public void FollowUpAction(Action action) { followUpActions.Add(action); } public void AddStorePreCondition(Func> action) { this.storeConditions.Add(action); } public async Task CheckStorePreConditions() { if (this.storeConditions.Count == 0) return true; bool[] results = await Task.WhenAll(this.storeConditions.Select(a => a.Invoke())); return results.All(b => b); } } } ================================================ FILE: src/Orleans.Transactions/State/TransactionManager.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.State { internal class TransactionManager : ITransactionManager where TState : class, new() { private readonly TransactionQueue queue; public TransactionManager(TransactionQueue queue) { this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); } public async Task PrepareAndCommit(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, List writeResources, int totalResources) { // validate the lock var (status, record) = await this.queue.RWLock.ValidateLock(transactionId, accessCount); var valid = status == TransactionalStatus.Ok; record.Timestamp = timeStamp; record.Role = CommitRole.LocalCommit; // we are the TM record.WaitCount = totalResources - 1; record.WaitingSince = DateTime.UtcNow; record.WriteParticipants = writeResources; record.PromiseForTA = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (!valid) { await this.queue.NotifyOfAbort(record, status, exception: null); } else { this.queue.Clock.Merge(record.Timestamp); } this.queue.RWLock.Notify(); return await record.PromiseForTA.Task; } public Task Prepared(Guid transactionId, DateTime timeStamp, ParticipantId resource, TransactionalStatus status) { return this.queue.NotifyOfPrepared(transactionId, timeStamp, status); } public async Task Ping(Guid transactionId, DateTime timeStamp, ParticipantId resource) { await this.queue.Ready(); await this.queue.NotifyOfPing(transactionId, timeStamp, resource); } } } ================================================ FILE: src/Orleans.Transactions/State/TransactionQueue.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Storage; using Orleans.Configuration; using Orleans.Timers.Internal; namespace Orleans.Transactions.State { internal partial class TransactionQueue where TState : class, new() { private readonly TransactionalStateOptions options; private readonly ParticipantId resource; private readonly Action deactivate; private readonly ITransactionalStateStorage storage; private readonly BatchWorker storageWorker; protected readonly ILogger logger; private readonly IActivationLifetime activationLifetime; private readonly ConfirmationWorker confirmationWorker; private CommitQueue commitQueue; private Task readyTask; protected StorageBatch storageBatch; private int failCounter; // collection tasks private readonly Dictionary unprocessedPreparedMessages; private class PreparedMessages { public PreparedMessages(TransactionalStatus status) { this.Status = status; } public int Count; public TransactionalStatus Status; } private TState stableState; private long stableSequenceNumber; public ReadWriteLock RWLock { get; } public CausalClock Clock { get; } public TransactionQueue( IOptions options, ParticipantId resource, Action deactivate, ITransactionalStateStorage storage, IClock clock, ILogger logger, ITimerManager timerManager, IActivationLifetime activationLifetime) { this.options = options.Value; this.resource = resource; this.deactivate = deactivate; this.storage = storage; this.Clock = new CausalClock(clock); this.logger = logger; this.activationLifetime = activationLifetime; this.storageWorker = new BatchWorkerFromDelegate(StorageWork, this.activationLifetime.OnDeactivating); this.RWLock = new ReadWriteLock(options, this, this.storageWorker, logger, activationLifetime); this.confirmationWorker = new ConfirmationWorker(options, this.resource, this.storageWorker, () => this.storageBatch, this.logger, timerManager, activationLifetime); this.unprocessedPreparedMessages = new Dictionary(); this.commitQueue = new CommitQueue(); this.readyTask = Task.CompletedTask; } public async Task EnqueueCommit(TransactionRecord record) { try { LogTraceStartTwoPhaseCommit(record.TransactionId, new(record.Timestamp)); commitQueue.Add(record); // additional actions for each commit type switch (record.Role) { case CommitRole.ReadOnly: { // no extra actions needed break; } case CommitRole.LocalCommit: { // process prepared messages received ahead of time if (unprocessedPreparedMessages.TryGetValue(record.Timestamp, out PreparedMessages info)) { if (info.Status == TransactionalStatus.Ok) { record.WaitCount -= info.Count; } else { await AbortCommits(info.Status, commitQueue.Count - 1); this.RWLock.Notify(); } unprocessedPreparedMessages.Remove(record.Timestamp); } break; } case CommitRole.RemoteCommit: { // optimization: can immediately proceed if dependency is implied bool behindRemoteEntryBySameTM = false; /* disabled - jbragg - TODO - revisit commitQueue.Count >= 2 && commitQueue[commitQueue.Count - 2] is TransactionRecord rce && rce.Role == CommitRole.RemoteCommit && rce.TransactionManager.Equals(record.TransactionManager); */ if (record.NumberWrites > 0) { this.storageBatch.Prepare(record.SequenceNumber, record.TransactionId, record.Timestamp, record.TransactionManager, record.State); } else { this.storageBatch.Read(record.Timestamp); } this.storageBatch.FollowUpAction(() => { LogTracePersisted(record); record.PrepareIsPersisted = true; if (behindRemoteEntryBySameTM) { LogTraceSendingImmediatePrepared(record); // can send prepared message immediately after persisting prepare record record.TransactionManager.Reference.AsReference() .Prepared(record.TransactionManager.Name, record.TransactionId, record.Timestamp, this.resource, TransactionalStatus.Ok) .Ignore(); record.LastSent = DateTime.UtcNow; } }); break; } default: { LogErrorImpossibleCase(record.Role); throw new NotSupportedException($"{record.Role} is not a supported CommitRole."); } } } catch (Exception exception) { LogErrorTransactionAbortInternalError(exception); await NotifyOfAbort(record, TransactionalStatus.UnknownException, exception); } } public async Task NotifyOfPrepared(Guid transactionId, DateTime timeStamp, TransactionalStatus status) { var pos = commitQueue.Find(transactionId, timeStamp); LogTraceNotifyOfPrepared(transactionId, new(timeStamp), status); if (pos != -1) { var localEntry = commitQueue[pos]; if (localEntry.Role != CommitRole.LocalCommit) { LogErrorTransactionAbortWrongCommitType(); throw new InvalidOperationException($"Wrong commit type: {localEntry.Role}"); } if (status == TransactionalStatus.Ok) { localEntry.WaitCount--; storageWorker.Notify(); } else { await AbortCommits(status, pos); this.RWLock.Notify(); } } else { // this message has arrived ahead of the commit request - we need to remember it if (!this.unprocessedPreparedMessages.TryGetValue(timeStamp, out PreparedMessages info)) { this.unprocessedPreparedMessages[timeStamp] = info = new PreparedMessages(status); } if (status == TransactionalStatus.Ok) { info.Count++; } else { info.Status = status; } // TODO fix memory leak if corresponding commit messages never arrive } } public async Task NotifyOfPrepare(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager) { var locked = await this.RWLock.ValidateLock(transactionId, accessCount); var status = locked.Item1; var record = locked.Item2; var valid = status == TransactionalStatus.Ok; record.Timestamp = timeStamp; record.Role = CommitRole.RemoteCommit; // we are not the TM record.TransactionManager = transactionManager; record.LastSent = null; record.PrepareIsPersisted = false; if (!valid) { await this.NotifyOfAbort(record, status, exception: null); } else { this.Clock.Merge(record.Timestamp); } this.RWLock.Notify(); } public async Task NotifyOfAbort(TransactionRecord entry, TransactionalStatus status, Exception exception) { switch (entry.Role) { case CommitRole.NotYetDetermined: { // cannot notify anyone. TA will detect broken lock during prepare. break; } case CommitRole.RemoteCommit: { LogTraceAborting(status, entry); entry.ConfirmationResponsePromise?.TrySetException(new OrleansException($"Confirm failed: Status {status}")); if (entry.LastSent.HasValue) return; // cannot abort anymore if we already sent prepare-ok message LogTraceAbortingViaPrepared(status, entry); entry.TransactionManager.Reference.AsReference() .Prepared(entry.TransactionManager.Name, entry.TransactionId, entry.Timestamp, resource, status) .Ignore(); break; } case CommitRole.LocalCommit: { LogTraceAborting(status, entry); try { // tell remote participants await Task.WhenAll(entry.WriteParticipants .Where(p => !p.Equals(resource)) .Select(p => p.Reference.AsReference() .Cancel(p.Name, entry.TransactionId, entry.Timestamp, status))); } catch(Exception ex) { LogWarningFailedToNotifyAllTransactionParticipantsOfCancellation(entry.TransactionId, new(entry.Timestamp), status, ex); } // reply to transaction agent if (exception is not null) { entry.PromiseForTA.TrySetException(exception); } else { entry.PromiseForTA.TrySetResult(status); } break; } case CommitRole.ReadOnly: { LogTraceAborting(status, entry); // reply to transaction agent if (exception is not null) { entry.PromiseForTA.TrySetException(exception); } else { entry.PromiseForTA.TrySetResult(status); } break; } default: { LogErrorImpossibleCase(entry.Role); throw new NotSupportedException($"{entry.Role} is not a supported CommitRole."); } } } public async Task NotifyOfPing(Guid transactionId, DateTime timeStamp, ParticipantId resource) { if (this.commitQueue.Find(transactionId, timeStamp) != -1) { // no need to take special action now - the transaction is still // in the commit queue and its status is not yet determined. // confirmation or cancellation will be sent after committing or aborting. LogTraceReceivedPingIrrelevant(transactionId); this.storageWorker.Notify(); // just in case the worker fell asleep or something } else { if (!this.confirmationWorker.IsConfirmed(transactionId)) { LogTraceReceivedPingUnknown(transactionId); // we never heard of this transaction - so it must have aborted await resource.Reference.AsReference() .Cancel(resource.Name, transactionId, timeStamp, TransactionalStatus.PresumedAbort); } } } public async Task NotifyOfConfirm(Guid transactionId, DateTime timeStamp) { LogTraceNotifyOfConfirm(transactionId, new(timeStamp)); // find in queue var pos = commitQueue.Find(transactionId, timeStamp); if (pos == -1) return; // must have already been confirmed var remoteEntry = commitQueue[pos]; if (remoteEntry.Role != CommitRole.RemoteCommit) { LogErrorInternalErrorNotifyOfConfirmWrongCommitType(); throw new InvalidOperationException($"Wrong commit type: {remoteEntry.Role}"); } // setting this field makes this entry ready for batching remoteEntry.ConfirmationResponsePromise = remoteEntry.ConfirmationResponsePromise ?? new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); storageWorker.Notify(); // now we wait for the batch to finish await remoteEntry.ConfirmationResponsePromise.Task; } public async Task NotifyOfCancel(Guid transactionId, DateTime timeStamp, TransactionalStatus status) { LogTraceNotifyOfCancel(nameof(NotifyOfCancel), transactionId, new(timeStamp), status); // find in queue var pos = commitQueue.Find(transactionId, timeStamp); if (pos == -1) return; this.storageBatch.Cancel(commitQueue[pos].SequenceNumber); await AbortCommits(status, pos); storageWorker.Notify(); this.RWLock.Notify(); } /// /// called on activation, and when recovering from storage conflicts or other exceptions. /// public async Task NotifyOfRestore() { try { await Ready(); } finally { this.readyTask = Restore(); } await this.readyTask; } /// /// Ensures queue is ready to process requests. /// /// public Task Ready() { if (this.readyTask.Status == TaskStatus.RanToCompletion) { return readyTask; } return ReadyAsync(); async Task ReadyAsync() { try { await readyTask; } catch (Exception exception) { LogWarningExceptionInTransactionQueue(exception); await AbortAndRestore(TransactionalStatus.UnknownException, exception); } } } private async Task Restore() { TransactionalStorageLoadResponse loadresponse = await storage.Load(); this.storageBatch = new StorageBatch(loadresponse); this.stableState = loadresponse.CommittedState; this.stableSequenceNumber = loadresponse.CommittedSequenceId; LogDebugLoad(stableSequenceNumber, loadresponse.PendingStates.Count, storageBatch.MetaData.CommitRecords.Count); // ensure clock is consistent with loaded state this.Clock.Merge(storageBatch.MetaData.TimeStamp); // resume prepared transactions (not TM) foreach (var pr in loadresponse.PendingStates.OrderBy(ps => ps.TimeStamp)) { if (pr.SequenceId > loadresponse.CommittedSequenceId && pr.TransactionManager.Reference != null) { LogDebugRecoverTwoPhaseCommit(pr.TransactionId); ParticipantId tm = pr.TransactionManager; commitQueue.Add(new TransactionRecord() { Role = CommitRole.RemoteCommit, TransactionId = Guid.Parse(pr.TransactionId), Timestamp = pr.TimeStamp, State = pr.State, SequenceNumber = pr.SequenceId, TransactionManager = tm, PrepareIsPersisted = true, LastSent = default(DateTime), ConfirmationResponsePromise = null, NumberWrites = 1 // was a writing transaction }); this.stableSequenceNumber = pr.SequenceId; } } // resume committed transactions (on TM) foreach (var kvp in storageBatch.MetaData.CommitRecords) { LogDebugRecoverCommitConfirmation(kvp.Key); this.confirmationWorker.Add(kvp.Key, kvp.Value.Timestamp, kvp.Value.WriteParticipants); } // check for work this.storageWorker.Notify(); this.RWLock.Notify(); } public void GetMostRecentState(out TState state, out long sequenceNumber) { if (commitQueue.Count == 0) { state = this.stableState; sequenceNumber = this.stableSequenceNumber; } else { var record = commitQueue.Last; state = record.State; sequenceNumber = record.SequenceNumber; } } public int BatchableOperationsCount() { int count = 0; int pos = commitQueue.Count - 1; while (pos >= 0 && commitQueue[pos].Batchable) { pos--; count++; } return count; } private async Task StorageWork() { // Stop if this activation is stopping/stopped. if (this.activationLifetime.OnDeactivating.IsCancellationRequested) return; using (this.activationLifetime.BlockDeactivation()) { try { // count committable entries at the bottom of the commit queue int committableEntries = 0; while (committableEntries < commitQueue.Count && commitQueue[committableEntries].ReadyToCommit) { committableEntries++; } // process all committable entries, assembling a storage batch if (committableEntries > 0) { // process all committable entries, adding storage events to the storage batch CollectEventsForBatch(committableEntries); LogDebugBatchCommit(committableEntries, commitQueue.Count - committableEntries, new(commitQueue, committableEntries)); } else { // send or re-send messages and detect timeouts await CheckProgressOfCommitQueue(); } // store the current storage batch, if it is not empty StorageBatch batchBeingSentToStorage = null; if (this.storageBatch.BatchSize > 0) { // get the next batch in place so it can be filled while we store the old one batchBeingSentToStorage = this.storageBatch; this.storageBatch = new StorageBatch(batchBeingSentToStorage); try { if (await batchBeingSentToStorage.CheckStorePreConditions()) { // perform the actual store, and record the e-tag this.storageBatch.ETag = await batchBeingSentToStorage.Store(storage); failCounter = 0; } else { LogWarningStorePreConditionsNotMet(); await AbortAndRestore(TransactionalStatus.CommitFailure, exception: null); return; } } catch (InconsistentStateException exception) { LogWarningReloadFromStorageTriggeredByETagMismatch(exception); await AbortAndRestore(TransactionalStatus.StorageConflict, exception, true); return; } catch (Exception exception) { LogWarningStorageExceptionInStorageWorker(exception); await AbortAndRestore(TransactionalStatus.UnknownException, exception); return; } } if (committableEntries > 0) { // update stable state var lastCommittedEntry = commitQueue[committableEntries - 1]; this.stableState = lastCommittedEntry.State; this.stableSequenceNumber = lastCommittedEntry.SequenceNumber; LogTraceStableStateVersion(stableSequenceNumber); // remove committed entries from commit queue commitQueue.RemoveFromFront(committableEntries); storageWorker.Notify(); // we have to re-check for work } if (batchBeingSentToStorage != null) { batchBeingSentToStorage.RunFollowUpActions(); storageWorker.Notify(); // we have to re-check for work } } catch (Exception exception) { LogWarningExceptionInStorageWorker(failCounter, exception); await AbortAndRestore(TransactionalStatus.UnknownException, exception); } } } private Task AbortAndRestore(TransactionalStatus status, Exception exception, bool force = false) { this.readyTask = Bail(status, exception, force); return this.readyTask; } private async Task Bail(TransactionalStatus status, Exception exception, bool force = false) { List pending = new List(); pending.Add(RWLock.AbortExecutingTransactions(exception)); this.RWLock.AbortQueuedTransactions(); // abort all entries in the commit queue foreach (var entry in commitQueue.Elements) { pending.Add(NotifyOfAbort(entry, status, exception: exception)); } commitQueue.Clear(); await Task.WhenAll(pending); if (++failCounter >= 10 || force) { LogDebugStorageWorkerTriggeringGrainDeactivation(); this.deactivate(); } await this.Restore(); } private async Task CheckProgressOfCommitQueue() { if (commitQueue.Count > 0) { var bottom = commitQueue[0]; var now = DateTime.UtcNow; LogTraceCommitQueueSize(commitQueue.Count, bottom); switch (bottom.Role) { case CommitRole.LocalCommit: { // check for timeout periodically if (bottom.WaitingSince + this.options.PrepareTimeout <= now) { await AbortCommits(TransactionalStatus.PrepareTimeout); this.RWLock.Notify(); } else { storageWorker.Notify(bottom.WaitingSince + this.options.PrepareTimeout); } break; } case CommitRole.RemoteCommit: { if (bottom.PrepareIsPersisted && !bottom.LastSent.HasValue) { // send PreparedMessage to remote TM bottom.TransactionManager.Reference.AsReference() .Prepared(bottom.TransactionManager.Name, bottom.TransactionId, bottom.Timestamp, resource, TransactionalStatus.Ok) .Ignore(); bottom.LastSent = now; LogTraceSentPrepared(bottom); if (bottom.IsReadOnly) { storageWorker.Notify(); // we are ready to batch now } else { storageWorker.Notify(bottom.LastSent.Value + this.options.RemoteTransactionPingFrequency); } } else if (!bottom.IsReadOnly && bottom.LastSent.HasValue) { // send ping messages periodically to reactivate crashed TMs if (bottom.LastSent + this.options.RemoteTransactionPingFrequency <= now) { LogTraceSentPing(bottom); bottom.TransactionManager.Reference.AsReference() .Ping(bottom.TransactionManager.Name, bottom.TransactionId, bottom.Timestamp, resource).Ignore(); bottom.LastSent = now; } storageWorker.Notify(bottom.LastSent.Value + this.options.RemoteTransactionPingFrequency); } break; } default: { LogErrorImpossibleCase(bottom.Role); throw new NotSupportedException($"{bottom.Role} is not a supported CommitRole."); } } } } private void CollectEventsForBatch(int batchsize) { // collect events for batch for (int i = 0; i < batchsize; i++) { TransactionRecord entry = commitQueue[i]; LogTraceCommitting(entry); switch (entry.Role) { case CommitRole.LocalCommit: { OnLocalCommit(entry); break; } case CommitRole.RemoteCommit: { if (entry.ConfirmationResponsePromise == null) { // this is a read-only participant that has sent // its prepared message. // So we are really done and need not store or do anything. } else { // we must confirm in storage, and then respond to TM so it can collect this.storageBatch.Confirm(entry.SequenceNumber); this.storageBatch.FollowUpAction(() => { entry.ConfirmationResponsePromise.TrySetResult(true); LogTraceConfirmedRemoteCommit(entry.SequenceNumber, entry.TransactionId, new(entry.Timestamp), entry.TransactionManager); }); } break; } case CommitRole.ReadOnly: { // we are a participant of a read-only transaction. Must store timestamp and then respond. this.storageBatch.Read(entry.Timestamp); this.storageBatch.FollowUpAction(() => { entry.PromiseForTA.TrySetResult(TransactionalStatus.Ok); }); break; } default: { LogErrorImpossibleCase(entry.Role); throw new NotSupportedException($"{entry.Role} is not a supported CommitRole."); } } } } protected virtual void OnLocalCommit(TransactionRecord entry) { this.storageBatch.Prepare(entry.SequenceNumber, entry.TransactionId, entry.Timestamp, entry.TransactionManager, entry.State); this.storageBatch.Commit(entry.TransactionId, entry.Timestamp, entry.WriteParticipants); this.storageBatch.Confirm(entry.SequenceNumber); // after store, send response back to TA this.storageBatch.FollowUpAction(() => { LogTraceLocallyCommitted(entry.TransactionId, new(entry.Timestamp)); entry.PromiseForTA.TrySetResult(TransactionalStatus.Ok); }); if (entry.WriteParticipants.Count > 1) { // after committing, we need to run a task to confirm and collect this.storageBatch.FollowUpAction(() => { LogTraceAddingConfirmationToWorker(entry.TransactionId, new(entry.Timestamp)); this.confirmationWorker.Add(entry.TransactionId, entry.Timestamp, entry.WriteParticipants); }); } else { // there are no remote write participants to notify, so we can finish it all in one shot this.storageBatch.Collect(entry.TransactionId); } } private async Task AbortCommits(TransactionalStatus status, int from = 0) { List pending = new List(); // emtpy the back of the commit queue, starting at specified position for (int i = from; i < commitQueue.Count; i++) { pending.Add(NotifyOfAbort(commitQueue[i], i == from ? status : TransactionalStatus.CascadingAbort, exception: null)); } commitQueue.RemoveFromBack(commitQueue.Count - from); pending.Add(this.RWLock.AbortExecutingTransactions(exception: null)); await Task.WhenAll(pending); } private readonly struct DateTimeLogRecord(DateTime ts) { public override string ToString() => ts.ToString("O"); } [LoggerMessage( Level = LogLevel.Trace, Message = "Start two-phase-commit {TransactionId} {Timestamp}" )] private partial void LogTraceStartTwoPhaseCommit(Guid transactionId, DateTimeLogRecord timeStamp); [LoggerMessage( Level = LogLevel.Trace, Message = "Persisted {Record}" )] private partial void LogTracePersisted(TransactionRecord record); [LoggerMessage( Level = LogLevel.Trace, Message = "Sending immediate prepared {Record}" )] private partial void LogTraceSendingImmediatePrepared(TransactionRecord record); [LoggerMessage( Level = LogLevel.Error, EventId = 777, Message = "Internal error: impossible case {CommitRole}" )] private partial void LogErrorImpossibleCase(CommitRole commitRole); [LoggerMessage( Level = LogLevel.Error, Message = $"Transaction abort due to internal error in {nameof(EnqueueCommit)}" )] private partial void LogErrorTransactionAbortInternalError(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "NotifyOfPrepared - TransactionId:{TransactionId} Timestamp:{Timestamp}, TransactionalStatus{TransactionalStatus}" )] private partial void LogTraceNotifyOfPrepared(Guid transactionId, DateTimeLogRecord timeStamp, TransactionalStatus transactionalStatus); [LoggerMessage( Level = LogLevel.Error, Message = $"Transaction abort due to internal error in {nameof(NotifyOfPrepared)}: Wrong commit type" )] private partial void LogErrorTransactionAbortWrongCommitType(); [LoggerMessage( Level = LogLevel.Trace, Message = "Aborting status={Status} {Entry}" )] private partial void LogTraceAborting(TransactionalStatus status, TransactionRecord entry); [LoggerMessage( Level = LogLevel.Trace, Message = "Aborting via Prepared. Status={Status} Entry={Entry}" )] private partial void LogTraceAbortingViaPrepared(TransactionalStatus status, TransactionRecord entry); [LoggerMessage( Level = LogLevel.Warning, Message = "Failed to notify all transaction participants of cancellation. TransactionId: {TransactionId}, Timestamp: {Timestamp}, Status: {Status}" )] private partial void LogWarningFailedToNotifyAllTransactionParticipantsOfCancellation(Guid transactionId, DateTimeLogRecord timeStamp, TransactionalStatus status, Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Received ping for {TransactionId}, irrelevant (still processing)" )] private partial void LogTraceReceivedPingIrrelevant(Guid transactionId); // [LoggerMessage( Level = LogLevel.Trace, Message = "Received ping for {TransactionId}, unknown - presumed abort" )] private partial void LogTraceReceivedPingUnknown(Guid transactionId); [LoggerMessage( Level = LogLevel.Trace, Message = "NotifyOfConfirm: {TransactionId} {TimeStamp}" )] private partial void LogTraceNotifyOfConfirm(Guid transactionId, DateTimeLogRecord timeStamp); [LoggerMessage( Level = LogLevel.Error, Message = $"Internal error in {nameof(NotifyOfConfirm)}: wrong commit type" )] private partial void LogErrorInternalErrorNotifyOfConfirmWrongCommitType(); [LoggerMessage( Level = LogLevel.Trace, Message = "{MethodName}. TransactionId: {TransactionId}, TimeStamp: {TimeStamp} Status: {TransactionalStatus}" )] private partial void LogTraceNotifyOfCancel(string methodName, Guid transactionId, DateTimeLogRecord timeStamp, TransactionalStatus transactionalStatus); [LoggerMessage( Level = LogLevel.Warning, Message = "Exception in TransactionQueue" )] private partial void LogWarningExceptionInTransactionQueue(Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Load v{StableSequenceNumber} {PendingStatesCount}p {CommitRecordsCount}c" )] private partial void LogDebugLoad(long stableSequenceNumber, int pendingStatesCount, int commitRecordsCount); [LoggerMessage( Level = LogLevel.Debug, Message = "Recover two-phase-commit {TransactionId}" )] private partial void LogDebugRecoverTwoPhaseCommit(string transactionId); [LoggerMessage( Level = LogLevel.Debug, Message = "Recover commit confirmation {Key}" )] private partial void LogDebugRecoverCommitConfirmation(Guid key); private readonly struct CommitQueueLogRecord(CommitQueue state, int committableEntries) { public override string ToString() => state.Count > committableEntries ? state[committableEntries].ToString() : ""; } [LoggerMessage( Level = LogLevel.Debug, Message = "BatchCommit: {CommittableEntries} Leave: {UncommittableEntries}, Record: {Record}" )] private partial void LogDebugBatchCommit(int committableEntries, int uncommittableEntries, CommitQueueLogRecord record); [LoggerMessage( Level = LogLevel.Warning, Message = "Store pre conditions not met." )] private partial void LogWarningStorePreConditionsNotMet(); [LoggerMessage( Level = LogLevel.Warning, EventId = 888, Message = "Reload from storage triggered by e-tag mismatch." )] private partial void LogWarningReloadFromStorageTriggeredByETagMismatch(Exception exception); [LoggerMessage( Level = LogLevel.Warning, Message = "Storage exception in storage worker." )] private partial void LogWarningStorageExceptionInStorageWorker(Exception exception); [LoggerMessage( Level = LogLevel.Trace, Message = "Stable state version: {StableSequenceNumber}" )] private partial void LogTraceStableStateVersion(long stableSequenceNumber); [LoggerMessage( Level = LogLevel.Warning, EventId = 888, Message = "Exception in storageWorker. Retry {FailCounter}" )] private partial void LogWarningExceptionInStorageWorker(int failCounter, Exception exception); [LoggerMessage( Level = LogLevel.Debug, Message = "StorageWorker triggering grain Deactivation" )] private partial void LogDebugStorageWorkerTriggeringGrainDeactivation(); [LoggerMessage( Level = LogLevel.Trace, Message = "{CommitQueueSize} entries in queue waiting for bottom: {BottomEntry}" )] private partial void LogTraceCommitQueueSize(int commitQueueSize, TransactionRecord bottomEntry); [LoggerMessage( Level = LogLevel.Trace, Message = "Sent Prepared {BottomEntry}" )] private partial void LogTraceSentPrepared(TransactionRecord bottomEntry); [LoggerMessage( Level = LogLevel.Trace, Message = "Sent ping {BottomEntry}" )] private partial void LogTraceSentPing(TransactionRecord bottomEntry); [LoggerMessage( Level = LogLevel.Trace, Message = "Committing {Entry}" )] private partial void LogTraceCommitting(TransactionRecord entry); [LoggerMessage( Level = LogLevel.Trace, Message = "Confirmed remote commit v{SequenceNumber}. TransactionId:{TransactionId} Timestamp:{Timestamp} TransactionManager:{TransactionManager}" )] private partial void LogTraceConfirmedRemoteCommit(long sequenceNumber, Guid transactionId, DateTimeLogRecord timeStamp, ParticipantId transactionManager); [LoggerMessage( Level = LogLevel.Trace, Message = "Locally committed {TransactionId} {Timestamp}" )] private partial void LogTraceLocallyCommitted(Guid transactionId, DateTimeLogRecord timeStamp); [LoggerMessage( Level = LogLevel.Trace, Message = "Adding confirmation to worker for {TransactionId} {Timestamp}" )] private partial void LogTraceAddingConfirmationToWorker(Guid transactionId, DateTimeLogRecord timeStamp); } } ================================================ FILE: src/Orleans.Transactions/State/TransactionalResource.cs ================================================ using System; using System.Threading.Tasks; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.State { internal class TransactionalResource : ITransactionalResource where TState : class, new() { private readonly TransactionQueue queue; public TransactionalResource(TransactionQueue queue) { this.queue = queue; } public async Task CommitReadOnly(Guid transactionId, AccessCounter accessCount, DateTime timeStamp) { // validate the lock var (status, record) = await this.queue.RWLock.ValidateLock(transactionId, accessCount); var valid = status == TransactionalStatus.Ok; record.Timestamp = timeStamp; record.Role = CommitRole.ReadOnly; record.PromiseForTA = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (!valid) { await this.queue.NotifyOfAbort(record, status, exception: null); } else { this.queue.Clock.Merge(record.Timestamp); } this.queue.RWLock.Notify(); return await record.PromiseForTA.Task; } public async Task Abort(Guid transactionId) { await this.queue.Ready(); // release the lock this.queue.RWLock.Rollback(transactionId); this.queue.RWLock.Notify(); } public async Task Cancel(Guid transactionId, DateTime timeStamp, TransactionalStatus status) { await this.queue.Ready(); await this.queue.NotifyOfCancel(transactionId, timeStamp, status); } public async Task Confirm(Guid transactionId, DateTime timeStamp) { await this.queue.Ready(); await this.queue.NotifyOfConfirm(transactionId, timeStamp); } public async Task Prepare(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager) { await this.queue.NotifyOfPrepare(transactionId, accessCount, timeStamp, transactionManager); } } } ================================================ FILE: src/Orleans.Transactions/State/TransactionalState.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.State; using Orleans.Configuration; using Orleans.Timers.Internal; namespace Orleans.Transactions { /// /// Stateful facet that respects Orleans transaction semantics /// public partial class TransactionalState : ITransactionalState, ILifecycleParticipant where TState : class, new() { private readonly TransactionalStateConfiguration config; private readonly IGrainContext context; private readonly ITransactionDataCopier copier; private readonly Dictionary copiers; private readonly IGrainRuntime grainRuntime; private readonly ILogger logger; private readonly ActivationLifetime activationLifetime; private ParticipantId participantId; private TransactionQueue queue; public string CurrentTransactionId => TransactionContext.GetRequiredTransactionInfo().Id; private bool detectReentrancy; public TransactionalState( TransactionalStateConfiguration transactionalStateConfiguration, IGrainContextAccessor contextAccessor, ITransactionDataCopier copier, IGrainRuntime grainRuntime, ILogger> logger) { this.config = transactionalStateConfiguration; this.context = contextAccessor.GrainContext; this.copier = copier; this.grainRuntime = grainRuntime; this.logger = logger; this.copiers = new Dictionary(); this.copiers.Add(typeof(TState), copier); this.activationLifetime = new ActivationLifetime(this.context); } /// /// Read the current state. /// public Task PerformRead(Func operation) { if (detectReentrancy) { throw new LockRecursionException("Cannot perform a read operation from within another operation"); } var info = TransactionContext.GetRequiredTransactionInfo(); LogTraceStartRead(info); info.Participants.TryGetValue(this.participantId, out var recordedaccesses); // schedule read access to happen under the lock return this.queue.RWLock.EnterLock(info.TransactionId, info.Priority, recordedaccesses, true, () => { // check if our record is gone because we expired while waiting if (!this.queue.RWLock.TryGetRecord(info.TransactionId, out TransactionRecord record)) { throw new OrleansCascadingAbortException(info.TransactionId.ToString()); } // merge the current clock into the transaction time stamp record.Timestamp = this.queue.Clock.MergeUtcNow(info.TimeStamp); if (record.State == null) { this.queue.GetMostRecentState(out record.State, out record.SequenceNumber); } LogDebugUpdateLockRead(record.SequenceNumber, record.TransactionId, new(record.Timestamp)); // record this read in the transaction info data structure info.RecordRead(this.participantId, record.Timestamp); // perform the read TResult result = default; try { detectReentrancy = true; result = CopyResult(operation(record.State)); } finally { LogTraceEndRead(info, result, record.State); detectReentrancy = false; } return result; }); } /// public Task PerformUpdate(Func updateAction) { if (updateAction == null) throw new ArgumentNullException(nameof(updateAction)); if (detectReentrancy) { throw new LockRecursionException("Cannot perform an update operation from within another operation"); } var info = TransactionContext.GetRequiredTransactionInfo(); LogTraceStartWrite(info); if (info.IsReadOnly) { throw new OrleansReadOnlyViolatedException(info.Id); } info.Participants.TryGetValue(this.participantId, out var recordedaccesses); return this.queue.RWLock.EnterLock(info.TransactionId, info.Priority, recordedaccesses, false, () => { // check if we expired while waiting if (!this.queue.RWLock.TryGetRecord(info.TransactionId, out TransactionRecord record)) { throw new OrleansCascadingAbortException(info.TransactionId.ToString()); } // merge the current clock into the transaction time stamp record.Timestamp = this.queue.Clock.MergeUtcNow(info.TimeStamp); // link to the latest state if (record.State == null) { this.queue.GetMostRecentState(out record.State, out record.SequenceNumber); } // if this is the first write, make a deep copy of the state if (!record.HasCopiedState) { record.State = this.copier.DeepCopy(record.State); record.SequenceNumber++; record.HasCopiedState = true; } LogDebugUpdateLockWrite(record.SequenceNumber, record.TransactionId, new(record.Timestamp)); // record this write in the transaction info data structure info.RecordWrite(this.participantId, record.Timestamp); // perform the write try { detectReentrancy = true; return CopyResult(updateAction(record.State)); } finally { LogTraceEndWrite(info, record.TransactionId, new(record.Timestamp)); detectReentrancy = false; } } ); } public void Participate(IGrainLifecycle lifecycle) { lifecycle.Subscribe>(GrainLifecycleStage.SetupState, (ct) => OnSetupState(SetupResourceFactory, ct)); } private static void SetupResourceFactory(IGrainContext context, string stateName, TransactionQueue queue) { // Add resources factory to the grain context context.RegisterResourceFactory(stateName, () => new TransactionalResource(queue)); // Add tm factory to the grain context context.RegisterResourceFactory(stateName, () => new TransactionManager(queue)); } internal async Task OnSetupState(Action> setupResourceFactory, CancellationToken ct) { if (ct.IsCancellationRequested) return; this.participantId = new ParticipantId(this.config.StateName, this.context.GrainReference, this.config.SupportedRoles); var storageFactory = this.context.ActivationServices.GetRequiredService(); ITransactionalStateStorage storage = storageFactory.Create(this.config.StorageName, this.config.StateName); // setup transaction processing pipe void deactivate() => grainRuntime.DeactivateOnIdle(context); var options = this.context.ActivationServices.GetRequiredService>(); var clock = this.context.ActivationServices.GetRequiredService(); var timerManager = this.context.ActivationServices.GetRequiredService(); this.queue = new TransactionQueue(options, this.participantId, deactivate, storage, clock, logger, timerManager, this.activationLifetime); setupResourceFactory(this.context, this.config.StateName, queue); // recover state await this.queue.NotifyOfRestore(); } private TResult CopyResult(TResult result) { ITransactionDataCopier resultCopier; if (!this.copiers.TryGetValue(typeof(TResult), out object cp)) { resultCopier = this.context.ActivationServices.GetRequiredService>(); this.copiers.Add(typeof(TResult), resultCopier); } else { resultCopier = (ITransactionDataCopier)cp; } return resultCopier.DeepCopy(result); } [LoggerMessage( Level = LogLevel.Trace, Message = "StartRead {Info}" )] private partial void LogTraceStartRead(TransactionInfo info); private readonly struct DateTimeLogRecord(DateTime ts) { public override string ToString() => ts.ToString("o"); } [LoggerMessage( Level = LogLevel.Debug, Message = "Update-lock read v{SequenceNumber} {TransactionId} {Timestamp}" )] private partial void LogDebugUpdateLockRead(long sequenceNumber, Guid transactionId, DateTimeLogRecord timestamp); [LoggerMessage( Level = LogLevel.Trace, Message = "EndRead {Info} {Result} {State}" )] private partial void LogTraceEndRead(TransactionInfo info, object result, TState state); [LoggerMessage( Level = LogLevel.Trace, Message = "StartWrite {Info}" )] private partial void LogTraceStartWrite(TransactionInfo info); [LoggerMessage( Level = LogLevel.Debug, Message = "Update-lock write v{SequenceNumber} {TransactionId} {Timestamp}" )] private partial void LogDebugUpdateLockWrite(long sequenceNumber, Guid transactionId, DateTimeLogRecord timestamp); [LoggerMessage( Level = LogLevel.Trace, Message = "EndWrite {Info} {TransactionId} {Timestamp}" )] private partial void LogTraceEndWrite(TransactionInfo info, Guid transactionId, DateTimeLogRecord timestamp); } } ================================================ FILE: src/Orleans.Transactions/State/TransactionalStateFactory.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionalStateFactory : ITransactionalStateFactory { private readonly IGrainContextAccessor contextAccessor; public TransactionalStateFactory(IGrainContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; } public ITransactionalState Create(TransactionalStateConfiguration config) where TState : class, new() { var currentContext = this.contextAccessor.GrainContext; TransactionalState transactionalState = ActivatorUtilities.CreateInstance>(currentContext.ActivationServices, config, this.contextAccessor); transactionalState.Participate(currentContext.ObservableLifecycle); return transactionalState; } public static JsonSerializerSettings GetJsonSerializerSettings(IServiceProvider serviceProvider) { var serializerSettings = OrleansJsonSerializerSettings.GetDefaultSerializerSettings(serviceProvider); serializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None; return serializerSettings; } } } ================================================ FILE: src/Orleans.Transactions/State/TransactionalStateOptions.cs ================================================ using System; namespace Orleans.Configuration { public class TransactionalStateOptions { // max time a group can occupy the lock public TimeSpan LockTimeout { get; set; } = DefaultLockTimeout; public static TimeSpan DefaultLockTimeout = TimeSpan.FromSeconds(8); // max time the TM will wait for prepare phase to complete public TimeSpan PrepareTimeout { get; set; } = DefaultPrepareTimeout; public static TimeSpan DefaultPrepareTimeout => TimeSpan.FromSeconds(20); // max time a transaction will wait for the lock to become available public TimeSpan LockAcquireTimeout { get; set; } = DefaultLockAcquireTimeout; public static TimeSpan DefaultLockAcquireTimeout => TimeSpan.FromSeconds(10); public TimeSpan RemoteTransactionPingFrequency { get; set; } = DefaultRemoteTransactionPingFrequency; public static TimeSpan DefaultRemoteTransactionPingFrequency = TimeSpan.FromSeconds(60); public TimeSpan ConfirmationRetryDelay { get; set; } = DefaultConfirmationRetryDelay; private static TimeSpan DefaultConfirmationRetryDelay => TimeSpan.FromSeconds(30); public static int ConfirmationRetryLimit { get; set; } = DefaultConfirmationRetryLimit; public const int DefaultConfirmationRetryLimit = 3; public int MaxLockGroupSize { get; set; } = DefaultMaxLockGroupSize; public const int DefaultMaxLockGroupSize = 20; } } ================================================ FILE: src/Orleans.Transactions/State/TransactionalStateStorageProviderWrapper.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Orleans.Core; using Orleans.Runtime; using Orleans.Storage; using Orleans.Transactions.Abstractions; #nullable enable namespace Orleans.Transactions { internal sealed class TransactionalStateStorageProviderWrapper : ITransactionalStateStorage where TState : class, new() { private readonly IGrainStorage grainStorage; private readonly IGrainContext context; private readonly string stateName; private StateStorageBridge>? stateStorage; [MemberNotNull(nameof(stateStorage))] private StateStorageBridge> StateStorage => stateStorage ??= GetStateStorage(); public TransactionalStateStorageProviderWrapper(IGrainStorage grainStorage, string stateName, IGrainContext context) { this.grainStorage = grainStorage; this.context = context; this.stateName = stateName; } public async Task> Load() { await this.StateStorage.ReadStateAsync(); var state = stateStorage.State; return new TransactionalStorageLoadResponse(stateStorage.Etag, state.CommittedState, state.CommittedSequenceId, state.Metadata, state.PendingStates); } public async Task Store(string expectedETag, TransactionalStateMetaData metadata, List> statesToPrepare, long? commitUpTo, long? abortAfter) { if (this.StateStorage.Etag != expectedETag) throw new ArgumentException(nameof(expectedETag), "Etag does not match"); var state = stateStorage.State; state.Metadata = metadata; var pendinglist = state.PendingStates; // abort if (abortAfter.HasValue && pendinglist.Count != 0) { var pos = pendinglist.FindIndex(t => t.SequenceId > abortAfter.Value); if (pos != -1) { pendinglist.RemoveRange(pos, pendinglist.Count - pos); } } // prepare if (statesToPrepare?.Count > 0) { foreach (var p in statesToPrepare) { var pos = pendinglist.FindIndex(t => t.SequenceId >= p.SequenceId); if (pos == -1) { pendinglist.Add(p); //append } else if (pendinglist[pos].SequenceId == p.SequenceId) { pendinglist[pos] = p; //replace } else { pendinglist.Insert(pos, p); //insert } } } // commit if (commitUpTo.HasValue && commitUpTo.Value > state.CommittedSequenceId) { var pos = pendinglist.FindIndex(t => t.SequenceId == commitUpTo.Value); if (pos != -1) { var committedState = pendinglist[pos]; state.CommittedSequenceId = committedState.SequenceId; state.CommittedState = committedState.State; pendinglist.RemoveRange(0, pos + 1); } else { throw new InvalidOperationException($"Transactional state corrupted. Missing prepare record (SequenceId={commitUpTo.Value}) for committed transaction."); } } await stateStorage.WriteStateAsync(); return stateStorage.Etag!; } private StateStorageBridge> GetStateStorage() { return new(this.stateName, context, grainStorage); } } [Serializable] [GenerateSerializer] public sealed class TransactionalStateRecord where TState : class, new() { [Id(0)] public TState CommittedState { get; set; } = new TState(); [Id(1)] public long CommittedSequenceId { get; set; } [Id(2)] public TransactionalStateMetaData Metadata { get; set; } = new TransactionalStateMetaData(); [Id(3)] public List> PendingStates { get; set; } = new List>(); } } ================================================ FILE: src/Orleans.Transactions/TOC/TocTransactionQueue.cs ================================================ using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Timers.Internal; using Orleans.Transactions.Abstractions; using Orleans.Transactions.State; namespace Orleans.Transactions.TOC { internal class TocTransactionQueue : TransactionQueue.OperationState> where TService : class { private readonly TService service; public TocTransactionQueue( TService service, IOptions options, ParticipantId resource, Action deactivate, ITransactionalStateStorage.OperationState> storage, IClock clock, ILogger logger, ITimerManager timerManager, IActivationLifetime activationLifetime) : base(options, resource, deactivate, storage, clock, logger, timerManager, activationLifetime) { this.service = service; } protected override void OnLocalCommit(TransactionRecord.OperationState> entry) { base.storageBatch.AddStorePreCondition(() => entry.State.Operation.Commit(entry.TransactionId, this.service)); base.OnLocalCommit(entry); } } } ================================================ FILE: src/Orleans.Transactions/TOC/TransactionCommitter.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Timers.Internal; using Orleans.Transactions.Abstractions; using Orleans.Transactions.State; using Orleans.Transactions.TOC; namespace Orleans.Transactions { public partial class TransactionCommitter : ITransactionCommitter, ILifecycleParticipant where TService : class { private readonly ITransactionCommitterConfiguration config; private readonly IGrainContext context; private readonly ITransactionDataCopier copier; private readonly IGrainRuntime grainRuntime; private readonly ActivationLifetime activationLifetime; private readonly ILogger logger; private ParticipantId participantId; private TransactionQueue queue; private bool detectReentrancy; public TransactionCommitter( ITransactionCommitterConfiguration config, IGrainContextAccessor contextAccessor, ITransactionDataCopier copier, IGrainRuntime grainRuntime, ILogger> logger) { this.config = config; this.context = contextAccessor.GrainContext; this.copier = copier; this.grainRuntime = grainRuntime; this.logger = logger; this.activationLifetime = new ActivationLifetime(this.context); } /// public Task OnCommit(ITransactionCommitOperation operation) { if (operation == null) throw new ArgumentNullException(nameof(operation)); if (detectReentrancy) { throw new LockRecursionException("cannot perform an update operation from within another operation"); } var info = TransactionContext.GetRequiredTransactionInfo(); LogTraceStartWrite(info); if (info.IsReadOnly) { throw new OrleansReadOnlyViolatedException(info.Id); } info.Participants.TryGetValue(this.participantId, out var recordedaccesses); return this.queue.RWLock.EnterLock(info.TransactionId, info.Priority, recordedaccesses, false, () => { // check if we expired while waiting if (!this.queue.RWLock.TryGetRecord(info.TransactionId, out TransactionRecord record)) { throw new OrleansCascadingAbortException(info.TransactionId.ToString()); } // merge the current clock into the transaction time stamp record.Timestamp = this.queue.Clock.MergeUtcNow(info.TimeStamp); // link to the latest state if (record.State == null) { this.queue.GetMostRecentState(out record.State, out record.SequenceNumber); } // if this is the first write, make a deep copy of the state if (!record.HasCopiedState) { record.State = this.copier.DeepCopy(record.State); record.SequenceNumber++; record.HasCopiedState = true; } LogDebugUpdateLockWrite(record.SequenceNumber, record.TransactionId, new(record.Timestamp)); // record this write in the transaction info data structure info.RecordWrite(this.participantId, record.Timestamp); // perform the write try { detectReentrancy = true; record.State.Operation = operation; return true; } finally { LogTraceEndWrite(info, record.TransactionId, new(record.Timestamp)); detectReentrancy = false; } } ); } public void Participate(IGrainLifecycle lifecycle) { lifecycle.Subscribe>(GrainLifecycleStage.SetupState, OnSetupState); } private async Task OnSetupState(CancellationToken ct) { if (ct.IsCancellationRequested) return; this.participantId = new ParticipantId(this.config.ServiceName, this.context.GrainReference, ParticipantId.Role.Resource | ParticipantId.Role.PriorityManager); var storageFactory = this.context.ActivationServices.GetRequiredService(); ITransactionalStateStorage storage = storageFactory.Create(this.config.StorageName, this.config.ServiceName); // setup transaction processing pipe void deactivate() => grainRuntime.DeactivateOnIdle(context); var options = this.context.ActivationServices.GetRequiredService>(); var clock = this.context.ActivationServices.GetRequiredService(); TService service = this.context.ActivationServices.GetRequiredKeyedService(this.config.ServiceName); var timerManager = this.context.ActivationServices.GetRequiredService(); this.queue = new TocTransactionQueue(service, options, this.participantId, deactivate, storage, clock, logger, timerManager, this.activationLifetime); // Add transaction manager factory to the grain context this.context.RegisterResourceFactory(this.config.ServiceName, () => new TransactionManager(this.queue)); // recover state await this.queue.NotifyOfRestore(); } [Serializable] [GenerateSerializer] public sealed class OperationState { [Id(0)] public ITransactionCommitOperation Operation { get; set; } } [LoggerMessage( Level = LogLevel.Trace, Message = "StartWrite {Info}" )] private partial void LogTraceStartWrite(TransactionInfo info); private readonly struct DateTimeLogRecord(DateTime ts) { public override string ToString() => ts.ToString("o"); } [LoggerMessage( Level = LogLevel.Debug, Message = "Update-lock write v{SequenceNumber} {TransactionId} {Timestamp}" )] private partial void LogDebugUpdateLockWrite(long sequenceNumber, Guid transactionId, DateTimeLogRecord timestamp); [LoggerMessage( Level = LogLevel.Trace, Message = "EndWrite {Info} {TransactionId} {Timestamp}" )] private partial void LogTraceEndWrite(TransactionInfo info, Guid transactionId, DateTimeLogRecord timestamp); } } ================================================ FILE: src/Orleans.Transactions/TOC/TransactionCommitterFactory.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions { public class TransactionCommitterFactory : ITransactionCommitterFactory { private readonly IGrainContextAccessor contextAccessor; public TransactionCommitterFactory(IGrainContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; } public ITransactionCommitter Create(ITransactionCommitterConfiguration config) where TService : class { var currentContext = contextAccessor.GrainContext; TransactionCommitter transactionalState = ActivatorUtilities.CreateInstance>(currentContext.ActivationServices, config, this.contextAccessor); transactionalState.Participate(currentContext.ObservableLifecycle); return transactionalState; } } } ================================================ FILE: src/Orleans.Transactions/TransactionAttribute.cs ================================================ using System; using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.CodeGeneration; using Orleans.Runtime; using Orleans.Serialization; using Orleans.Serialization.Invocation; using Orleans.Transactions; namespace Orleans { /// /// The TransactionAttribute attribute is used to mark methods that start and join transactions. /// [InvokableCustomInitializer("SetTransactionOptions")] [InvokableBaseType(typeof(GrainReference), typeof(ValueTask), typeof(TransactionRequest))] [InvokableBaseType(typeof(GrainReference), typeof(ValueTask<>), typeof(TransactionRequest<>))] [InvokableBaseType(typeof(GrainReference), typeof(Task), typeof(TransactionTaskRequest))] [InvokableBaseType(typeof(GrainReference), typeof(Task<>), typeof(TransactionTaskRequest<>))] [AttributeUsage(AttributeTargets.Method)] public sealed class TransactionAttribute : Attribute { public TransactionAttribute(TransactionOption requirement) { Requirement = requirement; } public TransactionAttribute(TransactionOptionAlias alias) { Requirement = (TransactionOption)(int)alias; } public TransactionOption Requirement { get; } [Obsolete("Use [ReadOnly] attribute instead.")] public bool ReadOnly { get; set; } } public enum TransactionOption { Suppress, // Logic is not transactional but can be called from within a transaction. If called within the context of a transaction, the context will not be passed to the call. CreateOrJoin, // Logic is transactional. If called within the context of a transaction, it will use that context, else it will create a new context. Create, // Logic is transactional and will always create a new transaction context, even if called within an existing transaction context. Join, // Logic is transactional but can only be called within the context of an existing transaction. Supported, // Logic is not transactional but supports transactions. If called within the context of a transaction, the context will be passed to the call. NotAllowed // Logic is not transactional and cannot be called from within a transaction. If called within the context of a transaction, it will throw a not supported exception. } public enum TransactionOptionAlias { Suppress = TransactionOption.Supported, Required = TransactionOption.CreateOrJoin, RequiresNew = TransactionOption.Create, Mandatory = TransactionOption.Join, Never = TransactionOption.NotAllowed, } [GenerateSerializer] public abstract class TransactionRequestBase : RequestBase, IOutgoingGrainCallFilter, IOnDeserialized { [NonSerialized] private Serializer _serializer; [NonSerialized] private ITransactionAgent _transactionAgent; private ITransactionAgent TransactionAgent => _transactionAgent ?? throw new OrleansTransactionsDisabledException(); [Id(0)] public TransactionOption TransactionOption { get; set; } [Id(1)] public TransactionInfo TransactionInfo { get; set; } [GeneratedActivatorConstructor] protected TransactionRequestBase(Serializer exceptionSerializer, IServiceProvider serviceProvider) { _serializer = exceptionSerializer; // May be null, eg on an external client. We will throw if it's null at the time of invocation. _transactionAgent = serviceProvider.GetService(); } public bool IsAmbientTransactionSuppressed => TransactionOption switch { TransactionOption.Create => true, TransactionOption.Suppress => true, _ => false }; public bool IsTransactionRequired => TransactionOption switch { TransactionOption.Create => true, TransactionOption.CreateOrJoin => true, TransactionOption.Join => true, _ => false }; protected void SetTransactionOptions(TransactionOptionAlias txOption) => SetTransactionOptions((TransactionOption)txOption); protected void SetTransactionOptions(TransactionOption txOption) { this.TransactionOption = txOption; } async Task IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext context) { var transactionInfo = SetTransactionInfo(); try { await context.Invoke(); } finally { if (context.Response is TransactionResponse txResponse) { var returnedTransactionInfo = txResponse.TransactionInfo; if (transactionInfo is { } && returnedTransactionInfo is { }) { transactionInfo.Join(returnedTransactionInfo); } if (txResponse.GetException() is { } exception) { ExceptionDispatchInfo.Throw(exception); } } } } private TransactionInfo SetTransactionInfo() { // Clear transaction info if transaction operation requires new transaction. var transactionInfo = TransactionContext.GetTransactionInfo(); // Enforce join transaction calls if (TransactionOption == TransactionOption.Join && transactionInfo == null) { throw new NotSupportedException("Call cannot be made outside of a transaction."); } // Enforce not allowed transaction calls if (TransactionOption == TransactionOption.NotAllowed && transactionInfo != null) { throw new NotSupportedException("Call cannot be made within a transaction."); } // Clear transaction context if creating a transaction or transaction is suppressed if (TransactionOption is TransactionOption.Create or TransactionOption.Suppress) { transactionInfo = null; } if (transactionInfo == null) { // if we're leaving a transaction context, make sure it's been cleared from the request context. TransactionContext.Clear(); } else { this.TransactionInfo = transactionInfo?.Fork(); } return transactionInfo; } public override async ValueTask Invoke() { Response response; var transactionInfo = this.TransactionInfo; bool startedNewTransaction = false; try { if (IsTransactionRequired && transactionInfo == null) { // TODO: this should be a configurable parameter var transactionTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(30) : TimeSpan.FromSeconds(10); // Start a new transaction var isReadOnly = this.Options.HasFlag(InvokeMethodOptions.ReadOnly); transactionInfo = await TransactionAgent.StartTransaction(isReadOnly, transactionTimeout); startedNewTransaction = true; } TransactionContext.SetTransactionInfo(transactionInfo); response = await BaseInvoke(); } catch (Exception exception) { response = Response.FromException(exception); } finally { TransactionContext.Clear(); } if (transactionInfo != null) { transactionInfo.ReconcilePending(); if (response.Exception is { } invokeException) { // Record reason for abort, if not already set. transactionInfo.RecordException(invokeException, _serializer); } OrleansTransactionException transactionException = transactionInfo.MustAbort(_serializer); // This request started the transaction, so we try to commit before returning, // or if it must abort, tell participants that it aborted if (startedNewTransaction) { if (transactionException is not null || transactionInfo.TryToCommit is false) { await TransactionAgent.Abort(transactionInfo); } else { var (status, exception) = await TransactionAgent.Resolve(transactionInfo); if (status != TransactionalStatus.Ok) { transactionException = status.ConvertToUserException(transactionInfo.Id, exception); } } } if (transactionException != null) { response = Response.FromException(transactionException); } response = TransactionResponse.Create(response, transactionInfo); } return response; } protected abstract ValueTask BaseInvoke(); public override void Dispose() { TransactionInfo = null; } void IOnDeserialized.OnDeserialized(DeserializationContext context) { _serializer = context.ServiceProvider.GetRequiredService>(); _transactionAgent = context.ServiceProvider.GetRequiredService(); } } [GenerateSerializer] public sealed class TransactionResponse : Response { [Id(0)] private Response _response; [Id(1)] public TransactionInfo TransactionInfo { get; set; } public static TransactionResponse Create(Response response, TransactionInfo transactionInfo) { return new TransactionResponse { _response = response, TransactionInfo = transactionInfo }; } public Response InnerResponse => _response; public override object Result { get { if (_response.Exception is { } exception) { ExceptionDispatchInfo.Capture(exception).Throw(); } return _response.Result; } set => _response.Result = value; } public override Exception Exception { get { // Suppress any exception here, allowing ResponseCompletionSource to complete with a Response instead of an exception. // This gives TransactionRequestBase a chance to inspect this instance and retrieve the TransactionInfo property first. // After, it will use GetException to get and throw the exeption. return null; } set => _response.Exception = value; } public Exception GetException() => _response.Exception; public override void Dispose() { TransactionInfo = null; _response.Dispose(); } public override T GetResult() => _response.GetResult(); } [SerializerTransparent] public abstract class TransactionRequest : TransactionRequestBase { protected TransactionRequest(Serializer exceptionSerializer, IServiceProvider serviceProvider) : base(exceptionSerializer, serviceProvider) { } protected sealed override ValueTask BaseInvoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { resultTask.GetAwaiter().GetResult(); return new ValueTask(Response.Completed); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(ValueTask resultTask) { try { await resultTask; return Response.Completed; } catch (Exception exception) { return Response.FromException(exception); } } // Generated protected abstract ValueTask InvokeInner(); } [SerializerTransparent] public abstract class TransactionRequest : TransactionRequestBase { protected TransactionRequest(Serializer exceptionSerializer, IServiceProvider serviceProvider) : base(exceptionSerializer, serviceProvider) { } protected sealed override ValueTask BaseInvoke() { try { var resultTask = InvokeInner(); if (resultTask.IsCompleted) { return new ValueTask(Response.FromResult(resultTask.Result)); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(ValueTask resultTask) { try { var result = await resultTask; return Response.FromResult(result); } catch (Exception exception) { return Response.FromException(exception); } } // Generated protected abstract ValueTask InvokeInner(); } [SerializerTransparent] public abstract class TransactionTaskRequest : TransactionRequestBase { protected TransactionTaskRequest(Serializer exceptionSerializer, IServiceProvider serviceProvider) : base(exceptionSerializer, serviceProvider) { } protected sealed override ValueTask BaseInvoke() { try { var resultTask = InvokeInner(); var status = resultTask.Status; if (resultTask.IsCompleted) { return new ValueTask(Response.FromResult(resultTask.GetAwaiter().GetResult())); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(Task resultTask) { try { var result = await resultTask; return Response.FromResult(result); } catch (Exception exception) { return Response.FromException(exception); } } // Generated protected abstract Task InvokeInner(); } [SerializerTransparent] public abstract class TransactionTaskRequest : TransactionRequestBase { protected TransactionTaskRequest(Serializer exceptionSerializer, IServiceProvider serviceProvider) : base(exceptionSerializer, serviceProvider) { } protected sealed override ValueTask BaseInvoke() { try { var resultTask = InvokeInner(); var status = resultTask.Status; if (resultTask.IsCompleted) { resultTask.GetAwaiter().GetResult(); return new ValueTask(Response.Completed); } return CompleteInvokeAsync(resultTask); } catch (Exception exception) { return new ValueTask(Response.FromException(exception)); } } private static async ValueTask CompleteInvokeAsync(Task resultTask) { try { await resultTask; return Response.Completed; } catch (Exception exception) { return Response.FromException(exception); } } // Generated protected abstract Task InvokeInner(); } } ================================================ FILE: src/Orleans.Transactions/TransactionContext.cs ================================================ using System.Threading; namespace Orleans.Transactions { public static class TransactionContext { private static readonly AsyncLocal CurrentContext = new(); public static TransactionInfo GetTransactionInfo() => CurrentContext.Value; public static string CurrentTransactionId => GetRequiredTransactionInfo().Id; public static TransactionInfo GetRequiredTransactionInfo() => GetTransactionInfo() ?? throw new OrleansTransactionException($"A transaction context is required for access. Did you forget a [Transaction] attribute?"); internal static void SetTransactionInfo(TransactionInfo info) { if (!ReferenceEquals(CurrentContext.Value, info)) { CurrentContext.Value = info; } } internal static void Clear() => CurrentContext.Value = null; } } ================================================ FILE: src/Orleans.Transactions/TransactionalStatus.cs ================================================ using System; namespace Orleans.Transactions { /// /// Used to propagate information about the status of a transaction. Used for transaction orchestration, for diagnostics, /// and for generating informative user exceptions /// public enum TransactionalStatus { Ok, PrepareTimeout, // TM could not finish prepare in time CascadingAbort, // a transaction this transaction depends on aborted BrokenLock, // a lock was lost due to timeout, wait-die, or failures LockValidationFailed, // during prepare, recorded accesses did not match ParticipantResponseTimeout, // TA timed out waiting for response from participants of read-only transaction TMResponseTimeout, // TA timed out waiting for response from TM StorageConflict, // storage was modified by duplicate grain activation PresumedAbort, // TM never heard of this transaction UnknownException, // an unknown exception was caught AssertionFailed, // an internal assertion was violated CommitFailure, // Unable to commit transaction } public static class TransactionalStatusExtensions { public static bool DefinitelyAborted(this TransactionalStatus status) { switch (status) { case TransactionalStatus.PrepareTimeout: case TransactionalStatus.CascadingAbort: case TransactionalStatus.BrokenLock: case TransactionalStatus.LockValidationFailed: case TransactionalStatus.ParticipantResponseTimeout: case TransactionalStatus.CommitFailure: return true; default: return false; } } public static OrleansTransactionException ConvertToUserException(this TransactionalStatus status, string transactionId, Exception exception) { switch (status) { case TransactionalStatus.PrepareTimeout: return new OrleansTransactionPrepareTimeoutException(transactionId, exception); case TransactionalStatus.CascadingAbort: return new OrleansCascadingAbortException(transactionId, exception); case TransactionalStatus.BrokenLock: return new OrleansBrokenTransactionLockException(transactionId, "before prepare", exception); case TransactionalStatus.LockValidationFailed: return new OrleansBrokenTransactionLockException(transactionId, "when validating accesses during prepare", exception); case TransactionalStatus.ParticipantResponseTimeout: return new OrleansTransactionTransientFailureException(transactionId, $"transaction agent timed out waiting for read-only transaction participant responses ({status})", exception); case TransactionalStatus.TMResponseTimeout: return new OrleansTransactionInDoubtException(transactionId, $"transaction agent timed out waiting for read-only transaction participant responses ({status})", exception); case TransactionalStatus.CommitFailure: return new OrleansTransactionAbortedException(transactionId, $"Unable to commit transaction ({status})", exception); default: return new OrleansTransactionInDoubtException(transactionId, $"failure during transaction commit, status={status}", exception); } } } } ================================================ FILE: src/Orleans.Transactions/Utilities/CausalClock.cs ================================================ using System; using System.Threading; namespace Orleans.Transactions { public class CausalClock { #if NET9_0_OR_GREATER private readonly Lock lockable = new(); #else private readonly object lockable = new(); #endif private readonly IClock clock; private long previous; public CausalClock(IClock clock) { this.clock = clock ?? throw new ArgumentNullException(nameof(clock)); } public DateTime UtcNow() { lock (this.lockable) { var ticks = previous = Math.Max(previous + 1, this.clock.UtcNow().Ticks); return new DateTime(ticks, DateTimeKind.Utc); } } public DateTime Merge(DateTime timestamp) { lock (this.lockable) { var ticks = previous = Math.Max(previous, timestamp.Ticks); return new DateTime(ticks, DateTimeKind.Utc); } } public DateTime MergeUtcNow(DateTime timestamp) { lock (this.lockable) { var ticks = previous = Math.Max(Math.Max(previous + 1, timestamp.Ticks + 1), this.clock.UtcNow().Ticks); return new DateTime(ticks, DateTimeKind.Utc); } } } } ================================================ FILE: src/Orleans.Transactions/Utilities/Clock.cs ================================================ using System; namespace Orleans.Transactions { public class Clock : IClock { public DateTime UtcNow() { return DateTime.UtcNow; } } } ================================================ FILE: src/Orleans.Transactions/Utilities/CommitQueue.cs ================================================ using System; using System.Collections.Generic; namespace Orleans.Transactions { /// /// A queue data structure that stores transaction records in a circular buffer, sorted by timestamps. /// /// internal struct CommitQueue { private const int DefaultCapacity = 8; private TransactionRecord[] _buffer; private int _pos; public int Count { get; private set; } // Indexer to provide read/write access to the file. public readonly TransactionRecord this[int index] { get { if (index < 0 || index > (Count - 1)) throw new ArgumentOutOfRangeException(nameof(index)); return _buffer[(_pos + index) % _buffer.Length]; } } public readonly IEnumerable> Elements { get { if (_buffer != null) { for (int i = 0; i < Count; i++) yield return _buffer[(_pos + i) % _buffer.Length]; } } } public readonly TransactionRecord First => _buffer[_pos]; public readonly TransactionRecord Last => _buffer[(_pos + Count - 1) % _buffer.Length]; public void Add(TransactionRecord entry) { // ensure we have room if (_buffer == null) { _buffer = new TransactionRecord[DefaultCapacity]; } else if (Count == _buffer.Length) { var newBuffer = new TransactionRecord[_buffer.Length * 2]; Array.Copy(_buffer, _pos, newBuffer, 0, _buffer.Length - _pos); Array.Copy(_buffer, 0, newBuffer, _buffer.Length - _pos, _pos); _buffer = newBuffer; _pos = 0; } if (Count > 0 && _buffer[(_pos + Count - 1) % _buffer.Length].Timestamp > entry.Timestamp) throw new ArgumentException($"elements must be added in timestamp order, but {entry.Timestamp:o} is before {_buffer[(_pos + Count - 1) % _buffer.Length].Timestamp:o}", nameof(entry)); // add the element _buffer[(_pos + Count) % _buffer.Length] = entry; Count++; } public void Clear() { for (int i = 0; i < Count; i++) _buffer[(_pos + i) % _buffer.Length] = null; Count = 0; _pos = 0; } public void RemoveFromFront(int howMany) { if (howMany <= 0) { throw new ArgumentException("Value must be greater than zero", nameof(howMany)); } if (_buffer == null || howMany > Count) { throw new ArgumentException("cannot remove more elements than are in the queue", nameof(howMany)); } // clear entries so they can ge GCd for (int i = 0; i < howMany; i++) _buffer[(_pos + i) % _buffer.Length] = null; _pos = (_pos + howMany) % _buffer.Length; Count -= howMany; } public void RemoveFromBack(int howMany) { if (howMany > 0 && (_buffer == null || howMany > Count)) throw new ArgumentException("cannot remove more elements than are in the queue", nameof(howMany)); // clear entries so they can ge GCd for (int i = 0; i < howMany; i++) _buffer[(_pos + Count - i - 1) % _buffer.Length] = null; Count -= howMany; } public readonly int Find(Guid TransactionId, DateTime key) { // do a binary search int left = 0; int right = Count; while (left < right) { int mid = (left + right) / 2; var record = _buffer[(_pos + mid) % _buffer.Length]; if (record.Timestamp < key) { left = mid + 1; continue; } else if (record.Timestamp > key) { right = mid; continue; } else if (record.TransactionId == TransactionId) { return mid; } else { // search to the left for (int j = mid - 1; j >= left; j--) { record = _buffer[(_pos + j) % _buffer.Length]; if (record.TransactionId == TransactionId) return j; if (record.Timestamp != key) break; } // search to the right for (int j = mid + 1; j < right; j++) { record = _buffer[(_pos + j) % _buffer.Length]; if (record.TransactionId == TransactionId) return j; if (record.Timestamp != key) break; } return NotFound; } } return NotFound; } private const int NotFound = -1; } } ================================================ FILE: src/Orleans.Transactions/Utilities/IClock.cs ================================================ using System; namespace Orleans.Transactions { /// /// System clock abstraction /// public interface IClock { /// /// Current time in utc /// /// DateTime UtcNow(); } } ================================================ FILE: src/Orleans.Transactions/Utilities/PeriodicAction.cs ================================================ using System; namespace Orleans.Internal.Trasactions { internal class PeriodicAction { private readonly Action action; private readonly TimeSpan period; private DateTime nextUtc; public PeriodicAction(TimeSpan period, Action action, DateTime? start = null) { this.period = period; this.nextUtc = start ?? DateTime.UtcNow + period; this.action = action; } public bool TryAction(DateTime nowUtc) { if (nowUtc < this.nextUtc) return false; this.nextUtc = nowUtc + this.period; this.action(); return true; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Consistency/ConsistencyTestGrain.cs ================================================ using Microsoft.Extensions.Logging; using Orleans.Concurrency; using Orleans.Transactions.Abstractions; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit.Consistency { [Reentrant] public class ConsistencyTestGrain : Grain, IConsistencyTestGrain { private Random random; private readonly ILogger logger; [Serializable] [GenerateSerializer] public class State { [Id(0)] public string WriterTx = ConsistencyTestHarness.InitialTx; // last writer [Id(1)] public int SeqNo; // 0, 1, 2,... } protected ITransactionalState data; public ConsistencyTestGrain( [TransactionalState("data", TransactionTestConstants.TransactionStore)] ITransactionalState data, ILoggerFactory loggerFactory ) { this.data = data; this.logger = loggerFactory.CreateLogger(nameof(ConsistencyTestGrain) + ".graincall"); } private int MyNumber => (int)(this.GetPrimaryKeyLong() % ConsistencyTestOptions.MaxGrains); private const double RecursionProbability = .1 - .9 * (1.0 / (10 * 40 - 1)); public async Task Run(ConsistencyTestOptions options, int depth, string stack, int maxgrain, DateTime stopAfter) { if (random == null) random = new Random(options.RandomSeed* options.NumGrains + MyNumber); if (depth < options.MaxDepth && random.NextDouble() < RecursionProbability) { switch (random.Next(2)) { case 0: return await Recurse(options, depth, stack, random, 10, ! options.AvoidDeadlocks, maxgrain, stopAfter); case 1: return await Recurse(options, depth, stack, random, 10, false, maxgrain, stopAfter); case 2: return await Recurse(options, depth, stack, random, 3, false, maxgrain, stopAfter); } } //if (random.Next(20 + 6 * depth) == 0) //{ // logger.LogTrace($"g{MyNumber} {data.CurrentTransactionId} {partition}.{iteration} L{depth} UserAbort"); // throw new UserAbort(); //} var txhash = stack[..stack.IndexOf(')')].GetHashCode(); var whethertoreadorwrite = (options.ReadWrite == ReadWriteDetermination.PerTransaction) ? new Random(options.RandomSeed + txhash) : (options.ReadWrite == ReadWriteDetermination.PerGrain) ? new Random(options.RandomSeed + txhash * 10000 + MyNumber) : random; try { switch (whethertoreadorwrite.Next(4)) { case 0: logger.LogTrace("g{MyNumber} {CurrentTransactionId} {Stack} Write", MyNumber, TransactionContext.CurrentTransactionId, stack); return await Write(); default: logger.LogTrace( "g{MyNumber} {CurrentTransactionId} {stack} Read", MyNumber, TransactionContext.CurrentTransactionId, stack); return await Read(); } } catch(Exception e) { logger.LogTrace("g{MyNumber} {CurrentTransactionId} {Stack} --> {ExceptionType}", MyNumber, TransactionContext.CurrentTransactionId, stack, e.GetType().Name); throw; } } private Task Read() { var txid = TransactionContext.CurrentTransactionId; return data.PerformRead((state) => { return new Observation[] { new Observation() { ExecutingTx = txid, WriterTx = state.WriterTx, Grain = MyNumber, SeqNo = state.SeqNo } }; }); } private Task Write() { var txid = TransactionContext.CurrentTransactionId; return data.PerformUpdate((state) => { var observe = new Observation[2]; observe[0] = new Observation() { ExecutingTx = txid, WriterTx = state.WriterTx, Grain = MyNumber, SeqNo = state.SeqNo }; state.WriterTx = txid; state.SeqNo++; observe[1] = new Observation() { ExecutingTx = txid, WriterTx = state.WriterTx, Grain = MyNumber, SeqNo = state.SeqNo }; return observe; }); } private async Task Recurse(ConsistencyTestOptions options, int depth, string stack, Random random, int count, bool parallel, int maxgrain, DateTime stopAfter) { logger.LogTrace("g{MyNumber} {CurrentTransactionId} {Stack} Recurse {Count} {ParallelOrSequential}", MyNumber, TransactionContext.CurrentTransactionId, stack, count, (parallel ? "par" : "seq")); try { int min = options.AvoidDeadlocks ? MyNumber : 0; int max = options.AvoidDeadlocks ? maxgrain : options.NumGrains; var tasks = new List>(); int[] targets = new int[count]; for (int i = 0; i < count; i++) targets[i] = random.Next(min, max); if (options.AvoidDeadlocks) Array.Sort(targets); for (int i = 0; i < count; i++) { var randomTarget = GrainFactory.GetGrain(options.GrainOffset + targets[i]); var maxgrainfornested = (i < count - 1) ? targets[i + 1] : max; var task = randomTarget.Run(options, depth + 1, $"{stack}.{(parallel ? 'P' : 'S')}{i}", maxgrainfornested, stopAfter); tasks.Add(task); if (!parallel) await task; if (DateTime.UtcNow > stopAfter) break; } await Task.WhenAll(tasks); var result = new HashSet(); for (int i = 0; i < count; i++) { foreach (var x in tasks[i].Result) result.Add(x); } return result.ToArray(); } catch (Exception e) { logger.LogTrace( "g{MyNumber} {CurrentTransactionId} {Stack} --> {ExceptionType}", MyNumber, TransactionContext.CurrentTransactionId, stack, e.GetType().Name); throw; } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Consistency/ConsistencyTestHarness.cs ================================================ using Orleans.Runtime; using Orleans.TestingHost; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit.Consistency { public class ConsistencyTestHarness { private readonly ConsistencyTestOptions options; private Action output; private readonly Dictionary>>> // ReaderTx tuples; private readonly HashSet succeeded; private readonly HashSet aborted; private readonly Dictionary indoubt; private bool timeoutsOccurred; private readonly bool tolerateUnknownExceptions; private readonly IGrainFactory grainFactory; private readonly Dictionary> orderEdges = new Dictionary>(); private readonly Dictionary marks = new Dictionary(); public ConsistencyTestHarness( IGrainFactory grainFactory, int numGrains, int seed, bool avoidDeadlocks, bool avoidTimeouts, ReadWriteDetermination readWrite, bool tolerateUnknownExceptions) { this.grainFactory = grainFactory; numGrains.Should().BeLessThan(ConsistencyTestOptions.MaxGrains); this.options = new ConsistencyTestOptions() { AvoidDeadlocks = avoidDeadlocks, ReadWrite = readWrite, MaxDepth = 5, NumGrains = numGrains, RandomSeed = seed, AvoidTimeouts = avoidTimeouts, GrainOffset = (DateTime.UtcNow.Ticks & 0xFFFFFFFF) * ConsistencyTestOptions.MaxGrains, }; this.tuples = new Dictionary>>>(); this.succeeded = new HashSet(); this.aborted = new HashSet(); this.indoubt = new Dictionary(); // determine what to check for in the end this.tolerateUnknownExceptions = tolerateUnknownExceptions; } public const string InitialTx = "initial"; public int NumAborted => aborted.Count; public async Task RunRandomTransactionSequence(int partition, int count, IGrainFactory grainFactory, Action output) { this.output = output; var localRandom = new Random(options.RandomSeed + partition); for (int i = 0; i < count; i++) { var target = localRandom.Next(options.NumGrains); output($"({partition},{i}) g{target}"); try { var targetgrain = grainFactory.GetGrain(options.GrainOffset + target); var stopAfter = options.AvoidTimeouts ? DateTime.UtcNow + TimeSpan.FromSeconds(22) : DateTime.MaxValue; var result = await targetgrain.Run(options, 0, $"({partition},{i})", options.NumGrains, stopAfter); if (result.Length > 0) { var id = result[0].ExecutingTx; lock (succeeded) succeeded.Add(id); output($"{partition}.{i} g{target} -> {result.Length} tuples"); foreach (var tuple in result) { tuple.ExecutingTx.Should().BeEquivalentTo(id); // all effects of this transaction must have same id lock (tuples) { if (!tuples.TryGetValue(tuple.Grain, out var versions)) { tuples.Add(tuple.Grain, versions = new SortedDictionary>>()); } if (!versions.TryGetValue(tuple.SeqNo, out var writers)) { versions.Add(tuple.SeqNo, writers = new Dictionary>()); } if (!writers.TryGetValue(tuple.WriterTx, out var readers)) { writers.Add(tuple.WriterTx, readers = new HashSet()); } readers.Add(tuple.ExecutingTx); } } } } catch (OrleansTransactionAbortedException e) { output($"{partition}.{i} g{target} -> aborted {e.GetType().Name} {e.InnerException} {e.TransactionId}"); lock (aborted) aborted.Add(e.TransactionId); } catch (OrleansTransactionInDoubtException f) { output($"{partition}.{i} g{target} -> in doubt {f.TransactionId}"); lock (indoubt) indoubt.Add(f.TransactionId, f.Message); } catch (System.TimeoutException) { output($"{partition}.{i} g{target} -> timeout"); timeoutsOccurred = true; } catch (OrleansException o) { if (o.InnerException is RandomlyInjectedStorageException) output($"{partition}.{i} g{target} -> injected fault"); else throw; } } } public void CheckConsistency(bool tolerateGenericTimeouts = false, bool tolerateUnknownExceptions = false) { foreach (var grainKvp in tuples) { var pos = 0; void fail(string msg) { foreach (var kvp1 in grainKvp.Value) foreach (var kvp2 in kvp1.Value) foreach (var r in kvp2.Value) output($"g{grainKvp.Key} v{kvp1.Key} w:{kvp2.Key} a:{r}"); true.Should().BeFalse(msg); } HashSet readersOfPreviousVersion = new HashSet(); foreach (var seqnoKvp in grainKvp.Value) { var seqno = seqnoKvp.Key; if (pos++ != seqno && indoubt.Count == 0 && !timeoutsOccurred) fail($"g{grainKvp.Key} is missing version v{pos - 1}, found v{seqno} instead"); var writers = seqnoKvp.Value; if (writers.Count != 1) fail($"g{grainKvp.Key} v{seqno} has multiple writers {string.Join(",", writers.Keys)}"); var writer = writers.First().Key; var readers = writers.First().Value; if (seqno == 0) { if (writer != InitialTx) fail($"g{grainKvp.Key} v{seqno} not written by {InitialTx}"); } else { if (aborted.Contains(writer)) fail($"g{grainKvp.Key} v{seqno} written by aborted transaction {writer}"); if (!timeoutsOccurred && !(succeeded.Contains(writer) || indoubt.ContainsKey(writer))) fail($"g{grainKvp.Key} v{seqno} written by unknown transaction {writer}"); if (indoubt.Count == 0 && !timeoutsOccurred && !readers.Contains(writer)) fail($"g{grainKvp.Key} v{seqno} writer {writer} missing"); } // add edges from previous readers to this write foreach (var r in readersOfPreviousVersion) if (r != writer) { if (!orderEdges.TryGetValue(r, out var readedges)) orderEdges[r] = readedges = new HashSet(); readedges.Add(writer); } if (!orderEdges.TryGetValue(writer, out var writeedges)) orderEdges[writer] = writeedges = new HashSet(); foreach (var r in readers) if (r != writer) { if (!succeeded.Contains(r)) fail($"g{grainKvp.Key} v{seqno} read by aborted transaction {r}"); writeedges.Add(r); } readersOfPreviousVersion = readers; } } // due a DFS to find cycles in the ordered-before graph (= violation of serializability) DFS(); // report unknown exceptions if (!tolerateUnknownExceptions) foreach (var kvp in indoubt) if (kvp.Value.Contains("failure during transaction commit")) true.Should().BeFalse($"exception during commit {kvp.Key} {kvp.Value}"); // report timeout exceptions if (!tolerateGenericTimeouts && timeoutsOccurred) true.Should().BeFalse($"generic timeout exception caught"); } private void DFS() { foreach (var kvp in orderEdges) if (!marks.ContainsKey(kvp.Key)) { var cycleFound = Visit(kvp.Key, kvp.Value); cycleFound.Should().BeFalse($"found serializability violation"); } } private bool Visit(string node, HashSet edges) { if (marks.TryGetValue(node, out var mark)) { if (mark) { return false; } else { output($"!!! CYCLE FOUND:"); output($"{node}"); return true; } } else { marks[node] = false; foreach (var n in edges) if (orderEdges.TryGetValue(n, out var edges2)) { if (Visit(n, edges2)) { output($"{node}"); return true; } } marks[node] = true; return false; } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Consistency/ConsistencyTestOptions.cs ================================================ using System; namespace Orleans.Transactions.TestKit.Consistency { [Serializable] [GenerateSerializer] public class ConsistencyTestOptions { [Id(0)] public int RandomSeed { get; set; } = 0; [Id(1)] public int NumGrains { get; set; } = 50; [Id(2)] public int MaxDepth { get; set; } = 5; [Id(3)] public bool AvoidDeadlocks { get; set; } = true; [Id(4)] public bool AvoidTimeouts { get; set; } = true; [Id(5)] public ReadWriteDetermination ReadWrite { get; set; } = ReadWriteDetermination.PerGrain; [Id(6)] public long GrainOffset { get; set; } public const int MaxGrains = 100000; } public enum ReadWriteDetermination { PerTransaction, PerGrain, PerAccess } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Consistency/IConsistencyTestGrain.cs ================================================ using System; using System.Runtime.Serialization; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit.Consistency { public interface IConsistencyTestGrain : IGrainWithIntegerKey { [Transaction(TransactionOption.CreateOrJoin)] Task Run(ConsistencyTestOptions options, int depth, string stack, int max, DateTime stopAfter); } [Serializable] [GenerateSerializer] public class UserAbort : Exception { public UserAbort() : base("User aborted transaction") { } [Obsolete] protected UserAbort(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Consistency/Observation.cs ================================================ using System; namespace Orleans.Transactions.TestKit.Consistency { [Serializable] [GenerateSerializer] public struct Observation { [Id(0)] public int Grain { get; set; } [Id(1)] public int SeqNo { get; set; } [Id(2)] public string WriterTx { get; set; } [Id(3)] public string ExecutingTx { get; set; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/FaultInjectionAzureTableTransactionStateStorage.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.AzureStorage; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Orleans.Transactions.TestKit { public class FaultInjectionAzureTableTransactionStateStorage : ITransactionalStateStorage where TState : class, new() { private readonly AzureTableTransactionalStateStorage stateStorage; private readonly ITransactionFaultInjector faultInjector; public FaultInjectionAzureTableTransactionStateStorage(ITransactionFaultInjector faultInjector, AzureTableTransactionalStateStorage azureStateStorage) { this.faultInjector = faultInjector; this.stateStorage = azureStateStorage; } public Task> Load() { return this.stateStorage.Load(); } public async Task Store( string expectedETag, TransactionalStateMetaData metadata, // a list of transactions to prepare. List> statesToPrepare, // if non-null, commit all pending transaction up to and including this sequence number. long? commitUpTo, // if non-null, abort all pending transactions with sequence numbers strictly larger than this one. long? abortAfter ) { faultInjector.BeforeStore(); var result = await this.stateStorage.Store(expectedETag, metadata, statesToPrepare, commitUpTo, abortAfter); faultInjector.AfterStore(); return result; } } public class FaultInjectionAzureTableTransactionStateStorageFactory : ITransactionalStateStorageFactory, ILifecycleParticipant { private readonly AzureTableTransactionalStateStorageFactory factory; public static ITransactionalStateStorageFactory Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); var azureFactory = ActivatorUtilities.CreateInstance(services, name, optionsMonitor.Get(name)); return new FaultInjectionAzureTableTransactionStateStorageFactory(azureFactory); } public FaultInjectionAzureTableTransactionStateStorageFactory( AzureTableTransactionalStateStorageFactory factory) { this.factory = factory; } public ITransactionalStateStorage Create(string stateName, IGrainContext context) where TState : class, new() { var azureStateStorage = this.factory.Create(stateName, context); return ActivatorUtilities.CreateInstance>( context.ActivationServices, azureStateStorage); } public void Participate(ISiloLifecycle lifecycle) { this.factory.Participate(lifecycle); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/FaultInjectionTransactionCoordinatorGrain.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { public interface IFaultInjectionTransactionCoordinatorGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Create)] Task MultiGrainSet(List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainAddAndFaultInjection(List grains, int numberToAdd, FaultInjectionControl faultInjection = null); } public class FaultInjectionTransactionCoordinatorGrain : Grain, IFaultInjectionTransactionCoordinatorGrain { public Task MultiGrainSet(List grains, int newValue) { return Task.WhenAll(grains.Select(g => g.Set(newValue))); } public Task MultiGrainAddAndFaultInjection(List grains, int numberToAdd, FaultInjectionControl faultInjection = null) { return Task.WhenAll(grains.Select(g => g.Add(numberToAdd, faultInjection))); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/FaultInjectionTransactionReource.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.State; namespace Orleans.Transactions.TestKit { internal class FaultInjectionTransactionManager : ITransactionManager where TState : class, new() { private readonly TransactionManager tm; private readonly IGrainRuntime grainRuntime; private readonly IGrainContext context; private readonly FaultInjectionControl faultInjectionControl; private readonly ILogger logger; private readonly IControlledTransactionFaultInjector faultInjector; public FaultInjectionTransactionManager(IControlledTransactionFaultInjector faultInjector, FaultInjectionControl faultInjectionControl, TransactionManager tm, IGrainContext activationContext, ILogger logger, IGrainRuntime grainRuntime) { this.grainRuntime = grainRuntime; this.tm = tm; this.faultInjectionControl = faultInjectionControl; this.logger = logger; this.context = activationContext; this.faultInjector = faultInjector; } public async Task PrepareAndCommit(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, List writeParticipants, int totalParticipants) { this.logger.LogInformation( "Grain {GrainInstance} started PrepareAndCommit transaction {TransactionId}", context.GrainInstance, transactionId); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.BeforePrepareAndCommit) { if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionBeforeStore) this.faultInjector.InjectBeforeStore = true; if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionAfterStore) this.faultInjector.InjectAfterStore = true; this.logger.LogInformation( "Grain {GrainInstance} injected fault before transaction {TransactionId} PrepareAndCommit, with fault type {FaultInjectionType}", faultInjectionControl.FaultInjectionType, context.GrainInstance, transactionId); } var result = await this.tm.PrepareAndCommit(transactionId, accessCount, timeStamp, writeParticipants, totalParticipants); if (this.faultInjectionControl?.FaultInjectionPhase == TransactionFaultInjectPhase.AfterPrepareAndCommit && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} PrepareAndCommit", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); return result; } public async Task Prepared(Guid transactionId, DateTime timeStamp, ParticipantId participant, TransactionalStatus status) { this.logger.LogInformation( "Grain {GrainInstance} started Prepared transaction {TransactionId}", context.GrainInstance, transactionId); await this.tm.Prepared(transactionId, timeStamp, participant, status); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterPrepared && this.faultInjectionControl?.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} Prepared", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } public async Task Ping(Guid transactionId, DateTime timeStamp, ParticipantId participant) { this.logger.LogInformation("Grain {GrainInstance} started Ping transaction {TransactionId}", context.GrainInstance, transactionId); await this.tm.Ping(transactionId, timeStamp, participant); if (this.faultInjectionControl?.FaultInjectionPhase == TransactionFaultInjectPhase.AfterPing && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} Ping", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } } internal class FaultInjectionTransactionalResource : ITransactionalResource where TState : class, new() { private readonly IGrainRuntime grainRuntime; private readonly IGrainContext context; private readonly FaultInjectionControl faultInjectionControl; private readonly TransactionalResource tResource; private readonly IControlledTransactionFaultInjector faultInjector; private readonly ILogger logger; public FaultInjectionTransactionalResource(IControlledTransactionFaultInjector faultInjector, FaultInjectionControl faultInjectionControl, TransactionalResource tResource, IGrainContext activationContext, ILogger logger, IGrainRuntime grainRuntime) { this.grainRuntime = grainRuntime; this.tResource = tResource; this.faultInjectionControl = faultInjectionControl; this.logger = logger; this.faultInjector = faultInjector; this.context = activationContext; } public async Task CommitReadOnly(Guid transactionId, AccessCounter accessCount, DateTime timeStamp) { this.logger.LogInformation( "Grain {GrainInstance} started CommitReadOnly transaction {TransactionId}", context.GrainInstance, transactionId); var result = await this.tResource.CommitReadOnly(transactionId, accessCount, timeStamp); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterCommitReadOnly && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} CommitReadOnly", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); return result; } public async Task Abort(Guid transactionId) { this.logger.LogInformation( "Grain {GrainInstance} aborting transaction {TransactionId}", context.GrainInstance, transactionId); await this.tResource.Abort(transactionId); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterAbort && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} abort", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } public async Task Cancel(Guid transactionId, DateTime timeStamp, TransactionalStatus status) { this.logger.LogInformation("Grain {GrainInstance} canceling transaction {TransactionId}", context.GrainInstance, transactionId); await this.tResource.Cancel(transactionId, timeStamp, status); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterCancel && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} cancel", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } public async Task Confirm(Guid transactionId, DateTime timeStamp) { this.logger.LogInformation( "Grain {GrainInstance} started Confirm transaction {TransactionId}", context.GrainInstance, transactionId); if (this.faultInjectionControl?.FaultInjectionPhase == TransactionFaultInjectPhase.BeforeConfirm) { if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionBeforeStore) this.faultInjector.InjectBeforeStore = true; if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionAfterStore) this.faultInjector.InjectAfterStore = true; this.logger.LogInformation( "Grain {GrainInstance} injected fault before transaction {TransactionId} Confirm, with fault type {FaultInjectionType}", faultInjectionControl.FaultInjectionType, context.GrainInstance, transactionId); } await this.tResource.Confirm(transactionId, timeStamp); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterConfirm && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation( "Grain {GrainInstance} deactivating after transaction {TransactionId} Confirm", context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } public async Task Prepare(Guid transactionId, AccessCounter accessCount, DateTime timeStamp, ParticipantId transactionManager) { this.logger.LogInformation( "Grain {GrainInstance} started Prepare transaction {TransactionId}", context.GrainInstance, transactionId); if (this.faultInjectionControl?.FaultInjectionPhase == TransactionFaultInjectPhase.BeforePrepare) { if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionBeforeStore) this.faultInjector.InjectBeforeStore = true; if (this.faultInjectionControl.FaultInjectionType == FaultInjectionType.ExceptionAfterStore) this.faultInjector.InjectAfterStore = true; this.logger.LogInformation( "Grain {GrainInstance} injected fault before transaction {TransactionId} Prepare, with fault type {FaultInjectionType}", this.context.GrainInstance, transactionId, faultInjectionControl.FaultInjectionType); } await this.tResource.Prepare(transactionId, accessCount, timeStamp, transactionManager); if (this.faultInjectionControl.FaultInjectionPhase == TransactionFaultInjectPhase.AfterPrepare && this.faultInjectionControl.FaultInjectionType == FaultInjectionType.Deactivation) { this.grainRuntime.DeactivateOnIdle(context); this.logger.LogInformation("Grain {GrainInstance} deactivating after transaction {TransactionId} Prepare", this.context.GrainInstance, transactionId); } this.faultInjectionControl.Reset(); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/FaultInjectionTransactionState.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.State; namespace Orleans.Transactions.TestKit { [GenerateSerializer] public class FaultInjectionControl { [Id(0)] public TransactionFaultInjectPhase FaultInjectionPhase = TransactionFaultInjectPhase.None; [Id(1)] public FaultInjectionType FaultInjectionType = FaultInjectionType.None; public void Reset() { this.FaultInjectionType = FaultInjectionType.None; this.FaultInjectionPhase = TransactionFaultInjectPhase.None; } } [GenerateSerializer] public enum TransactionFaultInjectPhase { None, //deactivation injection phase AfterCommitReadOnly, AfterPrepare, AfterPrepareAndCommit, AfterAbort, AfterPrepared, AfterCancel, AfterConfirm, AfterPing, //storage exception injection phase BeforeConfirm, BeforePrepare, BeforePrepareAndCommit } public enum FaultInjectionType { None, Deactivation, ExceptionBeforeStore, ExceptionAfterStore } public interface IFaultInjectionTransactionalState : ITransactionalState where TState : class, new() { FaultInjectionControl FaultInjectionControl { get; set; } } internal class FaultInjectionTransactionalState : IFaultInjectionTransactionalState, ILifecycleParticipant where TState : class, new() { private readonly IGrainRuntime grainRuntime; private readonly TransactionalState txState; private readonly ILogger logger; public FaultInjectionControl FaultInjectionControl { get; set; } private readonly IControlledTransactionFaultInjector faultInjector; public string CurrentTransactionId => this.txState.CurrentTransactionId; public FaultInjectionTransactionalState(TransactionalState txState, IControlledTransactionFaultInjector faultInjector, IGrainRuntime grainRuntime, ILogger> logger) { this.grainRuntime = grainRuntime; this.txState = txState; this.logger = logger; this.FaultInjectionControl = new FaultInjectionControl(); this.faultInjector = faultInjector; } public void Participate(IGrainLifecycle lifecycle) { lifecycle.Subscribe>(GrainLifecycleStage.SetupState, (ct) => this.txState.OnSetupState(this.SetupResourceFactory, ct)); } internal void SetupResourceFactory(IGrainContext context, string stateName, TransactionQueue queue) { // Add resources factory to the grain context context.RegisterResourceFactory(stateName, () => new FaultInjectionTransactionalResource(this.faultInjector, FaultInjectionControl, new TransactionalResource(queue), context, logger, grainRuntime)); // Add tm factory to the grain context context.RegisterResourceFactory(stateName, () => new FaultInjectionTransactionManager(this.faultInjector, FaultInjectionControl, new TransactionManager(queue), context, logger, grainRuntime)); } public Task PerformRead(Func readFunction) { return this.txState.PerformRead(readFunction); } public Task PerformUpdate(Func updateFunction) { return this.txState.PerformUpdate(updateFunction); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/FaultInjectionTransactionStateAttribute.cs ================================================ using System; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Orleans.Runtime; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit { public interface IFaultInjectionTransactionalStateConfiguration : ITransactionalStateConfiguration { } [AttributeUsage(AttributeTargets.Parameter)] public class FaultInjectionTransactionalStateAttribute : Attribute, IFacetMetadata, IFaultInjectionTransactionalStateConfiguration { public string StateName { get; } public string StorageName { get; } public FaultInjectionTransactionalStateAttribute(string stateName, string storageName = null) { this.StateName = stateName; this.StorageName = storageName; } } public interface IFaultInjectionTransactionalStateFactory { IFaultInjectionTransactionalState Create(IFaultInjectionTransactionalStateConfiguration config) where TState : class, new(); } public class FaultInjectionTransactionalStateFactory : IFaultInjectionTransactionalStateFactory { private readonly IGrainContextAccessor contextAccessor; public FaultInjectionTransactionalStateFactory(IGrainContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; } public IFaultInjectionTransactionalState Create(IFaultInjectionTransactionalStateConfiguration config) where TState : class, new() { var currentContext = this.contextAccessor.GrainContext; TransactionalState transactionalState = ActivatorUtilities.CreateInstance>(currentContext.ActivationServices, new TransactionalStateConfiguration(config), this.contextAccessor); FaultInjectionTransactionalState deactivationTransactionalState = ActivatorUtilities.CreateInstance>(currentContext.ActivationServices, transactionalState); deactivationTransactionalState.Participate(currentContext.ObservableLifecycle); return deactivationTransactionalState; } } public class FaultInjectionTransactionalStateAttributeMapper : IAttributeToFactoryMapper { private static readonly MethodInfo create = typeof(IFaultInjectionTransactionalStateFactory).GetMethod("Create"); public Factory GetFactory(ParameterInfo parameter, FaultInjectionTransactionalStateAttribute attribute) { IFaultInjectionTransactionalStateConfiguration config = attribute; // use generic type args to define collection type. MethodInfo genericCreate = create.MakeGenericMethod(parameter.ParameterType.GetGenericArguments()); object[] args = new object[] { config }; return context => Create(context, genericCreate, args); } private static object Create(IGrainContext context, MethodInfo genericCreate, object[] args) { IFaultInjectionTransactionalStateFactory factory = context.ActivationServices.GetRequiredService(); return genericCreate.Invoke(factory, args); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/HostingExtensions.cs ================================================ using System; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Providers; using Orleans.Transactions.TestKit; namespace Orleans.Transactions.TestKit { public static class SiloBuilderExtensions { /// /// Configure cluster to use the distributed TM algorithm /// public static ISiloBuilder UseControlledFaultInjectionTransactionState(this ISiloBuilder builder) { return builder.ConfigureServices(services => services.UseControlledFaultInjectionTransactionState()); } public static ISiloBuilder AddFaultInjectionAzureTableTransactionalStateStorage(this ISiloBuilder builder, Action configureOptions) { return builder.AddFaultInjectionAzureTableTransactionalStateStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } public static ISiloBuilder AddFaultInjectionAzureTableTransactionalStateStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddFaultInjectionAzureTableTransactionalStateStorage(name, ob => ob.Configure(configureOptions))); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/IControlledFaultInjector.cs ================================================ namespace Orleans.Transactions.TestKit { public interface IControlledTransactionFaultInjector : ITransactionFaultInjector { bool InjectBeforeStore { get; set; } bool InjectAfterStore { get; set; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/SimpleAzureStorageExceptionInjector.cs ================================================ using System; using System.ComponentModel; using System.Runtime.Serialization; using Azure; using Microsoft.Extensions.Logging; namespace Orleans.Transactions.TestKit { public class SimpleAzureStorageExceptionInjector : IControlledTransactionFaultInjector { public bool InjectBeforeStore { get; set; } public bool InjectAfterStore { get; set; } private int injectionBeforeStoreCounter = 0; private int injectionAfterStoreCounter = 0; private readonly ILogger logger; public SimpleAzureStorageExceptionInjector(ILogger logger) { this.logger = logger; } public void AfterStore() { if (InjectAfterStore) { InjectAfterStore = false; this.injectionAfterStoreCounter++; var message = $"Storage exception thrown after store, thrown total {injectionAfterStoreCounter}"; this.logger.LogInformation(message); throw new SimpleAzureStorageException(message); } } public void BeforeStore() { if (InjectBeforeStore) { InjectBeforeStore = false; this.injectionBeforeStoreCounter++; var message = $"Storage exception thrown before store. Thrown total {injectionBeforeStoreCounter}"; this.logger.LogInformation(message); throw new SimpleAzureStorageException(message); } } } [GenerateSerializer] public class SimpleAzureStorageException : RequestFailedException { public SimpleAzureStorageException(string message) : base(message) { } public SimpleAzureStorageException(string message, Exception innerException) : base(message, innerException) { } public SimpleAzureStorageException(int status, string message) : base(status, message) { } public SimpleAzureStorageException(int status, string message, Exception innerException) : base(status, message, innerException) { } public SimpleAzureStorageException(int status, string message, string errorCode, Exception innerException) : base(status, message, errorCode, innerException) { } [Obsolete("TThe serialization constructor pattern was made obsolete in modern versions of .NET. Use the other constructors instead.")] [EditorBrowsable(EditorBrowsableState.Never)] protected SimpleAzureStorageException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/SingleStateDeactivatingTransactionalGrain.cs ================================================ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit { public interface IFaultInjectionTransactionTestGrain : IGrainWithGuidKey { [Transaction(TransactionOption.CreateOrJoin)] Task Set(int newValue); [Transaction(TransactionOption.CreateOrJoin)] Task Add(int numberToAdd, FaultInjectionControl faultInjectionControl = null); [Transaction(TransactionOption.CreateOrJoin)] Task Get(); Task Deactivate(); } public class SingleStateFaultInjectionTransactionalGrain : Grain, IFaultInjectionTransactionTestGrain { private readonly IFaultInjectionTransactionalState data; private readonly ILoggerFactory loggerFactory; private ILogger logger; public SingleStateFaultInjectionTransactionalGrain( [FaultInjectionTransactionalState("data", TransactionTestConstants.TransactionStore)] IFaultInjectionTransactionalState data, ILoggerFactory loggerFactory) { this.data = data; this.loggerFactory = loggerFactory; } public override Task OnActivateAsync(CancellationToken cancellationToken) { this.logger = this.loggerFactory.CreateLogger(this.GetGrainId().ToString()); this.logger.LogInformation("GrainId {GrainId}", this.GetPrimaryKey()); return base.OnActivateAsync(cancellationToken); } public Task Set(int newValue) { return this.data.PerformUpdate(d => { this.logger.LogInformation("Setting value {NewValue}.", newValue); d.Value = newValue; }); } public Task Add(int numberToAdd, FaultInjectionControl faultInjectionControl = null) { //reset in case control from last tx isn't cleared for some reason this.data.FaultInjectionControl.Reset(); //dont replace it with this.data.FaultInjectionControl = faultInjectionControl, //this.data.FaultInjectionControl must remain the same reference if (faultInjectionControl != null) { this.data.FaultInjectionControl.FaultInjectionPhase = faultInjectionControl.FaultInjectionPhase; this.data.FaultInjectionControl.FaultInjectionType = faultInjectionControl.FaultInjectionType; } return this.data.PerformUpdate(d => { this.logger.LogInformation("Adding {NumberToAdd} to value {Value}.", numberToAdd, d.Value); d.Value += numberToAdd; }); } public Task Get() { return this.data.PerformRead(d => d.Value); } public Task Deactivate() { this.DeactivateOnIdle(); return Task.CompletedTask; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ControlledInjection/TransactionFaultInjectionServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Providers; using Orleans.Runtime; using Orleans.Transactions.Abstractions; using Orleans.Transactions.TestKit; namespace Orleans.Hosting { /// /// extensions. /// public static class TransactionFaultInjectionServiceCollectionExtensions { /// /// Configure cluster to use the distributed TM algorithm /// public static IServiceCollection UseControlledFaultInjectionTransactionState(this IServiceCollection services) { services.AddSingleton, FaultInjectionTransactionalStateAttributeMapper>(); services.TryAddTransient(); services.AddTransient(typeof(IFaultInjectionTransactionalState<>), typeof(FaultInjectionTransactionalState<>)); return services; } internal static IServiceCollection AddFaultInjectionAzureTableTransactionalStateStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.TryAddSingleton(sp => sp.GetKeyedService(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); services.AddKeyedSingleton(name, (sp, key) => FaultInjectionAzureTableTransactionStateStorageFactory.Create(sp, key as string)); services.AddSingleton>(s => (ILifecycleParticipant)s.GetRequiredKeyedService(name)); return services; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/ITransactionFaultInjector.cs ================================================ namespace Orleans.Transactions.TestKit { public interface ITransactionFaultInjector { void BeforeStore(); void AfterStore(); } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/FaultInjection/RandomInjection/RandomErrorInjector.cs ================================================ using System; using System.Runtime.Serialization; using Orleans.Storage; namespace Orleans.Transactions.TestKit { public class RandomErrorInjector : ITransactionFaultInjector { private readonly double conflictProbability; private readonly double beforeProbability; private readonly double afterProbability; public RandomErrorInjector(double injectionProbability) { conflictProbability = injectionProbability / 5; beforeProbability = 2 * injectionProbability / 5; afterProbability = 2 * injectionProbability / 5; } public void BeforeStore() { if (Random.Shared.NextDouble() < conflictProbability) { throw new RandomlyInjectedInconsistentStateException(); } if (Random.Shared.NextDouble() < beforeProbability) { throw new RandomlyInjectedStorageException(); } } public void AfterStore() { if (Random.Shared.NextDouble() < afterProbability) { throw new RandomlyInjectedStorageException(); } } [Serializable] [GenerateSerializer] public class RandomlyInjectedStorageException : Exception { public RandomlyInjectedStorageException() : base("injected fault") { } [Obsolete] protected RandomlyInjectedStorageException(SerializationInfo info, StreamingContext context) : base(info, context) { } } [Serializable] [GenerateSerializer] public class RandomlyInjectedInconsistentStateException : InconsistentStateException { public RandomlyInjectedInconsistentStateException() : base("injected fault") { } [Obsolete] protected RandomlyInjectedInconsistentStateException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/ITransactionAttributionGrain.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { public interface INoAttributionGrain : IGrainWithGuidKey { Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface ISuppressAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Suppress)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface ICreateOrJoinAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOption.CreateOrJoin)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface ICreateAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Create)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface IJoinAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOptionAlias.Mandatory)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface ISupportedAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Supported)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public interface INotAllowedAttributionGrain : IGrainWithGuidKey { [Transaction(TransactionOption.NotAllowed)] Task[]> GetNestedTransactionIds(int tier, List[] tiers); } #region wrappers public interface ITransactionAttributionGrain { Task[]> GetNestedTransactionIds(int tier, List[] tiers); } public static class TransactionAttributionGrainExtensions { public static ITransactionAttributionGrain GetTransactionAttributionGrain(this IGrainFactory grainFactory, Guid id, TransactionOption? option = null) { if(!option.HasValue) { return new NoAttributionGrain(grainFactory.GetGrain(id)); } switch(option.Value) { case TransactionOption.Suppress: return new SuppressAttributionGrain(grainFactory.GetGrain(id)); case TransactionOption.CreateOrJoin: return new CreateOrJoinAttributionGrain(grainFactory.GetGrain(id)); case TransactionOption.Create: return new CreateAttributionGrain(grainFactory.GetGrain(id)); case TransactionOption.Join: return new JoinAttributionGrain(grainFactory.GetGrain(id)); case TransactionOption.Supported: return new SupportedAttributionGrain(grainFactory.GetGrain(id)); case TransactionOption.NotAllowed: return new NotAllowedAttributionGrain(grainFactory.GetGrain(id)); default: throw new NotSupportedException($"Transaction option {option.Value} is not supported."); } } [GenerateSerializer] public class NoAttributionGrain : ITransactionAttributionGrain { [Id(0)] public INoAttributionGrain grain; public NoAttributionGrain(INoAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class SuppressAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ISuppressAttributionGrain grain; public SuppressAttributionGrain(ISuppressAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class CreateOrJoinAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ICreateOrJoinAttributionGrain grain; public CreateOrJoinAttributionGrain(ICreateOrJoinAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class CreateAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ICreateAttributionGrain grain; public CreateAttributionGrain(ICreateAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class JoinAttributionGrain : ITransactionAttributionGrain { [Id(0)] public IJoinAttributionGrain grain; public JoinAttributionGrain(IJoinAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class SupportedAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ISupportedAttributionGrain grain; public SupportedAttributionGrain(ISupportedAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } [GenerateSerializer] public class NotAllowedAttributionGrain : ITransactionAttributionGrain { [Id(0)] public INotAllowedAttributionGrain grain; public NotAllowedAttributionGrain(INotAllowedAttributionGrain grain) { this.grain = grain; } public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return this.grain.GetNestedTransactionIds(tier, tiers); } } } #endregion wrappers } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCommitterTestGrain.cs ================================================  using Orleans.Transactions.Abstractions; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { public interface ITransactionCommitterTestGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Join)] Task Commit(ITransactionCommitOperation operation); } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/ITransactionCoordinatorGrain.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Transactions.Abstractions; using Orleans.Transactions.TestKit.Correctnesss; namespace Orleans.Transactions.TestKit { public interface ITransactionCoordinatorGrain : IGrainWithGuidKey { [Transaction(TransactionOption.Create)] Task MultiGrainSet(List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainAdd(List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainDouble(List grains); [Transaction(TransactionOption.Create)] Task MultiGrainDoubleByRWRW(List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainDoubleByWRWR(List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task OrphanCallTransaction(); [Transaction(TransactionOption.Create)] Task AddAndThrow(ITransactionTestGrain grain, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainAddAndThrow(List grain, List grains, int numberToAdd); [Transaction(TransactionOption.Create)] Task MultiGrainSetBit(List grains, int bitIndex); [Transaction(TransactionOption.Create)] Task MultiGrainAdd(ITransactionCommitterTestGrain committer, ITransactionCommitOperation operation, List grains, int numberToAdd); [Transaction(TransactionOption.Create)] [ReadOnly] Task UpdateViolated(ITransactionTestGrain grains, int numberToAdd); } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/ITransactionTestGrain.cs ================================================ using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { public interface ITransactionTestGrain : IGrainWithGuidKey { /// /// apply set operation to every transaction state /// /// /// [Transaction(TransactionOption.CreateOrJoin)] Task Set(int newValue); /// /// apply add operation to every transaction state /// /// /// [Transaction(TransactionOption.CreateOrJoin)] Task Add(int numberToAdd); /// /// apply get operation to every transaction state /// /// [Transaction(TransactionOption.CreateOrJoin)] Task Get(); [Transaction(TransactionOption.CreateOrJoin)] Task AddAndThrow(int numberToAdd); [Transaction(TransactionOption.CreateOrJoin)] Task SetAndThrow(int numberToSet); Task Deactivate(); } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/ITransactionalBitArrayGrain.cs ================================================  using System.Collections.Generic; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit.Correctnesss { public interface ITransactionalBitArrayGrain : IGrainWithGuidKey { /// /// Ping /// /// Task Ping(); /// /// apply set operation to every transaction state /// /// /// [Transaction(TransactionOption.CreateOrJoin)] Task SetBit(int newValue); /// /// Performs a read transaction on each state, returning the results in order. /// [Transaction(TransactionOption.CreateOrJoin)] Task> Get(); } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/MultiStateTransactionalBitArrayGrain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit.Correctnesss { [Serializable] [GenerateSerializer] public class BitArrayState { protected bool Equals(BitArrayState other) { if (ReferenceEquals(null, this.value)) return false; if (ReferenceEquals(null, other.value)) return false; if (this.value.Length != other.value.Length) return false; for (var i = 0; i < this.value.Length; i++) { if (this.value[i] != other.value[i]) { return false; } } return true; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((BitArrayState) obj); } public override int GetHashCode() => HashCode.Combine(value); private static readonly int BitsInInt = sizeof(int) * 8; [JsonProperty("v")] [Id(0)] private int[] value = { 0 }; [JsonIgnore] public int[] Value => value; [JsonIgnore] public int Length => this.value.Length; public BitArrayState() { } public BitArrayState(BitArrayState other) { this.value = new int[other.value.Length]; for (var i = 0; i < other.value.Length; i++) { this.value[i] = other.value[i]; } } public void Set(int index, bool value) { int idx = index / BitsInInt; if (idx >= this.value.Length) { Array.Resize(ref this.value, idx+1); } int shift = 1 << (index % BitsInInt); if (value) { this.value[idx] |= shift; } else this.value[idx] &= ~shift; } public IEnumerator GetEnumerator() { foreach (var v in this.value) yield return v; } public override string ToString() { // Write the values from least significant bit to most significant bit var builder = new StringBuilder(); foreach (var v in this.value) { builder.Append(Reverse(Convert.ToString(v, 2)).PadRight(BitsInInt, '0')); string Reverse(string s) { char[] charArray = s.ToCharArray(); Array.Reverse(charArray); return new string(charArray); } } return builder.ToString(); } public int this[int index] { get => this.value[index]; set => this.value[index] = value; } public static bool operator ==(BitArrayState left, BitArrayState right) { if (ReferenceEquals(left, right)) return true; if (ReferenceEquals(left, null)) return false; if (ReferenceEquals(right, null)) return false; return left.Equals(right); } public static bool operator !=(BitArrayState left, BitArrayState right) { return !(left == right); } public static BitArrayState operator ^(BitArrayState left, BitArrayState right) { return Apply(left, right, (l, r) => l ^ r); } public static BitArrayState operator |(BitArrayState left, BitArrayState right) { return Apply(left, right, (l, r) => l | r); } public static BitArrayState operator &(BitArrayState left, BitArrayState right) { return Apply(left, right, (l, r) => l & r); } public static BitArrayState Apply(BitArrayState left, BitArrayState right, Func op) { var result = new BitArrayState(left.value.Length > right.value.Length ? left : right); var overlappingLength = Math.Min(left.value.Length, right.value.Length); var i = 0; for (; i < overlappingLength; i++) { result.value[i] = op(left.value[i], right.value[i]); } // Continue with the non-overlapping portion. for (; i < result.value.Length; i++) { var leftVal = left.value.Length > i ? left.value[i] : 0; var rightVal = right.value.Length > i ? right.value[i] : 0; result.value[i] = op(leftVal, rightVal); } return result; } } [GrainType("txn-correctness-MaxStateTransactionalGrain")] public class MaxStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public MaxStateTransactionalGrain(ITransactionalStateFactory stateFactory, ILoggerFactory loggerFactory) : base(Enumerable.Range(0, TransactionTestConstants.MaxCoordinatedTransactions) .Select(i => stateFactory.Create(new TransactionalStateConfiguration(new TransactionalStateAttribute($"data{i}", TransactionTestConstants.TransactionStore)))) .ToArray(), loggerFactory) { } } [GrainType("txn-correctness-DoubleStateTransactionalGrain")] public class DoubleStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public DoubleStateTransactionalGrain( [TransactionalState("data1", TransactionTestConstants.TransactionStore)] ITransactionalState data1, [TransactionalState("data2", TransactionTestConstants.TransactionStore)] ITransactionalState data2, ILoggerFactory loggerFactory) : base(new ITransactionalState[2] { data1, data2 }, loggerFactory) { } } [GrainType("txn-correctness-SingleStateTransactionalGrain")] public class SingleStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public SingleStateTransactionalGrain( [TransactionalState("data", TransactionTestConstants.TransactionStore)] ITransactionalState data, ILoggerFactory loggerFactory) : base(new ITransactionalState[1] { data }, loggerFactory) { } } [GrainType("txn-correctness-MultiStateTransactionalBitArrayGrain")] public class MultiStateTransactionalBitArrayGrain : Grain, ITransactionalBitArrayGrain { protected ITransactionalState[] dataArray; private readonly ILoggerFactory loggerFactory; protected ILogger logger; public MultiStateTransactionalBitArrayGrain( ITransactionalState[] dataArray, ILoggerFactory loggerFactory) { this.dataArray = dataArray; this.loggerFactory = loggerFactory; } public override Task OnActivateAsync(CancellationToken cancellationToken) { this.logger = this.loggerFactory.CreateLogger(this.GetGrainId().ToString()); this.logger.LogTrace("GrainId: {GrainId}.", this.GetPrimaryKey()); return base.OnActivateAsync(cancellationToken); } public Task Ping() { return Task.CompletedTask; } public Task SetBit(int index) { return Task.WhenAll(this.dataArray .Select(data => data.PerformUpdate(state => { this.logger.LogTrace("Setting bit {Index} in state {State}. Transaction {CurrentTransactionId}", index, state, TransactionContext.CurrentTransactionId); state.Set(index, true); this.logger.LogTrace("Set bit {Index} in state {State}.", index, state); }))); } public async Task> Get() { return (await Task.WhenAll(this.dataArray .Select(state => state.PerformRead(s => { this.logger.LogTrace("Get state {State}.", s); return s; })))).ToList(); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/MultiStateTransactionalGrain.cs ================================================ using Microsoft.Extensions.Logging; using Orleans.Transactions.Abstractions; using System; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { [Serializable] [GenerateSerializer] public class GrainData { [Id(0)] public int Value { get; set; } } public class MaxStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public MaxStateTransactionalGrain(ITransactionalStateFactory stateFactory, ILoggerFactory loggerFactory) : base(Enumerable.Range(0, TransactionTestConstants.MaxCoordinatedTransactions) .Select(i => stateFactory.Create(new TransactionalStateConfiguration(new TransactionalStateAttribute($"data{i}", TransactionTestConstants.TransactionStore)))) .ToArray(), loggerFactory) { } } public class DoubleStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public DoubleStateTransactionalGrain( [TransactionalState("data1", TransactionTestConstants.TransactionStore)] ITransactionalState data1, [TransactionalState("data2", TransactionTestConstants.TransactionStore)] ITransactionalState data2, ILoggerFactory loggerFactory) : base(new ITransactionalState[2] { data1, data2 }, loggerFactory) { } } public class SingleStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public SingleStateTransactionalGrain( [TransactionalState("data", TransactionTestConstants.TransactionStore)] ITransactionalState data, ILoggerFactory loggerFactory) : base(new ITransactionalState[1] { data }, loggerFactory) { } } public class NoStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public NoStateTransactionalGrain( ILoggerFactory loggerFactory) : base(Array.Empty>(), loggerFactory) { } } public class MultiStateTransactionalGrainBaseClass : Grain, ITransactionTestGrain { protected ITransactionalState[] dataArray; private readonly ILoggerFactory loggerFactory; protected ILogger logger; public MultiStateTransactionalGrainBaseClass( ITransactionalState[] dataArray, ILoggerFactory loggerFactory) { this.dataArray = dataArray; this.loggerFactory = loggerFactory; } public override Task OnActivateAsync(CancellationToken cancellationToken) { this.logger = this.loggerFactory.CreateLogger(this.GetGrainId().ToString()); return base.OnActivateAsync(cancellationToken); } public async Task Set(int newValue) { foreach(var data in this.dataArray) { await data.PerformUpdate(state => { this.logger.LogInformation("Setting from {Value} to {NewValue}.", state.Value, newValue); state.Value = newValue; this.logger.LogInformation("Set to {Value}.", state.Value); }); } } public async Task Add(int numberToAdd) { var result = new int[dataArray.Length]; for(int i = 0; i < dataArray.Length; i++) { result[i] = await dataArray[i].PerformUpdate(state => { this.logger.LogInformation("Adding {NumberToAdd} to value {Value}.", numberToAdd, state.Value); state.Value += numberToAdd; this.logger.LogInformation("Value after Adding {NumberToAdd} is {Value}.", numberToAdd, state.Value); return state.Value; }); } return result; } public async Task Get() { var result = new int[dataArray.Length]; for (int i = 0; i < dataArray.Length; i++) { result[i] = await dataArray[i].PerformRead(state => { this.logger.LogInformation("Get {Value}.", state.Value); return state.Value; }); } return result; } public async Task AddAndThrow(int numberToAdd) { await Add(numberToAdd); throw new AddAndThrowException($"{GetType().Name} test exception"); } public async Task SetAndThrow(int numberToSet) { await Set(numberToSet); throw new AddAndThrowException($"{GetType().Name} test exception"); } public Task Deactivate() { DeactivateOnIdle(); return Task.CompletedTask; } } [Serializable] [GenerateSerializer] public class AddAndThrowException : Exception { public AddAndThrowException() : base("Unexpected error.") { } public AddAndThrowException(string message) : base(message) { } public AddAndThrowException(string message, Exception innerException) : base(message, innerException) { } [Obsolete] protected AddAndThrowException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/RemoteCommitService.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit { public interface IRemoteCommitService { Task Pass(Guid transactionId, string data); Task Fail(Guid transactionId, string data); Task Throw(Guid transactionId, string data); } // TODO : Replace with more complete service implementation which: // - can be called to verify that commit service receive Callme with proper args. // - can produce errors for fault senarios. public class RemoteCommitService : IRemoteCommitService { private readonly ILogger logger; public RemoteCommitService(ILogger logger) { this.logger = logger; } public async Task Pass(Guid transactionId, string data) { this.logger.LogInformation("Transaction {TransactionId} Passed with data: {Data}", transactionId, data); await Task.Delay(30); return true; } public async Task Fail(Guid transactionId, string data) { this.logger.LogInformation("Transaction {TransactionId} Failed with data: {Data}", transactionId, data); await Task.Delay(30); return false; } public async Task Throw(Guid transactionId, string data) { this.logger.LogInformation("Transaction {TransactionId} Threw with data: {Data}", transactionId, data); await Task.Delay(30); throw new ApplicationException("Transaction {transactionId} Threw with data: {data}"); } } [Serializable] [GenerateSerializer] public class PassOperation : ITransactionCommitOperation { [Id(0)] public string Data { get; set; } public PassOperation(string data) { this.Data = data; } public async Task Commit(Guid transactionId, IRemoteCommitService service) { return await service.Pass(transactionId, this.Data); } } [Serializable] [GenerateSerializer] public class FailOperation : ITransactionCommitOperation { [Id(0)] public string Data { get; set; } public FailOperation(string data) { this.Data = data; } public async Task Commit(Guid transactionId, IRemoteCommitService service) { return await service.Fail(transactionId, this.Data); } } [Serializable] [GenerateSerializer] public class ThrowOperation : ITransactionCommitOperation { [Id(0)] public string Data { get; set; } public ThrowOperation(string data) { this.Data = data; } public async Task Commit(Guid transactionId, IRemoteCommitService service) { return await service.Throw(transactionId, this.Data); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/TransactionAttributionGrain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Orleans.Transactions.TestKit { public class NoAttributionGrain : Grain, INoAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class SuppressAttributionGrain : Grain, ISuppressAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class CreateOrJoinAttributionGrain : Grain, ICreateOrJoinAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class CreateAttributionGrain : Grain, ICreateAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class JoinAttributionGrain : Grain, IJoinAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class SupportedAttributionGrain : Grain, ISupportedAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } public class NotAllowedAttributionGrain : Grain, INotAllowedAttributionGrain { public Task[]> GetNestedTransactionIds(int tier, List[] tiers) { return AttributionGrain.GetNestedTransactionIds(tier, tiers); } } internal static class AttributionGrain { public static async Task[]> GetNestedTransactionIds(int tier, List[] tiers) { TransactionInfo ti = TransactionContext.GetTransactionInfo(); List[] results = new List[tier + 1 + tiers.Length]; results[tier] = new List(new[] { ti?.Id }); if (tiers.Length == 0) { return results; } List nextTier = tiers.FirstOrDefault(); List[] nextTiers = tiers.Skip(1).ToArray(); List[][] tiersResults = await Task.WhenAll(nextTier.Select(g => g.GetNestedTransactionIds(tier+1, nextTiers))); foreach (List[] result in tiersResults) { if (result.Length != results.Length) throw new ApplicationException("Invalid result length"); for (int i = tier + 1; i < results.Length; i++) { if (results[i] != null) { results[i].AddRange(result[i]); } else results[i] = result[i]; } } return results; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/TransactionCommitterTestGrain.cs ================================================ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit { public class TransactionCommitterTestGrain : Grain, ITransactionCommitterTestGrain { protected ITransactionCommitter committer; private readonly ILoggerFactory loggerFactory; protected ILogger logger; public TransactionCommitterTestGrain( [TransactionCommitter(TransactionTestConstants.RemoteCommitService, TransactionTestConstants.TransactionStore)] ITransactionCommitter committer, ILoggerFactory loggerFactory) { this.committer = committer; this.loggerFactory = loggerFactory; } public override Task OnActivateAsync(CancellationToken cancellationToken) { this.logger = this.loggerFactory.CreateLogger(this.GetGrainId().ToString()); return base.OnActivateAsync(cancellationToken); } public Task Commit(ITransactionCommitOperation operation) { return this.committer.OnCommit(operation); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Grains/TransactionCoordinatorGrain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans.Concurrency; using Orleans.Transactions; using Orleans.Transactions.Abstractions; using Orleans.Transactions.TestKit.Correctnesss; namespace Orleans.Transactions.TestKit { [StatelessWorker] public class TransactionCoordinatorGrain : Grain, ITransactionCoordinatorGrain { public Task MultiGrainSet(List grains, int newValue) { return Task.WhenAll(grains.Select(g => g.Set(newValue))); } public Task MultiGrainAdd(List grains, int numberToAdd) { return Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); } public Task MultiGrainDouble(List grains) { return Task.WhenAll(grains.Select(Double)); } public Task OrphanCallTransaction() { _ = TransactionContext.GetRequiredTransactionInfo().Fork(); return Task.CompletedTask; } public async Task AddAndThrow(ITransactionTestGrain grain, int numberToAdd) { await grain.Add(numberToAdd); throw new Exception("This should abort the transaction"); } public async Task MultiGrainAddAndThrow(List throwGrains, List grains, int numberToAdd) { await Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); await Task.WhenAll(throwGrains.Select(tg => tg.AddAndThrow(numberToAdd))); } public Task MultiGrainSetBit(List grains, int bitIndex) { return Task.WhenAll(grains.Select(g => g.SetBit(bitIndex))); } public Task MultiGrainAdd(ITransactionCommitterTestGrain committer, ITransactionCommitOperation operation, List grains, int numberToAdd) { List tasks = new List(); tasks.AddRange(grains.Select(g => g.Add(numberToAdd))); tasks.Add(committer.Commit(operation)); return Task.WhenAll(tasks); } public Task UpdateViolated(ITransactionTestGrain grain, int numberToAdd) { return grain.Add(numberToAdd); } private async Task Double(ITransactionTestGrain grain) { int[] values = await grain.Get(); await grain.Add(values[0]); } public async Task MultiGrainDoubleByRWRW(List grains, int numberToAdd) { await Task.WhenAll(grains.Select(g => g.Get())); await Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); await Task.WhenAll(grains.Select(g => g.Get())); await Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); } public async Task MultiGrainDoubleByWRWR(List grains, int numberToAdd) { await Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); await Task.WhenAll(grains.Select(g => g.Get())); await Task.WhenAll(grains.Select(g => g.Add(numberToAdd))); await Task.WhenAll(grains.Select(g => g.Get())); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/ITestState.cs ================================================ namespace Orleans.Transactions.TestKit { public interface ITestState { int state { get; set; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/Orleans.Transactions.TestKit.Base.csproj ================================================ Microsoft.Orleans.Transactions.TestKit.Base Microsoft Orleans Transactions test kit base Testkit base library for transactions $(PackageTags) TransactionTestkKit true false $(DefaultTargetFrameworks) Orleans.Transactions.TestKit.Base Orleans.Transactions.TestKit.Base ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/ConsistencyTransactionTestRunner.cs ================================================ using System; using System.Threading.Tasks; using AwesomeAssertions; using Orleans.Transactions.TestKit.Consistency; namespace Orleans.Transactions.TestKit { public abstract class ConsistencyTransactionTestRunner : TransactionTestRunnerBase { protected ConsistencyTransactionTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } // settings that are configuration dependent can be overridden by runner subclasses // this allows tests to adapt their logic, or be skipped, for specific contexts protected abstract bool StorageAdaptorHasLimitedCommitSpace { get; } protected abstract bool StorageErrorInjectionActive { get; } public virtual async Task RandomizedConsistency(int numGrains, int scale, bool avoidDeadlocks, bool avoidTimeouts, ReadWriteDetermination readwrite) { var random = new Random(scale + numGrains * 1000 + (avoidDeadlocks ? 666 : 333) + ((int)readwrite) * 123976); var harness = new ConsistencyTestHarness(grainFactory, numGrains, random.Next(), avoidDeadlocks, avoidTimeouts, readwrite, StorageErrorInjectionActive); // first, run the random work load to generate history events testOutput($"start at {DateTime.UtcNow}"); int numThreads = scale; int numTxsPerThread = scale * scale; // start the threads that run transactions var tasks = new Task[numThreads]; for (int i = 0; i < numThreads; i++) { tasks[i] = harness.RunRandomTransactionSequence(i, numTxsPerThread, grainFactory, this.testOutput); } // wait for the test to finish await Task.WhenAll(tasks); testOutput($"end at {DateTime.UtcNow}"); // golden path: all transactions are expected to pass when avoiding deadlocks and lock upgrades if (!StorageErrorInjectionActive && avoidDeadlocks && (readwrite == ReadWriteDetermination.PerGrain || readwrite == ReadWriteDetermination.PerTransaction)) { harness.NumAborted.Should().Be(0); } // then, analyze the history results var tolerateGenericTimeouts = StorageErrorInjectionActive || (scale >= 3 && !avoidTimeouts); var tolerateUnknownExceptions = StorageAdaptorHasLimitedCommitSpace || StorageErrorInjectionActive; harness.CheckConsistency(tolerateGenericTimeouts, tolerateUnknownExceptions); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/ControlledFaultInjectionTransactionTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public class ControlledFaultInjectionTransactionTestRunner : TransactionTestRunnerBase { public ControlledFaultInjectionTransactionTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual async Task SingleGrainReadTransaction() { const int expected = 5; IFaultInjectionTransactionTestGrain grain = grainFactory.GetGrain(Guid.NewGuid()); await grain.Set(expected); int actual = await grain.Get(); actual.Should().Be(expected); await grain.Deactivate(); actual = await grain.Get(); actual.Should().Be(expected); } public virtual async Task SingleGrainWriteTransaction() { const int delta = 5; IFaultInjectionTransactionTestGrain grain = this.grainFactory.GetGrain(Guid.NewGuid()); int original = await grain.Get(); await grain.Add(delta); await grain.Deactivate(); int expected = original + delta; int actual = await grain.Get(); actual.Should().Be(expected); } public virtual async Task MultiGrainWriteTransaction_FaultInjection(TransactionFaultInjectPhase injectionPhase, FaultInjectionType injectionType) { const int setval = 5; const int addval = 7; int expected = setval + addval; const int grainCount = TransactionTestConstants.MaxCoordinatedTransactions; var faultInjectionControl = new FaultInjectionControl() { FaultInjectionPhase = injectionPhase, FaultInjectionType = injectionType }; List grains = Enumerable.Range(0, grainCount) .Select(i => this.grainFactory.GetGrain(Guid.NewGuid())) .ToList(); IFaultInjectionTransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(grains, setval); // add delay between transactions so confirmation errors don't bleed into neighboring transactions if (injectionPhase == TransactionFaultInjectPhase.BeforeConfirm || injectionPhase == TransactionFaultInjectPhase.AfterConfirm) await Task.Delay(TimeSpan.FromSeconds(30)); try { await coordinator.MultiGrainAddAndFaultInjection(grains, addval, faultInjectionControl); // add delay between transactions so confirmation errors don't bleed into neighboring transactions if (injectionPhase == TransactionFaultInjectPhase.BeforeConfirm || injectionPhase == TransactionFaultInjectPhase.AfterConfirm) await Task.Delay(TimeSpan.FromSeconds(30)); } catch (OrleansTransactionAbortedException) { // add delay between transactions so errors don't bleed into neighboring transactions await coordinator.MultiGrainAddAndFaultInjection(grains, addval); } catch (OrleansTransactionException e) { this.testOutput($"Call failed with exception: {e}, retrying without fault"); bool cascadingAbort = false; bool firstAttempt = true; do { cascadingAbort = false; try { expected = await grains[0].Get() + addval; await coordinator.MultiGrainAddAndFaultInjection(grains, addval); } catch (OrleansCascadingAbortException) { this.testOutput($"Retry failed with OrleansCascadingAbortException: {e}, retrying without fault"); // should only encounter this when faulting after storage write injectionType.Should().Be(FaultInjectionType.ExceptionAfterStore); // only allow one retry firstAttempt.Should().BeTrue(); // add delay prevent castcading abort. cascadingAbort = true; firstAttempt = false; } } while (cascadingAbort); } //if transactional state loaded correctly after reactivation, then following should pass foreach (var grain in grains) { int actual = await grain.Get(); actual.Should().Be(expected); } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/DisabledTransactionsTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class DisabledTransactionsTestRunner : TransactionTestRunnerBase { protected DisabledTransactionsTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual void TransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { const int delta = 5; ITransactionTestGrain grain = RandomTestGrain(transactionTestGrainClassName); Func task = ()=>grain.Set(delta); var response = task.Should().ThrowAsync(); } public virtual void MultiTransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { const int delta = 5; const int grainCount = TransactionTestConstants.MaxCoordinatedTransactions; List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(transactionTestGrainClassName)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); Func task = () => coordinator.MultiGrainSet(grains, delta); var response = task.Should().ThrowAsync(); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/GoldenPathTransactionTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class GoldenPathTransactionTestRunner : TransactionTestRunnerBase { protected GoldenPathTransactionTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual async Task SingleGrainReadTransaction(string grainStates) { const int expected = 0; ITransactionTestGrain grain = RandomTestGrain(grainStates); var actualResults = await grain.Get(); //each transaction state should all be 0 since no operation was applied yet foreach (var actual in actualResults) { actual.Should().Be(expected); } } public virtual async Task SingleGrainWriteTransaction(string grainStates) { const int delta = 5; ITransactionTestGrain grain = RandomTestGrain(grainStates); var original = await grain.Get(); await grain.Add(delta); var expected = original.Select(value => value + delta).ToArray(); var actual = await grain.Get(); actual.Should().BeEquivalentTo(expected); } public virtual async Task MultiGrainWriteTransaction(string grainStates, int grainCount) { const int expected = 5; List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainAdd(grains, expected); foreach (var grain in grains) { var actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } } public virtual async Task MultiGrainReadWriteTransaction(string grainStates, int grainCount) { const int delta = 5; List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(grains, delta); await coordinator.MultiGrainDouble(grains); int expected = delta + delta; foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { if (expected != actual) this.testOutput($"{grain} - failed"); actual.Should().Be(expected); } } } public virtual async Task RepeatGrainReadWriteTransaction(string grainStates, int grainCount) { const int repeat = 10; const int delta = 5; List grainIds = Enumerable.Range(0, grainCount) .Select(i => Guid.NewGuid()) .ToList(); List grains = grainIds .Select(id => TestGrain(grainStates, id)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(grains, delta); for (int i = 0; i < repeat; i++) { await coordinator.MultiGrainDouble(grains); int expected = delta * (int)Math.Pow(2,i+1); foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { if (expected != actual) this.testOutput($"{grain} - failed"); actual.Should().Be(expected); } } } } public virtual async Task MultiWriteToSingleGrainTransaction(string grainStates) { const int delta = 5; const int concurrentWrites = 3; ITransactionTestGrain grain = RandomTestGrain(grainStates); List grains = Enumerable.Repeat(grain, concurrentWrites).ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainAdd(grains, delta); int expected = delta * concurrentWrites; int[] actualValues = await grains[0].Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } public virtual async Task RWRWTest(string grainStates, int grainCount) { const int delta = 5; List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainDoubleByRWRW(grains, delta); int expected = delta + delta; foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { if (expected != actual) this.testOutput($"{grain} - failed"); actual.Should().Be(expected); } } } public virtual async Task WRWRTest(string grainStates, int grainCount) { const int delta = 5; List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainDoubleByWRWR(grains, delta); int expected = delta + delta; foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { if (expected != actual) this.testOutput($"{grain} - failed"); actual.Should().Be(expected); } } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/GrainFaultTransactionTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class GrainFaultTransactionTestRunner : TransactionTestRunnerBase { public GrainFaultTransactionTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual async Task AbortTransactionOnExceptions(string grainStates) { const int expected = 5; ITransactionTestGrain grain = RandomTestGrain(grainStates); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(new List { grain }, expected); Func task = () => coordinator.AddAndThrow(grain, expected); await task.Should().ThrowAsync(); await TestAfterDustSettles(async () => { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } }); } public virtual async Task AbortTransactionOnReadOnlyViolatedException(string grainStates) { const int expected = 5; ITransactionTestGrain grain = RandomTestGrain(grainStates); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(new List { grain }, expected); Func task = () => coordinator.UpdateViolated(grain, expected); await task.Should().ThrowAsync(); await TestAfterDustSettles(async () => { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } }); } public virtual async Task MultiGrainAbortTransactionOnExceptions(string grainStates) { const int grainCount = TransactionTestConstants.MaxCoordinatedTransactions - 1; const int expected = 5; ITransactionTestGrain throwGrain = RandomTestGrain(grainStates); List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await throwGrain.Set(expected); await coordinator.MultiGrainSet(grains, expected); Func task = () => coordinator.MultiGrainAddAndThrow(new List() { throwGrain }, grains, expected); await task.Should().ThrowAsync(); grains.Add(throwGrain); await TestAfterDustSettles(async () => { foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } }); } public virtual async Task AbortTransactionExceptionInnerExceptionOnlyContainsOneRootCauseException(string grainStates) { const int throwGrainCount = 3; const int grainCount = TransactionTestConstants.MaxCoordinatedTransactions - throwGrainCount; const int expected = 5; List throwGrains = Enumerable.Range(0, throwGrainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainSet(throwGrains, expected); await coordinator.MultiGrainSet(grains, expected); async Task InnerExceptionCheck() { try { await coordinator.MultiGrainAddAndThrow(throwGrains, grains, expected); } catch (Exception e) { e.InnerException.Should().BeOfType(); throw; } } Func task = () => InnerExceptionCheck(); await task.Should().ThrowAsync(); grains.AddRange(throwGrains); await TestAfterDustSettles(async () => { foreach (var grain in grains) { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } }); } public virtual async Task AbortTransactionOnOrphanCalls(string grainStates) { const int expected = 5; ITransactionTestGrain grain = RandomTestGrain(grainStates); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await grain.Set(expected); Func task = () => coordinator.OrphanCallTransaction(); await task.Should().ThrowAsync(); //await Task.Delay(20000); // give time for GC await TestAfterDustSettles(async () => { int[] actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } }); } private static async Task TestAfterDustSettles(Func what) { int tries = 2; while (tries-- > 0) { try { await what(); } catch (OrleansCascadingAbortException) { // due to optimistic reading we may read state of aborted transactions // which causes cascading abort } } } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/ScopedTransactionsTestRunner.cs ================================================ using System; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class ScopedTransactionsTestRunner : TransactionTestRunnerBase { private readonly ITransactionClient _transactionClient; protected ScopedTransactionsTestRunner(IGrainFactory grainFactory, ITransactionClient transactionClient, Action output) : base(grainFactory, output) { _transactionClient = transactionClient; } public virtual async Task CreateTransactionScopeAndSetValue(string grainStates) { // Arrange var grain = RandomTestGrain(grainStates); // Act Func act = () => grain.Set(57); await _transactionClient.RunTransaction(TransactionOption.Create, async () => // Assert await act.Should().NotThrowAsync(because: "No failure expected")); } public virtual async Task CreateTransactionScopeAndSetValueWithFailure(string grainStates) { // Arrange var grain = RandomTestGrain(grainStates); // Act Func act = () => _transactionClient.RunTransaction(TransactionOption.Create, () => grain.SetAndThrow(57)); // Assert await act.Should().ThrowAsync(because: "Failure expected"); } public virtual async Task CreateTransactionScopeAndSetValueAndAssert(string grainStates) { var result = Array.Empty(); // Arrange var grain = RandomTestGrain(grainStates); // Act await _transactionClient.RunTransaction(TransactionOption.Create, async () => { await grain.Set(57); result = await grain.Get(); }); // Assert result.Should().OnlyContain(number => number == 57); } public virtual async Task CreateNestedTransactionScopeAndSetValueAndInnerFailAndAssert(string grainStates) { var result = Array.Empty(); // Arrange var grain = RandomTestGrain(grainStates); // Act await _transactionClient.RunTransaction(TransactionOption.Create, async () => { try { await _transactionClient.RunTransaction(TransactionOption.Create, async () => await grain.SetAndThrow(67)); } catch { } await grain.Set(57); }); result = await grain.Get(); // Assert result.Should().OnlyContain(number => number == 57); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/SkewedClock.cs ================================================ using System; namespace Orleans.Transactions.TestKit { public class SkewedClock : IClock { private readonly TimeSpan minSkew; private readonly int skewRangeTicks; public SkewedClock(TimeSpan minSkew, TimeSpan maxSkew) { this.minSkew = minSkew; this.skewRangeTicks = (int)(maxSkew.Ticks - minSkew.Ticks); } public DateTime UtcNow() { TimeSpan skew = TimeSpan.FromTicks(minSkew.Ticks + Random.Shared.Next(skewRangeTicks)); // skew forward in time or backward in time return ((Random.Shared.Next() & 1) != 0) ? DateTime.UtcNow + skew : DateTime.UtcNow - skew; } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/SkewedClockConfigurator.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.TestingHost; namespace Orleans.Transactions.TestKit { public class SkewedClockConfigurator : ISiloConfigurator { private static readonly TimeSpan MinSkew = TimeSpan.FromSeconds(3); private static readonly TimeSpan MaxSkew = TimeSpan.FromSeconds(5); public void Configure(ISiloBuilder hostBuilder) { hostBuilder .ConfigureServices(services => services.AddSingleton(sp => new SkewedClock(MinSkew, MaxSkew))); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/TOCGoldenPathTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class TocGoldenPathTestRunner : TransactionTestRunnerBase { protected TocGoldenPathTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual async Task MultiGrainWriteTransaction(string grainStates, int grainCount) { const int expected = 5; ITransactionCommitterTestGrain committer = this.grainFactory.GetGrain(Guid.NewGuid()); List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainAdd(committer, new PassOperation("pass"), grains, expected); foreach (var grain in grains) { var actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } // TODO : Add verification that commit service receive call with proper args. } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/TocFaultTransactionTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class TocFaultTransactionTestRunner : TransactionTestRunnerBase { protected TocFaultTransactionTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } public virtual async Task MultiGrainWriteTransactionWithCommitFailure(string grainStates, int grainCount) { const int expected = 5; ITransactionCommitterTestGrain committer = this.grainFactory.GetGrain(Guid.NewGuid()); List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainAdd(committer, new PassOperation("pass"), grains, expected); Func task = () => coordinator.MultiGrainAdd(committer, new FailOperation("fail"), grains, expected); await task.Should().ThrowAsync(); foreach (var grain in grains) { var actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } // TODO : Add verification that commit service receive call with proper args. } public virtual async Task MultiGrainWriteTransactionWithCommitException(string grainStates, int grainCount) { const int expected = 5; ITransactionCommitterTestGrain committer = this.grainFactory.GetGrain(Guid.NewGuid()); List grains = Enumerable.Range(0, grainCount) .Select(i => RandomTestGrain(grainStates)) .ToList(); ITransactionCoordinatorGrain coordinator = this.grainFactory.GetGrain(Guid.NewGuid()); await coordinator.MultiGrainAdd(committer, new PassOperation("pass"), grains, expected); Func task = () => coordinator.MultiGrainAdd(committer, new ThrowOperation("throw"), grains, expected); await task.Should().ThrowAsync(); foreach (var grain in grains) { var actualValues = await grain.Get(); foreach (var actual in actualValues) { actual.Should().Be(expected); } } // TODO : Add verification that commit service receive call with proper args. } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/TransactionConcurrencyTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; namespace Orleans.Transactions.TestKit { public abstract class TransactionConcurrencyTestRunner : TransactionTestRunnerBase { protected TransactionConcurrencyTestRunner(IGrainFactory grainFactory, Action output) : base(grainFactory, output) { } /// /// Two transaction share a single grain /// /// /// public virtual async Task SingleSharedGrainTest(string grainStates) { const int expected = 5; ITransactionTestGrain grain1 = RandomTestGrain(grainStates); ITransactionTestGrain grain2 = RandomTestGrain(grainStates); ITransactionTestGrain sharedGrain = RandomTestGrain(grainStates); List transaction1Members = new List(new[] { grain1, sharedGrain }); List transaction2Members = new List(new[] { grain2, sharedGrain }); ITransactionCoordinatorGrain coordinator1 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator2 = this.grainFactory.GetGrain(Guid.NewGuid()); await Task.WhenAll( coordinator1.MultiGrainAdd(transaction1Members, expected), coordinator2.MultiGrainAdd(transaction2Members, expected)); int[] actual = await grain1.Get(); expected.Should().Be(actual.FirstOrDefault()); actual = await grain2.Get(); expected.Should().Be(actual.FirstOrDefault()); actual = await sharedGrain.Get(); actual.FirstOrDefault().Should().Be(expected * 2); } /// /// Chain of transactions, each dependent on the results of the previous /// /// /// public virtual async Task TransactionChainTest(string grainStates) { const int expected = 5; ITransactionTestGrain grain1 = RandomTestGrain(grainStates); ITransactionTestGrain grain2 = RandomTestGrain(grainStates); ITransactionTestGrain grain3 = RandomTestGrain(grainStates); ITransactionTestGrain grain4 = RandomTestGrain(grainStates); ITransactionTestGrain grain5 = RandomTestGrain(grainStates); List transaction1Members = new List(new[] { grain1, grain2 }); List transaction2Members = new List(new[] { grain2, grain3 }); List transaction3Members = new List(new[] { grain3, grain4 }); List transaction4Members = new List(new[] { grain4, grain5 }); ITransactionCoordinatorGrain coordinator1 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator2 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator3 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator4 = this.grainFactory.GetGrain(Guid.NewGuid()); await Task.WhenAll( coordinator1.MultiGrainAdd(transaction1Members, expected), coordinator2.MultiGrainAdd(transaction2Members, expected), coordinator3.MultiGrainAdd(transaction3Members, expected), coordinator4.MultiGrainAdd(transaction4Members, expected)); int[] actual = await grain1.Get(); actual.FirstOrDefault().Should().Be(expected); actual = await grain2.Get(); actual.FirstOrDefault().Should().Be(expected*2); actual = await grain3.Get(); actual.FirstOrDefault().Should().Be(expected*2); actual = await grain4.Get(); actual.FirstOrDefault().Should().Be(expected*2); actual = await grain5.Get(); actual.FirstOrDefault().Should().Be(expected); } /// /// Single transaction containing two grains is dependent on two other transaction, one from each grain /// /// /// public virtual async Task TransactionTreeTest(string grainStates) { const int expected = 5; ITransactionTestGrain grain1 = RandomTestGrain(grainStates); ITransactionTestGrain grain2 = RandomTestGrain(grainStates); ITransactionTestGrain grain3 = RandomTestGrain(grainStates); ITransactionTestGrain grain4 = RandomTestGrain(grainStates); List transaction1Members = new List(new[] { grain1, grain2 }); List transaction2Members = new List(new[] { grain3, grain4 }); List transaction3Members = new List(new[] { grain2, grain3 }); ITransactionCoordinatorGrain coordinator1 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator2 = this.grainFactory.GetGrain(Guid.NewGuid()); ITransactionCoordinatorGrain coordinator3 = this.grainFactory.GetGrain(Guid.NewGuid()); await Task.WhenAll( coordinator1.MultiGrainAdd(transaction1Members, expected), coordinator2.MultiGrainAdd(transaction2Members, expected), coordinator3.MultiGrainAdd(transaction3Members, expected)); int[] actual = await grain1.Get(); actual.FirstOrDefault().Should().Be(expected); actual = await grain2.Get(); actual.FirstOrDefault().Should().Be(expected*2); actual = await grain3.Get(); actual.FirstOrDefault().Should().Be(expected*2); actual = await grain4.Get(); actual.FirstOrDefault().Should().Be(expected); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/TransactionRecoveryTestsRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Orleans.TestingHost; using Orleans.TestingHost.Utils; using Orleans.Transactions.TestKit.Correctnesss; namespace Orleans.Transactions.TestKit { public class TransactionRecoveryTestsRunner : TransactionTestRunnerBase { private static readonly TimeSpan RecoveryTimeout = TimeSpan.FromSeconds(60); // reduce to or remove once we fix timeouts abort private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(1); private readonly TestCluster testCluster; private readonly ILogger logger; protected void Log(string message) { this.testOutput($"[{DateTime.Now}] {message}"); this.logger.LogInformation(message); } private class ExpectedGrainActivity { public ExpectedGrainActivity(Guid grainId, ITransactionalBitArrayGrain grain) { this.GrainId = grainId; this.Grain = grain; } public Guid GrainId { get; } public ITransactionalBitArrayGrain Grain { get; } public BitArrayState Expected { get; } = new BitArrayState(); public BitArrayState Unambiguous { get; } = new BitArrayState(); public List Actual { get; set; } public async Task GetActual() { try { this.Actual = await this.Grain.Get(); } catch(Exception) { // allow a single retry await Task.Delay(TimeSpan.FromSeconds(30)); this.Actual = await this.Grain.Get(); } } } public TransactionRecoveryTestsRunner(TestCluster testCluster, Action testOutput) : base(testCluster.GrainFactory, testOutput) { this.testCluster = testCluster; this.logger = this.testCluster.ServiceProvider.GetService>(); } public virtual Task TransactionWillRecoverAfterRandomSiloGracefulShutdown(string transactionTestGrainClassName, int concurrent) { return TransactionWillRecoverAfterRandomSiloFailure(transactionTestGrainClassName, concurrent, true); } public virtual Task TransactionWillRecoverAfterRandomSiloUnGracefulShutdown(string transactionTestGrainClassName, int concurrent) { return TransactionWillRecoverAfterRandomSiloFailure(transactionTestGrainClassName, concurrent, false); } protected virtual async Task TransactionWillRecoverAfterRandomSiloFailure(string transactionTestGrainClassName, int concurrent, bool gracefulShutdown) { var endOnCommand = new[] { false }; var index = new[] { 0 }; int getIndex() => index[0]++; List txGrains = Enumerable.Range(0, concurrent * 2) .Select(i => Guid.NewGuid()) .Select(grainId => new ExpectedGrainActivity(grainId, TestGrain(transactionTestGrainClassName, grainId))) .ToList(); //ping all grains to activate them await WakeupGrains(txGrains.Select(g=>g.Grain).ToList()); List[] transactionGroups = txGrains .Select((txGrain, i) => new { index = i, value = txGrain }) .GroupBy(v => v.index / 2) .Select(g => g.Select(i => i.value).ToList()) .ToArray(); var txSucceedBeforeInterruption = await AllTxSucceed(transactionGroups, getIndex()); txSucceedBeforeInterruption.Should().BeTrue(); await ValidateResults(txGrains, transactionGroups); // have transactions in flight when silo goes down Task succeeding = RunWhileSucceeding(transactionGroups, getIndex, endOnCommand); await Task.Delay(TimeSpan.FromSeconds(2)); var siloToTerminate = this.testCluster.Silos[Random.Shared.Next(this.testCluster.Silos.Count)]; this.Log($"Warmup transaction succeeded. {(gracefulShutdown ? "Stopping" : "Killing")} silo {siloToTerminate.SiloAddress} ({siloToTerminate.Name}) and continuing"); if (gracefulShutdown) await this.testCluster.StopSiloAsync(siloToTerminate); else await this.testCluster.KillSiloAsync(siloToTerminate); this.Log("Waiting for transactions to stop completing successfully"); var complete = await Task.WhenAny(succeeding, Task.Delay(TimeSpan.FromSeconds(30))); endOnCommand[0] = true; bool endedOnCommand = await succeeding; if (endedOnCommand) this.Log($"No transactions failed due to silo death. Test may not be valid"); this.Log($"Waiting for system to recover. Performed {index[0]} transactions on each group."); var transactionGroupsRef = new[] { transactionGroups }; await TestingUtils.WaitUntilAsync(lastTry => CheckTxResult(transactionGroupsRef, getIndex, lastTry), RecoveryTimeout, RetryDelay); this.Log($"Recovery completed. Performed {index[0]} transactions on each group. Validating results."); await ValidateResults(txGrains, transactionGroups); } private static Task WakeupGrains(List grains) { var tasks = new List(); foreach (var grain in grains) { tasks.Add(grain.Ping()); } return Task.WhenAll(tasks); } private async Task RunWhileSucceeding(List[] transactionGroups, Func getIndex, bool[] end) { // Loop until failure, or getTime changes while (await AllTxSucceed(transactionGroups, getIndex()) && !end[0]) { } return end[0]; } private async Task CheckTxResult(List[][] transactionGroupsRef, Func getIndex, bool assertIsTrue) { // only retry failed transactions transactionGroupsRef[0] = await RunAllTxReportFailed(transactionGroupsRef[0], getIndex()); bool succeed = transactionGroupsRef[0] == null; this.Log($"All transactions succeed after interruption : {succeed}"); if (assertIsTrue) { //consider it recovered if all tx succeed this.Log($"Final check : {succeed}"); succeed.Should().BeTrue(); return succeed; } else { return succeed; } } // Runs all transactions and returns failed; private async Task[]> RunAllTxReportFailed(List[] transactionGroups, int index) { List tasks = transactionGroups .Select(p => SetBit(p, index)) .ToList(); try { await Task.WhenAll(tasks); return null; } catch (Exception) { // Collect the indices of the transaction groups which failed their transactions for diagnostics. List[] failedGroups = tasks.Select((task, i) => new { task, i }).Where(t => t.task.IsFaulted).Select(t => transactionGroups[t.i]).ToArray(); this.Log($"Some transactions failed. Index: {index}. {failedGroups.Length} out of {tasks.Count} failed. Failed groups: {string.Join(", ", failedGroups.Select(transactionGroup => string.Join(":", transactionGroup.Select(a => a.GrainId))))}"); return failedGroups; } } private async Task AllTxSucceed(List[] transactionGroups, int index) { // null return indicates none failed return (await RunAllTxReportFailed(transactionGroups, index) == null); } private async Task SetBit(List grains, int index) { try { await this.grainFactory.GetGrain(Guid.NewGuid()).MultiGrainSetBit(grains.Select(v => v.Grain).ToList(), index); grains.ForEach(g => { g.Expected.Set(index, true); g.Unambiguous.Set(index, true); }); } catch (OrleansTransactionAbortedException e) { this.Log($"Some transactions failed. Index: {index}: Exception: {e.GetType().Name}"); grains.ForEach(g => { g.Expected.Set(index, false); g.Unambiguous.Set(index, true); }); throw; } catch (Exception e) { this.Log($"Ambiguous transaction failure. Index: {index}: Exception: {e.GetType().Name}"); grains.ForEach(g => { g.Expected.Set(index, false); g.Unambiguous.Set(index, false); }); throw; } } private async Task ValidateResults(List txGrains, List[] transactionGroups) { await Task.WhenAll(txGrains.Select(a => a.GetActual())); this.Log($"Got all {txGrains.Count} actual values"); bool pass = true; foreach (List transactionGroup in transactionGroups) { if (transactionGroup.Count == 0) continue; BitArrayState first = transactionGroup[0].Actual.FirstOrDefault(); foreach (ExpectedGrainActivity activity in transactionGroup.Skip(1)) { BitArrayState actual = activity.Actual.FirstOrDefault(); BitArrayState difference = first ^ actual; if (difference.Value.Any(v => v != 0)) { this.Log($"Activity on grain {activity.GrainId} did not match activity on {transactionGroup[0].GrainId}:\n" + $"{first} ^\n" + $"{actual} = \n" + $"{difference}\n" + $"Activation: {activity.GrainId}"); pass = false; } } } int i = 0; foreach (ExpectedGrainActivity activity in txGrains) { BitArrayState expected = activity.Expected; BitArrayState unambiguous = activity.Unambiguous; BitArrayState unambuguousExpected = expected & unambiguous; List actual = activity.Actual; BitArrayState first = actual.FirstOrDefault(); if (first == null) { this.Log($"No activity for {i} ({activity.GrainId})"); pass = false; continue; } int j = 0; foreach (BitArrayState result in actual) { // skip comparing first to first. if (ReferenceEquals(first, result)) continue; // Check if each state is identical to the first state. var difference = result ^ first; if (difference.Value.Any(v => v != 0)) { this.Log($"Activity on grain {i}, state {j} did not match 'first':\n" + $" {first}\n" + $"^ {result}\n" + $"= {difference}\n" + $"Activation: {activity.GrainId}"); pass = false; } j++; } // Check if the unambiguous portions of the first match. var unambiguousFirst = first & unambiguous; var unambiguousDifference = unambuguousExpected ^ unambiguousFirst; if (unambiguousDifference.Value.Any(v => v != 0)) { this.Log( $"First state on grain {i} did not match 'expected':\n" + $" {unambuguousExpected}\n" + $"^ {unambiguousFirst}\n" + $"= {unambiguousDifference}\n" + $"Activation: {activity.GrainId}"); pass = false; } i++; } this.Log($"Report complete : {pass}"); pass.Should().BeTrue(); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TestRunners/TransactionalStateStorageTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using AwesomeAssertions; using AwesomeAssertions.Equivalency; using Orleans.Transactions.Abstractions; namespace Orleans.Transactions.TestKit { public abstract class TransactionalStateStorageTestRunner : TransactionTestRunnerBase where TState : class, new() { protected Func>> stateStorageFactory; protected Func stateFactory; protected Func, EquivalencyOptions> assertConfig; /// /// Constructor /// /// factory to create ITransactionalStateStorage, the test runner are assuming the state /// in storage is empty when ITransactionalStateStorage was created /// factory to create TState for test /// grain Factory needed for test runner /// test output to helpful messages /// A reference to the AwesomeAssertions.Equivalency.EquivalencyOptions`1 /// configuration object that can be used to influence the way the object graphs /// are compared protected TransactionalStateStorageTestRunner(Func>> stateStorageFactory, Func stateFactory, IGrainFactory grainFactory, Action testOutput, Func, EquivalencyOptions> assertConfig = null) :base(grainFactory, testOutput) { this.stateStorageFactory = stateStorageFactory; this.stateFactory = stateFactory; this.assertConfig = assertConfig; } public virtual async Task FirstTime_Load_ShouldReturnEmptyLoadResponse() { var stateStorage = await this.stateStorageFactory(); var response = await stateStorage.Load(); var defaultStateValue = new TState(); //Assertion response.Should().NotBeNull(); response.ETag.Should().BeNull(); response.CommittedSequenceId.Should().Be(0); AssertTState(response.CommittedState, defaultStateValue); response.PendingStates.Should().BeEmpty(); } private static readonly List> emptyPendingStates = new List>(); public virtual async Task StoreWithoutChanges() { var stateStorage = await this.stateStorageFactory(); // load first time var loadresponse = await stateStorage.Load(); // store without any changes var etag1 = await stateStorage.Store(loadresponse.ETag, loadresponse.Metadata, emptyPendingStates, null, null); // load again loadresponse = await stateStorage.Load(); loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Should().BeEmpty(); loadresponse.ETag.Should().Be(etag1); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Should().BeEmpty(); // update metadata, then write back var now = DateTime.UtcNow; var cr = MakeCommitRecords(2, 2); var metadata = new TransactionalStateMetaData() { TimeStamp = now, CommitRecords = cr }; var etag2 = await stateStorage.Store(etag1, metadata, emptyPendingStates, null, null); // load again, check content loadresponse = await stateStorage.Load(); loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.Metadata.TimeStamp.Should().Be(now); loadresponse.Metadata.CommitRecords.Count.Should().Be(cr.Count); loadresponse.ETag.Should().Be(etag2); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Should().BeEmpty(); } public virtual async Task WrongEtags() { var stateStorage = await this.stateStorageFactory(); // load first time var loadresponse = await stateStorage.Load(); // store with wrong e-tag, must fail try { var etag1 = await stateStorage.Store("wrong-etag", loadresponse.Metadata, emptyPendingStates, null, null); throw new Exception("storage did not catch e-tag mismatch"); } catch (Exception) { } // load again loadresponse = await stateStorage.Load(); loadresponse.Should().NotBeNull(); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Should().BeEmpty(); loadresponse.ETag.Should().BeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Should().BeEmpty(); // update timestamp in metadata, then write back with correct e-tag var now = DateTime.UtcNow; var cr = MakeCommitRecords(2,2); var metadata = new TransactionalStateMetaData() { TimeStamp = now, CommitRecords = cr }; var etag2 = await stateStorage.Store(null, metadata, emptyPendingStates, null, null); // update timestamp in metadata, then write back with wrong e-tag, must fail try { var now2 = DateTime.UtcNow; var metadata2 = new TransactionalStateMetaData() { TimeStamp = now2, CommitRecords = MakeCommitRecords(3,3) }; await stateStorage.Store(null, metadata, emptyPendingStates, null, null); throw new Exception("storage did not catch e-tag mismatch"); } catch (Exception) { } // load again, check content loadresponse = await stateStorage.Load(); loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.Metadata.TimeStamp.Should().Be(now); loadresponse.Metadata.CommitRecords.Count.Should().Be(cr.Count); loadresponse.ETag.Should().Be(etag2); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Should().BeEmpty(); } private void AssertTState(TState actual, TState expected) { if(assertConfig == null) actual.Should().BeEquivalentTo(expected); else actual.Should().BeEquivalentTo(expected, assertConfig); } private static PendingTransactionState MakePendingState(long seqno, TState val, bool tm) { var result = new PendingTransactionState() { SequenceId = seqno, TimeStamp = DateTime.UtcNow, TransactionId = Guid.NewGuid().ToString(), TransactionManager = tm ? default : MakeParticipantId(), State = new TState() }; result.State = val; return result; } private static ParticipantId MakeParticipantId() { return new ParticipantId( "tm", null, // (GrainReference) grainFactory.GetGrain(Guid.NewGuid(), TransactionTestConstants.SingleStateTransactionalGrain), ParticipantId.Role.Resource | ParticipantId.Role.Manager); } private static Dictionary MakeCommitRecords(int count, int size) { var result = new Dictionary(); for (int j = 0; j < size; j++) { var r = new CommitRecord() { Timestamp = DateTime.UtcNow, WriteParticipants = new List(), }; for (int i = 0; i < size; i++) { r.WriteParticipants.Add(MakeParticipantId()); } result.Add(Guid.NewGuid(), r); } return result; } private async Task PrepareOne() { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedState = this.stateFactory(123); var pendingstate = MakePendingState(1, expectedState, false); _ = await stateStorage.Store(etag, metadata, new List>() { pendingstate }, null, null); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(1); loadresponse.PendingStates[0].SequenceId.Should().Be(1); loadresponse.PendingStates[0].TimeStamp.Should().Be(pendingstate.TimeStamp); loadresponse.PendingStates[0].TransactionManager.Should().Be(pendingstate.TransactionManager); loadresponse.PendingStates[0].TransactionId.Should().Be(pendingstate.TransactionId); AssertTState(loadresponse.PendingStates[0].State, expectedState); } public virtual async Task ConfirmOne(bool useTwoSteps) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedState = this.stateFactory(123); var pendingstate = MakePendingState(1, expectedState, false); if (useTwoSteps) { etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate }, null, null); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, 1, null); } else { _ = await stateStorage.Store(etag, metadata, new List>() { pendingstate }, 1, null); } loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(1); loadresponse.PendingStates.Count.Should().Be(0); AssertTState(loadresponse.CommittedState, expectedState); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); } public virtual async Task CancelOne() { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var pendingstate = MakePendingState(1, this.stateFactory(123), false); etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate }, null, null); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, null, 0); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(0); AssertTState(loadresponse.CommittedState,initialstate); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); } public virtual async Task ReplaceOne() { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedState1 = this.stateFactory(123); var expectedState2 = this.stateFactory(456); var pendingstate1 = MakePendingState(1, expectedState1, false); var pendingstate2 = MakePendingState(1, expectedState2, false); etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate1 }, null, null); _ = await stateStorage.Store(etag, metadata, new List>() { pendingstate2 }, null, null); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(1); loadresponse.PendingStates[0].SequenceId.Should().Be(1); loadresponse.PendingStates[0].TimeStamp.Should().Be(pendingstate2.TimeStamp); loadresponse.PendingStates[0].TransactionManager.Should().Be(pendingstate2.TransactionManager); loadresponse.PendingStates[0].TransactionId.Should().Be(pendingstate2.TransactionId); AssertTState(loadresponse.PendingStates[0].State,expectedState2); } public virtual async Task ConfirmOneAndCancelOne(bool useTwoSteps = false, bool reverseOrder = false) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedState = this.stateFactory(123); var pendingstate1 = MakePendingState(1, expectedState, false); var pendingstate2 = MakePendingState(2, this.stateFactory(456), false); etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate1, pendingstate2 }, null, null); if (useTwoSteps) { if (reverseOrder) { etag = await stateStorage.Store(etag, metadata, emptyPendingStates, null, 1); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, 1, null); } else { etag = await stateStorage.Store(etag, metadata, emptyPendingStates, 1, null); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, null, 1); } } else { _ = await stateStorage.Store(etag, metadata, emptyPendingStates, 1, 1); } loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(1); loadresponse.PendingStates.Count.Should().Be(0); AssertTState(loadresponse.CommittedState,expectedState); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); } public virtual async Task PrepareMany(int count) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var pendingstates = new List>(); var expectedStates = new List(); for (int i = 0; i < count; i++) { expectedStates.Add(this.stateFactory(i * 1000)); } for (int i = 0; i < count; i++) { pendingstates.Add(MakePendingState(i + 1, expectedStates[i], false)); } _ = await stateStorage.Store(etag, metadata, pendingstates, null, null); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(count); for (int i = 0; i < count; i++) { loadresponse.PendingStates[i].SequenceId.Should().Be(i+1); loadresponse.PendingStates[i].TimeStamp.Should().Be(pendingstates[i].TimeStamp); loadresponse.PendingStates[i].TransactionManager.Should().Be(pendingstates[i].TransactionManager); loadresponse.PendingStates[i].TransactionId.Should().Be(pendingstates[i].TransactionId); AssertTState(loadresponse.PendingStates[i].State,expectedStates[i]); } } public virtual async Task ConfirmMany(int count, bool useTwoSteps) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedStates = new List(); for (int i = 0; i < count; i++) { expectedStates.Add(this.stateFactory(i * 1000)); } var pendingstates = new List>(); for (int i = 0; i < count; i++) { pendingstates.Add(MakePendingState(i + 1, expectedStates[i], false)); } if (useTwoSteps) { etag = await stateStorage.Store(etag, metadata, pendingstates, null, null); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, count, null); } else { _ = await stateStorage.Store(etag, metadata, pendingstates, count, null); } loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(count); loadresponse.PendingStates.Count.Should().Be(0); AssertTState(loadresponse.CommittedState,expectedStates[count - 1]); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); } public virtual async Task CancelMany(int count) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedStates = new List(); for (int i = 0; i < count; i++) { expectedStates.Add(this.stateFactory(i * 1000)); } var pendingstates = new List>(); for (int i = 0; i < count; i++) { pendingstates.Add(MakePendingState(i + 1, expectedStates[i], false)); } etag = await stateStorage.Store(etag, metadata, pendingstates, null, null); _ = await stateStorage.Store(etag, metadata, emptyPendingStates, null, 0); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(0); AssertTState(loadresponse.CommittedState,initialstate); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); } public virtual async Task ReplaceMany(int count) { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var expectedStates1 = new List(); for (int i = 0; i < count; i++) { expectedStates1.Add(this.stateFactory(i * 1000 + 1)); } var expectedStates2 = new List(); for (int i = 0; i < count; i++) { expectedStates2.Add(this.stateFactory(i * 1000)); } var pendingstates1 = new List>(); for (int i = 0; i < count; i++) { pendingstates1.Add(MakePendingState(i + 1, expectedStates1[i], false)); } var pendingstates2 = new List>(); for (int i = 0; i < count; i++) { pendingstates2.Add(MakePendingState(i + 1, expectedStates2[i], false)); } etag = await stateStorage.Store(etag, metadata, pendingstates1, null, null); _ = await stateStorage.Store(etag, metadata, pendingstates2, null, null); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(count); for (int i = 0; i < count; i++) { loadresponse.PendingStates[i].SequenceId.Should().Be(i + 1); loadresponse.PendingStates[i].TimeStamp.Should().Be(pendingstates2[i].TimeStamp); loadresponse.PendingStates[i].TransactionManager.Should().Be(pendingstates2[i].TransactionManager); loadresponse.PendingStates[i].TransactionId.Should().Be(pendingstates2[i].TransactionId); AssertTState(loadresponse.PendingStates[i].State, expectedStates2[i]); } } public virtual async Task GrowingBatch() { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var pendingstate1 = MakePendingState(1, this.stateFactory(11), false); var pendingstate2 = MakePendingState(2, this.stateFactory(22), false); var pendingstate3a = MakePendingState(3, this.stateFactory(333), false); var pendingstate4a = MakePendingState(4, this.stateFactory(444), false); var pendingstate3b = MakePendingState(3, this.stateFactory(33), false); var pendingstate4b = MakePendingState(4, this.stateFactory(44), false); var pendingstate5 = MakePendingState(5, this.stateFactory(55), false); var expectedState6 = this.stateFactory(66); var pendingstate6 = MakePendingState(6, expectedState6, false); var expectedState7 = this.stateFactory(77); var pendingstate7 = MakePendingState(7, expectedState7, false); var expectedState8 = this.stateFactory(88); var pendingstate8 = MakePendingState(8, expectedState8, false); // prepare 1,2,3a,4a etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate1, pendingstate2, pendingstate3a, pendingstate4a}, null, null); // replace 3b,4b, prepare 5, 6, 7, 8 confirm 1, 2, 3b, 4b, 5, 6 _ = await stateStorage.Store(etag, metadata, new List>() { pendingstate3b, pendingstate4b, pendingstate5, pendingstate6, pendingstate7, pendingstate8 }, 6, null); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(6); AssertTState(loadresponse.CommittedState, expectedState6); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(2); loadresponse.PendingStates[0].SequenceId.Should().Be(7); loadresponse.PendingStates[0].TimeStamp.Should().Be(pendingstate7.TimeStamp); loadresponse.PendingStates[0].TransactionManager.Should().Be(pendingstate7.TransactionManager); loadresponse.PendingStates[0].TransactionId.Should().Be(pendingstate7.TransactionId); AssertTState(loadresponse.PendingStates[0].State, expectedState7); loadresponse.PendingStates[1].SequenceId.Should().Be(8); loadresponse.PendingStates[1].TimeStamp.Should().Be(pendingstate8.TimeStamp); loadresponse.PendingStates[1].TransactionManager.Should().Be(pendingstate8.TransactionManager); loadresponse.PendingStates[1].TransactionId.Should().Be(pendingstate8.TransactionId); AssertTState(loadresponse.PendingStates[1].State, expectedState8); } public virtual async Task ShrinkingBatch() { var stateStorage = await this.stateStorageFactory(); var loadresponse = await stateStorage.Load(); var etag = loadresponse.ETag; var metadata = loadresponse.Metadata; var initialstate = loadresponse.CommittedState; var pendingstate1 = MakePendingState(1, this.stateFactory(11), false); var pendingstate2 = MakePendingState(2, this.stateFactory(22), false); var pendingstate3a = MakePendingState(3, this.stateFactory(333), false); var pendingstate4a = MakePendingState(4, this.stateFactory(444), false); var pendingstate5 = MakePendingState(5, this.stateFactory(55), false); var pendingstate6 = MakePendingState(6, this.stateFactory(66), false); var pendingstate7 = MakePendingState(7, this.stateFactory(77), false); var pendingstate8 = MakePendingState(8, this.stateFactory(88), false); var expectedState3b = this.stateFactory(33); var pendingstate3b = MakePendingState(3, expectedState3b, false); var expectedState4b = this.stateFactory(44); var pendingstate4b = MakePendingState(4, expectedState4b, false); // prepare 1,2,3a,4a, 5, 6, 7, 8 etag = await stateStorage.Store(etag, metadata, new List>() { pendingstate1, pendingstate2, pendingstate3a, pendingstate4a, pendingstate5, pendingstate6, pendingstate7, pendingstate8 }, null, null); // replace 3b,4b, confirm 1, 2, 3b, cancel 5, 6, 7, 8 _ = await stateStorage.Store(etag, metadata, new List>() { pendingstate3b, pendingstate4b }, 3, 4); loadresponse = await stateStorage.Load(); _ = loadresponse.ETag; _ = loadresponse.Metadata; loadresponse.Should().NotBeNull(); loadresponse.Metadata.Should().NotBeNull(); loadresponse.CommittedSequenceId.Should().Be(3); AssertTState(loadresponse.CommittedState, expectedState3b); loadresponse.Metadata.TimeStamp.Should().Be(default); loadresponse.Metadata.CommitRecords.Count.Should().Be(0); loadresponse.PendingStates.Count.Should().Be(1); loadresponse.PendingStates[0].SequenceId.Should().Be(4); loadresponse.PendingStates[0].TimeStamp.Should().Be(pendingstate4b.TimeStamp); loadresponse.PendingStates[0].TransactionManager.Should().Be(pendingstate4b.TransactionManager); loadresponse.PendingStates[0].TransactionId.Should().Be(pendingstate4b.TransactionId); AssertTState(loadresponse.PendingStates[0].State, expectedState4b); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TransactionTestConstants.cs ================================================  namespace Orleans.Transactions.TestKit { public static class TransactionTestConstants { /// /// Max number of grains to include in a transaction for test purposes. Not a hard limit of the transaction system. /// public const int MaxCoordinatedTransactions = 8; // storage providers public const string TransactionStore = "TransactionStore"; // committer service public const string RemoteCommitService = "RemoteCommitService"; // grain implementations public const string NoStateTransactionalGrain = "NoStateTransactionalGrain"; public const string SingleStateTransactionalGrain = "SingleStateTransactionalGrain"; public const string DoubleStateTransactionalGrain = "DoubleStateTransactionalGrain"; public const string MaxStateTransactionalGrain = "MaxStateTransactionalGrain"; } } ================================================ FILE: src/Orleans.Transactions.TestKit.Base/TransactionTestRunnerBase.cs ================================================ using System; namespace Orleans.Transactions.TestKit { public class TransactionTestRunnerBase { protected readonly IGrainFactory grainFactory; protected readonly Action testOutput; protected TransactionTestRunnerBase(IGrainFactory grainFactory, Action testOutput) { this.grainFactory = grainFactory; this.testOutput = testOutput; } protected ITransactionTestGrain RandomTestGrain(string transactionTestGrainClassNames) { return RandomTestGrain(transactionTestGrainClassNames); } protected TGrainInterface RandomTestGrain(string transactionTestGrainClassNames) where TGrainInterface : IGrainWithGuidKey { return TestGrain(transactionTestGrainClassNames, Guid.NewGuid()); } protected virtual ITransactionTestGrain TestGrain(string transactionTestGrainClassName, Guid id) { return TestGrain(transactionTestGrainClassName, id); } protected virtual TGrainInterface TestGrain(string transactionTestGrainClassName, Guid id) where TGrainInterface : IGrainWithGuidKey { return grainFactory.GetGrain(id, $"{typeof(TGrainInterface).Namespace}.{transactionTestGrainClassName}"); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/ConsistencyTransactionTestRunner.cs ================================================ using System.Threading.Tasks; using Orleans.Transactions.TestKit.Consistency; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class ConsistencyTransactionTestRunnerxUnit : ConsistencyTransactionTestRunner { public ConsistencyTransactionTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) :base(grainFactory, output.WriteLine) { } protected override bool StorageAdaptorHasLimitedCommitSpace => true; protected override bool StorageErrorInjectionActive => true; [SkippableTheory] // high congestion [InlineData(2, 2, true, true, ReadWriteDetermination.PerGrain)] [InlineData(2, 3, true, true, ReadWriteDetermination.PerGrain)] [InlineData(2, 4, true, true, ReadWriteDetermination.PerGrain)] [InlineData(2, 5, true, true, ReadWriteDetermination.PerGrain)] [InlineData(2, 2, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(2, 3, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(2, 4, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(2, 5, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(2, 2, true, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 3, true, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 4, true, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 5, true, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 2, false, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 3, false, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 4, false, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 5, false, true, ReadWriteDetermination.PerAccess)] [InlineData(2, 2, true, false, ReadWriteDetermination.PerGrain)] [InlineData(2, 3, true, false, ReadWriteDetermination.PerGrain)] [InlineData(2, 4, true, false, ReadWriteDetermination.PerGrain)] [InlineData(2, 5, true, false, ReadWriteDetermination.PerGrain)] [InlineData(2, 2, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(2, 3, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(2, 4, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(2, 5, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(2, 2, true, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 3, true, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 4, true, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 5, true, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 2, false, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 3, false, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 4, false, false, ReadWriteDetermination.PerAccess)] [InlineData(2, 5, false, false, ReadWriteDetermination.PerAccess)] // medium congestion [InlineData(30, 2, true, true, ReadWriteDetermination.PerGrain)] [InlineData(30, 3, true, true, ReadWriteDetermination.PerGrain)] [InlineData(30, 4, true, true, ReadWriteDetermination.PerGrain)] [InlineData(30, 2, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(30, 3, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(30, 4, true, true, ReadWriteDetermination.PerTransaction)] [InlineData(30, 2, true, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 3, true, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 4, true, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 2, false, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 3, false, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 4, false, true, ReadWriteDetermination.PerAccess)] [InlineData(30, 2, true, false, ReadWriteDetermination.PerGrain)] [InlineData(30, 3, true, false, ReadWriteDetermination.PerGrain)] [InlineData(30, 4, true, false, ReadWriteDetermination.PerGrain)] [InlineData(30, 5, true, false, ReadWriteDetermination.PerGrain)] [InlineData(30, 2, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(30, 3, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(30, 4, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(30, 5, true, false, ReadWriteDetermination.PerTransaction)] [InlineData(30, 2, true, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 3, true, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 4, true, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 5, true, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 2, false, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 3, false, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 4, false, false, ReadWriteDetermination.PerAccess)] [InlineData(30, 5, false, false, ReadWriteDetermination.PerAccess)] // low congestion [InlineData(1000, 2, false, true, ReadWriteDetermination.PerGrain)] [InlineData(1000, 3, false, true, ReadWriteDetermination.PerGrain)] [InlineData(1000, 4, false, true, ReadWriteDetermination.PerGrain)] [InlineData(1000, 2, false, true, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 3, false, true, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 4, false, true, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 2, false, true, ReadWriteDetermination.PerAccess)] [InlineData(1000, 3, false, true, ReadWriteDetermination.PerAccess)] [InlineData(1000, 4, false, true, ReadWriteDetermination.PerAccess)] [InlineData(1000, 2, false, false, ReadWriteDetermination.PerGrain)] [InlineData(1000, 3, false, false, ReadWriteDetermination.PerGrain)] [InlineData(1000, 4, false, false, ReadWriteDetermination.PerGrain)] [InlineData(1000, 5, false, false, ReadWriteDetermination.PerGrain)] [InlineData(1000, 2, false, false, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 3, false, false, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 4, false, false, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 5, false, false, ReadWriteDetermination.PerTransaction)] [InlineData(1000, 2, false, false, ReadWriteDetermination.PerAccess)] [InlineData(1000, 3, false, false, ReadWriteDetermination.PerAccess)] [InlineData(1000, 4, false, false, ReadWriteDetermination.PerAccess)] [InlineData(1000, 5, false, false, ReadWriteDetermination.PerAccess)] public override Task RandomizedConsistency(int numGrains, int scale, bool avoidDeadlocks, bool avoidTimeouts, ReadWriteDetermination readwrite) { return base.RandomizedConsistency(numGrains, scale, avoidDeadlocks, avoidTimeouts, readwrite); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/ControlledFaultInjectionTransactionTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public class ControlledFaultInjectionTransactionTestRunnerxUnit : ControlledFaultInjectionTransactionTestRunner { public ControlledFaultInjectionTransactionTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableFact] public override Task SingleGrainReadTransaction() { return base.SingleGrainReadTransaction(); } [SkippableFact] public override Task SingleGrainWriteTransaction() { return base.SingleGrainWriteTransaction(); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9551")] [InlineData(TransactionFaultInjectPhase.AfterPrepare, FaultInjectionType.Deactivation)] [InlineData(TransactionFaultInjectPhase.AfterConfirm, FaultInjectionType.Deactivation)] [InlineData(TransactionFaultInjectPhase.AfterPrepared, FaultInjectionType.Deactivation)] [InlineData(TransactionFaultInjectPhase.AfterPrepareAndCommit, FaultInjectionType.Deactivation)] [InlineData(TransactionFaultInjectPhase.BeforePrepare, FaultInjectionType.ExceptionAfterStore)] [InlineData(TransactionFaultInjectPhase.BeforePrepare, FaultInjectionType.ExceptionBeforeStore)] [InlineData(TransactionFaultInjectPhase.BeforeConfirm, FaultInjectionType.ExceptionAfterStore)] [InlineData(TransactionFaultInjectPhase.BeforeConfirm, FaultInjectionType.ExceptionBeforeStore)] [InlineData(TransactionFaultInjectPhase.BeforePrepareAndCommit, FaultInjectionType.ExceptionAfterStore)] [InlineData(TransactionFaultInjectPhase.BeforePrepareAndCommit, FaultInjectionType.ExceptionBeforeStore)] public override Task MultiGrainWriteTransaction_FaultInjection(TransactionFaultInjectPhase injectionPhase, FaultInjectionType injectionType) { return base.MultiGrainWriteTransaction_FaultInjection(injectionPhase, injectionType); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/DisabledTransactionsTestRunner.cs ================================================ using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public class DisabledTransactionsTestRunnerxUnit : DisabledTransactionsTestRunner { protected DisabledTransactionsTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableTheory] [InlineData(TransactionTestConstants.NoStateTransactionalGrain)] public override void TransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { base.TransactionGrainsThrowWhenTransactions(transactionTestGrainClassName); } [SkippableTheory] [InlineData(TransactionTestConstants.NoStateTransactionalGrain)] public override void MultiTransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { base.MultiTransactionGrainsThrowWhenTransactions(transactionTestGrainClassName); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/GoldenPathTransactionTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class GoldenPathTransactionTestRunnerxUnit : GoldenPathTransactionTestRunner { protected GoldenPathTransactionTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task SingleGrainReadTransaction(string grainStates) { return base.SingleGrainReadTransaction(grainStates); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task SingleGrainWriteTransaction(string grainStates) { return base.SingleGrainWriteTransaction(grainStates); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task MultiGrainWriteTransaction(string grainStates, int grainCount) { return base.MultiGrainWriteTransaction(grainStates, grainCount); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task MultiGrainReadWriteTransaction(string grainStates, int grainCount) { return base.MultiGrainReadWriteTransaction(grainStates, grainCount); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task RepeatGrainReadWriteTransaction(string grainStates, int grainCount) { return base.RepeatGrainReadWriteTransaction(grainStates, grainCount); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task MultiWriteToSingleGrainTransaction(string grainStates) { return base.MultiWriteToSingleGrainTransaction(grainStates); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task RWRWTest(string grainStates, int grainCount) { return base.RWRWTest(grainStates, grainCount); } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9553")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task WRWRTest(string grainStates, int grainCount) { return base.WRWRTest(grainStates, grainCount); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/GrainFaultTransactionTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public class GrainFaultTransactionTestRunnerxUnit : GrainFaultTransactionTestRunner { public GrainFaultTransactionTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task AbortTransactionOnExceptions(string grainStates) { return base.AbortTransactionOnExceptions(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task AbortTransactionOnReadOnlyViolatedException(string grainStates) { return base.AbortTransactionOnReadOnlyViolatedException(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task MultiGrainAbortTransactionOnExceptions(string grainStates) { return base.MultiGrainAbortTransactionOnExceptions(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task AbortTransactionExceptionInnerExceptionOnlyContainsOneRootCauseException(string grainStates) { return base.AbortTransactionExceptionInnerExceptionOnlyContainsOneRootCauseException(grainStates); } [SkippableTheory()] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task AbortTransactionOnOrphanCalls(string grainStates) { return base.AbortTransactionOnOrphanCalls(grainStates); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/Orleans.Transactions.TestKit.xUnit.csproj ================================================ Microsoft.Orleans.Transactions.TestKit.xUnit Microsoft Orleans Transactions test kit for xUnit xUnit testkit library for transactions $(PackageTags) TransactionTestKit $(DefaultTargetFrameworks) true false Orleans.Transactions.TestKit.xUnit Orleans.Transactions.TestKit.xUnit ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/ScopedTransactionsTestRunnerxUnit.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class ScopedTransactionsTestRunnerxUnit : ScopedTransactionsTestRunner { protected ScopedTransactionsTestRunnerxUnit(IGrainFactory grainFactory, ITransactionClient transactionFrame, ITestOutputHelper output) : base(grainFactory, transactionFrame, output.WriteLine) { } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task CreateTransactionScopeAndSetValue(string grainStates) { return base.CreateTransactionScopeAndSetValue(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task CreateTransactionScopeAndSetValueWithFailure(string grainStates) { return base.CreateTransactionScopeAndSetValueWithFailure(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task CreateTransactionScopeAndSetValueAndAssert(string grainStates) { return base.CreateTransactionScopeAndSetValueAndAssert(grainStates); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task CreateNestedTransactionScopeAndSetValueAndInnerFailAndAssert(string grainStates) { return base.CreateNestedTransactionScopeAndSetValueAndInnerFailAndAssert(grainStates); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/TOCGoldenPathTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class TocGoldenPathTestRunnerxUnit : TocGoldenPathTestRunner { protected TocGoldenPathTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9556")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] public override Task MultiGrainWriteTransaction(string grainStates, int grainCount) { return base.MultiGrainWriteTransaction(grainStates, grainCount); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/TocFaultTransactionTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class TocFaultTransactionTestRunnerxUnit : TocFaultTransactionTestRunner { protected TocFaultTransactionTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9556")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task MultiGrainWriteTransactionWithCommitFailure(string grainStates, int grainCount) { return base.MultiGrainWriteTransactionWithCommitFailure(grainStates, grainCount); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, TransactionTestConstants.MaxCoordinatedTransactions / 2)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain, 1)] public override Task MultiGrainWriteTransactionWithCommitException(string grainStates, int grainCount) { return base.MultiGrainWriteTransactionWithCommitException(grainStates, grainCount); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/TransactionConcurrencyTestRunner.cs ================================================ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class TransactionConcurrencyTestRunnerxUnit : TransactionConcurrencyTestRunner { protected TransactionConcurrencyTestRunnerxUnit(IGrainFactory grainFactory, ITestOutputHelper output) : base(grainFactory, output.WriteLine) { } /// /// Two transaction share a single grain /// /// /// [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9554")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task SingleSharedGrainTest(string grainStates) { return base.SingleSharedGrainTest(grainStates); } /// /// Chain of transactions, each dependent on the results of the previous /// /// /// [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9554")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task TransactionChainTest(string grainStates) { return base.TransactionChainTest(grainStates); } /// /// Single transaction containing two grains is dependent on two other transaction, one from each grain /// /// /// [SkippableTheory(Skip = "https://github.com/dotnet/orleans/issues/9554")] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain)] [InlineData(TransactionTestConstants.MaxStateTransactionalGrain)] public override Task TransactionTreeTest(string grainStates) { return base.TransactionTreeTest(grainStates); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/TransactionRecoveryTestsRunner.cs ================================================ using System.Threading.Tasks; using Orleans.TestingHost; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public class TransactionRecoveryTestsRunnerxUnit : TransactionRecoveryTestsRunner { public TransactionRecoveryTestsRunnerxUnit(TestCluster cluster, ITestOutputHelper testOutput) :base(cluster, testOutput.WriteLine) { } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, 30)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, 20)] public override Task TransactionWillRecoverAfterRandomSiloGracefulShutdown(string transactionTestGrainClassName, int concurrent) { return base.TransactionWillRecoverAfterRandomSiloGracefulShutdown(transactionTestGrainClassName, concurrent); } [SkippableTheory] [InlineData(TransactionTestConstants.SingleStateTransactionalGrain, 30)] [InlineData(TransactionTestConstants.DoubleStateTransactionalGrain, 20)] public override Task TransactionWillRecoverAfterRandomSiloUnGracefulShutdown(string transactionTestGrainClassName, int concurrent) { return base.TransactionWillRecoverAfterRandomSiloUnGracefulShutdown(transactionTestGrainClassName, concurrent); } } } ================================================ FILE: src/Orleans.Transactions.TestKit.xUnit/TransactionalStateStorageTestRunner.cs ================================================ using System; using System.Threading.Tasks; using AwesomeAssertions.Equivalency; using Orleans.Transactions.Abstractions; using Xunit; using Xunit.Abstractions; namespace Orleans.Transactions.TestKit.xUnit { public abstract class TransactionalStateStorageTestRunnerxUnit : TransactionalStateStorageTestRunner where TState: class, new() { /// /// Constructor /// /// factory to create ITransactionalStateStorage, the test runner are assuming the state /// in storage is empty when ITransactionalStateStorage was created /// factory to create TState for test /// grain Factory needed for test runner /// test output to helpful messages /// A reference to the AwesomeAssertions.Equivalency.EquivalencyOptions`1 /// configuration object that can be used to influence the way the object graphs /// are compared public TransactionalStateStorageTestRunnerxUnit(Func>> stateStorageFactory, Func stateFactory, IGrainFactory grainFactory, ITestOutputHelper testOutput, Func, EquivalencyOptions> assertConfig = null) : base(stateStorageFactory, stateFactory, grainFactory, testOutput.WriteLine, assertConfig) { } [Fact] public override Task FirstTime_Load_ShouldReturnEmptyLoadResponse() { return base.FirstTime_Load_ShouldReturnEmptyLoadResponse(); } [Theory] [InlineData(true)] [InlineData(false)] public override Task ConfirmOne(bool useTwoSteps) { return base.ConfirmOne(useTwoSteps); } [Fact] public override Task CancelOne() { return base.CancelOne(); } [Fact] public override Task ReplaceOne() { return base.ReplaceOne(); } [Theory] [InlineData(false, false)] [InlineData(true, true)] [InlineData(true, false)] public override Task ConfirmOneAndCancelOne(bool useTwoSteps, bool reverseOrder) { return base.ConfirmOneAndCancelOne(useTwoSteps, reverseOrder); } [Fact] public override Task GrowingBatch() { return base.GrowingBatch(); } [Fact] public override Task ShrinkingBatch() { return base.ShrinkingBatch(); } [Theory] [InlineData(99)] [InlineData(100)] [InlineData(200)] public override Task PrepareMany(int count) { return base.PrepareMany(count); } [Theory] [InlineData(99, true)] [InlineData(99, false)] [InlineData(100, true)] [InlineData(100, false)] [InlineData(200, true)] [InlineData(200, false)] public override Task ConfirmMany(int count, bool useTwoSteps) { return base.ConfirmMany(count, useTwoSteps); } [Theory] [InlineData(99)] [InlineData(100)] [InlineData(200)] public override Task CancelMany(int count) { return base.CancelMany(count); } [Theory] [InlineData(99)] [InlineData(100)] [InlineData(200)] public override Task ReplaceMany(int count) { return base.ReplaceMany(count); } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Hosting/HostingExtensions.ICientBuilder.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Messaging; using Orleans.Clustering.Redis; using StackExchange.Redis; namespace Microsoft.Extensions.Hosting { /// /// Hosting extensions for Redis clustering. /// public static class RedisClusteringIClientBuilderExtensions { /// /// Configures Redis as the clustering provider. /// public static IClientBuilder UseRedisClustering(this IClientBuilder builder, Action configuration) { return builder.ConfigureServices(services => { if (configuration != null) { services.Configure(configuration); } services .AddRedisClustering() .AddSingleton(); }); } /// /// Configures Redis as the clustering provider. /// public static IClientBuilder UseRedisClustering(this IClientBuilder builder, string redisConnectionString) { return builder.ConfigureServices(services => services .Configure(opt => { opt.ConfigurationOptions = ConfigurationOptions.Parse(redisConnectionString); }) .AddRedisClustering() .AddSingleton()); } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Hosting/HostingExtensions.ISiloBuilder.cs ================================================ using System; using Orleans; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Clustering.Redis; using StackExchange.Redis; namespace Microsoft.Extensions.Hosting { /// /// Hosting extensions for the Redis clustering provider. /// public static class RedisClusteringISiloBuilderExtensions { /// /// Configures Redis as the clustering provider. /// public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, Action configuration) { return builder.ConfigureServices(services => { if (configuration != null) { services.Configure(configuration); } services.AddRedisClustering(); }); } /// /// Configures Redis as the clustering provider. /// public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, string redisConnectionString) { return builder.ConfigureServices(services => services .Configure(options => { options.ConfigurationOptions = ConfigurationOptions.Parse(redisConnectionString); }) .AddRedisClustering()); } internal static IServiceCollection AddRedisClustering(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); return services; } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Hosting/RedisClusteringProviderBuilder.cs ================================================ using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using StackExchange.Redis; using Orleans.Clustering.Redis.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; [assembly: RegisterProvider("Redis", "Clustering", "Silo", typeof(RedisClusteringProviderBuilder))] [assembly: RegisterProvider("AzureRedisCache", "Clustering", "Silo", typeof(RedisClusteringProviderBuilder))] [assembly: RegisterProvider("Redis", "Clustering", "Client", typeof(RedisClusteringProviderBuilder))] [assembly: RegisterProvider("AzureRedisCache", "Clustering", "Client", typeof(RedisClusteringProviderBuilder))] namespace Orleans.Clustering.Redis.Hosting; internal sealed class RedisClusteringProviderBuilder : IProviderBuilder, IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseRedisClustering(_ => { }); builder.Services.AddOptions() .Configure((options, services) => { var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a connection multiplexer instance by name. var multiplexer = services.GetRequiredKeyedService(serviceKey); options.CreateMultiplexer = _ => Task.FromResult(multiplexer); options.ConfigurationOptions = new ConfigurationOptions(); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString); } } }); } public void Configure(IClientBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseRedisClustering(_ => { }); builder.Services.AddOptions() .Configure((options, services) => { var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a connection multiplexer instance by name. var multiplexer = services.GetRequiredKeyedService(serviceKey); options.CreateMultiplexer = _ => Task.FromResult(multiplexer); options.ConfigurationOptions = new ConfigurationOptions(); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString); } } }); } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Orleans.Clustering.Redis.csproj ================================================ README.md Microsoft.Orleans.Clustering.Redis Microsoft Orleans Clustering Redis Provider Microsoft Orleans Clustering implementation that uses Redis $(PackageTags) Redis Clustering $(DefaultTargetFrameworks) true ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Providers/RedisClusteringOptions.cs ================================================ using Microsoft.Extensions.Options; using Orleans.Runtime; using StackExchange.Redis; using System; using System.Text; using System.Threading.Tasks; using Orleans.Configuration; namespace Orleans.Clustering.Redis { /// /// Options for Redis clustering. /// public class RedisClusteringOptions { /// /// Gets or sets the Redis client configuration. /// [RedactRedisConfigurationOptions] public ConfigurationOptions ConfigurationOptions { get; set; } /// /// The delegate used to create a Redis connection multiplexer. /// public Func> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer; /// /// The delegate used to create redis key for RedisMembershipTable. /// public Func CreateRedisKey { get; set; } = DefaultCreateRedisKey; /// /// Entry expiry, null by default. A value should be set ONLY for ephemeral environments (like in tests). /// Setting a value different from null will cause entries to be deleted after some period of time. /// public TimeSpan? EntryExpiry { get; set; } = null; /// /// The default multiplexer creation delegate. /// public static async Task DefaultCreateMultiplexer(RedisClusteringOptions options) { return await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions); } /// /// The default multiplexer creation redis key for RedisMembershipTable. /// /// public static RedisKey DefaultCreateRedisKey(ClusterOptions clusterOptions) { return Encoding.UTF8.GetBytes($"{clusterOptions.ServiceId}/members/{clusterOptions.ClusterId}"); } } internal class RedactRedisConfigurationOptions : RedactAttribute { public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value); } /// /// Configuration validator for . /// public class RedisClusteringOptionsValidator : IConfigurationValidator { private readonly RedisClusteringOptions _options; public RedisClusteringOptionsValidator(IOptions options) { _options = options.Value; } /// public void ValidateConfiguration() { if (_options.ConfigurationOptions == null) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisMembershipTable)}. {nameof(RedisClusteringOptions)}.{nameof(_options.ConfigurationOptions)} is required."); } } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Providers/RedisGatewayListProvider.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans.Messaging; using Orleans.Runtime; using Orleans.Configuration; using System.Linq; using Microsoft.Extensions.Options; namespace Orleans.Clustering.Redis; internal sealed class RedisGatewayListProvider(RedisMembershipTable table, IOptions options) : IGatewayListProvider { private readonly RedisMembershipTable _table = table; private readonly GatewayOptions _gatewayOptions = options.Value; public TimeSpan MaxStaleness => _gatewayOptions.GatewayListRefreshPeriod; public bool IsUpdatable => true; public async Task> GetGateways() { if (!_table.IsInitialized) { await _table.InitializeMembershipTable(true); } var all = await _table.ReadAll(); var result = all.Members .Where(x => x.Item1.Status == SiloStatus.Active && x.Item1.ProxyPort != 0) .Select(x => { var entry = x.Item1; return SiloAddress.New(entry.SiloAddress.Endpoint.Address, entry.ProxyPort, entry.SiloAddress.Generation).ToGatewayUri(); }).ToList(); return result; } public async Task InitializeGatewayListProvider() { await _table.InitializeMembershipTable(true); } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/README.md ================================================ # Microsoft Orleans Clustering for Redis ## Introduction Microsoft Orleans Clustering for Redis provides cluster membership functionality for Microsoft Orleans using Redis. This allows Orleans silos to coordinate and form a cluster using Redis as the backing store. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Clustering.Redis ``` ## Example - Configuring Redis Membership ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder // Configure Redis as the membership provider .UseRedisClustering(options => { options.ConnectionString = "localhost:6379"; options.Database = 0; }); }); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("Redis"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Example - Client Configuration ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; var clientBuilder = Host.CreateApplicationBuilder(args) .UseOrleansClient(builder => { builder // Configure Redis as the gateway provider .UseRedisGatewayListProvider(options => { options.ConnectionString = "localhost:6379"; options.Database = 0; }); }); var host = clientBuilder.Build(); await host.StartAsync(); var client = host.Services.GetRequiredService(); // Get a reference to a grain and call it var grain = client.GetGrain("user123"); var response = await grain.SayHello("Redis Client"); // Print the result Console.WriteLine($"Grain response: {response}"); // Keep the host running until the application is shut down await host.WaitForShutdownAsync(); ``` ## Configuration via Microsoft.Extensions.Configuration You can configure Orleans Redis clustering using `Microsoft.Extensions.Configuration` (such as `appsettings.json`) instead of configuring it in code. When using this approach, Orleans will automatically read the configuration from the `Orleans` section. > **Note**: You can use either `"ProviderType": "Redis"` or `"ProviderType": "AzureRedisCache"` - both are supported and functionally equivalent. ### Example - appsettings.json (Silo) ```json { "ConnectionStrings": { "redis": "localhost:6379" }, "Orleans": { "ClusterId": "my-cluster", "ServiceId": "MyOrleansService", "Clustering": { "ProviderType": "Redis", "ServiceKey": "redis" } } } ``` ### Example - appsettings.json (Client) ```json { "ConnectionStrings": { "redis": "localhost:6379" }, "Orleans": { "ClusterId": "my-cluster", "ServiceId": "MyOrleansService", "Clustering": { "ProviderType": "Redis", "ServiceKey": "redis" } } } ``` ### .NET Aspire Integration For applications using .NET Aspire, consider using the [.NET Aspire Redis integration](https://learn.microsoft.com/en-us/dotnet/aspire/caching/stackexchange-redis-integration) which provides simplified Redis configuration, automatic service discovery, health checks, and telemetry. The Aspire integration automatically configures connection strings that Orleans can consume via the configuration system. #### Example - Program.cs with Aspire Redis Integration ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); // Add service defaults (Aspire configurations) builder.AddServiceDefaults(); // Add Redis via Aspire client integration builder.AddKeyedRedisClient("redis"); // Add Orleans builder.UseOrleans(); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("Aspire Redis"); Console.WriteLine($"Grain response: {response}"); await host.WaitForShutdownAsync(); ``` This example assumes your AppHost project has configured Redis like this: ```csharp // In your AppHost/Program.cs var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("redis"); var orleans = builder.AddOrleans("orleans") .WithClustering(redis); builder.AddProject("orleans-app") .WithReference(orleans); builder.Build().Run(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Configuration Guide](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/) - [Orleans Clustering](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/cluster-management) - [Redis Documentation](https://redis.io/documentation) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Storage/JsonSettings.cs ================================================ using System; using Newtonsoft.Json; using System.Net; using Orleans.Runtime; using Newtonsoft.Json.Linq; using System.Globalization; namespace Orleans.Clustering.Redis { internal static class JsonSettings { public static JsonSerializerSettings JsonSerializerSettings => new JsonSerializerSettings { Formatting = Formatting.None, TypeNameHandling = TypeNameHandling.None, DefaultValueHandling = DefaultValueHandling.Include, DateTimeZoneHandling = DateTimeZoneHandling.Utc, DateParseHandling = DateParseHandling.DateTimeOffset, Culture = CultureInfo.InvariantCulture, MissingMemberHandling = MissingMemberHandling.Ignore, PreserveReferencesHandling = PreserveReferencesHandling.None, MetadataPropertyHandling = MetadataPropertyHandling.Ignore, MaxDepth = 10, Converters = { new IPAddressConverter(), new IPEndPointConverter(), new SiloAddressConverter() } }; private class IPAddressConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(IPAddress); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IPAddress ip = (IPAddress)value; writer.WriteValue(ip.ToString()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JToken token = JToken.Load(reader); return IPAddress.Parse(token.Value()); } } private class IPEndPointConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(IPEndPoint); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IPEndPoint ep = (IPEndPoint)value; writer.WriteStartObject(); writer.WritePropertyName("Address"); serializer.Serialize(writer, ep.Address); writer.WritePropertyName("Port"); writer.WriteValue(ep.Port); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); IPAddress address = jo["Address"].ToObject(serializer); int port = jo["Port"].Value(); return new IPEndPoint(address, port); } } private class SiloAddressConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(SiloAddress); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { SiloAddress addr = (SiloAddress)value; writer.WriteStartObject(); writer.WritePropertyName("SiloAddress"); writer.WriteValue(addr.ToParsableString()); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); SiloAddress addr = SiloAddress.FromParsableString(jo["SiloAddress"].ToObject()); return addr; } } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Storage/RedisClusteringException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Clustering.Redis { /// /// Represents an exception which occurred in the Redis clustering. /// [Serializable] public class RedisClusteringException : Exception { /// public RedisClusteringException() : base() { } /// public RedisClusteringException(string message) : base(message) { } /// public RedisClusteringException(string message, Exception innerException) : base(message, innerException) { } /// [Obsolete] protected RedisClusteringException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Redis/Orleans.Clustering.Redis/Storage/RedisMembershipTable.cs ================================================ #nullable enable using System; using System.Threading.Tasks; using Orleans.Runtime; using StackExchange.Redis; using Orleans.Configuration; using Newtonsoft.Json; using System.Linq; using Microsoft.Extensions.Options; using System.Globalization; using System.Diagnostics.CodeAnalysis; namespace Orleans.Clustering.Redis { internal class RedisMembershipTable : IMembershipTable, IDisposable { private const string TableVersionKey = "Version"; private static readonly TableVersion DefaultTableVersion = new TableVersion(0, "0"); private readonly RedisClusteringOptions _redisOptions; private readonly ClusterOptions _clusterOptions; private readonly JsonSerializerSettings _jsonSerializerSettings; private readonly RedisKey _clusterKey; private IConnectionMultiplexer _muxer = null!; private IDatabase _db = null!; public RedisMembershipTable(IOptions redisOptions, IOptions clusterOptions) { _redisOptions = redisOptions.Value; _clusterOptions = clusterOptions.Value; _clusterKey = _redisOptions.CreateRedisKey(_clusterOptions); _jsonSerializerSettings = JsonSettings.JsonSerializerSettings; } public bool IsInitialized { get; private set; } public async Task DeleteMembershipTableEntries(string clusterId) { await _db.KeyDeleteAsync(_clusterKey); } public async Task InitializeMembershipTable(bool tryInitTableVersion) { _muxer = await _redisOptions.CreateMultiplexer(_redisOptions); _db = _muxer.GetDatabase(); if (tryInitTableVersion) { await _db.HashSetAsync(_clusterKey, TableVersionKey, SerializeVersion(DefaultTableVersion), When.NotExists); if (_redisOptions.EntryExpiry is { } expiry) { await _db.KeyExpireAsync(_clusterKey, expiry); } } this.IsInitialized = true; } public async Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { return await UpsertRowInternal(entry, tableVersion, updateTableVersion: true, allowInsertOnly: true) == UpsertResult.Success; } private async Task UpsertRowInternal(MembershipEntry entry, TableVersion tableVersion, bool updateTableVersion, bool allowInsertOnly) { var tx = _db.CreateTransaction(); var rowKey = entry.SiloAddress.ToString(); if (updateTableVersion) { tx.HashSetAsync(_clusterKey, TableVersionKey, SerializeVersion(tableVersion)).Ignore(); } var versionCondition = tx.AddCondition(Condition.HashEqual(_clusterKey, TableVersionKey, SerializeVersion(Predeccessor(tableVersion)))); ConditionResult? insertCondition; if (allowInsertOnly) { insertCondition = tx.AddCondition(Condition.HashNotExists(_clusterKey, rowKey)); } else { insertCondition = null; } tx.HashSetAsync(_clusterKey, rowKey, Serialize(entry)).Ignore(); var success = await tx.ExecuteAsync(); if (success) { return UpsertResult.Success; } if (!versionCondition.WasSatisfied) { return UpsertResult.Conflict; } if (insertCondition is not null && !insertCondition.WasSatisfied) { return UpsertResult.Conflict; } return UpsertResult.Failure; } public async Task ReadAll() { var all = await _db.HashGetAllAsync(_clusterKey); var tableVersionRow = all.SingleOrDefault(h => TableVersionKey.Equals(h.Name, StringComparison.Ordinal)); TableVersion tableVersion = GetTableVersionFromRow(tableVersionRow.Value); var data = all.Where(x => !TableVersionKey.Equals(x.Name, StringComparison.Ordinal) && x.Value.HasValue) .Select(x => Tuple.Create(Deserialize(x.Value!), tableVersion.VersionEtag)) .ToList(); return new MembershipTableData(data, tableVersion); } private static TableVersion GetTableVersionFromRow(RedisValue tableVersionRow) { if (TryGetValueString(tableVersionRow, out var value)) { return DeserializeVersion(value); } return DefaultTableVersion; } private static bool TryGetValueString(RedisValue key, [NotNullWhen(true)] out string? value) { if (key.HasValue) { value = key.ToString(); return true; } value = null; return false; } public async Task ReadRow(SiloAddress key) { var tx = _db.CreateTransaction(); var tableVersionRowTask = tx.HashGetAsync(_clusterKey, TableVersionKey); var entryRowTask = tx.HashGetAsync(_clusterKey, key.ToString()); if (!await tx.ExecuteAsync()) { throw new RedisClusteringException($"Unexpected transaction failure while reading key {key}"); } TableVersion tableVersion = GetTableVersionFromRow(await tableVersionRowTask); var entryRow = await entryRowTask; if (TryGetValueString(entryRow, out var entryValueString)) { var entry = Deserialize(entryValueString); return new MembershipTableData(Tuple.Create(entry, tableVersion.VersionEtag), tableVersion); } else { return new MembershipTableData(tableVersion); } } public async Task UpdateIAmAlive(MembershipEntry entry) { var key = entry.SiloAddress.ToString(); var tx = _db.CreateTransaction(); var tableVersionRowTask = tx.HashGetAsync(_clusterKey, TableVersionKey); var entryRowTask = tx.HashGetAsync(_clusterKey, key); if (!await tx.ExecuteAsync()) { throw new RedisClusteringException($"Unexpected transaction failure while reading key {key}"); } var entryRow = await entryRowTask; if (!TryGetValueString(entryRow, out var entryRowValue)) { throw new RedisClusteringException($"Could not find a value for the key {key}"); } TableVersion tableVersion = GetTableVersionFromRow(await tableVersionRowTask).Next(); var existingEntry = Deserialize(entryRowValue); // Update only the IAmAliveTime property. existingEntry.IAmAliveTime = entry.IAmAliveTime; var result = await UpsertRowInternal(existingEntry, tableVersion, updateTableVersion: false, allowInsertOnly: false); if (result == UpsertResult.Conflict) { throw new RedisClusteringException($"Failed to update IAmAlive value for key {key} due to conflict"); } else if (result != UpsertResult.Success) { throw new RedisClusteringException($"Failed to update IAmAlive value for key {key} for an unknown reason"); } } public async Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { return await UpsertRowInternal(entry, tableVersion, updateTableVersion: true, allowInsertOnly: false) == UpsertResult.Success; } public async Task CleanupDefunctSiloEntries(DateTimeOffset beforeDate) { var entries = await this.ReadAll(); foreach (var (entry, _) in entries.Members) { if (entry.Status != SiloStatus.Active && new DateTime(Math.Max(entry.IAmAliveTime.Ticks, entry.StartTime.Ticks), DateTimeKind.Utc) < beforeDate) { await _db.HashDeleteAsync(_clusterKey, entry.SiloAddress.ToString()); } } } public void Dispose() { _muxer?.Dispose(); } private enum UpsertResult { Success = 1, Failure = 2, Conflict = 3, } private static string SerializeVersion(TableVersion tableVersion) => tableVersion.Version.ToString(CultureInfo.InvariantCulture); private static TableVersion DeserializeVersion(string versionString) { if (string.IsNullOrWhiteSpace(versionString)) { return DefaultTableVersion; } var version = int.Parse(versionString); return new TableVersion(version, versionString); } private static TableVersion Predeccessor(TableVersion tableVersion) => new TableVersion(tableVersion.Version - 1, (tableVersion.Version - 1).ToString(CultureInfo.InvariantCulture)); private string Serialize(MembershipEntry value) { return JsonConvert.SerializeObject(value, _jsonSerializerSettings); } private MembershipEntry Deserialize(string json) { return JsonConvert.DeserializeObject(json, _jsonSerializerSettings)!; } } } ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/Hosting/RedisGrainDirectoryExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.GrainDirectory; using Orleans.GrainDirectory.Redis; using Orleans.Runtime; using Orleans.Runtime.Hosting; namespace Orleans.Hosting { /// /// Extensions for configuring Redis as a grain directory provider. /// public static class RedisGrainDirectoryExtensions { /// /// Adds a default grain directory which persists entries in Redis. /// public static ISiloBuilder UseRedisGrainDirectoryAsDefault( this ISiloBuilder builder, Action configureOptions) { return builder.UseRedisGrainDirectoryAsDefault(ob => ob.Configure(configureOptions)); } /// /// Adds a default grain directory which persists entries in Redis. /// public static ISiloBuilder UseRedisGrainDirectoryAsDefault( this ISiloBuilder builder, Action> configureOptions) { return builder.ConfigureServices(services => services.AddRedisGrainDirectory(GrainDirectoryAttribute.DEFAULT_GRAIN_DIRECTORY, configureOptions)); } /// /// Adds a named grain directory which persists entries in Redis. /// public static ISiloBuilder AddRedisGrainDirectory( this ISiloBuilder builder, string name, Action configureOptions) { return builder.AddRedisGrainDirectory(name, ob => ob.Configure(configureOptions)); } /// /// Adds a named grain directory which persists entries in Redis. /// public static ISiloBuilder AddRedisGrainDirectory( this ISiloBuilder builder, string name, Action> configureOptions) { return builder.ConfigureServices(services => services.AddRedisGrainDirectory(name, configureOptions)); } private static IServiceCollection AddRedisGrainDirectory( this IServiceCollection services, string name, Action> configureOptions) { configureOptions.Invoke(services.AddOptions(name)); services .AddTransient(sp => new RedisGrainDirectoryOptionsValidator(sp.GetRequiredService>().Get(name), name)) .ConfigureNamedOptionForLogging(name) .AddGrainDirectory(name, (sp, key) => ActivatorUtilities.CreateInstance(sp, sp.GetOptionsByName(key))); return services; } } } ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/Hosting/RedisGrainDirectoryProviderBuilder.cs ================================================ using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using StackExchange.Redis; using System; using Microsoft.Extensions.Options; using Orleans.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; [assembly: RegisterProvider("Redis", "GrainDirectory", "Silo", typeof(RedisGrainDirectoryProviderBuilder))] [assembly: RegisterProvider("AzureRedisCache", "GrainDirectory", "Silo", typeof(RedisGrainDirectoryProviderBuilder))] namespace Orleans.Hosting; internal sealed class RedisGrainDirectoryProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddRedisGrainDirectory(name, (OptionsBuilder optionsBuilder) => { optionsBuilder.Configure((options, services) => { var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a connection multiplexer instance by name. var multiplexer = services.GetRequiredKeyedService(serviceKey); options.CreateMultiplexer = _ => Task.FromResult(multiplexer); options.ConfigurationOptions = new ConfigurationOptions(); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString); } } }); }); } } ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/Options/RedisGrainDirectoryOptions.cs ================================================ using System; using System.Threading.Tasks; using Orleans.GrainDirectory.Redis; using Orleans.Runtime; using StackExchange.Redis; namespace Orleans.Configuration { /// /// Configuration options for the /// public class RedisGrainDirectoryOptions { /// /// Gets or sets the Redis client configuration. /// [RedactRedisConfigurationOptions] public ConfigurationOptions ConfigurationOptions { get; set; } /// /// The delegate used to create a Redis connection multiplexer. /// public Func> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer; /// /// Entry expiry, null by default. A value should be set ONLY for ephemeral environments (like in tests). /// Setting a value different from null will cause duplicate activations in the cluster. /// public TimeSpan? EntryExpiry { get; set; } = null; /// /// The default multiplexer creation delegate. /// public static async Task DefaultCreateMultiplexer(RedisGrainDirectoryOptions options) => await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions); } internal class RedactRedisConfigurationOptions : RedactAttribute { public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value); } /// /// Configuration validator for . /// public class RedisGrainDirectoryOptionsValidator : IConfigurationValidator { private readonly RedisGrainDirectoryOptions _options; private readonly string _name; public RedisGrainDirectoryOptionsValidator(RedisGrainDirectoryOptions options, string name) { _options = options; _name = name; } /// public void ValidateConfiguration() { if (_options.ConfigurationOptions == null) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisGrainDirectory)} with name {_name}. {nameof(RedisGrainDirectoryOptions)}.{nameof(_options.ConfigurationOptions)} is required."); } } } } ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/Orleans.GrainDirectory.Redis.csproj ================================================ README.md Microsoft.Orleans.GrainDirectory.Redis Microsoft Orleans Grain Directory Redis Provider Microsoft Orleans Grain Directory implementation that uses Redis $(PackageTags) Redis Grain Directory $(DefaultTargetFrameworks) true ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/README.md ================================================ # Microsoft Orleans Grain Directory for Redis ## Introduction Microsoft Orleans Grain Directory for Redis provides a grain directory implementation using Redis. The grain directory is used to locate active grain instances across the cluster, and this package allows Orleans to store that information in Redis. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.GrainDirectory.Redis ``` ## Example - Configuring Redis Grain Directory ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Redis as the grain directory .UseRedisGrainDirectoryAsDefault(options => { options.ConnectionString = "localhost:6379"; options.Database = 0; }); }); // Run the host await builder.RunAsync(); ``` ## Configuration via Microsoft.Extensions.Configuration You can configure Orleans Redis grain directory using `Microsoft.Extensions.Configuration` (such as `appsettings.json`) instead of configuring it in code. When using this approach, Orleans will automatically read the configuration from the `Orleans` section. > **Note**: You can use either `"ProviderType": "Redis"` or `"ProviderType": "AzureRedisCache"` - both are supported and functionally equivalent. ### Example - appsettings.json ```json { "ConnectionStrings": { "redis": "localhost:6379" }, "Orleans": { "ClusterId": "my-cluster", "ServiceId": "MyOrleansService", "GrainDirectory": { "Default": { "ProviderType": "Redis", "ServiceKey": "redis" } } } } ``` ### .NET Aspire Integration For applications using .NET Aspire, consider using the [.NET Aspire Redis integration](https://learn.microsoft.com/en-us/dotnet/aspire/caching/stackexchange-redis-integration) which provides simplified Redis configuration, automatic service discovery, health checks, and telemetry. The Aspire integration automatically configures connection strings that Orleans can consume via the configuration system. #### Example - Program.cs with Aspire Redis Integration ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); // Add service defaults (Aspire configurations) builder.AddServiceDefaults(); // Add Redis via Aspire client integration builder.AddKeyedRedisClient("redis"); // Add Orleans builder.UseOrleans(); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); var response = await grain.SayHello("Aspire Redis Grain Directory"); Console.WriteLine($"Grain response: {response}"); await host.WaitForShutdownAsync(); ``` This example assumes your AppHost project has configured Redis like this: ```csharp // In your AppHost/Program.cs var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("redis"); var orleans = builder.AddOrleans("orleans") .WithGrainDirectory("Default", redis); builder.AddProject("orleans-app") .WithReference(orleans); builder.Build().Run(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Configuration Guide](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/) - [Implementation Details](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/index) - [Redis Documentation](https://redis.io/documentation) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Redis/Orleans.GrainDirectory.Redis/RedisGrainDirectory.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Globalization; using System.Net; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using StackExchange.Redis; namespace Orleans.GrainDirectory.Redis { public partial class RedisGrainDirectory : IGrainDirectory, ILifecycleParticipant { private readonly RedisGrainDirectoryOptions _directoryOptions; private readonly ClusterOptions _clusterOptions; private readonly ILogger _logger; private readonly RedisKey _keyPrefix; private readonly string _ttl; // Both are initialized in the Initialize method. private IConnectionMultiplexer _redis = null!; private IDatabase _database = null!; private bool _disposed; public RedisGrainDirectory( RedisGrainDirectoryOptions directoryOptions, IOptions clusterOptions, ILogger logger) { _directoryOptions = directoryOptions; _logger = logger; _clusterOptions = clusterOptions.Value; _keyPrefix = Encoding.UTF8.GetBytes($"{_clusterOptions.ClusterId}/directory/"); _ttl = directoryOptions.EntryExpiry is { } ts ? ts.TotalSeconds.ToString(CultureInfo.InvariantCulture) : "-1"; } public async Task Lookup(GrainId grainId) { try { var result = _disposed ? null : (string?)await _database.StringGetAsync(GetKey(grainId)); LogDebugLookup(grainId, string.IsNullOrWhiteSpace(result) ? "null" : result); if (string.IsNullOrWhiteSpace(result)) return default; return JsonSerializer.Deserialize(result); } catch (Exception ex) { LogErrorLookupFailed(ex, grainId); if (IsRedisException(ex)) throw new OrleansException($"Lookup failed for {grainId} : {ex}"); else throw; } } public Task Register(GrainAddress address) => Register(address, null); public async Task Register(GrainAddress address, GrainAddress? previousAddress) { const string RegisterScript = """ local cur = redis.call('GET', KEYS[1]) local success = true if cur ~= false then local typedCur = cjson.decode(cur) if typedCur.ActivationId ~= ARGV[2] then success = false end end if (success) then redis.call('SET', KEYS[1], ARGV[1]) if ARGV[3] ~= '-1' then redis.call('EXPIRE', KEYS[1], ARGV[3]) end return nil end return cur """; var value = JsonSerializer.Serialize(address); try { ObjectDisposedException.ThrowIf(_disposed, _database); var previousActivationId = previousAddress is { } ? previousAddress.ActivationId.ToString() : ""; var key = GetKey(address.GrainId); var entryString = (string?)await _database.ScriptEvaluateAsync( RegisterScript, keys: new RedisKey[] { key }, values: new RedisValue[] { value, previousActivationId, _ttl })!; if (entryString is null) { LogDebugRegistered(address.GrainId, value); return address; } LogDebugRegisterFailed(address.GrainId, value, entryString); return JsonSerializer.Deserialize(entryString); } catch (Exception ex) { LogErrorRegisterFailed(ex, address.GrainId, value); if (IsRedisException(ex)) { throw new OrleansException($"Register failed for {address.GrainId} ({value}) : {ex}"); } else { throw; } } } public async Task Unregister(GrainAddress address) { const string DeleteScript = """ local cur = redis.call('GET', KEYS[1]) if cur ~= false then local typedCur = cjson.decode(cur) if typedCur.ActivationId == ARGV[1] then return redis.call('DEL', KEYS[1]) end end return 0 """; try { ObjectDisposedException.ThrowIf(_disposed, _database); var value = JsonSerializer.Serialize(address); var result = (int)await _database.ScriptEvaluateAsync( DeleteScript, keys: new RedisKey[] { GetKey(address.GrainId) }, values: new RedisValue[] { address.ActivationId.ToString() }); LogDebugUnregister(address.GrainId, new(address), (result != 0) ? "OK" : "Conflict"); } catch (Exception ex) { LogErrorUnregisterFailed(ex, address.GrainId, new(address)); if (IsRedisException(ex)) throw new OrleansException($"Unregister failed for {address.GrainId} ({JsonSerializer.Serialize(address)}) : {ex}"); else throw; } } public Task UnregisterSilos(List siloAddresses) { return Task.CompletedTask; } public void Participate(ISiloLifecycle lifecycle) { lifecycle.Subscribe(nameof(RedisGrainDirectory), ServiceLifecycleStage.RuntimeInitialize, Initialize, Uninitialize); } public async Task Initialize(CancellationToken ct = default) { _redis = await _directoryOptions.CreateMultiplexer(_directoryOptions); // Configure logging _redis.ConnectionRestored += LogConnectionRestored; _redis.ConnectionFailed += LogConnectionFailed; _redis.ErrorMessage += LogErrorMessage; _redis.InternalError += LogInternalError; _database = _redis.GetDatabase(); } private async Task Uninitialize(CancellationToken arg) { if (_redis != null && _redis.IsConnected) { _disposed = true; await _redis.CloseAsync(); _redis.Dispose(); _redis = null!; _database = null!; } } private RedisKey GetKey(GrainId grainId) => _keyPrefix.Append(grainId.ToString()); #region Logging private void LogConnectionRestored(object? sender, ConnectionFailedEventArgs e) => LogInfoConnectionRestored(e.Exception, e.EndPoint, e.FailureType); private void LogConnectionFailed(object? sender, ConnectionFailedEventArgs e) => LogErrorConnectionFailed(e.Exception, e.EndPoint, e.FailureType); private void LogErrorMessage(object? sender, RedisErrorEventArgs e) => LogErrorRedisMessage(e.Message); private void LogInternalError(object? sender, InternalErrorEventArgs e) => LogErrorInternalError(e.Exception); [LoggerMessage( Level = LogLevel.Debug, Message = "Lookup {GrainId}: {Result}" )] private partial void LogDebugLookup(GrainId grainId, string result); [LoggerMessage( Level = LogLevel.Error, Message = "Lookup failed for {GrainId}" )] private partial void LogErrorLookupFailed(Exception exception, GrainId grainId); [LoggerMessage( Level = LogLevel.Debug, Message = "Registered {GrainId} ({Address})" )] private partial void LogDebugRegistered(GrainId grainId, string address); [LoggerMessage( Level = LogLevel.Debug, Message = "Failed to register {GrainId} ({Address}) in directory: Conflicted with existing value, {Result}" )] private partial void LogDebugRegisterFailed(GrainId grainId, string address, string result); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to register {GrainId} ({Address}) in directory" )] private partial void LogErrorRegisterFailed(Exception exception, GrainId grainId, string address); private readonly struct GrainAddressLogRecord(GrainAddress address) { public override string ToString() => JsonSerializer.Serialize(address); } [LoggerMessage( Level = LogLevel.Debug, Message = "Unregister {GrainId} ({Address}): {Result}" )] private partial void LogDebugUnregister(GrainId grainId, GrainAddressLogRecord address, string result); [LoggerMessage( Level = LogLevel.Error, Message = "Unregister failed for {GrainId} ({Address})" )] private partial void LogErrorUnregisterFailed(Exception exception, GrainId grainId, GrainAddressLogRecord address); [LoggerMessage( Level = LogLevel.Information, Message = "Connection to {EndPoint} restored: {FailureType}" )] private partial void LogInfoConnectionRestored(Exception? exception, EndPoint? endPoint, ConnectionFailureType failureType); [LoggerMessage( Level = LogLevel.Error, Message = "Connection to {EndPoint} failed: {FailureType}" )] private partial void LogErrorConnectionFailed(Exception? exception, EndPoint? endPoint, ConnectionFailureType failureType); [LoggerMessage( Level = LogLevel.Error, Message = "{Message}" )] private partial void LogErrorRedisMessage(string message); [LoggerMessage( Level = LogLevel.Error, Message = "Internal error" )] private partial void LogErrorInternalError(Exception? exception); #endregion // These exceptions are not serializable by the client private static bool IsRedisException(Exception ex) => ex is RedisException || ex is RedisTimeoutException || ex is RedisCommandException; } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Hosting/RedisGrainStorageProviderBuilder.cs ================================================ using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using StackExchange.Redis; using Microsoft.Extensions.Options; using Orleans.Persistence; using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Orleans.Storage; [assembly: RegisterProvider("Redis", "GrainStorage", "Silo", typeof(RedisGrainStorageProviderBuilder))] [assembly: RegisterProvider("AzureRedisCache", "GrainStorage", "Silo", typeof(RedisGrainStorageProviderBuilder))] namespace Orleans.Hosting; internal sealed class RedisGrainStorageProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.AddRedisGrainStorage(name, (OptionsBuilder optionsBuilder) => { optionsBuilder.Configure((options, services) => { var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a connection multiplexer instance by name. var multiplexer = services.GetRequiredKeyedService(serviceKey); options.CreateMultiplexer = _ => Task.FromResult(multiplexer); options.ConfigurationOptions = new ConfigurationOptions(); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString); } } var serializerKey = configurationSection["SerializerKey"]; if (!string.IsNullOrEmpty(serializerKey)) { options.GrainStorageSerializer = services.GetRequiredKeyedService(serializerKey); } }); }); } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Hosting/RedisGrainStorageServiceCollectionExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Persistence; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Hosting; using Orleans.Storage; namespace Orleans.Hosting { /// /// extensions. /// public static class RedisGrainStorageServiceCollectionExtensions { /// /// Configures Redis as the default grain storage provider. /// public static IServiceCollection AddRedisGrainStorageAsDefault(this IServiceCollection services, Action configureOptions) { return services.AddRedisGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, ob => ob.Configure(configureOptions)); } /// /// Configures Redis as a grain storage provider. /// public static IServiceCollection AddRedisGrainStorage(this IServiceCollection services, string name, Action configureOptions) { return services.AddRedisGrainStorage(name, ob => ob.Configure(configureOptions)); } /// /// Configures Redis as the default grain storage provider. /// public static IServiceCollection AddRedisGrainStorageAsDefault(this IServiceCollection services, Action> configureOptions = null) { return services.AddRedisGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configures Redis as a grain storage provider. /// public static IServiceCollection AddRedisGrainStorage(this IServiceCollection services, string name, Action> configureOptions = null) { configureOptions?.Invoke(services.AddOptions(name)); services.AddTransient(sp => new RedisStorageOptionsValidator(sp.GetRequiredService>().Get(name), name)); services.AddTransient, DefaultStorageProviderSerializerOptionsConfigurator>(); services.ConfigureNamedOptionForLogging(name); return services.AddGrainStorage(name, RedisGrainStorageFactory.Create); } } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Hosting/RedisSiloBuilderExtensions.cs ================================================ using System; using Microsoft.Extensions.Options; using Orleans.Persistence; using Orleans.Providers; namespace Orleans.Hosting { /// /// extensions. /// public static class RedisSiloBuilderExtensions { /// /// Configures Redis as the default grain storage provider. /// public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder, Action configureOptions) { return builder.AddRedisGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions); } /// /// Configures Redis as a grain storage provider. /// public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name, Action configureOptions) { return builder.ConfigureServices(services => services.AddRedisGrainStorage(name, configureOptions)); } /// /// Configures Redis as the default grain storage provider. /// public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder) => builder.AddRedisGrainStorageAsDefault(configureOptionsBuilder: null); /// /// Configures Redis as the default grain storage provider. /// public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder, Action> configureOptionsBuilder) { return builder.AddRedisGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptionsBuilder); } /// /// Configures Redis as a grain storage provider. /// public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name) => builder.AddRedisGrainStorage(name, configureOptionsBuilder: null); /// /// Configures Redis as a grain storage provider. /// public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name, Action> configureOptionsBuilder) { return builder.ConfigureServices(services => services.AddRedisGrainStorage(name, configureOptionsBuilder)); } } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Orleans.Persistence.Redis.csproj ================================================ README.md Microsoft.Orleans.Persistence.Redis Microsoft Orleans Persistence Redis Provider Microsoft Orleans Persistence implementation that uses Redis $(PackageTags) Redis Persistence $(DefaultTargetFrameworks) true ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Providers/RedisStorageOptions.cs ================================================ #nullable enable using System; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Runtime; using Orleans.Storage; using StackExchange.Redis; namespace Orleans.Persistence { /// /// Redis grain storage options. /// public class RedisStorageOptions : IStorageProviderSerializerOptions { /// /// Whether or not to delete state during a clear operation. /// public bool DeleteStateOnClear { get; set; } /// /// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use. /// public int InitStage { get; set; } = ServiceLifecycleStage.ApplicationServices; /// public IGrainStorageSerializer? GrainStorageSerializer { get; set; } /// /// Gets or sets the Redis client configuration. /// [RedactRedisConfigurationOptions] public ConfigurationOptions? ConfigurationOptions { get; set; } /// /// The delegate used to create a Redis connection multiplexer. /// public Func> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer; /// /// Entry expiry, null by default. A value should be set only for ephemeral environments, such as testing environments. /// Setting a value different from will cause duplicate activations in the cluster. /// public TimeSpan? EntryExpiry { get; set; } = null; /// /// Gets the Redis key for the provided grain type and grain identifier. If not set, the default implementation will be used, which is equivalent to {ServiceId}/state/{grainId}/{grainType}. /// public Func? GetStorageKey { get; set; } /// /// The default multiplexer creation delegate. /// public static async Task DefaultCreateMultiplexer(RedisStorageOptions options) => await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions!); } /// /// Extension methods for configuring . /// public static class RedisStorageOptionsExtensions { /// /// Configures the provided options to use a Redis key format that ignores the grain type, equivalent to {ServiceId}/state/{grainId}. /// /// /// This method is provided as a compatibility utility for users who are migrating from prerelease versions of the Redis storage provider. /// /// The options builder. public static void UseGetRedisKeyIgnoringGrainType(this OptionsBuilder optionsBuilder) { optionsBuilder.Configure((RedisStorageOptions options, IOptions clusterOptions) => { RedisKey keyPrefix = Encoding.UTF8.GetBytes($"{clusterOptions.Value.ServiceId}/state/"); options.GetStorageKey = (_, grainId) => keyPrefix.Append(grainId.ToString()); }); } } internal class RedactRedisConfigurationOptions : RedactAttribute { public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value); } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Providers/RedisStorageOptionsValidator.cs ================================================ using Orleans.Runtime; namespace Orleans.Persistence { internal class RedisStorageOptionsValidator : IConfigurationValidator { private readonly RedisStorageOptions _options; private readonly string _name; public RedisStorageOptionsValidator(RedisStorageOptions options, string name) { _options = options; _name = name; } public void ValidateConfiguration() { if (_options.ConfigurationOptions == null) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisGrainStorage)} with name {_name}. {nameof(RedisStorageOptions)}.{nameof(_options.ConfigurationOptions)} is required."); } } } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/README.md ================================================ # Microsoft Orleans Persistence for Redis ## Introduction Microsoft Orleans Persistence for Redis provides grain persistence for Microsoft Orleans using Redis. This allows your grains to persist their state in Redis and reload it when they are reactivated, leveraging Redis's in-memory data store for fast access. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Persistence.Redis ``` ## Example - Configuring Redis Persistence ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Redis as grain storage .AddRedisGrainStorage( name: "redisStore", configureOptions: options => { options.ConnectionString = "localhost:6379"; options.Database = 0; options.UseJson = true; // Serializes grain state as JSON options.KeyPrefix = "grain-"; // Optional prefix for Redis keys }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Grain Storage in a Grain ```csharp // Define grain state class public class MyGrainState { public string Data { get; set; } public int Version { get; set; } } // Grain implementation that uses Redis storage public class MyGrain : Grain, IMyGrain, IGrainWithStringKey { private readonly IPersistentState _state; public MyGrain([PersistentState("state", "redisStore")] IPersistentState state) { _state = state; } public async Task SetData(string data) { _state.State.Data = data; _state.State.Version++; await _state.WriteStateAsync(); } public Task GetData() { return Task.FromResult(_state.State.Data); } } ``` ## Configuration via Microsoft.Extensions.Configuration You can configure Orleans Redis persistence using `Microsoft.Extensions.Configuration` (such as `appsettings.json`) instead of configuring it in code. When using this approach, Orleans will automatically read the configuration from the `Orleans` section. > **Note**: You can use either `"ProviderType": "Redis"` or `"ProviderType": "AzureRedisCache"` - both are supported and functionally equivalent. ### Example - appsettings.json ```json { "ConnectionStrings": { "redis": "localhost:6379" }, "Orleans": { "ClusterId": "my-cluster", "ServiceId": "MyOrleansService", "GrainStorage": { "redisStore": { "ProviderType": "Redis", "ServiceKey": "redis" } } } } ``` ### .NET Aspire Integration For applications using .NET Aspire, consider using the [.NET Aspire Redis integration](https://learn.microsoft.com/en-us/dotnet/aspire/caching/stackexchange-redis-integration) which provides simplified Redis configuration, automatic service discovery, health checks, and telemetry. The Aspire integration automatically configures connection strings that Orleans can consume via the configuration system. #### Example - Program.cs with Aspire Redis Integration ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); // Add service defaults (Aspire configurations) builder.AddServiceDefaults(); // Add Redis via Aspire client integration builder.AddKeyedRedisClient("redis"); // Add Orleans builder.UseOrleans(); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); await grain.SetData("Hello from Aspire Redis!"); var data = await grain.GetData(); Console.WriteLine($"Grain data: {data}"); await host.WaitForShutdownAsync(); ``` This example assumes your AppHost project has configured Redis like this: ```csharp // In your AppHost/Program.cs var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("redis"); var orleans = builder.AddOrleans("orleans") .WithGrainStorage("redisStore", redis); builder.AddProject("orleans-app") .WithReference(orleans); builder.Build().Run(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Grain Persistence](https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence) - [Redis Documentation](https://redis.io/documentation) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Storage/RedisGrainStorage.cs ================================================ using System; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; using Orleans.Persistence.Redis; using Orleans.Runtime; using Orleans.Serialization.Serializers; using Orleans.Storage; using StackExchange.Redis; using static System.FormattableString; namespace Orleans.Persistence { /// /// Redis-based grain storage provider /// public partial class RedisGrainStorage : IGrainStorage, ILifecycleParticipant { private readonly string _serviceId; private readonly RedisValue _ttl; private readonly RedisKey _keyPrefix; private readonly string _name; private readonly ILogger _logger; private readonly RedisStorageOptions _options; private readonly IActivatorProvider _activatorProvider; private readonly IGrainStorageSerializer _grainStorageSerializer; private readonly Func _getKeyFunc; private IConnectionMultiplexer _connection; private IDatabase _db; /// /// Creates a new instance of the type. /// public RedisGrainStorage( string name, RedisStorageOptions options, IGrainStorageSerializer grainStorageSerializer, IOptions clusterOptions, IActivatorProvider activatorProvider, ILogger logger) { _name = name; _logger = logger; _options = options; _activatorProvider = activatorProvider; _grainStorageSerializer = options.GrainStorageSerializer ?? grainStorageSerializer; _serviceId = clusterOptions.Value.ServiceId; _ttl = options.EntryExpiry is { } ts ? ts.TotalSeconds.ToString(CultureInfo.InvariantCulture) : "-1"; _keyPrefix = Encoding.UTF8.GetBytes($"{_serviceId}/state/"); _getKeyFunc = _options.GetStorageKey ?? DefaultGetStorageKey; } /// public void Participate(ISiloLifecycle lifecycle) { var name = OptionFormattingUtilities.Name(_name); lifecycle.Subscribe(name, _options.InitStage, Init, Close); } private async Task Init(CancellationToken cancellationToken) { var startTime = Stopwatch.GetTimestamp(); try { LogDebugInitializing(_name, _serviceId, _options.DeleteStateOnClear); _connection = await _options.CreateMultiplexer(_options).ConfigureAwait(false); _db = _connection.GetDatabase(); var elapsed = Stopwatch.GetElapsedTime(startTime); LogDebugInitialized(_name, _serviceId, elapsed.TotalMilliseconds); } catch (Exception ex) { var elapsed = Stopwatch.GetElapsedTime(startTime); LogErrorInitFailed(ex, _name, _serviceId, elapsed.TotalMilliseconds); throw new RedisStorageException(Invariant($"{ex.GetType()}: {ex.Message}")); } } /// public async Task ReadStateAsync(string grainType, GrainId grainId, IGrainState grainState) { var key = _getKeyFunc(grainType, grainId); try { var hashEntries = await _db.HashGetAllAsync(key).ConfigureAwait(false); if (hashEntries.Length == 2) { string eTag = hashEntries.Single(static e => e.Name == "etag").Value; grainState.ETag = eTag; ReadOnlyMemory data = hashEntries.Single(static e => e.Name == "data").Value; if (data.Length > 0) { grainState.State = _grainStorageSerializer.Deserialize(data); grainState.RecordExists = true; } else { grainState.State = CreateInstance(); grainState.RecordExists = false; } } else { grainState.ETag = null; grainState.State = CreateInstance(); grainState.RecordExists = false; } } catch (Exception exception) { LogErrorReadStateFailed(exception, grainType, grainId, key); throw new RedisStorageException(Invariant($"Failed to read grain state for {grainType} with ID {grainId} and storage key {key}. {exception.GetType()}: {exception.Message}")); } } /// public async Task WriteStateAsync(string grainType, GrainId grainId, IGrainState grainState) { const string WriteScript = """ local etag = redis.call('HGET', KEYS[1], 'etag') if ((not etag or etag == '') and (not ARGV[1] or ARGV[1] == '')) or etag == ARGV[1] then redis.call('HMSET', KEYS[1], 'etag', ARGV[2], 'data', ARGV[3]) if ARGV[4] ~= '-1' then redis.call('EXPIRE', KEYS[1], ARGV[4]) end return 0 else return -1 end """; var key = _getKeyFunc(grainType, grainId); RedisValue etag = grainState.ETag ?? ""; RedisValue newEtag = Guid.NewGuid().ToString("N"); try { RedisValue payload = _grainStorageSerializer.Serialize(grainState.State).ToMemory(); var keys = new RedisKey[] { key }; var args = new RedisValue[] { etag, newEtag, payload, _ttl }; var response = await _db.ScriptEvaluateAsync(WriteScript, keys, args).ConfigureAwait(false); if (response is not null && (int)response == -1) { throw new InconsistentStateException($"Version conflict ({nameof(WriteStateAsync)}): ServiceId={_serviceId} ProviderName={_name} GrainType={grainType} GrainId={grainId} ETag={grainState.ETag}."); } grainState.ETag = newEtag; grainState.RecordExists = true; } catch (Exception exception) when (exception is not InconsistentStateException) { LogErrorWriteStateFailed(exception, grainType, grainId, key); throw new RedisStorageException( Invariant($"Failed to write grain state for {grainType} grain with ID {grainId} and storage key {key}. {exception.GetType()}: {exception.Message}")); } } /// /// Default implementation of which returns a key equivalent to {ServiceId}/state/{grainId}/{grainType} /// private RedisKey DefaultGetStorageKey(string grainType, GrainId grainId) { var grainIdTypeBytes = IdSpan.UnsafeGetArray(grainId.Type.Value); var grainIdKeyBytes = IdSpan.UnsafeGetArray(grainId.Key); var grainTypeLength = Encoding.UTF8.GetByteCount(grainType); var suffix = new byte[grainIdTypeBytes.Length + 1 + grainIdKeyBytes.Length + 1 + grainTypeLength]; var index = 0; grainIdTypeBytes.CopyTo(suffix, 0); index += grainIdTypeBytes.Length; suffix[index++] = (byte)'/'; grainIdKeyBytes.CopyTo(suffix, index); index += grainIdKeyBytes.Length; suffix[index++] = (byte)'/'; var bytesWritten = Encoding.UTF8.GetBytes(grainType, suffix.AsSpan(index)); Debug.Assert(bytesWritten == grainTypeLength); Debug.Assert(index + bytesWritten == suffix.Length); return _keyPrefix.Append(suffix); } /// public async Task ClearStateAsync(string grainType, GrainId grainId, IGrainState grainState) { try { RedisValue etag = grainState.ETag ?? ""; RedisResult response; string newETag; var key = _getKeyFunc(grainType, grainId); if (_options.DeleteStateOnClear) { const string DeleteScript = """ local etag = redis.call('HGET', KEYS[1], 'etag') if ((not etag or etag == '') and (not ARGV[1] or ARGV[1] == '')) or etag == ARGV[1] then redis.call('DEL', KEYS[1]) return 0 else return -1 end """; response = await _db.ScriptEvaluateAsync(DeleteScript, keys: new[] { key }, values: new[] { etag }).ConfigureAwait(false); newETag = null; } else { const string ClearScript = """ local etag = redis.call('HGET', KEYS[1], 'etag') if ((not etag or etag == '') and (not ARGV[1] or ARGV[1] == '')) or etag == ARGV[1] then redis.call('HMSET', KEYS[1], 'etag', ARGV[2], 'data', '') return 0 else return -1 end """; newETag = Guid.NewGuid().ToString("N"); response = await _db.ScriptEvaluateAsync(ClearScript, keys: new[] { key }, values: new RedisValue[] { etag, newETag }).ConfigureAwait(false); } if (response is not null && (int)response == -1) { throw new InconsistentStateException($"Version conflict ({nameof(ClearStateAsync)}): ServiceId={_serviceId} ProviderName={_name} GrainType={grainType} GrainId={grainId} ETag={grainState.ETag}."); } grainState.ETag = newETag; grainState.State = CreateInstance(); grainState.RecordExists = false; } catch (Exception exception) when (exception is not InconsistentStateException) { throw new RedisStorageException(Invariant($"Failed to clear grain state for grain {grainType} with ID {grainId}. {exception.GetType()}: {exception.Message}")); } } private async Task Close(CancellationToken cancellationToken) { if (_connection is null) return; await _connection.CloseAsync().ConfigureAwait(false); _connection.Dispose(); } private T CreateInstance() => _activatorProvider.GetActivator().Create(); [LoggerMessage( Level = LogLevel.Debug, Message = "RedisGrainStorage {Name} is initializing: ServiceId={ServiceId} DeleteOnClear={DeleteOnClear}" )] private partial void LogDebugInitializing(string name, string serviceId, bool deleteOnClear); [LoggerMessage( Level = LogLevel.Debug, Message = "Init: Name={Name} ServiceId={ServiceId}, initialized in {ElapsedMilliseconds} ms" )] private partial void LogDebugInitialized(string name, string serviceId, double elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, Message = "Init: Name={Name} ServiceId={ServiceId}, errored in {ElapsedMilliseconds} ms." )] private partial void LogErrorInitFailed(Exception exception, string name, string serviceId, double elapsedMilliseconds); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to read grain state for {GrainType} grain with ID {GrainId} and storage key {Key}." )] private partial void LogErrorReadStateFailed(Exception exception, string grainType, GrainId grainId, RedisKey key); [LoggerMessage( Level = LogLevel.Error, Message = "Failed to write grain state for {GrainType} grain with ID {GrainId} and storage key {Key}." )] private partial void LogErrorWriteStateFailed(Exception exception, string grainType, GrainId grainId, RedisKey key); } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Storage/RedisGrainStorageFactory.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Orleans.Storage; using System; namespace Orleans.Persistence { /// /// Factory used to create instances of Redis grain storage. /// public static class RedisGrainStorageFactory { /// /// Creates a grain storage instance. /// public static RedisGrainStorage Create(IServiceProvider services, string name) { var optionsMonitor = services.GetRequiredService>(); var redisGrainStorage = ActivatorUtilities.CreateInstance(services, name, optionsMonitor.Get(name)); return redisGrainStorage; } } } ================================================ FILE: src/Redis/Orleans.Persistence.Redis/Storage/RedisStorageException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Persistence.Redis { /// /// Exception for throwing from Redis grain storage. /// [GenerateSerializer] public class RedisStorageException : Exception { /// /// Initializes a new instance of . /// public RedisStorageException() { } /// /// Initializes a new instance of . /// /// The error message that explains the reason for the exception. public RedisStorageException(string message) : base(message) { } /// /// Initializes a new instance of . /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. public RedisStorageException(string message, Exception inner) : base(message, inner) { } /// [Obsolete] protected RedisStorageException( SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Hosting/RedisRemindersProviderBuilder.cs ================================================ using Orleans.Providers; using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using StackExchange.Redis; using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using System.Threading.Tasks; [assembly: RegisterProvider("Redis", "Reminders", "Silo", typeof(RedisRemindersProviderBuilder))] [assembly: RegisterProvider("AzureRedisCache", "Reminders", "Silo", typeof(RedisRemindersProviderBuilder))] namespace Orleans.Hosting; internal sealed class RedisRemindersProviderBuilder : IProviderBuilder { public void Configure(ISiloBuilder builder, string name, IConfigurationSection configurationSection) { builder.UseRedisReminderService(_ => { }); builder.Services.AddOptions() .Configure((options, services) => { var serviceKey = configurationSection["ServiceKey"]; if (!string.IsNullOrEmpty(serviceKey)) { // Get a connection multiplexer instance by name. var multiplexer = services.GetRequiredKeyedService(serviceKey); options.CreateMultiplexer = _ => Task.FromResult(multiplexer); options.ConfigurationOptions = new ConfigurationOptions(); } else { // Construct a connection multiplexer from a connection string. var connectionName = configurationSection["ConnectionName"]; var connectionString = configurationSection["ConnectionString"]; if (!string.IsNullOrEmpty(connectionName) && string.IsNullOrEmpty(connectionString)) { var rootConfiguration = services.GetRequiredService(); connectionString = rootConfiguration.GetConnectionString(connectionName); } if (!string.IsNullOrEmpty(connectionString)) { options.ConfigurationOptions = ConfigurationOptions.Parse(connectionString); } } }); } } ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Hosting/SiloBuilderReminderExtensions.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Hosting; using Orleans.Reminders.Redis; namespace Orleans.Hosting { /// /// Silo host builder extensions. /// public static class SiloBuilderReminderExtensions { /// /// Adds reminder storage backed by Redis. /// /// /// The builder. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static ISiloBuilder UseRedisReminderService(this ISiloBuilder builder, Action configure) { builder.ConfigureServices(services => services.UseRedisReminderService(configure)); return builder; } /// /// Adds reminder storage backed by Redis. /// /// /// The service collection. /// /// /// The delegate used to configure the reminder store. /// /// /// The provided , for chaining. /// public static IServiceCollection UseRedisReminderService(this IServiceCollection services, Action configure) { services.AddReminders(); services.AddSingleton(); services.Configure(configure); services.AddSingleton(); services.ConfigureFormatter(); return services; } } } ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Orleans.Reminders.Redis.csproj ================================================ README.md Microsoft.Orleans.Reminders.Redis Microsoft Orleans Reminders Redis Provider Microsoft Orleans Reminders implementation that uses Redis $(PackageTags) Redis Reminders $(DefaultTargetFrameworks) true ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Providers/RedisReminderTableOptions.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Orleans.Reminders.Redis; using Orleans.Runtime; using StackExchange.Redis; namespace Orleans.Configuration { /// /// Redis reminder options. /// public class RedisReminderTableOptions { /// /// Gets or sets the Redis client options. /// [RedactRedisConfigurationOptions] public ConfigurationOptions ConfigurationOptions { get; set; } /// /// The delegate used to create a Redis connection multiplexer. /// public Func> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer; /// /// Entry expiry, null by default. A value should be set ONLY for ephemeral environments (like in tests). /// Setting a value different from null will cause reminder entries to be deleted after some period of time. /// public TimeSpan? EntryExpiry { get; set; } = null; /// /// The default multiplexer creation delegate. /// public static async Task DefaultCreateMultiplexer(RedisReminderTableOptions options) => await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions); } internal class RedactRedisConfigurationOptions : RedactAttribute { public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value); } /// /// Configuration validator for . /// public class RedisReminderTableOptionsValidator : IConfigurationValidator { private readonly RedisReminderTableOptions _options; public RedisReminderTableOptionsValidator(IOptions options) { _options = options.Value; } public void ValidateConfiguration() { if (_options.ConfigurationOptions == null) { throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisReminderTable)}. {nameof(RedisReminderTableOptions)}.{nameof(_options.ConfigurationOptions)} is required."); } } } } ================================================ FILE: src/Redis/Orleans.Reminders.Redis/README.md ================================================ # Microsoft Orleans Reminders for Redis ## Introduction Microsoft Orleans Reminders for Redis provides persistence for Orleans reminders using Redis. This allows your Orleans applications to schedule persistent reminders that will be triggered even after silo restarts or grain deactivation. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Reminders.Redis ``` ## Example - Configuring Redis Reminders ```csharp using Microsoft.Extensions.Hosting; using Orleans.Configuration; using Orleans.Hosting; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Redis as reminder storage .UseRedisReminderService(options => { options.ConnectionString = "localhost:6379"; options.Database = 0; options.KeyPrefix = "reminder-"; // Optional prefix for Redis keys }); }); // Run the host await builder.RunAsync(); ``` ## Example - Using Reminders in a Grain ```csharp public class ReminderGrain : Grain, IReminderGrain, IRemindable { private string _reminderName = "MyReminder"; public async Task StartReminder(string reminderName) { _reminderName = reminderName; // Register a persistent reminder await RegisterOrUpdateReminder( reminderName, TimeSpan.FromMinutes(2), // Time to delay before the first tick (must be > 1 minute) TimeSpan.FromMinutes(5)); // Period of the reminder (must be > 1 minute) } public async Task StopReminder() { // Find and unregister the reminder var reminder = await GetReminder(_reminderName); if (reminder != null) { await UnregisterReminder(reminder); } } public Task ReceiveReminder(string reminderName, TickStatus status) { // This method is called when the reminder ticks Console.WriteLine($"Reminder {reminderName} triggered at {DateTime.UtcNow}. Status: {status}"); return Task.CompletedTask; } } ``` ## Configuration via Microsoft.Extensions.Configuration You can configure Orleans Redis reminders using `Microsoft.Extensions.Configuration` (such as `appsettings.json`) instead of configuring it in code. When using this approach, Orleans will automatically read the configuration from the `Orleans` section. > **Note**: You can use either `"ProviderType": "Redis"` or `"ProviderType": "AzureRedisCache"` - both are supported and functionally equivalent. ### Example - appsettings.json ```json { "ConnectionStrings": { "redis": "localhost:6379" }, "Orleans": { "ClusterId": "my-cluster", "ServiceId": "MyOrleansService", "Reminders": { "ProviderType": "Redis", "ServiceKey": "redis", "Database": 0, "KeyPrefix": "reminder-" } } } ``` ### .NET Aspire Integration For applications using .NET Aspire, consider using the [.NET Aspire Redis integration](https://learn.microsoft.com/en-us/dotnet/aspire/caching/stackexchange-redis-integration) which provides simplified Redis configuration, automatic service discovery, health checks, and telemetry. The Aspire integration automatically configures connection strings that Orleans can consume via the configuration system. #### Example - Program.cs with Aspire Redis Integration ```csharp using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Microsoft.Extensions.DependencyInjection; var builder = Host.CreateApplicationBuilder(args); // Add service defaults (Aspire configurations) builder.AddServiceDefaults(); // Add Redis via Aspire client integration builder.AddKeyedRedisClient("redis"); // Add Orleans builder.UseOrleans(); var host = builder.Build(); await host.StartAsync(); // Get a reference to a grain and call it var client = host.Services.GetRequiredService(); var grain = client.GetGrain("user123"); await grain.StartReminder("AspireReminder"); Console.WriteLine("Reminder started with Aspire Redis!"); await host.WaitForShutdownAsync(); ``` This example assumes your AppHost project has configured Redis like this: ```csharp // In your AppHost/Program.cs var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("redis"); var orleans = builder.AddOrleans("orleans") .WithReminders(redis); builder.AddProject("orleans-app") .WithReference(orleans); builder.Build().Run(); ``` ## Documentation For more comprehensive documentation, please refer to: - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Reminders and Timers](https://learn.microsoft.com/en-us/dotnet/orleans/grains/timers-and-reminders) - [Reminder Services](https://learn.microsoft.com/en-us/dotnet/orleans/implementation/reminder-services) - [Redis Documentation](https://redis.io/documentation) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Storage/RedisReminderTable.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Orleans.Configuration; using Orleans.Runtime; using StackExchange.Redis; using static System.FormattableString; namespace Orleans.Reminders.Redis { internal partial class RedisReminderTable : IReminderTable { private readonly RedisKey _hashSetKey; private readonly RedisReminderTableOptions _redisOptions; private readonly ClusterOptions _clusterOptions; private readonly ILogger _logger; private IConnectionMultiplexer _muxer; private IDatabase _db; private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { DateFormatHandling = DateFormatHandling.IsoDateFormat, DefaultValueHandling = DefaultValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, }; public RedisReminderTable( ILogger logger, IOptions clusterOptions, IOptions redisOptions) { _redisOptions = redisOptions.Value; _clusterOptions = clusterOptions.Value; _logger = logger; _hashSetKey = Encoding.UTF8.GetBytes($"{_clusterOptions.ServiceId}/reminders"); } public async Task Init() { try { _muxer = await _redisOptions.CreateMultiplexer(_redisOptions); _db = _muxer.GetDatabase(); if (_redisOptions.EntryExpiry is { } expiry) { await _db.KeyExpireAsync(_hashSetKey, expiry); } } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task ReadRow(GrainId grainId, string reminderName) { try { var (from, to) = GetFilter(grainId, reminderName); RedisValue[] values = await _db.SortedSetRangeByValueAsync(_hashSetKey, from, to); if (values.Length == 0) { return null; } else { return ConvertToEntry(values.SingleOrDefault()); } } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task ReadRows(GrainId grainId) { try { var (from, to) = GetFilter(grainId); RedisValue[] values = await _db.SortedSetRangeByValueAsync(_hashSetKey, from, to); IEnumerable records = values.Select(static v => ConvertToEntry(v)); return new ReminderTableData(records); } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task ReadRows(uint begin, uint end) { try { var (_, from) = GetFilter(begin); var (_, to) = GetFilter(end); IEnumerable values; if (begin < end) { // -----begin******end----- values = await _db.SortedSetRangeByValueAsync(_hashSetKey, from, to); } else { // *****end------begin***** RedisValue[] values1 = await _db.SortedSetRangeByValueAsync(_hashSetKey, from, "\"FFFFFFFF\",#"); RedisValue[] values2 = await _db.SortedSetRangeByValueAsync(_hashSetKey, "\"00000000\",\"", to); values = values1.Concat(values2); } IEnumerable records = values.Select(static v => ConvertToEntry(v)); return new ReminderTableData(records); } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task RemoveRow(GrainId grainId, string reminderName, string eTag) { try { var (from, to) = GetFilter(grainId, reminderName, eTag); long removed = await _db.SortedSetRemoveRangeByValueAsync(_hashSetKey, from, to); return removed > 0; } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task TestOnlyClearTable() { try { await _db.KeyDeleteAsync(_hashSetKey); } catch (Exception exception) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } public async Task UpsertRow(ReminderEntry entry) { const string UpsertScript = """ local key = KEYS[1] local from = '[' .. ARGV[1] -- start of the conditional (with etag) key range local to = '[' .. ARGV[2] -- end of the conditional (with etag) key range local value = ARGV[3] -- Remove all entries for this reminder local remRes = redis.call('ZREMRANGEBYLEX', key, from, to); -- Add the new reminder entry local addRes = redis.call('ZADD', key, 0, value); return { key, from, to, value, remRes, addRes } """; try { LogDebugUpsertRow(new(entry), entry.ETag); var (newETag, value) = ConvertFromEntry(entry); var (from, to) = GetFilter(entry.GrainId, entry.ReminderName); var res = await _db.ScriptEvaluateAsync(UpsertScript, keys: new[] { _hashSetKey }, values: new[] { from, to, value }); return newETag; } catch (Exception exception) when (exception is not ReminderException) { throw new RedisRemindersException(Invariant($"{exception.GetType()}: {exception.Message}")); } } private static ReminderEntry ConvertToEntry(string reminderValue) { string[] segments = JsonConvert.DeserializeObject($"[{reminderValue}]"); return new ReminderEntry { GrainId = GrainId.Parse(segments[1]), ReminderName = segments[2], ETag = segments[3], StartAt = DateTime.Parse(segments[4], null, DateTimeStyles.RoundtripKind), Period = TimeSpan.Parse(segments[5]), }; } private (RedisValue from, RedisValue to) GetFilter(uint grainHash) { return GetFilter(grainHash.ToString("X8")); } private (RedisValue from, RedisValue to) GetFilter(GrainId grainId) { return GetFilter(grainId.GetUniformHashCode().ToString("X8"), grainId.ToString()); } private (RedisValue from, RedisValue to) GetFilter(GrainId grainId, string reminderName) { return GetFilter(grainId.GetUniformHashCode().ToString("X8"), grainId.ToString(), reminderName); } private (RedisValue from, RedisValue to) GetFilter(GrainId grainId, string reminderName, string eTag) { return GetFilter(grainId.GetUniformHashCode().ToString("X8"), grainId.ToString(), reminderName, eTag); } private (RedisValue from, RedisValue to) GetFilter(params string[] segments) { string prefix = JsonConvert.SerializeObject(segments, _jsonSettings); return ($"{prefix[1..^1]},\"", $"{prefix[1..^1]},#"); } private (RedisValue eTag, RedisValue value) ConvertFromEntry(ReminderEntry entry) { string grainHash = entry.GrainId.GetUniformHashCode().ToString("X8"); string eTag = Guid.NewGuid().ToString(); string[] segments = new string[] { grainHash, entry.GrainId.ToString(), entry.ReminderName, eTag, entry.StartAt.ToString("O"), entry.Period.ToString() }; return (eTag, JsonConvert.SerializeObject(segments, _jsonSettings)[1..^1]); } private readonly struct ReminderEntryLogValue(ReminderEntry entry) { public override string ToString() => entry.ToString(); } [LoggerMessage( Level = LogLevel.Debug, Message = "UpsertRow entry = {Entry}, ETag = {ETag}" )] private partial void LogDebugUpsertRow(ReminderEntryLogValue entry, string eTag); } } ================================================ FILE: src/Redis/Orleans.Reminders.Redis/Storage/RedisRemindersException.cs ================================================ using System; using System.Runtime.Serialization; namespace Orleans.Reminders.Redis { /// /// Exception thrown from . /// [GenerateSerializer] public class RedisRemindersException : Exception { /// /// Initializes a new instance of . /// public RedisRemindersException() { } /// /// Initializes a new instance of . /// /// The error message that explains the reason for the exception. public RedisRemindersException(string message) : base(message) { } /// /// Initializes a new instance of . /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. public RedisRemindersException(string message, Exception inner) : base(message, inner) { } /// [Obsolete] protected RedisRemindersException( SerializationInfo info, StreamingContext context) : base(info, context) { } } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/ByteStringCodec.cs ================================================ using System; using Google.Protobuf; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// Serializer for . /// [RegisterSerializer] public sealed class ByteStringCodec : IFieldCodec { /// ByteString IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference(ref reader, field); } field.EnsureWireType(WireType.LengthPrefixed); var length = reader.ReadVarUInt32(); var result = UnsafeByteOperations.UnsafeWrap(reader.ReadBytes(length)); ReferenceCodec.RecordObject(reader.Session, result); return result; } /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, ByteString value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(ByteString), WireType.LengthPrefixed); writer.WriteVarUInt32((uint)value.Length); writer.Write(value.Span); } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/ByteStringCopier.cs ================================================ using Google.Protobuf; using Orleans.Serialization.Cloning; namespace Orleans.Serialization; /// /// Copier for . /// [RegisterCopier] public sealed class ByteStringCopier : IDeepCopier { /// public ByteString DeepCopy(ByteString input, CopyContext context) { if (context.TryGetCopy(input, out var result)) { return result; } result = ByteString.CopyFrom(input.Span); context.RecordCopy(input, result); return result; } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/MapFieldCodec.cs ================================================ using System; using System.Buffers; using Google.Protobuf.Collections; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// Serializer for . /// /// The key type. /// The value type. [RegisterSerializer] public sealed class MapFieldCodec : IFieldCodec> { private readonly Type _keyFieldType = typeof(TKey); private readonly Type _valueFieldType = typeof(TValue); private readonly IFieldCodec _keyCodec; private readonly IFieldCodec _valueCodec; /// /// Initializes a new instance of the class. /// /// The key codec. /// The value codec. public MapFieldCodec( IFieldCodec keyCodec, IFieldCodec valueCodec) { _keyCodec = OrleansGeneratedCodeHelper.UnwrapService(this, keyCodec); _valueCodec = OrleansGeneratedCodeHelper.UnwrapService(this, valueCodec); } /// public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, MapField value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _keyCodec.WriteField(ref writer, innerFieldIdDelta, _keyFieldType, element.Key); _valueCodec.WriteField(ref writer, 0, _valueFieldType, element.Value); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public MapField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); TKey key = default; var valueExpected = false; MapField result = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = CreateInstance(reader.Session, placeholderReferenceId); break; case 1: if (result is null) ThrowLengthFieldMissing(); if (!valueExpected) { key = _keyCodec.ReadValue(ref reader, header); valueExpected = true; } else { result.Add(key, _valueCodec.ReadValue(ref reader, header)); valueExpected = false; } break; default: reader.ConsumeUnknownField(header); break; } } result ??= CreateInstance(reader.Session, placeholderReferenceId); return result; } private static MapField CreateInstance(SerializerSession session, uint placeholderReferenceId) { var result = new MapField(); ReferenceCodec.RecordObject(session, result, placeholderReferenceId); return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(MapField)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized MapField is missing its length field."); } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/MapFieldCopier.cs ================================================ using Google.Protobuf.Collections; using Orleans.Serialization.Cloning; namespace Orleans.Serialization; /// /// Copier for . /// /// The type of the t key. /// The type of the t value. [RegisterCopier] public sealed class MapFieldCopier : IDeepCopier>, IBaseCopier> { private readonly IDeepCopier _keyCopier; private readonly IDeepCopier _valueCopier; /// /// Initializes a new instance of the class. /// /// The key copier. /// The value copier. public MapFieldCopier(IDeepCopier keyCopier, IDeepCopier valueCopier) { _keyCopier = keyCopier; _valueCopier = valueCopier; } /// public MapField DeepCopy(MapField input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() != typeof(MapField)) { return context.DeepCopy(input); } result = new MapField(); context.RecordCopy(input, result); foreach (var pair in input) { result[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } return result; } /// public void DeepCopy(MapField input, MapField output, CopyContext context) { foreach (var pair in input) { output[_keyCopier.DeepCopy(pair.Key, context)] = _valueCopier.DeepCopy(pair.Value, context); } } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.csproj ================================================ README.md Microsoft.Orleans.Serialization.Protobuf $(DefaultTargetFrameworks);netstandard2.1 Google.Protobuf integration for Orleans.Serialization true false ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/ProtobufCodec.cs ================================================ using Google.Protobuf; using Orleans.Metadata; using Orleans.Serialization.Buffers; using Orleans.Serialization.Cloning; using Orleans.Serialization.Codecs; using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Orleans.Serialization; [Alias(WellKnownAlias)] public sealed class ProtobufCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter { public const string WellKnownAlias = "protobuf"; private static readonly Type SelfType = typeof(ProtobufCodec); private static readonly Type MessageType = typeof(IMessage); private static readonly Type MessageGenericType = typeof(IMessage<>); private static readonly ConcurrentDictionary MessageParsers = new(); private readonly ICodecSelector[] _serializableTypeSelectors; private readonly ICopierSelector[] _copyableTypeSelectors; /// /// Initializes a new instance of the class. /// /// Filters used to indicate which types should be serialized by this codec. /// Filters used to indicate which types should be copied by this codec. public ProtobufCodec( IEnumerable serializableTypeSelectors, IEnumerable copyableTypeSelectors) { _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray(); } /// public object DeepCopy(object input, CopyContext context) { if (!context.TryGetCopy(input, out object result)) { if (input is not IMessage protobufMessage) { throw new InvalidOperationException("Input is not a protobuf message"); } var messageSize = protobufMessage.CalculateSize(); using var buffer = new PooledBuffer(); var spanBuffer = buffer.GetSpan(messageSize)[..messageSize]; protobufMessage.WriteTo(spanBuffer); result = protobufMessage.Descriptor.Parser.ParseFrom(spanBuffer); context.RecordCopy(input, result); } return result; } /// bool IGeneralizedCodec.IsSupportedType(Type type) { if (type == SelfType) { return true; } if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _serializableTypeSelectors) { if (selector.IsSupportedType(type)) { return IsProtobufMessage(type); } } return false; } /// bool IGeneralizedCopier.IsSupportedType(Type type) { if (CommonCodecTypeFilter.IsAbstractOrFrameworkType(type)) { return false; } foreach (var selector in _copyableTypeSelectors) { if (selector.IsSupportedType(type)) { return IsProtobufMessage(type); } } return false; } /// bool? ITypeFilter.IsTypeAllowed(Type type) { if (!MessageType.IsAssignableFrom(type)) { return null; } if (type == MessageType) { // While IMessage is the basis of all supported types, it isn't directly supported return null; } return ((IGeneralizedCodec)this).IsSupportedType(type) || ((IGeneralizedCopier)this).IsSupportedType(type); } private static bool IsProtobufMessage(Type type) { if (type == MessageType) { // Not a concrete implementation, so not directly serializable return false; } if (type == MessageGenericType) { // Not a concrete implementation, but the generic type does give the concrete type type = type.GenericTypeArguments[0]; } if (!MessageParsers.ContainsKey(type.TypeHandle)) { if (Activator.CreateInstance(type) is not IMessage protobufMessageInstance) { return false; } MessageParsers.TryAdd(type.TypeHandle, protobufMessageInstance.Descriptor.Parser); } return true; } /// object IFieldCodec.ReadValue(ref Reader reader, Field field) { if (field.IsReference) { return ReferenceCodec.ReadReference(ref reader, field.FieldType); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); object result = null; Type type = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: ReferenceCodec.MarkValueField(reader.Session); type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader); break; case 1: if (type is null) { ThrowTypeFieldMissing(); } if (!MessageParsers.TryGetValue(type.TypeHandle, out var messageParser)) { throw new ArgumentException($"No parser found for the expected type {type.Name}", nameof(TInput)); } ReferenceCodec.MarkValueField(reader.Session); var length = (int)reader.ReadVarUInt32(); using (var buffer = new PooledBuffer()) { var spanBuffer = buffer.GetSpan(length)[..length]; reader.ReadBytes(spanBuffer); result = messageParser.ParseFrom(spanBuffer); } break; default: reader.ConsumeUnknownField(header); break; } } ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); return result; } private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field."); /// void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value) { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } if (value is not IMessage protobufMessage) { throw new ArgumentException("The provided value for serialization in not an instance of IMessage"); } writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited); // Write the type name ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed); writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType()); var messageSize = protobufMessage.CalculateSize(); using var buffer = new PooledBuffer(); var spanBuffer = buffer.GetSpan(messageSize)[..messageSize]; // Write the serialized payload protobufMessage.WriteTo(spanBuffer); ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed); writer.WriteVarUInt32((uint)spanBuffer.Length); writer.Write(spanBuffer); writer.WriteEndObject(); } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/README.md ================================================ # Microsoft Orleans Serialization for Protobuf ## Introduction Microsoft Orleans Serialization for Protobuf provides Protocol Buffers (Protobuf) serialization support for Microsoft Orleans using **Google.Protobuf**. This package integrates Google's official `Google.Protobuf` library with Orleans, allowing you to use Protocol Buffers messages in your grain interfaces and implementations. ## Getting Started To use this package, install it via NuGet: ```shell dotnet add package Microsoft.Orleans.Serialization.Protobuf ``` ## Example - Configuring Protobuf Serialization ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Hosting; using Orleans.Serialization; var builder = Host.CreateApplicationBuilder(args) .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering() // Configure Protobuf as a serializer .AddSerializer(serializerBuilder => serializerBuilder.AddProtobufSerializer()); }); // Run the host await builder.RunAsync(); ``` ## Using Protobuf Types with Orleans This package supports types generated from `.proto` files using Google.Protobuf. For detailed information on creating Protobuf messages and configuring your project, see [Create Protobuf messages for .NET apps](https://learn.microsoft.com/en-us/aspnet/core/grpc/protobuf). Once you have defined your Protobuf messages and configured code generation, you can use them directly in your grain interfaces: ```csharp using Orleans; using MyApp.Models; public interface IMyGrain : IGrainWithStringKey { Task GetData(); Task SetData(MyProtobufClass data); } public class MyGrain : Grain, IMyGrain { private MyProtobufClass _data; public Task GetData() => Task.FromResult(_data); public Task SetData(MyProtobufClass data) { _data = data; return Task.CompletedTask; } } ``` **Note:** Google.Protobuf collection types (`RepeatedField`, `MapField`, and `ByteString`) are automatically supported. ## Documentation For more comprehensive documentation, please refer to: - [Create Protobuf messages for .NET apps](https://learn.microsoft.com/en-us/aspnet/core/grpc/protobuf) - Official guide for working with Protobuf in .NET - [Microsoft Orleans Documentation](https://learn.microsoft.com/dotnet/orleans/) - [Orleans Serialization](https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization) ## Feedback & Contributing - If you have any issues or would like to provide feedback, please [open an issue on GitHub](https://github.com/dotnet/orleans/issues) - Join our community on [Discord](https://aka.ms/orleans-discord) - Follow the [@msftorleans](https://twitter.com/msftorleans) Twitter account for Orleans announcements - Contributions are welcome! Please review our [contribution guidelines](https://github.com/dotnet/orleans/blob/main/CONTRIBUTING.md) - This project is licensed under the [MIT license](https://github.com/dotnet/orleans/blob/main/LICENSE) ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/RepeatedFieldCodec.cs ================================================ using System; using System.Buffers; using System.Runtime.CompilerServices; using Google.Protobuf.Collections; using Orleans.Serialization.Buffers; using Orleans.Serialization.Codecs; using Orleans.Serialization.GeneratedCodeHelpers; using Orleans.Serialization.WireProtocol; namespace Orleans.Serialization; /// /// Serializer for . /// /// The element type. [RegisterSerializer] public sealed class RepeatedFieldCodec : IFieldCodec> { private readonly Type CodecElementType = typeof(T); private readonly IFieldCodec _fieldCodec; /// /// Initializes a new instance of the class. /// /// The field codec. public RepeatedFieldCodec(IFieldCodec fieldCodec) { _fieldCodec = OrleansGeneratedCodeHelper.UnwrapService(this, fieldCodec); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, RepeatedField value) where TBufferWriter : IBufferWriter { if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value)) { return; } writer.WriteFieldHeader(fieldIdDelta, expectedType, value.GetType(), WireType.TagDelimited); if (value.Count > 0) { UInt32Codec.WriteField(ref writer, 0, (uint)value.Count); uint innerFieldIdDelta = 1; foreach (var element in value) { _fieldCodec.WriteField(ref writer, innerFieldIdDelta, CodecElementType, element); innerFieldIdDelta = 0; } } writer.WriteEndObject(); } /// public RepeatedField ReadValue(ref Reader reader, Field field) { if (field.WireType == WireType.Reference) { return ReferenceCodec.ReadReference, TInput>(ref reader, field); } field.EnsureWireTypeTagDelimited(); var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session); RepeatedField result = null; uint fieldId = 0; while (true) { var header = reader.ReadFieldHeader(); if (header.IsEndBaseOrEndObject) { break; } fieldId += header.FieldIdDelta; switch (fieldId) { case 0: var length = (int)UInt32Codec.ReadValue(ref reader, header); if (length > 10240 && length > reader.Length) { ThrowInvalidSizeException(length); } result = new RepeatedField{ Capacity = length }; ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); break; case 1: if (result is null) { ThrowLengthFieldMissing(); } result.Add(_fieldCodec.ReadValue(ref reader, header)); break; default: reader.ConsumeUnknownField(header); break; } } if (result is null) { result = new(); ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId); } return result; } private static void ThrowInvalidSizeException(int length) => throw new IndexOutOfRangeException( $"Declared length of {typeof(RepeatedField)}, {length}, is greater than total length of input."); private static void ThrowLengthFieldMissing() => throw new RequiredFieldMissingException("Serialized RepeatedField is missing its length field."); } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/RepeatedFieldCopier.cs ================================================ using Google.Protobuf.Collections; using Orleans.Serialization.Cloning; namespace Orleans.Serialization; /// /// Copier for . /// /// The element type. [RegisterCopier] public sealed class RepeatedFieldCopier : IDeepCopier>, IBaseCopier> { private readonly IDeepCopier _copier; /// /// Initializes a new instance of the class. /// /// The value copier. public RepeatedFieldCopier(IDeepCopier valueCopier) { _copier = valueCopier; } /// public RepeatedField DeepCopy(RepeatedField input, CopyContext context) { if (context.TryGetCopy>(input, out var result)) { return result; } if (input.GetType() != typeof(RepeatedField)) { return context.DeepCopy(input); } result = new RepeatedField { Capacity = input.Count }; context.RecordCopy(input, result); foreach (var item in input) { result.Add(_copier.DeepCopy(item, context)); } return result; } /// public void DeepCopy(RepeatedField input, RepeatedField output, CopyContext context) { foreach (var item in input) { output.Add(_copier.DeepCopy(item, context)); } } } ================================================ FILE: src/Serializers/Orleans.Serialization.Protobuf/SerializationHostingExtensions.cs ================================================ using Google.Protobuf; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Cloning; using Orleans.Serialization.Serializers; using Orleans.Serialization.Utilities.Internal; using System; namespace Orleans.Serialization; /// /// Extension method for . /// public static class SerializationHostingExtensions { private static readonly ServiceDescriptor ServiceDescriptor = new (typeof(ProtobufCodec), typeof(ProtobufCodec)); /// /// Adds support for serializing and deserializing Protobuf IMessage types using . /// /// The serializer builder. public static ISerializerBuilder AddProtobufSerializer( this ISerializerBuilder serializerBuilder) => serializerBuilder.AddProtobufSerializer( isSerializable: type => typeof(IMessage).IsAssignableFrom(type), isCopyable: type => typeof(IMessage).IsAssignableFrom(type)); /// /// Adds support for serializing and deserializing Protobuf IMessage types using . /// /// The serializer builder. /// A delegate used to indicate which types should be serialized by this codec. /// A delegate used to indicate which types should be copied by this codec. public static ISerializerBuilder AddProtobufSerializer( this ISerializerBuilder serializerBuilder, Func isSerializable, Func isCopyable) { var services = serializerBuilder.Services; if (isSerializable != null) { services.AddSingleton(new DelegateCodecSelector { CodecName = ProtobufCodec.WellKnownAlias, IsSupportedTypeDelegate = isSerializable }); } if (isCopyable != null) { services.AddSingleton(new DelegateCopierSelector { CopierName = ProtobufCodec.WellKnownAlias, IsSupportedTypeDelegate = isCopyable }); } if (!services.Contains(ServiceDescriptor)) { services.AddSingleton(); services.AddFromExisting(); services.AddFromExisting(); services.AddFromExisting(); serializerBuilder.Configure(options => options.WellKnownTypeAliases[ProtobufCodec.WellKnownAlias] = typeof(ProtobufCodec)); } return serializerBuilder; } } ================================================ FILE: src/api/AWS/Orleans.Clustering.DynamoDB/Orleans.Clustering.DynamoDB.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Clustering.DynamoDB { public partial class DynamoDBClientOptions { [Redact] public string AccessKey { get { throw null; } set { } } public string ProfileName { get { throw null; } set { } } [Redact] public string SecretKey { get { throw null; } set { } } public string Service { get { throw null; } set { } } public string Token { get { throw null; } set { } } } public partial class DynamoDBGatewayListProviderHelper { } public partial class DynamoDBMembershipHelper { public static void ParseDataConnectionString(string dataConnectionString, Configuration.DynamoDBClusteringOptions options) { } } } namespace Orleans.Configuration { public partial class DynamoDBClusteringOptions : Clustering.DynamoDB.DynamoDBClientOptions { public bool CreateIfNotExists { get { throw null; } set { } } public int ReadCapacityUnits { get { throw null; } set { } } public string TableName { get { throw null; } set { } } public bool UpdateIfExists { get { throw null; } set { } } public bool UseProvisionedThroughput { get { throw null; } set { } } public int WriteCapacityUnits { get { throw null; } set { } } } public partial class DynamoDBClusteringSiloOptions { [RedactConnectionString] public string ConnectionString { get { throw null; } set { } } } public partial class DynamoDBGatewayOptions : Clustering.DynamoDB.DynamoDBClientOptions { public bool CreateIfNotExists { get { throw null; } set { } } public int ReadCapacityUnits { get { throw null; } set { } } public string TableName { get { throw null; } set { } } public bool UpdateIfExists { get { throw null; } set { } } public bool UseProvisionedThroughput { get { throw null; } set { } } public int WriteCapacityUnits { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class AwsUtilsHostingExtensions { public static IClientBuilder UseDynamoDBClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseDynamoDBClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseDynamoDBClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseDynamoDBClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/AWS/Orleans.Persistence.DynamoDB/Orleans.Persistence.DynamoDB.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class DynamoDBGrainStorageOptionsValidator : IConfigurationValidator { public DynamoDBGrainStorageOptionsValidator(DynamoDBStorageOptions options, string name) { } public void ValidateConfiguration() { } } public partial class DynamoDBStorageOptions : Persistence.DynamoDB.DynamoDBClientOptions, Storage.IStorageProviderSerializerOptions { public const int DEFAULT_INIT_STAGE = 10000; public bool CreateIfNotExists { get { throw null; } set { } } public bool DeleteStateOnClear { get { throw null; } set { } } public Storage.IGrainStorageSerializer GrainStorageSerializer { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public int ReadCapacityUnits { get { throw null; } set { } } public string ServiceId { get { throw null; } set { } } public string TableName { get { throw null; } set { } } public System.TimeSpan? TimeToLive { get { throw null; } set { } } public bool UpdateIfExists { get { throw null; } set { } } public bool UseProvisionedThroughput { get { throw null; } set { } } public int WriteCapacityUnits { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class DynamoDBGrainStorageServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDynamoDBGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDynamoDBGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDynamoDBGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddDynamoDBGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } } public static partial class DynamoDBGrainStorageSiloBuilderExtensions { public static ISiloBuilder AddDynamoDBGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddDynamoDBGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddDynamoDBGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddDynamoDBGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Persistence.DynamoDB { public partial class DynamoDBClientOptions { [Redact] public string AccessKey { get { throw null; } set { } } public string ProfileName { get { throw null; } set { } } [Redact] public string SecretKey { get { throw null; } set { } } public string Service { get { throw null; } set { } } public string Token { get { throw null; } set { } } } } namespace Orleans.Storage { public partial class DynamoDBGrainStorage : IGrainStorage, ILifecycleParticipant { public DynamoDBGrainStorage(string name, Configuration.DynamoDBStorageOptions options, Serialization.Serializers.IActivatorProvider activatorProvider, Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task Close(System.Threading.CancellationToken ct) { throw null; } public System.Threading.Tasks.Task Init(System.Threading.CancellationToken ct) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class DynamoDBGrainStorageFactory { public static DynamoDBGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } } ================================================ FILE: src/api/AWS/Orleans.Reminders.DynamoDB/Orleans.Reminders.DynamoDB.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class DynamoDBReminderStorageOptions : Reminders.DynamoDB.DynamoDBClientOptions { public bool CreateIfNotExists { get { throw null; } set { } } public int ReadCapacityUnits { get { throw null; } set { } } public string TableName { get { throw null; } set { } } public bool UpdateIfExists { get { throw null; } set { } } public bool UseProvisionedThroughput { get { throw null; } set { } } public int WriteCapacityUnits { get { throw null; } set { } } } public static partial class DynamoDBReminderStorageOptionsExtensions { public static void ParseConnectionString(this DynamoDBReminderStorageOptions options, string connectionString) { } } public partial class DynamoDBReminderTableOptions { [RedactConnectionString] public string ConnectionString { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class DynamoDBServiceCollectionReminderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseDynamoDBReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } } public static partial class DynamoDBSiloBuilderReminderExtensions { public static ISiloBuilder UseDynamoDBReminderService(this ISiloBuilder builder, System.Action configure) { throw null; } } } namespace Orleans.Reminders.DynamoDB { public partial class DynamoDBClientOptions { [Redact] public string AccessKey { get { throw null; } set { } } public string ProfileName { get { throw null; } set { } } [Redact] public string SecretKey { get { throw null; } set { } } public string Service { get { throw null; } set { } } public string Token { get { throw null; } set { } } } } ================================================ FILE: src/api/AWS/Orleans.Streaming.SQS/Orleans.Streaming.SQS.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class SqsOptions { [Redact] public string ConnectionString { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class ClientBuilderExtensions { public static IClientBuilder AddSqsStreams(this IClientBuilder builder, string name, System.Action configureOptions) { throw null; } public static IClientBuilder AddSqsStreams(this IClientBuilder builder, string name, System.Action configure) { throw null; } } public partial class ClusterClientSqsStreamConfigurator : ClusterClientPersistentStreamConfigurator { public ClusterClientSqsStreamConfigurator(string name, IClientBuilder builder) : base(default!, default!, default!) { } public ClusterClientSqsStreamConfigurator ConfigurePartitioning(int numOfparitions = 8) { throw null; } public ClusterClientSqsStreamConfigurator ConfigureSqs(System.Action> configureOptions) { throw null; } } public static partial class SiloBuilderExtensions { public static ISiloBuilder AddSqsStreams(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddSqsStreams(this ISiloBuilder builder, string name, System.Action configure) { throw null; } } public partial class SiloSqsStreamConfigurator : SiloPersistentStreamConfigurator { public SiloSqsStreamConfigurator(string name, System.Action> configureServicesDelegate) : base(default!, default!, default!) { } public SiloSqsStreamConfigurator ConfigureCache(int cacheSize = 4096) { throw null; } public SiloSqsStreamConfigurator ConfigurePartitioning(int numOfparitions = 8) { throw null; } public SiloSqsStreamConfigurator ConfigureSqs(System.Action> configureOptions) { throw null; } } } namespace OrleansAWSUtils.Streams { public partial class SQSAdapterFactory : Orleans.Streams.IQueueAdapterFactory { public SQSAdapterFactory(string name, Orleans.Configuration.SqsOptions sqsOptions, Orleans.Configuration.HashRingStreamQueueMapperOptions queueMapperOptions, Orleans.Configuration.SimpleQueueCacheOptions cacheOptions, Microsoft.Extensions.Options.IOptions clusterOptions, Orleans.Serialization.Serializer serializer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } protected System.Func> StreamFailureHandlerFactory { set { } } public static SQSAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public virtual System.Threading.Tasks.Task CreateAdapter() { throw null; } public System.Threading.Tasks.Task GetDeliveryFailureHandler(Orleans.Streams.QueueId queueId) { throw null; } public virtual Orleans.Streams.IQueueAdapterCache GetQueueAdapterCache() { throw null; } public Orleans.Streams.IStreamQueueMapper GetStreamQueueMapper() { throw null; } public virtual void Init() { } } public partial class SQSStreamProviderUtils { public static System.Threading.Tasks.Task DeleteAllUsedQueues(string providerName, string clusterId, string storageConnectionString, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { throw null; } } } ================================================ FILE: src/api/AdoNet/Orleans.Clustering.AdoNet/Orleans.Clustering.AdoNet.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AdoNetClusteringClientOptions { [Redact] public string ConnectionString { get { throw null; } set { } } public string Invariant { get { throw null; } set { } } } public partial class AdoNetClusteringClientOptionsValidator : IConfigurationValidator { public AdoNetClusteringClientOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } public partial class AdoNetClusteringSiloOptions { [Redact] public string ConnectionString { get { throw null; } set { } } public string Invariant { get { throw null; } set { } } } public partial class AdoNetClusteringSiloOptionsValidator : IConfigurationValidator { public AdoNetClusteringSiloOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class AdoNetHostingExtensions { public static IClientBuilder UseAdoNetClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseAdoNetClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseAdoNetClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAdoNetClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Runtime.Membership { public partial class AdoNetGatewayListProvider : Orleans.Messaging.IGatewayListProvider { public AdoNetGatewayListProvider(Microsoft.Extensions.Logging.ILogger logger, System.IServiceProvider serviceProvider, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Options.IOptions gatewayOptions, Microsoft.Extensions.Options.IOptions clusterOptions) { } public bool IsUpdatable { get { throw null; } } public System.TimeSpan MaxStaleness { get { throw null; } } public System.Threading.Tasks.Task> GetGateways() { throw null; } public System.Threading.Tasks.Task InitializeGatewayListProvider() { throw null; } } } namespace Orleans.Runtime.MembershipService { public partial class AdoNetClusteringTable : IMembershipTable { public AdoNetClusteringTable(System.IServiceProvider serviceProvider, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Options.IOptions clusteringOptions, Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task CleanupDefunctSiloEntries(System.DateTimeOffset beforeDate) { throw null; } public System.Threading.Tasks.Task DeleteMembershipTableEntries(string clusterId) { throw null; } public System.Threading.Tasks.Task InitializeMembershipTable(bool tryInitTableVersion) { throw null; } public System.Threading.Tasks.Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { throw null; } public System.Threading.Tasks.Task ReadAll() { throw null; } public System.Threading.Tasks.Task ReadRow(SiloAddress key) { throw null; } public System.Threading.Tasks.Task UpdateIAmAlive(MembershipEntry entry) { throw null; } public System.Threading.Tasks.Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { throw null; } } } ================================================ FILE: src/api/AdoNet/Orleans.GrainDirectory.AdoNet/Orleans.GrainDirectory.AdoNet.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AdoNetGrainDirectoryOptionsValidator : IConfigurationValidator { public AdoNetGrainDirectoryOptionsValidator(GrainDirectory.AdoNet.AdoNetGrainDirectoryOptions options, string name) { } public void ValidateConfiguration() { } } } namespace Orleans.GrainDirectory.AdoNet { public partial class AdoNetGrainDirectoryOptions { [Redact] [System.ComponentModel.DataAnnotations.Required] public string ConnectionString { get { throw null; } set { } } [System.ComponentModel.DataAnnotations.Required] public string Invariant { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class AdoNetGrainDirectorySiloBuilderExtensions { public static ISiloBuilder AddAdoNetGrainDirectory(this ISiloBuilder builder, string name, System.Action> configureOptions) { throw null; } public static ISiloBuilder AddAdoNetGrainDirectory(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder UseAdoNetGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAdoNetGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/AdoNet/Orleans.Persistence.AdoNet/Orleans.Persistence.AdoNet.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AdoNetGrainStorageOptions : Storage.IStorageProviderSerializerOptions { public const string DEFAULT_ADONET_INVARIANT = "System.Data.SqlClient"; public const int DEFAULT_INIT_STAGE = 10000; [Redact] public string ConnectionString { get { throw null; } set { } } public Storage.IGrainStorageSerializer GrainStorageSerializer { get { throw null; } set { } } public Storage.IStorageHasherPicker HashPicker { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public string Invariant { get { throw null; } set { } } public void UseOrleans3CompatibleHasher() { } } public partial class AdoNetGrainStorageOptionsValidator : IConfigurationValidator { public AdoNetGrainStorageOptionsValidator(AdoNetGrainStorageOptions configurationOptions, string name) { } public void ValidateConfiguration() { } } public partial class DefaultAdoNetGrainStorageOptionsHashPickerConfigurator : Microsoft.Extensions.Options.IPostConfigureOptions { public void PostConfigure(string name, AdoNetGrainStorageOptions options) { } } } namespace Orleans.Hosting { public static partial class AdoNetGrainStorageServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAdoNetGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAdoNetGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAdoNetGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAdoNetGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions = null) { throw null; } } public static partial class AdoNetGrainStorageSiloBuilderExtensions { public static ISiloBuilder AddAdoNetGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAdoNetGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddAdoNetGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAdoNetGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Storage { [System.Diagnostics.DebuggerDisplay("Name = {Name}, ConnectionString = {Storage.ConnectionString}")] public partial class AdoNetGrainStorage : IGrainStorage, ILifecycleParticipant { public const string BinaryFormatSerializerTag = "BinaryFormatSerializer"; public const string DefaultInitializationQuery = "SELECT QueryKey, QueryText FROM OrleansQuery WHERE QueryKey = 'WriteToStorageKey' OR QueryKey = 'ReadFromStorageKey' OR QueryKey = 'ClearStorageKey'"; public const string JsonFormatSerializerTag = "JsonFormatSerializer"; public const string XmlFormatSerializerTag = "XmlFormatSerializer"; public AdoNetGrainStorage(Serialization.Serializers.IActivatorProvider activatorProvider, Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Options.IOptions clusterOptions, string name) { } public RelationalStorageProviderQueries CurrentOperationalQueries { get { throw null; } set { } } public IStorageHasherPicker HashPicker { get { throw null; } set { } } public IGrainStorageSerializer Serializer { get { throw null; } set { } } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainReference, IGrainState grainState) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainReference, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainReference, IGrainState grainState) { throw null; } } public static partial class AdoNetGrainStorageFactory { public static AdoNetGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } public partial interface IHasher { string Description { get; } int Hash(byte[] data); } public partial interface IStorageHasherPicker { System.Collections.Generic.ICollection HashProviders { get; } IHasher PickHasher(string serviceId, string storageProviderInstanceName, string grainType, Runtime.GrainId grainId, IGrainState grainState, string tag = null); } public partial class Orleans3CompatibleStorageHashPicker : IStorageHasherPicker { public System.Collections.Generic.ICollection HashProviders { get { throw null; } } public IHasher PickHasher(string serviceId, string storageProviderInstanceName, string grainType, Runtime.GrainId grainId, IGrainState grainState, string tag = null) { throw null; } } public sealed partial class OrleansDefaultHasher : IHasher { public string Description { get { throw null; } } public int Hash(byte[] data) { throw null; } } public partial class RelationalStorageProviderQueries { public RelationalStorageProviderQueries(string writeToStorage, string readFromStorage, string clearState) { } public string ClearState { get { throw null; } set { } } public string ReadFromStorage { get { throw null; } set { } } public string WriteToStorage { get { throw null; } } } public partial class StorageHasherPicker : IStorageHasherPicker { public StorageHasherPicker(System.Collections.Generic.IEnumerable hashProviders) { } public System.Collections.Generic.ICollection HashProviders { get { throw null; } } public IHasher PickHasher(string serviceId, string storageProviderInstanceName, string grainType, Runtime.GrainId grainId, IGrainState grainState, string tag = null) { throw null; } } } ================================================ FILE: src/api/AdoNet/Orleans.Reminders.AdoNet/Orleans.Reminders.AdoNet.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AdoNetReminderTableOptions { [Redact] public string ConnectionString { get { throw null; } set { } } public string Invariant { get { throw null; } set { } } } public partial class AdoNetReminderTableOptionsValidator : IConfigurationValidator { public AdoNetReminderTableOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class SiloBuilderReminderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseAdoNetReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAdoNetReminderService(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAdoNetReminderService(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/AdoNet/Orleans.Streaming.AdoNet/Orleans.Streaming.AdoNet.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AdoNetStreamOptions { [Redact] public string ConnectionString { get { throw null; } set { } } public System.TimeSpan DeadLetterEvictionTimeout { get { throw null; } set { } } public int EvictionBatchSize { get { throw null; } set { } } public System.TimeSpan EvictionInterval { get { throw null; } set { } } public System.TimeSpan ExpiryTimeout { get { throw null; } set { } } public System.TimeSpan InitializationTimeout { get { throw null; } set { } } public string Invariant { get { throw null; } set { } } public int MaxAttempts { get { throw null; } set { } } public System.TimeSpan VisibilityTimeout { get { throw null; } set { } } } public partial class AdoNetStreamOptionsValidator : IConfigurationValidator { public AdoNetStreamOptionsValidator(AdoNetStreamOptions options, string name) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public partial class ClusterClientAdoNetStreamConfigurator : ClusterClientPersistentStreamConfigurator { public ClusterClientAdoNetStreamConfigurator(string name, IClientBuilder clientBuilder) : base(default!, default!, default!) { } public ClusterClientAdoNetStreamConfigurator ConfigureAdoNet(System.Action> configureOptions) { throw null; } public ClusterClientAdoNetStreamConfigurator ConfigureCache(int cacheSize = 4096) { throw null; } public ClusterClientAdoNetStreamConfigurator ConfigurePartitioning(int partitions = 8) { throw null; } } public static partial class ClusterClientAdoNetStreamExtensions { public static IClientBuilder AddAdoNetStreams(this IClientBuilder builder, string name, System.Action configureOptions) { throw null; } public static IClientBuilder AddAdoNetStreams(this IClientBuilder builder, string name, System.Action configure) { throw null; } } public partial class SiloAdoNetStreamConfigurator : SiloPersistentStreamConfigurator { public SiloAdoNetStreamConfigurator(string name, System.Action> configureDelegate) : base(default!, default!, default!) { } public SiloAdoNetStreamConfigurator ConfigureAdoNet(System.Action> configureOptions) { throw null; } public SiloAdoNetStreamConfigurator ConfigureCache(int cacheSize = 4096) { throw null; } public SiloAdoNetStreamConfigurator ConfigurePartitioning(int partitions = 8) { throw null; } } public static partial class SiloBuilderAdoNetStreamExtensions { public static ISiloBuilder AddAdoNetStreams(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddAdoNetStreams(this ISiloBuilder builder, string name, System.Action configure) { throw null; } } } ================================================ FILE: src/api/Azure/Orleans.Clustering.AzureStorage/Orleans.Clustering.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Clustering.AzureStorage { public partial class AzureStorageClusteringOptions : AzureStorageOperationOptions { public const string DEFAULT_TABLE_NAME = "OrleansSiloInstances"; public override string TableName { get { throw null; } set { } } } public partial class AzureStorageClusteringOptionsValidator : AzureStorageOperationOptionsValidator { public AzureStorageClusteringOptionsValidator(AzureStorageClusteringOptions options, string name) : base(default!, default!) { } } public partial class AzureStorageGatewayOptions : AzureStorageOperationOptions { public override string TableName { get { throw null; } set { } } } public partial class AzureStorageGatewayOptionsValidator : AzureStorageOperationOptionsValidator { public AzureStorageGatewayOptionsValidator(AzureStorageGatewayOptions options, string name) : base(default!, default!) { } } public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class AzureTableClusteringExtensions { public static IClientBuilder UseAzureStorageClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseAzureStorageClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseAzureStorageClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAzureStorageClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/Azure/Orleans.Clustering.Cosmos/Orleans.Clustering.Cosmos.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Clustering.Cosmos { public partial class CosmosClusteringOptions : CosmosOptions { } public partial class CosmosClusteringOptionsValidator : CosmosOptionsValidator { public CosmosClusteringOptionsValidator(CosmosClusteringOptions options, string name) : base(default!, default!) { } } public abstract partial class CosmosOptions { public bool CleanResourcesOnInitialization { get { throw null; } set { } } public Microsoft.Azure.Cosmos.CosmosClientOptions ClientOptions { get { throw null; } set { } } public string ContainerName { get { throw null; } set { } } public Microsoft.Azure.Cosmos.ThroughputProperties? ContainerThroughputProperties { get { throw null; } set { } } public string DatabaseName { get { throw null; } set { } } public int? DatabaseThroughput { get { throw null; } set { } } public bool IsResourceCreationEnabled { get { throw null; } set { } } public ICosmosOperationExecutor OperationExecutor { get { throw null; } set { } } public void ConfigureCosmosClient(System.Func> createClient) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.AzureKeyCredential authKeyOrResourceTokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.Core.TokenCredential tokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, string authKeyOrResourceToken) { } public void ConfigureCosmosClient(string connectionString) { } } public partial class CosmosOptionsValidator : IConfigurationValidator where TOptions : CosmosOptions { public CosmosOptionsValidator(TOptions options, string name) { } public void ValidateConfiguration() { } } public partial interface ICosmosOperationExecutor { System.Threading.Tasks.Task ExecuteOperation(System.Func> func, TArg arg); } } namespace Orleans.Hosting { public static partial class HostingExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosClustering(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosClustering(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } public static ISiloBuilder UseCosmosClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseCosmosClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseCosmosClustering(this ISiloBuilder builder) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosGatewayListProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosGatewayListProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } public static IClientBuilder UseCosmosGatewayListProvider(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseCosmosGatewayListProvider(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static IClientBuilder UseCosmosGatewayListProvider(this IClientBuilder builder) { throw null; } } } ================================================ FILE: src/api/Azure/Orleans.GrainDirectory.AzureStorage/Orleans.GrainDirectory.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AzureTableGrainDirectoryOptions : GrainDirectory.AzureStorage.AzureStorageOperationOptions { public const string DEFAULT_TABLE_NAME = "GrainDirectory"; public override string TableName { get { throw null; } set { } } } public partial class AzureTableGrainDirectoryOptionsValidator : GrainDirectory.AzureStorage.AzureStorageOperationOptionsValidator { public AzureTableGrainDirectoryOptionsValidator(AzureTableGrainDirectoryOptions options, string name) : base(default!, default!) { } } } namespace Orleans.GrainDirectory.AzureStorage { public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } public partial class AzureTableGrainDirectory : IGrainDirectory, ILifecycleParticipant { public AzureTableGrainDirectory(Configuration.AzureTableGrainDirectoryOptions directoryOptions, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task InitializeIfNeeded(System.Threading.CancellationToken ct = default) { throw null; } public System.Threading.Tasks.Task Lookup(Runtime.GrainId grainId) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task Register(Runtime.GrainAddress address, Runtime.GrainAddress? previousAddress) { throw null; } public System.Threading.Tasks.Task Register(Runtime.GrainAddress address) { throw null; } public System.Threading.Tasks.Task Unregister(Runtime.GrainAddress address) { throw null; } public System.Threading.Tasks.Task UnregisterMany(System.Collections.Generic.List addresses) { throw null; } public System.Threading.Tasks.Task UnregisterSilos(System.Collections.Generic.List siloAddresses) { throw null; } } } namespace Orleans.Hosting { public static partial class AzureTableGrainDirectoryServiceCollectionExtensions { } public static partial class AzureTableGrainDirectorySiloBuilderExtensions { public static ISiloBuilder AddAzureTableGrainDirectory(this ISiloBuilder builder, string name, System.Action> configureOptions) { throw null; } public static ISiloBuilder AddAzureTableGrainDirectory(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder UseAzureTableGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAzureTableGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/Azure/Orleans.Journaling.AzureStorage/Orleans.Journaling.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Journaling { public sealed partial class AzureAppendBlobStateMachineStorageOptions { public const string DEFAULT_CONTAINER_NAME = "state"; public const int DEFAULT_INIT_STAGE = 10000; public Azure.Storage.Blobs.BlobServiceClient? BlobServiceClient { get { throw null; } set { } } public System.Func BuildContainerFactory { get { throw null; } set { } } public Azure.Storage.Blobs.BlobClientOptions? ClientOptions { get { throw null; } set { } } public string ContainerName { get { throw null; } set { } } public System.Func GetBlobName { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public void ConfigureBlobServiceClient(System.Func> createClientCallback) { } public void ConfigureBlobServiceClient(string connectionString) { } public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Storage.StorageSharedKeyCredential sharedKeyCredential) { } public void ConfigureBlobServiceClient(System.Uri serviceUri) { } } public static partial class AzureBlobStorageHostingExtensions { public static Hosting.ISiloBuilder AddAzureAppendBlobStateMachineStorage(this Hosting.ISiloBuilder builder, System.Action? configure) { throw null; } public static Hosting.ISiloBuilder AddAzureAppendBlobStateMachineStorage(this Hosting.ISiloBuilder builder) { throw null; } } public partial interface IBlobContainerFactory { Azure.Storage.Blobs.BlobContainerClient GetBlobContainerClient(Runtime.GrainId grainId); System.Threading.Tasks.Task InitializeAsync(Azure.Storage.Blobs.BlobServiceClient client, System.Threading.CancellationToken cancellationToken); } } ================================================ FILE: src/api/Azure/Orleans.Persistence.AzureStorage/Orleans.Persistence.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AzureBlobStorageOptions : Storage.IStorageProviderSerializerOptions { public const string DEFAULT_CONTAINER_NAME = "grainstate"; public const int DEFAULT_INIT_STAGE = 10000; public Azure.Storage.Blobs.BlobServiceClient BlobServiceClient { get { throw null; } set { } } public System.Func BuildContainerFactory { get { throw null; } set { } } public Azure.Storage.Blobs.BlobClientOptions ClientOptions { get { throw null; } set { } } public string ContainerName { get { throw null; } set { } } public bool DeleteStateOnClear { get { throw null; } set { } } public Storage.IGrainStorageSerializer GrainStorageSerializer { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(string connectionString) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Storage.StorageSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri) { } } public partial class AzureBlobStorageOptionsValidator : IConfigurationValidator { public AzureBlobStorageOptionsValidator(AzureBlobStorageOptions options, string name) { } public void ValidateConfiguration() { } } public partial class AzureTableGrainStorageOptionsValidator : Persistence.AzureStorage.AzureStorageOperationOptionsValidator { public AzureTableGrainStorageOptionsValidator(AzureTableStorageOptions options, string name) : base(default!, default!) { } } public partial class AzureTableStorageOptions : Persistence.AzureStorage.AzureStorageOperationOptions, Storage.IStorageProviderSerializerOptions { public const int DEFAULT_INIT_STAGE = 10000; public const string DEFAULT_TABLE_NAME = "OrleansGrainState"; public bool DeleteStateOnClear { get { throw null; } set { } } public Storage.IGrainStorageSerializer GrainStorageSerializer { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public override string TableName { get { throw null; } set { } } public bool UseStringFormat { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class AzureBlobGrainStorageServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureBlobGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureBlobGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureBlobGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureBlobGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } } public static partial class AzureBlobSiloBuilderExtensions { public static ISiloBuilder AddAzureBlobGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureBlobGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddAzureBlobGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureBlobGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } public static partial class AzureTableSiloBuilderExtensions { public static ISiloBuilder AddAzureTableGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureTableGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddAzureTableGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureTableGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Persistence.AzureStorage { public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } } namespace Orleans.Storage { public partial class AzureBlobGrainStorage : IGrainStorage, ILifecycleParticipant { public AzureBlobGrainStorage(string name, Configuration.AzureBlobStorageOptions options, IBlobContainerFactory blobContainerFactory, Serialization.Serializers.IActivatorProvider activatorProvider, Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class AzureBlobGrainStorageFactory { public static AzureBlobGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } public partial class AzureTableGrainStorage : IGrainStorage, IRestExceptionDecoder, ILifecycleParticipant { public AzureTableGrainStorage(string name, Configuration.AzureTableStorageOptions options, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Logging.ILogger logger, Serialization.Serializers.IActivatorProvider activatorProvider) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public bool DecodeException(System.Exception e, out System.Net.HttpStatusCode httpStatusCode, out string restStatus, bool getRESTErrors = false) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class AzureTableGrainStorageFactory { public static AzureTableGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } public partial interface IBlobContainerFactory { Azure.Storage.Blobs.BlobContainerClient GetBlobContainerClient(Runtime.GrainId grainId); System.Threading.Tasks.Task InitializeAsync(Azure.Storage.Blobs.BlobServiceClient client); } [GenerateSerializer] public partial class TableStorageUpdateConditionNotSatisfiedException : InconsistentStateException { public TableStorageUpdateConditionNotSatisfiedException() { } [System.Obsolete] protected TableStorageUpdateConditionNotSatisfiedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public TableStorageUpdateConditionNotSatisfiedException(string msg, System.Exception exc) { } public TableStorageUpdateConditionNotSatisfiedException(string grainType, string grainId, string tableName, string storedEtag, string currentEtag, System.Exception storageException) { } public TableStorageUpdateConditionNotSatisfiedException(string errorMsg, string grainType, string grainId, string tableName, string storedEtag, string currentEtag, System.Exception storageException) { } public TableStorageUpdateConditionNotSatisfiedException(string msg) { } [Id(0)] public string GrainId { get { throw null; } } [Id(1)] public string GrainType { get { throw null; } } [Id(2)] public string TableName { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } } namespace OrleansCodeGen.Orleans.Storage { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TableStorageUpdateConditionNotSatisfiedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_TableStorageUpdateConditionNotSatisfiedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException instance) { } public global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TableStorageUpdateConditionNotSatisfiedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_TableStorageUpdateConditionNotSatisfiedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException input, global::Orleans.Storage.TableStorageUpdateConditionNotSatisfiedException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } ================================================ FILE: src/api/Azure/Orleans.Persistence.Cosmos/Orleans.Persistence.Cosmos.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class HostingExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCosmosGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action>? configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCosmosGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Action>? configureOptions = null) { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions, System.Type customPartitionKeyProviderType) { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Type customPartitionKeyProviderType, System.Action>? configureOptions = null) { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Action>? configureOptions = null) where TPartitionKeyProvider : class, Persistence.Cosmos.IPartitionKeyProvider { throw null; } public static ISiloBuilder AddCosmosGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) where TPartitionKeyProvider : class, Persistence.Cosmos.IPartitionKeyProvider { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCosmosGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action>? configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCosmosGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Action>? configureOptions = null) { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions, System.Type customPartitionKeyProviderType) { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Type customPartitionKeyProviderType, System.Action>? configureOptions = null) { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Action>? configureOptions = null) where TPartitionKeyProvider : class, Persistence.Cosmos.IPartitionKeyProvider { throw null; } public static ISiloBuilder AddCosmosGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) where TPartitionKeyProvider : class, Persistence.Cosmos.IPartitionKeyProvider { throw null; } } } namespace Orleans.Persistence.Cosmos { [GenerateSerializer] public partial class CosmosConditionNotSatisfiedException : Storage.InconsistentStateException { public CosmosConditionNotSatisfiedException() { } [System.Obsolete] protected CosmosConditionNotSatisfiedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public CosmosConditionNotSatisfiedException(string grainType, Runtime.GrainId grainId, string collection, string storedEtag, string currentEtag) { } public CosmosConditionNotSatisfiedException(string msg, System.Exception exc) { } public CosmosConditionNotSatisfiedException(string errorMsg, string grainType, Runtime.GrainId grainId, string collection, string storedEtag, string currentEtag) { } public CosmosConditionNotSatisfiedException(string msg) { } [Id(2)] public string Collection { get { throw null; } } [Id(0)] public string GrainId { get { throw null; } } [Id(1)] public string GrainType { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } public sealed partial class CosmosGrainStorage : Storage.IGrainStorage, ILifecycleParticipant { public CosmosGrainStorage(string name, CosmosGrainStorageOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.IServiceProvider serviceProvider, Microsoft.Extensions.Options.IOptions clusterOptions, IPartitionKeyProvider partitionKeyProvider, Serialization.Serializers.IActivatorProvider activatorProvider) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public partial class CosmosGrainStorageOptions : CosmosOptions { public const int DEFAULT_INIT_STAGE = 10000; public bool DeleteStateOnClear { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public string PartitionKeyPath { get { throw null; } set { } } public System.Collections.Generic.List StateFieldsToIndex { get { throw null; } set { } } } public abstract partial class CosmosOptions { public bool CleanResourcesOnInitialization { get { throw null; } set { } } public Microsoft.Azure.Cosmos.CosmosClientOptions ClientOptions { get { throw null; } set { } } public string ContainerName { get { throw null; } set { } } public Microsoft.Azure.Cosmos.ThroughputProperties? ContainerThroughputProperties { get { throw null; } set { } } public string DatabaseName { get { throw null; } set { } } public int? DatabaseThroughput { get { throw null; } set { } } public bool IsResourceCreationEnabled { get { throw null; } set { } } public ICosmosOperationExecutor OperationExecutor { get { throw null; } set { } } public void ConfigureCosmosClient(System.Func> createClient) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.AzureKeyCredential authKeyOrResourceTokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.Core.TokenCredential tokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, string authKeyOrResourceToken) { } public void ConfigureCosmosClient(string connectionString) { } } public partial class CosmosOptionsValidator : IConfigurationValidator where TOptions : CosmosOptions { public CosmosOptionsValidator(TOptions options, string name) { } public void ValidateConfiguration() { } } public static partial class CosmosStorageFactory { public static CosmosGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } public partial interface ICosmosOperationExecutor { System.Threading.Tasks.Task ExecuteOperation(System.Func> func, TArg arg); } public partial interface IPartitionKeyProvider { System.Threading.Tasks.ValueTask GetPartitionKey(string grainType, Runtime.GrainId grainId); } } namespace OrleansCodeGen.Orleans.Persistence.Cosmos { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CosmosConditionNotSatisfiedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_CosmosConditionNotSatisfiedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException instance) { } public global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_CosmosConditionNotSatisfiedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_CosmosConditionNotSatisfiedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException input, global::Orleans.Persistence.Cosmos.CosmosConditionNotSatisfiedException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } ================================================ FILE: src/api/Azure/Orleans.Reminders.AzureStorage/Orleans.Reminders.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class AzureStorageReminderServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseAzureTableReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseAzureTableReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseAzureTableReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string connectionString) { throw null; } } public static partial class AzureStorageReminderSiloBuilderExtensions { public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, System.Action configure) { throw null; } public static ISiloBuilder UseAzureTableReminderService(this ISiloBuilder builder, string connectionString) { throw null; } } } namespace Orleans.Reminders.AzureStorage { public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } public partial class AzureTableReminderStorageOptions : AzureStorageOperationOptions { public const string DEFAULT_TABLE_NAME = "OrleansReminders"; public override string TableName { get { throw null; } set { } } } public partial class AzureTableReminderStorageOptionsValidator : AzureStorageOperationOptionsValidator { public AzureTableReminderStorageOptionsValidator(AzureTableReminderStorageOptions options, string name) : base(default!, default!) { } } } namespace Orleans.Runtime.ReminderService { public sealed partial class AzureBasedReminderTable : IReminderTable { public AzureBasedReminderTable(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Options.IOptions storageOptions) { } public System.Threading.Tasks.Task ReadRow(GrainId grainId, string reminderName) { throw null; } public System.Threading.Tasks.Task ReadRows(GrainId grainId) { throw null; } public System.Threading.Tasks.Task ReadRows(uint begin, uint end) { throw null; } public System.Threading.Tasks.Task RemoveRow(GrainId grainId, string reminderName, string eTag) { throw null; } public System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task TestOnlyClearTable() { throw null; } public System.Threading.Tasks.Task UpsertRow(ReminderEntry entry) { throw null; } } } ================================================ FILE: src/api/Azure/Orleans.Reminders.Cosmos/Orleans.Reminders.Cosmos.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class HostingExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configure) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseCosmosReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static ISiloBuilder UseCosmosReminderService(this ISiloBuilder builder, System.Action> configure) { throw null; } public static ISiloBuilder UseCosmosReminderService(this ISiloBuilder builder, System.Action configure) { throw null; } } } namespace Orleans.Reminders.Cosmos { public abstract partial class CosmosOptions { public bool CleanResourcesOnInitialization { get { throw null; } set { } } public Microsoft.Azure.Cosmos.CosmosClientOptions ClientOptions { get { throw null; } set { } } public string ContainerName { get { throw null; } set { } } public Microsoft.Azure.Cosmos.ThroughputProperties? ContainerThroughputProperties { get { throw null; } set { } } public string DatabaseName { get { throw null; } set { } } public int? DatabaseThroughput { get { throw null; } set { } } public bool IsResourceCreationEnabled { get { throw null; } set { } } public ICosmosOperationExecutor OperationExecutor { get { throw null; } set { } } public void ConfigureCosmosClient(System.Func> createClient) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.AzureKeyCredential authKeyOrResourceTokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, Azure.Core.TokenCredential tokenCredential) { } public void ConfigureCosmosClient(string accountEndpoint, string authKeyOrResourceToken) { } public void ConfigureCosmosClient(string connectionString) { } } public partial class CosmosOptionsValidator : IConfigurationValidator where TOptions : CosmosOptions { public CosmosOptionsValidator(TOptions options, string name) { } public void ValidateConfiguration() { } } public partial class CosmosReminderTableOptions : CosmosOptions { } public partial interface ICosmosOperationExecutor { System.Threading.Tasks.Task ExecuteOperation(System.Func> func, TArg arg); } } ================================================ FILE: src/api/Azure/Orleans.Streaming.AzureStorage/Orleans.Streaming.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.AzureUtils { public partial class AzureQueueDataManager { public AzureQueueDataManager(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, string queueName, Configuration.AzureQueueOptions options) { } public AzureQueueDataManager(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, string queueName, string storageConnectionString, System.TimeSpan? visibilityTimeout = null) { } public string QueueName { get { throw null; } } public System.Threading.Tasks.Task AddQueueMessage(string message) { throw null; } public System.Threading.Tasks.Task ClearQueue() { throw null; } public System.Threading.Tasks.Task DeleteQueue() { throw null; } public System.Threading.Tasks.Task DeleteQueueMessage(Azure.Storage.Queues.Models.QueueMessage message) { throw null; } public System.Threading.Tasks.Task GetApproximateMessageCount() { throw null; } public System.Threading.Tasks.Task GetQueueMessage() { throw null; } public System.Threading.Tasks.Task> GetQueueMessages(int? count = null) { throw null; } public System.Threading.Tasks.Task InitQueueAsync() { throw null; } public System.Threading.Tasks.Task PeekQueueMessage() { throw null; } } } namespace Orleans.Configuration { public partial class AzureBlobLeaseProviderOptions { public const string DefaultBlobContainerName = "Leases"; public string BlobContainerName { get { throw null; } set { } } public Azure.Storage.Blobs.BlobServiceClient BlobServiceClient { get { throw null; } set { } } [System.Obsolete("Set the BlobServiceClient property directly.")] public Azure.Storage.Blobs.BlobClientOptions ClientOptions { get { throw null; } set { } } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(string connectionString) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri, Azure.Storage.StorageSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the BlobServiceClient property directly.")] public void ConfigureBlobServiceClient(System.Uri serviceUri) { } } public partial class AzureBlobLeaseProviderOptionsValidator : IConfigurationValidator { public AzureBlobLeaseProviderOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public static IConfigurationValidator Create(System.IServiceProvider services, string name) { throw null; } public void ValidateConfiguration() { } } public partial class AzureQueueOptions { [System.Obsolete("Set the QueueServiceClient property directly.")] public Azure.Storage.Queues.QueueClientOptions ClientOptions { get { throw null; } set { } } public System.TimeSpan? MessageVisibilityTimeout { get { throw null; } set { } } public System.Collections.Generic.List QueueNames { get { throw null; } set { } } public Azure.Storage.Queues.QueueServiceClient QueueServiceClient { get { throw null; } set { } } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(string connectionString) { } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(System.Uri serviceUri, Azure.Storage.StorageSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the QueueServiceClient property directly.")] public void ConfigureQueueServiceClient(System.Uri serviceUri) { } } public partial class AzureQueueOptionsValidator : IConfigurationValidator { internal AzureQueueOptionsValidator() { } public static IConfigurationValidator Create(System.IServiceProvider services, string name) { throw null; } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class AzureQueueStreamConfiguratorExtensions { public static void ConfigureAzureQueue(this IAzureQueueStreamConfigurator configurator, System.Action> configureOptions) { } public static void ConfigureQueueDataAdapter(this IAzureQueueStreamConfigurator configurator, System.Func> factory) { } public static void ConfigureQueueDataAdapter(this IAzureQueueStreamConfigurator configurator) where TQueueDataAdapter : Streams.IQueueDataAdapter { } } public sealed partial class AzureQueueStreamProviderBuilder : Providers.IProviderBuilder, Providers.IProviderBuilder { public void Configure(IClientBuilder builder, string name, Microsoft.Extensions.Configuration.IConfigurationSection configurationSection) { } public void Configure(ISiloBuilder builder, string name, Microsoft.Extensions.Configuration.IConfigurationSection configurationSection) { } } public static partial class ClientBuilderExtensions { public static IClientBuilder AddAzureQueueStreams(this IClientBuilder builder, string name, System.Action> configureOptions) { throw null; } public static IClientBuilder AddAzureQueueStreams(this IClientBuilder builder, string name, System.Action configure) { throw null; } } public partial class ClusterClientAzureQueueStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientAzureQueueStreamConfigurator, IAzureQueueStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator { public ClusterClientAzureQueueStreamConfigurator(string name, IClientBuilder builder) : base(default!, default!, default!) { } } public partial interface IAzureQueueStreamConfigurator : INamedServiceConfigurator { } public partial interface IClusterClientAzureQueueStreamConfigurator : IAzureQueueStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator { } public partial interface ISiloAzureQueueStreamConfigurator : IAzureQueueStreamConfigurator, INamedServiceConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator { } public partial class SiloAzureQueueStreamConfigurator : SiloPersistentStreamConfigurator, ISiloAzureQueueStreamConfigurator, IAzureQueueStreamConfigurator, INamedServiceConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator { public SiloAzureQueueStreamConfigurator(string name, System.Action> configureServicesDelegate) : base(default!, default!, default!) { } } public static partial class SiloAzureQueueStreamConfiguratorExtensions { public static void ConfigureCacheSize(this ISiloAzureQueueStreamConfigurator configurator, int cacheSize = 4096) { } } public static partial class SiloBuilderExtensions { public static ISiloBuilder AddAzureQueueStreams(this ISiloBuilder builder, string name, System.Action> configureOptions) { throw null; } public static ISiloBuilder AddAzureQueueStreams(this ISiloBuilder builder, string name, System.Action configure) { throw null; } public static ISiloBuilder UseAzureBlobLeaseProvider(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static void UseAzureBlobLeaseProvider(this ISiloPersistentStreamConfigurator configurator, System.Action> configureOptions) { } } } namespace Orleans.LeaseProviders { public partial class AzureBlobLeaseProvider : ILeaseProvider { public AzureBlobLeaseProvider(Microsoft.Extensions.Options.IOptions options) { } public System.Threading.Tasks.Task Acquire(string category, LeaseRequest[] leaseRequests) { throw null; } public static ILeaseProvider Create(System.IServiceProvider services, string name) { throw null; } public System.Threading.Tasks.Task Release(string category, AcquiredLease[] acquiredLeases) { throw null; } public System.Threading.Tasks.Task Renew(string category, AcquiredLease[] acquiredLeases) { throw null; } } } namespace Orleans.Providers.Streams.AzureQueue { public partial class AzureQueueAdapterFactory : Orleans.Streams.IQueueAdapterFactory { public AzureQueueAdapterFactory(string name, Configuration.AzureQueueOptions options, Configuration.SimpleQueueCacheOptions cacheOptions, Orleans.Streams.IQueueDataAdapter dataAdapter, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } protected System.Func> StreamFailureHandlerFactory { set { } } public static AzureQueueAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public virtual System.Threading.Tasks.Task CreateAdapter() { throw null; } public System.Threading.Tasks.Task GetDeliveryFailureHandler(Orleans.Streams.QueueId queueId) { throw null; } public virtual Orleans.Streams.IQueueAdapterCache GetQueueAdapterCache() { throw null; } public Orleans.Streams.IStreamQueueMapper GetStreamQueueMapper() { throw null; } public virtual void Init() { } } [SerializationCallbacks(typeof(Runtime.OnDeserializedCallbacks))] public partial class AzureQueueDataAdapterV1 : Orleans.Streams.IQueueDataAdapter, Orleans.Streams.IQueueDataAdapter, Serialization.IOnDeserialized { public AzureQueueDataAdapterV1(Serialization.Serializer serializer) { } public Orleans.Streams.IBatchContainer FromQueueMessage(string cloudMsg, long sequenceId) { throw null; } void Serialization.IOnDeserialized.OnDeserialized(Serialization.DeserializationContext context) { } public string ToQueueMessage(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Orleans.Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } [SerializationCallbacks(typeof(Runtime.OnDeserializedCallbacks))] public partial class AzureQueueDataAdapterV2 : Orleans.Streams.IQueueDataAdapter, Orleans.Streams.IQueueDataAdapter, Serialization.IOnDeserialized { public AzureQueueDataAdapterV2(Serialization.Serializer serializer) { } public Orleans.Streams.IBatchContainer FromQueueMessage(string cloudMsg, long sequenceId) { throw null; } void Serialization.IOnDeserialized.OnDeserialized(Serialization.DeserializationContext context) { } public string ToQueueMessage(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Orleans.Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } public partial class AzureQueueStreamProviderUtils { public static System.Threading.Tasks.Task ClearAllUsedAzureQueues(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Collections.Generic.List azureQueueNames, Configuration.AzureQueueOptions queueOptions) { throw null; } public static System.Threading.Tasks.Task ClearAllUsedAzureQueues(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Collections.Generic.List azureQueueNames, string storageConnectionString) { throw null; } public static System.Threading.Tasks.Task DeleteAllUsedAzureQueues(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Collections.Generic.List azureQueueNames, Configuration.AzureQueueOptions queueOptions) { throw null; } public static System.Threading.Tasks.Task DeleteAllUsedAzureQueues(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Collections.Generic.List azureQueueNames, string storageConnectionString) { throw null; } public static System.Collections.Generic.List GenerateDefaultAzureQueueNames(string serviceId, string providerName) { throw null; } } } namespace Orleans.Providers.Streams.PersistentStreams { public partial class AzureTableStorageStreamFailureHandler : Orleans.Streams.IStreamFailureHandler where TEntity : StreamDeliveryFailureEntity, new() { public AzureTableStorageStreamFailureHandler(Serialization.Serializer serializer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, bool faultOnFailure, string clusterId, Streaming.AzureStorage.AzureStorageOperationOptions azureStorageOptions, System.Func createEntity = null) { } public bool ShouldFaultSubsriptionOnError { get { throw null; } } public System.Threading.Tasks.Task InitAsync() { throw null; } public System.Threading.Tasks.Task OnDeliveryFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken sequenceToken) { throw null; } public System.Threading.Tasks.Task OnSubscriptionFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken sequenceToken) { throw null; } } public partial class StreamDeliveryFailureEntity : Azure.Data.Tables.ITableEntity { public Azure.ETag ETag { get { throw null; } set { } } public string PartitionKey { get { throw null; } set { } } public string RowKey { get { throw null; } set { } } public byte[] SequenceToken { get { throw null; } set { } } public string StreamGuid { get { throw null; } set { } } public string StreamNamespace { get { throw null; } set { } } public string StreamProviderName { get { throw null; } set { } } public System.Guid SubscriptionId { get { throw null; } set { } } public System.DateTimeOffset? Timestamp { get { throw null; } set { } } public virtual Orleans.Streams.StreamSequenceToken GetSequenceToken(Serialization.Serializer serializer) { throw null; } public static string MakeDefaultPartitionKey(string streamProviderName, string deploymentId) { throw null; } protected static long ReverseOrderTimestampTicks() { throw null; } public virtual void SetPartitionKey(string deploymentId) { } public virtual void SetRowkey() { } public virtual void SetSequenceToken(Serialization.Serializer serializer, Orleans.Streams.StreamSequenceToken token) { } } } namespace Orleans.Streaming.AzureStorage { public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } } ================================================ FILE: src/api/Azure/Orleans.Streaming.EventHubs/Orleans.Streaming.EventHubs.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AzureTableStreamCheckpointerOptions : Streaming.EventHubs.AzureStorageOperationOptions { public static readonly System.TimeSpan DEFAULT_CHECKPOINT_PERSIST_INTERVAL; public const string DEFAULT_TABLE_NAME = "Checkpoint"; public System.TimeSpan PersistInterval { get { throw null; } set { } } public override string TableName { get { throw null; } set { } } } public partial class AzureTableStreamCheckpointerOptionsValidator : Streaming.EventHubs.AzureStorageOperationOptionsValidator { public AzureTableStreamCheckpointerOptionsValidator(AzureTableStreamCheckpointerOptions options, string name) : base(default!, default!) { } } public partial class EventDataGeneratorStreamOptions { public const int DefaultEventHubPartitionCount = 4; public int EventHubPartitionCount; } public partial class EventHubOptions { public Azure.Messaging.EventHubs.EventHubConnectionOptions ConnectionOptions { get { throw null; } set { } } public void ConfigureEventHubConnection(Azure.Messaging.EventHubs.EventHubConnection connection, string consumerGroup) { } public void ConfigureEventHubConnection(CreateConnectionDelegate createConnection, string eventHubName, string consumerGroup) { } public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, Azure.AzureNamedKeyCredential credential) { } public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, Azure.AzureSasCredential credential) { } public void ConfigureEventHubConnection(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, Azure.Core.TokenCredential credential) { } public void ConfigureEventHubConnection(string connectionString, string eventHubName, string consumerGroup) { } public delegate Azure.Messaging.EventHubs.EventHubConnection CreateConnectionDelegate(Azure.Messaging.EventHubs.EventHubConnectionOptions connectionOptions); } public partial class EventHubOptionsValidator : IConfigurationValidator { public EventHubOptionsValidator(EventHubOptions options, string name) { } public void ValidateConfiguration() { } } public partial class EventHubReceiverOptions { public int? PrefetchCount { get { throw null; } set { } } public bool StartFromNow { get { throw null; } set { } } } public partial class EventHubStreamCachePressureOptions { public double? AveragingCachePressureMonitorFlowControlThreshold { get { throw null; } set { } } public double? SlowConsumingMonitorFlowControlThreshold { get { throw null; } set { } } public System.TimeSpan? SlowConsumingMonitorPressureWindowSize { get { throw null; } set { } } } public partial class StreamCheckpointerConfigurationValidator : IConfigurationValidator { public StreamCheckpointerConfigurationValidator(System.IServiceProvider services, string name) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class ClientBuilderExtensions { public static IClientBuilder AddEventHubStreams(this IClientBuilder builder, string name, System.Action configureEventHub) { throw null; } public static IClientBuilder AddEventHubStreams(this IClientBuilder builder, string name, System.Action configure) { throw null; } } public partial class ClusterClientEventHubStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientEventHubStreamConfigurator, IEventHubStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator { public ClusterClientEventHubStreamConfigurator(string name, IClientBuilder builder) : base(default!, default!, default!) { } } public static partial class EventHubStreamConfiguratorExtensions { public static void ConfigureEventHub(this IEventHubStreamConfigurator configurator, System.Action> configureOptions) { } public static void UseDataAdapter(this IEventHubStreamConfigurator configurator, System.Func factory) { } } public partial interface IClusterClientEventHubStreamConfigurator : IEventHubStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator { } public partial interface IEventHubStreamConfigurator : INamedServiceConfigurator { } public partial interface ISiloEventHubStreamConfigurator : IEventHubStreamConfigurator, INamedServiceConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator { } public static partial class SiloBuilderExtensions { public static ISiloBuilder AddEventHubStreams(this ISiloBuilder builder, string name, System.Action configureEventHub, System.Action configureDefaultCheckpointer) { throw null; } public static ISiloBuilder AddEventHubStreams(this ISiloBuilder builder, string name, System.Action configure) { throw null; } } public partial class SiloEventHubStreamConfigurator : SiloRecoverableStreamConfigurator, ISiloEventHubStreamConfigurator, IEventHubStreamConfigurator, INamedServiceConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator { public SiloEventHubStreamConfigurator(string name, System.Action> configureServicesDelegate) : base(default!, default!, default!) { } } public static partial class SiloEventHubStreamConfiguratorExtensions { public static void ConfigureCachePressuring(this ISiloEventHubStreamConfigurator configurator, System.Action> configureOptions) { } public static void ConfigureCheckpointer(this ISiloEventHubStreamConfigurator configurator, System.Func checkpointerFactoryBuilder, System.Action> configureOptions) where TOptions : class, new() { } public static void ConfigurePartitionReceiver(this ISiloEventHubStreamConfigurator configurator, System.Action> configureOptions) { } public static void UseAzureTableCheckpointer(this ISiloEventHubStreamConfigurator configurator, System.Action> configureOptions) { } } } namespace Orleans.Hosting.Developer { public static partial class EventDataGeneratorConfiguratorExtensions { public static void ConfigureCachePressuring(this IEventDataGeneratorStreamConfigurator configurator, System.Action> configureOptions) { } public static void UseDataAdapter(this IEventDataGeneratorStreamConfigurator configurator, System.Func factory) { } } public partial class EventDataGeneratorStreamConfigurator : SiloRecoverableStreamConfigurator, IEventDataGeneratorStreamConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { public EventDataGeneratorStreamConfigurator(string name, System.Action> configureServicesDelegate) : base(default!, default!, default!) { } } public partial interface IEventDataGeneratorStreamConfigurator : ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { } public static partial class SiloBuilderExtensions { public static ISiloBuilder AddEventDataGeneratorStreams(this ISiloBuilder builder, string name, System.Action configure) { throw null; } } } namespace Orleans.Streaming.EventHubs { public partial class AggregatedCachePressureMonitor : System.Collections.Generic.List, ICachePressureMonitor { public AggregatedCachePressureMonitor(Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public Providers.Streams.Common.ICacheMonitor CacheMonitor { set { } } public void AddCachePressureMonitor(ICachePressureMonitor monitor) { } public bool IsUnderPressure(System.DateTime utcNow) { throw null; } public void RecordCachePressureContribution(double cachePressureContribution) { } } public partial class AveragingCachePressureMonitor : ICachePressureMonitor { public AveragingCachePressureMonitor(Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public AveragingCachePressureMonitor(double flowControlThreshold, Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public Providers.Streams.Common.ICacheMonitor CacheMonitor { set { } } public bool IsUnderPressure(System.DateTime utcNow) { throw null; } public void RecordCachePressureContribution(double cachePressureContribution) { } } public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } public partial class DefaultEventHubReceiverMonitor : Providers.Streams.Common.DefaultQueueAdapterReceiverMonitor { public DefaultEventHubReceiverMonitor(EventHubReceiverMonitorDimensions dimensions) : base(default(System.Collections.Generic.KeyValuePair[])!) { } } public static partial class EventDataExtensions { public static System.Collections.Generic.IDictionary DeserializeProperties(this System.ArraySegment bytes, Serialization.Serializer serializer) { throw null; } public static string GetStreamNamespaceProperty(this Azure.Messaging.EventHubs.EventData eventData) { throw null; } public static byte[] SerializeProperties(this Azure.Messaging.EventHubs.EventData eventData, Serialization.Serializer serializer) { throw null; } public static void SetStreamNamespaceProperty(this Azure.Messaging.EventHubs.EventData eventData, string streamNamespace) { } } public partial class EventHubAdapterFactory : Streams.IQueueAdapterFactory, Streams.IQueueAdapter, Streams.IQueueAdapterCache { protected readonly IEventHubDataAdapter dataAdapter; protected System.Func EventHubReceiverFactory; protected Microsoft.Extensions.Logging.ILogger logger; protected readonly System.IServiceProvider serviceProvider; public EventHubAdapterFactory(string name, Configuration.EventHubOptions ehOptions, Configuration.EventHubReceiverOptions receiverOptions, Configuration.EventHubStreamCachePressureOptions cacheOptions, Configuration.StreamCacheEvictionOptions cacheEvictionOptions, Configuration.StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdapter, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Statistics.IEnvironmentStatisticsProvider environmentStatisticsProvider) { } protected System.Func, Microsoft.Extensions.Logging.ILoggerFactory, IEventHubQueueCache> CacheFactory { get { throw null; } set { } } public Streams.StreamProviderDirection Direction { get { throw null; } protected set { } } public bool IsRewindable { get { throw null; } } public string Name { get { throw null; } } protected System.Func QueueMapperFactory { get { throw null; } set { } } protected System.Func ReceiverMonitorFactory { get { throw null; } set { } } protected System.Func> StreamFailureHandlerFactory { get { throw null; } set { } } public static EventHubAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public System.Threading.Tasks.Task CreateAdapter() { throw null; } protected virtual IEventHubQueueCacheFactory CreateCacheFactory(Configuration.EventHubStreamCachePressureOptions eventHubCacheOptions) { throw null; } public Streams.IQueueCache CreateQueueCache(Streams.QueueId queueId) { throw null; } public Streams.IQueueAdapterReceiver CreateReceiver(Streams.QueueId queueId) { throw null; } public System.Threading.Tasks.Task GetDeliveryFailureHandler(Streams.QueueId queueId) { throw null; } protected virtual System.Threading.Tasks.Task GetPartitionIdsAsync() { throw null; } public Streams.IQueueAdapterCache GetQueueAdapterCache() { throw null; } public Streams.IStreamQueueMapper GetStreamQueueMapper() { throw null; } public virtual void Init() { } protected virtual void InitEventHubClient() { } public virtual System.Threading.Tasks.Task QueueMessageBatchAsync(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } [GenerateSerializer] public partial class EventHubBatchContainer : Streams.IBatchContainer { public EventHubBatchContainer(EventHubMessage eventHubMessage, Serialization.Serializer serializer) { } public Streams.StreamSequenceToken SequenceToken { get { throw null; } } public Runtime.StreamId StreamId { get { throw null; } } public System.Collections.Generic.IEnumerable> GetEvents() { throw null; } public bool ImportRequestContext() { throw null; } public static Azure.Messaging.EventHubs.EventData ToEventData(Serialization.Serializer bodySerializer, Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, System.Collections.Generic.Dictionary requestContext) { throw null; } public static void UpdateEventData(Azure.Messaging.EventHubs.EventData eventData, Serialization.Serializer bodySerializer, Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, System.Collections.Generic.Dictionary requestContext) { } } public partial class EventHubBlockPoolMonitorDimensions : EventHubMonitorAggregationDimensions { public EventHubBlockPoolMonitorDimensions() { } public EventHubBlockPoolMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string blockPoolId) { } public string BlockPoolId { get { throw null; } set { } } } public partial class EventHubCacheMonitorDimensions : EventHubReceiverMonitorDimensions { public EventHubCacheMonitorDimensions() { } public EventHubCacheMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string ehPartition, string blockPoolId) { } public string BlockPoolId { get { throw null; } set { } } } public partial class EventHubCheckpointer : Streams.IStreamQueueCheckpointer { internal EventHubCheckpointer() { } public bool CheckpointExists { get { throw null; } } public static System.Threading.Tasks.Task> Create(Configuration.AzureTableStreamCheckpointerOptions options, string streamProviderName, string partition, string serviceId, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { throw null; } public System.Threading.Tasks.Task Load() { throw null; } public void Update(string offset, System.DateTime utcNow) { } } public partial class EventHubCheckpointerFactory : Streams.IStreamQueueCheckpointerFactory { public EventHubCheckpointerFactory(string providerName, Configuration.AzureTableStreamCheckpointerOptions options, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task> Create(string partition) { throw null; } public static Streams.IStreamQueueCheckpointerFactory CreateFactory(System.IServiceProvider services, string providerName) { throw null; } } public partial class EventHubDataAdapter : IEventHubDataAdapter, Streams.IQueueDataAdapter, Providers.Streams.Common.ICacheDataAdapter { public EventHubDataAdapter(Serialization.Serializer serializer) { } protected virtual System.ArraySegment EncodeMessageIntoSegment(Azure.Messaging.EventHubs.EventData queueMessage, System.Func> getSegment) { throw null; } public virtual Providers.Streams.Common.CachedMessage FromQueueMessage(Streams.StreamPosition streamPosition, Azure.Messaging.EventHubs.EventData queueMessage, System.DateTime dequeueTime, System.Func> getSegment) { throw null; } public virtual Streams.IBatchContainer GetBatchContainer(ref Providers.Streams.Common.CachedMessage cachedMessage) { throw null; } protected virtual Streams.IBatchContainer GetBatchContainer(EventHubMessage eventHubMessage) { throw null; } public virtual string GetOffset(Providers.Streams.Common.CachedMessage lastItemPurged) { throw null; } public virtual string GetPartitionKey(Runtime.StreamId streamId) { throw null; } public virtual Streams.StreamSequenceToken GetSequenceToken(ref Providers.Streams.Common.CachedMessage cachedMessage) { throw null; } public virtual Runtime.StreamId GetStreamIdentity(Azure.Messaging.EventHubs.EventData queueMessage) { throw null; } public virtual Streams.StreamPosition GetStreamPosition(string partition, Azure.Messaging.EventHubs.EventData queueMessage) { throw null; } public virtual Azure.Messaging.EventHubs.EventData ToQueueMessage(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } [GenerateSerializer] public partial class EventHubMessage { public EventHubMessage(Providers.Streams.Common.CachedMessage cachedMessage, Serialization.Serializer serializer) { } public EventHubMessage(Runtime.StreamId streamId, string partitionKey, string offset, long sequenceNumber, System.DateTime enqueueTimeUtc, System.DateTime dequeueTimeUtc, System.Collections.Generic.IDictionary properties, byte[] payload) { } [Id(5)] public System.DateTime DequeueTimeUtc { get { throw null; } } [Id(4)] public System.DateTime EnqueueTimeUtc { get { throw null; } } [Id(2)] public string Offset { get { throw null; } } [Id(1)] public string PartitionKey { get { throw null; } } [Id(7)] public byte[] Payload { get { throw null; } } [Id(6)] public System.Collections.Generic.IDictionary Properties { get { throw null; } } [Id(3)] public long SequenceNumber { get { throw null; } } [Id(0)] public Runtime.StreamId StreamId { get { throw null; } } } public partial class EventHubMonitorAggregationDimensions { public EventHubMonitorAggregationDimensions() { } public EventHubMonitorAggregationDimensions(EventHubMonitorAggregationDimensions dimensions) { } public EventHubMonitorAggregationDimensions(string ehHubPath) { } public string EventHubPath { get { throw null; } set { } } } public partial class EventHubPartitionSettings { public Configuration.EventHubOptions Hub { get { throw null; } set { } } public string Partition { get { throw null; } set { } } public Configuration.EventHubReceiverOptions ReceiverOptions { get { throw null; } set { } } } public partial class EventHubQueueCache : IEventHubQueueCache, Streams.IQueueFlowController, System.IDisposable { protected readonly Providers.Streams.Common.PooledQueueCache cache; public EventHubQueueCache(string partition, int defaultMaxAddCount, Providers.Streams.Common.IObjectPool bufferPool, IEventHubDataAdapter dataAdapter, Providers.Streams.Common.IEvictionStrategy evictionStrategy, Streams.IStreamQueueCheckpointer checkpointer, Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor cacheMonitor, System.TimeSpan? cacheMonitorWriteInterval, System.TimeSpan? metadataMinTimeInCache) { } public string Partition { get { throw null; } } public System.Collections.Generic.List Add(System.Collections.Generic.List messages, System.DateTime dequeueTimeUtc) { throw null; } public void AddCachePressureMonitor(ICachePressureMonitor monitor) { } public void Dispose() { } public object GetCursor(Runtime.StreamId streamId, Streams.StreamSequenceToken sequenceToken) { throw null; } public int GetMaxAddCount() { throw null; } public void SignalPurge() { } public bool TryGetNextMessage(object cursorObj, out Streams.IBatchContainer message) { throw null; } } public partial class EventHubQueueCacheFactory : IEventHubQueueCacheFactory { public EventHubQueueCacheFactory(Configuration.EventHubStreamCachePressureOptions cacheOptions, Configuration.StreamCacheEvictionOptions evictionOptions, Configuration.StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdater, EventHubMonitorAggregationDimensions sharedDimensions, System.Func cacheMonitorFactory = null, System.Func blockPoolMonitorFactory = null) { } public System.Func BlockPoolMonitorFactory { get { throw null; } set { } } public System.Func CacheMonitorFactory { get { throw null; } set { } } protected virtual void AddCachePressureMonitors(IEventHubQueueCache cache, Configuration.EventHubStreamCachePressureOptions providerOptions, Microsoft.Extensions.Logging.ILogger cacheLogger) { } protected virtual Providers.Streams.Common.IObjectPool CreateBufferPool(Configuration.StreamStatisticOptions statisticOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, EventHubMonitorAggregationDimensions sharedDimensions, out string blockPoolId) { throw null; } protected virtual IEventHubQueueCache CreateCache(string partition, IEventHubDataAdapter dataAdatper, Configuration.StreamStatisticOptions statisticOptions, Configuration.StreamCacheEvictionOptions streamCacheEvictionOptions, Streams.IStreamQueueCheckpointer checkpointer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Providers.Streams.Common.IObjectPool bufferPool, string blockPoolId, Providers.Streams.Common.TimePurgePredicate timePurge, EventHubMonitorAggregationDimensions sharedDimensions) { throw null; } public IEventHubQueueCache CreateCache(string partition, Streams.IStreamQueueCheckpointer checkpointer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { throw null; } } public partial class EventHubReceiverMonitorDimensions : EventHubMonitorAggregationDimensions { public EventHubReceiverMonitorDimensions() { } public EventHubReceiverMonitorDimensions(EventHubMonitorAggregationDimensions dimensions, string ehPartition) { } public string EventHubPartition { get { throw null; } set { } } } [GenerateSerializer] public partial class EventHubSequenceToken : Providers.Streams.Common.EventSequenceToken, IEventHubPartitionLocation { public EventHubSequenceToken() { } public EventHubSequenceToken(string eventHubOffset, long sequenceNumber, int eventIndex) { } [Id(0)] [Newtonsoft.Json.JsonProperty] public string EventHubOffset { get { throw null; } } public override string ToString() { throw null; } } [GenerateSerializer] public partial class EventHubSequenceTokenV2 : EventHubSequenceToken { public EventHubSequenceTokenV2() { } public EventHubSequenceTokenV2(string eventHubOffset, long sequenceNumber, int eventIndex) { } } public partial interface ICachePressureMonitor { Providers.Streams.Common.ICacheMonitor CacheMonitor { set; } bool IsUnderPressure(System.DateTime utcNow); void RecordCachePressureContribution(double cachePressureContribution); } public partial interface IEventHubDataAdapter : Streams.IQueueDataAdapter, Providers.Streams.Common.ICacheDataAdapter { Providers.Streams.Common.CachedMessage FromQueueMessage(Streams.StreamPosition position, Azure.Messaging.EventHubs.EventData queueMessage, System.DateTime dequeueTime, System.Func> getSegment); string GetOffset(Providers.Streams.Common.CachedMessage cachedMessage); string GetPartitionKey(Runtime.StreamId streamId); Runtime.StreamId GetStreamIdentity(Azure.Messaging.EventHubs.EventData queueMessage); Streams.StreamPosition GetStreamPosition(string partition, Azure.Messaging.EventHubs.EventData queueMessage); } public partial interface IEventHubPartitionLocation { string EventHubOffset { get; } long SequenceNumber { get; } } public partial interface IEventHubQueueCache : Streams.IQueueFlowController, System.IDisposable { System.Collections.Generic.List Add(System.Collections.Generic.List message, System.DateTime dequeueTimeUtc); void AddCachePressureMonitor(ICachePressureMonitor monitor); object GetCursor(Runtime.StreamId streamId, Streams.StreamSequenceToken sequenceToken); void SignalPurge(); bool TryGetNextMessage(object cursorObj, out Streams.IBatchContainer message); } public partial interface IEventHubQueueCacheFactory { IEventHubQueueCache CreateCache(string partition, Streams.IStreamQueueCheckpointer checkpointer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory); } public partial interface IEventHubReceiver { System.Threading.Tasks.Task CloseAsync(); System.Threading.Tasks.Task> ReceiveAsync(int maxCount, System.TimeSpan waitTime); } public partial class SlowConsumingPressureMonitor : ICachePressureMonitor { public const double DefaultFlowControlThreshold = 0.5D; public static System.TimeSpan DefaultPressureWindowSize; public SlowConsumingPressureMonitor(Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public SlowConsumingPressureMonitor(double flowControlThreshold, Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public SlowConsumingPressureMonitor(double flowControlThreshold, System.TimeSpan pressureWindowSzie, Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public SlowConsumingPressureMonitor(System.TimeSpan pressureWindowSize, Microsoft.Extensions.Logging.ILogger logger, Providers.Streams.Common.ICacheMonitor monitor = null) { } public Providers.Streams.Common.ICacheMonitor CacheMonitor { set { } } public double FlowControlThreshold { get { throw null; } set { } } public System.TimeSpan PressureWindowSize { get { throw null; } set { } } public bool IsUnderPressure(System.DateTime utcNow) { throw null; } public void RecordCachePressureContribution(double cachePressureContribution) { } } } namespace Orleans.Streaming.EventHubs.StatisticMonitors { public partial class DefaultEventHubBlockPoolMonitor : Providers.Streams.Common.DefaultBlockPoolMonitor { public DefaultEventHubBlockPoolMonitor(EventHubBlockPoolMonitorDimensions dimensions) : base(default(System.Collections.Generic.KeyValuePair[])!) { } } public partial class DefaultEventHubCacheMonitor : Providers.Streams.Common.DefaultCacheMonitor { public DefaultEventHubCacheMonitor(EventHubCacheMonitorDimensions dimensions) : base(default(System.Collections.Generic.KeyValuePair[])!) { } } } namespace Orleans.Streaming.EventHubs.Testing { public partial class EventDataGeneratorAdapterFactory : EventHubAdapterFactory, Providers.IControllable { public EventDataGeneratorAdapterFactory(string name, Configuration.EventDataGeneratorStreamOptions options, Configuration.EventHubOptions ehOptions, Configuration.EventHubReceiverOptions receiverOptions, Configuration.EventHubStreamCachePressureOptions cacheOptions, Configuration.StreamCacheEvictionOptions evictionOptions, Configuration.StreamStatisticOptions statisticOptions, IEventHubDataAdapter dataAdapter, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Statistics.IEnvironmentStatisticsProvider environmentStatisticsProvider) : base(default!, default!, default!, default!, default!, default!, default!, default!, default!, default!) { } public new static EventDataGeneratorAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public virtual System.Threading.Tasks.Task ExecuteCommand(int command, object arg) { throw null; } public static string[] GenerateEventHubPartitions(int partitionCount) { throw null; } protected override System.Threading.Tasks.Task GetPartitionIdsAsync() { throw null; } public override void Init() { } protected override void InitEventHubClient() { } public enum Commands { Randomly_Place_Stream_To_Queue = 20004, Stop_Producing_On_Stream = 20005 } [GenerateSerializer] public partial class StreamRandomPlacementArg { public StreamRandomPlacementArg(Runtime.StreamId streamId, int randomNumber) { } [Id(1)] public int RandomNumber { get { throw null; } set { } } [Id(0)] public Runtime.StreamId StreamId { get { throw null; } set { } } } } public partial class EventHubPartitionDataGenerator : IDataGenerator, IStreamDataGeneratingController { public EventHubPartitionDataGenerator(Configuration.EventDataGeneratorStreamOptions options, System.Func> generatorFactory, Microsoft.Extensions.Logging.ILogger logger) { } public void AddDataGeneratorForStream(Runtime.StreamId streamId) { } public void StopProducingOnStream(Runtime.StreamId streamId) { } public bool TryReadEvents(int maxCount, out System.Collections.Generic.IEnumerable events) { throw null; } } public partial class EventHubPartitionGeneratorReceiver : IEventHubReceiver { public EventHubPartitionGeneratorReceiver(IDataGenerator generator) { } public System.Threading.Tasks.Task CloseAsync() { throw null; } public void ConfigureDataGeneratorForStream(Runtime.StreamId streamId) { } public System.Threading.Tasks.Task> ReceiveAsync(int maxCount, System.TimeSpan waitTime) { throw null; } public void StopProducingOnStream(Runtime.StreamId streamId) { } } public partial interface IDataGenerator { bool TryReadEvents(int maxCount, out System.Collections.Generic.IEnumerable events); } public partial interface IIntCounter { int Value { get; } void Increment(); } public partial interface IStreamDataGeneratingController { void AddDataGeneratorForStream(Runtime.StreamId streamId); void StopProducingOnStream(Runtime.StreamId streamId); } public partial interface IStreamDataGenerator : IDataGenerator { IIntCounter SequenceNumberCounter { set; } bool ShouldProduce { set; } Runtime.StreamId StreamId { get; } } public partial class NoOpCheckpointer : Streams.IStreamQueueCheckpointer { public static NoOpCheckpointer Instance; public bool CheckpointExists { get { throw null; } } public System.Threading.Tasks.Task Load() { throw null; } public void Update(string offset, System.DateTime utcNow) { } } public partial class NoOpCheckpointerFactory : Streams.IStreamQueueCheckpointerFactory { public static NoOpCheckpointerFactory Instance; public System.Threading.Tasks.Task> Create(string partition) { throw null; } } public partial class SimpleStreamEventDataGenerator : IStreamDataGenerator, IDataGenerator { public SimpleStreamEventDataGenerator(Runtime.StreamId streamId, Microsoft.Extensions.Logging.ILogger logger, Serialization.DeepCopier deepCopier, Serialization.Serializer serializer) { } public IIntCounter SequenceNumberCounter { set { } } public bool ShouldProduce { set { } } public Runtime.StreamId StreamId { get { throw null; } set { } } public static System.Func> CreateFactory(System.IServiceProvider services) { throw null; } public bool TryReadEvents(int maxCount, out System.Collections.Generic.IEnumerable events) { throw null; } } } namespace OrleansCodeGen.Orleans.Streaming.EventHubs { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventHubBatchContainer : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventHubBatchContainer(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streaming.EventHubs.EventHubBatchContainer instance) { } public global::Orleans.Streaming.EventHubs.EventHubBatchContainer ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streaming.EventHubs.EventHubBatchContainer instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streaming.EventHubs.EventHubBatchContainer value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventHubMessage : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventHubMessage(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streaming.EventHubs.EventHubMessage instance) { } public global::Orleans.Streaming.EventHubs.EventHubMessage ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streaming.EventHubs.EventHubMessage instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streaming.EventHubs.EventHubMessage value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventHubSequenceToken : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventHubSequenceToken(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streaming.EventHubs.EventHubSequenceToken instance) { } public global::Orleans.Streaming.EventHubs.EventHubSequenceToken ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streaming.EventHubs.EventHubSequenceToken instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streaming.EventHubs.EventHubSequenceToken value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventHubSequenceTokenV2 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventHubSequenceTokenV2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 instance) { } public global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventHubBatchContainer : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventHubBatchContainer(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Streaming.EventHubs.EventHubBatchContainer DeepCopy(global::Orleans.Streaming.EventHubs.EventHubBatchContainer original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streaming.EventHubs.EventHubBatchContainer input, global::Orleans.Streaming.EventHubs.EventHubBatchContainer output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventHubMessage : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventHubMessage(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Streaming.EventHubs.EventHubMessage DeepCopy(global::Orleans.Streaming.EventHubs.EventHubMessage original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streaming.EventHubs.EventHubMessage input, global::Orleans.Streaming.EventHubs.EventHubMessage output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventHubSequenceToken : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventHubSequenceToken(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Streaming.EventHubs.EventHubSequenceToken DeepCopy(global::Orleans.Streaming.EventHubs.EventHubSequenceToken original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streaming.EventHubs.EventHubSequenceToken input, global::Orleans.Streaming.EventHubs.EventHubSequenceToken output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventHubSequenceTokenV2 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventHubSequenceTokenV2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 DeepCopy(global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 input, global::Orleans.Streaming.EventHubs.EventHubSequenceTokenV2 output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } namespace OrleansCodeGen.Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamRandomPlacementArg : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_StreamRandomPlacementArg(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg instance) { } public global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_StreamRandomPlacementArg : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_StreamRandomPlacementArg(global::Orleans.Serialization.Activators.IActivator _activator) { } public global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg DeepCopy(global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg input, global::Orleans.Streaming.EventHubs.Testing.EventDataGeneratorAdapterFactory.StreamRandomPlacementArg output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } ================================================ FILE: src/api/Azure/Orleans.Transactions.AzureStorage/Orleans.Transactions.AzureStorage.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class AzureTableTransactionalStateOptions : Transactions.AzureStorage.AzureStorageOperationOptions { public const int DEFAULT_INIT_STAGE = 10000; public int InitStage { get { throw null; } set { } } public override string TableName { get { throw null; } set { } } } public partial class AzureTableTransactionalStateOptionsValidator : Transactions.AzureStorage.AzureStorageOperationOptionsValidator { public AzureTableTransactionalStateOptionsValidator(AzureTableTransactionalStateOptions options, string name) : base(default!, default!) { } } } namespace Orleans.Hosting { public static partial class AzureTableTransactionServicecollectionExtensions { } public static partial class AzureTableTransactionSiloBuilderExtensions { public static ISiloBuilder AddAzureTableTransactionalStateStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureTableTransactionalStateStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddAzureTableTransactionalStateStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddAzureTableTransactionalStateStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Transactions.AzureStorage { public partial class AzureStorageOperationOptions { public Azure.Data.Tables.TableClientOptions ClientOptions { get { throw null; } set { } } public AzureStoragePolicyOptions StoragePolicyOptions { get { throw null; } } public virtual string TableName { get { throw null; } set { } } public Azure.Data.Tables.TableServiceClient TableServiceClient { get { throw null; } set { } } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Func> createClientCallback) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(string connectionString) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.AzureSasCredential azureSasCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Core.TokenCredential tokenCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri, Azure.Data.Tables.TableSharedKeyCredential sharedKeyCredential) { } [System.Obsolete("Set the TableServiceClient property directly.")] public void ConfigureTableServiceClient(System.Uri serviceUri) { } } public partial class AzureStorageOperationOptionsValidator : IConfigurationValidator where TOptions : AzureStorageOperationOptions { public AzureStorageOperationOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } public partial class AzureStoragePolicyOptions { public System.TimeSpan CreationTimeout { get { throw null; } set { } } public int MaxBulkUpdateRows { get { throw null; } set { } } public int MaxCreationRetries { get { throw null; } set { } } public int MaxOperationRetries { get { throw null; } set { } } public System.TimeSpan OperationTimeout { get { throw null; } set { } } public System.TimeSpan PauseBetweenCreationRetries { get { throw null; } set { } } public System.TimeSpan PauseBetweenOperationRetries { get { throw null; } set { } } } public partial class AzureTableTransactionalStateStorageFactory : Abstractions.ITransactionalStateStorageFactory, ILifecycleParticipant { public AzureTableTransactionalStateStorageFactory(string name, Configuration.AzureTableTransactionalStateOptions options, Microsoft.Extensions.Options.IOptions clusterOptions, System.IServiceProvider services, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public static Abstractions.ITransactionalStateStorageFactory Create(System.IServiceProvider services, string name) { throw null; } public Abstractions.ITransactionalStateStorage Create(string stateName, Runtime.IGrainContext context) where TState : class, new() { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } } public partial class AzureTableTransactionalStateStorage : Abstractions.ITransactionalStateStorage where TState : class, new() { public AzureTableTransactionalStateStorage(Azure.Data.Tables.TableClient table, string partition, Newtonsoft.Json.JsonSerializerSettings JsonSettings, Microsoft.Extensions.Logging.ILogger> logger) { } public System.Threading.Tasks.Task> Load() { throw null; } public System.Threading.Tasks.Task Store(string expectedETag, Abstractions.TransactionalStateMetaData metadata, System.Collections.Generic.List> statesToPrepare, long? commitUpTo, long? abortAfter) { throw null; } } } ================================================ FILE: src/api/Cassandra/Orleans.Clustering.Cassandra/Orleans.Clustering.Cassandra.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Clustering.Cassandra.Hosting { public partial class CassandraClusteringOptions { public System.TimeSpan InitializeRetryMaxDelay { get { throw null; } set { } } public bool UseCassandraTtl { get { throw null; } set { } } public void ConfigureClient(System.Func> configurationDelegate) { } public void ConfigureClient(string connectionString, string keyspace = "orleans") { } } public static partial class CassandraMembershipHostingExtensions { public static Orleans.Hosting.IClientBuilder UseCassandraClustering(this Orleans.Hosting.IClientBuilder builder, System.Action configureOptions) { throw null; } public static Orleans.Hosting.IClientBuilder UseCassandraClustering(this Orleans.Hosting.IClientBuilder builder, System.Func> sessionProvider) { throw null; } public static Orleans.Hosting.IClientBuilder UseCassandraClustering(this Orleans.Hosting.IClientBuilder builder, string connectionString, string keyspace = "orleans") { throw null; } public static Orleans.Hosting.IClientBuilder UseCassandraClustering(this Orleans.Hosting.IClientBuilder builder) { throw null; } public static Orleans.Hosting.ISiloBuilder UseCassandraClustering(this Orleans.Hosting.ISiloBuilder builder, System.Action configureOptions) { throw null; } public static Orleans.Hosting.ISiloBuilder UseCassandraClustering(this Orleans.Hosting.ISiloBuilder builder, System.Func> sessionProvider) { throw null; } public static Orleans.Hosting.ISiloBuilder UseCassandraClustering(this Orleans.Hosting.ISiloBuilder builder, string connectionString, string keyspace = "orleans") { throw null; } public static Orleans.Hosting.ISiloBuilder UseCassandraClustering(this Orleans.Hosting.ISiloBuilder builder) { throw null; } } } ================================================ FILE: src/api/Orleans.BroadcastChannel/Orleans.BroadcastChannel.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public partial class ImplicitChannelSubscriptionAttribute : System.Attribute, Metadata.IGrainBindingsProviderAttribute { public ImplicitChannelSubscriptionAttribute() { } public ImplicitChannelSubscriptionAttribute(BroadcastChannel.IChannelNamespacePredicate predicate, string channelIdMapper = null) { } public ImplicitChannelSubscriptionAttribute(string streamNamespace, string channelIdMapper = null) { } public ImplicitChannelSubscriptionAttribute(System.Type predicateType, string channelIdMapper = null) { } public string ChannelIdMapper { get { throw null; } } public BroadcastChannel.IChannelNamespacePredicate Predicate { get { throw null; } } public System.Collections.Generic.IEnumerable> GetBindings(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public sealed partial class RegexImplicitChannelSubscriptionAttribute : ImplicitChannelSubscriptionAttribute { public RegexImplicitChannelSubscriptionAttribute(string pattern) { } } } namespace Orleans.BroadcastChannel { public partial class BroadcastChannelOptions { public bool FireAndForgetDelivery { get { throw null; } set { } } } [GenerateSerializer] [Immutable] public readonly partial struct ChannelId : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable { private readonly object _dummy; private readonly int _dummyPrimitive; public System.ReadOnlyMemory FullKey { get { throw null; } } public System.ReadOnlyMemory Key { get { throw null; } } public System.ReadOnlyMemory Namespace { get { throw null; } } public readonly int CompareTo(ChannelId other) { throw null; } public static ChannelId Create(System.ReadOnlySpan ns, System.ReadOnlySpan key) { throw null; } public static ChannelId Create(string ns, System.Guid key) { throw null; } public static ChannelId Create(string ns, string key) { throw null; } public readonly bool Equals(ChannelId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly string GetKeyAsString() { throw null; } public readonly string? GetNamespace() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static bool operator ==(ChannelId s1, ChannelId s2) { throw null; } public static bool operator !=(ChannelId s1, ChannelId s2) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } } public partial class ConstructorChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider { public const string Prefix = "ctor"; public static string FormatPattern(System.Type predicateType, string constructorArgument) { throw null; } public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate) { throw null; } } public sealed partial class DefaultChannelIdMapper : IChannelIdMapper { public const string Name = "default"; public Runtime.IdSpan GetGrainKeyId(Metadata.GrainBindings grainBindings, ChannelId streamId) { throw null; } } public partial class DefaultChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider { public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate) { throw null; } } public partial interface IBroadcastChannelProvider { IBroadcastChannelWriter GetChannelWriter(ChannelId streamId); } public partial interface IBroadcastChannelSubscription { ChannelId ChannelId { get; } string ProviderName { get; } System.Threading.Tasks.Task Attach(System.Func onPublished, System.Func onError = null); } public partial interface IBroadcastChannelWriter { System.Threading.Tasks.Task Publish(T item); } public partial interface IChannelIdMapper { Runtime.IdSpan GetGrainKeyId(Metadata.GrainBindings grainBindings, ChannelId streamId); } public partial interface IChannelNamespacePredicate { string PredicatePattern { get; } bool IsMatch(string streamNamespace); } public partial interface IChannelNamespacePredicateProvider { bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate); } public partial interface IOnBroadcastChannelSubscribed { System.Threading.Tasks.Task OnSubscribed(IBroadcastChannelSubscription streamSubscription); } public partial class RegexChannelNamespacePredicate : IChannelNamespacePredicate { public RegexChannelNamespacePredicate(string regex) { } public string PredicatePattern { get { throw null; } } public bool IsMatch(string streamNameSpace) { throw null; } } } namespace Orleans.Hosting { public static partial class ChannelHostingExtensions { public static IClientBuilder AddBroadcastChannel(this IClientBuilder @this, string name, System.Action> configureOptions = null) { throw null; } public static IClientBuilder AddBroadcastChannel(this IClientBuilder @this, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddBroadcastChannel(this ISiloBuilder @this, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddBroadcastChannel(this ISiloBuilder @this, string name, System.Action configureOptions) { throw null; } public static BroadcastChannel.IBroadcastChannelProvider GetBroadcastChannelProvider(this IClusterClient @this, string name) { throw null; } } } namespace OrleansCodeGen.Orleans.BroadcastChannel { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ChannelId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.BroadcastChannel.ChannelId instance) { } public global::Orleans.BroadcastChannel.ChannelId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.BroadcastChannel.ChannelId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.BroadcastChannel.ChannelId value) where TBufferWriter : System.Buffers.IBufferWriter { } } } ================================================ FILE: src/api/Orleans.Client/Orleans.Client.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ ================================================ FILE: src/api/Orleans.Clustering.Consul/Orleans.Clustering.Consul.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class ConsulClusteringOptions { public System.Func CreateClient { get { throw null; } } public string KvRootFolder { get { throw null; } set { } } public void ConfigureConsulClient(System.Func createClientCallback) { } public void ConfigureConsulClient(System.Uri address, string aclClientToken = null) { } } public partial class ConsulClusteringOptionsValidator : IConfigurationValidator where TOptions : ConsulClusteringOptions { public ConsulClusteringOptionsValidator(TOptions options, string name = null) { } public string Name { get { throw null; } } public TOptions Options { get { throw null; } } public virtual void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class ConsulUtilsHostingExtensions { public static IClientBuilder UseConsulClientClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseConsulClientClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseConsulSiloClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseConsulSiloClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Runtime.Host { [Newtonsoft.Json.JsonObject] public partial class ConsulSiloRegistration { internal ConsulSiloRegistration() { } [Newtonsoft.Json.JsonProperty] public string Hostname { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public int ProxyPort { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public string SiloName { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public System.DateTime StartTime { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public SiloStatus Status { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public System.Collections.Generic.List SuspectingSilos { get { throw null; } set { } } } [Newtonsoft.Json.JsonObject] public partial class SuspectingSilo { [Newtonsoft.Json.JsonProperty] public string Id { get { throw null; } set { } } [Newtonsoft.Json.JsonProperty] public System.DateTime Time { get { throw null; } set { } } } } namespace Orleans.Runtime.Membership { public partial class ConsulBasedMembershipTable : IMembershipTable { public ConsulBasedMembershipTable(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptions membershipTableOptions, Microsoft.Extensions.Options.IOptions clusterOptions) { } public System.Threading.Tasks.Task CleanupDefunctSiloEntries(System.DateTimeOffset beforeDate) { throw null; } public System.Threading.Tasks.Task DeleteMembershipTableEntries(string clusterId) { throw null; } public System.Threading.Tasks.Task InitializeMembershipTable(bool tryInitTableVersion) { throw null; } public System.Threading.Tasks.Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { throw null; } public System.Threading.Tasks.Task ReadAll() { throw null; } public static System.Threading.Tasks.Task ReadAll(Consul.IConsulClient consulClient, string clusterId, string kvRootFolder, Microsoft.Extensions.Logging.ILogger logger, string versionKey) { throw null; } public System.Threading.Tasks.Task ReadRow(SiloAddress siloAddress) { throw null; } public System.Threading.Tasks.Task UpdateIAmAlive(MembershipEntry entry) { throw null; } public System.Threading.Tasks.Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { throw null; } } public partial class ConsulGatewayListProvider : Orleans.Messaging.IGatewayListProvider { public ConsulGatewayListProvider(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Options.IOptions gatewayOptions, Microsoft.Extensions.Options.IOptions clusterOptions) { } public bool IsUpdatable { get { throw null; } } public System.TimeSpan MaxStaleness { get { throw null; } } public System.Threading.Tasks.Task> GetGateways() { throw null; } public System.Threading.Tasks.Task InitializeGatewayListProvider() { throw null; } } } ================================================ FILE: src/api/Orleans.Clustering.ZooKeeper/Orleans.Clustering.ZooKeeper.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class ZooKeeperClusteringSiloOptions { [Redact] public string ConnectionString { get { throw null; } set { } } } public partial class ZooKeeperGatewayListProviderOptions { [Redact] public string ConnectionString { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class ZooKeeperHostingExtensions { public static IClientBuilder UseZooKeeperClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseZooKeeperClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseZooKeeperClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseZooKeeperClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Runtime.Membership { public partial class ZooKeeperBasedMembershipTable : IMembershipTable { public ZooKeeperBasedMembershipTable(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptions membershipTableOptions, Microsoft.Extensions.Options.IOptions clusterOptions) { } public System.Threading.Tasks.Task CleanupDefunctSiloEntries(System.DateTimeOffset beforeDate) { throw null; } public System.Threading.Tasks.Task DeleteMembershipTableEntries(string clusterId) { throw null; } public System.Threading.Tasks.Task InitializeMembershipTable(bool tryInitPath) { throw null; } public System.Threading.Tasks.Task InsertRow(MembershipEntry entry, TableVersion tableVersion) { throw null; } public System.Threading.Tasks.Task ReadAll() { throw null; } public System.Threading.Tasks.Task ReadRow(SiloAddress siloAddress) { throw null; } public System.Threading.Tasks.Task UpdateIAmAlive(MembershipEntry entry) { throw null; } public System.Threading.Tasks.Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion) { throw null; } } public partial class ZooKeeperGatewayListProvider : Orleans.Messaging.IGatewayListProvider { public ZooKeeperGatewayListProvider(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Options.IOptions gatewayOptions, Microsoft.Extensions.Options.IOptions clusterOptions) { } public bool IsUpdatable { get { throw null; } } public System.TimeSpan MaxStaleness { get { throw null; } } public System.Threading.Tasks.Task> GetGateways() { throw null; } public System.Threading.Tasks.Task InitializeGatewayListProvider() { throw null; } } } ================================================ FILE: src/api/Orleans.Connections.Security/Orleans.Connections.Security.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { public static partial class TlsConnectionBuilderExtensions { public static void UseClientTls(this Microsoft.AspNetCore.Connections.IConnectionBuilder builder, Connections.Security.TlsOptions options) { } public static void UseServerTls(this Microsoft.AspNetCore.Connections.IConnectionBuilder builder, Connections.Security.TlsOptions options) { } } } namespace Orleans.Connections.Security { public static partial class CertificateLoader { public static System.Security.Cryptography.X509Certificates.X509Certificate2 LoadFromStoreCert(string subject, string storeName, System.Security.Cryptography.X509Certificates.StoreLocation storeLocation, bool allowInvalid, bool server) { throw null; } } public delegate System.Security.Cryptography.X509Certificates.X509Certificate ClientCertificateSelectionCallback(object sender, string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection localCertificates, System.Security.Cryptography.X509Certificates.X509Certificate remoteCertificate, string[] acceptableIssuers); public partial interface ITlsApplicationProtocolFeature { System.ReadOnlyMemory ApplicationProtocol { get; } } public partial interface ITlsConnectionFeature { System.Security.Cryptography.X509Certificates.X509Certificate2 RemoteCertificate { get; set; } System.Threading.Tasks.Task GetRemoteCertificateAsync(System.Threading.CancellationToken cancellationToken); } public partial interface ITlsHandshakeFeature { System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; } int CipherStrength { get; } System.Security.Authentication.HashAlgorithmType HashAlgorithm { get; } int HashStrength { get; } System.Security.Authentication.ExchangeAlgorithmType KeyExchangeAlgorithm { get; } int KeyExchangeStrength { get; } System.Security.Authentication.SslProtocols Protocol { get; } } public enum RemoteCertificateMode { NoCertificate = 0, AllowCertificate = 1, RequireCertificate = 2 } public delegate bool RemoteCertificateValidator(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors policyErrors); public delegate System.Security.Cryptography.X509Certificates.X509Certificate ServerCertificateSelectionCallback(object sender, string hostName); public partial class TlsClientAuthenticationOptions { public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public ClientCertificateSelectionCallback LocalCertificateSelectionCallback { get { throw null; } set { } } public object SslClientAuthenticationOptions { get { throw null; } } public string TargetHost { get { throw null; } set { } } } public partial class TlsOptions { public bool CheckCertificateRevocation { get { throw null; } set { } } public RemoteCertificateMode ClientCertificateMode { get { throw null; } set { } } public System.TimeSpan HandshakeTimeout { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509Certificate2 LocalCertificate { get { throw null; } set { } } public System.Func LocalClientCertificateSelector { get { throw null; } set { } } public System.Func LocalServerCertificateSelector { get { throw null; } set { } } public System.Action OnAuthenticateAsClient { get { throw null; } set { } } public System.Action OnAuthenticateAsServer { get { throw null; } set { } } public RemoteCertificateMode RemoteCertificateMode { get { throw null; } set { } } public RemoteCertificateValidator RemoteCertificateValidation { get { throw null; } set { } } public System.Security.Authentication.SslProtocols SslProtocols { get { throw null; } set { } } public void AllowAnyRemoteCertificate() { } } public partial class TlsServerAuthenticationOptions { public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } public bool ClientCertificateRequired { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509Certificate ServerCertificate { get { throw null; } set { } } public ServerCertificateSelectionCallback ServerCertificateSelectionCallback { get { throw null; } set { } } public object SslServerAuthenticationOptions { get { throw null; } } } } namespace Orleans.Hosting { public static partial class OrleansConnectionSecurityHostingExtensions { public static IClientBuilder UseTls(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static IClientBuilder UseTls(this IClientBuilder builder, System.Security.Cryptography.X509Certificates.StoreName storeName, string subject, bool allowInvalid, System.Security.Cryptography.X509Certificates.StoreLocation location, System.Action configureOptions) { throw null; } public static IClientBuilder UseTls(this IClientBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.Action configureOptions) { throw null; } public static IClientBuilder UseTls(this IClientBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { throw null; } public static ISiloBuilder UseTls(this ISiloBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseTls(this ISiloBuilder builder, System.Security.Cryptography.X509Certificates.StoreName storeName, string subject, bool allowInvalid, System.Security.Cryptography.X509Certificates.StoreLocation location, System.Action configureOptions) { throw null; } public static ISiloBuilder UseTls(this ISiloBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.Action configureOptions) { throw null; } public static ISiloBuilder UseTls(this ISiloBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { throw null; } } } ================================================ FILE: src/api/Orleans.Core/Orleans.Core.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.Extensions.Hosting { public static partial class OrleansClientGenericHostExtensions { public static DependencyInjection.IServiceCollection AddOrleansClient(this DependencyInjection.IServiceCollection services, Configuration.IConfiguration configuration, System.Action configureDelegate) { throw null; } public static DependencyInjection.IServiceCollection AddOrleansClient(this DependencyInjection.IServiceCollection services, System.Action configureDelegate) { throw null; } public static HostApplicationBuilder UseOrleansClient(this HostApplicationBuilder hostAppBuilder, System.Action configureDelegate) { throw null; } public static HostApplicationBuilder UseOrleansClient(this HostApplicationBuilder hostAppBuilder) { throw null; } public static IHostApplicationBuilder UseOrleansClient(this IHostApplicationBuilder hostAppBuilder, System.Action configureDelegate) { throw null; } public static IHostApplicationBuilder UseOrleansClient(this IHostApplicationBuilder hostAppBuilder) { throw null; } public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder) { throw null; } } } namespace Orleans { public partial class AsyncSerialExecutor { public System.Threading.Tasks.Task AddNext(System.Func func) { throw null; } } public partial class AsyncSerialExecutor { public System.Threading.Tasks.Task AddNext(System.Func> func) { throw null; } } public abstract partial class BatchWorker { protected System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } public bool IsIdle() { throw null; } public void Notify() { } public void Notify(System.DateTime utcTime) { } public System.Threading.Tasks.Task NotifyAndWaitForWorkToBeServiced() { throw null; } public System.Threading.Tasks.Task WaitForCurrentWorkToBeServiced() { throw null; } protected abstract System.Threading.Tasks.Task Work(); } public partial class BatchWorkerFromDelegate : BatchWorker { public BatchWorkerFromDelegate(System.Func work, System.Threading.CancellationToken cancellationToken = default) { } protected override System.Threading.Tasks.Task Work() { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public partial class CollectionAgeLimitAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public static readonly System.TimeSpan MinAgeLimit; public CollectionAgeLimitAttribute() { } public CollectionAgeLimitAttribute(string inactivityPeriod) { } public System.TimeSpan AgeLimit { get { throw null; } } public bool AlwaysActive { get { throw null; } set { } } public double Days { get { throw null; } set { } } public double Hours { get { throw null; } set { } } public double Minutes { get { throw null; } set { } } public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public delegate void ConnectionToClusterLostHandler(object sender, System.EventArgs e); public delegate TInstance Factory(); public delegate TInstance Factory(TParam1 param1); public delegate TInstance Factory(TParam1 param1, TParam2 param2); public delegate TInstance Factory(TParam1 param1, TParam2 param2, TParam3 param3); public partial class GatewayCountChangedEventArgs : System.EventArgs { public GatewayCountChangedEventArgs(int currentNumberOfConnectedGateways, int previousNumberOfConnectedGateways) { } public bool ConnectionRecovered { get { throw null; } } public int NumberOfConnectedGateways { get { throw null; } } public int PreviousNumberOfConnectedGateways { get { throw null; } } } public delegate void GatewayCountChangedHandler(object sender, GatewayCountChangedEventArgs e); public partial class GrainInterfaceTypeToGrainTypeResolver { public GrainInterfaceTypeToGrainTypeResolver(Runtime.IClusterManifestProvider clusterManifestProvider) { } public Runtime.GrainType GetGrainType(Runtime.GrainInterfaceType interfaceType, string prefix) { throw null; } public Runtime.GrainType GetGrainType(Runtime.GrainInterfaceType interfaceType) { throw null; } public bool TryGetGrainType(Runtime.GrainInterfaceType interfaceType, out Runtime.GrainType result) { throw null; } } [GenerateSerializer] public sealed partial class GrainState : IGrainState { public GrainState() { } public GrainState(T state, string eTag) { } public GrainState(T state) { } [Id(1)] public string ETag { get { throw null; } set { } } [Id(2)] public bool RecordExists { get { throw null; } set { } } [Id(0)] public T State { get { throw null; } set { } } } public partial interface IClientConnectionRetryFilter { System.Threading.Tasks.Task ShouldRetryConnectionAttempt(System.Exception exception, System.Threading.CancellationToken cancellationToken); } public partial interface IClusterClient : IGrainFactory { System.IServiceProvider ServiceProvider { get; } } public partial interface IClusterClientLifecycle : ILifecycleObservable { } public partial interface IClusterConnectionStatusObserver { void NotifyClusterConnectionLost(); void NotifyGatewayCountChanged(int currentNumberOfGateways, int previousNumberOfGateways, bool connectionRecovered); } public partial interface IGrainState { string ETag { get; set; } bool RecordExists { get; set; } T State { get; set; } } public partial interface IMembershipTable { System.Threading.Tasks.Task CleanupDefunctSiloEntries(System.DateTimeOffset beforeDate); System.Threading.Tasks.Task DeleteMembershipTableEntries(string clusterId); System.Threading.Tasks.Task InitializeMembershipTable(bool tryInitTableVersion); System.Threading.Tasks.Task InsertRow(MembershipEntry entry, TableVersion tableVersion); System.Threading.Tasks.Task ReadAll(); System.Threading.Tasks.Task ReadRow(Runtime.SiloAddress key); System.Threading.Tasks.Task UpdateIAmAlive(MembershipEntry entry); System.Threading.Tasks.Task UpdateRow(MembershipEntry entry, string etag, TableVersion tableVersion); } [Concurrency.Unordered] public partial interface IMembershipTableSystemTarget : IMembershipTable, ISystemTarget, Runtime.IAddressable { } public partial interface IOptionFormatter { string Name { get; } System.Collections.Generic.IEnumerable Format(); } public partial interface IOptionFormatterResolver { IOptionFormatter Resolve(string name); } public partial interface IOptionFormatter : IOptionFormatter { } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public partial class KeepAliveAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public abstract partial class LifecycleSubject : ILifecycleSubject, ILifecycleObservable, ILifecycleObserver { protected readonly Microsoft.Extensions.Logging.ILogger Logger; protected LifecycleSubject(Microsoft.Extensions.Logging.ILogger logger) { } protected virtual System.Threading.Tasks.Task CallObserverStopAsync(ILifecycleObserver observer, System.Threading.CancellationToken cancellationToken) { throw null; } protected virtual string GetStageName(int stage) { throw null; } protected static System.Collections.Immutable.ImmutableDictionary GetStageNames(System.Type type) { throw null; } public virtual System.Threading.Tasks.Task OnStart(System.Threading.CancellationToken cancellationToken = default) { throw null; } protected virtual void OnStartStageCompleted(int stage) { } public virtual System.Threading.Tasks.Task OnStop(System.Threading.CancellationToken cancellationToken = default) { throw null; } protected virtual void OnStopStageCompleted(int stage) { } protected virtual void PerfMeasureOnStart(int stage, System.TimeSpan elapsed) { } protected virtual void PerfMeasureOnStop(int stage, System.TimeSpan elapsed) { } public virtual System.IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer) { throw null; } } [GenerateSerializer] public sealed partial class MembershipEntry { [Id(8)] public int FaultZone { get { throw null; } set { } } [Id(4)] public string HostName { get { throw null; } set { } } [Id(10)] public System.DateTime IAmAliveTime { get { throw null; } set { } } [Id(3)] public int ProxyPort { get { throw null; } set { } } [Id(6)] public string RoleName { get { throw null; } set { } } [Id(0)] public Runtime.SiloAddress SiloAddress { get { throw null; } set { } } [Id(5)] public string SiloName { get { throw null; } set { } } [Id(9)] public System.DateTime StartTime { get { throw null; } set { } } [Id(1)] public Runtime.SiloStatus Status { get { throw null; } set { } } [Id(2)] public System.Collections.Generic.List> SuspectTimes { get { throw null; } set { } } [Id(7)] public int UpdateZone { get { throw null; } set { } } public void AddOrUpdateSuspector(Runtime.SiloAddress localSilo, System.DateTime voteTime, int maxVotes) { } public void AddSuspector(Runtime.SiloAddress suspectingSilo, System.DateTime suspectingTime) { } public string ToFullString() { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class MembershipTableData { public MembershipTableData(TableVersion version) { } public MembershipTableData(System.Collections.Generic.List> list, TableVersion version) { } public MembershipTableData(System.Tuple tuple, TableVersion version) { } [Id(0)] public System.Collections.Generic.IReadOnlyList> Members { get { throw null; } } [Id(1)] public TableVersion Version { get { throw null; } } public override string ToString() { throw null; } public System.Tuple TryGet(Runtime.SiloAddress silo) { throw null; } public MembershipTableData WithoutDuplicateDeads() { throw null; } } public static partial class NamedOptionExtensions { public static TOption GetOptionsByName(this System.IServiceProvider services, string name) where TOption : class, new() { throw null; } } public static partial class OptionFormattingUtilities { public static string Format(object key, object value, string formatting = null) { throw null; } public static string Name(string name = null, string formatting = null) { throw null; } } public abstract partial class OptionsLogger { protected OptionsLogger(Microsoft.Extensions.Logging.ILogger logger, System.IServiceProvider services) { } public void LogOption(IOptionFormatter formatter) { } public void LogOptions() { } public void LogOptions(System.Collections.Generic.IEnumerable formatters) { } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)] public partial class RedactAttribute : System.Attribute { public virtual string Redact(object value) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)] public partial class RedactConnectionStringAttribute : RedactAttribute { public override string Redact(object value) { throw null; } } public partial class SerializerConfigurationValidator : IConfigurationValidator { public SerializerConfigurationValidator(Serialization.Serializers.ICodecProvider codecProvider, Microsoft.Extensions.Options.IOptions options, System.IServiceProvider serviceProvider) { } void IConfigurationValidator.ValidateConfiguration() { } } public static partial class ServiceLifecycleStage { public const int Active = 20000; public const int AfterRuntimeGrainServices = 8100; public const int ApplicationServices = 10000; public const int BecomeActive = 19999; public const int First = int.MinValue; public const int Last = int.MaxValue; public const int RuntimeGrainServices = 8000; public const int RuntimeInitialize = 2000; public const int RuntimeServices = 4000; public const int RuntimeStorageServices = 6000; } [GenerateSerializer] [Immutable] public sealed partial class TableVersion : System.ISpanFormattable, System.IFormattable, System.IEquatable { public TableVersion(int version, string eTag) { } [Id(0)] public int Version { get { throw null; } } [Id(1)] public string VersionEtag { get { throw null; } } public bool Equals(TableVersion other) { throw null; } public override bool Equals(object obj) { throw null; } public override int GetHashCode() { throw null; } public TableVersion Next() { throw null; } public static bool operator ==(TableVersion left, TableVersion right) { throw null; } public static bool operator !=(TableVersion left, TableVersion right) { throw null; } string System.IFormattable.ToString(string format, System.IFormatProvider formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider provider) { throw null; } public override string ToString() { throw null; } } } namespace Orleans.Configuration { public partial class ClientConnectionOptions { public void ConfigureConnection(System.Action configure) { } } public partial class ClientMessagingOptions : MessagingOptions { public const int DEFAULT_CLIENT_SENDER_BUCKETS = 8192; public const System.Net.Sockets.AddressFamily DEFAULT_PREFERRED_FAMILY = 2; public int ClientSenderBuckets { get { throw null; } set { } } public System.Net.IPAddress LocalAddress { get { throw null; } set { } } public string NetworkInterfaceName { get { throw null; } set { } } public System.Net.Sockets.AddressFamily PreferredFamily { get { throw null; } set { } } } public partial class ClusterMembershipOptions { public System.TimeSpan DeathVoteExpirationTimeout { get { throw null; } set { } } public System.TimeSpan? DefunctSiloCleanupPeriod { get { throw null; } set { } } public System.TimeSpan DefunctSiloExpiration { get { throw null; } set { } } public bool EnableIndirectProbes { get { throw null; } set { } } public bool EvictWhenMaxJoinAttemptTimeExceeded { get { throw null; } set { } } public bool ExtendProbeTimeoutDuringDegradation { get { throw null; } set { } } public System.TimeSpan IAmAliveTablePublishTimeout { get { throw null; } set { } } public bool LivenessEnabled { get { throw null; } set { } } public System.TimeSpan LocalHealthDegradationMonitoringPeriod { get { throw null; } set { } } public System.TimeSpan MaxJoinAttemptTime { get { throw null; } set { } } public int NumMissedProbesLimit { get { throw null; } set { } } public int NumMissedTableIAmAliveLimit { get { throw null; } set { } } public int NumProbedSilos { get { throw null; } set { } } public int NumVotesForDeathDeclaration { get { throw null; } set { } } public System.TimeSpan ProbeTimeout { get { throw null; } set { } } public System.TimeSpan TableRefreshTimeout { get { throw null; } set { } } public bool UseLivenessGossip { get { throw null; } set { } } } public partial class ClusterOptions { public const string DefaultClusterId = "default"; public const string DefaultServiceId = "default"; public string ClusterId { get { throw null; } set { } } public string ServiceId { get { throw null; } set { } } } public partial class ClusterOptionsValidator : IConfigurationValidator { public ClusterOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } public partial class ConnectionOptions { public static readonly System.TimeSpan DEFAULT_OPENCONNECTION_TIMEOUT; public System.TimeSpan ConnectionRetryDelay { get { throw null; } set { } } public int ConnectionsPerEndpoint { get { throw null; } set { } } public System.TimeSpan OpenConnectionTimeout { get { throw null; } set { } } public Runtime.Messaging.NetworkProtocolVersion ProtocolVersion { get { throw null; } set { } } } public partial class GatewayOptions { public const int DEFAULT_PREFERED_GATEWAY_INDEX = -1; public System.TimeSpan GatewayListRefreshPeriod { get { throw null; } set { } } public int PreferredGatewayIndex { get { throw null; } set { } } } public partial class GrainTypeOptions { public System.Collections.Generic.HashSet Classes { get { throw null; } } public System.Collections.Generic.HashSet Interfaces { get { throw null; } } } public sealed partial class GrainTypeOptionsValidator : IConfigurationValidator { public GrainTypeOptionsValidator(Microsoft.Extensions.Options.IOptions options, System.IServiceProvider serviceProvider) { } public void ValidateConfiguration() { } } public partial class GrainVersioningOptions { public string DefaultCompatibilityStrategy { get { throw null; } set { } } public string DefaultVersionSelectorStrategy { get { throw null; } set { } } } public partial class LoadSheddingOptions { public int CpuThreshold { get { throw null; } set { } } public bool LoadSheddingEnabled { get { throw null; } set { } } [System.Obsolete("Use CpuThreshold instead.", true)] public int LoadSheddingLimit { get { throw null; } set { } } public int MemoryThreshold { get { throw null; } set { } } } public abstract partial class MessagingOptions { public bool CancelRequestOnTimeout { get { throw null; } set { } } public bool DropExpiredMessages { get { throw null; } set { } } public int MaxMessageBodySize { get { throw null; } set { } } public int MaxMessageHeaderSize { get { throw null; } set { } } public System.TimeSpan ResponseTimeout { get { throw null; } set { } } public System.TimeSpan ResponseTimeoutWithDebugger { get { throw null; } set { } } public bool WaitForCancellationAcknowledgement { get { throw null; } set { } } } public static partial class OptionConfigureExtensionMethods { public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureFormatter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class, new() { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureFormatter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class where TOptionFormatter : class, IOptionFormatter { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureFormatterResolver(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class where TOptionFormatterResolver : class, IOptionFormatterResolver { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureNamedOptionForLogging(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name) where TOptions : class { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection TryConfigureFormatter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class where TOptionFormatter : class, IOptionFormatter { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection TryConfigureFormatterResolver(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class where TOptionFormatterResolver : class, IOptionFormatterResolver { throw null; } } public partial class StaticGatewayListProviderOptions { public System.Collections.Generic.List Gateways { get { throw null; } set { } } } public partial class TypeManagementOptions { public static readonly System.TimeSpan DEFAULT_REFRESH_CLUSTER_INTERFACEMAP_TIME; public System.TimeSpan TypeMapRefreshInterval { get { throw null; } set { } } } } namespace Orleans.Configuration.Internal { public static partial class ServiceCollectionExtensions { public static void AddFromExisting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type service, System.Type implementation) { } public static void AddFromExisting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TImplementation : TService { } public static void TryAddFromExisting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TImplementation : TService { } } } namespace Orleans.Configuration.Overrides { public static partial class OptionsOverrides { public static Microsoft.Extensions.Options.IOptions GetProviderClusterOptions(this System.IServiceProvider services, string providerName) { throw null; } } } namespace Orleans.GrainDirectory { public partial interface IGrainLocator { void InvalidateCache(Runtime.GrainAddress address); void InvalidateCache(Runtime.GrainId grainId); System.Threading.Tasks.ValueTask Lookup(Runtime.GrainId grainId); System.Threading.Tasks.Task Register(Runtime.GrainAddress address, Runtime.GrainAddress? previousRegistration); bool TryLookupInCache(Runtime.GrainId grainId, out Runtime.GrainAddress? address); System.Threading.Tasks.Task Unregister(Runtime.GrainAddress address, UnregistrationCause cause); void UpdateCache(Runtime.GrainId grainId, Runtime.SiloAddress siloAddress); } public enum UnregistrationCause : byte { Force = 0, NonexistentActivation = 1 } } namespace Orleans.GrainReferences { public sealed partial class GrainReferenceActivator { public GrainReferenceActivator(System.IServiceProvider serviceProvider, System.Collections.Generic.IEnumerable providers) { } public Runtime.GrainReference CreateReference(Runtime.GrainId grainId, Runtime.GrainInterfaceType interfaceType) { throw null; } } public partial interface IGrainReferenceActivator { Runtime.GrainReference CreateReference(Runtime.GrainId grainId); } public partial interface IGrainReferenceActivatorProvider { bool TryGet(Runtime.GrainType grainType, Runtime.GrainInterfaceType interfaceType, out IGrainReferenceActivator activator); } } namespace Orleans.Hosting { public partial class ClientBuilder : IClientBuilder { public ClientBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration configuration) { } public Microsoft.Extensions.Configuration.IConfiguration Configuration { get { throw null; } } public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get { throw null; } } } public static partial class ClientBuilderExtensions { public static IClientBuilder AddActivityPropagation(this IClientBuilder builder) { throw null; } public static IClientBuilder AddClusterConnectionLostHandler(this IClientBuilder builder, ConnectionToClusterLostHandler handler) { throw null; } public static IClientBuilder AddClusterConnectionLostHandler(this IClientBuilder builder, System.Func handlerFactory) { throw null; } public static IClientBuilder AddClusterConnectionStatusObserver(this IClientBuilder builder, TObserver observer) where TObserver : IClusterConnectionStatusObserver { throw null; } public static IClientBuilder AddClusterConnectionStatusObserver(this IClientBuilder builder) where TObserver : class, IClusterConnectionStatusObserver { throw null; } public static IClientBuilder AddGatewayCountChangedHandler(this IClientBuilder builder, GatewayCountChangedHandler handler) { throw null; } public static IClientBuilder AddGatewayCountChangedHandler(this IClientBuilder builder, System.Func handlerFactory) { throw null; } public static IClientBuilder Configure(this IClientBuilder builder, Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class { throw null; } public static IClientBuilder Configure(this IClientBuilder builder, System.Action configureOptions) where TOptions : class { throw null; } public static IClientBuilder ConfigureServices(this IClientBuilder builder, System.Action configureDelegate) { throw null; } public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder, IClientConnectionRetryFilter connectionRetryFilter) { throw null; } public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder, System.Func> connectionRetryFilter) { throw null; } public static IClientBuilder UseConnectionRetryFilter(this IClientBuilder builder) where TConnectionRetryFilter : class, IClientConnectionRetryFilter { throw null; } public static IClientBuilder UseLocalhostClustering(this IClientBuilder builder, int gatewayPort = 30000, string serviceId = "dev", string clusterId = "dev") { throw null; } public static IClientBuilder UseLocalhostClustering(this IClientBuilder builder, int[] gatewayPorts, string serviceId = "dev", string clusterId = "dev") { throw null; } public static IClientBuilder UseStaticClustering(this IClientBuilder builder, System.Action> configureOptions) { throw null; } public static IClientBuilder UseStaticClustering(this IClientBuilder builder, System.Action configureOptions) { throw null; } public static IClientBuilder UseStaticClustering(this IClientBuilder builder, params System.Net.IPEndPoint[] endpoints) { throw null; } } public static partial class ClientBuilderGrainCallFilterExtensions { public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder, IIncomingGrainCallFilter filter) { throw null; } public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder, IncomingGrainCallFilterDelegate filter) { throw null; } public static IClientBuilder AddIncomingGrainCallFilter(this IClientBuilder builder) where TImplementation : class, IIncomingGrainCallFilter { throw null; } public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder, IOutgoingGrainCallFilter filter) { throw null; } public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder, OutgoingGrainCallFilterDelegate filter) { throw null; } public static IClientBuilder AddOutgoingGrainCallFilter(this IClientBuilder builder) where TImplementation : class, IOutgoingGrainCallFilter { throw null; } } public static partial class GrainCallFilterServiceCollectionExtensions { [System.Obsolete("Use ISiloBuilder.AddIncomingGrainCallFilter", true)] public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainCallFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, GrainCallFilterDelegate filter) { throw null; } [System.Obsolete("Use ISiloBuilder.AddIncomingGrainCallFilter", true)] public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainCallFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, IIncomingGrainCallFilter filter) { throw null; } [System.Obsolete("Use ISiloBuilder.AddIncomingGrainCallFilter", true)] public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainCallFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TImplementation : class, IIncomingGrainCallFilter { throw null; } } public partial interface IClientBuilder { Microsoft.Extensions.Configuration.IConfiguration Configuration { get; } Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } } public partial interface INamedServiceConfigurator { System.Action> ConfigureDelegate { get; } string Name { get; } } public partial class NamedServiceConfigurator : INamedServiceConfigurator { public NamedServiceConfigurator(string name, System.Action> configureDelegate) { } public System.Action> ConfigureDelegate { get { throw null; } } public string Name { get { throw null; } } } public static partial class NamedServiceConfiguratorExtensions { public static void Configure(this INamedServiceConfigurator configurator, System.Action> configureOptions) where TOptions : class, new() { } public static void ConfigureComponent(this INamedServiceConfigurator configurator, System.Func factory) where TComponent : class { } public static void ConfigureComponent(this INamedServiceConfigurator configurator, System.Func factory, System.Action> configureOptions = null) where TOptions : class, new() where TComponent : class { } public static void ConfigureLifecycle(this INamedServiceConfigurator configurator) where T : ILifecycleSubject { } } } namespace Orleans.Internal { public static partial class AsyncExecutorWithRetries { public static readonly int INFINITE_RETRIES; public static System.Threading.Tasks.Task ExecuteWithRetries(System.Func action, int maxNumErrorTries, System.Func retryExceptionFilter, System.TimeSpan maxExecutionTime, IBackoffProvider onErrorBackOff) { throw null; } public static System.Threading.Tasks.Task ExecuteWithRetries(System.Func> function, int maxNumErrorTries, System.Func retryExceptionFilter, System.TimeSpan maxExecutionTime, IBackoffProvider onErrorBackOff, System.Threading.CancellationToken cancellationToken = default) { throw null; } public static System.Threading.Tasks.Task ExecuteWithRetries(System.Func> function, int maxNumSuccessTries, int maxNumErrorTries, System.Func retryValueFilter, System.Func retryExceptionFilter, System.TimeSpan maxExecutionTime = default, IBackoffProvider onSuccessBackOff = null, IBackoffProvider onErrorBackOff = null, System.Threading.CancellationToken cancellationToken = default) { throw null; } } public partial class FixedBackoff : IBackoffProvider { public FixedBackoff(System.TimeSpan delay) { } public System.TimeSpan Next(int attempt) { throw null; } } public partial interface IBackoffProvider { System.TimeSpan Next(int attempt); } } namespace Orleans.LeaseProviders { [GenerateSerializer] [Immutable] public sealed partial class AcquiredLease { public AcquiredLease(string resourceKey, System.TimeSpan duration, string token, System.DateTime startTimeUtc) { } public AcquiredLease(string resourceKey) { } [Id(1)] public System.TimeSpan Duration { get { throw null; } } [Id(0)] public string ResourceKey { get { throw null; } } [Id(3)] public System.DateTime StartTimeUtc { get { throw null; } } [Id(2)] public string Token { get { throw null; } } } [GenerateSerializer] [Immutable] public sealed partial class AcquireLeaseResult { public AcquireLeaseResult(AcquiredLease acquiredLease, ResponseCode statusCode, System.Exception failureException) { } [Id(0)] public AcquiredLease AcquiredLease { get { throw null; } } [Id(2)] public System.Exception FailureException { get { throw null; } } [Id(1)] public ResponseCode StatusCode { get { throw null; } } } public partial interface ILeaseProvider { System.Threading.Tasks.Task Acquire(string category, LeaseRequest[] leaseRequests); System.Threading.Tasks.Task Release(string category, AcquiredLease[] aquiredLeases); System.Threading.Tasks.Task Renew(string category, AcquiredLease[] aquiredLeases); } [GenerateSerializer] [Immutable] public sealed partial class LeaseRequest { public LeaseRequest(string resourceKey, System.TimeSpan duration) { } [Id(1)] public System.TimeSpan Duration { get { throw null; } } [Id(0)] public string ResourceKey { get { throw null; } } } [GenerateSerializer] public enum ResponseCode { OK = 0, LeaseNotAvailable = 1, InvalidToken = 2, TransientFailure = 3 } } namespace Orleans.Messaging { public partial interface IGatewayListProvider { [System.Obsolete("This attribute is no longer used and all providers are considered updatable")] bool IsUpdatable { get; } System.TimeSpan MaxStaleness { get; } System.Threading.Tasks.Task> GetGateways(); System.Threading.Tasks.Task InitializeGatewayListProvider(); } public partial class StaticGatewayListProvider : IGatewayListProvider { public StaticGatewayListProvider(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Options.IOptions gatewayOptions) { } public bool IsUpdatable { get { throw null; } } public System.TimeSpan MaxStaleness { get { throw null; } } public System.Threading.Tasks.Task> GetGateways() { throw null; } public System.Threading.Tasks.Task InitializeGatewayListProvider() { throw null; } } } namespace Orleans.Metadata { public partial class GrainBindings { public GrainBindings(Runtime.GrainType grainType, System.Collections.Immutable.ImmutableArray> bindings) { } public System.Collections.Immutable.ImmutableArray> Bindings { get { throw null; } } public Runtime.GrainType GrainType { get { throw null; } } } public partial class GrainBindingsResolver { public GrainBindingsResolver(Runtime.IClusterManifestProvider clusterManifestProvider) { } public (MajorMinorVersion Version, System.Collections.Immutable.ImmutableDictionary Bindings) GetAllBindings() { throw null; } public GrainBindings GetBindings(Runtime.GrainType grainType) { throw null; } } public partial class GrainInterfaceTypeResolver { public GrainInterfaceTypeResolver(System.Collections.Generic.IEnumerable providers, Serialization.TypeSystem.TypeConverter typeConverter) { } public Runtime.GrainInterfaceType GetGrainInterfaceType(System.Type type) { throw null; } public Runtime.GrainInterfaceType GetGrainInterfaceTypeByConvention(System.Type type) { throw null; } } public partial class GrainPropertiesResolver { public GrainPropertiesResolver(Runtime.IClusterManifestProvider clusterManifestProvider) { } public GrainProperties GetGrainProperties(Runtime.GrainType grainType) { throw null; } public bool TryGetGrainProperties(Runtime.GrainType grainType, out GrainProperties properties) { throw null; } } public partial class GrainTypeResolver { public GrainTypeResolver(System.Collections.Generic.IEnumerable resolvers, Serialization.TypeSystem.TypeConverter argumentFormatter) { } public Runtime.GrainType GetGrainType(System.Type type) { throw null; } } } namespace Orleans.Networking.Shared { [GenerateSerializer] public sealed partial class SocketConnectionException : Runtime.OrleansException { [System.Obsolete] public SocketConnectionException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public SocketConnectionException(string message, System.Exception innerException) { } public SocketConnectionException(string message) { } } public partial class SocketConnectionOptions { public int IOQueueCount { get { throw null; } set { } } public bool KeepAlive { get { throw null; } set { } } public int KeepAliveIntervalSeconds { get { throw null; } set { } } public int KeepAliveRetryCount { get { throw null; } set { } } public int KeepAliveTimeSeconds { get { throw null; } set { } } public bool NoDelay { get { throw null; } set { } } } } namespace Orleans.Placement { public partial interface IPlacementFilterDirector { System.Collections.Generic.IEnumerable Filter(PlacementFilterStrategy filterStrategy, Runtime.Placement.PlacementTarget target, System.Collections.Generic.IEnumerable silos); } public static partial class PlacementFilterExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddPlacementFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceLifetime strategyLifetime) where TFilter : PlacementFilterStrategy, new() where TDirector : class, IPlacementFilterDirector { throw null; } } } namespace Orleans.Placement.Rebalancing { public partial interface IActivationRebalancer { System.Threading.Tasks.ValueTask GetRebalancingReport(bool force = false); System.Threading.Tasks.Task ResumeRebalancing(); void SubscribeToReports(IActivationRebalancerReportListener listener); System.Threading.Tasks.Task SuspendRebalancing(System.TimeSpan? duration = null); void UnsubscribeFromReports(IActivationRebalancerReportListener listener); } public partial interface IActivationRebalancerReportListener { void OnReport(RebalancingReport report); } public partial interface IFailedSessionBackoffProvider : Internal.IBackoffProvider { } [GenerateSerializer] public enum RebalancerStatus : byte { Executing = 0, Suspended = 1 } [GenerateSerializer] [Immutable] [Alias("RebalancingReport")] public readonly partial struct RebalancingReport { private readonly object _dummy; private readonly int _dummyPrimitive; [Id(3)] public required double ClusterImbalance { get { throw null; } init { } } [Id(0)] public required Runtime.SiloAddress Host { get { throw null; } init { } } [Id(4)] public required System.Collections.Immutable.ImmutableArray Statistics { get { throw null; } init { } } [Id(1)] public required RebalancerStatus Status { get { throw null; } init { } } [Id(2)] public System.TimeSpan? SuspensionDuration { get { throw null; } init { } } } [GenerateSerializer] [Immutable] [Alias("RebalancingStatistics")] public readonly partial struct RebalancingStatistics { private readonly object _dummy; private readonly int _dummyPrimitive; [Id(3)] public required ulong AcquiredActivations { get { throw null; } init { } } [Id(2)] public required ulong DispersedActivations { get { throw null; } init { } } [Id(1)] public required Runtime.SiloAddress SiloAddress { get { throw null; } init { } } [Id(0)] public required System.DateTime TimeStamp { get { throw null; } init { } } } } namespace Orleans.Placement.Repartitioning { [GenerateSerializer] [Immutable] public readonly partial struct CandidateConnectedVertex { private readonly int _dummyPrimitive; public CandidateConnectedVertex(Runtime.GrainId id, long transferScore) { } [Id(0)] public Runtime.GrainId Id { get { throw null; } } [Id(1)] public long TransferScore { get { throw null; } } public readonly bool Equals(CandidateConnectedVertex other) { throw null; } public override readonly bool Equals(object obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(CandidateConnectedVertex left, CandidateConnectedVertex right) { throw null; } public static bool operator !=(CandidateConnectedVertex left, CandidateConnectedVertex right) { throw null; } public override readonly string ToString() { throw null; } } [GenerateSerializer] [Immutable] public readonly partial struct EdgeVertex : System.IEquatable { [Id(0)] public readonly Runtime.GrainId Id; [Id(2)] public readonly bool IsMigratable; [Id(1)] public readonly Runtime.SiloAddress Silo; public EdgeVertex(Runtime.GrainId id, Runtime.SiloAddress silo, bool isMigratable) { } public readonly bool Equals(EdgeVertex other) { throw null; } public override readonly bool Equals(object obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(EdgeVertex left, EdgeVertex right) { throw null; } public static bool operator !=(EdgeVertex left, EdgeVertex right) { throw null; } public override readonly string ToString() { throw null; } } public partial interface IImbalanceToleranceRule { bool IsSatisfiedBy(uint imbalance); } } namespace Orleans.Providers { public partial interface IControllable { System.Threading.Tasks.Task ExecuteCommand(int command, object arg); } public partial interface IProviderRuntime { IGrainFactory GrainFactory { get; } System.IServiceProvider ServiceProvider { get; } (TExtension Extension, TExtensionInterface ExtensionReference) BindExtension(System.Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, Runtime.IGrainExtension; } [GenerateSerializer] public sealed partial class ProviderInitializationException : Runtime.OrleansException { public ProviderInitializationException() { } public ProviderInitializationException(string message, System.Exception innerException) { } public ProviderInitializationException(string message) { } } [GenerateSerializer] public sealed partial class ProviderStateException : Runtime.OrleansException { public ProviderStateException() { } public ProviderStateException(string message, System.Exception innerException) { } public ProviderStateException(string message) { } } } namespace Orleans.Runtime { public static partial class ClientInstruments { } [GenerateSerializer] [Immutable] public partial class ClusterManifestUpdate { public ClusterManifestUpdate(Metadata.MajorMinorVersion manifestVersion, System.Collections.Immutable.ImmutableDictionary siloManifests, bool includesAllActiveServers) { } [Id(2)] public bool IncludesAllActiveServers { get { throw null; } } [Id(1)] public System.Collections.Immutable.ImmutableDictionary SiloManifests { get { throw null; } } [Id(0)] public Metadata.MajorMinorVersion Version { get { throw null; } } } [GenerateSerializer] [Immutable] public sealed partial class DetailedGrainStatistic { [Id(2)] public GrainId GrainId { get { throw null; } init { } } [Id(0)] public string GrainType { get { throw null; } init { } } [Id(1)] public SiloAddress SiloAddress { get { throw null; } init { } } } [Immutable] public readonly partial struct GenericGrainInterfaceType { private readonly int _dummyPrimitive; public int Arity { get { throw null; } } public bool IsConstructed { get { throw null; } } public GrainInterfaceType Value { get { throw null; } } public readonly GenericGrainInterfaceType Construct(Orleans.Serialization.TypeSystem.TypeConverter formatter, params System.Type[] typeArguments) { throw null; } public readonly System.Type[] GetArguments(Orleans.Serialization.TypeSystem.TypeConverter formatter) { throw null; } public readonly GenericGrainInterfaceType GetGenericGrainType() { throw null; } public override readonly string ToString() { throw null; } public static bool TryParse(GrainInterfaceType grainType, out GenericGrainInterfaceType result) { throw null; } } [Immutable] public readonly partial struct GenericGrainType : System.IEquatable { private readonly int _dummyPrimitive; public int Arity { get { throw null; } } public GrainType GrainType { get { throw null; } } public bool IsConstructed { get { throw null; } } public readonly GenericGrainType Construct(Orleans.Serialization.TypeSystem.TypeConverter formatter, params System.Type[] typeArguments) { throw null; } public readonly bool Equals(GenericGrainType other) { throw null; } public override readonly bool Equals(object obj) { throw null; } public readonly System.Type[] GetArguments(Orleans.Serialization.TypeSystem.TypeConverter converter) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly GenericGrainType GetUnconstructedGrainType() { throw null; } public override readonly string ToString() { throw null; } public static bool TryParse(GrainType grainType, out GenericGrainType result) { throw null; } } [GenerateSerializer] [Alias("Orleans.Runtime.GrainCallFrequency")] [Immutable] public partial struct GrainCallFrequency { private object _dummy; private int _dummyPrimitive; [Id(4)] public ulong CallCount { get { throw null; } set { } } [Id(0)] public GrainId SourceGrain { get { throw null; } set { } } [Id(2)] public SiloAddress SourceHost { get { throw null; } set { } } [Id(1)] public GrainId TargetGrain { get { throw null; } set { } } [Id(3)] public SiloAddress TargetHost { get { throw null; } set { } } } public partial interface IClusterManifestProvider { Metadata.ClusterManifest Current { get; } Metadata.GrainManifest LocalGrainManifest { get; } System.Collections.Generic.IAsyncEnumerable Updates { get; } } public partial interface IHealthCheckable { bool CheckHealth(System.DateTime lastCheckTime, out string reason); } public partial interface ILocalSiloDetails { string ClusterId { get; } string DnsHostName { get; } SiloAddress GatewayAddress { get; } string Name { get; } SiloAddress SiloAddress { get; } } public partial interface IManagementGrain : IGrainWithIntegerKey, IGrain, IAddressable, IVersionManager { System.Threading.Tasks.Task ForceActivationCollection(SiloAddress[] hostsIds, System.TimeSpan ageLimit); System.Threading.Tasks.Task ForceActivationCollection(System.TimeSpan ageLimit); System.Threading.Tasks.Task ForceGarbageCollection(SiloAddress[] hostsIds); System.Threading.Tasks.Task ForceRuntimeStatisticsCollection(SiloAddress[] siloAddresses); System.Threading.Tasks.ValueTask GetActivationAddress(IAddressable reference); System.Threading.Tasks.ValueTask> GetActiveGrains(GrainType type); System.Threading.Tasks.Task GetDetailedGrainStatistics(string[] types = null, SiloAddress[] hostsIds = null); System.Threading.Tasks.Task GetDetailedHosts(bool onlyActive = false); System.Threading.Tasks.Task GetGrainActivationCount(GrainReference grainReference); System.Threading.Tasks.Task> GetGrainCallFrequencies(SiloAddress[] hostsIds = null); System.Threading.Tasks.Task> GetHosts(bool onlyActive = false); System.Threading.Tasks.Task GetRuntimeStatistics(SiloAddress[] hostsIds); System.Threading.Tasks.Task GetSimpleGrainStatistics(); System.Threading.Tasks.Task GetSimpleGrainStatistics(SiloAddress[] hostsIds); System.Threading.Tasks.Task GetTotalActivationCount(); System.Threading.Tasks.ValueTask ResetGrainCallFrequencies(SiloAddress[] hostsIds = null); System.Threading.Tasks.Task SendControlCommandToProvider(string providerName, int command, object arg = null) where T : Providers.IControllable; } [GenerateSerializer] [Immutable] public readonly partial struct IndirectProbeResponse { private readonly object _dummy; private readonly int _dummyPrimitive; [Id(3)] public string FailureMessage { get { throw null; } init { } } [Id(0)] public int IntermediaryHealthScore { get { throw null; } init { } } [Id(2)] public System.TimeSpan ProbeResponseTime { get { throw null; } init { } } [Id(1)] public bool Succeeded { get { throw null; } init { } } public override readonly string ToString() { throw null; } } public static partial class Instruments { public static readonly System.Diagnostics.Metrics.Meter Meter; } public partial interface IRingRange { bool InRange(GrainId grainId); bool InRange(uint value); } public partial interface ISingleRange : IRingRange { uint Begin { get; } uint End { get; } } public static partial class LifecycleParticipantExtensions { public static ILifecycleParticipant ParticipateIn(this ILifecycleParticipant participant) where TLifecycle : ILifecycleObservable { throw null; } } public partial class OnDeserializedCallbacks : Orleans.Serialization.DeserializationContext { public OnDeserializedCallbacks(System.IServiceProvider serviceProvider) { } public override object RuntimeClient { get { throw null; } } public override System.IServiceProvider ServiceProvider { get { throw null; } } public void OnDeserialized(Orleans.Serialization.IOnDeserialized value) { } } public static partial class RangeFactory { public const long RING_SIZE = 4294967296L; public static IRingRange CreateFullRange() { throw null; } public static IRingRange CreateRange(System.Collections.Generic.List inRanges) { throw null; } public static IRingRange CreateRange(uint begin, uint end) { throw null; } public static System.Collections.Generic.IEnumerable GetSubRanges(IRingRange range) { throw null; } } public static partial class RequestContextExtensions { public static System.Collections.Generic.Dictionary? Export(Orleans.Serialization.DeepCopier copier) { throw null; } public static void Import(System.Collections.Generic.Dictionary? contextData) { } } public static partial class SiloRuntimeMetricsListener { public static long ConnectedClientCount { get { throw null; } } public static long MessageReceivedTotal { get { throw null; } } public static long MessageSentTotal { get { throw null; } } } [GenerateSerializer] [Immutable] public sealed partial class SiloRuntimeStatistics { internal SiloRuntimeStatistics() { } [Id(0)] public int ActivationCount { get { throw null; } } [Id(3)] [System.Obsolete("The will be removed, use EnvironmentStatistics.FilteredAvailableMemoryBytes instead.", false)] public float? AvailableMemory { get { throw null; } } [Id(7)] public long ClientCount { get { throw null; } } [Id(2)] [System.Obsolete("The will be removed, use EnvironmentStatistics.FilteredCpuUsagePercentage instead.", false)] public float? CpuUsage { get { throw null; } } [Id(10)] public System.DateTime DateTime { get { throw null; } } [Id(11)] public Statistics.EnvironmentStatistics EnvironmentStatistics { get { throw null; } } [Id(6)] public bool IsOverloaded { get { throw null; } } [Id(4)] [System.Obsolete("The will be removed, use EnvironmentStatistics.FilteredMemoryUsageBytes instead.", false)] public long? MemoryUsage { get { throw null; } } [Id(8)] public long ReceivedMessages { get { throw null; } } [Id(1)] public int RecentlyUsedActivationCount { get { throw null; } } [Id(9)] public long SentMessages { get { throw null; } } [Id(5)] [System.Obsolete("The will be removed, use EnvironmentStatistics.MaximumAvailableMemoryBytes instead.", false)] public long? TotalPhysicalMemory { get { throw null; } } public override string ToString() { throw null; } } [GenerateSerializer] public enum SiloStatus { None = 0, Created = 1, Joining = 2, Active = 3, ShuttingDown = 4, Stopping = 5, Dead = 6 } public static partial class SiloStatusExtensions { public static bool IsTerminating(this SiloStatus siloStatus) { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class SimpleGrainStatistic { [Id(2)] public int ActivationCount { get { throw null; } init { } } [Id(0)] public string GrainType { get { throw null; } init { } } [Id(1)] public SiloAddress SiloAddress { get { throw null; } init { } } public override string ToString() { throw null; } } } namespace Orleans.Runtime.Configuration { public static partial class ConfigUtilities { public static string RedactConnectionStringInfo(string connectionString) { throw null; } } } namespace Orleans.Runtime.Internal { public ref partial struct ExecutionContextSuppressor { private int _dummyPrimitive; public readonly void Dispose() { } } } namespace Orleans.Runtime.Messaging { public partial class ConnectionFailedException : OrleansException { public ConnectionFailedException() { } [System.Obsolete] protected ConnectionFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public ConnectionFailedException(string message, System.Exception innerException) { } public ConnectionFailedException(string message) { } } [GenerateSerializer] public sealed partial class InvalidMessageFrameException : OrleansException { public InvalidMessageFrameException() { } [System.Obsolete] protected InvalidMessageFrameException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public InvalidMessageFrameException(string message, System.Exception innerException) { } public InvalidMessageFrameException(string message) { } } public enum NetworkProtocolVersion : byte { Version1 = 1 } } namespace Orleans.Runtime.Placement { public partial interface IPlacementContext { SiloAddress LocalSilo { get; } SiloStatus LocalSiloStatus { get; } SiloAddress[] GetCompatibleSilos(PlacementTarget target); System.Collections.Generic.IReadOnlyDictionary GetCompatibleSilosWithVersions(PlacementTarget target); } public partial interface IPlacementDirector { string PlacementHintKey { get; set; } SiloAddress GetPlacementHint(System.Collections.Generic.Dictionary requestContextData, SiloAddress[] compatibleSilos); System.Threading.Tasks.Task OnAddActivation(PlacementStrategy strategy, PlacementTarget target, IPlacementContext context); } public readonly partial struct PlacementTarget { private readonly object _dummy; private readonly int _dummyPrimitive; public PlacementTarget(GrainId grainIdentity, System.Collections.Generic.Dictionary requestContextData, GrainInterfaceType interfaceType, ushort interfaceVersion) { } public GrainId GrainIdentity { get { throw null; } } public GrainInterfaceType InterfaceType { get { throw null; } } public ushort InterfaceVersion { get { throw null; } } public System.Collections.Generic.Dictionary RequestContextData { get { throw null; } } } } namespace Orleans.Serialization { public partial class ActivationIdConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class ConfigureOrleansJsonSerializerOptions : Microsoft.Extensions.Options.IPostConfigureOptions { public ConfigureOrleansJsonSerializerOptions(System.IServiceProvider serviceProvider) { } public void PostConfigure(string name, OrleansJsonSerializerOptions options) { } } public partial class GrainIdConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class GrainReferenceJsonConverter : Newtonsoft.Json.JsonConverter { public GrainReferenceJsonConverter(GrainReferences.GrainReferenceActivator referenceActivator) { } public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class IPAddressConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class IPEndPointConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class MembershipVersionJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class OrleansJsonSerializationBinder : Newtonsoft.Json.Serialization.DefaultSerializationBinder { public OrleansJsonSerializationBinder(TypeSystem.TypeResolver typeResolver) { } public override System.Type BindToType(string assemblyName, string typeName) { throw null; } } public partial class OrleansJsonSerializer { public const string IndentJsonProperty = "IndentJSON"; public const string TypeNameHandlingProperty = "TypeNameHandling"; public const string UseFullAssemblyNamesProperty = "UseFullAssemblyNames"; public OrleansJsonSerializer(Microsoft.Extensions.Options.IOptions options) { } public object Deserialize(System.Type expectedType, string input) { throw null; } public string Serialize(object item, System.Type expectedType) { throw null; } } public partial class OrleansJsonSerializerOptions { public Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { throw null; } set { } } } public static partial class OrleansJsonSerializerSettings { public static Newtonsoft.Json.JsonSerializerSettings GetDefaultSerializerSettings(System.IServiceProvider services) { throw null; } public static Newtonsoft.Json.JsonSerializerSettings UpdateSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings, bool useFullAssemblyNames, bool indentJson, Newtonsoft.Json.TypeNameHandling? typeNameHandling) { throw null; } } public partial class SiloAddressJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } public partial class UniqueKeyConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(System.Type objectType) { throw null; } public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw null; } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { } } } namespace Orleans.Storage { [GenerateSerializer] public sealed partial class BadProviderConfigException : Runtime.OrleansException { public BadProviderConfigException() { } public BadProviderConfigException(string msg, System.Exception exc) { } public BadProviderConfigException(string msg) { } } public partial class DefaultStorageProviderSerializerOptionsConfigurator : Microsoft.Extensions.Options.IPostConfigureOptions where TOptions : class, IStorageProviderSerializerOptions { public DefaultStorageProviderSerializerOptionsConfigurator(System.IServiceProvider serviceProvider) { } public void PostConfigure(string name, TOptions options) { } } public static partial class GrainStorageHelpers { public static IGrainStorage GetGrainStorage(System.Type grainType, System.IServiceProvider services) { throw null; } } public partial class GrainStorageSerializer : IGrainStorageSerializer { public GrainStorageSerializer(IGrainStorageSerializer serializer, IGrainStorageSerializer fallbackDeserializer) { } public T Deserialize(System.BinaryData input) { throw null; } public System.BinaryData Serialize(T input) { throw null; } } public static partial class GrainStorageSerializerExtensions { public static T Deserialize(this IGrainStorageSerializer serializer, System.ReadOnlyMemory input) { throw null; } } public partial interface IGrainStorage { System.Threading.Tasks.Task ClearStateAsync(string stateName, Runtime.GrainId grainId, IGrainState grainState); System.Threading.Tasks.Task ReadStateAsync(string stateName, Runtime.GrainId grainId, IGrainState grainState); System.Threading.Tasks.Task WriteStateAsync(string stateName, Runtime.GrainId grainId, IGrainState grainState); } public partial interface IGrainStorageSerializer { T Deserialize(System.BinaryData input); System.BinaryData Serialize(T input); } public partial interface IMemoryStorageGrain : IGrainWithIntegerKey, IGrain, Runtime.IAddressable { System.Threading.Tasks.Task DeleteStateAsync(string grainStoreKey, string eTag); System.Threading.Tasks.Task> ReadStateAsync(string grainStoreKey); System.Threading.Tasks.Task WriteStateAsync(string grainStoreKey, IGrainState grainState); } [GenerateSerializer] public partial class InconsistentStateException : Runtime.OrleansException { public InconsistentStateException() { } [System.Obsolete] protected InconsistentStateException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public InconsistentStateException(string message, System.Exception innerException) { } public InconsistentStateException(string storedEtag, string currentEtag, System.Exception storageException) { } public InconsistentStateException(string errorMsg, string storedEtag, string currentEtag, System.Exception storageException) { } public InconsistentStateException(string errorMsg, string storedEtag, string currentEtag) { } public InconsistentStateException(string message) { } [Id(2)] public string CurrentEtag { get { throw null; } } [Id(1)] public string StoredEtag { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public override string ToString() { throw null; } } public partial interface IRestExceptionDecoder { bool DecodeException(System.Exception exception, out System.Net.HttpStatusCode httpStatusCode, out string restStatus, bool getExtendedErrors = false); } public partial interface IStorageProviderSerializerOptions { IGrainStorageSerializer GrainStorageSerializer { get; set; } } public partial class JsonGrainStorageSerializer : IGrainStorageSerializer { public JsonGrainStorageSerializer(Serialization.OrleansJsonSerializer orleansJsonSerializer) { } public T Deserialize(System.BinaryData input) { throw null; } public System.BinaryData Serialize(T value) { throw null; } } public partial class OrleansGrainStorageSerializer : IGrainStorageSerializer { public OrleansGrainStorageSerializer(Serialization.Serializer serializer) { } public T Deserialize(System.BinaryData input) { throw null; } public System.BinaryData Serialize(T value) { throw null; } } } namespace Orleans.Timers.Internal { public partial interface ITimerManager { System.Threading.Tasks.Task Delay(System.TimeSpan timeSpan, System.Threading.CancellationToken cancellationToken = default); } } namespace Orleans.Utilities { public partial class ObserverManager : ObserverManager { public ObserverManager(System.TimeSpan expiration, Microsoft.Extensions.Logging.ILogger log) : base(default, default!) { } } public partial class ObserverManager : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public ObserverManager(System.TimeSpan expiration, Microsoft.Extensions.Logging.ILogger log) { } public int Count { get { throw null; } } public System.TimeSpan ExpirationDuration { get { throw null; } set { } } public System.Func GetDateTime { get { throw null; } set { } } public System.Collections.Generic.IReadOnlyDictionary Observers { get { throw null; } } public void Clear() { } public void ClearExpired() { } public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } public void Notify(System.Action notification, System.Func? predicate = null) { } public System.Threading.Tasks.Task Notify(System.Func notification, System.Func? predicate = null) { throw null; } public void Subscribe(TIdentity id, TObserver observer) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public void Unsubscribe(TIdentity id) { } } } namespace OrleansCodeGen.Orleans { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainState : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.GrainState instance) { } public global::Orleans.GrainState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.GrainState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.GrainState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_00BCE16F : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_00BCE16F instance) { } public Invokable_IMembershipTable_GrainReference_00BCE16F ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_00BCE16F instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_00BCE16F value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_7A519C2E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_7A519C2E instance) { } public Invokable_IMembershipTable_GrainReference_7A519C2E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_7A519C2E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_7A519C2E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_B1A52D2B : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMembershipTable_GrainReference_B1A52D2B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_B1A52D2B instance) { } public Invokable_IMembershipTable_GrainReference_B1A52D2B ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_B1A52D2B instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_B1A52D2B value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_BF899C85 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_BF899C85 instance) { } public Invokable_IMembershipTable_GrainReference_BF899C85 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_BF899C85 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_BF899C85 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_D851FB33 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMembershipTable_GrainReference_D851FB33(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_D851FB33 instance) { } public Invokable_IMembershipTable_GrainReference_D851FB33 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_D851FB33 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_D851FB33 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_E06D3DBC : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMembershipTable_GrainReference_E06D3DBC(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_E06D3DBC instance) { } public Invokable_IMembershipTable_GrainReference_E06D3DBC ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_E06D3DBC instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_E06D3DBC value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_FB89E5E9 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_FB89E5E9 instance) { } public Invokable_IMembershipTable_GrainReference_FB89E5E9 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_FB89E5E9 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_FB89E5E9 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMembershipTable_GrainReference_FEF3AC5A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMembershipTable_GrainReference_FEF3AC5A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMembershipTable_GrainReference_FEF3AC5A instance) { } public Invokable_IMembershipTable_GrainReference_FEF3AC5A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMembershipTable_GrainReference_FEF3AC5A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMembershipTable_GrainReference_FEF3AC5A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IVersionManager_GrainReference_4AAEAFCE : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IVersionManager_GrainReference_4AAEAFCE(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IVersionManager_GrainReference_4AAEAFCE instance) { } public Invokable_IVersionManager_GrainReference_4AAEAFCE ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IVersionManager_GrainReference_4AAEAFCE instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IVersionManager_GrainReference_4AAEAFCE value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IVersionManager_GrainReference_8F5C15A9 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IVersionManager_GrainReference_8F5C15A9(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IVersionManager_GrainReference_8F5C15A9 instance) { } public Invokable_IVersionManager_GrainReference_8F5C15A9 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IVersionManager_GrainReference_8F5C15A9 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IVersionManager_GrainReference_8F5C15A9 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IVersionManager_GrainReference_90AB9D5E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IVersionManager_GrainReference_90AB9D5E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IVersionManager_GrainReference_90AB9D5E instance) { } public Invokable_IVersionManager_GrainReference_90AB9D5E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IVersionManager_GrainReference_90AB9D5E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IVersionManager_GrainReference_90AB9D5E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IVersionManager_GrainReference_C01C4EE8 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IVersionManager_GrainReference_C01C4EE8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IVersionManager_GrainReference_C01C4EE8 instance) { } public Invokable_IVersionManager_GrainReference_C01C4EE8 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IVersionManager_GrainReference_C01C4EE8 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IVersionManager_GrainReference_C01C4EE8 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MembershipEntry : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_MembershipEntry(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.MembershipEntry instance) { } public global::Orleans.MembershipEntry ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.MembershipEntry instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.MembershipEntry value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MembershipTableData : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_MembershipTableData(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.MembershipTableData instance) { } public global::Orleans.MembershipTableData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.MembershipTableData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.MembershipTableData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TableVersion : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_TableVersion(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.TableVersion instance) { } public global::Orleans.TableVersion ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.TableVersion instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.TableVersion value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainState : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_GrainState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.GrainState DeepCopy(global::Orleans.GrainState original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_00BCE16F : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMembershipTable_GrainReference_00BCE16F DeepCopy(Invokable_IMembershipTable_GrainReference_00BCE16F original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_7A519C2E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMembershipTable_GrainReference_7A519C2E DeepCopy(Invokable_IMembershipTable_GrainReference_7A519C2E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_B1A52D2B : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IMembershipTable_GrainReference_B1A52D2B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IMembershipTable_GrainReference_B1A52D2B DeepCopy(Invokable_IMembershipTable_GrainReference_B1A52D2B original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_BF899C85 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMembershipTable_GrainReference_BF899C85 DeepCopy(Invokable_IMembershipTable_GrainReference_BF899C85 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_D851FB33 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMembershipTable_GrainReference_D851FB33 DeepCopy(Invokable_IMembershipTable_GrainReference_D851FB33 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_E06D3DBC : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IMembershipTable_GrainReference_E06D3DBC(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IMembershipTable_GrainReference_E06D3DBC DeepCopy(Invokable_IMembershipTable_GrainReference_E06D3DBC original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_FB89E5E9 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMembershipTable_GrainReference_FB89E5E9 DeepCopy(Invokable_IMembershipTable_GrainReference_FB89E5E9 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMembershipTable_GrainReference_FEF3AC5A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IMembershipTable_GrainReference_FEF3AC5A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IMembershipTable_GrainReference_FEF3AC5A DeepCopy(Invokable_IMembershipTable_GrainReference_FEF3AC5A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IVersionManager_GrainReference_4AAEAFCE : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IVersionManager_GrainReference_4AAEAFCE(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IVersionManager_GrainReference_4AAEAFCE DeepCopy(Invokable_IVersionManager_GrainReference_4AAEAFCE original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IVersionManager_GrainReference_8F5C15A9 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IVersionManager_GrainReference_8F5C15A9(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IVersionManager_GrainReference_8F5C15A9 DeepCopy(Invokable_IVersionManager_GrainReference_8F5C15A9 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IVersionManager_GrainReference_90AB9D5E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IVersionManager_GrainReference_90AB9D5E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IVersionManager_GrainReference_90AB9D5E DeepCopy(Invokable_IVersionManager_GrainReference_90AB9D5E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IVersionManager_GrainReference_C01C4EE8 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IVersionManager_GrainReference_C01C4EE8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IVersionManager_GrainReference_C01C4EE8 DeepCopy(Invokable_IVersionManager_GrainReference_C01C4EE8 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_MembershipEntry : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_MembershipEntry(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.MembershipEntry DeepCopy(global::Orleans.MembershipEntry original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_MembershipTableData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_MembershipTableData(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.MembershipTableData DeepCopy(global::Orleans.MembershipTableData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "00BCE16F" })] public sealed partial class Invokable_IMembershipTable_GrainReference_00BCE16F : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "7A519C2E" })] public sealed partial class Invokable_IMembershipTable_GrainReference_7A519C2E : global::Orleans.Runtime.TaskRequest { public System.DateTimeOffset arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "B1A52D2B" })] public sealed partial class Invokable_IMembershipTable_GrainReference_B1A52D2B : global::Orleans.Runtime.TaskRequest { public global::Orleans.MembershipEntry arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "BF899C85" })] public sealed partial class Invokable_IMembershipTable_GrainReference_BF899C85 : global::Orleans.Runtime.TaskRequest { public string arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "D851FB33" })] public sealed partial class Invokable_IMembershipTable_GrainReference_D851FB33 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "E06D3DBC" })] public sealed partial class Invokable_IMembershipTable_GrainReference_E06D3DBC : global::Orleans.Runtime.TaskRequest { public global::Orleans.MembershipEntry arg0; public string arg1; public global::Orleans.TableVersion arg2; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "FB89E5E9" })] public sealed partial class Invokable_IMembershipTable_GrainReference_FB89E5E9 : global::Orleans.Runtime.TaskRequest { public bool arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IMembershipTable), "FEF3AC5A" })] public sealed partial class Invokable_IMembershipTable_GrainReference_FEF3AC5A : global::Orleans.Runtime.TaskRequest { public global::Orleans.MembershipEntry arg0; public global::Orleans.TableVersion arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IVersionManager), "4AAEAFCE" })] public sealed partial class Invokable_IVersionManager_GrainReference_4AAEAFCE : global::Orleans.Runtime.TaskRequest { public global::Orleans.Versions.Selector.VersionSelectorStrategy arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IVersionManager), "8F5C15A9" })] public sealed partial class Invokable_IVersionManager_GrainReference_8F5C15A9 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Versions.Compatibility.CompatibilityStrategy arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IVersionManager), "90AB9D5E" })] public sealed partial class Invokable_IVersionManager_GrainReference_90AB9D5E : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainInterfaceType arg0; public global::Orleans.Versions.Selector.VersionSelectorStrategy arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IVersionManager), "C01C4EE8" })] public sealed partial class Invokable_IVersionManager_GrainReference_C01C4EE8 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainInterfaceType arg0; public global::Orleans.Versions.Compatibility.CompatibilityStrategy arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.LeaseProviders { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AcquiredLease : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_AcquiredLease(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.LeaseProviders.AcquiredLease instance) { } public global::Orleans.LeaseProviders.AcquiredLease ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.LeaseProviders.AcquiredLease instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.LeaseProviders.AcquiredLease value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AcquireLeaseResult : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_AcquireLeaseResult(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.LeaseProviders.AcquireLeaseResult instance) { } public global::Orleans.LeaseProviders.AcquireLeaseResult ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.LeaseProviders.AcquireLeaseResult instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.LeaseProviders.AcquireLeaseResult value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LeaseRequest : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_LeaseRequest(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.LeaseProviders.LeaseRequest instance) { } public global::Orleans.LeaseProviders.LeaseRequest ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.LeaseProviders.LeaseRequest instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.LeaseProviders.LeaseRequest value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ResponseCode : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.LeaseProviders.ResponseCode ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.LeaseProviders.ResponseCode value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Networking.Shared { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SocketConnectionException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_SocketConnectionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Networking.Shared.SocketConnectionException instance) { } public global::Orleans.Networking.Shared.SocketConnectionException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Networking.Shared.SocketConnectionException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Networking.Shared.SocketConnectionException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SocketConnectionException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_SocketConnectionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Placement.Rebalancing { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IActivationRebalancer_GrainReference_25E91D88 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IActivationRebalancer_GrainReference_25E91D88(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IActivationRebalancer_GrainReference_25E91D88 instance) { } public Invokable_IActivationRebalancer_GrainReference_25E91D88 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IActivationRebalancer_GrainReference_25E91D88 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IActivationRebalancer_GrainReference_25E91D88 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IActivationRebalancer_GrainReference_2FF852F5 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IActivationRebalancer_GrainReference_2FF852F5 instance) { } public Invokable_IActivationRebalancer_GrainReference_2FF852F5 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IActivationRebalancer_GrainReference_2FF852F5 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IActivationRebalancer_GrainReference_2FF852F5 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IActivationRebalancer_GrainReference_D7EB6469 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IActivationRebalancer_GrainReference_D7EB6469 instance) { } public Invokable_IActivationRebalancer_GrainReference_D7EB6469 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IActivationRebalancer_GrainReference_D7EB6469 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IActivationRebalancer_GrainReference_D7EB6469 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IActivationRebalancer_GrainReference_D9CA3E16 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IActivationRebalancer_GrainReference_D9CA3E16(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IActivationRebalancer_GrainReference_D9CA3E16 instance) { } public Invokable_IActivationRebalancer_GrainReference_D9CA3E16 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IActivationRebalancer_GrainReference_D9CA3E16 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IActivationRebalancer_GrainReference_D9CA3E16 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IActivationRebalancer_GrainReference_DCF3A7BB : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IActivationRebalancer_GrainReference_DCF3A7BB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IActivationRebalancer_GrainReference_DCF3A7BB instance) { } public Invokable_IActivationRebalancer_GrainReference_DCF3A7BB ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IActivationRebalancer_GrainReference_DCF3A7BB instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IActivationRebalancer_GrainReference_DCF3A7BB value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RebalancerStatus : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Placement.Rebalancing.RebalancerStatus ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Placement.Rebalancing.RebalancerStatus value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RebalancingReport : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_RebalancingReport(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Placement.Rebalancing.RebalancingReport instance) { } public global::Orleans.Placement.Rebalancing.RebalancingReport ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Placement.Rebalancing.RebalancingReport instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Placement.Rebalancing.RebalancingReport value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RebalancingStatistics : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_RebalancingStatistics(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Placement.Rebalancing.RebalancingStatistics instance) { } public global::Orleans.Placement.Rebalancing.RebalancingStatistics ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Placement.Rebalancing.RebalancingStatistics instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Placement.Rebalancing.RebalancingStatistics value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IActivationRebalancer_GrainReference_25E91D88 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IActivationRebalancer_GrainReference_25E91D88(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IActivationRebalancer_GrainReference_25E91D88 DeepCopy(Invokable_IActivationRebalancer_GrainReference_25E91D88 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IActivationRebalancer_GrainReference_2FF852F5 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IActivationRebalancer_GrainReference_2FF852F5 DeepCopy(Invokable_IActivationRebalancer_GrainReference_2FF852F5 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IActivationRebalancer_GrainReference_D7EB6469 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IActivationRebalancer_GrainReference_D7EB6469 DeepCopy(Invokable_IActivationRebalancer_GrainReference_D7EB6469 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IActivationRebalancer_GrainReference_D9CA3E16 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IActivationRebalancer_GrainReference_D9CA3E16(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IActivationRebalancer_GrainReference_D9CA3E16 DeepCopy(Invokable_IActivationRebalancer_GrainReference_D9CA3E16 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IActivationRebalancer_GrainReference_DCF3A7BB : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IActivationRebalancer_GrainReference_DCF3A7BB DeepCopy(Invokable_IActivationRebalancer_GrainReference_DCF3A7BB original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Placement.Rebalancing.IActivationRebalancer), "25E91D88" })] public sealed partial class Invokable_IActivationRebalancer_GrainReference_25E91D88 : global::Orleans.Runtime.VoidRequest { public global::Orleans.Placement.Rebalancing.IActivationRebalancerReportListener arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override void InvokeInner() { } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Placement.Rebalancing.IActivationRebalancer), "2FF852F5" })] public sealed partial class Invokable_IActivationRebalancer_GrainReference_2FF852F5 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Placement.Rebalancing.IActivationRebalancer), "D7EB6469" })] public sealed partial class Invokable_IActivationRebalancer_GrainReference_D7EB6469 : global::Orleans.Runtime.Request { public bool arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Placement.Rebalancing.IActivationRebalancer), "D9CA3E16" })] public sealed partial class Invokable_IActivationRebalancer_GrainReference_D9CA3E16 : global::Orleans.Runtime.VoidRequest { public global::Orleans.Placement.Rebalancing.IActivationRebalancerReportListener arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override void InvokeInner() { } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Placement.Rebalancing.IActivationRebalancer), "DCF3A7BB" })] public sealed partial class Invokable_IActivationRebalancer_GrainReference_DCF3A7BB : global::Orleans.Runtime.TaskRequest { public System.TimeSpan? arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Placement.Repartitioning { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CandidateConnectedVertex : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_CandidateConnectedVertex(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Placement.Repartitioning.CandidateConnectedVertex instance) { } public global::Orleans.Placement.Repartitioning.CandidateConnectedVertex ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Placement.Repartitioning.CandidateConnectedVertex instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Placement.Repartitioning.CandidateConnectedVertex value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EdgeVertex : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_EdgeVertex(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Placement.Repartitioning.EdgeVertex instance) { } public global::Orleans.Placement.Repartitioning.EdgeVertex ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Placement.Repartitioning.EdgeVertex instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Placement.Repartitioning.EdgeVertex value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Providers { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ProviderInitializationException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ProviderInitializationException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.ProviderInitializationException instance) { } public global::Orleans.Providers.ProviderInitializationException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.ProviderInitializationException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.ProviderInitializationException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ProviderStateException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ProviderStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.ProviderStateException instance) { } public global::Orleans.Providers.ProviderStateException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.ProviderStateException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.ProviderStateException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ProviderInitializationException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ProviderInitializationException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ProviderStateException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ProviderStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Runtime { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClusterManifestUpdate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_ClusterManifestUpdate(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ClusterManifestUpdate instance) { } public global::Orleans.Runtime.ClusterManifestUpdate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ClusterManifestUpdate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ClusterManifestUpdate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DetailedGrainStatistic : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_DetailedGrainStatistic(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.DetailedGrainStatistic instance) { } public global::Orleans.Runtime.DetailedGrainStatistic ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.DetailedGrainStatistic instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.DetailedGrainStatistic value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainCallFrequency : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_GrainCallFrequency(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.GrainCallFrequency instance) { } public global::Orleans.Runtime.GrainCallFrequency ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.GrainCallFrequency instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainCallFrequency value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_IndirectProbeResponse : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.IndirectProbeResponse instance) { } public global::Orleans.Runtime.IndirectProbeResponse ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.IndirectProbeResponse instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.IndirectProbeResponse value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_0A1C0D82 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_0A1C0D82(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_0A1C0D82 instance) { } public Invokable_IManagementGrain_GrainReference_0A1C0D82 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_0A1C0D82 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_0A1C0D82 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_0F06E027 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_0F06E027(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_0F06E027 instance) { } public Invokable_IManagementGrain_GrainReference_0F06E027 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_0F06E027 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_0F06E027 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_2D761B36 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_2D761B36(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_2D761B36 instance) { } public Invokable_IManagementGrain_GrainReference_2D761B36 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_2D761B36 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_2D761B36 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_317D82B6 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_317D82B6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_317D82B6 instance) { } public Invokable_IManagementGrain_GrainReference_317D82B6 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_317D82B6 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_317D82B6 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_329F9A1B : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_329F9A1B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_329F9A1B instance) { } public Invokable_IManagementGrain_GrainReference_329F9A1B ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_329F9A1B instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_329F9A1B value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_3CFF788C : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_3CFF788C(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_3CFF788C instance) { } public Invokable_IManagementGrain_GrainReference_3CFF788C ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_3CFF788C instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_3CFF788C value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_3DB7923B : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_3DB7923B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_3DB7923B instance) { } public Invokable_IManagementGrain_GrainReference_3DB7923B ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_3DB7923B instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_3DB7923B value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_4C0864C2 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_4C0864C2 instance) { } public Invokable_IManagementGrain_GrainReference_4C0864C2 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_4C0864C2 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_4C0864C2 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_54E6D1D1 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_54E6D1D1 instance) { } public Invokable_IManagementGrain_GrainReference_54E6D1D1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_54E6D1D1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_54E6D1D1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_54FE0FEC : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_54FE0FEC(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_54FE0FEC instance) { } public Invokable_IManagementGrain_GrainReference_54FE0FEC ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_54FE0FEC instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_54FE0FEC value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_5922EB76 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_5922EB76(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_5922EB76 instance) { } public Invokable_IManagementGrain_GrainReference_5922EB76 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_5922EB76 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_5922EB76 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_ACCE9D6A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_ACCE9D6A instance) { } public Invokable_IManagementGrain_GrainReference_ACCE9D6A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_ACCE9D6A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_ACCE9D6A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_AEDE93F6 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_AEDE93F6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_AEDE93F6 instance) { } public Invokable_IManagementGrain_GrainReference_AEDE93F6 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_AEDE93F6 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_AEDE93F6 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_B761B345 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IManagementGrain_GrainReference_B761B345(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_B761B345 instance) { } public Invokable_IManagementGrain_GrainReference_B761B345 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_B761B345 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_B761B345 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_CC6CCBC3 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_CC6CCBC3 instance) { } public Invokable_IManagementGrain_GrainReference_CC6CCBC3 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_CC6CCBC3 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_CC6CCBC3 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_D7365B43 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_D7365B43 instance) { } public Invokable_IManagementGrain_GrainReference_D7365B43 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_D7365B43 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_D7365B43 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IManagementGrain_GrainReference_F67965CC_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where T : global::Orleans.Providers.IControllable { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IManagementGrain_GrainReference_F67965CC_1 instance) { } public Invokable_IManagementGrain_GrainReference_F67965CC_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IManagementGrain_GrainReference_F67965CC_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IManagementGrain_GrainReference_F67965CC_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SiloRuntimeStatistics : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_SiloRuntimeStatistics(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.SiloRuntimeStatistics instance) { } public global::Orleans.Runtime.SiloRuntimeStatistics ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.SiloRuntimeStatistics instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SiloRuntimeStatistics value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SiloStatus : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Runtime.SiloStatus ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SiloStatus value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SimpleGrainStatistic : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_SimpleGrainStatistic(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.SimpleGrainStatistic instance) { } public global::Orleans.Runtime.SimpleGrainStatistic ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.SimpleGrainStatistic instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SimpleGrainStatistic value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ClusterManifestUpdate : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public void DeepCopy(global::Orleans.Runtime.ClusterManifestUpdate input, global::Orleans.Runtime.ClusterManifestUpdate output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Runtime.ClusterManifestUpdate DeepCopy(global::Orleans.Runtime.ClusterManifestUpdate original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_0A1C0D82 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_0A1C0D82(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_0A1C0D82 DeepCopy(Invokable_IManagementGrain_GrainReference_0A1C0D82 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_0F06E027 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_0F06E027(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_0F06E027 DeepCopy(Invokable_IManagementGrain_GrainReference_0F06E027 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_2D761B36 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_2D761B36(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_2D761B36 DeepCopy(Invokable_IManagementGrain_GrainReference_2D761B36 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_317D82B6 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_317D82B6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_317D82B6 DeepCopy(Invokable_IManagementGrain_GrainReference_317D82B6 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_329F9A1B : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_329F9A1B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_329F9A1B DeepCopy(Invokable_IManagementGrain_GrainReference_329F9A1B original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_3CFF788C : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_3CFF788C(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_3CFF788C DeepCopy(Invokable_IManagementGrain_GrainReference_3CFF788C original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_3DB7923B : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_3DB7923B DeepCopy(Invokable_IManagementGrain_GrainReference_3DB7923B original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_4C0864C2 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_4C0864C2 DeepCopy(Invokable_IManagementGrain_GrainReference_4C0864C2 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_54E6D1D1 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_54E6D1D1 DeepCopy(Invokable_IManagementGrain_GrainReference_54E6D1D1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_54FE0FEC : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_54FE0FEC(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_54FE0FEC DeepCopy(Invokable_IManagementGrain_GrainReference_54FE0FEC original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_5922EB76 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_5922EB76(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_5922EB76 DeepCopy(Invokable_IManagementGrain_GrainReference_5922EB76 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_ACCE9D6A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_ACCE9D6A DeepCopy(Invokable_IManagementGrain_GrainReference_ACCE9D6A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_AEDE93F6 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_AEDE93F6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_AEDE93F6 DeepCopy(Invokable_IManagementGrain_GrainReference_AEDE93F6 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_B761B345 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IManagementGrain_GrainReference_B761B345(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IManagementGrain_GrainReference_B761B345 DeepCopy(Invokable_IManagementGrain_GrainReference_B761B345 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_CC6CCBC3 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_CC6CCBC3 DeepCopy(Invokable_IManagementGrain_GrainReference_CC6CCBC3 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_D7365B43 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IManagementGrain_GrainReference_D7365B43 DeepCopy(Invokable_IManagementGrain_GrainReference_D7365B43 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IManagementGrain_GrainReference_F67965CC_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where T : global::Orleans.Providers.IControllable { public Invokable_IManagementGrain_GrainReference_F67965CC_1 DeepCopy(Invokable_IManagementGrain_GrainReference_F67965CC_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "0A1C0D82" })] public sealed partial class Invokable_IManagementGrain_GrainReference_0A1C0D82 : global::Orleans.Runtime.TaskRequest { public string[] arg0; public global::Orleans.Runtime.SiloAddress[] arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "0F06E027" })] public sealed partial class Invokable_IManagementGrain_GrainReference_0F06E027 : global::Orleans.Runtime.TaskRequest> { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "2D761B36" })] public sealed partial class Invokable_IManagementGrain_GrainReference_2D761B36 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "317D82B6" })] public sealed partial class Invokable_IManagementGrain_GrainReference_317D82B6 : global::Orleans.Runtime.Request { public global::Orleans.Runtime.IAddressable arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "329F9A1B" })] public sealed partial class Invokable_IManagementGrain_GrainReference_329F9A1B : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress[] arg0; public System.TimeSpan arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "3CFF788C" })] public sealed partial class Invokable_IManagementGrain_GrainReference_3CFF788C : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "3DB7923B" })] public sealed partial class Invokable_IManagementGrain_GrainReference_3DB7923B : global::Orleans.Runtime.Request> { public global::Orleans.Runtime.GrainType arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "4C0864C2" })] public sealed partial class Invokable_IManagementGrain_GrainReference_4C0864C2 : global::Orleans.Runtime.TaskRequest> { public bool arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "54E6D1D1" })] public sealed partial class Invokable_IManagementGrain_GrainReference_54E6D1D1 : global::Orleans.Runtime.TaskRequest { public System.TimeSpan arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "54FE0FEC" })] public sealed partial class Invokable_IManagementGrain_GrainReference_54FE0FEC : global::Orleans.Runtime.Request { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "5922EB76" })] public sealed partial class Invokable_IManagementGrain_GrainReference_5922EB76 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "ACCE9D6A" })] public sealed partial class Invokable_IManagementGrain_GrainReference_ACCE9D6A : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "AEDE93F6" })] public sealed partial class Invokable_IManagementGrain_GrainReference_AEDE93F6 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainReference arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "B761B345" })] public sealed partial class Invokable_IManagementGrain_GrainReference_B761B345 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.SiloAddress[] arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "CC6CCBC3" })] public sealed partial class Invokable_IManagementGrain_GrainReference_CC6CCBC3 : global::Orleans.Runtime.TaskRequest { public bool arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "D7365B43" })] public sealed partial class Invokable_IManagementGrain_GrainReference_D7365B43 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Runtime.IManagementGrain), "F67965CC" })] public sealed partial class Invokable_IManagementGrain_GrainReference_F67965CC_1 : global::Orleans.Runtime.TaskRequest where T : global::Orleans.Providers.IControllable { public string arg0; public int arg1; public object arg2; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Runtime.Messaging { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_InvalidMessageFrameException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_InvalidMessageFrameException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.Messaging.InvalidMessageFrameException instance) { } public global::Orleans.Runtime.Messaging.InvalidMessageFrameException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.Messaging.InvalidMessageFrameException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.Messaging.InvalidMessageFrameException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_InvalidMessageFrameException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_InvalidMessageFrameException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Storage { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_BadProviderConfigException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_BadProviderConfigException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Storage.BadProviderConfigException instance) { } public global::Orleans.Storage.BadProviderConfigException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Storage.BadProviderConfigException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Storage.BadProviderConfigException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_InconsistentStateException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_InconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Storage.InconsistentStateException instance) { } public global::Orleans.Storage.InconsistentStateException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Storage.InconsistentStateException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Storage.InconsistentStateException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMemoryStorageGrain_GrainReference_45659318_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMemoryStorageGrain_GrainReference_45659318_1 instance) { } public Invokable_IMemoryStorageGrain_GrainReference_45659318_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMemoryStorageGrain_GrainReference_45659318_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMemoryStorageGrain_GrainReference_45659318_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 instance) { } public Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 instance) { } public Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_BadProviderConfigException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_BadProviderConfigException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_InconsistentStateException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_InconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Storage.InconsistentStateException input, global::Orleans.Storage.InconsistentStateException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMemoryStorageGrain_GrainReference_45659318_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMemoryStorageGrain_GrainReference_45659318_1 DeepCopy(Invokable_IMemoryStorageGrain_GrainReference_45659318_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 DeepCopy(Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 DeepCopy(Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Storage.IMemoryStorageGrain), "45659318" })] public sealed partial class Invokable_IMemoryStorageGrain_GrainReference_45659318_1 : global::Orleans.Runtime.TaskRequest> { public string arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Storage.IMemoryStorageGrain), "7CC6CA25" })] public sealed partial class Invokable_IMemoryStorageGrain_GrainReference_7CC6CA25_1 : global::Orleans.Runtime.TaskRequest { public string arg0; public global::Orleans.IGrainState arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Storage.IMemoryStorageGrain), "B7CADD03" })] public sealed partial class Invokable_IMemoryStorageGrain_GrainReference_B7CADD03_1 : global::Orleans.Runtime.TaskRequest { public string arg0; public string arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } ================================================ FILE: src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { public readonly partial struct DeactivationReason { private readonly object _dummy; private readonly int _dummyPrimitive; public DeactivationReason(DeactivationReasonCode code, System.Exception? exception, string text) { } public DeactivationReason(DeactivationReasonCode code, string text) { } public string Description { get { throw null; } } public System.Exception? Exception { get { throw null; } } public DeactivationReasonCode ReasonCode { get { throw null; } } public override readonly string ToString() { throw null; } } [GenerateSerializer] public enum DeactivationReasonCode : byte { None = 0, ShuttingDown = 1, ActivationFailed = 2, DirectoryFailure = 3, ActivationIdle = 4, ActivationUnresponsive = 5, DuplicateActivation = 6, IncompatibleRequest = 7, ApplicationError = 8, ApplicationRequested = 9, Migrating = 10, RuntimeRequested = 11, HighMemoryPressure = 12 } public enum ErrorCode { Runtime = 100000, Runtime_Error_100001 = 100001, Logger_ProcessCrashing = 100002, Runtime_Error_100002 = 100002, Runtime_Error_100003 = 100003, Runtime_Error_100004 = 100004, Runtime_Error_100005 = 100005, Runtime_Error_100006 = 100006, Runtime_Error_100007 = 100007, Runtime_Error_100008 = 100008, Runtime_Error_100009 = 100009, Runtime_Error_100010 = 100010, Runtime_Error_100011 = 100011, Runtime_Error_100012 = 100012, Runtime_Error_100013 = 100013, Runtime_Error_100014 = 100014, Runtime_Error_100015 = 100015, Runtime_Error_100016 = 100016, Runtime_Error_100017 = 100017, Runtime_Error_100018 = 100018, Runtime_Error_100019 = 100019, Runtime_Error_100020 = 100020, ProxyClient_ReceiveError = 100021, Runtime_Error_100021 = 100021, Runtime_Error_100022 = 100022, Runtime_Error_100023 = 100023, Runtime_Error_100024 = 100024, Runtime_Error_100025 = 100025, Runtime_Error_100026 = 100026, Runtime_Error_100027 = 100027, Runtime_Error_100028 = 100028, Runtime_Error_100029 = 100029, Runtime_Error_100030 = 100030, Runtime_Error_100031 = 100031, Runtime_Error_100032 = 100032, Runtime_Error_100033 = 100033, Ser_IncompatibleIntermediateType = 100033, Runtime_Error_100034 = 100034, Ser_CannotConstructBaseObj = 100034, Runtime_Error_100035 = 100035, Ser_IncompatibleType = 100035, Runtime_Error_100036 = 100036, Runtime_Error_100037 = 100037, TimerCallbackError = 100037, Runtime_Error_100039 = 100039, Runtime_Error_100040 = 100040, Runtime_Error_100041 = 100041, Runtime_Error_100042 = 100042, Runtime_Error_100043 = 100043, Runtime_Error_100044 = 100044, Runtime_Error_100045 = 100045, Runtime_Error_100046 = 100046, Loader_NotGrainAssembly = 100047, Runtime_Error_100047 = 100047, Loader_TypeLoadError = 100048, Runtime_Error_100048 = 100048, Loader_ProxyLoadError = 100049, Runtime_Error_100049 = 100049, Runtime_Error_100050 = 100050, Runtime_Error_100051 = 100051, Runtime_Error_100052 = 100052, Runtime_Error_100053 = 100053, Runtime_Error_100054 = 100054, Runtime_Error_100055 = 100055, Runtime_Error_100056 = 100056, Runtime_Error_100057 = 100057, Runtime_Error_100058 = 100058, Runtime_Error_100059 = 100059, Runtime_Error_100060 = 100060, Runtime_Error_100061 = 100061, Runtime_Error_100062 = 100062, Runtime_Error_100063 = 100063, Runtime_Error_100064 = 100064, Runtime_Error_100065 = 100065, Runtime_Error_100066 = 100066, Runtime_Error_100067 = 100067, Runtime_Error_100068 = 100068, Runtime_Error_100069 = 100069, Runtime_Error_100070 = 100070, Dispatcher_SelectTarget_Failed = 100071, Runtime_Error_100071 = 100071, Dispatcher_InvalidEnum_Direction = 100072, Runtime_Error_100072 = 100072, Dispatcher_NoCallbackForRejectionResp = 100073, Runtime_Error_100073 = 100073, Runtime_Error_100074 = 100074, Dispatcher_InvalidEnum_RejectionType = 100075, Runtime_Error_100075 = 100075, Dispatcher_NoCallbackForResp = 100076, Runtime_Error_100076 = 100076, Dispatcher_InvalidMsg_Direction = 100077, Runtime_Error_100077 = 100077, Runtime_Error_100078 = 100078, Runtime_Error_100079 = 100079, Runtime_Error_100080 = 100080, Runtime_Error_100081 = 100081, Runtime_Error_100082 = 100082, Runtime_Error_100083 = 100083, Runtime_Error_100084 = 100084, Runtime_Error_100085 = 100085, Runtime_Error_100086 = 100086, Runtime_Error_100087 = 100087, Runtime_Error_100088 = 100088, Runtime_Error_100089 = 100089, Runtime_Error_100090 = 100090, Runtime_Error_100091 = 100091, Runtime_Error_100092 = 100092, Runtime_Error_100093 = 100093, Runtime_Error_100094 = 100094, Runtime_Error_100095 = 100095, Runtime_Error_100096 = 100096, Runtime_Error_100097 = 100097, Runtime_Error_100098 = 100098, Runtime_Error_100099 = 100099, Runtime_Error_100100 = 100100, Runtime_Error_100101 = 100101, Runtime_Error_100102 = 100102, Runtime_Error_100103 = 100103, Runtime_Error_100104 = 100104, Runtime_Error_100105 = 100105, Runtime_Error_100106 = 100106, Runtime_Error_100107 = 100107, Runtime_Error_100108 = 100108, Runtime_Error_100109 = 100109, Runtime_Error_100110 = 100110, Runtime_Error_100111 = 100111, Runtime_Error_100112 = 100112, Runtime_Error_100113 = 100113, Runtime_Error_100114 = 100114, Runtime_Error_100115 = 100115, Runtime_Error_100116 = 100116, Runtime_Error_100117 = 100117, Runtime_Error_100118 = 100118, Runtime_Error_100119 = 100119, Runtime_Error_100120 = 100120, Runtime_Error_100121 = 100121, Runtime_Error_100122 = 100122, Runtime_Error_100123 = 100123, Runtime_Error_100124 = 100124, Runtime_Error_100125 = 100125, Runtime_Error_100126 = 100126, Runtime_Error_100127 = 100127, Runtime_Error_100128 = 100128, Runtime_Error_100129 = 100129, Runtime_Error_100130 = 100130, Runtime_Error_100131 = 100131, Runtime_Error_100132 = 100132, Runtime_Error_100133 = 100133, Runtime_Error_100134 = 100134, Runtime_Error_100135 = 100135, Runtime_Error_100136 = 100136, Runtime_Error_100137 = 100137, Runtime_Error_100138 = 100138, Runtime_Error_100139 = 100139, Runtime_Error_100140 = 100140, Runtime_Error_100141 = 100141, Runtime_Error_100142 = 100142, Runtime_Error_100143 = 100143, Runtime_Error_100144 = 100144, Runtime_Error_100145 = 100145, Runtime_Error_100146 = 100146, Dispatcher_Intermediate_GetOrCreateActivation = 100147, Runtime_Error_100147 = 100147, Dispatcher_NoTargetActivation = 100148, Runtime_Error_100148 = 100148, Runtime_Error_100149 = 100149, Runtime_Error_100150 = 100150, SiloHeartbeatTimerStalled = 100150, Runtime_Error_100151 = 100151, Dispatcher_QueueingRequestBadTargetState = 100152, Runtime_Error_100152 = 100152, Runtime_Error_100153 = 100153, Runtime_Error_100154 = 100154, Runtime_Error_100155 = 100155, Runtime_Error_100156 = 100156, Runtime_Error_100157 = 100157, Runtime_Error_100158 = 100158, ProxyClient_SerializationError = 100159, Runtime_Error_100159 = 100159, Runtime_Error_100160 = 100160, ProxyClient_SocketSendError = 100161, Runtime_Error_100161 = 100161, Runtime_Error_100162 = 100162, ProxyClient_ByteCountMismatch = 100163, Runtime_Error_100163 = 100163, Runtime_Error_100164 = 100164, Runtime_Error_100165 = 100165, Runtime_Error_100166 = 100166, Runtime_Error_100167 = 100167, Runtime_Error_100168 = 100168, Runtime_Error_100169 = 100169, Runtime_Error_100170 = 100170, Runtime_Error_100171 = 100171, Runtime_Error_100172 = 100172, Runtime_Error_100173 = 100173, Runtime_Error_100174 = 100174, Runtime_Error_100175 = 100175, Runtime_Error_100176 = 100176, Runtime_Error_100177 = 100177, ProxyClient_CannotConnect = 100178, Runtime_Error_100178 = 100178, Runtime_Error_100179 = 100179, Runtime_Error_100180 = 100180, Runtime_Error_100181 = 100181, Runtime_Error_100182 = 100182, Runtime_Error_100183 = 100183, Runtime_Error_100184 = 100184, Runtime_Error_100185 = 100185, Runtime_Error_100186 = 100186, Runtime_Error_100187 = 100187, Runtime_Error_100188 = 100188, Runtime_Error_100189 = 100189, Runtime_Error_100190 = 100190, Runtime_Error_100191 = 100191, Runtime_Error_100192 = 100192, Runtime_Error_100193 = 100193, Runtime_Error_100194 = 100194, Runtime_Error_100195 = 100195, Runtime_Error_100196 = 100196, Runtime_Error_100197 = 100197, Runtime_Error_100198 = 100198, Runtime_Error_100199 = 100199, Runtime_Error_100200 = 100200, Runtime_Error_100201 = 100201, Runtime_Error_100202 = 100202, Runtime_Error_100203 = 100203, Runtime_Error_100204 = 100204, Runtime_Error_100205 = 100205, Runtime_Error_100206 = 100206, Runtime_Error_100207 = 100207, Runtime_Error_100208 = 100208, Runtime_Error_100209 = 100209, Runtime_Error_100210 = 100210, Runtime_Error_100211 = 100211, Runtime_Error_100212 = 100212, Runtime_Error_100213 = 100213, Runtime_Error_100214 = 100214, Runtime_Error_100215 = 100215, Runtime_Error_100216 = 100216, Runtime_Error_100217 = 100217, Runtime_Error_100218 = 100218, Runtime_Error_100219 = 100219, Runtime_Error_100220 = 100220, Runtime_Error_100221 = 100221, Runtime_Error_100222 = 100222, Runtime_Error_100223 = 100223, Runtime_Error_100224 = 100224, MembershipCantWriteLivenessDisabled = 100225, Runtime_Error_100225 = 100225, Runtime_Error_100226 = 100226, Runtime_Error_100227 = 100227, Runtime_Error_100228 = 100228, Runtime_Error_100229 = 100229, Runtime_Error_100230 = 100230, Runtime_Error_100231 = 100231, Runtime_Error_100232 = 100232, Runtime_Error_100233 = 100233, Runtime_Error_100234 = 100234, Runtime_Error_100235 = 100235, Runtime_Error_100236 = 100236, Runtime_Error_100237 = 100237, Runtime_Error_100238 = 100238, Runtime_Error_100239 = 100239, Runtime_Error_100240 = 100240, Runtime_Error_100241 = 100241, Runtime_Error_100242 = 100242, Runtime_Error_100243 = 100243, Runtime_Error_100244 = 100244, Runtime_Error_100245 = 100245, Runtime_Error_100246 = 100246, Runtime_Error_100247 = 100247, Runtime_Error_100248 = 100248, Runtime_Error_100249 = 100249, Runtime_Error_100250 = 100250, Runtime_Error_100251 = 100251, Runtime_Error_100252 = 100252, Runtime_Error_100253 = 100253, Runtime_Error_100254 = 100254, Runtime_Error_100255 = 100255, Runtime_Error_100256 = 100256, Runtime_Error_100257 = 100257, Runtime_Error_100258 = 100258, Runtime_Error_100259 = 100259, Runtime_Error_100260 = 100260, Runtime_Error_100261 = 100261, Runtime_Error_100262 = 100262, Runtime_Error_100263 = 100263, Runtime_Error_100264 = 100264, Runtime_Error_100265 = 100265, Runtime_Error_100266 = 100266, Runtime_Error_100267 = 100267, Runtime_Error_100268 = 100268, Runtime_Error_100269 = 100269, Runtime_Error_100270 = 100270, Runtime_Error_100271 = 100271, Runtime_Error_100272 = 100272, Runtime_Error_100273 = 100273, Runtime_Error_100274 = 100274, Runtime_Error_100275 = 100275, Runtime_Error_100276 = 100276, Runtime_Error_100277 = 100277, Runtime_Error_100278 = 100278, Runtime_Error_100279 = 100279, Runtime_Error_100280 = 100280, Runtime_Error_100281 = 100281, Runtime_Error_100282 = 100282, Runtime_Error_100283 = 100283, Runtime_Error_100284 = 100284, Runtime_Error_100285 = 100285, Runtime_Error_100286 = 100286, Runtime_Error_100287 = 100287, Runtime_Error_100288 = 100288, Runtime_Error_100289 = 100289, Runtime_Error_100290 = 100290, Runtime_Error_100291 = 100291, Runtime_Error_100292 = 100292, Runtime_Error_100293 = 100293, Runtime_Error_100294 = 100294, Runtime_Error_100295 = 100295, Runtime_Error_100296 = 100296, Runtime_Error_100297 = 100297, Runtime_Error_100298 = 100298, Dispatcher_InjectingRejection = 100299, Runtime_Error_100299 = 100299, Dispatcher_InjectingMessageLoss = 100300, Runtime_Error_100300 = 100300, Runtime_Error_100301 = 100301, Runtime_Error_100302 = 100302, Dispatcher_UnknownTypeCode = 100303, Runtime_Error_100303 = 100303, Runtime_Error_100304 = 100304, Runtime_Error_100305 = 100305, Runtime_Error_100306 = 100306, Runtime_Error_100307 = 100307, Runtime_Error_100308 = 100308, Runtime_Error_100309 = 100309, Runtime_Error_100310 = 100310, Runtime_Error_100311 = 100311, Runtime_Error_100312 = 100312, ClientInitializing = 100313, ClientStarting = 100314, ClientError = 100315, Runtime_Error_100316 = 100316, Runtime_Error_100317 = 100317, Runtime_Error_100318 = 100318, Runtime_Error_100319 = 100319, Runtime_Error_100320 = 100320, Runtime_Error_100321 = 100321, GrainInvokeException = 100322, Runtime_Error_100323 = 100323, Runtime_Error_100324 = 100324, Runtime_Error_100325 = 100325, Runtime_Error_100326 = 100326, Runtime_Error_100327 = 100327, Runtime_Error_100328 = 100328, Runtime_Error_100329 = 100329, Runtime_Error_100330 = 100330, Runtime_Error_100331 = 100331, SiloBase = 100400, SiloStarting = 100401, SiloStarted = 100402, SiloInitializing = 100403, SiloGcSetting = 100404, SiloGcWarning = 100405, SiloSetDeploymentId = 100406, SiloSetSiloEndpoint = 100407, SiloSetProxyEndpoint = 100408, SiloSetSeedNode = 100409, SiloAddSeedNode = 100410, SiloSetPrimaryNode = 100411, SiloSetWorkingDir = 100412, SiloStopped = 100413, SiloStopping = 100414, SiloInitConfig = 100415, SiloDebugDump = 100416, SiloShuttingDown = 100417, SiloShutDown = 100418, SiloFailedToStopMembership = 100419, SiloIgnoreErrorDuringStop = 100420, SiloCannotResetHeartbeatTimer = 100421, SiloInitializingFinished = 100422, SiloSetSiloType = 100423, SiloStartupEventName = 100424, SiloStartupEventCreated = 100425, SiloStartupEventOpened = 100426, SiloStopInProgress = 100427, WaitingForSiloStop = 100428, CannotCheckRoleEnvironment = 100429, SiloConfiguredThreadPool = 100430, SiloFailedToConfigureThreadPool = 100431, SetSiloLivenessType = 100434, SiloEndpointConfigError = 100435, SiloConfiguredServicePointManager = 100436, SiloCallingProviderInit = 100437, SetReminderServiceType = 100438, SiloStartError = 100439, SiloConfigDeprecated = 100440, SiloShutdownEventName = 100441, SiloShutdownEventCreated = 100442, SiloShutdownEventOpened = 100443, SiloShutdownEventReceived = 100444, SiloLoadedDI = 100445, SiloFailedToLoadDI = 100446, SiloFileNotFoundLoadingDI = 100447, SiloStartupEventFailure = 100448, SiloShutdownEventFailure = 100449, LifecycleStartFailure = 100450, LifecycleStopFailure = 100451, SiloStartPerfMeasure = 100452, LifecycleStagesReport = 100453, CatalogBase = 100500, CatalogNonExistingActivation1 = 100501, Catalog_UnregisterManyAsync = 100502, Catalog_DestroyActivations = 100503, Catalog_UnknownActivation = 100504, Catalog_ActivationException = 100505, Catalog_GetApproximateSiloStatuses = 100506, Catalog_BeforeCollection = 100507, Catalog_AfterCollection = 100508, Catalog_ShutdownActivations_1 = 100509, CatalogNonExistingActivation2 = 100510, Catalog_BeforeCallingActivate = 100511, Catalog_AfterCallingActivate = 100512, Catalog_ErrorCallingActivate = 100513, Catalog_BeforeCallingDeactivate = 100514, Catalog_AfterCallingDeactivate = 100515, Catalog_ErrorCallingDeactivate = 100516, Catalog_MissingTypeOnCreate = 100517, Catalog_ResendDuplicateFailed = 100518, Catalog_NullGetTypeAndStrategies = 100519, Catalog_DuplicateActivation = 100520, Catalog_RegistrationFailure = 100521, Catalog_Warn_ActivationTooManyRequests = 100522, Catalog_Reject_ActivationTooManyRequests = 100523, Catalog_SiloStatusChangeNotification = 100524, Catalog_SiloStatusChangeNotification_Exception = 100525, Catalog_AttemptToCollectActivationEarly = 100526, Catalog_DeactivateActivation_Exception = 100527, Catalog_ActivationDirectory_Statistics = 100528, Catalog_UnregisterMessageTarget1 = 100529, Catalog_UnregisterMessageTarget2 = 100530, Catalog_UnregisterMessageTarget3 = 100531, Catalog_UnregisterMessageTarget4 = 100532, Catalog_Failed_SetupActivationState = 100533, Catalog_Failed_InvokeActivate = 100534, Catalog_RerouteAllQueuedMessages = 100535, Catalog_WaitForAllTimersToFinish_Exception = 100536, Catalog_ActivationCollector_BadState_1 = 100537, Catalog_ActivationCollector_BadState_2 = 100538, Catalog_DestroyActivations_Done = 100539, Catalog_ShutdownActivations_2 = 100540, Catalog_ShutdownActivations_3 = 100541, Catalog_DeactivateStreamResources_Exception = 100542, Catalog_FinishDeactivateActivation_Exception = 100543, Catalog_FinishGrainDeactivateAndCleanupStreams_Exception = 100544, Catalog_DeactivateAllActivations = 100545, Catalog_ActivationCollector_BadState_3 = 100546, Catalog_UnregisterAsync = 100547, MembershipBase = 100600, MembershipNodeMigrated = 100601, MembershipNodeRestarted = 100602, MembershipStarting = 100603, MembershipBecomeActive = 100604, MembershipFinishBecomeActive = 100605, MembershipShutDown = 100606, MembershipStop = 100607, MembershipReadTable = 100608, MembershipKillMyself = 100609, MembershipVotingForKill = 100610, MembershipMarkingAsDead = 100611, MembershipWatchList = 100612, MembershipMissedPing = 100613, MembershipSendingPreJoinPing = 100614, MembershipFailedToWrite = 100615, MembershipFailedToWriteConditional = 100616, MembershipFoundMyselfDead1 = 100617, MembershipFoundMyselfDead2 = 100618, MembershipDetectedOlder = 100619, MembershipDetectedNewer = 100620, MembershipDelayedTableUpdateTimer = 100621, MembershipDelayedProbeOtherSilosTimer = 100622, MembershipFailedToReadSilo = 100623, MembershipDelayedIAmAliveUpdateTimer = 100624, MembershipMissedIAmAliveTableUpdate = 100625, MembershipLocalSubscriberException = 100626, MembershipKillMyselfLocally = 100627, MembershipFoundMyselfDead3 = 100628, MembershipMarkDeadWriteFailed = 100629, MembershipTableGrainInit1 = 100630, MembershipTableGrainInit2 = 100631, MembershipTableGrainInit3 = 100632, MembershipTableGrainInit4 = 100633, MembershipReadAll_1 = 100634, MembershipFactory1 = 100635, MembershipFactory2 = 100636, MembershipGrainBasedTable1 = 100637, MembershipGrainBasedTable2 = 100638, MembershipGrainBasedTable3 = 100639, MembershipFileBasedTable1 = 100640, MembershipFileBasedTable2 = 100641, MembershipFileBasedTable3 = 100642, MembershipFileBasedTable4 = 100643, MembershipPingedSiloNotInWatchList = 100644, MembershipReadAll_2 = 100645, MembershipFailedToStart = 100646, MembershipFailedToBecomeActive = 100647, MembershipFailedToStop = 100648, MembershipFailedToShutdown = 100649, MembershipFailedToKillMyself = 100650, MembershipFailedToSuspect = 100651, MembershipReadAll_Cleanup = 100652, MembershipShutDownFailure = 100653, MembershipKillMyselfFailure = 100654, MembershipGossipProcessingFailure = 100655, MembershipGossipSendFailure = 100656, MembershipTimerProcessingFailure = 100657, MembershipSendPingFailure = 100658, MembershipUpdateIAmAliveFailure = 100659, MembershipStartingIAmAliveTimer = 100660, MembershipJoiningPreconditionFailure = 100661, MembershipCleanDeadEntriesFailure = 100662, MembershipJoining = 100663, MembershipFailedToJoin = 100664, NSMembershipStarting = 100670, NSMembershipBecomeActive = 100671, NSMembershipFailedToBecomeActive = 100672, NSMembershipShutDown = 100673, NSMembershipStop = 100674, NSMembershipKillMyself = 100675, NSMembershipKillMyselfLocally = 100676, NSMembershipNotificationProcessingFailure = 100677, NSMembershipReadAll_1 = 100678, NSMembershipReadAll_2 = 100679, NSMembershipFoundMyselfDead2 = 100680, NSMembershipDetectedOlder = 100681, NSMembershipDetectedNewer = 100682, NSMembershipTimerProcessingFailure = 100683, NSMembershipShutDownFailure = 100684, NSMembershipKillMyselfFailure = 100685, NSMembershipNSDetails = 100686, SSMT_ReadRowError = 100687, SSMT_ReadAllError = 100688, SSMT_InsertRowError = 100689, SSMT_UpdateRowError = 100690, SSMT_MergeRowError = 100691, SSMT_EtagMismatch_Insert = 100692, SSMT_EtagMismatch_Update = 100693, PerfCounterBase = 100700, PerfCounterNotFound = 100701, PerfCounterStarting = 100702, PerfCounterStopping = 100703, PerfCounterDumpAll = 100704, PerfCounterWriteErrors = 100705, PerfCounterWriteSuccess = 100706, PerfCounterWriteTooManyErrors = 100707, PerfCounterNotRegistered = 100708, PerfCounterUnableToConnect = 100709, PerfCounterUnableToWrite = 100710, PerfCounterWriting = 100711, PerfCounterSkipping = 100712, PerfMetricsStoppingTimer = 100713, PerfMetricsStartingTimer = 100714, PerfStatistics = 100715, PerfCounterRegistering = 100716, PerfCounterTimerError = 100717, TimerChangeError = 100717, PerfCounterCategoryCheckError = 100718, PerfCounterConnectError = 100719, PerfCounterFailedToInitialize = 100720, ProxyClientBase = 100900, ProxyClientUnhandledExceptionWhileSending = 100901, ProxyClientUnhandledExceptionWhileReceiving = 100902, ProxyClient_CannotSend = 100903, ProxyClient_CannotSend_NoGateway = 100904, ProxyClient_DroppingMsg = 100905, ProxyClient_RejectingMsg = 100906, ProxyClient_MsgSent = 100907, ProxyClient_Connected = 100908, ProxyClient_PauseBeforeRetry = 100909, ProxyClient_MsgCtrNotRunning = 100910, ProxyClient_DeadGateway = 100911, ProxyClient_MarkGatewayDead = 100912, ProxyClient_MarkGatewayDisconnected = 100913, ProxyClient_GatewayConnStarted = 100914, ProxyClient_CreatedGatewayUnordered = 100915, ProxyClient_CreatedGatewayToGrain = 100916, ProxyClient_NewBucketIndex = 100917, ProxyClient_QueueRequest = 100918, ProxyClient_ThreadAbort = 100919, ProxyClient_OperationCancelled = 100920, ProxyClient_GetGateways = 100921, ProxyClient_NetworkError = 100922, ProxyClient_SendException = 100923, ProxyClient_OGC_TargetNotFound = 100924, ProxyClient_OGC_SendResponseFailed = 100925, ProxyClient_OGC_SendExceptionResponseFailed = 100926, ProxyClient_OGC_UnhandledExceptionInOneWayInvoke = 100927, ProxyClient_ClientInvokeCallback_Error = 100928, ProxyClient_StartDone = 100929, ProxyClient_OGC_TargetNotFound_2 = 100930, ProxyClient_AppDomain_Unload = 100931, ProxyClient_GatewayUnknownStatus = 100932, ProxyClient_FailedToUnregisterCallback = 100933, MessagingBase = 101000, Messaging_IMA_DroppingConnection = 101001, Messaging_Dispatcher_DiscardRejection = 101002, MessagingBeginReceiveException = 101003, MessagingBeginAcceptSocketException = 101004, MessagingAcceptingSocketClosed = 101005, MessagingEndAcceptSocketException = 101006, MessagingUnexpectedSendError = 101007, MessagingSendingRejection = 101008, MessagingMessageFromUnknownActivation = 101009, Messaging_IMA_OpenedListeningSocket = 101010, Messaging_IMA_AcceptCallbackNullState = 101011, Messaging_IMA_AcceptCallbackUnexpectedState = 101012, Messaging_IMA_NewBeginReceiveException = 101013, Messaging_Socket_ReceiveError = 101014, Messaging_IMA_ClosingSocket = 101015, Messaging_OutgoingMS_DroppingMessage = 101016, MessagingProcessReceiveBufferException = 101017, Messaging_LargeMsg_Outgoing = 101018, Messaging_LargeMsg_Incoming = 101019, Messaging_SiloNetworkError = 101020, Messaging_UnableToGetSendingSocket = 101021, Messaging_ExceptionSending = 101022, Messaging_CountMismatchSending = 101023, Messaging_ExceptionReceiving = 101024, Messaging_ExceptionBeginReceiving = 101025, Messaging_IMA_ExceptionAccepting = 101026, Messaging_IMA_BadBufferReceived = 101027, Messaging_IMA_ActivationOverloaded = 101028, Messaging_SerializationError = 101029, Messaging_UnableToDeserializeBody = 101030, Messaging_Dispatcher_TryForward = 101031, Messaging_Dispatcher_TryForwardFailed = 101032, Messaging_Dispatcher_ForwardingRequests = 101033, Messaging_SimulatedMessageLoss = 101034, Messaging_Dispatcher_ReturnToOriginCluster = 101035, MessagingAcceptAsyncSocketException = 101036, Messaging_ExceptionReceiveAsync = 101037, Messaging_DroppingExpiredMessage = 101038, Messaging_DroppingBlockedMessage = 101039, Messaging_Inbound_Enqueue = 101040, Messaging_Inbound_Dequeue = 101041, Messaging_Dispatcher_Rejected = 101042, DirectoryBase = 101100, DirectoryBothPrimaryAndBackupForGrain = 101101, DirectoryPartitionPredecessorExpected = 101102, DirectoryUnexpectedDelta = 101104, Directory_SiloStatusChangeNotification_Exception = 101105, SchedulerBase = 101200, SchedulerWorkerPoolThreadQueueWaitTime = 101201, SchedulerWorkItemGroupQueueWaitTime = 101202, SchedulerStatistics = 101203, SchedulerFinishShutdown = 101204, SchedulerNullActivation = 101205, SchedulerExceptionFromExecute = 101206, SchedulerNullContext = 101207, SchedulerTaskExecuteIncomplete1 = 101208, WaitCalledInsideGrain = 101209, SchedulerStatus = 101210, WaitCalledInServerCode = 101211, ExecutorTurnTooLong = 101212, SchedulerTooManyPendingItems = 101213, SchedulerTurnTooLong2 = 101214, SchedulerTurnTooLong3 = 101215, SchedulerWorkGroupShuttingDown = 101216, SchedulerEnqueueWorkWhenShutdown = 101217, SchedulerNotExecuteWhenShutdown = 101218, SchedulerAppTurnsStopped_1 = 101219, SchedulerWorkGroupStopping = 101220, SchedulerSkipWorkStopping = 101221, SchedulerSkipWorkCancelled = 101222, SchedulerTaskRunningOnWrongScheduler1 = 101223, SchedulerQueueWorkItemWrongCall = 101224, SchedulerQueueTaskWrongCall = 101225, SchedulerTaskExecuteIncomplete2 = 101226, SchedulerTaskExecuteIncomplete3 = 101227, SchedulerTaskExecuteIncomplete4 = 101228, SchedulerTaskWaitIncomplete = 101229, ExecutorWorkerThreadExc = 101230, SchedulerQueueWorkItemWrongContext = 101231, SchedulerAppTurnsStopped_2 = 101232, ExecutorProcessingError = 101233, GatewayBase = 101300, GatewayClientOpenedSocket = 101301, GatewayClientClosedSocket = 101302, GatewayDroppingClient = 101303, GatewayTryingToSendToUnrecognizedClient = 101304, GatewayByteCountMismatch = 101305, GatewayExceptionSendingToClient = 101306, GatewayAcceptor_SocketClosed = 101307, GatewayAcceptor_ExceptionReceiving = 101308, GatewayManager_FoundKnownGateways = 101309, MessageAcceptor_Connection = 101310, MessageAcceptor_NotAProxiedConnection = 101311, MessageAcceptor_UnexpectedProxiedConnection = 101312, GatewayManager_NoGateways = 101313, GatewayNetworkError = 101314, GatewayFailedToParse = 101315, ClientRegistrarFailedToRegister = 101316, ClientRegistrarFailedToRegister_2 = 101317, ClientRegistrarFailedToUnregister = 101318, ClientRegistrarTimerFailed = 101319, GatewayAcceptor_WrongClusterId = 101320, GatewayManager_AllGatewaysDead = 101321, GatewayAcceptor_InvalidSize = 101322, TimerBase = 101400, TimerDisposeError = 101401, TimerStopError = 101402, TimerQueueTickError = 101403, TimerChanging = 101404, TimerBeforeCallback = 101405, TimerAfterCallback = 101406, TimerNextTick = 101407, TimerDisposing = 101408, TimerStopped = 101409, Timer_TimerInsideGrainIsNotTicking = 101410, Timer_TimerInsideGrainIsDelayed = 101411, Timer_SafeTimerIsNotTicking = 101412, Timer_GrainTimerCallbackError = 101413, Timer_InvalidContext = 101414, DispatcherBase = 101500, Dispatcher_SelectTarget_FailPending = 101501, Dispatcher_RegisterCallback_Replaced = 101502, Dispatcher_Send_BufferResponse = 101503, Dispatcher_Send_AddressedMessage = 101504, Dispatcher_Receive_InvalidActivation = 101505, Dispatcher_WriteGrainFailed = 101506, Dispatcher_ActivationEndedTurn_Waiting = 101507, Dispatcher_Retarget = 101508, Dispatcher_TryAcceptMessage = 101509, Dispatcher_UpdateReceiveOrder = 101510, Dispatcher_ReceiveOrderCorrelation = 101511, Dispatcher_AddSendOrder = 101512, Dispatcher_AddSendOrderNoPrior = 101513, Dispatcher_AddSendOrder_PriorIds = 101514, Dispatcher_AddSendOrder_First = 101515, Dispatcher_EnqueueMessage = 101516, Dispatcher_AddressMsg = 101517, Dispatcher_AddressMsg_GrainOrder = 101518, Dispatcher_AddressMsg_NullingLastSentTo = 101519, Dispatcher_AddressMsg_SMPlacement = 101520, Dispatcher_AddressMsg_UnregisteredClient = 101521, Dispatcher_AddressMsg_SelectTarget = 101522, Dispatcher_HandleMsg = 101523, Dispatcher_OnActivationCompletedRequest_Waiting = 101524, IGC_DisposeError = 101525, IGC_SendRequest_NullContext = 101526, IGC_SniffIncomingMessage_Exc = 101527, Dispatcher_DetectedDeadlock = 101528, Dispatcher_ActivationOverloaded = 101530, IGC_SendResponseFailed = 101531, IGC_SendExceptionResponseFailed = 101532, IGC_UnhandledExceptionInInvoke = 101533, Dispatcher_ExtendedMessageProcessing = 101534, Dispatcher_FailedToUnregisterNonExistingAct = 101535, Dispatcher_NoGrainInstance = 101536, Dispatcher_RuntimeStatisticsUnavailable = 101537, Dispatcher_InvalidActivation = 101538, InvokeWorkItem_UnhandledExceptionInInvoke = 101539, Dispatcher_ErrorCreatingActivation = 101540, Dispatcher_StuckActivation = 101541, Dispatcher_FailedToUnregisterCallback = 101542, SerializationBase = 101600, Ser_AssemblyLoadError = 101601, Ser_BadRegisterSerializer = 101602, Ser_AssemblyLoadErrorDetails = 101603, Ser_AssemblyLoadSuccess = 101604, Ser_LargeObjectAllocated = 101605, LoaderBase = 101700, Loader_AssemblyLookupFailed = 101701, Loader_AssemblyLookupResolved = 101702, Loader_LoadingFromDir = 101703, Loader_LoadingFromFile = 101704, Loader_DirNotFound = 101705, Loader_LoadingSerInfo = 101706, Loader_LoadingGrainType = 101707, Loader_SkippingFile = 101708, Loader_SkippingDynamicAssembly = 101709, Loader_AssemblyInspectError = 101710, Loader_GrainTypeFullList = 101711, Loader_IgnoreAbstractGrainClass = 101712, Loader_AssemblyInspectionError = 101713, Loader_FoundBinary = 101714, Loader_IgnoreNonPublicGrainClass = 101715, Loader_UnexpectedException = 101716, Loader_SkippingBadAssembly = 101717, Loader_TypeLoadError_2 = 101718, Loader_TypeLoadError_3 = 101719, Loader_TypeLoadError_4 = 101720, Loader_LoadAndCreateInstance_Failure = 101721, Loader_TryLoadAndCreateInstance_Failure = 101722, Loader_TypeLoadError_5 = 101723, Loader_AssemblyLoadError = 101724, PlacementBase = 101800, Placement_RuntimeStatisticsUpdateFailure_1 = 101801, Placement_RuntimeStatisticsUpdateFailure_2 = 101802, Placement_RuntimeStatisticsUpdateFailure_3 = 101803, Placement_ActivationCountBasedDirector_NoSilos = 101804, StorageProviderBase = 102200, StorageProvider_ReadFailed = 102202, StorageProvider_WriteFailed = 102203, StorageProvider_DeleteFailed = 102204, StorageProvider_ForceReRead = 102205, SerializationManagerBase = 102400, SerMgr_TypeRegistrationFailure = 102401, SerMgr_MissingRegisterMethod = 102402, SerMgr_ErrorBindingMethods = 102403, SerMgr_ErrorLoadingAssemblyTypes = 102404, SerMgr_TooLongSerialize = 102405, SerMgr_TooLongDeserialize = 102406, SerMgr_TooLongDeepCopy = 102407, SerMgr_IgnoreAssembly = 102408, SerMgr_TypeRegistrationFailureIgnore = 102409, SerMgr_ArtifactReport = 102410, SerMgr_UnavailableSerializer = 102411, SerMgr_SerializationMethodsMissing = 102412, WatchdogBase = 102600, Watchdog_ParticipantThrownException = 102601, Watchdog_InternalError = 102602, Watchdog_HealthCheckFailure = 102603, LoggerBase = 102700, Logger_LogMessageTruncated = 102701, WFServiceBase = 102800, WFService_Error_1 = 102801, WFService_Error_2 = 102802, WFService_Error_3 = 102803, WFService_Error_4 = 102804, WFService_Error_5 = 102805, WFService_Error_6 = 102806, WFService_Error_7 = 102807, WFService_Error_8 = 102808, WFService_Error_9 = 102809, ReminderServiceBase = 102900, RS_Register_TableError = 102905, RS_Register_AlreadyRegistered = 102907, RS_Register_InvalidPeriod = 102908, RS_Register_NotRemindable = 102909, RS_NotResponsible = 102910, RS_Unregister_NotFoundLocally = 102911, RS_Unregister_TableError = 102912, RS_Table_Insert = 102913, RS_Table_Remove = 102914, RS_Tick_Delivery_Error = 102915, RS_Not_Started = 102916, RS_UnregisterGrain_TableError = 102917, RS_GrainBasedTable1 = 102918, RS_Factory1 = 102919, RS_FailedToReadTableAndStartTimer = 102920, RS_TableGrainInit1 = 102921, RS_TableGrainInit2 = 102922, RS_TableGrainInit3 = 102923, RS_GrainBasedTable2 = 102924, RS_ServiceStarting = 102925, RS_ServiceStarted = 102926, RS_ServiceStopping = 102927, RS_RegisterOrUpdate = 102928, RS_Unregister = 102929, RS_Stop = 102930, RS_RemoveFromTable = 102931, RS_GetReminder = 102932, RS_GetReminders = 102933, RS_RangeChanged = 102934, RS_LocalStop = 102935, RS_Started = 102936, RS_ServiceInitialLoadFailing = 102937, RS_ServiceInitialLoadFailed = 102938, RS_FastReminderInterval = 102939, ConsistentRingProviderBase = 103000, CRP_Local_Subscriber_Exception = 103001, CRP_ForGrains_Local_Subscriber_Exception_1 = 103002, CRP_Added_Silo = 103003, CRP_Removed_Silo = 103004, CRP_Notify = 103005, CRP_ForGrains_Local_Subscriber_Exception_2 = 103006, ProviderManagerBase = 103100, Provider_InstanceConstructionError1 = 103101, Provider_Loaded = 103102, Provider_AssemblyLoadError = 103103, Provider_CatalogNoStorageProvider_1 = 103104, Provider_CatalogNoStorageProvider_2 = 103105, Provider_CatalogStorageProviderAllocated = 103106, Provider_NoDefaultProvider = 103107, Provider_ConfiguredProviderNotLoaded = 103108, Provider_ErrorFromInit = 103109, Provider_IgnoringExplicitSet = 103110, Provider_NotLoaded = 103111, Provider_Manager_Already_Loaded = 103112, Provider_CatalogNoStorageProvider_3 = 103113, Provider_ProviderLoadedOk = 103114, Provider_ProviderNotFound = 103115, Provider_ProviderNotControllable = 103116, Provider_CatalogNoLogConsistencyProvider = 103117, Provider_CatalogLogConsistencyProviderAllocated = 103118, Provider_ErrorFromClose = 103119, PersistentStreamPullingAgentBase = 103300, PersistentStreamPullingAgent_01 = 103301, PersistentStreamPullingAgent_02 = 103302, PersistentStreamPullingAgent_03 = 103303, PersistentStreamPullingAgent_04 = 103304, PersistentStreamPullingAgent_05 = 103305, PersistentStreamPullingAgent_06 = 103306, PersistentStreamPullingAgent_07 = 103307, PersistentStreamPullingAgent_08 = 103308, PersistentStreamPullingAgent_09 = 103309, PersistentStreamPullingAgent_10 = 103310, PersistentStreamPullingAgent_11 = 103311, PersistentStreamPullingAgent_12 = 103312, PersistentStreamPullingAgent_13 = 103313, PersistentStreamPullingAgent_14 = 103314, PersistentStreamPullingAgent_15 = 103315, PersistentStreamPullingAgent_16 = 103316, PersistentStreamPullingAgent_17 = 103317, PersistentStreamPullingAgent_18 = 103318, PersistentStreamPullingAgent_19 = 103319, PersistentStreamPullingAgent_20 = 103320, PersistentStreamPullingAgent_21 = 103321, PersistentStreamPullingAgent_22 = 103322, PersistentStreamPullingAgent_23 = 103323, PersistentStreamPullingAgent_24 = 103324, PersistentStreamPullingAgent_25 = 103325, PersistentStreamPullingAgent_26 = 103326, PersistentStreamPullingAgent_27 = 103327, PersistentStreamPullingAgent_28 = 103328, StreamProviderManagerBase = 103400, StreamProvider_FailedToDispose = 103401, StreamProvider_ProducerFailedToUnregister = 103402, StreamProvider_NoStreamForItem = 103403, StreamProvider_AddObserverException = 103404, Stream_ExtensionNotInstalled = 103405, Stream_ProducerIsDead = 103406, StreamProvider_NoStreamForBatch = 103407, StreamProvider_ConsumerFailedToUnregister = 103408, Stream_ConsumerIsDead = 103409, Stream_RegisterProducerFailed = 103410, Stream_UnregisterProducerFailed = 103411, Stream_RegisterConsumerFailed = 103412, Stream_UnregisterConsumerFailed = 103413, Stream_SetSubscriptionToFaultedFailed = 103414, PersistentStreamPullingManagerBase = 103500, PersistentStreamPullingManager_01 = 103501, PersistentStreamPullingManager_02 = 103502, PersistentStreamPullingManager_03 = 103503, PersistentStreamPullingManager_04 = 103504, PersistentStreamPullingManager_05 = 103505, PersistentStreamPullingManager_06 = 103506, PersistentStreamPullingManager_07 = 103507, PersistentStreamPullingManager_08 = 103508, PersistentStreamPullingManager_09 = 103509, PersistentStreamPullingManager_10 = 103510, PersistentStreamPullingManager_11 = 103511, PersistentStreamPullingManager_12 = 103512, PersistentStreamPullingManager_13 = 103513, PersistentStreamPullingManager_14 = 103514, PersistentStreamPullingManager_15 = 103515, PersistentStreamPullingManager_16 = 103516, PersistentStreamPullingManager_Starting = 103517, PersistentStreamPullingManager_Stopping = 103518, PersistentStreamPullingManager_Started = 103519, PersistentStreamPullingManager_Stopped = 103520, PersistentStreamPullingManager_AlreadyStarted = 103521, PersistentStreamPullingManager_AlreadyStopped = 103522, PersistentStreamPullingManager_PeriodicPrint = 103523, AzureServiceRuntimeWrapper = 103700, AzureServiceRuntime_NotLoaded = 103701, AzureServiceRuntime_FailedToLoad = 103702, CodeGenBase = 103800, CodeGenCompilationFailed = 103801, CodeGenCompilationSucceeded = 103802, CodeGenSourceGenerated = 103803, CodeGenSerializerGenerator = 103804, CodeGenIgnoringTypes = 103805, CodeGenDllMissing = 103806, CodeGenSystemTypeRequiresSerializer = 103807, MultiClusterNetworkBase = 103900, MultiClusterNetwork_Starting = 103901, MultiClusterNetwork_Started = 103902, MultiClusterNetwork_FailedToStart = 103903, MultiClusterNetwork_LocalSubscriberException = 103904, MultiClusterNetwork_GossipCommunicationFailure = 103905, MultiClusterNetwork_NoChannelsConfigured = 103906, CancellationTokenManagerBase = 104000, CancellationTokenCancelFailed = 104001, CancellationExtensionCreationFailed = 104002, GlobalSingleInstanceBase = 104100, GlobalSingleInstance_ProtocolError = 104101, GlobalSingleInstance_WarningInvalidOrigin = 104102, GlobalSingleInstance_MaintainerException = 104103, GlobalSingleInstance_MultipleOwners = 104104, TypeManagerBase = 104200, TypeManager_GetSiloGrainInterfaceMapError = 104201, TypeManager_GetClusterGrainTypeResolverError = 104202, LogConsistencyBase = 104300, LogConsistency_UserCodeException = 104301, LogConsistency_CaughtException = 104302, LogConsistency_ProtocolError = 104303, LogConsistency_ProtocolFatalError = 104304, ServiceFabricBase = 104400, TransactionsBase = 104500, Transactions_SendingTMRequest = 104501, Transactions_ReceivedTMResponse = 104502, Transactions_TMError = 104503, OSBase = 104600, OS_InvalidOS = 104601 } public abstract partial class Grain : IGrainBase, Runtime.IAddressable { protected Grain() { } protected Grain(Runtime.IGrainContext grainContext, Runtime.IGrainRuntime? grainRuntime = null) { } public Runtime.IGrainContext GrainContext { get { throw null; } } protected IGrainFactory GrainFactory { get { throw null; } } public Runtime.GrainReference GrainReference { get { throw null; } } public string IdentityString { get { throw null; } } public string RuntimeIdentity { get { throw null; } } protected internal System.IServiceProvider ServiceProvider { get { throw null; } } protected void DeactivateOnIdle() { } protected void DelayDeactivation(System.TimeSpan timeSpan) { } protected void MigrateOnIdle() { } public virtual System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public virtual System.Threading.Tasks.Task OnDeactivateAsync(DeactivationReason reason, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Obsolete("Use 'this.RegisterGrainTimer(callback, state, new() { DueTime = dueTime, Period = period, Interleave = true })' instead.")] protected System.IDisposable RegisterTimer(System.Func callback, object? state, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } } public static partial class GrainBaseExtensions { public static void DeactivateOnIdle(this IGrainBase grain) { } public static void MigrateOnIdle(this IGrainBase grain) { } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, Runtime.GrainTimerCreationOptions options) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, Runtime.GrainTimerCreationOptions options) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, TState state, Runtime.GrainTimerCreationOptions options) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, TState state, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, TState state, Runtime.GrainTimerCreationOptions options) { throw null; } public static Runtime.IGrainTimer RegisterGrainTimer(this IGrainBase grain, System.Func callback, TState state, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } } public delegate System.Threading.Tasks.Task GrainCallFilterDelegate(IGrainCallContext context); [Immutable] public sealed partial class GrainCancellationToken : System.IDisposable { internal GrainCancellationToken() { } public System.Threading.CancellationToken CancellationToken { get { throw null; } } public void Dispose() { } } public sealed partial class GrainCancellationTokenSource : System.IDisposable { public bool IsCancellationRequested { get { throw null; } } public GrainCancellationToken Token { get { throw null; } } public System.Threading.Tasks.Task Cancel() { throw null; } public void Dispose() { } } public static partial class GrainContextComponentExtensions { public static TComponent GetGrainExtension(this Runtime.IGrainContext context) where TComponent : class, Runtime.IGrainExtension { throw null; } } public static partial class GrainExtensions { public static object AsReference(this Runtime.IAddressable grain, System.Type interfaceType) { throw null; } public static TGrainInterface AsReference(this Runtime.IAddressable grain) { throw null; } public static object Cast(this Runtime.IAddressable grain, System.Type interfaceType) { throw null; } public static TGrainInterface Cast(this Runtime.IAddressable grain) { throw null; } public static Runtime.GrainId GetGrainId(this Runtime.IAddressable grain) { throw null; } public static System.Guid GetPrimaryKey(this Runtime.IAddressable grain, out string? keyExt) { throw null; } public static System.Guid GetPrimaryKey(this Runtime.IAddressable grain) { throw null; } public static long GetPrimaryKeyLong(this Runtime.IAddressable grain, out string? keyExt) { throw null; } public static long GetPrimaryKeyLong(this Runtime.IAddressable grain) { throw null; } public static string GetPrimaryKeyString(this Runtime.IAddressable grain) { throw null; } public static bool IsPrimaryKeyBasedOnLong(this Runtime.IAddressable grain) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class GrainTypeAttribute : System.Attribute, Metadata.IGrainTypeProviderAttribute { public GrainTypeAttribute(string grainType) { } public Runtime.GrainType GetGrainType(System.IServiceProvider services, System.Type type) { throw null; } } public partial class Grain : Grain { protected Grain() { } protected Grain(Core.IStorage storage) { } protected TGrainState State { get { throw null; } set { } } protected virtual System.Threading.Tasks.Task ClearStateAsync() { throw null; } protected virtual System.Threading.Tasks.Task ReadStateAsync() { throw null; } protected virtual System.Threading.Tasks.Task WriteStateAsync() { throw null; } } public partial interface IConfigurationValidator { void ValidateConfiguration(); } public partial interface IGrain : Runtime.IAddressable { } public partial interface IGrainBase { Runtime.IGrainContext GrainContext { get; } System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken token); System.Threading.Tasks.Task OnDeactivateAsync(DeactivationReason reason, System.Threading.CancellationToken token); } public partial interface IGrainCallContext { object Grain { get; } System.Reflection.MethodInfo InterfaceMethod { get; } string InterfaceName { get; } Runtime.GrainInterfaceType InterfaceType { get; } string MethodName { get; } Serialization.Invocation.IInvokable Request { get; } Serialization.Invocation.Response? Response { get; set; } object? Result { get; set; } Runtime.GrainId? SourceId { get; } Runtime.GrainId TargetId { get; } System.Threading.Tasks.Task Invoke(); } public partial interface IGrainFactory { TGrainObserverInterface CreateObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver; void DeleteObjectReference(IGrainObserver obj) where TGrainObserverInterface : IGrainObserver; Runtime.IAddressable GetGrain(Runtime.GrainId grainId, Runtime.GrainInterfaceType interfaceType); Runtime.IAddressable GetGrain(Runtime.GrainId grainId); IGrain GetGrain(System.Type grainInterfaceType, System.Guid grainPrimaryKey, string keyExtension); IGrain GetGrain(System.Type grainInterfaceType, System.Guid grainPrimaryKey); IGrain GetGrain(System.Type grainInterfaceType, long grainPrimaryKey, string keyExtension); IGrain GetGrain(System.Type grainInterfaceType, long grainPrimaryKey); IGrain GetGrain(System.Type grainInterfaceType, string grainPrimaryKey); TGrainInterface GetGrain(Runtime.GrainId grainId) where TGrainInterface : Runtime.IAddressable; TGrainInterface GetGrain(System.Guid primaryKey, string keyExtension, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey; TGrainInterface GetGrain(System.Guid primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey; TGrainInterface GetGrain(long primaryKey, string keyExtension, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey; TGrainInterface GetGrain(long primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey; TGrainInterface GetGrain(string primaryKey, string? grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey; } public partial interface IGrainObserver : Runtime.IAddressable { } public partial interface IGrainWithGuidCompoundKey : IGrain, Runtime.IAddressable { } public partial interface IGrainWithGuidKey : IGrain, Runtime.IAddressable { } public partial interface IGrainWithIntegerCompoundKey : IGrain, Runtime.IAddressable { } public partial interface IGrainWithIntegerKey : IGrain, Runtime.IAddressable { } public partial interface IGrainWithStringKey : IGrain, Runtime.IAddressable { } public partial interface IIncomingGrainCallContext : IGrainCallContext { System.Reflection.MethodInfo ImplementationMethod { get; } Runtime.IGrainContext TargetContext { get; } } public partial interface IIncomingGrainCallFilter { System.Threading.Tasks.Task Invoke(IIncomingGrainCallContext context); } public partial interface ILifecycleObservable { System.IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer); } public partial interface ILifecycleObserver { System.Threading.Tasks.Task OnStart(System.Threading.CancellationToken cancellationToken = default); System.Threading.Tasks.Task OnStop(System.Threading.CancellationToken cancellationToken = default); } public partial interface ILifecycleParticipant where TLifecycleObservable : ILifecycleObservable { void Participate(TLifecycleObservable lifecycle); } public partial interface ILifecycleSubject : ILifecycleObservable, ILifecycleObserver { } public delegate System.Threading.Tasks.Task IncomingGrainCallFilterDelegate(IIncomingGrainCallContext context); public partial interface IOutgoingGrainCallContext : IGrainCallContext { Runtime.IGrainContext? SourceContext { get; } } public partial interface IOutgoingGrainCallFilter { System.Threading.Tasks.Task Invoke(IOutgoingGrainCallContext context); } public partial interface ISystemTarget : Runtime.IAddressable { } public partial interface IVersionManager { System.Threading.Tasks.Task SetCompatibilityStrategy(Runtime.GrainInterfaceType interfaceType, Versions.Compatibility.CompatibilityStrategy strategy); System.Threading.Tasks.Task SetCompatibilityStrategy(Versions.Compatibility.CompatibilityStrategy strategy); System.Threading.Tasks.Task SetSelectorStrategy(Runtime.GrainInterfaceType interfaceType, Versions.Selector.VersionSelectorStrategy strategy); System.Threading.Tasks.Task SetSelectorStrategy(Versions.Selector.VersionSelectorStrategy strategy); } public static partial class LifecycleExtensions { public static System.IDisposable Subscribe(this ILifecycleObservable observable, int stage, ILifecycleObserver observer) { throw null; } public static System.IDisposable Subscribe(this ILifecycleObservable observable, string observerName, int stage, System.Func onStart, System.Func? onStop) { throw null; } public static System.IDisposable Subscribe(this ILifecycleObservable observable, string observerName, int stage, System.Func onStart) { throw null; } public static System.IDisposable Subscribe(this ILifecycleObservable observable, int stage, ILifecycleObserver observer) { throw null; } public static System.IDisposable Subscribe(this ILifecycleObservable observable, int stage, System.Func onStart, System.Func onStop) { throw null; } public static System.IDisposable Subscribe(this ILifecycleObservable observable, int stage, System.Func onStart) { throw null; } } public delegate System.Threading.Tasks.Task OutgoingGrainCallFilterDelegate(IOutgoingGrainCallContext context); public static partial class PublicOrleansTaskExtensions { public static void Ignore(this System.Threading.Tasks.Task task) { } } public static partial class StableHash { public static uint ComputeHash(System.ReadOnlySpan data) { throw null; } public static uint ComputeHash(string data) { throw null; } } } namespace Orleans.CodeGeneration { [System.Flags] [GenerateSerializer] public enum InvokeMethodOptions { None = 0, OneWay = 1, ReadOnly = 2, AlwaysInterleave = 4, Unordered = 8 } [System.AttributeUsage(System.AttributeTargets.Interface)] public sealed partial class VersionAttribute : System.Attribute, Metadata.IGrainInterfacePropertiesProviderAttribute { public VersionAttribute(ushort version) { } public ushort Version { get { throw null; } } void Metadata.IGrainInterfacePropertiesProviderAttribute.Populate(System.IServiceProvider services, System.Type type, System.Collections.Generic.Dictionary properties) { } } } namespace Orleans.Concurrency { [InvokableCustomInitializer("AddInvokeMethodOptions", CodeGeneration.InvokeMethodOptions.AlwaysInterleave)] [System.AttributeUsage(System.AttributeTargets.Method)] public sealed partial class AlwaysInterleaveAttribute : System.Attribute { } public static partial class ImmutableExtensions { public static Immutable AsImmutable(this T value) { throw null; } } [GenerateSerializer] [Immutable] public readonly partial struct Immutable { [Id(0)] public readonly T Value; public Immutable(T value) { } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class MayInterleaveAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public MayInterleaveAttribute(string callbackMethodName) { } public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [InvokableCustomInitializer("AddInvokeMethodOptions", CodeGeneration.InvokeMethodOptions.OneWay)] [System.AttributeUsage(System.AttributeTargets.Method)] public sealed partial class OneWayAttribute : System.Attribute { } [InvokableCustomInitializer("AddInvokeMethodOptions", CodeGeneration.InvokeMethodOptions.ReadOnly)] [System.AttributeUsage(System.AttributeTargets.Method)] public sealed partial class ReadOnlyAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class ReentrantAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class StatelessWorkerAttribute : Placement.PlacementAttribute, Metadata.IGrainPropertiesProviderAttribute { public StatelessWorkerAttribute() : base(default!) { } public StatelessWorkerAttribute(int maxLocalWorkers, bool removeIdleWorkers) : base(default!) { } public StatelessWorkerAttribute(int maxLocalWorkers) : base(default!) { } public override void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [System.AttributeUsage(System.AttributeTargets.Interface)] public sealed partial class UnorderedAttribute : System.Attribute { } } namespace Orleans.Core { public partial interface IStorage { string? Etag { get; } bool RecordExists { get; } System.Threading.Tasks.Task ClearStateAsync(); System.Threading.Tasks.Task ClearStateAsync(System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.Task ReadStateAsync(); System.Threading.Tasks.Task ReadStateAsync(System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.Task WriteStateAsync(); System.Threading.Tasks.Task WriteStateAsync(System.Threading.CancellationToken cancellationToken); } public partial interface IStorage : IStorage { TState State { get; set; } } } namespace Orleans.Core.Internal { public partial interface ICallChainReentrantGrainContext { void OnEnterReentrantSection(System.Guid reentrancyId); void OnExitReentrantSection(System.Guid reentrancyId); } public partial interface IGrainManagementExtension : Runtime.IGrainExtension, Runtime.IAddressable { System.Threading.Tasks.ValueTask DeactivateOnIdle(); System.Threading.Tasks.ValueTask MigrateOnIdle(); } } namespace Orleans.GrainDirectory { [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class GrainDirectoryAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public const string DEFAULT_GRAIN_DIRECTORY = "default"; public GrainDirectoryAttribute() { } public GrainDirectoryAttribute(string grainDirectoryName) { } public string GrainDirectoryName { get { throw null; } set { } } public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public partial interface IGrainDirectory { System.Threading.Tasks.Task Lookup(Runtime.GrainId grainId); System.Threading.Tasks.Task Register(Runtime.GrainAddress address, Runtime.GrainAddress? previousAddress); System.Threading.Tasks.Task Register(Runtime.GrainAddress address); System.Threading.Tasks.Task Unregister(Runtime.GrainAddress address); System.Threading.Tasks.Task UnregisterSilos(System.Collections.Generic.List siloAddresses); } } namespace Orleans.Metadata { public sealed partial class AttributeGrainBindingsProvider : IGrainPropertiesProvider { public AttributeGrainBindingsProvider(System.IServiceProvider serviceProvider) { } public void Populate(System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public sealed partial class AttributeGrainPropertiesProvider : IGrainPropertiesProvider { public AttributeGrainPropertiesProvider(System.IServiceProvider serviceProvider) { } public void Populate(System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public partial class AttributeGrainTypeProvider : IGrainTypeProvider { public AttributeGrainTypeProvider(System.IServiceProvider serviceProvider) { } public bool TryGetGrainType(System.Type grainClass, out Runtime.GrainType grainType) { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class ClusterManifest { public ClusterManifest(MajorMinorVersion version, System.Collections.Immutable.ImmutableDictionary silos) { } [Id(2)] public System.Collections.Immutable.ImmutableArray AllGrainManifests { get { throw null; } } [Id(1)] public System.Collections.Immutable.ImmutableDictionary Silos { get { throw null; } } [Id(0)] public MajorMinorVersion Version { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false)] public sealed partial class DefaultGrainTypeAttribute : System.Attribute, IGrainInterfacePropertiesProviderAttribute { public DefaultGrainTypeAttribute(string grainType) { } void IGrainInterfacePropertiesProviderAttribute.Populate(System.IServiceProvider services, System.Type type, System.Collections.Generic.Dictionary properties) { } } [GenerateSerializer] [Immutable] public sealed partial class GrainInterfaceProperties { public GrainInterfaceProperties(System.Collections.Immutable.ImmutableDictionary values) { } [Id(0)] public System.Collections.Immutable.ImmutableDictionary Properties { get { throw null; } } public string ToDetailedString() { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class GrainManifest { public GrainManifest(System.Collections.Immutable.ImmutableDictionary grains, System.Collections.Immutable.ImmutableDictionary interfaces) { } [Id(1)] public System.Collections.Immutable.ImmutableDictionary Grains { get { throw null; } } [Id(0)] public System.Collections.Immutable.ImmutableDictionary Interfaces { get { throw null; } } } [GenerateSerializer] [Immutable] public sealed partial class GrainProperties { public GrainProperties(System.Collections.Immutable.ImmutableDictionary values) { } [Id(0)] public System.Collections.Immutable.ImmutableDictionary Properties { get { throw null; } } public string ToDetailedString() { throw null; } } public partial interface IGrainBindingsProviderAttribute { System.Collections.Generic.IEnumerable> GetBindings(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType); } public partial interface IGrainInterfacePropertiesProvider { void Populate(System.Type interfaceType, Runtime.GrainInterfaceType grainInterfaceType, System.Collections.Generic.Dictionary properties); } public partial interface IGrainInterfacePropertiesProviderAttribute { void Populate(System.IServiceProvider services, System.Type interfaceType, System.Collections.Generic.Dictionary properties); } public partial interface IGrainPropertiesProvider { void Populate(System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties); } public partial interface IGrainPropertiesProviderAttribute { void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties); } public partial interface IGrainTypeProvider { bool TryGetGrainType(System.Type type, out Runtime.GrainType grainType); } public partial interface IGrainTypeProviderAttribute { Runtime.GrainType GetGrainType(System.IServiceProvider services, System.Type type); } [GenerateSerializer] [Immutable] public readonly partial struct MajorMinorVersion : System.IComparable, System.IEquatable { private readonly int _dummyPrimitive; public MajorMinorVersion(long majorVersion, long minorVersion) { } [Id(0)] public long Major { get { throw null; } } [Id(1)] public long Minor { get { throw null; } } public static MajorMinorVersion MinValue { get { throw null; } } public static MajorMinorVersion Zero { get { throw null; } } public readonly int CompareTo(MajorMinorVersion other) { throw null; } public readonly bool Equals(MajorMinorVersion other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static bool operator >(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static bool operator >=(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static bool operator !=(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static bool operator <(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static bool operator <=(MajorMinorVersion left, MajorMinorVersion right) { throw null; } public static MajorMinorVersion Parse(string value) { throw null; } public override readonly string ToString() { throw null; } } public static partial class WellKnownGrainInterfaceProperties { public const string DefaultGrainType = "primary-grain-type"; public const string TypeName = "type-name"; public const string Version = "version"; } public static partial class WellKnownGrainTypeProperties { public const string BindingPrefix = "binding"; public const string BindingTypeKey = "type"; public const string BroadcastChannelBindingPatternKey = "channel-pattern"; public const string BroadcastChannelBindingTypeValue = "broadcast-channel"; public const string ChannelIdMapperKey = "channelid-mapper"; public const string FullTypeName = "full-type-name"; public const string GrainDirectory = "directory-policy"; public const string IdleDeactivationPeriod = "idle-duration"; public const string Immovable = "immovable"; public const string ImplementedInterfacePrefix = "interface."; public const string IndefiniteIdleDeactivationPeriodValue = "indefinite"; public const string LegacyGrainKeyType = "legacy-grain-key-type"; public const string MayInterleavePredicate = "may-interleave-predicate"; public const string PlacementFilter = "placement-filter"; public const string PlacementStrategy = "placement-strategy"; public const string Reentrant = "reentrant"; public const string StreamBindingIncludeNamespaceKey = "include-namespace"; public const string StreamBindingPatternKey = "pattern"; public const string StreamBindingTypeValue = "stream"; public const string StreamIdMapperKey = "streamid-mapper"; public const string TypeName = "type-name"; public const string Unordered = "unordered"; } } namespace Orleans.Placement { [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class ActivationCountBasedPlacementAttribute : PlacementAttribute { public ActivationCountBasedPlacementAttribute() : base(default!) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class HashBasedPlacementAttribute : PlacementAttribute { public HashBasedPlacementAttribute() : base(default!) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class ImmovableAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { public ImmovableAttribute(ImmovableKind kind = ImmovableKind.Any) { } public ImmovableKind Kind { get { throw null; } } public void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [System.Flags] public enum ImmovableKind : byte { Repartitioner = 1, Rebalancer = 2, Any = 3 } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public abstract partial class PlacementAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { protected PlacementAttribute(Runtime.PlacementStrategy placement) { } public Runtime.PlacementStrategy PlacementStrategy { get { throw null; } } public virtual void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public abstract partial class PlacementFilterAttribute : System.Attribute, Metadata.IGrainPropertiesProviderAttribute { protected PlacementFilterAttribute(PlacementFilterStrategy placement) { } public PlacementFilterStrategy PlacementFilterStrategy { get { throw null; } } public virtual void Populate(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } public abstract partial class PlacementFilterStrategy { protected PlacementFilterStrategy(int order) { } public int Order { get { throw null; } } public virtual void AdditionalInitialize(Metadata.GrainProperties properties) { } protected virtual System.Collections.Generic.IEnumerable> GetAdditionalGrainProperties(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.IReadOnlyDictionary existingProperties) { throw null; } protected string? GetPlacementFilterGrainProperty(string key, Metadata.GrainProperties properties) { throw null; } public void Initialize(Metadata.GrainProperties properties) { } public void PopulateGrainProperties(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class PreferLocalPlacementAttribute : PlacementAttribute { public PreferLocalPlacementAttribute() : base(default!) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class RandomPlacementAttribute : PlacementAttribute { public RandomPlacementAttribute() : base(default!) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class ResourceOptimizedPlacementAttribute : PlacementAttribute { public ResourceOptimizedPlacementAttribute() : base(default!) { } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public sealed partial class SiloRoleBasedPlacementAttribute : PlacementAttribute { public SiloRoleBasedPlacementAttribute() : base(default!) { } } } namespace Orleans.Providers { public partial interface IProviderBuilder { void Configure(TBuilder builder, string? name, Microsoft.Extensions.Configuration.IConfigurationSection configurationSection); } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class LogConsistencyProviderAttribute : System.Attribute { public string ProviderName { get { throw null; } set { } } } public static partial class ProviderConstants { public const string DEFAULT_LOG_CONSISTENCY_PROVIDER_NAME = "Default"; public const string DEFAULT_PUBSUB_PROVIDER_NAME = "PubSubStore"; public const string DEFAULT_STORAGE_PROVIDER_NAME = "Default"; } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class StorageProviderAttribute : System.Attribute { public string ProviderName { get { throw null; } set { } } } } namespace Orleans.Runtime { [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class ActivationCountBasedPlacement : PlacementStrategy { } [GenerateSerializer] [Immutable] [System.Text.Json.Serialization.JsonConverter(typeof(ActivationIdConverter))] public readonly partial struct ActivationId : System.IEquatable, System.ISpanFormattable, System.IFormattable { private readonly int _dummyPrimitive; public ActivationId(System.Guid key) { } public bool IsDefault { get { throw null; } } public readonly bool Equals(ActivationId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public static ActivationId FromParsableString(string activationId) { throw null; } public static ActivationId GetDeterministic(GrainId grain) { throw null; } public override readonly int GetHashCode() { throw null; } public static ActivationId NewId() { throw null; } public static bool operator ==(ActivationId left, ActivationId right) { throw null; } public static bool operator !=(ActivationId left, ActivationId right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public readonly string ToParsableString() { throw null; } public override readonly string ToString() { throw null; } } public sealed partial class ActivationIdConverter : System.Text.Json.Serialization.JsonConverter { public override ActivationId Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } public override void Write(System.Text.Json.Utf8JsonWriter writer, ActivationId value, System.Text.Json.JsonSerializerOptions options) { } } public static partial class AsyncEnumerableExtensions { public static System.Collections.Generic.IAsyncEnumerable WithBatchSize(this System.Collections.Generic.IAsyncEnumerable self, int maxBatchSize) { throw null; } } [GenerateSerializer] [SuppressReferenceTracking] [Invocation.ReturnValueProxy("InitializeRequest")] public abstract partial class AsyncEnumerableRequest : RequestBase, System.Collections.Generic.IAsyncEnumerable, IAsyncEnumerableRequest, IRequest, Orleans.Serialization.Invocation.IInvokable, System.IDisposable { [Id(0)] public int MaxBatchSize { get { throw null; } set { } } public System.Collections.Generic.IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default) { throw null; } public System.Collections.Generic.IAsyncEnumerable InitializeRequest(GrainReference targetGrainReference) { throw null; } public override System.Threading.Tasks.ValueTask Invoke() { throw null; } public System.Collections.Generic.IAsyncEnumerable InvokeImplementation() { throw null; } protected abstract System.Collections.Generic.IAsyncEnumerable InvokeInner(); } public partial class AttributeGrainInterfaceTypeProvider : IGrainInterfaceTypeProvider { public AttributeGrainInterfaceTypeProvider(System.IServiceProvider serviceProvider) { } public bool TryGetGrainInterfaceType(System.Type type, out GrainInterfaceType grainInterfaceType) { throw null; } } [GenerateSerializer] public sealed partial class ClientNotAvailableException : OrleansException { internal ClientNotAvailableException() { } } [GenerateSerializer] public sealed partial class EnumerationAbortedException : System.Exception { public EnumerationAbortedException() { } [System.Obsolete] protected EnumerationAbortedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public EnumerationAbortedException(string message, System.Exception innerException) { } public EnumerationAbortedException(string message) { } } [GenerateSerializer] public enum EnumerationResult { Heartbeat = 1, Element = 2, Batch = 4, Completed = 8, CompletedWithElement = 10, CompletedWithBatch = 12, MissingEnumeratorError = 16, Error = 32, Canceled = 64 } [GenerateSerializer] public sealed partial class GatewayTooBusyException : OrleansException { public GatewayTooBusyException() { } public GatewayTooBusyException(string message, System.Exception innerException) { } public GatewayTooBusyException(string message) { } } [GenerateSerializer] [Immutable] public sealed partial class GrainAddress : System.IEquatable, System.ISpanFormattable, System.IFormattable { public ActivationId ActivationId { get { throw null; } init { } } public GrainId GrainId { get { throw null; } init { } } [System.Text.Json.Serialization.JsonIgnore] public bool IsComplete { get { throw null; } } [Id(3)] public MembershipVersion MembershipVersion { get { throw null; } init { } } [Id(2)] public SiloAddress? SiloAddress { get { throw null; } init { } } public bool Equals(GrainAddress? other) { throw null; } public override bool Equals(object? obj) { throw null; } public override int GetHashCode() { throw null; } public bool Matches(GrainAddress? other) { throw null; } string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public string ToFullString() { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class GrainAddressCacheUpdate : System.ISpanFormattable, System.IFormattable { public GrainAddressCacheUpdate(GrainAddress invalidAddress, GrainAddress? validAddress) { } public GrainId GrainId { get { throw null; } } public ActivationId InvalidActivationId { get { throw null; } } public GrainAddress InvalidGrainAddress { get { throw null; } } public SiloAddress? InvalidSiloAddress { get { throw null; } } public GrainAddress? ValidGrainAddress { get { throw null; } } string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public string ToFullString() { throw null; } public override string ToString() { throw null; } } public static partial class GrainContextExtensions { [System.Obsolete("This method is error-prone: waiting deactivation to complete from within the grain being deactivated will usually result in a deadlock.")] public static System.Threading.Tasks.Task DeactivateAsync(this IGrainContext grainContext, DeactivationReason deactivationReason, System.Threading.CancellationToken cancellationToken = default) { throw null; } } [GenerateSerializer] public sealed partial class GrainExtensionNotInstalledException : OrleansException { public GrainExtensionNotInstalledException() { } public GrainExtensionNotInstalledException(string message, System.Exception innerException) { } public GrainExtensionNotInstalledException(string message) { } } [GenerateSerializer] [Immutable] [System.Text.Json.Serialization.JsonConverter(typeof(GrainIdJsonConverter))] public readonly partial struct GrainId : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable, System.ISpanParsable, System.IParsable { public GrainId(GrainType type, IdSpan key) { } public bool IsDefault { get { throw null; } } public IdSpan Key { get { throw null; } } public GrainType Type { get { throw null; } } public readonly int CompareTo(GrainId other) { throw null; } public static GrainId Create(GrainType type, IdSpan key) { throw null; } public static GrainId Create(GrainType type, string key) { throw null; } public static GrainId Create(string type, string key) { throw null; } public readonly bool Equals(GrainId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public readonly uint GetUniformHashCode() { throw null; } public static bool operator ==(GrainId left, GrainId right) { throw null; } public static bool operator !=(GrainId left, GrainId right) { throw null; } static GrainId System.ISpanParsable.Parse(System.ReadOnlySpan value, System.IFormatProvider? provider) { throw null; } static GrainId System.IParsable.Parse(string value, System.IFormatProvider? provider) { throw null; } public static GrainId Parse(string value) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } static bool System.ISpanParsable.TryParse(System.ReadOnlySpan value, System.IFormatProvider? provider, out GrainId result) { throw null; } public static bool TryParse(string? value, out GrainId result) { throw null; } static bool System.IParsable.TryParse(string? value, System.IFormatProvider? provider, out GrainId result) { throw null; } } public sealed partial class GrainIdJsonConverter : System.Text.Json.Serialization.JsonConverter { public override GrainId Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } public override GrainId ReadAsPropertyName(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } public override void Write(System.Text.Json.Utf8JsonWriter writer, GrainId value, System.Text.Json.JsonSerializerOptions options) { } public override void WriteAsPropertyName(System.Text.Json.Utf8JsonWriter writer, GrainId value, System.Text.Json.JsonSerializerOptions options) { } } public static partial class GrainIdKeyExtensions { public static IdSpan CreateGuidKey(System.Guid key, System.ReadOnlySpan keyExtension) { throw null; } public static IdSpan CreateGuidKey(System.Guid key, string? keyExtension) { throw null; } public static IdSpan CreateGuidKey(System.Guid key) { throw null; } public static IdSpan CreateIntegerKey(long key, System.ReadOnlySpan keyExtension) { throw null; } public static IdSpan CreateIntegerKey(long key, string? keyExtension) { throw null; } public static IdSpan CreateIntegerKey(long key) { throw null; } public static System.Guid GetGuidKey(this GrainId grainId, out string? keyExt) { throw null; } public static System.Guid GetGuidKey(this GrainId grainId) { throw null; } public static long GetIntegerKey(this GrainId grainId, out string? keyExt) { throw null; } public static long GetIntegerKey(this GrainId grainId) { throw null; } public static bool TryGetGuidKey(this GrainId grainId, out System.Guid key, out string? keyExt) { throw null; } public static bool TryGetIntegerKey(this GrainId grainId, out long key, out string? keyExt) { throw null; } } [GenerateSerializer] [Immutable] public readonly partial struct GrainInterfaceType : System.IEquatable, System.ISpanFormattable, System.IFormattable { public GrainInterfaceType(IdSpan value) { } public GrainInterfaceType(string value) { } public bool IsDefault { get { throw null; } } public IdSpan Value { get { throw null; } } public static GrainInterfaceType Create(string value) { throw null; } public readonly bool Equals(GrainInterfaceType other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(GrainInterfaceType left, GrainInterfaceType right) { throw null; } public static bool operator !=(GrainInterfaceType left, GrainInterfaceType right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } } [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = false)] public sealed partial class GrainInterfaceTypeAttribute : System.Attribute, IGrainInterfaceTypeProviderAttribute { public GrainInterfaceTypeAttribute(string value) { } public GrainInterfaceType GetGrainInterfaceType(System.IServiceProvider services, System.Type type) { throw null; } } public static partial class GrainLifecycleStage { public const int Activate = 2000; public const int First = int.MinValue; public const int Last = int.MaxValue; public const int SetupState = 1000; } [Alias("GrainRef")] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask<>), typeof(Request<>))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.ValueTask), typeof(Request))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.Task<>), typeof(TaskRequest<>))] [DefaultInvokableBaseType(typeof(System.Threading.Tasks.Task), typeof(TaskRequest))] [DefaultInvokableBaseType(typeof(void), typeof(VoidRequest))] [DefaultInvokableBaseType(typeof(System.Collections.Generic.IAsyncEnumerable<>), typeof(AsyncEnumerableRequest<>))] public partial class GrainReference : IAddressable, System.IEquatable, System.ISpanFormattable, System.IFormattable { protected GrainReference(GrainReferenceShared shared, IdSpan key) { } protected Orleans.Serialization.Serializers.CodecProvider CodecProvider { get { throw null; } } protected Orleans.Serialization.Cloning.CopyContextPool CopyContextPool { get { throw null; } } public GrainId GrainId { get { throw null; } } public virtual string InterfaceName { get { throw null; } } public GrainInterfaceType InterfaceType { get { throw null; } } public ushort InterfaceVersion { get { throw null; } } public virtual TGrainInterface Cast() where TGrainInterface : IAddressable { throw null; } public bool Equals(GrainReference? other) { throw null; } public override bool Equals(object? obj) { throw null; } public override int GetHashCode() { throw null; } protected TInvokable GetInvokable() { throw null; } public uint GetUniformHashCode() { throw null; } protected void Invoke(IRequest methodDescription) { } protected System.Threading.Tasks.ValueTask InvokeAsync(IRequest methodDescription) { throw null; } protected System.Threading.Tasks.ValueTask InvokeAsync(IRequest methodDescription) { throw null; } public static bool operator ==(GrainReference? reference1, GrainReference? reference2) { throw null; } public static bool operator !=(GrainReference? reference1, GrainReference? reference2) { throw null; } string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public sealed override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class GrainReferenceNotBoundException : OrleansException { internal GrainReferenceNotBoundException() { } } public partial class GrainReferenceShared { public GrainReferenceShared(GrainType grainType, GrainInterfaceType grainInterfaceType, ushort interfaceVersion, IGrainReferenceRuntime runtime, CodeGeneration.InvokeMethodOptions invokeMethodOptions, Orleans.Serialization.Serializers.CodecProvider codecProvider, Orleans.Serialization.Cloning.CopyContextPool copyContextPool, System.IServiceProvider serviceProvider) { } public Orleans.Serialization.Serializers.CodecProvider CodecProvider { get { throw null; } } public Orleans.Serialization.Cloning.CopyContextPool CopyContextPool { get { throw null; } } public GrainType GrainType { get { throw null; } } public GrainInterfaceType InterfaceType { get { throw null; } } public ushort InterfaceVersion { get { throw null; } } public CodeGeneration.InvokeMethodOptions InvokeMethodOptions { get { throw null; } } public IGrainReferenceRuntime Runtime { get { throw null; } } public System.IServiceProvider ServiceProvider { get { throw null; } } } public readonly partial struct GrainTimerCreationOptions { private readonly int _dummyPrimitive; public GrainTimerCreationOptions() { } [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] public GrainTimerCreationOptions(System.TimeSpan dueTime, System.TimeSpan period) { } public required System.TimeSpan DueTime { get { throw null; } init { } } public bool Interleave { get { throw null; } init { } } public bool KeepAlive { get { throw null; } init { } } public required System.TimeSpan Period { get { throw null; } init { } } } [GenerateSerializer] [Immutable] public readonly partial struct GrainType : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable { public GrainType(IdSpan id) { } public GrainType(byte[] value) { } public bool IsDefault { get { throw null; } } public IdSpan Value { get { throw null; } } public readonly System.ReadOnlySpan AsSpan() { throw null; } public readonly int CompareTo(GrainType other) { throw null; } public static GrainType Create(string value) { throw null; } public readonly bool Equals(GrainType obj) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public readonly uint GetUniformHashCode() { throw null; } public static bool operator ==(GrainType left, GrainType right) { throw null; } public static explicit operator IdSpan(GrainType kind) { throw null; } public static explicit operator GrainType(IdSpan id) { throw null; } public static bool operator !=(GrainType left, GrainType right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string? ToString() { throw null; } public static byte[]? UnsafeGetArray(GrainType id) { throw null; } } public static partial class GrainTypePrefix { public static readonly GrainType ClientGrainType; public const string ClientPrefix = "sys.client"; public static readonly System.ReadOnlyMemory ClientPrefixBytes; public const string GrainServicePrefix = "sys.svc.user."; public static readonly System.ReadOnlyMemory GrainServicePrefixBytes; public const string LegacyGrainPrefix = "sys.grain.v1."; public static readonly System.ReadOnlyMemory LegacyGrainPrefixBytes; public const string SystemPrefix = "sys."; public const string SystemTargetPrefix = "sys.svc."; public static readonly System.ReadOnlyMemory SystemTargetPrefixBytes; public static bool IsClient(this in GrainId id) { throw null; } public static bool IsClient(this in GrainType type) { throw null; } public static bool IsGrainService(this in GrainType type) { throw null; } public static bool IsLegacyGrain(this in GrainType type) { throw null; } public static bool IsSystemTarget(this in GrainId id) { throw null; } public static bool IsSystemTarget(this in GrainType type) { throw null; } } [Immutable] [GenerateSerializer] public sealed partial class GuidId : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable { internal GuidId() { } [Id(0)] public readonly System.Guid Guid; public int CompareTo(GuidId? other) { throw null; } public bool Equals(GuidId? other) { throw null; } public override bool Equals(object? obj) { throw null; } public static GuidId GetGuidId(System.Guid guid) { throw null; } public override int GetHashCode() { throw null; } public static GuidId GetNewGuidId() { throw null; } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static bool operator ==(GuidId? left, GuidId? right) { throw null; } public static bool operator !=(GuidId? left, GuidId? right) { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class HashBasedPlacement : PlacementStrategy { } [GenerateMethodSerializers(typeof(GrainReference), false)] public partial interface IAddressable { } public partial interface IAsyncEnumerableGrainExtension : IGrainExtension, IAddressable { [Concurrency.AlwaysInterleave] System.Threading.Tasks.ValueTask DisposeAsync(System.Guid requestId); [Concurrency.AlwaysInterleave] System.Threading.Tasks.ValueTask<(EnumerationResult Status, object Value)> MoveNext(System.Guid requestId); [Concurrency.AlwaysInterleave] System.Threading.Tasks.ValueTask<(EnumerationResult Status, object Value)> StartEnumeration(System.Guid requestId, IAsyncEnumerableRequest request); } public partial interface IAsyncEnumerableRequest : IRequest, Orleans.Serialization.Invocation.IInvokable, System.IDisposable { int MaxBatchSize { get; set; } System.Collections.Generic.IAsyncEnumerable InvokeImplementation(); } public partial interface IDehydrationContext { System.Collections.Generic.IEnumerable Keys { get; } void AddBytes(string key, System.ReadOnlySpan value); void AddBytes(string key, System.Action> valueWriter, T value); bool TryAddValue(string key, T? value); } [GenerateSerializer] [Immutable] public readonly partial struct IdSpan : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable { private readonly object _dummy; private readonly int _dummyPrimitive; public IdSpan(byte[] value) { } public bool IsDefault { get { throw null; } } public System.ReadOnlyMemory Value { get { throw null; } } public readonly System.ReadOnlySpan AsSpan() { throw null; } public readonly int CompareTo(IdSpan other) { throw null; } public static IdSpan Create(string id) { throw null; } public readonly bool Equals(IdSpan obj) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public readonly uint GetUniformHashCode() { throw null; } public static bool operator ==(IdSpan left, IdSpan right) { throw null; } public static bool operator !=(IdSpan left, IdSpan right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } public readonly bool TryFormat(System.Span destination, out int charsWritten) { throw null; } public static IdSpan UnsafeCreate(byte[]? value, int hashCode) { throw null; } public static byte[]? UnsafeGetArray(IdSpan id) { throw null; } } [RegisterSerializer] public sealed partial class IdSpanCodec : Orleans.Serialization.Codecs.IFieldCodec, Orleans.Serialization.Codecs.IFieldCodec { public static IdSpan ReadRaw(ref Orleans.Serialization.Buffers.Reader reader) { throw null; } public IdSpan ReadValue(ref Orleans.Serialization.Buffers.Reader reader, Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, IdSpan value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteRaw(ref Orleans.Serialization.Buffers.Writer writer, IdSpan value) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial interface IGrainContext : Orleans.Serialization.Invocation.ITargetHolder, System.IEquatable { ActivationId ActivationId { get; } System.IServiceProvider ActivationServices { get; } GrainAddress Address { get; } System.Threading.Tasks.Task Deactivated { get; } GrainId GrainId { get; } object? GrainInstance { get; } GrainReference GrainReference { get; } IGrainLifecycle ObservableLifecycle { get; } IWorkItemScheduler Scheduler { get; } void Activate(System.Collections.Generic.Dictionary? requestContext, System.Threading.CancellationToken cancellationToken = default); void Deactivate(DeactivationReason deactivationReason, System.Threading.CancellationToken cancellationToken = default); void Migrate(System.Collections.Generic.Dictionary? requestContext, System.Threading.CancellationToken cancellationToken = default); void ReceiveMessage(object message); void Rehydrate(IRehydrationContext context); void SetComponent(TComponent? value) where TComponent : class; } public partial interface IGrainContextAccessor { IGrainContext GrainContext { get; } } [GenerateMethodSerializers(typeof(GrainReference), true)] public partial interface IGrainExtension : IAddressable { } public partial interface IGrainExtensionBinder { TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension; (TExtension, TExtensionInterface) GetOrSetExtension(System.Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension; } public partial interface IGrainInterfaceTypeProvider { bool TryGetGrainInterfaceType(System.Type type, out GrainInterfaceType grainInterfaceType); } public partial interface IGrainInterfaceTypeProviderAttribute { GrainInterfaceType GetGrainInterfaceType(System.IServiceProvider services, System.Type type); } public partial interface IGrainLifecycle : ILifecycleObservable { void AddMigrationParticipant(IGrainMigrationParticipant participant); void RemoveMigrationParticipant(IGrainMigrationParticipant participant); } public partial interface IGrainMigrationParticipant { void OnDehydrate(IDehydrationContext dehydrationContext); void OnRehydrate(IRehydrationContext rehydrationContext); } public partial interface IGrainReferenceRuntime { object Cast(IAddressable grain, System.Type interfaceType); void InvokeMethod(GrainReference reference, Orleans.Serialization.Invocation.IInvokable request, CodeGeneration.InvokeMethodOptions options); System.Threading.Tasks.ValueTask InvokeMethodAsync(GrainReference reference, Orleans.Serialization.Invocation.IInvokable request, CodeGeneration.InvokeMethodOptions options); System.Threading.Tasks.ValueTask InvokeMethodAsync(GrainReference reference, Orleans.Serialization.Invocation.IInvokable request, CodeGeneration.InvokeMethodOptions options); } public partial interface IGrainRuntime { IGrainFactory GrainFactory { get; } System.IServiceProvider ServiceProvider { get; } SiloAddress SiloAddress { get; } string SiloIdentity { get; } System.TimeProvider TimeProvider { get; } Timers.ITimerRegistry TimerRegistry { get; } void DeactivateOnIdle(IGrainContext grainContext); void DelayDeactivation(IGrainContext grainContext, System.TimeSpan timeSpan); Core.IStorage GetStorage(IGrainContext grainContext); } public partial interface IGrainTimer : System.IDisposable { void Change(System.TimeSpan dueTime, System.TimeSpan period); } public partial interface IRehydrationContext { System.Collections.Generic.IEnumerable Keys { get; } bool TryGetBytes(string key, out System.Buffers.ReadOnlySequence value); bool TryGetValue(string key, out T? value); } public partial interface IRequest : Orleans.Serialization.Invocation.IInvokable, System.IDisposable { CodeGeneration.InvokeMethodOptions Options { get; } void AddInvokeMethodOptions(CodeGeneration.InvokeMethodOptions options); string ToMethodCallString(IRequest request); string ToString(IRequest request); } public partial interface IWorkItemScheduler { void QueueAction(System.Action action); void QueueAction(System.Action action, object state); void QueueTask(System.Threading.Tasks.Task task); } [GenerateSerializer] [Immutable] public sealed partial class LegacyGrainId : System.IEquatable, System.IComparable { internal LegacyGrainId() { } public UniqueKey.Category Category { get { throw null; } } public string IdentityString { get { throw null; } } public bool IsClient { get { throw null; } } public bool IsGrain { get { throw null; } } public bool IsLongKey { get { throw null; } } public bool IsSystemTarget { get { throw null; } } public System.Guid PrimaryKey { get { throw null; } } public long PrimaryKeyLong { get { throw null; } } public string PrimaryKeyString { get { throw null; } } public int TypeCode { get { throw null; } } public int CompareTo(LegacyGrainId? other) { throw null; } public static GrainType CreateGrainTypeForGrain(int typeCode) { throw null; } public static GrainType CreateGrainTypeForSystemTarget(int typeCode) { throw null; } public bool Equals(LegacyGrainId? other) { throw null; } public override bool Equals(object? obj) { throw null; } public static LegacyGrainId FromGrainId(GrainId id) { throw null; } public override int GetHashCode() { throw null; } public uint GetHashCode_Modulo(uint umod) { throw null; } public System.Guid GetPrimaryKey(out string? keyExt) { throw null; } public long GetPrimaryKeyLong(out string? keyExt) { throw null; } public uint GetUniformHashCode() { throw null; } public static bool IsLegacyGrainType(System.Type type) { throw null; } public static bool IsLegacyKeyExtGrainType(System.Type type) { throw null; } public static LegacyGrainId NewClientId() { throw null; } public static LegacyGrainId NewId() { throw null; } public static implicit operator GrainId(LegacyGrainId legacy) { throw null; } public GrainId ToGrainId() { throw null; } public override string ToString() { throw null; } public static bool TryConvertFromGrainId(GrainId id, out LegacyGrainId? legacyId) { throw null; } } [GenerateSerializer] public sealed partial class LimitExceededException : OrleansException { public LimitExceededException() { } public LimitExceededException(string message, System.Exception innerException) { } public LimitExceededException(string limitName, int current, int threshold, object extraInfo) { } public LimitExceededException(string message) { } } public static partial class LogFormatter { public const int MAX_LOG_MESSAGE_SIZE = 20000; public static System.DateTime ParseDate(string dateStr) { throw null; } public static string PrintDate(System.DateTime date) { throw null; } public static string PrintException(System.Exception exception) { throw null; } public static string PrintTime(System.DateTime date) { throw null; } public static void SetExceptionDecoder(System.Type exceptionType, System.Func decoder) { } } [GenerateSerializer] [Immutable] [System.Text.Json.Serialization.JsonConverter(typeof(MembershipVersionConverter))] public readonly partial struct MembershipVersion : System.IComparable, System.IEquatable { private readonly int _dummyPrimitive; public MembershipVersion(long version) { } public static MembershipVersion MinValue { get { throw null; } } [Id(0)] public long Value { get { throw null; } init { } } public readonly int CompareTo(MembershipVersion other) { throw null; } public readonly bool Equals(MembershipVersion other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(MembershipVersion left, MembershipVersion right) { throw null; } public static bool operator >(MembershipVersion left, MembershipVersion right) { throw null; } public static bool operator >=(MembershipVersion left, MembershipVersion right) { throw null; } public static bool operator !=(MembershipVersion left, MembershipVersion right) { throw null; } public static bool operator <(MembershipVersion left, MembershipVersion right) { throw null; } public static bool operator <=(MembershipVersion left, MembershipVersion right) { throw null; } public override readonly string ToString() { throw null; } } public sealed partial class MembershipVersionConverter : System.Text.Json.Serialization.JsonConverter { public override MembershipVersion Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } public override void Write(System.Text.Json.Utf8JsonWriter writer, MembershipVersion value, System.Text.Json.JsonSerializerOptions options) { } } [GenerateSerializer] public sealed partial class OrleansConfigurationException : System.Exception { public OrleansConfigurationException(string message, System.Exception innerException) { } public OrleansConfigurationException(string message) { } } [GenerateSerializer] public partial class OrleansException : System.Exception { public OrleansException() { } [System.Obsolete] protected OrleansException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public OrleansException(string message, System.Exception innerException) { } public OrleansException(string message) { } } [GenerateSerializer] public sealed partial class OrleansLifecycleCanceledException : OrleansException { internal OrleansLifecycleCanceledException() { } } [GenerateSerializer] public partial class OrleansMessageRejectionException : OrleansException { [System.Obsolete] protected OrleansMessageRejectionException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [SerializerTransparent] public abstract partial class PlacementStrategy { public virtual bool IsUsingGrainDirectory { get { throw null; } } public virtual void Initialize(Metadata.GrainProperties properties) { } public virtual void PopulateGrainProperties(System.IServiceProvider services, System.Type grainClass, GrainType grainType, System.Collections.Generic.Dictionary properties) { } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class PreferLocalPlacement : PlacementStrategy { } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class RandomPlacement : PlacementStrategy { } [SerializerTransparent] public abstract partial class Request : RequestBase { public sealed override System.Threading.Tasks.ValueTask Invoke() { throw null; } protected abstract System.Threading.Tasks.ValueTask InvokeInner(); } [SuppressReferenceTracking] [SerializerTransparent] public abstract partial class RequestBase : IRequest, Orleans.Serialization.Invocation.IInvokable, System.IDisposable { public CodeGeneration.InvokeMethodOptions Options { get { throw null; } protected set { } } public void AddInvokeMethodOptions(CodeGeneration.InvokeMethodOptions options) { } public abstract void Dispose(); public abstract string GetActivityName(); public virtual object GetArgument(int index) { throw null; } public virtual int GetArgumentCount() { throw null; } public virtual System.Threading.CancellationToken GetCancellationToken() { throw null; } public virtual System.TimeSpan? GetDefaultResponseTimeout() { throw null; } public abstract string GetInterfaceName(); public abstract System.Type GetInterfaceType(); public abstract System.Reflection.MethodInfo GetMethod(); public abstract string GetMethodName(); public abstract object GetTarget(); public abstract System.Threading.Tasks.ValueTask Invoke(); public virtual void SetArgument(int index, object value) { } public abstract void SetTarget(Orleans.Serialization.Invocation.ITargetHolder holder); public override string ToString() { throw null; } public virtual bool TryCancel() { throw null; } } public static partial class RequestContext { public static System.Collections.Generic.IEnumerable> Entries { get { throw null; } } public static System.Collections.Generic.IEnumerable Keys { get { throw null; } } public static System.Guid ReentrancyId { get { throw null; } set { } } public static ReentrancySection AllowCallChainReentrancy() { throw null; } public static void Clear() { } public static object? Get(string key) { throw null; } public static bool Remove(string key) { throw null; } public static void Set(string key, object value) { } public static ReentrancySection SuppressCallChainReentrancy() { throw null; } public readonly partial struct ReentrancySection : System.IDisposable { private readonly int _dummyPrimitive; public ReentrancySection(System.Guid originalReentrancyId, System.Guid newReentrancyId) { } public readonly void Dispose() { } } } [SerializerTransparent] public abstract partial class Request : RequestBase { public sealed override System.Threading.Tasks.ValueTask Invoke() { throw null; } protected abstract System.Threading.Tasks.ValueTask InvokeInner(); } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class ResourceOptimizedPlacement : PlacementStrategy { } [Immutable] [System.Text.Json.Serialization.JsonConverter(typeof(SiloAddressConverter))] [System.Diagnostics.DebuggerDisplay("SiloAddress {ToString()}")] [SuppressReferenceTracking] public sealed partial class SiloAddress : System.IEquatable, System.IComparable, System.ISpanFormattable, System.IFormattable { internal SiloAddress() { } [Id(0)] public System.Net.IPEndPoint Endpoint { get { throw null; } } [Id(1)] public int Generation { get { throw null; } } public bool IsClient { get { throw null; } } public static SiloAddress Zero { get { throw null; } } public static int AllocateNewGeneration() { throw null; } public int CompareTo(SiloAddress? other) { throw null; } public bool Equals(SiloAddress? other) { throw null; } public override bool Equals(object? obj) { throw null; } public static SiloAddress FromParsableString(string addr) { throw null; } public static SiloAddress FromUtf8String(System.ReadOnlySpan addr) { throw null; } public int GetConsistentHashCode() { throw null; } public override int GetHashCode() { throw null; } public uint[] GetUniformHashCodes(int numHashes) { throw null; } public bool IsPredecessorOf(SiloAddress other) { throw null; } public bool IsSuccessorOf(SiloAddress other) { throw null; } public static SiloAddress New(System.Net.IPAddress address, int port, int generation) { throw null; } public static SiloAddress New(System.Net.IPEndPoint ep, int gen) { throw null; } string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public string ToParsableString() { throw null; } public override string ToString() { throw null; } public string ToStringWithHashCode() { throw null; } } public sealed partial class SiloAddressConverter : System.Text.Json.Serialization.JsonConverter { public override SiloAddress? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } public override void Write(System.Text.Json.Utf8JsonWriter writer, SiloAddress value, System.Text.Json.JsonSerializerOptions options) { } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public partial class SiloRoleBasedPlacement : PlacementStrategy { } [GenerateSerializer] public sealed partial class SiloUnavailableException : OrleansMessageRejectionException { public SiloUnavailableException() : base(default!, default) { } public SiloUnavailableException(string message, System.Exception innerException) : base(default!, default) { } public SiloUnavailableException(string msg) : base(default!, default) { } } [Immutable] public readonly partial struct SystemTargetGrainId : System.IEquatable, System.IComparable, System.ISpanFormattable, System.IFormattable { public GrainId GrainId { get { throw null; } } public readonly int CompareTo(SystemTargetGrainId other) { throw null; } public static SystemTargetGrainId Create(GrainType kind, SiloAddress address, string? extraIdentifier) { throw null; } public static SystemTargetGrainId Create(GrainType kind, SiloAddress address) { throw null; } public static GrainId CreateGrainServiceGrainId(int typeCode, string grainSystemId, SiloAddress address) { throw null; } public static GrainType CreateGrainType(string name) { throw null; } public readonly bool Equals(SystemTargetGrainId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly SiloAddress GetSiloAddress() { throw null; } public static bool IsSystemTargetGrainId(in GrainId id) { throw null; } public static bool operator ==(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } public static bool operator >(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } public static bool operator >=(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } public static bool operator !=(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } public static bool operator <(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } public static bool operator <=(SystemTargetGrainId left, SystemTargetGrainId right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } public static bool TryParse(GrainId grainId, out SystemTargetGrainId systemTargetId) { throw null; } public readonly SystemTargetGrainId WithSiloAddress(SiloAddress siloAddress) { throw null; } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class SystemTargetPlacementStrategy : PlacementStrategy { public static SystemTargetPlacementStrategy Instance { get { throw null; } } public override bool IsUsingGrainDirectory { get { throw null; } } } [SerializerTransparent] public abstract partial class TaskRequest : RequestBase { public sealed override System.Threading.Tasks.ValueTask Invoke() { throw null; } protected abstract System.Threading.Tasks.Task InvokeInner(); } [SerializerTransparent] public abstract partial class TaskRequest : RequestBase { public sealed override System.Threading.Tasks.ValueTask Invoke() { throw null; } protected abstract System.Threading.Tasks.Task InvokeInner(); } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class UniqueKey : System.IComparable, System.IEquatable { public int BaseTypeCode { get { throw null; } } public bool HasKeyExt { get { throw null; } } public Category IdCategory { get { throw null; } } public bool IsLongKey { get { throw null; } } public bool IsSystemTargetKey { get { throw null; } } [Id(3)] public string? KeyExt { get { throw null; } } [Id(0)] public ulong N0 { get { throw null; } } [Id(1)] public ulong N1 { get { throw null; } } [Id(2)] public ulong TypeCodeData { get { throw null; } } public int CompareTo(UniqueKey? other) { throw null; } public bool Equals(UniqueKey? other) { throw null; } public override bool Equals(object? o) { throw null; } public override int GetHashCode() { throw null; } public static UniqueKey NewGrainServiceKey(short key, long typeData) { throw null; } public static UniqueKey NewGrainServiceKey(string key, long typeData) { throw null; } public static UniqueKey NewKey() { throw null; } public static UniqueKey NewSystemTargetKey(System.Guid guid, long typeData) { throw null; } public static UniqueKey NewSystemTargetKey(short systemId) { throw null; } public System.Guid PrimaryKeyToGuid() { throw null; } public System.Guid PrimaryKeyToGuid(out string? extendedKey) { throw null; } public long PrimaryKeyToLong() { throw null; } public long PrimaryKeyToLong(out string? extendedKey) { throw null; } public override string ToString() { throw null; } public enum Category : byte { None = 0, SystemTarget = 1, SystemGrain = 2, Grain = 3, Client = 4, KeyExtGrain = 6, KeyExtSystemTarget = 8 } } public static partial class Utils { public static float AverageTicksToMilliSeconds(float ticks) { throw null; } public static System.Collections.Generic.IEnumerable> BatchIEnumerable(this System.Collections.Generic.IEnumerable sequence, int batchSize) { throw null; } public static string DictionaryToString(System.Collections.Generic.ICollection> dict, System.Func? toString = null, string? separator = null) { throw null; } public static string EnumerableToString(System.Collections.Generic.IEnumerable? collection, System.Func? toString = null, string separator = ", ", bool putInBrackets = true) { throw null; } public static string GetStackTrace(int skipFrames = 0) { throw null; } public static void SafeExecute(System.Action action, Microsoft.Extensions.Logging.ILogger? logger = null, string? caller = null) { } public static void SafeExecute(System.Action action) { } public static System.Threading.Tasks.Task SafeExecuteAsync(System.Threading.Tasks.Task task) { throw null; } public static long TicksToMilliSeconds(long ticks) { throw null; } public static string TimeSpanToString(System.TimeSpan timeSpan) { throw null; } public static SiloAddress? ToGatewayAddress(this System.Uri uri) { throw null; } public static System.Uri ToGatewayUri(this SiloAddress address) { throw null; } public static System.Uri ToGatewayUri(this System.Net.IPEndPoint ep) { throw null; } public static System.Net.IPEndPoint? ToIPEndPoint(this System.Uri uri) { throw null; } } [SerializerTransparent] public abstract partial class VoidRequest : RequestBase { public sealed override System.Threading.Tasks.ValueTask Invoke() { throw null; } protected abstract void InvokeInner(); } [GenerateSerializer] public partial class WrappedException : OrleansException { [System.Obsolete] protected WrappedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public WrappedException(string message) { } [Id(0)] public string? OriginalExceptionType { get { throw null; } set { } } [System.Diagnostics.CodeAnalysis.DoesNotReturn] public static void CreateAndRethrow(System.Exception exception) { } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public override string ToString() { throw null; } } } namespace Orleans.Runtime.Serialization { [RegisterSerializer] public sealed partial class SiloAddressCodec : Orleans.Serialization.Codecs.IFieldCodec, Orleans.Serialization.Codecs.IFieldCodec { public SiloAddress ReadValue(ref Orleans.Serialization.Buffers.Reader reader, Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, SiloAddress? value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace Orleans.Serialization { public abstract partial class DeserializationContext { public abstract object RuntimeClient { get; } public abstract System.IServiceProvider ServiceProvider { get; } } public partial interface IOnDeserialized { void OnDeserialized(DeserializationContext context); } } namespace Orleans.Services { public partial interface IGrainService : ISystemTarget, Runtime.IAddressable { } public partial interface IGrainServiceClient where TGrainService : IGrainService { } } namespace Orleans.Statistics { [Immutable] [GenerateSerializer] [Alias("Orleans.Statistics.EnvironmentStatistics")] [System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] public readonly partial struct EnvironmentStatistics { [Id(2)] public readonly long FilteredAvailableMemoryBytes; [Id(0)] public readonly float FilteredCpuUsagePercentage; [Id(1)] public readonly long FilteredMemoryUsageBytes; [Id(3)] public readonly long MaximumAvailableMemoryBytes; [Id(6)] public readonly long RawAvailableMemoryBytes; [Id(4)] public readonly float RawCpuUsagePercentage; [Id(5)] public readonly long RawMemoryUsageBytes; public float AvailableMemoryPercentage { get { throw null; } } public float MemoryUsagePercentage { get { throw null; } } public float NormalizedAvailableMemory { get { throw null; } } public float NormalizedFilteredAvailableMemory { get { throw null; } } public float NormalizedFilteredMemoryUsage { get { throw null; } } public float NormalizedMemoryUsage { get { throw null; } } public override readonly string ToString() { throw null; } } [System.Obsolete("This functionality will be removed, use IEnvironmentStatisticsProvider.GetEnvironmentStatistics instead.")] public partial interface IAppEnvironmentStatistics { long? MemoryUsage { get; } } public partial interface IEnvironmentStatisticsProvider { EnvironmentStatistics GetEnvironmentStatistics(); } [System.Obsolete("This functionality will be removed, use IEnvironmentStatisticsProvider.GetEnvironmentStatistics instead.")] public partial interface IHostEnvironmentStatistics { long? AvailableMemory { get; } float? CpuUsage { get; } long? TotalPhysicalMemory { get; } } } namespace Orleans.Timers { public partial interface ITimerRegistry { Runtime.IGrainTimer RegisterGrainTimer(Runtime.IGrainContext grainContext, System.Func callback, TState state, Runtime.GrainTimerCreationOptions options); [System.Obsolete("Use 'RegisterGrainTimer(grainContext, callback, state, new() { DueTime = dueTime, Period = period, Interleave = true })' instead.")] System.IDisposable RegisterTimer(Runtime.IGrainContext grainContext, System.Func callback, object? state, System.TimeSpan dueTime, System.TimeSpan period); } } namespace Orleans.Versions { public partial interface IVersionStore : IVersionManager { bool IsEnabled { get; } System.Threading.Tasks.Task> GetCompatibilityStrategies(); System.Threading.Tasks.Task GetCompatibilityStrategy(); System.Threading.Tasks.Task> GetSelectorStrategies(); System.Threading.Tasks.Task GetSelectorStrategy(); } } namespace Orleans.Versions.Compatibility { [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class AllVersionsCompatible : CompatibilityStrategy { public static AllVersionsCompatible Singleton { get { throw null; } } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class BackwardCompatible : CompatibilityStrategy { public static BackwardCompatible Singleton { get { throw null; } } } [SerializerTransparent] public abstract partial class CompatibilityStrategy { } public partial interface ICompatibilityDirector { bool IsCompatible(ushort requestedVersion, ushort currentVersion); } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class StrictVersionCompatible : CompatibilityStrategy { public static StrictVersionCompatible Singleton { get { throw null; } } } } namespace Orleans.Versions.Selector { [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class AllCompatibleVersions : VersionSelectorStrategy { public static AllCompatibleVersions Singleton { get { throw null; } } } public partial interface IVersionSelector { ushort[] GetSuitableVersion(ushort requestedVersion, ushort[] availableVersions, Compatibility.ICompatibilityDirector compatibilityDirector); } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class LatestVersion : VersionSelectorStrategy { public static LatestVersion Singleton { get { throw null; } } } [GenerateSerializer] [Immutable] [SuppressReferenceTracking] public sealed partial class MinimumVersion : VersionSelectorStrategy { public static MinimumVersion Singleton { get { throw null; } } } [SerializerTransparent] public abstract partial class VersionSelectorStrategy { } } namespace OrleansCodeGen.Orleans { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DeactivationReasonCode : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.DeactivationReasonCode ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.DeactivationReasonCode value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.CodeGeneration { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_InvokeMethodOptions : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.CodeGeneration.InvokeMethodOptions ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.CodeGeneration.InvokeMethodOptions value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Concurrency { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Immutable : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_Immutable(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Concurrency.Immutable instance) { } public global::Orleans.Concurrency.Immutable ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Concurrency.Immutable instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Concurrency.Immutable value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Immutable : global::Orleans.Serialization.Cloning.ShallowCopier> { } } namespace OrleansCodeGen.Orleans.Core.Internal { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 instance) { } public Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 instance) { } public Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 DeepCopy(Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 DeepCopy(Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Core.Internal.IGrainManagementExtension), typeof(global::Orleans.Core.Internal.IGrainManagementExtension), "1B9614D1" })] public sealed partial class Invokable_IGrainManagementExtension_GrainReference_Ext_1B9614D1 : global::Orleans.Runtime.Request { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Core.Internal.IGrainManagementExtension), typeof(global::Orleans.Core.Internal.IGrainManagementExtension), "4CC93B45" })] public sealed partial class Invokable_IGrainManagementExtension_GrainReference_Ext_4CC93B45 : global::Orleans.Runtime.Request { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Metadata { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClusterManifest : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ClusterManifest(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Metadata.ClusterManifest instance) { } public global::Orleans.Metadata.ClusterManifest ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Metadata.ClusterManifest instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Metadata.ClusterManifest value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainInterfaceProperties : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainInterfaceProperties(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Metadata.GrainInterfaceProperties instance) { } public global::Orleans.Metadata.GrainInterfaceProperties ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Metadata.GrainInterfaceProperties instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Metadata.GrainInterfaceProperties value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainManifest : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainManifest(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Metadata.GrainManifest instance) { } public global::Orleans.Metadata.GrainManifest ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Metadata.GrainManifest instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Metadata.GrainManifest value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainProperties : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainProperties(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Metadata.GrainProperties instance) { } public global::Orleans.Metadata.GrainProperties ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Metadata.GrainProperties instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Metadata.GrainProperties value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MajorMinorVersion : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Metadata.MajorMinorVersion instance) { } public global::Orleans.Metadata.MajorMinorVersion ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Metadata.MajorMinorVersion instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Metadata.MajorMinorVersion value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Runtime { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ActivationCountBasedPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ActivationCountBasedPlacement instance) { } public global::Orleans.Runtime.ActivationCountBasedPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ActivationCountBasedPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ActivationCountBasedPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ActivationId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.ActivationId instance) { } public global::Orleans.Runtime.ActivationId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.ActivationId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ActivationId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AsyncEnumerableRequest : global::Orleans.Serialization.Serializers.AbstractTypeSerializer> { public override void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.AsyncEnumerableRequest instance) { } public override void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.AsyncEnumerableRequest instance) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClientNotAvailableException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ClientNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ClientNotAvailableException instance) { } public global::Orleans.Runtime.ClientNotAvailableException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ClientNotAvailableException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ClientNotAvailableException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EnumerationAbortedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_EnumerationAbortedException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.EnumerationAbortedException instance) { } public global::Orleans.Runtime.EnumerationAbortedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.EnumerationAbortedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.EnumerationAbortedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EnumerationResult : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Runtime.EnumerationResult ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.EnumerationResult value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GatewayTooBusyException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GatewayTooBusyException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GatewayTooBusyException instance) { } public global::Orleans.Runtime.GatewayTooBusyException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GatewayTooBusyException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GatewayTooBusyException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainAddress : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainAddress(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GrainAddress instance) { } public global::Orleans.Runtime.GrainAddress ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GrainAddress instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainAddress value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainAddressCacheUpdate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainAddressCacheUpdate(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GrainAddressCacheUpdate instance) { } public global::Orleans.Runtime.GrainAddressCacheUpdate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GrainAddressCacheUpdate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainAddressCacheUpdate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainExtensionNotInstalledException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainExtensionNotInstalledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GrainExtensionNotInstalledException instance) { } public global::Orleans.Runtime.GrainExtensionNotInstalledException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GrainExtensionNotInstalledException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainExtensionNotInstalledException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_GrainId(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.GrainId instance) { } public global::Orleans.Runtime.GrainId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.GrainId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainInterfaceType : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_GrainInterfaceType(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.GrainInterfaceType instance) { } public global::Orleans.Runtime.GrainInterfaceType ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.GrainInterfaceType instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainInterfaceType value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainReferenceNotBoundException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GrainReferenceNotBoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GrainReferenceNotBoundException instance) { } public global::Orleans.Runtime.GrainReferenceNotBoundException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GrainReferenceNotBoundException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainReferenceNotBoundException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainType : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_GrainType(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.GrainType instance) { } public global::Orleans.Runtime.GrainType ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.GrainType instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GrainType value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GuidId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GuidId(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.GuidId instance) { } public global::Orleans.Runtime.GuidId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.GuidId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.GuidId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_HashBasedPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.HashBasedPlacement instance) { } public global::Orleans.Runtime.HashBasedPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.HashBasedPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.HashBasedPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_IdSpan : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.IdSpan instance) { } public global::Orleans.Runtime.IdSpan ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.IdSpan instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.IdSpan value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 instance) { } public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 instance) { } public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 instance) { } public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LegacyGrainId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_LegacyGrainId(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.LegacyGrainId instance) { } public global::Orleans.Runtime.LegacyGrainId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.LegacyGrainId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.LegacyGrainId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LimitExceededException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_LimitExceededException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.LimitExceededException instance) { } public global::Orleans.Runtime.LimitExceededException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.LimitExceededException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.LimitExceededException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MembershipVersion : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.MembershipVersion instance) { } public global::Orleans.Runtime.MembershipVersion ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.MembershipVersion instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.MembershipVersion value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansConfigurationException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansConfigurationException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.OrleansConfigurationException instance) { } public global::Orleans.Runtime.OrleansConfigurationException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.OrleansConfigurationException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.OrleansConfigurationException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_OrleansException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.OrleansException instance) { } public global::Orleans.Runtime.OrleansException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.OrleansException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.OrleansException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansLifecycleCanceledException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansLifecycleCanceledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.OrleansLifecycleCanceledException instance) { } public global::Orleans.Runtime.OrleansLifecycleCanceledException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.OrleansLifecycleCanceledException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.OrleansLifecycleCanceledException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansMessageRejectionException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_OrleansMessageRejectionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.OrleansMessageRejectionException instance) { } public global::Orleans.Runtime.OrleansMessageRejectionException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.OrleansMessageRejectionException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.OrleansMessageRejectionException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_PreferLocalPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.PreferLocalPlacement instance) { } public global::Orleans.Runtime.PreferLocalPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.PreferLocalPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.PreferLocalPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RandomPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.RandomPlacement instance) { } public global::Orleans.Runtime.RandomPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.RandomPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.RandomPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ResourceOptimizedPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ResourceOptimizedPlacement instance) { } public global::Orleans.Runtime.ResourceOptimizedPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ResourceOptimizedPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ResourceOptimizedPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SiloRoleBasedPlacement : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.SiloRoleBasedPlacement instance) { } public global::Orleans.Runtime.SiloRoleBasedPlacement ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.SiloRoleBasedPlacement instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SiloRoleBasedPlacement value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SiloUnavailableException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_SiloUnavailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.SiloUnavailableException instance) { } public global::Orleans.Runtime.SiloUnavailableException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.SiloUnavailableException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SiloUnavailableException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SystemTargetPlacementStrategy : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.SystemTargetPlacementStrategy instance) { } public global::Orleans.Runtime.SystemTargetPlacementStrategy ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.SystemTargetPlacementStrategy instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.SystemTargetPlacementStrategy value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UniqueKey : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.UniqueKey instance) { } public global::Orleans.Runtime.UniqueKey ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.UniqueKey instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.UniqueKey value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_WrappedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_WrappedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.WrappedException instance) { } public global::Orleans.Runtime.WrappedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.WrappedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.WrappedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_AsyncEnumerableRequest : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier>, global::Orleans.Serialization.Cloning.IBaseCopier { public void DeepCopy(global::Orleans.Runtime.AsyncEnumerableRequest input, global::Orleans.Runtime.AsyncEnumerableRequest output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Runtime.AsyncEnumerableRequest DeepCopy(global::Orleans.Runtime.AsyncEnumerableRequest original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ClientNotAvailableException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ClientNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EnumerationAbortedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_EnumerationAbortedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GatewayTooBusyException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_GatewayTooBusyException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainExtensionNotInstalledException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_GrainExtensionNotInstalledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainReferenceNotBoundException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_GrainReferenceNotBoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 DeepCopy(Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 DeepCopy(Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 DeepCopy(Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_LimitExceededException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_LimitExceededException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansConfigurationException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansConfigurationException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansLifecycleCanceledException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansLifecycleCanceledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansMessageRejectionException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansMessageRejectionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SiloRoleBasedPlacement : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public void DeepCopy(global::Orleans.Runtime.SiloRoleBasedPlacement input, global::Orleans.Runtime.SiloRoleBasedPlacement output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Runtime.SiloRoleBasedPlacement DeepCopy(global::Orleans.Runtime.SiloRoleBasedPlacement original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SiloUnavailableException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_SiloUnavailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_WrappedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_WrappedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Runtime.WrappedException input, global::Orleans.Runtime.WrappedException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), "370CD5AB" })] public sealed partial class Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_370CD5AB_1 : global::Orleans.Runtime.Request<(global::Orleans.Runtime.EnumerationResult, object)> { public System.Guid arg0; public global::Orleans.Runtime.IAsyncEnumerableRequest arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask<(global::Orleans.Runtime.EnumerationResult, object)> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), "3C6D7209" })] public sealed partial class Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_3C6D7209 : global::Orleans.Runtime.Request { public System.Guid arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), typeof(global::Orleans.Runtime.IAsyncEnumerableGrainExtension), "A7FA7E30" })] public sealed partial class Invokable_IAsyncEnumerableGrainExtension_GrainReference_Ext_A7FA7E30_1 : global::Orleans.Runtime.Request<(global::Orleans.Runtime.EnumerationResult, object)> { public System.Guid arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.ValueTask<(global::Orleans.Runtime.EnumerationResult, object)> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Statistics { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EnvironmentStatistics : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Statistics.EnvironmentStatistics instance) { } public global::Orleans.Statistics.EnvironmentStatistics ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Statistics.EnvironmentStatistics instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Statistics.EnvironmentStatistics value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Versions.Compatibility { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AllVersionsCompatible : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Compatibility.AllVersionsCompatible instance) { } public global::Orleans.Versions.Compatibility.AllVersionsCompatible ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Compatibility.AllVersionsCompatible instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Compatibility.AllVersionsCompatible value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_BackwardCompatible : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Compatibility.BackwardCompatible instance) { } public global::Orleans.Versions.Compatibility.BackwardCompatible ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Compatibility.BackwardCompatible instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Compatibility.BackwardCompatible value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StrictVersionCompatible : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Compatibility.StrictVersionCompatible instance) { } public global::Orleans.Versions.Compatibility.StrictVersionCompatible ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Compatibility.StrictVersionCompatible instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Compatibility.StrictVersionCompatible value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Versions.Selector { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AllCompatibleVersions : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Selector.AllCompatibleVersions instance) { } public global::Orleans.Versions.Selector.AllCompatibleVersions ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Selector.AllCompatibleVersions instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Selector.AllCompatibleVersions value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LatestVersion : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Selector.LatestVersion instance) { } public global::Orleans.Versions.Selector.LatestVersion ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Selector.LatestVersion instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Selector.LatestVersion value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MinimumVersion : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Versions.Selector.MinimumVersion instance) { } public global::Orleans.Versions.Selector.MinimumVersion ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Versions.Selector.MinimumVersion instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Versions.Selector.MinimumVersion value) where TBufferWriter : System.Buffers.IBufferWriter { } } } ================================================ FILE: src/api/Orleans.EventSourcing/Orleans.EventSourcing.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class CustomStorageLogConsistencyOptions { public string PrimaryCluster { get { throw null; } set { } } } } namespace Orleans.EventSourcing { [GenerateSerializer] public abstract partial class ConnectionIssue { [Id(2)] public int NumberOfConsecutiveFailures { get { throw null; } set { } } [Id(3)] public System.TimeSpan RetryDelay { get { throw null; } set { } } [Id(1)] public System.DateTime TimeOfFirstFailure { get { throw null; } set { } } [Id(0)] public System.DateTime TimeStamp { get { throw null; } set { } } public abstract System.TimeSpan ComputeRetryDelay(System.TimeSpan? previous); } public partial interface IConnectionIssueListener { void OnConnectionIssue(ConnectionIssue connectionIssue); void OnConnectionIssueResolved(ConnectionIssue connectionIssue); } public partial interface ILogConsistencyDiagnostics { void DisableStatsCollection(); void EnableStatsCollection(); LogConsistencyStatistics GetStats(); } public partial interface ILogConsistencyProtocolMessage { } public partial interface ILogConsistencyProtocolParticipant : IGrain, Runtime.IAddressable { System.Threading.Tasks.Task DeactivateProtocolParticipant(); System.Threading.Tasks.Task PostActivateProtocolParticipant(); System.Threading.Tasks.Task PreActivateProtocolParticipant(); } public partial interface ILogConsistencyProtocolServices { Runtime.GrainId GrainId { get; } string MyClusterId { get; } void CaughtException(string where, System.Exception e); void CaughtUserCodeException(string callback, string where, System.Exception e); T DeepCopy(T value); void Log(Microsoft.Extensions.Logging.LogLevel level, string format, params object[] args); void ProtocolError(string msg, bool throwexception); } public partial interface ILogViewAdaptorFactory { bool UsesStorageProvider { get; } ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TLogView initialState, string grainTypeName, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TLogView : class, new() where TLogEntry : class; } public partial interface ILogViewAdaptorHost : IConnectionIssueListener { void OnViewChanged(bool tentative, bool confirmed); void UpdateView(TLogView view, TLogEntry entry); } public partial interface ILogViewAdaptor : ILogViewRead, ILogViewUpdate, ILogConsistencyDiagnostics where TLogView : new() { System.Threading.Tasks.Task PostOnActivate(); System.Threading.Tasks.Task PostOnDeactivate(); System.Threading.Tasks.Task PreOnActivate(); } public partial interface ILogViewRead { int ConfirmedVersion { get; } TView ConfirmedView { get; } TView TentativeView { get; } System.Collections.Generic.IEnumerable UnconfirmedSuffix { get; } System.Threading.Tasks.Task> RetrieveLogSegment(int fromVersion, int toVersion); } public partial interface ILogViewUpdate { System.Threading.Tasks.Task ClearLogAsync(System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.Task ConfirmSubmittedEntries(); void Submit(TLogEntry entry); void SubmitRange(System.Collections.Generic.IEnumerable entries); System.Threading.Tasks.Task Synchronize(); System.Threading.Tasks.Task TryAppend(TLogEntry entry); System.Threading.Tasks.Task TryAppendRange(System.Collections.Generic.IEnumerable entries); } public abstract partial class JournaledGrain : JournaledGrain where TGrainState : class, new() { } public abstract partial class JournaledGrain : LogConsistentGrain, ILogConsistencyProtocolParticipant, IGrain, Runtime.IAddressable, ILogViewAdaptorHost, IConnectionIssueListener where TGrainState : class, new() where TEventBase : class { protected override ILogViewAdaptorFactory DefaultAdaptorFactory { get { throw null; } } protected TGrainState State { get { throw null; } } protected TGrainState TentativeState { get { throw null; } } public System.Collections.Generic.IEnumerable UnconfirmedEvents { get { throw null; } } protected int Version { get { throw null; } } protected System.Threading.Tasks.Task ConfirmEvents() { throw null; } protected void DisableStatsCollection() { } protected void EnableStatsCollection() { } protected LogConsistencyStatistics GetStats() { throw null; } protected override void InstallAdaptor(ILogViewAdaptorFactory factory, object initialState, string graintypename, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services) { } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } protected virtual void OnConnectionIssue(ConnectionIssue issue) { } protected virtual void OnConnectionIssueResolved(ConnectionIssue issue) { } protected virtual void OnStateChanged() { } protected virtual void OnTentativeStateChanged() { } void IConnectionIssueListener.OnConnectionIssue(ConnectionIssue connectionIssue) { } void IConnectionIssueListener.OnConnectionIssueResolved(ConnectionIssue connectionIssue) { } System.Threading.Tasks.Task ILogConsistencyProtocolParticipant.DeactivateProtocolParticipant() { throw null; } System.Threading.Tasks.Task ILogConsistencyProtocolParticipant.PostActivateProtocolParticipant() { throw null; } System.Threading.Tasks.Task ILogConsistencyProtocolParticipant.PreActivateProtocolParticipant() { throw null; } void ILogViewAdaptorHost.OnViewChanged(bool tentative, bool confirmed) { } void ILogViewAdaptorHost.UpdateView(TGrainState view, TEventBase entry) { } protected virtual System.Threading.Tasks.Task RaiseConditionalEvent(TEvent @event) where TEvent : TEventBase { throw null; } protected virtual System.Threading.Tasks.Task RaiseConditionalEvents(System.Collections.Generic.IEnumerable events) where TEvent : TEventBase { throw null; } protected virtual void RaiseEvent(TEvent @event) where TEvent : TEventBase { } protected virtual void RaiseEvents(System.Collections.Generic.IEnumerable events) where TEvent : TEventBase { } protected System.Threading.Tasks.Task RefreshNow() { throw null; } protected System.Threading.Tasks.Task> RetrieveConfirmedEvents(int fromVersion, int toVersion) { throw null; } protected virtual void TransitionState(TGrainState state, TEventBase @event) { } } public partial class LogConsistencyStatistics { public System.Collections.Generic.Dictionary EventCounters; public System.Collections.Generic.List StabilizationLatenciesInMsecs; } public abstract partial class LogConsistentGrain : Grain, ILifecycleParticipant { protected abstract ILogViewAdaptorFactory DefaultAdaptorFactory { get; } protected abstract void InstallAdaptor(ILogViewAdaptorFactory factory, object state, string grainTypeName, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services); public virtual void Participate(Runtime.IGrainLifecycle lifecycle) { } } [GenerateSerializer] public sealed partial class ProtocolTransportException : Runtime.OrleansException { public ProtocolTransportException() { } [System.Obsolete] protected ProtocolTransportException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public ProtocolTransportException(string msg, System.Exception exc) { } public ProtocolTransportException(string msg) { } public override string ToString() { throw null; } } } namespace Orleans.EventSourcing.Common { [GenerateSerializer] public sealed partial class BatchedNotificationMessage : INotificationMessage, ILogConsistencyProtocolMessage { [Id(0)] public System.Collections.Generic.List Notifications { get { throw null; } set { } } public int Version { get { throw null; } } } public partial interface INotificationMessage : ILogConsistencyProtocolMessage { int Version { get; } } public abstract partial class PrimaryBasedLogViewAdaptor : ILogViewAdaptor, ILogViewRead, ILogViewUpdate, ILogConsistencyDiagnostics where TLogView : class, new() where TLogEntry : class where TSubmissionEntry : SubmissionEntry { protected RecordedConnectionIssue LastPrimaryIssue; protected LogConsistencyStatistics stats; protected PrimaryBasedLogViewAdaptor(ILogViewAdaptorHost host, TLogView initialstate, ILogConsistencyProtocolServices services) { } public int ConfirmedVersion { get { throw null; } } public TLogView ConfirmedView { get { throw null; } } protected ILogViewAdaptorHost Host { get { throw null; } } protected ILogConsistencyProtocolServices Services { get { throw null; } } protected virtual bool SupportSubmissions { get { throw null; } } public TLogView TentativeView { get { throw null; } } public System.Collections.Generic.IEnumerable UnconfirmedSuffix { get { throw null; } } public System.Threading.Tasks.Task ConfirmSubmittedEntries() { throw null; } protected TLogView CopyTentativeState() { throw null; } public void DisableStatsCollection() { } public virtual void EnableStatsCollection() { } protected System.Threading.Tasks.Task EnsureClusterJoinedAsync() { throw null; } protected abstract int GetConfirmedVersion(); protected TSubmissionEntry[] GetCurrentBatchOfUpdates() { throw null; } protected int GetNumberPendingUpdates() { throw null; } public LogConsistencyStatistics GetStats() { throw null; } protected abstract void InitializeConfirmedView(TLogView initialstate); protected bool IsMyClusterJoined() { throw null; } protected abstract TLogView LastConfirmedView(); protected abstract TSubmissionEntry MakeSubmissionEntry(TLogEntry entry); protected virtual INotificationMessage Merge(INotificationMessage earliermessage, INotificationMessage latermessage) { throw null; } protected void NotifyPromises(int count, bool success) { } protected virtual System.Threading.Tasks.Task OnMessageReceived(ILogConsistencyProtocolMessage payload) { throw null; } protected virtual void OnNotificationReceived(INotificationMessage payload) { } public System.Threading.Tasks.Task OnProtocolMessageReceived(ILogConsistencyProtocolMessage payLoad) { throw null; } public virtual System.Threading.Tasks.Task PostOnActivate() { throw null; } public virtual System.Threading.Tasks.Task PostOnDeactivate() { throw null; } public virtual System.Threading.Tasks.Task PreOnActivate() { throw null; } protected virtual void ProcessNotifications() { } protected abstract System.Threading.Tasks.Task ReadAsync(); protected void RemoveStaleConditionalUpdates() { } public virtual System.Threading.Tasks.Task> RetrieveLogSegment(int fromVersion, int length) { throw null; } public void Submit(TLogEntry logEntry) { } public void SubmitRange(System.Collections.Generic.IEnumerable logEntries) { } public System.Threading.Tasks.Task Synchronize() { throw null; } public System.Threading.Tasks.Task TryAppend(TLogEntry logEntry) { throw null; } public System.Threading.Tasks.Task TryAppendRange(System.Collections.Generic.IEnumerable logEntries) { throw null; } protected abstract System.Threading.Tasks.Task WriteAsync(); } [GenerateSerializer] public abstract partial class PrimaryOperationFailed : ConnectionIssue { [Id(0)] public System.Exception Exception { get { throw null; } set { } } public override System.TimeSpan ComputeRetryDelay(System.TimeSpan? previous) { throw null; } } public partial struct RecordedConnectionIssue { private object _dummy; private int _dummyPrimitive; public ConnectionIssue Issue { get { throw null; } } public readonly System.Threading.Tasks.Task DelayBeforeRetry() { throw null; } public void Record(ConnectionIssue newIssue, IConnectionIssueListener listener, ILogConsistencyProtocolServices services) { } public void Resolve(IConnectionIssueListener listener, ILogConsistencyProtocolServices services) { } public override readonly string ToString() { throw null; } } public static partial class StringEncodedWriteVector { public static bool FlipBit(ref string writeVector, string Replica) { throw null; } public static bool GetBit(string writeVector, string Replica) { throw null; } } public partial class SubmissionEntry { public int ConditionalPosition; public TLogEntry Entry; public System.Threading.Tasks.TaskCompletionSource ResultPromise; public System.DateTime SubmissionTime; } [GenerateSerializer] public sealed partial class VersionNotificationMessage : INotificationMessage, ILogConsistencyProtocolMessage { [Id(0)] public int Version { get { throw null; } set { } } } } namespace Orleans.EventSourcing.CustomStorage { public partial interface ICustomStorageInterface { System.Threading.Tasks.Task ApplyUpdatesToStorage(System.Collections.Generic.IReadOnlyList updates, int expectedVersion); System.Threading.Tasks.Task> ReadStateFromStorage(); } public partial class LogConsistencyProvider : ILogViewAdaptorFactory { public LogConsistencyProvider(Configuration.CustomStorageLogConsistencyOptions options) { } public string PrimaryCluster { get { throw null; } } public bool UsesStorageProvider { get { throw null; } } public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { throw null; } } public static partial class LogConsistencyProviderFactory { public static ILogViewAdaptorFactory Create(System.IServiceProvider services, string name) { throw null; } } } namespace Orleans.EventSourcing.LogStorage { public partial class LogConsistencyProvider : ILogViewAdaptorFactory { public bool UsesStorageProvider { get { throw null; } } public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { throw null; } } [GenerateSerializer] public sealed partial class LogStateWithMetaDataAndETag : IGrainState> where TEntry : class { [Id(1)] public string ETag { get { throw null; } set { } } [Id(2)] public bool RecordExists { get { throw null; } set { } } public LogStateWithMetaData State { get { throw null; } set { } } [Id(0)] public LogStateWithMetaData StateAndMetaData { get { throw null; } set { } } public override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class LogStateWithMetaData where TEntry : class { public int GlobalVersion { get { throw null; } } [Id(0)] public System.Collections.Generic.List Log { get { throw null; } set { } } [Id(1)] public string WriteVector { get { throw null; } set { } } public bool FlipBit(string replica) { throw null; } public bool GetBit(string replica) { throw null; } } } namespace Orleans.EventSourcing.StateStorage { [GenerateSerializer] public sealed partial class GrainStateWithMetaDataAndETag : IGrainState> where TView : class, new() { public GrainStateWithMetaDataAndETag() { } public GrainStateWithMetaDataAndETag(TView initialview) { } [Id(1)] public string ETag { get { throw null; } set { } } [Id(2)] public bool RecordExists { get { throw null; } set { } } public GrainStateWithMetaData State { get { throw null; } set { } } [Id(0)] public GrainStateWithMetaData StateAndMetaData { get { throw null; } set { } } public override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class GrainStateWithMetaData where TView : class, new() { public GrainStateWithMetaData() { } public GrainStateWithMetaData(TView initialstate) { } [Id(1)] public int GlobalVersion { get { throw null; } set { } } [Id(0)] public TView State { get { throw null; } set { } } [Id(2)] public string WriteVector { get { throw null; } set { } } public bool FlipBit(string Replica) { throw null; } public bool GetBit(string Replica) { throw null; } } public partial class LogConsistencyProvider : ILogViewAdaptorFactory { public bool UsesStorageProvider { get { throw null; } } public ILogViewAdaptor MakeLogViewAdaptor(ILogViewAdaptorHost hostGrain, TView initialState, string grainTypeName, Storage.IGrainStorage grainStorage, ILogConsistencyProtocolServices services) where TView : class, new() where TEntry : class { throw null; } } } namespace Orleans.Hosting { public static partial class CustomStorageSiloBuilderExtensions { public static ISiloBuilder AddCustomStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "LogStorage", string primaryCluster = null) { throw null; } public static ISiloBuilder AddCustomStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder, string primaryCluster = null) { throw null; } } public static partial class LogStorageSiloBuilderExtensions { public static ISiloBuilder AddLogStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "LogStorage") { throw null; } public static ISiloBuilder AddLogStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder) { throw null; } } public static partial class StateStorageSiloBuilderExtensions { public static ISiloBuilder AddStateStorageBasedLogConsistencyProvider(this ISiloBuilder builder, string name = "StateStorage") { throw null; } public static ISiloBuilder AddStateStorageBasedLogConsistencyProviderAsDefault(this ISiloBuilder builder) { throw null; } } } namespace OrleansCodeGen.Orleans.EventSourcing { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ConnectionIssue : global::Orleans.Serialization.Serializers.AbstractTypeSerializer { public override void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.ConnectionIssue instance) { } public override void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.ConnectionIssue instance) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 instance) { } public Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 instance) { } public Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 instance) { } public Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ProtocolTransportException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ProtocolTransportException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.ProtocolTransportException instance) { } public global::Orleans.EventSourcing.ProtocolTransportException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.ProtocolTransportException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.ProtocolTransportException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ConnectionIssue : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public void DeepCopy(global::Orleans.EventSourcing.ConnectionIssue input, global::Orleans.EventSourcing.ConnectionIssue output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.EventSourcing.ConnectionIssue DeepCopy(global::Orleans.EventSourcing.ConnectionIssue original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 DeepCopy(Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 DeepCopy(Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 DeepCopy(Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ProtocolTransportException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ProtocolTransportException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.EventSourcing.ILogConsistencyProtocolParticipant), "0DB087C8" })] public sealed partial class Invokable_ILogConsistencyProtocolParticipant_GrainReference_0DB087C8 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.EventSourcing.ILogConsistencyProtocolParticipant), "22FD7D72" })] public sealed partial class Invokable_ILogConsistencyProtocolParticipant_GrainReference_22FD7D72 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.EventSourcing.ILogConsistencyProtocolParticipant), "A36FC884" })] public sealed partial class Invokable_ILogConsistencyProtocolParticipant_GrainReference_A36FC884 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.EventSourcing.Common { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_BatchedNotificationMessage : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_BatchedNotificationMessage(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.Common.BatchedNotificationMessage instance) { } public global::Orleans.EventSourcing.Common.BatchedNotificationMessage ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.Common.BatchedNotificationMessage instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.Common.BatchedNotificationMessage value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_PrimaryOperationFailed : global::Orleans.Serialization.Serializers.AbstractTypeSerializer { public Codec_PrimaryOperationFailed(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public override void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.Common.PrimaryOperationFailed instance) { } public override void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.Common.PrimaryOperationFailed instance) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_VersionNotificationMessage : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.Common.VersionNotificationMessage instance) { } public global::Orleans.EventSourcing.Common.VersionNotificationMessage ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.Common.VersionNotificationMessage instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.Common.VersionNotificationMessage value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_BatchedNotificationMessage : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_BatchedNotificationMessage(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.EventSourcing.Common.BatchedNotificationMessage DeepCopy(global::Orleans.EventSourcing.Common.BatchedNotificationMessage original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_PrimaryOperationFailed : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_PrimaryOperationFailed(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void DeepCopy(global::Orleans.EventSourcing.Common.PrimaryOperationFailed input, global::Orleans.EventSourcing.Common.PrimaryOperationFailed output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.EventSourcing.Common.PrimaryOperationFailed DeepCopy(global::Orleans.EventSourcing.Common.PrimaryOperationFailed original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_VersionNotificationMessage : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public global::Orleans.EventSourcing.Common.VersionNotificationMessage DeepCopy(global::Orleans.EventSourcing.Common.VersionNotificationMessage original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.EventSourcing.LogStorage { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LogStateWithMetaDataAndETag : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TEntry : class { public Codec_LogStateWithMetaDataAndETag(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag instance) { } public global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_LogStateWithMetaData : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TEntry : class { public Codec_LogStateWithMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData instance) { } public global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_LogStateWithMetaDataAndETag : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where TEntry : class { public Copier_LogStateWithMetaDataAndETag(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag DeepCopy(global::Orleans.EventSourcing.LogStorage.LogStateWithMetaDataAndETag original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_LogStateWithMetaData : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where TEntry : class { public Copier_LogStateWithMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData DeepCopy(global::Orleans.EventSourcing.LogStorage.LogStateWithMetaData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.EventSourcing.StateStorage { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainStateWithMetaDataAndETag : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TView : class, new() { public Codec_GrainStateWithMetaDataAndETag(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag instance) { } public global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainStateWithMetaData : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TView : class, new() { public Codec_GrainStateWithMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData instance) { } public global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainStateWithMetaDataAndETag : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where TView : class, new() { public Copier_GrainStateWithMetaDataAndETag(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag DeepCopy(global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaDataAndETag original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainStateWithMetaData : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where TView : class, new() { public Copier_GrainStateWithMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData DeepCopy(global::Orleans.EventSourcing.StateStorage.GrainStateWithMetaData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } ================================================ FILE: src/api/Orleans.Hosting.Kubernetes/Orleans.Hosting.Kubernetes.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class KubernetesHostingExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseKubernetesHosting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseKubernetesHosting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } public static ISiloBuilder UseKubernetesHosting(this ISiloBuilder siloBuilder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseKubernetesHosting(this ISiloBuilder siloBuilder) { throw null; } } } namespace Orleans.Hosting.Kubernetes { public sealed partial class KubernetesClusterAgent : ILifecycleParticipant { public KubernetesClusterAgent(Runtime.IClusterMembershipService clusterMembershipService, Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Options.IOptionsMonitor options, Microsoft.Extensions.Options.IOptions clusterOptions, Runtime.ILocalSiloDetails localSiloDetails) { } public System.Threading.Tasks.Task OnStop(System.Threading.CancellationToken cancellationToken) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } } public sealed partial class KubernetesHostingOptions { public const string ClusterIdEnvironmentVariable = "ORLEANS_CLUSTER_ID"; public const string ClusterIdLabel = "orleans/clusterId"; public const string PodIPEnvironmentVariable = "POD_IP"; public const string PodNameEnvironmentVariable = "POD_NAME"; public const string PodNamespaceEnvironmentVariable = "POD_NAMESPACE"; public const string ServiceIdEnvironmentVariable = "ORLEANS_SERVICE_ID"; public const string ServiceIdLabel = "orleans/serviceId"; public bool DeleteDefunctSiloPods { get { throw null; } set { } } public System.Func GetClientConfiguration { get { throw null; } set { } } public int MaxAgents { get { throw null; } set { } } public int MaxKubernetesApiRetryAttempts { get { throw null; } set { } } } } ================================================ FILE: src/api/Orleans.Journaling/Orleans.Journaling.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Journaling { public abstract partial class DurableGrain : Grain, IGrainBase { protected IStateMachineManager StateMachineManager { get { throw null; } } protected TStateMachine GetOrCreateStateMachine(string name) where TStateMachine : class, IDurableStateMachine { throw null; } protected TStateMachine GetOrCreateStateMachine(string name, System.Func createStateMachine, TState state) where TStateMachine : class, IDurableStateMachine { throw null; } protected System.Threading.Tasks.ValueTask WriteStateAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; } } [GenerateSerializer] [Immutable] public readonly partial struct DurableTaskCompletionSourceState { private readonly T? _Value_k__BackingField; private readonly object _dummy; private readonly int _dummyPrimitive; [Id(2)] public System.Exception? Exception { get { throw null; } init { } } [Id(0)] public DurableTaskCompletionSourceStatus Status { get { throw null; } init { } } [Id(1)] public T? Value { get { throw null; } init { } } } [GenerateSerializer] public enum DurableTaskCompletionSourceStatus : byte { Pending = 0, Completed = 1, Faulted = 2, Canceled = 3 } public static partial class HostingExtensions { public static Hosting.ISiloBuilder AddStateMachineStorage(this Hosting.ISiloBuilder builder) { throw null; } } public partial interface IDurableDictionary : System.Collections.Generic.IDictionary, System.Collections.Generic.ICollection>, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { } public partial interface IDurableList : System.Collections.Generic.IList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { void AddRange(System.Collections.Generic.IEnumerable collection); System.Collections.ObjectModel.ReadOnlyCollection AsReadOnly(); } public partial interface IDurableNothing { } public partial interface IDurableQueue : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable, System.Collections.Generic.IReadOnlyCollection { void Clear(); bool Contains(T item); void CopyTo(T[] array, int arrayIndex); T Dequeue(); void Enqueue(T item); T Peek(); bool TryDequeue(out T item); bool TryPeek(out T item); } public partial interface IDurableSet : System.Collections.Generic.ISet, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlySet { int Count { get; } bool Add(T item); bool Contains(T item); bool IsProperSubsetOf(System.Collections.Generic.IEnumerable other); bool IsProperSupersetOf(System.Collections.Generic.IEnumerable other); bool IsSubsetOf(System.Collections.Generic.IEnumerable other); bool IsSupersetOf(System.Collections.Generic.IEnumerable other); bool Overlaps(System.Collections.Generic.IEnumerable other); bool SetEquals(System.Collections.Generic.IEnumerable other); } public partial interface IDurableStateMachine { void AppendEntries(StateMachineStorageWriter writer); void AppendSnapshot(StateMachineStorageWriter writer); void Apply(System.Buffers.ReadOnlySequence entry); IDurableStateMachine DeepCopy(); void OnRecoveryCompleted(); void OnWriteCompleted(); void Reset(IStateMachineLogWriter storage); } public partial interface IDurableTaskCompletionSource { DurableTaskCompletionSourceState State { get; } System.Threading.Tasks.Task Task { get; } bool TrySetCanceled(); bool TrySetException(System.Exception exception); bool TrySetResult(T value); } public partial interface IDurableValue { T? Value { get; set; } } public partial interface IStateMachineLogWriter { void AppendEntries(System.Action action, TState state); void AppendEntry(System.Action> action, TState state); } public partial interface IStateMachineManager { System.Threading.Tasks.ValueTask DeleteStateAsync(System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.ValueTask InitializeAsync(System.Threading.CancellationToken cancellationToken); void RegisterStateMachine(string name, IDurableStateMachine stateMachine); bool TryGetStateMachine(string name, out IDurableStateMachine? stateMachine); System.Threading.Tasks.ValueTask WriteStateAsync(System.Threading.CancellationToken cancellationToken); } public partial interface IStateMachineStorage { bool IsCompactionRequested { get; } System.Threading.Tasks.ValueTask AppendAsync(LogExtentBuilder value, System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.ValueTask DeleteAsync(System.Threading.CancellationToken cancellationToken); System.Collections.Generic.IAsyncEnumerable ReadAsync(System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.ValueTask ReplaceAsync(LogExtentBuilder value, System.Threading.CancellationToken cancellationToken); } public partial interface IStateMachineStorageProvider { IStateMachineStorage Create(Runtime.IGrainContext grainContext); } public sealed partial class LogExtent : System.IDisposable { public LogExtent() { } public LogExtent(Serialization.Buffers.ArcBuffer buffer) { } public bool IsEmpty { get { throw null; } } public void Dispose() { } public readonly partial struct Entry : System.IEquatable { private readonly int _dummyPrimitive; public Entry(StateMachineId StreamId, System.Buffers.ReadOnlySequence Payload) { } public System.Buffers.ReadOnlySequence Payload { get { throw null; } init { } } public StateMachineId StreamId { get { throw null; } init { } } [System.Runtime.CompilerServices.CompilerGenerated] public readonly void Deconstruct(out StateMachineId StreamId, out System.Buffers.ReadOnlySequence Payload) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public readonly bool Equals(Entry other) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly bool Equals(object obj) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly int GetHashCode() { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public static bool operator ==(Entry left, Entry right) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public static bool operator !=(Entry left, Entry right) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly string ToString() { throw null; } } } public sealed partial class LogExtentBuilder : System.IDisposable, System.Buffers.IBufferWriter { public LogExtentBuilder() { } public LogExtentBuilder(Serialization.Buffers.ArcBufferWriter buffer) { } public bool IsEmpty { get { throw null; } } public long Length { get { throw null; } } public void CopyTo(System.IO.Stream destination, int bufferSize) { } public System.Threading.Tasks.ValueTask CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } public StateMachineStorageWriter CreateLogWriter(StateMachineId id) { throw null; } public void Dispose() { } public void Reset() { } void System.Buffers.IBufferWriter.Advance(int count) { } System.Memory System.Buffers.IBufferWriter.GetMemory(int sizeHint) { throw null; } System.Span System.Buffers.IBufferWriter.GetSpan(int sizeHint) { throw null; } public byte[] ToArray() { throw null; } public sealed partial class ReadOnlyStream : System.IO.Stream { public override bool CanRead { get { throw null; } } public override bool CanSeek { get { throw null; } } public override bool CanWrite { get { throw null; } } public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default) { throw null; } public void Reset() { } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public void SetBuilder(LogExtentBuilder builder) { } public override void SetLength(long value) { } public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default) { throw null; } public override void WriteByte(byte value) { } } } public readonly partial struct StateMachineId : System.IEquatable { private readonly int _dummyPrimitive; public StateMachineId(ulong Value) { } public ulong Value { get { throw null; } init { } } [System.Runtime.CompilerServices.CompilerGenerated] public readonly void Deconstruct(out ulong Value) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public readonly bool Equals(StateMachineId other) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly bool Equals(object obj) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly int GetHashCode() { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public static bool operator ==(StateMachineId left, StateMachineId right) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public static bool operator !=(StateMachineId left, StateMachineId right) { throw null; } [System.Runtime.CompilerServices.CompilerGenerated] public override readonly string ToString() { throw null; } } public readonly partial struct StateMachineStorageWriter { private readonly object _dummy; private readonly int _dummyPrimitive; public readonly void AppendEntry(System.ArraySegment value) { } public readonly void AppendEntry(System.Buffers.ReadOnlySequence value) { } public readonly void AppendEntry(byte[] value) { } public readonly void AppendEntry(System.Memory value) { } public readonly void AppendEntry(System.ReadOnlyMemory value) { } public readonly void AppendEntry(System.ReadOnlySpan value) { } public readonly void AppendEntry(System.Span value) { } public readonly void AppendEntry(System.Action> valueWriter, T value) { } } public sealed partial class VolatileStateMachineStorage : IStateMachineStorage { public bool IsCompactionRequested { get { throw null; } } public System.Threading.Tasks.ValueTask AppendAsync(LogExtentBuilder segment, System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.ValueTask DeleteAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Collections.Generic.IAsyncEnumerable ReadAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.ValueTask ReplaceAsync(LogExtentBuilder snapshot, System.Threading.CancellationToken cancellationToken) { throw null; } } public sealed partial class VolatileStateMachineStorageProvider : IStateMachineStorageProvider { public IStateMachineStorage Create(Runtime.IGrainContext grainContext) { throw null; } } } namespace OrleansCodeGen.Orleans.Journaling { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DurableTaskCompletionSourceState : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_DurableTaskCompletionSourceState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Journaling.DurableTaskCompletionSourceState instance) { } public global::Orleans.Journaling.DurableTaskCompletionSourceState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Journaling.DurableTaskCompletionSourceState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Journaling.DurableTaskCompletionSourceState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DurableTaskCompletionSourceStatus : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Journaling.DurableTaskCompletionSourceStatus ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Journaling.DurableTaskCompletionSourceStatus value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_DurableTaskCompletionSourceState : global::Orleans.Serialization.Cloning.ShallowCopier> { } } ================================================ FILE: src/api/Orleans.Persistence.Memory/Orleans.Persistence.Memory.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class MemoryGrainStorageOptions : Storage.IStorageProviderSerializerOptions { public const int DEFAULT_INIT_STAGE = 10000; public const int NumStorageGrainsDefaultValue = 10; public Storage.IGrainStorageSerializer GrainStorageSerializer { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public int NumStorageGrains { get { throw null; } set { } } } public partial class MemoryGrainStorageOptionsValidator : IConfigurationValidator { public MemoryGrainStorageOptionsValidator(MemoryGrainStorageOptions options, string name) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class MemoryGrainStorageSiloBuilderExtensions { public static ISiloBuilder AddMemoryGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddMemoryGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddMemoryGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptions = null) { throw null; } public static ISiloBuilder AddMemoryGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } namespace Orleans.Storage { [System.Diagnostics.DebuggerDisplay("MemoryStore:{name}")] public partial class MemoryGrainStorage : IGrainStorage, System.IDisposable { public MemoryGrainStorage(string name, Configuration.MemoryGrainStorageOptions options, Microsoft.Extensions.Logging.ILogger logger, IGrainFactory grainFactory, IGrainStorageSerializer defaultGrainStorageSerializer, Serialization.Serializers.IActivatorProvider activatorProvider) { } public virtual System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public void Dispose() { } public virtual System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public virtual System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class MemoryGrainStorageFactory { public static MemoryGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } [System.Diagnostics.DebuggerDisplay("MemoryStore:{Name},WithLatency:{latency}")] public partial class MemoryGrainStorageWithLatency : IGrainStorage { public MemoryGrainStorageWithLatency(string name, MemoryStorageWithLatencyOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IGrainFactory grainFactory, Serialization.Serializers.IActivatorProvider activatorProvider, IGrainStorageSerializer defaultGrainStorageSerializer) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public partial class MemoryStorageWithLatencyOptions : Configuration.MemoryGrainStorageOptions { public static readonly System.TimeSpan DefaultLatency; public System.TimeSpan Latency { get { throw null; } set { } } public bool MockCallsOnly { get { throw null; } set { } } } } ================================================ FILE: src/api/Orleans.Reminders/Orleans.Reminders.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { public static partial class GrainReminderExtensions { public static System.Threading.Tasks.Task GetReminder(this Grain grain, string reminderName) { throw null; } public static System.Threading.Tasks.Task GetReminder(this IGrainBase grain, string reminderName) { throw null; } public static System.Threading.Tasks.Task> GetReminders(this Grain grain) { throw null; } public static System.Threading.Tasks.Task> GetReminders(this IGrainBase grain) { throw null; } public static System.Threading.Tasks.Task RegisterOrUpdateReminder(this Grain grain, string reminderName, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public static System.Threading.Tasks.Task RegisterOrUpdateReminder(this IGrainBase grain, string reminderName, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public static System.Threading.Tasks.Task UnregisterReminder(this Grain grain, Runtime.IGrainReminder reminder) { throw null; } public static System.Threading.Tasks.Task UnregisterReminder(this IGrainBase grain, Runtime.IGrainReminder reminder) { throw null; } } public partial interface IRemindable : IGrain, Runtime.IAddressable { System.Threading.Tasks.Task ReceiveReminder(string reminderName, Runtime.TickStatus status); } public partial interface IReminderService : Services.IGrainService, ISystemTarget, Runtime.IAddressable { System.Threading.Tasks.Task GetReminder(Runtime.GrainId grainId, string reminderName); System.Threading.Tasks.Task> GetReminders(Runtime.GrainId grainId); System.Threading.Tasks.Task RegisterOrUpdateReminder(Runtime.GrainId grainId, string reminderName, System.TimeSpan dueTime, System.TimeSpan period); System.Threading.Tasks.Task Start(); System.Threading.Tasks.Task Stop(); System.Threading.Tasks.Task UnregisterReminder(Runtime.IGrainReminder reminder); } public partial interface IReminderTable { [System.Obsolete("Implement and use StartAsync instead")] System.Threading.Tasks.Task Init(); System.Threading.Tasks.Task ReadRow(Runtime.GrainId grainId, string reminderName); System.Threading.Tasks.Task ReadRows(Runtime.GrainId grainId); System.Threading.Tasks.Task ReadRows(uint begin, uint end); System.Threading.Tasks.Task RemoveRow(Runtime.GrainId grainId, string reminderName, string eTag); System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default); System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken = default); System.Threading.Tasks.Task TestOnlyClearTable(); System.Threading.Tasks.Task UpsertRow(ReminderEntry entry); } [GenerateSerializer] public sealed partial class ReminderEntry { [Id(4)] public string ETag { get { throw null; } set { } } [Id(0)] public Runtime.GrainId GrainId { get { throw null; } set { } } [Id(3)] public System.TimeSpan Period { get { throw null; } set { } } [Id(1)] public string ReminderName { get { throw null; } set { } } [Id(2)] public System.DateTime StartAt { get { throw null; } set { } } public override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class ReminderTableData { public ReminderTableData() { } public ReminderTableData(ReminderEntry entry) { } public ReminderTableData(System.Collections.Generic.IEnumerable list) { } [Id(0)] public System.Collections.Generic.IList Reminders { get { throw null; } } public override string ToString() { throw null; } } } namespace Orleans.Hosting { public sealed partial class ReminderOptions { public System.TimeSpan InitializationTimeout { get { throw null; } set { } } public System.TimeSpan MinimumReminderPeriod { get { throw null; } set { } } public System.TimeSpan RefreshReminderListPeriod { get { throw null; } set { } } } public static partial class SiloBuilderReminderExtensions { public static void AddReminders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } public static ISiloBuilder AddReminders(this ISiloBuilder builder) { throw null; } } public static partial class SiloBuilderReminderMemoryExtensions { public static ISiloBuilder UseInMemoryReminderService(this ISiloBuilder builder) { throw null; } } } namespace Orleans.Reminders { public enum RSErrorCode { ReminderServiceBase = 102900, RS_Register_TableError = 102905, RS_Register_AlreadyRegistered = 102907, RS_Register_InvalidPeriod = 102908, RS_Register_NotRemindable = 102909, RS_NotResponsible = 102910, RS_Unregister_NotFoundLocally = 102911, RS_Unregister_TableError = 102912, RS_Table_Insert = 102913, RS_Table_Remove = 102914, RS_Tick_Delivery_Error = 102915, RS_Not_Started = 102916, RS_UnregisterGrain_TableError = 102917, RS_GrainBasedTable1 = 102918, RS_Factory1 = 102919, RS_FailedToReadTableAndStartTimer = 102920, RS_TableGrainInit1 = 102921, RS_TableGrainInit2 = 102922, RS_TableGrainInit3 = 102923, RS_GrainBasedTable2 = 102924, RS_ServiceStarting = 102925, RS_ServiceStarted = 102926, RS_ServiceStopping = 102927, RS_RegisterOrUpdate = 102928, RS_Unregister = 102929, RS_Stop = 102930, RS_RemoveFromTable = 102931, RS_GetReminder = 102932, RS_GetReminders = 102933, RS_RangeChanged = 102934, RS_LocalStop = 102935, RS_Started = 102936, RS_ServiceInitialLoadFailing = 102937, RS_ServiceInitialLoadFailed = 102938, RS_FastReminderInterval = 102939 } } namespace Orleans.Runtime { public partial interface IGrainReminder { string ReminderName { get; } } [GenerateSerializer] public sealed partial class ReminderException : OrleansException { [System.Obsolete] public ReminderException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public ReminderException(string message) { } } [GenerateSerializer] [Immutable] public readonly partial struct TickStatus { private readonly int _dummyPrimitive; public TickStatus(System.DateTime firstTickTime, System.TimeSpan period, System.DateTime timeStamp) { } [Id(2)] public System.DateTime CurrentTickTime { get { throw null; } } [Id(0)] public System.DateTime FirstTickTime { get { throw null; } } [Id(1)] public System.TimeSpan Period { get { throw null; } } public override readonly string ToString() { throw null; } } } namespace Orleans.Timers { public partial interface IReminderRegistry : Services.IGrainServiceClient { System.Threading.Tasks.Task GetReminder(Runtime.GrainId callingGrainId, string reminderName); System.Threading.Tasks.Task> GetReminders(Runtime.GrainId callingGrainId); System.Threading.Tasks.Task RegisterOrUpdateReminder(Runtime.GrainId callingGrainId, string reminderName, System.TimeSpan dueTime, System.TimeSpan period); System.Threading.Tasks.Task UnregisterReminder(Runtime.GrainId callingGrainId, Runtime.IGrainReminder reminder); } } namespace OrleansCodeGen.Orleans { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IRemindable_GrainReference_6461BF2F : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IRemindable_GrainReference_6461BF2F(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IRemindable_GrainReference_6461BF2F instance) { } public Invokable_IRemindable_GrainReference_6461BF2F ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IRemindable_GrainReference_6461BF2F instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IRemindable_GrainReference_6461BF2F value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_1281C86D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IReminderService_GrainReference_1281C86D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_1281C86D instance) { } public Invokable_IReminderService_GrainReference_1281C86D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_1281C86D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_1281C86D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_419EB51E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IReminderService_GrainReference_419EB51E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_419EB51E instance) { } public Invokable_IReminderService_GrainReference_419EB51E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_419EB51E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_419EB51E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_5CF78F8A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_5CF78F8A instance) { } public Invokable_IReminderService_GrainReference_5CF78F8A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_5CF78F8A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_5CF78F8A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_A7AF84A8 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IReminderService_GrainReference_A7AF84A8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_A7AF84A8 instance) { } public Invokable_IReminderService_GrainReference_A7AF84A8 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_A7AF84A8 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_A7AF84A8 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_AC622EEB : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IReminderService_GrainReference_AC622EEB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_AC622EEB instance) { } public Invokable_IReminderService_GrainReference_AC622EEB ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_AC622EEB instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_AC622EEB value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IReminderService_GrainReference_DCFCA00D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IReminderService_GrainReference_DCFCA00D instance) { } public Invokable_IReminderService_GrainReference_DCFCA00D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IReminderService_GrainReference_DCFCA00D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IReminderService_GrainReference_DCFCA00D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReminderEntry : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ReminderEntry(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.ReminderEntry instance) { } public global::Orleans.ReminderEntry ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.ReminderEntry instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.ReminderEntry value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReminderTableData : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ReminderTableData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.ReminderTableData instance) { } public global::Orleans.ReminderTableData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.ReminderTableData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.ReminderTableData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IRemindable_GrainReference_6461BF2F : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IRemindable_GrainReference_6461BF2F DeepCopy(Invokable_IRemindable_GrainReference_6461BF2F original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_1281C86D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IReminderService_GrainReference_1281C86D DeepCopy(Invokable_IReminderService_GrainReference_1281C86D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_419EB51E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IReminderService_GrainReference_419EB51E DeepCopy(Invokable_IReminderService_GrainReference_419EB51E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_5CF78F8A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IReminderService_GrainReference_5CF78F8A DeepCopy(Invokable_IReminderService_GrainReference_5CF78F8A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_A7AF84A8 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IReminderService_GrainReference_A7AF84A8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IReminderService_GrainReference_A7AF84A8 DeepCopy(Invokable_IReminderService_GrainReference_A7AF84A8 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_AC622EEB : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IReminderService_GrainReference_AC622EEB DeepCopy(Invokable_IReminderService_GrainReference_AC622EEB original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IReminderService_GrainReference_DCFCA00D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IReminderService_GrainReference_DCFCA00D DeepCopy(Invokable_IReminderService_GrainReference_DCFCA00D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReminderEntry : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public global::Orleans.ReminderEntry DeepCopy(global::Orleans.ReminderEntry original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReminderTableData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ReminderTableData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.ReminderTableData DeepCopy(global::Orleans.ReminderTableData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IRemindable), "6461BF2F" })] public sealed partial class Invokable_IRemindable_GrainReference_6461BF2F : global::Orleans.Runtime.TaskRequest { public string arg0; public global::Orleans.Runtime.TickStatus arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "1281C86D" })] public sealed partial class Invokable_IReminderService_GrainReference_1281C86D : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public string arg1; public System.TimeSpan arg2; public System.TimeSpan arg3; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "419EB51E" })] public sealed partial class Invokable_IReminderService_GrainReference_419EB51E : global::Orleans.Runtime.TaskRequest> { public global::Orleans.Runtime.GrainId arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "5CF78F8A" })] public sealed partial class Invokable_IReminderService_GrainReference_5CF78F8A : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "A7AF84A8" })] public sealed partial class Invokable_IReminderService_GrainReference_A7AF84A8 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.IGrainReminder arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "AC622EEB" })] public sealed partial class Invokable_IReminderService_GrainReference_AC622EEB : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public string arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.IReminderService), "DCFCA00D" })] public sealed partial class Invokable_IReminderService_GrainReference_DCFCA00D : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Runtime { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReminderException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ReminderException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ReminderException instance) { } public global::Orleans.Runtime.ReminderException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ReminderException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ReminderException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TickStatus : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.TickStatus instance) { } public global::Orleans.Runtime.TickStatus ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.TickStatus instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.TickStatus value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReminderException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ReminderException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } ================================================ FILE: src/api/Orleans.Reminders.Abstractions/Orleans.Reminders.Abstractions.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ ================================================ FILE: src/api/Orleans.Runtime/Orleans.Runtime.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.Extensions.Hosting { public static partial class OrleansSiloGenericHostExtensions { public static DependencyInjection.IServiceCollection AddOrleans(this DependencyInjection.IServiceCollection services, System.Action configureDelegate) { throw null; } public static HostApplicationBuilder UseOrleans(this HostApplicationBuilder hostAppBuilder, System.Action configureDelegate) { throw null; } public static HostApplicationBuilder UseOrleans(this HostApplicationBuilder hostAppBuilder) { throw null; } public static IHostApplicationBuilder UseOrleans(this IHostApplicationBuilder hostAppBuilder, System.Action configureDelegate) { throw null; } public static IHostApplicationBuilder UseOrleans(this IHostApplicationBuilder hostAppBuilder) { throw null; } public static IHostBuilder UseOrleans(this IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } public static IHostBuilder UseOrleans(this IHostBuilder hostBuilder, System.Action configureDelegate) { throw null; } } } namespace Orleans { public partial interface IFacetMetadata { } public partial class PersistentStateAttributeMapper : Runtime.IAttributeToFactoryMapper { public Factory GetFactory(System.Reflection.ParameterInfo parameter, Runtime.PersistentStateAttribute attribute) { throw null; } } } namespace Orleans.Configuration { public partial class ActivationCountBasedPlacementOptions { public const int DEFAULT_ACTIVATION_COUNT_PLACEMENT_CHOOSE_OUT_OF = 2; public int ChooseOutOf { get { throw null; } set { } } } public sealed partial class ActivationRebalancerOptions { public const int DEFAULT_ACTIVATION_MIGRATION_COUNT_LIMIT = int.MaxValue; public const double DEFAULT_ALLOWED_ENTROPY_DEVIATION = 0.0001D; public const double DEFAULT_CYCLE_NUMBER_WEIGHT = 0.1D; public const double DEFAULT_ENTROPY_QUANTUM = 0.0001D; public const int DEFAULT_MAX_STAGNANT_CYCLES = 3; public static readonly System.TimeSpan DEFAULT_REBALANCER_DUE_TIME; public const bool DEFAULT_SCALE_ALLOWED_ENTROPY_DEVIATION = true; public const int DEFAULT_SCALED_ENTROPY_DEVIATION_ACTIVATION_THRESHOLD = 10000; public static readonly System.TimeSpan DEFAULT_SESSION_CYCLE_PERIOD; public const double DEFAULT_SILO_NUMBER_WEIGHT = 0.1D; public const double MAX_SCALED_ENTROPY_DEVIATION = 0.1D; public int ActivationMigrationCountLimit { get { throw null; } set { } } public double AllowedEntropyDeviation { get { throw null; } set { } } public double CycleNumberWeight { get { throw null; } set { } } public double EntropyQuantum { get { throw null; } set { } } public int MaxStagnantCycles { get { throw null; } set { } } public System.TimeSpan RebalancerDueTime { get { throw null; } set { } } public bool ScaleAllowedEntropyDeviation { get { throw null; } set { } } public int ScaledEntropyDeviationActivationThreshold { get { throw null; } set { } } public System.TimeSpan SessionCyclePeriod { get { throw null; } set { } } public double SiloNumberWeight { get { throw null; } set { } } } public sealed partial class ActivationRepartitionerOptions { public const bool DEFAULT_ANCHORING_FILTER_ENABLED = true; public const int DEFAULT_MAX_EDGE_COUNT = 10000; public const int DEFAULT_MAX_UNPROCESSED_EDGES = 100000; public static readonly System.TimeSpan DEFAULT_MAXIMUM_ROUND_PERIOD; public static readonly System.TimeSpan DEFAULT_MINUMUM_ROUND_PERIOD; public const double DEFAULT_PROBABILISTIC_FILTERING_MAX_ALLOWED_ERROR = 0.01D; public static readonly System.TimeSpan DEFAULT_RECOVERY_PERIOD; public bool AnchoringFilterEnabled { get { throw null; } set { } } public int MaxEdgeCount { get { throw null; } set { } } public System.TimeSpan MaxRoundPeriod { get { throw null; } set { } } public int MaxUnprocessedEdges { get { throw null; } set { } } public System.TimeSpan MinRoundPeriod { get { throw null; } set { } } public double ProbabilisticFilteringMaxAllowedErrorRate { get { throw null; } set { } } public System.TimeSpan RecoveryPeriod { get { throw null; } set { } } } public partial class ConsistentRingOptions { public const int DEFAULT_NUM_VIRTUAL_RING_BUCKETS = 30; public const bool DEFAULT_USE_VIRTUAL_RING_BUCKETS = true; public int NumVirtualBucketsConsistentRing { get { throw null; } set { } } public bool UseVirtualBucketsConsistentRing { get { throw null; } set { } } } public partial class DeploymentLoadPublisherOptions { public static readonly System.TimeSpan DEFAULT_DEPLOYMENT_LOAD_PUBLISHER_REFRESH_TIME; public System.TimeSpan DeploymentLoadPublisherRefreshTime { get { throw null; } set { } } } public partial class DevelopmentClusterMembershipOptions { public System.Net.IPEndPoint PrimarySiloEndpoint { get { throw null; } set { } } } public partial class EndpointOptions { public const int DEFAULT_GATEWAY_PORT = 30000; public const int DEFAULT_SILO_PORT = 11111; public System.Net.IPAddress AdvertisedIPAddress { get { throw null; } set { } } public System.Net.IPEndPoint GatewayListeningEndpoint { get { throw null; } set { } } public int GatewayPort { get { throw null; } set { } } public System.Net.IPEndPoint SiloListeningEndpoint { get { throw null; } set { } } public int SiloPort { get { throw null; } set { } } } public partial class GrainCollectionOptions { public static readonly System.TimeSpan DEFAULT_ACTIVATION_TIMEOUT; public static readonly System.TimeSpan DEFAULT_COLLECTION_QUANTUM; public static readonly System.TimeSpan DEFAULT_DEACTIVATION_TIMEOUT; public System.TimeSpan ActivationTimeout { get { throw null; } set { } } public System.Collections.Generic.Dictionary ClassSpecificCollectionAge { get { throw null; } set { } } public System.TimeSpan CollectionAge { get { throw null; } set { } } public System.TimeSpan CollectionQuantum { get { throw null; } set { } } public System.TimeSpan DeactivationTimeout { get { throw null; } set { } } public bool EnableActivationSheddingOnMemoryPressure { get { throw null; } set { } } public double MemoryUsageLimitPercentage { get { throw null; } set { } } public System.TimeSpan MemoryUsagePollingPeriod { get { throw null; } set { } } public double MemoryUsageTargetPercentage { get { throw null; } set { } } } public partial class GrainDirectoryOptions { public const int DEFAULT_CACHE_SIZE = 1000000; public const CachingStrategyType DEFAULT_CACHING_STRATEGY = 1; [System.Obsolete("DEFAULT_INITIAL_CACHE_TTL is deprecated and will be removed in a future version.")] public static readonly System.TimeSpan DEFAULT_INITIAL_CACHE_TTL; [System.Obsolete("DEFAULT_MAXIMUM_CACHE_TTL is deprecated and will be removed in a future version.")] public static readonly System.TimeSpan DEFAULT_MAXIMUM_CACHE_TTL; [System.Obsolete("DEFAULT_TTL_EXTENSION_FACTOR is deprecated and will be removed in a future version.")] public const double DEFAULT_TTL_EXTENSION_FACTOR = 2D; public static readonly System.TimeSpan DEFAULT_UNREGISTER_RACE_DELAY; public int CacheSize { get { throw null; } set { } } [System.Obsolete("CacheTTLExtensionFactor is deprecated and will be removed in a future version.")] public double CacheTTLExtensionFactor { get { throw null; } set { } } public CachingStrategyType CachingStrategy { get { throw null; } set { } } [System.Obsolete("InitialCacheTTL is deprecated and will be removed in a future version.")] public System.TimeSpan InitialCacheTTL { get { throw null; } set { } } public System.TimeSpan LazyDeregistrationDelay { get { throw null; } set { } } [System.Obsolete("MaximumCacheTTL is deprecated and will be removed in a future version.")] public System.TimeSpan MaximumCacheTTL { get { throw null; } set { } } public enum CachingStrategyType { None = 0, LRU = 1, Adaptive = 2, Custom = 3 } } public sealed partial class ResourceOptimizedPlacementOptions { public const int DEFAULT_ACTIVATION_COUNT_WEIGHT = 15; public const int DEFAULT_AVAILABLE_MEMORY_WEIGHT = 20; public const int DEFAULT_CPU_USAGE_WEIGHT = 40; public const int DEFAULT_LOCAL_SILO_PREFERENCE_MARGIN = 5; public const int DEFAULT_MAX_AVAILABLE_MEMORY_WEIGHT = 5; public const int DEFAULT_MEMORY_USAGE_WEIGHT = 20; public int ActivationCountWeight { get { throw null; } set { } } public int AvailableMemoryWeight { get { throw null; } set { } } public int CpuUsageWeight { get { throw null; } set { } } public int LocalSiloPreferenceMargin { get { throw null; } set { } } public int MaxAvailableMemoryWeight { get { throw null; } set { } } public int MemoryUsageWeight { get { throw null; } set { } } } public partial class SchedulingOptions { public static readonly System.TimeSpan DEFAULT_ACTIVATION_SCHEDULING_QUANTUM; public static readonly System.TimeSpan DEFAULT_DELAY_WARNING_THRESHOLD; public const int DEFAULT_MAX_PENDING_ITEMS_SOFT_LIMIT = 0; public static readonly System.TimeSpan DEFAULT_TURN_WARNING_THRESHOLD; public System.TimeSpan ActivationSchedulingQuantum { get { throw null; } set { } } public System.TimeSpan DelayWarningThreshold { get { throw null; } set { } } public int MaxPendingWorkItemsSoftLimit { get { throw null; } set { } } public System.TimeSpan StoppedActivationWarningInterval { get { throw null; } set { } } public System.TimeSpan TurnWarningLengthThreshold { get { throw null; } set { } } } public partial class SiloConnectionOptions : SiloConnectionOptions.ISiloConnectionBuilderOptions { public void ConfigureGatewayInboundConnection(System.Action configure) { } public void ConfigureSiloInboundConnection(System.Action configure) { } public void ConfigureSiloOutboundConnection(System.Action configure) { } void ISiloConnectionBuilderOptions.ConfigureGatewayInboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder) { } void ISiloConnectionBuilderOptions.ConfigureSiloInboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder) { } void ISiloConnectionBuilderOptions.ConfigureSiloOutboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder) { } public partial interface ISiloConnectionBuilderOptions { void ConfigureGatewayInboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder); void ConfigureSiloInboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder); void ConfigureSiloOutboundBuilder(Microsoft.AspNetCore.Connections.IConnectionBuilder builder); } } public partial class SiloMessagingOptions : MessagingOptions { public static readonly System.TimeSpan DEFAULT_CLIENT_GW_NOTIFICATION_TIMEOUT; public static readonly System.TimeSpan DEFAULT_CLIENT_REGISTRATION_REFRESH; public const int DEFAULT_MAX_ENQUEUED_REQUESTS_HARD_LIMIT = 0; public const int DEFAULT_MAX_ENQUEUED_REQUESTS_SOFT_LIMIT = 0; public const int DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_HARD_LIMIT = 0; public const int DEFAULT_MAX_ENQUEUED_REQUESTS_STATELESS_WORKER_SOFT_LIMIT = 0; public static readonly System.TimeSpan DEFAULT_MAX_REQUEST_PROCESSING_TIME; [System.Obsolete("Unused, will be removed in a future version.")] public static readonly System.TimeSpan DEFAULT_SHUTDOWN_REROUTE_TIMEOUT; public static readonly System.TimeSpan DEFAULT_WAIT_FOR_MESSAGE_TO_BE_QUEUED_FOR_OUTBOUND_TIME; public bool AssumeHomogenousSilosForTesting { get { throw null; } set { } } public System.TimeSpan ClientDropTimeout { get { throw null; } set { } } public System.TimeSpan ClientGatewayShutdownNotificationTimeout { get { throw null; } set { } } public System.TimeSpan ClientRegistrationRefresh { get { throw null; } set { } } public int GatewaySenderQueues { get { throw null; } set { } } public System.TimeSpan GrainWorkloadAnalysisPeriod { get { throw null; } set { } } public int MaxEnqueuedRequestsHardLimit { get { throw null; } set { } } public int MaxEnqueuedRequestsHardLimit_StatelessWorker { get { throw null; } set { } } public int MaxEnqueuedRequestsSoftLimit { get { throw null; } set { } } public int MaxEnqueuedRequestsSoftLimit_StatelessWorker { get { throw null; } set { } } public int MaxForwardCount { get { throw null; } set { } } public System.TimeSpan MaxRequestProcessingTime { get { throw null; } set { } } public System.TimeSpan RequestProcessingWarningTime { get { throw null; } set { } } public System.TimeSpan RequestQueueDelayWarningTime { get { throw null; } set { } } [System.Obsolete("Unused, will be removed in a future version.")] public System.TimeSpan ShutdownRerouteTimeout { get { throw null; } set { } } public int SiloSenderQueues { get { throw null; } set { } } public System.TimeSpan SystemResponseTimeout { get { throw null; } set { } } public System.TimeSpan WaitForMessageToBeQueuedForOutboundTime { get { throw null; } set { } } } public partial class SiloOptions { public string SiloName { get { throw null; } set { } } } public partial class StatelessWorkerOptions { public static readonly System.TimeSpan DEFAULT_IDLE_WORKERS_INSPECTION_PERIOD; public const int DEFAULT_MIN_IDLE_CYCLES_BEFORE_REMOVAL = 3; public const bool DEFAULT_REMOVE_IDLE_WORKERS = true; public System.TimeSpan IdleWorkersInspectionPeriod { get { throw null; } set { } } public int MinIdleCyclesBeforeRemoval { get { throw null; } set { } } public bool RemoveIdleWorkers { get { throw null; } set { } } } } namespace Orleans.Core { public partial class StateStorageBridge : IStorage, IStorage, Runtime.IGrainMigrationParticipant { [System.Obsolete("Use StateStorageBridge(string, IGrainContext, IGrainStorage) instead.")] public StateStorageBridge(string name, Runtime.IGrainContext grainContext, Storage.IGrainStorage store, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Serialization.Serializers.IActivatorProvider activatorProvider) { } public StateStorageBridge(string name, Runtime.IGrainContext grainContext, Storage.IGrainStorage store) { } public string? Etag { get { throw null; } set { } } public bool RecordExists { get { throw null; } } public TState State { get { throw null; } set { } } public System.Threading.Tasks.Task ClearStateAsync() { throw null; } public void OnDehydrate(Runtime.IDehydrationContext dehydrationContext) { } public void OnRehydrate(Runtime.IRehydrationContext rehydrationContext) { } public System.Threading.Tasks.Task ReadStateAsync() { throw null; } public System.Threading.Tasks.Task WriteStateAsync() { throw null; } } } namespace Orleans.Hosting { public static partial class ActivationRebalancerExtensions { [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP002")] public static ISiloBuilder AddActivationRebalancer(this ISiloBuilder builder) { throw null; } [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP002")] public static ISiloBuilder AddActivationRebalancer(this ISiloBuilder builder) where TProvider : class, Placement.Rebalancing.IFailedSessionBackoffProvider { throw null; } } public static partial class ActivationRepartitioningExtensions { [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP001")] public static ISiloBuilder AddActivationRepartitioner(this ISiloBuilder builder) { throw null; } [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP001")] public static ISiloBuilder AddActivationRepartitioner(this ISiloBuilder builder) where TRule : class, Placement.Repartitioning.IImbalanceToleranceRule { throw null; } } public static partial class CoreHostingExtensions { public static ISiloBuilder AddActivityPropagation(this ISiloBuilder builder) { throw null; } [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP003")] public static ISiloBuilder AddDistributedGrainDirectory(this ISiloBuilder siloBuilder, string? name = null) { throw null; } public static ISiloBuilder UseDevelopmentClustering(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseDevelopmentClustering(this ISiloBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder UseDevelopmentClustering(this ISiloBuilder builder, System.Net.IPEndPoint primarySiloEndpoint) { throw null; } public static ISiloBuilder UseLocalhostClustering(this ISiloBuilder builder, int siloPort = 11111, int gatewayPort = 30000, System.Net.IPEndPoint? primarySiloEndpoint = null, string serviceId = "dev", string clusterId = "dev") { throw null; } } public static partial class EndpointOptionsExtensions { public static ISiloBuilder ConfigureEndpoints(this ISiloBuilder builder, int siloPort, int gatewayPort, System.Net.Sockets.AddressFamily addressFamily = System.Net.Sockets.AddressFamily.InterNetwork, bool listenOnAnyHostAddress = false) { throw null; } public static ISiloBuilder ConfigureEndpoints(this ISiloBuilder builder, System.Net.IPAddress advertisedIP, int siloPort, int gatewayPort, bool listenOnAnyHostAddress = false) { throw null; } public static ISiloBuilder ConfigureEndpoints(this ISiloBuilder builder, string hostname, int siloPort, int gatewayPort, System.Net.Sockets.AddressFamily addressFamily = System.Net.Sockets.AddressFamily.InterNetwork, bool listenOnAnyHostAddress = false) { throw null; } } public static partial class GrainCallFilterSiloBuilderExtensions { public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder, IIncomingGrainCallFilter filter) { throw null; } public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder, IncomingGrainCallFilterDelegate filter) { throw null; } public static ISiloBuilder AddIncomingGrainCallFilter(this ISiloBuilder builder) where TImplementation : class, IIncomingGrainCallFilter { throw null; } public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder, IOutgoingGrainCallFilter filter) { throw null; } public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder, OutgoingGrainCallFilterDelegate filter) { throw null; } public static ISiloBuilder AddOutgoingGrainCallFilter(this ISiloBuilder builder) where TImplementation : class, IOutgoingGrainCallFilter { throw null; } } public static partial class GrainServicesSiloBuilderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type grainServiceType) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } public static ISiloBuilder AddGrainService(this ISiloBuilder builder) where T : Runtime.GrainService { throw null; } } public static partial class HostingGrainExtensions { public static ISiloBuilder AddGrainExtension(this ISiloBuilder builder) where TExtensionInterface : class, Runtime.IGrainExtension where TExtension : class, TExtensionInterface { throw null; } } public partial interface ISiloBuilder { Microsoft.Extensions.Configuration.IConfiguration Configuration { get; } Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } } public static partial class PlacementStrategyExtensions { public static void AddPlacementDirector(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Func createDirector, Microsoft.Extensions.DependencyInjection.ServiceLifetime strategyLifetime) where TStrategy : Runtime.PlacementStrategy, new() { } public static void AddPlacementDirector(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Func createDirector) where TStrategy : Runtime.PlacementStrategy, new() { } public static ISiloBuilder AddPlacementDirector(this ISiloBuilder builder, System.Func createDirector) where TStrategy : Runtime.PlacementStrategy, new() { throw null; } public static void AddPlacementDirector(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceLifetime strategyLifetime) where TStrategy : Runtime.PlacementStrategy, new() where TDirector : class, Runtime.Placement.IPlacementDirector { } public static void AddPlacementDirector(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TStrategy : Runtime.PlacementStrategy, new() where TDirector : class, Runtime.Placement.IPlacementDirector { } public static ISiloBuilder AddPlacementDirector(this ISiloBuilder builder) where TStrategy : Runtime.PlacementStrategy, new() where TDirector : class, Runtime.Placement.IPlacementDirector { throw null; } } public static partial class SiloBuilderExtensions { public static ISiloBuilder Configure(this ISiloBuilder builder, Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class { throw null; } public static ISiloBuilder Configure(this ISiloBuilder builder, System.Action configureOptions) where TOptions : class { throw null; } public static ISiloBuilder ConfigureLogging(this ISiloBuilder builder, System.Action configureLogging) { throw null; } public static ISiloBuilder ConfigureServices(this ISiloBuilder builder, System.Action configureDelegate) { throw null; } } public static partial class SiloBuilderStartupExtensions { public static ISiloBuilder AddStartupTask(this ISiloBuilder builder, Runtime.IStartupTask startupTask, int stage = 20000) { throw null; } public static ISiloBuilder AddStartupTask(this ISiloBuilder builder, System.Func startupTask, int stage = 20000) { throw null; } public static ISiloBuilder AddStartupTask(this ISiloBuilder builder, int stage = 20000) where TStartup : class, Runtime.IStartupTask { throw null; } } } namespace Orleans.Metadata { public partial class GrainClassMap { public GrainClassMap(Serialization.TypeSystem.TypeConverter typeConverter, System.Collections.Immutable.ImmutableDictionary classes) { } public bool TryGetGrainClass(Runtime.GrainType grainType, out System.Type grainClass) { throw null; } } } namespace Orleans.Runtime { [GenerateSerializer] [Immutable] public sealed partial class ClusterMember : System.IEquatable { public ClusterMember(SiloAddress siloAddress, SiloStatus status, string name) { } [Id(2)] public string Name { get { throw null; } } [Id(0)] public SiloAddress SiloAddress { get { throw null; } } [Id(1)] public SiloStatus Status { get { throw null; } } public bool Equals(ClusterMember other) { throw null; } public override bool Equals(object obj) { throw null; } public override int GetHashCode() { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class ClusterMembershipSnapshot { public ClusterMembershipSnapshot(System.Collections.Immutable.ImmutableDictionary members, MembershipVersion version) { } [Id(0)] public System.Collections.Immutable.ImmutableDictionary Members { get { throw null; } } [Id(1)] public MembershipVersion Version { get { throw null; } } public ClusterMembershipUpdate AsUpdate() { throw null; } public ClusterMembershipUpdate CreateUpdate(ClusterMembershipSnapshot previous) { throw null; } public SiloStatus GetSiloStatus(SiloAddress silo) { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class ClusterMembershipUpdate { public ClusterMembershipUpdate(ClusterMembershipSnapshot snapshot, System.Collections.Immutable.ImmutableArray changes) { } [Id(0)] public System.Collections.Immutable.ImmutableArray Changes { get { throw null; } } public bool HasChanges { get { throw null; } } [Id(1)] public ClusterMembershipSnapshot Snapshot { get { throw null; } } } public partial class DefaultGrainActivator : IGrainActivator { public DefaultGrainActivator(System.IServiceProvider serviceProvider, System.Type grainClass) { } public object CreateInstance(IGrainContext context) { throw null; } public System.Threading.Tasks.ValueTask DisposeInstance(IGrainContext context, object instance) { throw null; } } public partial class GrainConstructorArgumentFactory { public GrainConstructorArgumentFactory(System.IServiceProvider serviceProvider, System.Type grainType) { } public System.Type[] ArgumentTypes { get { throw null; } } public object[] CreateArguments(IGrainContext grainContext) { throw null; } } public sealed partial class GrainContextActivator { public GrainContextActivator(System.Collections.Generic.IEnumerable providers, System.Collections.Generic.IEnumerable configureContextActions, Orleans.Metadata.GrainPropertiesResolver grainPropertiesResolver) { } public IGrainContext CreateInstance(GrainAddress address) { throw null; } } public abstract partial class GrainService : SystemTarget, Orleans.Services.IGrainService, ISystemTarget, IAddressable { [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Obsolete("Do not call the empty constructor.")] protected GrainService() { } protected GrainService(GrainId grainId, Silo silo, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } protected int RangeSerialNumber { get { throw null; } } protected IRingRange RingRange { get { throw null; } } protected GrainServiceStatus Status { get { throw null; } set { } } protected System.Threading.CancellationTokenSource StoppedCancellationTokenSource { get { throw null; } } public virtual System.Threading.Tasks.Task Init(System.IServiceProvider serviceProvider) { throw null; } public virtual System.Threading.Tasks.Task OnRangeChange(IRingRange oldRange, IRingRange newRange, bool increased) { throw null; } public virtual System.Threading.Tasks.Task Start() { throw null; } protected virtual System.Threading.Tasks.Task StartInBackground() { throw null; } public virtual System.Threading.Tasks.Task Stop() { throw null; } protected enum GrainServiceStatus { Booting = 0, Started = 1, Stopped = 2 } } public sealed partial class GrainTypeSharedContext { public GrainTypeSharedContext(GrainType grainType, IClusterManifestProvider clusterManifestProvider, Orleans.Metadata.GrainClassMap grainClassMap, Placement.PlacementStrategyResolver placementStrategyResolver, Microsoft.Extensions.Options.IOptions messagingOptions, Microsoft.Extensions.Options.IOptions collectionOptions, Microsoft.Extensions.Options.IOptions schedulingOptions, Microsoft.Extensions.Options.IOptions statelessWorkerOptions, IGrainRuntime grainRuntime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, GrainReferences.GrainReferenceActivator grainReferenceActivator, System.IServiceProvider serviceProvider, Orleans.Serialization.Session.SerializerSessionPool serializerSessionPool) { } public System.TimeSpan CollectionAgeLimit { get { throw null; } } public Orleans.GrainDirectory.IGrainDirectory? GrainDirectory { get { throw null; } } public GrainReferences.GrainReferenceActivator GrainReferenceActivator { get { throw null; } } public string? GrainTypeName { get { throw null; } } public Microsoft.Extensions.Logging.ILogger Logger { get { throw null; } } public System.TimeSpan MaxRequestProcessingTime { get { throw null; } } public System.TimeSpan MaxWarningRequestProcessingTime { get { throw null; } } public Orleans.Configuration.SiloMessagingOptions MessagingOptions { get { throw null; } } public PlacementStrategy PlacementStrategy { get { throw null; } } public IGrainRuntime Runtime { get { throw null; } } public Orleans.Configuration.SchedulingOptions SchedulingOptions { get { throw null; } } public Orleans.Serialization.Session.SerializerSessionPool SerializerSessionPool { get { throw null; } } public Orleans.Configuration.StatelessWorkerOptions StatelessWorkerOptions { get { throw null; } } public TComponent? GetComponent() { throw null; } public void OnCreateActivation(IGrainContext grainContext) { } public void OnDestroyActivation(IGrainContext grainContext) { } public void SetComponent(TComponent? instance) { } } public partial class GrainTypeSharedContextResolver { public GrainTypeSharedContextResolver(System.Collections.Generic.IEnumerable configurators, Orleans.Metadata.GrainPropertiesResolver grainPropertiesResolver, System.IServiceProvider serviceProvider) { } public GrainTypeSharedContext GetComponents(GrainType grainType) { throw null; } } public partial interface IActivationWorkingSet { int Count { get; } void OnActivated(IActivationWorkingSetMember member); void OnActive(IActivationWorkingSetMember member); void OnDeactivated(IActivationWorkingSetMember member); void OnDeactivating(IActivationWorkingSetMember member); } public partial interface IActivationWorkingSetMember { bool IsCandidateForRemoval(bool wouldRemove); } public partial interface IActivationWorkingSetObserver { void OnActive(IActivationWorkingSetMember member); void OnAdded(IActivationWorkingSetMember member); void OnDeactivated(IActivationWorkingSetMember member); void OnDeactivating(IActivationWorkingSetMember member); void OnEvicted(IActivationWorkingSetMember member); void OnIdle(IActivationWorkingSetMember member); } public partial interface IAttributeToFactoryMapper where TMetadata : IFacetMetadata { Factory GetFactory(System.Reflection.ParameterInfo parameter, TMetadata metadata); } public partial interface IClusterMembershipService { ClusterMembershipSnapshot CurrentSnapshot { get; } System.Collections.Generic.IAsyncEnumerable MembershipUpdates { get; } System.Threading.Tasks.ValueTask Refresh(MembershipVersion minimumVersion = default); System.Threading.Tasks.Task TryKill(SiloAddress siloAddress); } public partial interface IConfigureGrainContext { void Configure(IGrainContext context); } public partial interface IConfigureGrainContextProvider { bool TryGetConfigurator(GrainType grainType, Orleans.Metadata.GrainProperties properties, out IConfigureGrainContext configurator); } public partial interface IConfigureGrainTypeComponents { void Configure(GrainType grainType, Orleans.Metadata.GrainProperties properties, GrainTypeSharedContext shared); } public partial interface IFatalErrorHandler { bool IsUnexpected(System.Exception exception); void OnFatalException(object sender = null, string context = null, System.Exception exception = null); } public partial interface IGrainActivator { object CreateInstance(IGrainContext context); System.Threading.Tasks.ValueTask DisposeInstance(IGrainContext context, object instance); } public partial interface IGrainContextActivator { IGrainContext CreateContext(GrainAddress address); } public partial interface IGrainContextActivatorProvider { bool TryGet(GrainType grainType, out IGrainContextActivator activator); } public partial interface IGrainServiceFactory { T CastToGrainServiceReference(GrainReference grainReference) where T : Orleans.Services.IGrainService; } public partial interface IHealthCheckParticipant : IHealthCheckable { } public partial interface IPersistentStateConfiguration { string StateName { get; } string StorageName { get; } } public partial interface IPersistentStateFactory { IPersistentState Create(IGrainContext context, IPersistentStateConfiguration config); } public partial interface IPersistentState : Core.IStorage, Core.IStorage { } public partial interface ISiloLifecycle : ILifecycleObservable { int HighestCompletedStage { get; } int LowestStoppedStage { get; } } public partial interface ISiloLifecycleSubject : ISiloLifecycle, ILifecycleObservable, ILifecycleObserver { } public partial interface ISiloStatusListener { void SiloStatusChangeNotification(SiloAddress updatedSilo, SiloStatus status); } public partial interface ISiloStatusOracle { SiloStatus CurrentStatus { get; } SiloAddress SiloAddress { get; } string SiloName { get; } System.Collections.Immutable.ImmutableArray GetActiveSilos(); SiloStatus GetApproximateSiloStatus(SiloAddress siloAddress); System.Collections.Generic.Dictionary GetApproximateSiloStatuses(bool onlyActive = false); bool IsDeadSilo(SiloAddress silo); bool IsFunctionalDirectory(SiloAddress siloAddress); bool SubscribeToSiloStatusEvents(ISiloStatusListener observer); bool TryGetSiloName(SiloAddress siloAddress, out string siloName); bool UnSubscribeFromSiloStatusEvents(ISiloStatusListener observer); } public partial interface IStartupTask { System.Threading.Tasks.Task Execute(System.Threading.CancellationToken cancellationToken); } [System.AttributeUsage(System.AttributeTargets.Parameter)] public partial class PersistentStateAttribute : System.Attribute, IFacetMetadata, IPersistentStateConfiguration { public PersistentStateAttribute(string stateName, string storageName = null) { } public string StateName { get { throw null; } } public string StorageName { get { throw null; } } } public partial class PersistentStateFactory : IPersistentStateFactory { public IPersistentState Create(IGrainContext context, IPersistentStateConfiguration cfg) { throw null; } protected virtual string GetFullStateName(IGrainContext context, IPersistentStateConfiguration cfg) { throw null; } } public sealed partial class Silo : System.IAsyncDisposable, System.IDisposable { public const string PrimarySiloName = "Primary"; [System.Obsolete("This constructor is obsolete and may be removed in a future release. Use SiloHostBuilder to create an instance of ISiloHost instead.")] public Silo(ILocalSiloDetails siloDetails, System.IServiceProvider services) { } public SiloAddress SiloAddress { get { throw null; } } public System.Threading.Tasks.Task SiloTerminated { get { throw null; } } public void Dispose() { } public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public void Stop() { } public System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public override string ToString() { throw null; } } public partial class SiloLifecycleSubject : LifecycleSubject, ISiloLifecycleSubject, ISiloLifecycle, ILifecycleObservable, ILifecycleObserver { public SiloLifecycleSubject(Microsoft.Extensions.Logging.ILogger logger) : base(default!) { } public int HighestCompletedStage { get { throw null; } } public int LowestStoppedStage { get { throw null; } } protected override string GetStageName(int stage) { throw null; } public override System.Threading.Tasks.Task OnStart(System.Threading.CancellationToken cancellationToken = default) { throw null; } protected override void OnStartStageCompleted(int stage) { } protected override void OnStopStageCompleted(int stage) { } protected override void PerfMeasureOnStart(int stage, System.TimeSpan elapsed) { } protected override void PerfMeasureOnStop(int stage, System.TimeSpan elapsed) { } public override System.IDisposable Subscribe(string observerName, int stage, ILifecycleObserver observer) { throw null; } } public abstract partial class SystemTarget : ISystemTarget, IAddressable, IGrainContext, Orleans.Serialization.Invocation.ITargetHolder, System.IEquatable, IGrainExtensionBinder, System.ISpanFormattable, System.IFormattable, System.IDisposable { public System.IServiceProvider ActivationServices { get { throw null; } } public System.Threading.Tasks.Task Deactivated { get { throw null; } } public GrainId GrainId { get { throw null; } } public GrainReference GrainReference { get { throw null; } } ActivationId IGrainContext.ActivationId { get { throw null; } } GrainAddress IGrainContext.Address { get { throw null; } } object IGrainContext.GrainInstance { get { throw null; } } IGrainLifecycle IGrainContext.ObservableLifecycle { get { throw null; } } public IWorkItemScheduler Scheduler { get { throw null; } } public SiloAddress Silo { get { throw null; } } public void Activate(System.Collections.Generic.Dictionary requestContext, System.Threading.CancellationToken cancellationToken) { } public void Deactivate(DeactivationReason deactivationReason, System.Threading.CancellationToken cancellationToken) { } public void Dispose() { } public TComponent GetComponent() { throw null; } public TExtensionInterface GetExtension() where TExtensionInterface : class, IGrainExtension { throw null; } public (TExtension, TExtensionInterface) GetOrSetExtension(System.Func newExtensionFunc) where TExtension : class, TExtensionInterface where TExtensionInterface : class, IGrainExtension { throw null; } public TTarget GetTarget() where TTarget : class { throw null; } public void Migrate(System.Collections.Generic.Dictionary requestContext, System.Threading.CancellationToken cancellationToken) { } TComponent Orleans.Serialization.Invocation.ITargetHolder.GetComponent() { throw null; } public void ReceiveMessage(object message) { } public IGrainTimer RegisterGrainTimer(System.Func callback, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public IGrainTimer RegisterGrainTimer(System.Func callback, TState state, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public IGrainTimer RegisterTimer(System.Func callback, object state, System.TimeSpan dueTime, System.TimeSpan period) { throw null; } public void Rehydrate(IRehydrationContext context) { } public void SetComponent(TComponent instance) where TComponent : class { } bool System.IEquatable.Equals(IGrainContext other) { throw null; } string System.IFormattable.ToString(string format, System.IFormatProvider formatProvider) { throw null; } bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider provider) { throw null; } public sealed override string ToString() { throw null; } } } namespace Orleans.Runtime.Development { public static partial class DevelopmentSiloBuilderExtensions { public static Orleans.Hosting.ISiloBuilder UseInMemoryLeaseProvider(this Orleans.Hosting.ISiloBuilder builder) { throw null; } } public partial class InMemoryLeaseProvider : LeaseProviders.ILeaseProvider { public InMemoryLeaseProvider(IGrainFactory grainFactory) { } public System.Threading.Tasks.Task Acquire(string category, LeaseProviders.LeaseRequest[] leaseRequests) { throw null; } public System.Threading.Tasks.Task Release(string category, LeaseProviders.AcquiredLease[] acquiredLeases) { throw null; } public System.Threading.Tasks.Task Renew(string category, LeaseProviders.AcquiredLease[] acquiredLeases) { throw null; } } } namespace Orleans.Runtime.GrainDirectory { public static partial class GrainDirectoryCacheFactory { public static IGrainDirectoryCache CreateGrainDirectoryCache(System.IServiceProvider services, Orleans.Configuration.GrainDirectoryOptions options) { throw null; } } public partial interface IGrainDirectoryCache { System.Collections.Generic.IEnumerable<(GrainAddress ActivationAddress, int Version)> KeyValues { get; } void AddOrUpdate(GrainAddress value, int version); void Clear(); bool LookUp(GrainId key, out GrainAddress result, out int version); bool Remove(GrainAddress key); bool Remove(GrainId key); } public partial interface IGrainDirectoryResolver { bool TryResolveGrainDirectory(GrainType grainType, Orleans.Metadata.GrainProperties properties, out Orleans.GrainDirectory.IGrainDirectory grainDirectory); } } namespace Orleans.Runtime.Hosting { public static partial class DirectorySiloBuilderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainDirectory(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, string name, System.Func implementationFactory) where T : class, Orleans.GrainDirectory.IGrainDirectory { throw null; } public static Orleans.Hosting.ISiloBuilder AddGrainDirectory(this Orleans.Hosting.ISiloBuilder builder, string name, System.Func implementationFactory) where T : class, Orleans.GrainDirectory.IGrainDirectory { throw null; } } public static partial class StorageProviderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, string name, System.Func implementationFactory) where T : Storage.IGrainStorage { throw null; } } } namespace Orleans.Runtime.MembershipService { [GenerateSerializer] public sealed partial class OrleansClusterConnectivityCheckFailedException : OrleansException { public OrleansClusterConnectivityCheckFailedException() { } public OrleansClusterConnectivityCheckFailedException(string message, System.Exception innerException) { } public OrleansClusterConnectivityCheckFailedException(string message) { } } [GenerateSerializer] public sealed partial class OrleansMissingMembershipEntryException : OrleansException { public OrleansMissingMembershipEntryException() { } public OrleansMissingMembershipEntryException(string message, System.Exception innerException) { } public OrleansMissingMembershipEntryException(string message) { } } } namespace Orleans.Runtime.MembershipService.SiloMetadata { public partial interface ISiloMetadataCache { SiloMetadata GetSiloMetadata(SiloAddress siloAddress); } [GenerateSerializer] [Alias("Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata")] public partial record SiloMetadata() { public static SiloMetadata Empty { get { throw null; } } [Id(0)] public System.Collections.Immutable.ImmutableDictionary Metadata { get { throw null; } } } public static partial class SiloMetadataHostingExtensions { public static Orleans.Hosting.ISiloBuilder UseSiloMetadata(this Orleans.Hosting.ISiloBuilder builder, Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } public static Orleans.Hosting.ISiloBuilder UseSiloMetadata(this Orleans.Hosting.ISiloBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection configurationSection) { throw null; } public static Orleans.Hosting.ISiloBuilder UseSiloMetadata(this Orleans.Hosting.ISiloBuilder builder, System.Collections.Generic.Dictionary metadata) { throw null; } public static Orleans.Hosting.ISiloBuilder UseSiloMetadata(this Orleans.Hosting.ISiloBuilder builder) { throw null; } } } namespace Orleans.Runtime.Placement { public partial interface IPlacementStrategyResolver { bool TryResolvePlacementStrategy(GrainType grainType, Orleans.Metadata.GrainProperties properties, out PlacementStrategy result); } public sealed partial class PlacementDirectorResolver { public PlacementDirectorResolver(System.IServiceProvider services) { } public IPlacementDirector GetPlacementDirector(PlacementStrategy placementStrategy) { throw null; } } public sealed partial class PlacementStrategyResolver { public PlacementStrategyResolver(System.IServiceProvider services, System.Collections.Generic.IEnumerable resolvers, Orleans.Metadata.GrainPropertiesResolver grainPropertiesResolver) { } public PlacementStrategy GetPlacementStrategy(GrainType grainType) { throw null; } } } namespace Orleans.Runtime.Placement.Filtering { public sealed partial class PlacementFilterDirectorResolver { public PlacementFilterDirectorResolver(System.IServiceProvider services) { } public Orleans.Placement.IPlacementFilterDirector GetFilterDirector(Orleans.Placement.PlacementFilterStrategy placementFilterStrategy) { throw null; } } public sealed partial class PlacementFilterStrategyResolver { public PlacementFilterStrategyResolver(System.IServiceProvider services, Orleans.Metadata.GrainPropertiesResolver grainPropertiesResolver) { } public Orleans.Placement.PlacementFilterStrategy[] GetPlacementFilterStrategies(GrainType grainType) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP004")] public partial class PreferredMatchSiloMetadataPlacementFilterAttribute : Orleans.Placement.PlacementFilterAttribute { public PreferredMatchSiloMetadataPlacementFilterAttribute(string[] orderedMetadataKeys, int minCandidates = 2, int order = 0) : base(default!) { } } public partial class PreferredMatchSiloMetadataPlacementFilterStrategy : Orleans.Placement.PlacementFilterStrategy { public PreferredMatchSiloMetadataPlacementFilterStrategy() : base(default) { } public PreferredMatchSiloMetadataPlacementFilterStrategy(string[] orderedMetadataKeys, int minCandidates, int order) : base(default) { } public int MinCandidates { get { throw null; } set { } } public string[] OrderedMetadataKeys { get { throw null; } set { } } public override void AdditionalInitialize(Orleans.Metadata.GrainProperties properties) { } protected override System.Collections.Generic.IEnumerable> GetAdditionalGrainProperties(System.IServiceProvider services, System.Type grainClass, GrainType grainType, System.Collections.Generic.IReadOnlyDictionary existingProperties) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] [System.Diagnostics.CodeAnalysis.Experimental("ORLEANSEXP004")] public partial class RequiredMatchSiloMetadataPlacementFilterAttribute : Orleans.Placement.PlacementFilterAttribute { public RequiredMatchSiloMetadataPlacementFilterAttribute(string[] metadataKeys, int order = 0) : base(default!) { } } public partial class RequiredMatchSiloMetadataPlacementFilterStrategy : Orleans.Placement.PlacementFilterStrategy { public RequiredMatchSiloMetadataPlacementFilterStrategy() : base(default) { } public RequiredMatchSiloMetadataPlacementFilterStrategy(string[] metadataKeys, int order) : base(default) { } public string[] MetadataKeys { get { throw null; } } public override void AdditionalInitialize(Orleans.Metadata.GrainProperties properties) { } protected override System.Collections.Generic.IEnumerable> GetAdditionalGrainProperties(System.IServiceProvider services, System.Type grainClass, GrainType grainType, System.Collections.Generic.IReadOnlyDictionary existingProperties) { throw null; } } } namespace Orleans.Runtime.Services { public abstract partial class GrainServiceClient : Orleans.Services.IGrainServiceClient where TGrainService : Orleans.Services.IGrainService { protected GrainServiceClient(System.IServiceProvider serviceProvider) { } protected GrainReference CurrentGrainReference { get { throw null; } } protected TGrainService GetGrainService(GrainId callingGrainId) { throw null; } protected TGrainService GetGrainService(SiloAddress destination) { throw null; } protected TGrainService GetGrainService(uint key) { throw null; } } } namespace Orleans.Runtime.Utilities { public static partial class OrleansDebuggerHelper { public static object GetGrainInstance(object grainReference) { throw null; } } } namespace OrleansCodeGen.Orleans.LeaseProviders { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILeaseProvider_GrainReference_5C7B2877 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ILeaseProvider_GrainReference_5C7B2877(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILeaseProvider_GrainReference_5C7B2877 instance) { } public Invokable_ILeaseProvider_GrainReference_5C7B2877 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILeaseProvider_GrainReference_5C7B2877 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILeaseProvider_GrainReference_5C7B2877 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILeaseProvider_GrainReference_ACF8E0DD : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ILeaseProvider_GrainReference_ACF8E0DD(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILeaseProvider_GrainReference_ACF8E0DD instance) { } public Invokable_ILeaseProvider_GrainReference_ACF8E0DD ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILeaseProvider_GrainReference_ACF8E0DD instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILeaseProvider_GrainReference_ACF8E0DD value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ILeaseProvider_GrainReference_F2BF11D0 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ILeaseProvider_GrainReference_F2BF11D0(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ILeaseProvider_GrainReference_F2BF11D0 instance) { } public Invokable_ILeaseProvider_GrainReference_F2BF11D0 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ILeaseProvider_GrainReference_F2BF11D0 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ILeaseProvider_GrainReference_F2BF11D0 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILeaseProvider_GrainReference_5C7B2877 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ILeaseProvider_GrainReference_5C7B2877(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_ILeaseProvider_GrainReference_5C7B2877 DeepCopy(Invokable_ILeaseProvider_GrainReference_5C7B2877 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILeaseProvider_GrainReference_ACF8E0DD : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ILeaseProvider_GrainReference_ACF8E0DD(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_ILeaseProvider_GrainReference_ACF8E0DD DeepCopy(Invokable_ILeaseProvider_GrainReference_ACF8E0DD original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ILeaseProvider_GrainReference_F2BF11D0 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ILeaseProvider_GrainReference_F2BF11D0(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_ILeaseProvider_GrainReference_F2BF11D0 DeepCopy(Invokable_ILeaseProvider_GrainReference_F2BF11D0 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.LeaseProviders.ILeaseProvider), "5C7B2877" })] public sealed partial class Invokable_ILeaseProvider_GrainReference_5C7B2877 : global::Orleans.Runtime.TaskRequest { public string arg0; public global::Orleans.LeaseProviders.LeaseRequest[] arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.LeaseProviders.ILeaseProvider), "ACF8E0DD" })] public sealed partial class Invokable_ILeaseProvider_GrainReference_ACF8E0DD : global::Orleans.Runtime.TaskRequest { public string arg0; public global::Orleans.LeaseProviders.AcquiredLease[] arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.LeaseProviders.ILeaseProvider), "F2BF11D0" })] public sealed partial class Invokable_ILeaseProvider_GrainReference_F2BF11D0 : global::Orleans.Runtime.TaskRequest { public string arg0; public global::Orleans.LeaseProviders.AcquiredLease[] arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Runtime { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClusterMember : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ClusterMember(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ClusterMember instance) { } public global::Orleans.Runtime.ClusterMember ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ClusterMember instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ClusterMember value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClusterMembershipSnapshot : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ClusterMembershipSnapshot(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ClusterMembershipSnapshot instance) { } public global::Orleans.Runtime.ClusterMembershipSnapshot ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ClusterMembershipSnapshot instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ClusterMembershipSnapshot value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ClusterMembershipUpdate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ClusterMembershipUpdate(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.ClusterMembershipUpdate instance) { } public global::Orleans.Runtime.ClusterMembershipUpdate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.ClusterMembershipUpdate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.ClusterMembershipUpdate value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Runtime.MembershipService { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansClusterConnectivityCheckFailedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansClusterConnectivityCheckFailedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.MembershipService.OrleansClusterConnectivityCheckFailedException instance) { } public global::Orleans.Runtime.MembershipService.OrleansClusterConnectivityCheckFailedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.MembershipService.OrleansClusterConnectivityCheckFailedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.MembershipService.OrleansClusterConnectivityCheckFailedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansMissingMembershipEntryException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansMissingMembershipEntryException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.MembershipService.OrleansMissingMembershipEntryException instance) { } public global::Orleans.Runtime.MembershipService.OrleansMissingMembershipEntryException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.MembershipService.OrleansMissingMembershipEntryException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.MembershipService.OrleansMissingMembershipEntryException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansClusterConnectivityCheckFailedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansClusterConnectivityCheckFailedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansMissingMembershipEntryException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansMissingMembershipEntryException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Runtime.MembershipService.SiloMetadata { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SiloMetadata : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_SiloMetadata(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata instance) { } public global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SiloMetadata : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public void DeepCopy(global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata input, global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata DeepCopy(global::Orleans.Runtime.MembershipService.SiloMetadata.SiloMetadata original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } ================================================ FILE: src/api/Orleans.Sdk/Orleans.Sdk.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ ================================================ FILE: src/api/Orleans.Serialization/Orleans.Serialization.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [GenerateSerializer] public sealed partial class CodecNotFoundException : SerializerException { public CodecNotFoundException(string message) { } } public sealed partial class DeepCopier { public DeepCopier(Serializers.CodecProvider codecProvider, Cloning.CopyContextPool contextPool) { } public T Copy(T value) { throw null; } public DeepCopier GetCopier() { throw null; } } public sealed partial class DeepCopier { public DeepCopier(Cloning.IDeepCopier copier, Cloning.CopyContextPool contextPool) { } public T Copy(T value) { throw null; } } [Alias("ISerializable")] public partial class DotNetSerializableCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec { public static readonly System.Type CodecType; public DotNetSerializableCodec(TypeSystem.TypeConverter typeResolver) { } [System.Security.SecurityCritical] public bool IsSupportedType(System.Type type) { throw null; } [System.Security.SecurityCritical] public object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } [System.Security.SecurityCritical] public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] [RegisterCopier] [Alias("Exception")] public sealed partial class ExceptionCodec : Codecs.IFieldCodec, Codecs.IFieldCodec, Serializers.IBaseCodec, Serializers.IBaseCodec, Serializers.IGeneralizedCodec, Serializers.IGeneralizedBaseCodec, Serializers.IBaseCodec, Cloning.IBaseCopier, Cloning.IBaseCopier { public ExceptionCodec(TypeSystem.TypeConverter typeConverter, Codecs.IFieldCodec> dictionaryCodec, Cloning.IDeepCopier> dictionaryCopier, Cloning.IDeepCopier exceptionCopier, Microsoft.Extensions.Options.IOptions exceptionSerializationOptions) { } public void DeepCopy(System.Exception input, System.Exception output, Cloning.CopyContext context) { } public void Deserialize(ref Buffers.Reader reader, System.Exception value) { } public void Deserialize(ref Buffers.Reader reader, object value) { } public System.Exception DeserializeException(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public System.Collections.Generic.Dictionary GetDataProperty(System.Exception exception) { throw null; } public System.Runtime.Serialization.SerializationInfo GetObjectData(System.Exception value) { throw null; } public bool IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public System.Exception ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, System.Exception value) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(ref Buffers.Writer writer, object value) where TBufferWriter : System.Buffers.IBufferWriter { } public void SerializeException(ref Buffers.Writer writer, System.Exception value) where TBufferWriter : System.Buffers.IBufferWriter { } public void SetBaseProperties(System.Exception value, string message, string stackTrace, System.Exception innerException, int hResult, System.Collections.Generic.Dictionary data) { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Exception value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial class ExceptionSerializationOptions { public System.Func SupportedExceptionTypeFilter { get { throw null; } set { } } public System.Collections.Generic.HashSet SupportedNamespacePrefixes { get { throw null; } } } [GenerateSerializer] public sealed partial class ExtendedWireTypeInvalidException : SerializerException { } [GenerateSerializer] public sealed partial class FieldIdNotPresentException : SerializerException { } [GenerateSerializer] public sealed partial class FieldTypeInvalidException : SerializerException { } [GenerateSerializer] public sealed partial class FieldTypeMissingException : SerializerException { public FieldTypeMissingException(System.Type type) { } } [GenerateSerializer] public sealed partial class IllegalTypeException : SerializerException { public IllegalTypeException(string typeName) { } [Id(0)] public string TypeName { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } public partial interface ISerializerBuilder { Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } } public partial interface ITypeConverter { bool TryFormat(System.Type type, out string formatted); bool TryParse(string formatted, out System.Type type); } public partial interface ITypeFilter { bool? IsTypeAllowed(System.Type type); } public partial interface ITypeNameFilter { bool? IsTypeNameAllowed(string typeName, string assemblyName); } public sealed partial class ObjectSerializer { public ObjectSerializer(Session.SerializerSessionPool sessionPool) { } public bool CanSerialize(System.Type type) { throw null; } public object Deserialize(System.ArraySegment source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(System.ArraySegment source, System.Type type) { throw null; } public object Deserialize(System.Buffers.ReadOnlySequence source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(System.Buffers.ReadOnlySequence source, System.Type type) { throw null; } public object Deserialize(byte[] source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(byte[] source, System.Type type) { throw null; } public object Deserialize(System.IO.Stream source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(System.IO.Stream source, System.Type type) { throw null; } public object Deserialize(System.ReadOnlyMemory source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(System.ReadOnlyMemory source, System.Type type) { throw null; } public object Deserialize(System.ReadOnlySpan source, Session.SerializerSession session, System.Type type) { throw null; } public object Deserialize(System.ReadOnlySpan source, System.Type type) { throw null; } public object Deserialize(ref Buffers.Reader source, System.Type type) { throw null; } public int Serialize(object value, System.ArraySegment destination, Session.SerializerSession session, System.Type type) { throw null; } public int Serialize(object value, System.ArraySegment destination, System.Type type) { throw null; } public int Serialize(object value, byte[] destination, Session.SerializerSession session, System.Type type) { throw null; } public int Serialize(object value, byte[] destination, System.Type type) { throw null; } public void Serialize(object value, System.IO.Stream destination, Session.SerializerSession session, System.Type type, int sizeHint = 0) { } public void Serialize(object value, System.IO.Stream destination, System.Type type, int sizeHint = 0) { } public void Serialize(object value, ref System.Memory destination, Session.SerializerSession session, System.Type type) { } public void Serialize(object value, ref System.Memory destination, System.Type type) { } public void Serialize(object value, ref System.Span destination, Session.SerializerSession session, System.Type type) { } public void Serialize(object value, ref System.Span destination, System.Type type) { } public void Serialize(object value, TBufferWriter destination, Session.SerializerSession session, System.Type type) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(object value, TBufferWriter destination, System.Type type) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(object value, ref Buffers.Writer destination, System.Type type) where TBufferWriter : System.Buffers.IBufferWriter { } } [GenerateSerializer] public sealed partial class ReferenceFieldNotSupportedException : SerializerException { public ReferenceFieldNotSupportedException(System.Type targetType) { } [Id(0)] public System.Type TargetReferenceType { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class ReferenceNotFoundException : SerializerException { public ReferenceNotFoundException(System.Type targetType, uint targetId) { } [Id(0)] public uint TargetReference { get { throw null; } } [Id(1)] public System.Type TargetReferenceType { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class RequiredFieldMissingException : SerializerException { public RequiredFieldMissingException(string message) { } } [GenerateSerializer] public sealed partial class SchemaTypeInvalidException : SerializerException { } public partial class SerializationConstructorNotFoundException : System.Exception { [System.Security.SecurityCritical] [System.Obsolete] protected SerializationConstructorNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } [System.Security.SecurityCritical] public SerializationConstructorNotFoundException(System.Type type) { } } public sealed partial class Serializer { public Serializer(Session.SerializerSessionPool sessionPool) { } public Session.SerializerSessionPool SessionPool { get { throw null; } } public bool CanSerialize(System.Type type) { throw null; } public bool CanSerialize() { throw null; } public T Deserialize(Buffers.PooledBuffer.BufferSlice source, Session.SerializerSession session) { throw null; } public T Deserialize(Buffers.PooledBuffer.BufferSlice source) { throw null; } public T Deserialize(System.ArraySegment source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ArraySegment source) { throw null; } public T Deserialize(System.Buffers.ReadOnlySequence source, Session.SerializerSession session) { throw null; } public T Deserialize(System.Buffers.ReadOnlySequence source) { throw null; } public T Deserialize(byte[] source, Session.SerializerSession session) { throw null; } public T Deserialize(byte[] source) { throw null; } public T Deserialize(System.IO.Stream source, Session.SerializerSession session) { throw null; } public T Deserialize(System.IO.Stream source) { throw null; } public T Deserialize(System.ReadOnlyMemory source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ReadOnlyMemory source) { throw null; } public T Deserialize(System.ReadOnlySpan source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ReadOnlySpan source) { throw null; } public T Deserialize(ref Buffers.Reader source) { throw null; } public Serializer GetSerializer() { throw null; } public int Serialize(T value, System.ArraySegment destination, Session.SerializerSession session) { throw null; } public int Serialize(T value, System.ArraySegment destination) { throw null; } public int Serialize(T value, byte[] destination, Session.SerializerSession session) { throw null; } public int Serialize(T value, byte[] destination) { throw null; } public void Serialize(T value, System.IO.Stream destination, Session.SerializerSession session, int sizeHint = 0) { } public void Serialize(T value, System.IO.Stream destination, int sizeHint = 0) { } public void Serialize(T value, ref System.Memory destination, Session.SerializerSession session) { } public void Serialize(T value, ref System.Memory destination) { } public void Serialize(T value, ref System.Span destination, Session.SerializerSession session) { } public void Serialize(T value, ref System.Span destination) { } public void Serialize(T value, TBufferWriter destination, Session.SerializerSession session) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(T value, TBufferWriter destination) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(T value, ref Buffers.Writer destination) where TBufferWriter : System.Buffers.IBufferWriter { } public byte[] SerializeToArray(T value) { throw null; } } public static partial class SerializerBuilderExtensions { public static ISerializerBuilder AddAssembly(this ISerializerBuilder builder, System.Reflection.Assembly assembly) { throw null; } public static ISerializerBuilder Configure(this ISerializerBuilder builder, Microsoft.Extensions.Options.IConfigureOptions configure) { throw null; } public static ISerializerBuilder Configure(this ISerializerBuilder builder, System.Action configure) { throw null; } public static ISerializerBuilder Configure(this ISerializerBuilder builder, System.Func> factory) { throw null; } } public static partial class SerializerConfigurationAnalyzer { public static System.Collections.Generic.Dictionary AnalyzeSerializerAvailability(Serializers.ICodecProvider codecProvider, Configuration.TypeManifestOptions options) { throw null; } public partial class SerializerConfigurationComplaint { public bool HasCopier { get { throw null; } set { } } public bool HasSerializer { get { throw null; } set { } } public System.Collections.Generic.Dictionary> Methods { get { throw null; } } } } [GenerateSerializer] public partial class SerializerException : System.Exception { public SerializerException() { } [System.Obsolete] protected SerializerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public SerializerException(string message, System.Exception innerException) { } public SerializerException(string message) { } } public sealed partial class Serializer { public Serializer(Codecs.IFieldCodec codec, Session.SerializerSessionPool sessionPool) { } public Serializer(Session.SerializerSessionPool sessionPool) { } public T Deserialize(Buffers.PooledBuffer.BufferSlice source, Session.SerializerSession session) { throw null; } public T Deserialize(Buffers.PooledBuffer.BufferSlice source) { throw null; } public T Deserialize(System.ArraySegment source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ArraySegment source) { throw null; } public T Deserialize(System.Buffers.ReadOnlySequence source, Session.SerializerSession session) { throw null; } public T Deserialize(System.Buffers.ReadOnlySequence source) { throw null; } public T Deserialize(byte[] source, Session.SerializerSession session) { throw null; } public T Deserialize(byte[] source) { throw null; } public T Deserialize(System.IO.Stream source, Session.SerializerSession session) { throw null; } public T Deserialize(System.IO.Stream source) { throw null; } public T Deserialize(System.ReadOnlyMemory source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ReadOnlyMemory source) { throw null; } public T Deserialize(System.ReadOnlySpan source, Session.SerializerSession session) { throw null; } public T Deserialize(System.ReadOnlySpan source) { throw null; } public T Deserialize(ref Buffers.Reader source) { throw null; } public int Serialize(T value, byte[] destination, Session.SerializerSession session) { throw null; } public int Serialize(T value, byte[] destination) { throw null; } public void Serialize(T value, System.IO.Stream destination, Session.SerializerSession session, int sizeHint = 0) { } public void Serialize(T value, System.IO.Stream destination, int sizeHint = 0) { } public void Serialize(T value, ref System.Memory destination, Session.SerializerSession session) { } public void Serialize(T value, ref System.Memory destination) { } public void Serialize(T value, ref System.Span destination, Session.SerializerSession session) { } public void Serialize(T value, ref System.Span destination) { } public void Serialize(T value, TBufferWriter destination, Session.SerializerSession session) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(T value, TBufferWriter destination) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(T value, ref Buffers.Writer destination) where TBufferWriter : System.Buffers.IBufferWriter { } public byte[] SerializeToArray(T value) { throw null; } } public static partial class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSerializer(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure = null) { throw null; } } [GenerateSerializer] public sealed partial class TypeMissingException : SerializerException { } [System.Diagnostics.DebuggerDisplay("{GetDebuggerDisplay(),nq}")] public sealed partial class UnavailableExceptionFallbackException : System.Exception { public UnavailableExceptionFallbackException() { } [System.Obsolete] public UnavailableExceptionFallbackException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public UnavailableExceptionFallbackException(string message, System.Exception innerException) { } public string ExceptionType { get { throw null; } } public System.Collections.Generic.Dictionary Properties { get { throw null; } } public override string ToString() { throw null; } } [GenerateSerializer] public sealed partial class UnexpectedLengthPrefixValueException : SerializerException { public UnexpectedLengthPrefixValueException(string typeName, uint expectedLength, uint actualLength) { } public UnexpectedLengthPrefixValueException(string message) { } } [GenerateSerializer] public sealed partial class UnknownReferencedTypeException : SerializerException { public UnknownReferencedTypeException(uint reference) { } [Id(0)] public uint Reference { get { throw null; } set { } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class UnknownWellKnownTypeException : SerializerException { public UnknownWellKnownTypeException(uint id) { } [Id(0)] public uint Id { get { throw null; } set { } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class UnsupportedWireTypeException : SerializerException { public UnsupportedWireTypeException() { } public UnsupportedWireTypeException(string message) { } } public sealed partial class ValueSerializer where T : struct { public ValueSerializer(Serializers.IValueSerializerProvider codecProvider, Session.SerializerSessionPool sessionPool) { } public void Deserialize(System.ArraySegment source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(System.ArraySegment source, scoped ref T result) { } public void Deserialize(System.Buffers.ReadOnlySequence source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(System.Buffers.ReadOnlySequence source, scoped ref T result) { } public void Deserialize(byte[] source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(byte[] source, scoped ref T result) { } public void Deserialize(System.IO.Stream source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(System.IO.Stream source, scoped ref T result) { } public void Deserialize(System.ReadOnlyMemory source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(System.ReadOnlyMemory source, scoped ref T result) { } public void Deserialize(System.ReadOnlySpan source, scoped ref T result, Session.SerializerSession session) { } public void Deserialize(System.ReadOnlySpan source, scoped ref T result) { } public void Deserialize(ref Buffers.Reader source, scoped ref T result) { } public void Serialize(scoped ref T value, System.ArraySegment destination) { } public int Serialize(scoped ref T value, byte[] destination, Session.SerializerSession session) { throw null; } public int Serialize(scoped ref T value, byte[] destination) { throw null; } public void Serialize(scoped ref T value, System.IO.Stream destination, Session.SerializerSession session, int sizeHint = 0) { } public void Serialize(scoped ref T value, System.IO.Stream destination, int sizeHint = 0) { } public void Serialize(scoped ref T value, ref System.Memory destination, Session.SerializerSession session) { } public void Serialize(scoped ref T value, ref System.Memory destination) { } public void Serialize(scoped ref T value, ref System.Span destination, Session.SerializerSession session) { } public void Serialize(scoped ref T value, ref System.Span destination) { } public void Serialize(scoped ref T value, TBufferWriter destination, Session.SerializerSession session) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(scoped ref T value, TBufferWriter destination) where TBufferWriter : System.Buffers.IBufferWriter { } public void Serialize(scoped ref T value, ref Buffers.Writer destination) where TBufferWriter : System.Buffers.IBufferWriter { } public byte[] SerializeToArray(scoped ref T value) { throw null; } } } namespace Orleans.Serialization.Activators { public partial interface IActivator { T Create(); } } namespace Orleans.Serialization.Buffers { public partial struct ArcBuffer : System.IDisposable { private int _dummyPrimitive; public readonly ArcBufferPage First; public readonly int Length; public readonly int Offset; public ArcBuffer(ArcBufferPage first, int token, int offset, int length) { } public ArraySegmentEnumerator ArraySegments { get { throw null; } } public MemoryEnumerator MemorySegments { get { throw null; } } public SpanEnumerator SpanSegments { get { throw null; } } public readonly System.Buffers.ReadOnlySequence AsReadOnlySequence() { throw null; } public readonly void CopyTo(ArcBufferWriter output) { } public readonly int CopyTo(System.Span output) { throw null; } public readonly void CopyTo(ref TBufferWriter output) where TBufferWriter : System.Buffers.IBufferWriter { } public void Dispose() { } public readonly SpanEnumerator GetEnumerator() { throw null; } public readonly void Pin() { } public readonly ArcBuffer Slice(int offset, int length) { throw null; } public readonly ArcBuffer Slice(int offset) { throw null; } public readonly byte[] ToArray() { throw null; } public void Unpin() { } public readonly ArcBuffer UnsafeSlice(int offset, int length) { throw null; } public partial struct ArraySegmentEnumerator : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable, System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { public ArraySegmentEnumerator(ArcBuffer slice) { } public System.ArraySegment Current { get { throw null; } } public bool IsCompleted { get { throw null; } } object? System.Collections.IEnumerator.Current { get { throw null; } } public readonly ArraySegmentEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } readonly System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } readonly System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } void System.Collections.IEnumerator.Reset() { } readonly void System.IDisposable.Dispose() { } } public partial struct MemoryEnumerator : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable, System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { public MemoryEnumerator(ArcBuffer slice) { } public System.ReadOnlyMemory Current { get { throw null; } } object? System.Collections.IEnumerator.Current { get { throw null; } } public readonly MemoryEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } readonly System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } readonly System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } void System.Collections.IEnumerator.Reset() { } readonly void System.IDisposable.Dispose() { } } public readonly partial struct PageSegment { public readonly int Length; public readonly int Offset; public readonly ArcBufferPage Page; public PageSegment(ArcBufferPage page, int offset, int length) { } public System.ArraySegment ArraySegment { get { throw null; } } public System.ReadOnlyMemory Memory { get { throw null; } } public System.ReadOnlySpan Span { get { throw null; } } } public ref partial struct SpanEnumerator { private object _dummy; private int _dummyPrimitive; public SpanEnumerator(ArcBuffer slice) { } public System.ReadOnlySpan Current { get { throw null; } } public readonly SpanEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } } public sealed partial class ArcBufferPage { internal ArcBufferPage() { } public byte[] Array { get { throw null; } } public bool IsMinimumSize { get { throw null; } } public bool IsValid { get { throw null; } } public int Length { get { throw null; } } public ArcBufferPage? Next { get { throw null; } protected set { } } public System.ArraySegment ReadableArraySegment { get { throw null; } } public System.ReadOnlyMemory ReadableMemory { get { throw null; } } public System.ReadOnlySpan ReadableSpan { get { throw null; } } public int Version { get { throw null; } } public System.ArraySegment WritableArraySegment { get { throw null; } } public System.Memory WritableMemory { get { throw null; } } public System.Span WritableSpan { get { throw null; } } public int WriteCapacity { get { throw null; } } public void Advance(int bytes) { } public System.ArraySegment AsArraySegment(int offset, int length) { throw null; } public System.Memory AsMemory(int offset, int length) { throw null; } public System.Memory AsMemory(int offset) { throw null; } public System.Span AsSpan(int offset, int length) { throw null; } public System.Span AsSpan(int offset) { throw null; } public void CheckValidity(int token) { } public void Pin(int token) { } public void ResizeLargeSegment(int length) { } public void SetNext(ArcBufferPage next, int token) { } public void Unpin(int token) { } } public readonly partial struct ArcBufferReader { private readonly object _dummy; private readonly int _dummyPrimitive; public ArcBufferReader(ArcBufferWriter writer) { } public int Length { get { throw null; } } public readonly void Consume(System.Span output) { } public readonly ArcBuffer ConsumeSlice(int count) { throw null; } public readonly System.ReadOnlySpan Peek(scoped in System.Span destination) { throw null; } public readonly ArcBuffer PeekSlice(int count) { throw null; } public readonly void Skip(int count) { } } [Immutable] public sealed partial class ArcBufferWriter : System.Buffers.IBufferWriter, System.IDisposable { public const int MinimumPageSize = 16384; public int Length { get { throw null; } } public void AdvanceReader(int count) { } public void AdvanceWriter(int count) { } public ArcBuffer ConsumeSlice(int count) { throw null; } public void Dispose() { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } public int Peek(System.Span output) { throw null; } public System.ReadOnlySpan Peek(scoped in System.Span destination) { throw null; } public ArcBuffer PeekSlice(int count) { throw null; } public void ReplenishBuffers(System.Collections.Generic.List> buffers) { } public void Reset() { } void System.Buffers.IBufferWriter.Advance(int count) { } public void Write(System.Buffers.ReadOnlySequence input) { } public void Write(System.ReadOnlySpan value) { } } public static partial class BufferWriterExtensions { public static Writer CreateWriter(this TBufferWriter buffer, Session.SerializerSession session) where TBufferWriter : System.Buffers.IBufferWriter { throw null; } } [Immutable] public partial struct PooledBuffer : System.Buffers.IBufferWriter, System.IDisposable { private object _dummy; private int _dummyPrimitive; public int Length { get { throw null; } } public MemoryEnumerator MemorySegments { get { throw null; } } public void Advance(int bytes) { } public System.Buffers.ReadOnlySequence AsReadOnlySequence() { throw null; } public readonly void CopyTo(System.Span output) { } public readonly void CopyTo(ref TBufferWriter writer) where TBufferWriter : System.Buffers.IBufferWriter { } public readonly void CopyTo(ref Writer writer) where TBufferWriter : System.Buffers.IBufferWriter { } public void Dispose() { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } public void Reset() { } public readonly BufferSlice Slice() { throw null; } public readonly BufferSlice Slice(int offset, int length) { throw null; } public readonly BufferSlice Slice(int offset) { throw null; } public readonly byte[] ToArray() { throw null; } public void Write(System.Buffers.ReadOnlySequence input) { } public void Write(System.ReadOnlySpan value) { } public readonly partial struct BufferSlice { private readonly object _dummy; private readonly int _dummyPrimitive; public BufferSlice(in PooledBuffer buffer, int offset, int length) { } public PooledBuffer Buffer { get { throw null; } } public int Length { get { throw null; } } public MemoryEnumerator MemorySegments { get { throw null; } } public int Offset { get { throw null; } } public readonly void CopyTo(ref PooledBuffer output) { } public readonly int CopyTo(System.Span output) { throw null; } public readonly void CopyTo(ref TBufferWriter output) where TBufferWriter : struct, System.Buffers.IBufferWriter { } public readonly SpanEnumerator GetEnumerator() { throw null; } public readonly BufferSlice Slice(int offset, int length) { throw null; } public readonly BufferSlice Slice(int offset) { throw null; } public readonly byte[] ToArray() { throw null; } public partial struct MemoryEnumerator { private object _dummy; private int _dummyPrimitive; public MemoryEnumerator(BufferSlice slice) { } public System.ReadOnlyMemory Current { get { throw null; } } public readonly MemoryEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } public ref partial struct SpanEnumerator { private object _dummy; private int _dummyPrimitive; public SpanEnumerator(BufferSlice slice) { } public System.ReadOnlySpan Current { get { throw null; } } public readonly SpanEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } } public partial struct MemoryEnumerator { private object _dummy; private int _dummyPrimitive; public System.ReadOnlyMemory CurrentMemory; public MemoryEnumerator(PooledBuffer buffer) { } public System.ReadOnlyMemory Current { get { throw null; } } public readonly MemoryEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } public ref partial struct SpanEnumerator { private object _dummy; private int _dummyPrimitive; public SpanEnumerator(ref PooledBuffer buffer) { } public System.ReadOnlySpan Current { get { throw null; } } public readonly SpanEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } } } public static partial class PooledBufferExtensions { public static PooledBuffer.SpanEnumerator GetEnumerator(this ref PooledBuffer buffer) { throw null; } } public static partial class Reader { public static Reader Create(PooledBuffer input, Session.SerializerSession session) { throw null; } public static Reader Create(PooledBuffer.BufferSlice input, Session.SerializerSession session) { throw null; } public static Reader Create(System.Buffers.ReadOnlySequence sequence, Session.SerializerSession session) { throw null; } public static Reader Create(byte[] buffer, Session.SerializerSession session) { throw null; } public static Reader Create(System.IO.Stream stream, Session.SerializerSession session) { throw null; } public static Reader Create(System.ReadOnlyMemory buffer, Session.SerializerSession session) { throw null; } public static Reader Create(System.ReadOnlySpan buffer, Session.SerializerSession session) { throw null; } } public abstract partial class ReaderInput { public abstract long Length { get; } public abstract long Position { get; } public abstract byte ReadByte(); public abstract void ReadBytes(byte[] destination, int offset, int length); public abstract void ReadBytes(System.Span destination); public abstract uint ReadUInt32(); public abstract ulong ReadUInt64(); public abstract void Seek(long position); public abstract void Skip(long count); public abstract bool TryReadBytes(int length, out System.ReadOnlySpan bytes); } public ref partial struct Reader { private TInput _input; private object _dummy; private int _dummyPrimitive; public long Length { get { throw null; } } public long Position { get { throw null; } } public Session.SerializerSession Session { get { throw null; } } public void ForkFrom(long position, out Reader forked) { throw null; } public byte ReadByte() { throw null; } public void ReadBytes(scoped System.Span destination) { } public byte[] ReadBytes(uint count) { throw null; } public void ReadBytes(scoped ref TBufferWriter writer, int count) where TBufferWriter : System.Buffers.IBufferWriter { } public int ReadInt32() { throw null; } public long ReadInt64() { throw null; } public uint ReadUInt32() { throw null; } public ulong ReadUInt64() { throw null; } public uint ReadVarUInt32() { throw null; } public ulong ReadVarUInt64() { throw null; } public void ResumeFrom(long position) { } public void Skip(long count) { } public bool TryReadBytes(int length, out System.ReadOnlySpan bytes) { throw null; } } public partial struct ReadOnlySequenceInput { private int _dummyPrimitive; } public readonly partial struct SpanReaderInput { } public static partial class VarIntReaderExtensions { public static short ReadInt16(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static int ReadInt32(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static long ReadInt64(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static sbyte ReadInt8(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static ushort ReadUInt16(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static uint ReadUInt32(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static ulong ReadUInt64(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static byte ReadUInt8(this ref Reader reader, WireProtocol.WireType wireType) { throw null; } public static short ReadVarInt16(this ref Reader reader) { throw null; } public static int ReadVarInt32(this ref Reader reader) { throw null; } public static long ReadVarInt64(this ref Reader reader) { throw null; } public static sbyte ReadVarInt8(this ref Reader reader) { throw null; } public static ushort ReadVarUInt16(this ref Reader reader) { throw null; } public static byte ReadVarUInt8(this ref Reader reader) { throw null; } } public static partial class Writer { public static Writer Create(byte[] output, Session.SerializerSession session) { throw null; } public static Writer Create(System.IO.MemoryStream destination, Session.SerializerSession session) { throw null; } public static Writer Create(System.IO.Stream destination, Session.SerializerSession session, int sizeHint = 0) { throw null; } public static Writer Create(System.Memory output, Session.SerializerSession session) { throw null; } public static Writer Create(System.Span output, Session.SerializerSession session) { throw null; } public static Writer Create(TBufferWriter destination, Session.SerializerSession session) where TBufferWriter : System.Buffers.IBufferWriter { throw null; } public static Writer CreatePooled(Session.SerializerSession session) { throw null; } public static Writer CreatePooled(System.IO.Stream destination, Session.SerializerSession session, int sizeHint = 0) { throw null; } } public ref partial struct Writer where TBufferWriter : System.Buffers.IBufferWriter { private object _dummy; private int _dummyPrimitive; public TBufferWriter Output; public int Position { get { throw null; } } public Session.SerializerSession Session { get { throw null; } } public System.Span WritableSpan { get { throw null; } } public void AdvanceSpan(int length) { } public void Allocate(int sizeHint) { } public void Commit() { } public void Dispose() { } public void EnsureContiguous(int length) { } public void Write(scoped System.ReadOnlySpan value) { } public void WriteByte(byte value) { } public void WriteEndBase() { } public void WriteEndObject() { } public void WriteFieldHeader(uint fieldId, System.Type expectedType, System.Type actualType, WireProtocol.WireType wireType) { } public void WriteFieldHeaderExpected(uint fieldId, WireProtocol.WireType wireType) { } public void WriteInt32(int value) { } public void WriteInt64(long value) { } public void WriteStartObject(uint fieldId, System.Type expectedType, System.Type actualType) { } public void WriteUInt32(uint value) { } public void WriteUInt64(ulong value) { } public void WriteVarInt16(short value) { } public void WriteVarInt32(int value) { } public void WriteVarInt64(long value) { } public void WriteVarInt8(sbyte value) { } public void WriteVarUInt16(ushort value) { } public void WriteVarUInt32(uint value) { } public void WriteVarUInt64(ulong value) { } public void WriteVarUInt8(byte value) { } } } namespace Orleans.Serialization.Buffers.Adaptors { public partial struct ArrayStreamBufferWriter : System.Buffers.IBufferWriter { private object _dummy; private int _dummyPrimitive; public const int DefaultInitialBufferSize = 256; public ArrayStreamBufferWriter(System.IO.Stream stream, int sizeHint = 0) { } public void Advance(int count) { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } } public partial struct BufferSliceReaderInput { private object _dummy; private int _dummyPrimitive; public BufferSliceReaderInput(in PooledBuffer.BufferSlice slice) { } } public partial class BufferWriterBox : System.Buffers.IBufferWriter where TBufferWriter : struct, System.Buffers.IBufferWriter { public BufferWriterBox(TBufferWriter bufferWriter) { } public ref TBufferWriter Value { get { throw null; } } public void Advance(int count) { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } } public partial struct MemoryBufferWriter : System.Buffers.IBufferWriter { private int _dummyPrimitive; public int BytesWritten { get { throw null; } } public void Advance(int count) { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } } public readonly partial struct MemoryStreamBufferWriter : System.Buffers.IBufferWriter { private readonly object _dummy; private readonly int _dummyPrimitive; public MemoryStreamBufferWriter(System.IO.MemoryStream stream) { } public readonly void Advance(int count) { } public readonly System.Memory GetMemory(int sizeHint = 0) { throw null; } public readonly System.Span GetSpan(int sizeHint = 0) { throw null; } } public sealed partial class PooledBufferStream : System.IO.Stream { public PooledBufferStream() { } public PooledBufferStream(int minAllocationSize = 0) { } public override bool CanRead { get { throw null; } } public override bool CanSeek { get { throw null; } } public override bool CanWrite { get { throw null; } } public override long Length { get { throw null; } } public override long Position { get { throw null; } set { } } public void CopyTo(ref Writer writer) where TBufferWriter : System.Buffers.IBufferWriter { } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { throw null; } public static PooledBufferStream Rent() { throw null; } public System.Buffers.ReadOnlySequence RentReadOnlySequence() { throw null; } public void Reset() { } public static void Return(PooledBufferStream stream) { } public void ReturnReadOnlySequence(in System.Buffers.ReadOnlySequence sequence) { } public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } public override void SetLength(long value) { } public byte[] ToArray() { throw null; } public override void Write(byte[] buffer, int offset, int count) { } } public partial struct PoolingStreamBufferWriter : System.Buffers.IBufferWriter, System.IDisposable { private object _dummy; private int _dummyPrimitive; public void Advance(int count) { } public readonly void Dispose() { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Span GetSpan(int sizeHint = 0) { throw null; } } public partial struct SpanBufferWriter : System.Buffers.IBufferWriter { private int _dummyPrimitive; public int BytesWritten { get { throw null; } } public void Advance(int count) { } public readonly System.Memory GetMemory(int sizeHint = 0) { throw null; } public readonly System.Span GetSpan(int sizeHint = 0) { throw null; } } } namespace Orleans.Serialization.Cloning { public sealed partial class CopyContext : System.IDisposable { public CopyContext(Serializers.CodecProvider codecProvider, System.Action onDisposed) { } public T? DeepCopy(T? value) { throw null; } public void Dispose() { } public void RecordCopy(object original, object copy) { } public void Reset() { } public bool TryGetCopy(object? original, out T? result) where T : class { throw null; } } public sealed partial class CopyContextPool { public CopyContextPool(Serializers.CodecProvider codecProvider) { } public CopyContext GetContext() { throw null; } } public partial interface IBaseCopier { } public partial interface IBaseCopier : IBaseCopier where T : class { void DeepCopy(T input, T output, CopyContext context); } public partial interface IDeepCopier { object? DeepCopy(object? input, CopyContext context); } public partial interface IDeepCopierProvider { IBaseCopier GetBaseCopier() where T : class; IDeepCopier GetDeepCopier(System.Type type); IDeepCopier GetDeepCopier(); IDeepCopier? TryGetDeepCopier(System.Type type); IDeepCopier? TryGetDeepCopier(); } public partial interface IDeepCopier : IDeepCopier { T DeepCopy(T input, CopyContext context); object? IDeepCopier.DeepCopy(object? input, CopyContext context); } public partial interface IDerivedTypeCopier : IDeepCopier { } public partial interface IGeneralizedCopier : IDeepCopier { bool IsSupportedType(System.Type type); } public partial interface IOptionalDeepCopier : IDeepCopier { bool IsShallowCopyable(); } public partial interface ISpecializableCopier { IDeepCopier GetSpecializedCopier(System.Type type); bool IsSupportedType(System.Type type); } public partial class ShallowCopier : IOptionalDeepCopier, IDeepCopier, IDeepCopier { public T DeepCopy(T input, CopyContext _) { throw null; } public object? DeepCopy(object? input, CopyContext _) { throw null; } public bool IsShallowCopyable() { throw null; } } } namespace Orleans.Serialization.Codecs { [RegisterSerializer] public sealed partial class ArrayCodec : IFieldCodec, IFieldCodec { public ArrayCodec(IFieldCodec fieldCodec) { } public T[] ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, T[] value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ArrayCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public ArrayCopier(Cloning.IDeepCopier elementCopier) { } public T[] DeepCopy(T[] input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class ArrayListCodec : GeneralizedReferenceTypeSurrogateCodec { public ArrayListCodec(Serializers.IValueSerializer surrogateSerializer) : base(default!) { } public override System.Collections.ArrayList ConvertFromSurrogate(ref ArrayListSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.ArrayList value, ref ArrayListSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ArrayListCopier : Cloning.IDeepCopier, Cloning.IDeepCopier, Cloning.IBaseCopier, Cloning.IBaseCopier { public System.Collections.ArrayList DeepCopy(System.Collections.ArrayList input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.ArrayList input, System.Collections.ArrayList output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct ArrayListSurrogate { [Id(0)] public object[] Values; } [RegisterSerializer] public sealed partial class ArraySegmentCodec : IFieldCodec>, IFieldCodec { public ArraySegmentCodec(IFieldCodec fieldCodec) { } public System.ArraySegment ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ArraySegment value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ArraySegmentCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public ArraySegmentCopier(Cloning.IDeepCopier elementCopier) { } public System.ArraySegment DeepCopy(System.ArraySegment input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ArraySegmentOfByteCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public static System.ArraySegment DeepCopy(System.ArraySegment input, Cloning.CopyContext copyContext) { throw null; } System.ArraySegment Cloning.IDeepCopier>.DeepCopy(System.ArraySegment input, Cloning.CopyContext _) { throw null; } } [RegisterSerializer] public sealed partial class BitArrayCodec : IFieldCodec, IFieldCodec { System.Collections.BitArray IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.BitArray value) { } public static System.Collections.BitArray ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } } [RegisterCopier] public sealed partial class BitArrayCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public static System.Collections.BitArray DeepCopy(System.Collections.BitArray input, Cloning.CopyContext context) { throw null; } System.Collections.BitArray Cloning.IDeepCopier.DeepCopy(System.Collections.BitArray input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class BitVector32Codec : IFieldCodec, IFieldCodec { public System.Collections.Specialized.BitVector32 ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Specialized.BitVector32 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class BoolCodec : IFieldCodec, IFieldCodec { bool IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, bool value) { } public static bool ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, bool value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ByteArrayCodec : IFieldCodec, IFieldCodec { byte[] IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, byte[] value) { } public static byte[] ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, byte[] value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ByteArrayCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public static byte[] DeepCopy(byte[] input, Cloning.CopyContext context) { throw null; } byte[] Cloning.IDeepCopier.DeepCopy(byte[] input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class ByteCodec : IFieldCodec, IFieldCodec { byte IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, byte value) { } public static byte ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, byte value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, byte value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class CharCodec : IFieldCodec, IFieldCodec { char IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, char value) { } public static char ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, char value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class CollectionCodec : IFieldCodec>, IFieldCodec, Serializers.IBaseCodec>, Serializers.IBaseCodec { public CollectionCodec(IFieldCodec fieldCodec) { } public void Deserialize(ref Buffers.Reader reader, System.Collections.ObjectModel.Collection value) { } public System.Collections.ObjectModel.Collection ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, System.Collections.ObjectModel.Collection value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.ObjectModel.Collection value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class CollectionCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public CollectionCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.ObjectModel.Collection DeepCopy(System.Collections.ObjectModel.Collection input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.ObjectModel.Collection input, System.Collections.ObjectModel.Collection output, Cloning.CopyContext context) { } } public partial class CommonCodecTypeFilter { public static bool IsAbstractOrFrameworkType(System.Type type) { throw null; } } [RegisterSerializer] public sealed partial class CompareInfoCodec : IFieldCodec, IFieldCodec { public System.Globalization.CompareInfo ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Globalization.CompareInfo value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ConcurrentDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ConcurrentDictionarySurrogate> { public ConcurrentDictionaryCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Concurrent.ConcurrentDictionary ConvertFromSurrogate(ref ConcurrentDictionarySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Concurrent.ConcurrentDictionary value, ref ConcurrentDictionarySurrogate surrogate) { } } [RegisterCopier] public sealed partial class ConcurrentDictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public ConcurrentDictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Concurrent.ConcurrentDictionary DeepCopy(System.Collections.Concurrent.ConcurrentDictionary input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Concurrent.ConcurrentDictionary input, System.Collections.Concurrent.ConcurrentDictionary output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct ConcurrentDictionarySurrogate { [Id(0)] public System.Collections.Generic.Dictionary Values; } [RegisterSerializer] public sealed partial class ConcurrentQueueCodec : GeneralizedReferenceTypeSurrogateCodec, ConcurrentQueueSurrogate> { public ConcurrentQueueCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Concurrent.ConcurrentQueue ConvertFromSurrogate(ref ConcurrentQueueSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Concurrent.ConcurrentQueue value, ref ConcurrentQueueSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ConcurrentQueueCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public ConcurrentQueueCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.Concurrent.ConcurrentQueue DeepCopy(System.Collections.Concurrent.ConcurrentQueue input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Concurrent.ConcurrentQueue input, System.Collections.Concurrent.ConcurrentQueue output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct ConcurrentQueueSurrogate { [Id(0)] public System.Collections.Generic.Queue Values; } public static partial class ConsumeFieldExtension { public static void ConsumeUnknownField(this ref Buffers.Reader reader, WireProtocol.Field field) { } public static void ConsumeUnknownField(this ref Buffers.Reader reader, scoped ref WireProtocol.Field field) { } } [RegisterSerializer] public sealed partial class CultureInfoCodec : GeneralizedReferenceTypeSurrogateCodec { public CultureInfoCodec(Serializers.IValueSerializer surrogateSerializer) : base(default!) { } public override System.Globalization.CultureInfo ConvertFromSurrogate(ref CultureInfoSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Globalization.CultureInfo value, ref CultureInfoSurrogate surrogate) { } } [GenerateSerializer] public partial struct CultureInfoSurrogate { [Id(0)] public string Name; } [RegisterSerializer] public sealed partial class DateOnlyCodec : IFieldCodec, IFieldCodec { System.DateOnly IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.DateOnly value) { } public static System.DateOnly ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.DateOnly value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class DateTimeCodec : IFieldCodec, IFieldCodec { System.DateTime IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.DateTime value) { } public static System.DateTime ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.DateTime value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class DateTimeOffsetCodec : IFieldCodec, IFieldCodec { System.DateTimeOffset IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.DateTimeOffset value) { } public static System.DateTimeOffset ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.DateTimeOffset value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class DecimalCodec : IFieldCodec, IFieldCodec { decimal IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, decimal value) { } public static decimal ReadDecimalRaw(ref Buffers.Reader reader) { throw null; } public static decimal ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, decimal value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class DictionaryBaseCodec : Serializers.IBaseCodec>, Serializers.IBaseCodec { public DictionaryBaseCodec(IFieldCodec keyCodec, IFieldCodec valueCodec, IFieldCodec> comparerCodec) { } void Serializers.IBaseCodec>.Deserialize(ref Buffers.Reader reader, System.Collections.Generic.Dictionary value) { } void Serializers.IBaseCodec>.Serialize(ref Buffers.Writer writer, System.Collections.Generic.Dictionary value) { } } [RegisterSerializer] public sealed partial class DictionaryCodec : IFieldCodec>, IFieldCodec { public DictionaryCodec(IFieldCodec keyCodec, IFieldCodec valueCodec, IFieldCodec> comparerCodec) { } public System.Collections.Generic.Dictionary ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.Dictionary value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class DictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public DictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.Dictionary DeepCopy(System.Collections.Generic.Dictionary input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.Dictionary input, System.Collections.Generic.Dictionary output, Cloning.CopyContext context) { } } [RegisterSerializer] public sealed partial class DoubleCodec : IFieldCodec, IFieldCodec { double IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, double value) { } public static double ReadDoubleRaw(ref Buffers.Reader reader) { throw null; } public static double ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, double value) where TBufferWriter : System.Buffers.IBufferWriter { } } public abstract partial class Enum32BaseCodec : IFieldCodec, IFieldCodec where T : unmanaged, System.Enum { public T ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, T value) where TBufferWriter : System.Buffers.IBufferWriter { } } public static partial class FieldHeaderCodec { public static void ReadFieldHeader(this ref Buffers.Reader reader, scoped ref WireProtocol.Field field) { } public static WireProtocol.Field ReadFieldHeader(this ref Buffers.Reader reader) { throw null; } public static (WireProtocol.Field Field, string Type) ReadFieldHeaderForAnalysis(this ref Buffers.Reader reader) { throw null; } } [RegisterSerializer] public sealed partial class FloatCodec : IFieldCodec, IFieldCodec { float IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, float value) { } public static float ReadFloatRaw(ref Buffers.Reader reader) { throw null; } public static float ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, float value) where TBufferWriter : System.Buffers.IBufferWriter { } } public abstract partial class GeneralizedReferenceTypeSurrogateCodec : IFieldCodec, IFieldCodec, IDerivedTypeCodec where TField : class where TSurrogate : struct { protected GeneralizedReferenceTypeSurrogateCodec(Serializers.IValueSerializer surrogateSerializer) { } public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } public abstract partial class GeneralizedValueTypeSurrogateCodec : IFieldCodec, IFieldCodec where TField : struct where TSurrogate : struct { protected GeneralizedValueTypeSurrogateCodec(Serializers.IValueSerializer surrogateSerializer) { } public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class GuidCodec : IFieldCodec, IFieldCodec { System.Guid IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Guid value) { } public static System.Guid ReadRaw(ref Buffers.Reader reader) { throw null; } public static System.Guid ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Guid value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteRaw(ref Buffers.Writer writer, System.Guid value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class HalfCodec : IFieldCodec, IFieldCodec { System.Half IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Half value) { } public static System.Half ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Half value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class HashSetCodec : IFieldCodec>, IFieldCodec, Serializers.IBaseCodec>, Serializers.IBaseCodec { public HashSetCodec(IFieldCodec fieldCodec, IFieldCodec> comparerCodec) { } void Serializers.IBaseCodec>.Deserialize(ref Buffers.Reader reader, System.Collections.Generic.HashSet value) { } public System.Collections.Generic.HashSet ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, System.Collections.Generic.HashSet value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.HashSet value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class HashSetCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public HashSetCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.HashSet DeepCopy(System.Collections.Generic.HashSet input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.HashSet input, System.Collections.Generic.HashSet output, Cloning.CopyContext context) { } } public partial interface IDerivedTypeCodec : IFieldCodec { } public partial interface IFieldCodec { object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field); void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter; } public partial interface IFieldCodec : IFieldCodec { object IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field); void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value); T ReadValue(ref Buffers.Reader reader, WireProtocol.Field field); void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, T value) where TBufferWriter : System.Buffers.IBufferWriter; } [RegisterSerializer] public sealed partial class ImmutableArrayCodec : GeneralizedValueTypeSurrogateCodec, ImmutableArraySurrogate> { public ImmutableArrayCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableArray ConvertFromSurrogate(ref ImmutableArraySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableArray value, ref ImmutableArraySurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableArrayCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableArrayCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableArray DeepCopy(System.Collections.Immutable.ImmutableArray input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct ImmutableArraySurrogate { [Id(0)] public T[] Values; } [RegisterSerializer] public sealed partial class ImmutableDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableDictionarySurrogate> { public ImmutableDictionaryCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableDictionary ConvertFromSurrogate(ref ImmutableDictionarySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableDictionary value, ref ImmutableDictionarySurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableDictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableDictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Immutable.ImmutableDictionary DeepCopy(System.Collections.Immutable.ImmutableDictionary input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableDictionarySurrogate { [Id(0)] public System.Collections.Generic.Dictionary Values; } [RegisterSerializer] public sealed partial class ImmutableHashSetCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableHashSetSurrogate> { public ImmutableHashSetCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableHashSet ConvertFromSurrogate(ref ImmutableHashSetSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableHashSet value, ref ImmutableHashSetSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableHashSetCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableHashSetCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableHashSet DeepCopy(System.Collections.Immutable.ImmutableHashSet input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableHashSetSurrogate { [Id(1)] public System.Collections.Generic.IEqualityComparer KeyComparer; [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class ImmutableListCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableListSurrogate> { public ImmutableListCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableList ConvertFromSurrogate(ref ImmutableListSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableList value, ref ImmutableListSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableListCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableListCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableList DeepCopy(System.Collections.Immutable.ImmutableList input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableListSurrogate { [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class ImmutableQueueCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableQueueSurrogate> { public ImmutableQueueCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableQueue ConvertFromSurrogate(ref ImmutableQueueSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableQueue value, ref ImmutableQueueSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableQueueCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableQueueCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableQueue DeepCopy(System.Collections.Immutable.ImmutableQueue input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableQueueSurrogate { [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class ImmutableSortedDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableSortedDictionarySurrogate> { public ImmutableSortedDictionaryCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableSortedDictionary ConvertFromSurrogate(ref ImmutableSortedDictionarySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableSortedDictionary value, ref ImmutableSortedDictionarySurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableSortedDictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableSortedDictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Immutable.ImmutableSortedDictionary DeepCopy(System.Collections.Immutable.ImmutableSortedDictionary input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableSortedDictionarySurrogate { [Id(1)] public System.Collections.Generic.IComparer KeyComparer; [Id(2)] public System.Collections.Generic.IEqualityComparer ValueComparer; [Id(0)] public System.Collections.Generic.List> Values; } [RegisterSerializer] public sealed partial class ImmutableSortedSetCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableSortedSetSurrogate> { public ImmutableSortedSetCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableSortedSet ConvertFromSurrogate(ref ImmutableSortedSetSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableSortedSet value, ref ImmutableSortedSetSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableSortedSetCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableSortedSetCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableSortedSet DeepCopy(System.Collections.Immutable.ImmutableSortedSet input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableSortedSetSurrogate { [Id(1)] public System.Collections.Generic.IComparer KeyComparer; [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class ImmutableStackCodec : GeneralizedReferenceTypeSurrogateCodec, ImmutableStackSurrogate> { public ImmutableStackCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Immutable.ImmutableStack ConvertFromSurrogate(ref ImmutableStackSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Immutable.ImmutableStack value, ref ImmutableStackSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ImmutableStackCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ImmutableStackCopier(Cloning.IDeepCopier copier) { } public System.Collections.Immutable.ImmutableStack DeepCopy(System.Collections.Immutable.ImmutableStack input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [GenerateSerializer] public partial struct ImmutableStackSurrogate { [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class Int128Codec : IFieldCodec, IFieldCodec { System.Int128 IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Int128 value) { } public static System.Int128 ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Int128 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class Int16Codec : IFieldCodec, IFieldCodec { short IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, short value) { } public static short ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, short value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, short value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class Int32Codec : IFieldCodec, IFieldCodec { int IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, int value) { } public static int ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, int value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, int value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class Int64Codec : IFieldCodec, IFieldCodec { long IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, long value) { } public static long ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, long value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, long value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class IPAddressCodec : IFieldCodec, IFieldCodec, IDerivedTypeCodec { System.Net.IPAddress IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Net.IPAddress value) { } public static System.Net.IPAddress ReadRaw(ref Buffers.Reader reader) { throw null; } public static System.Net.IPAddress ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Net.IPAddress value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteRaw(ref Buffers.Writer writer, System.Net.IPAddress value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class IPEndPointCodec : IFieldCodec, IFieldCodec, IDerivedTypeCodec { System.Net.IPEndPoint IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Net.IPEndPoint value) { } public static System.Net.IPEndPoint ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Net.IPEndPoint value) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial interface ISerializationCallbacks { void OnCopied(T original, T result); void OnCopying(T original, T result); void OnDeserialized(T value); void OnDeserializing(T value); void OnSerialized(T value); void OnSerializing(T value); } [RegisterSerializer] public sealed partial class KeyValuePairCodec : IFieldCodec>, IFieldCodec { public KeyValuePairCodec(IFieldCodec keyCodec, IFieldCodec valueCodec) { } public System.Collections.Generic.KeyValuePair ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.KeyValuePair value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class KeyValuePairCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public KeyValuePairCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.KeyValuePair DeepCopy(System.Collections.Generic.KeyValuePair input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class ListCodec : IFieldCodec>, IFieldCodec, Serializers.IBaseCodec>, Serializers.IBaseCodec { public ListCodec(IFieldCodec fieldCodec) { } public void Deserialize(ref Buffers.Reader reader, System.Collections.Generic.List value) { } public System.Collections.Generic.List ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, System.Collections.Generic.List value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.List value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ListCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public ListCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.List DeepCopy(System.Collections.Generic.List input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.List input, System.Collections.Generic.List output, Cloning.CopyContext context) { } } [RegisterSerializer] public sealed partial class MemoryCodec : IFieldCodec>, IFieldCodec { public MemoryCodec(IFieldCodec fieldCodec) { } public System.Memory ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Memory value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class MemoryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public MemoryCopier(Cloning.IDeepCopier elementCopier) { } public System.Memory DeepCopy(System.Memory input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class MemoryOfByteCodec : IFieldCodec>, IFieldCodec { System.Memory IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Memory value) { } public static System.Memory ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Memory value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class MemoryOfByteCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public static System.Memory DeepCopy(System.Memory input, Cloning.CopyContext copyContext) { throw null; } System.Memory Cloning.IDeepCopier>.DeepCopy(System.Memory input, Cloning.CopyContext _) { throw null; } } [RegisterSerializer] public sealed partial class NameValueCollectionCodec : GeneralizedReferenceTypeSurrogateCodec { public NameValueCollectionCodec(Serializers.IValueSerializer surrogateSerializer) : base(default!) { } public override System.Collections.Specialized.NameValueCollection ConvertFromSurrogate(ref NameValueCollectionSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Specialized.NameValueCollection value, ref NameValueCollectionSurrogate surrogate) { } } [RegisterCopier] public sealed partial class NameValueCollectionCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public System.Collections.Specialized.NameValueCollection DeepCopy(System.Collections.Specialized.NameValueCollection input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct NameValueCollectionSurrogate { [Id(0)] public System.Collections.Generic.Dictionary Values; } [RegisterSerializer] public sealed partial class NullableCodec : IFieldCodec, IFieldCodec where T : struct { public NullableCodec(IFieldCodec fieldCodec) { } public T? ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, T? value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class NullableCopier : Cloning.IDeepCopier, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier where T : struct { public NullableCopier(Cloning.IDeepCopier copier) { } public T? DeepCopy(T? input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class ObjectCodec : IFieldCodec, IFieldCodec { object IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } public static object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, object value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ObjectCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public static object DeepCopy(object input, Cloning.CopyContext context) { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class PooledBufferCodec : IFieldCodec, IFieldCodec { public Buffers.PooledBuffer ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Buffers.PooledBuffer value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class PooledBufferCopier : Cloning.IDeepCopier, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public Buffers.PooledBuffer DeepCopy(Buffers.PooledBuffer input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterSerializer] public sealed partial class QueueCodec : IFieldCodec>, IFieldCodec { public QueueCodec(IFieldCodec fieldCodec) { } public System.Collections.Generic.Queue ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.Queue value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class QueueCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public QueueCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.Queue DeepCopy(System.Collections.Generic.Queue input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.Queue input, System.Collections.Generic.Queue output, Cloning.CopyContext context) { } } [RegisterSerializer] public sealed partial class ReadOnlyCollectionCodec : GeneralizedReferenceTypeSurrogateCodec, ReadOnlyCollectionSurrogate> { public ReadOnlyCollectionCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.ObjectModel.ReadOnlyCollection ConvertFromSurrogate(ref ReadOnlyCollectionSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.ObjectModel.ReadOnlyCollection value, ref ReadOnlyCollectionSurrogate surrogate) { } } [RegisterCopier] public sealed partial class ReadOnlyCollectionCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public ReadOnlyCollectionCopier(Cloning.IDeepCopier elementCopier) { } public System.Collections.ObjectModel.ReadOnlyCollection DeepCopy(System.Collections.ObjectModel.ReadOnlyCollection input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct ReadOnlyCollectionSurrogate { [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class ReadOnlyDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, ReadOnlyDictionarySurrogate> { public ReadOnlyDictionaryCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.ObjectModel.ReadOnlyDictionary ConvertFromSurrogate(ref ReadOnlyDictionarySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.ObjectModel.ReadOnlyDictionary value, ref ReadOnlyDictionarySurrogate surrogate) { } } [RegisterCopier] public sealed partial class ReadOnlyDictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public ReadOnlyDictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.ObjectModel.ReadOnlyDictionary DeepCopy(System.Collections.ObjectModel.ReadOnlyDictionary input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct ReadOnlyDictionarySurrogate { [Id(0)] public System.Collections.Generic.Dictionary Values; } [RegisterSerializer] public sealed partial class ReadOnlyMemoryCodec : IFieldCodec>, IFieldCodec { public ReadOnlyMemoryCodec(IFieldCodec fieldCodec) { } public System.ReadOnlyMemory ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ReadOnlyMemory value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ReadOnlyMemoryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public ReadOnlyMemoryCopier(Cloning.IDeepCopier elementCopier) { } public System.ReadOnlyMemory DeepCopy(System.ReadOnlyMemory input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class ReadOnlyMemoryOfByteCodec : IFieldCodec>, IFieldCodec { System.ReadOnlyMemory IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ReadOnlyMemory value) { } public static byte[] ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.ReadOnlyMemory value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ReadOnlyMemoryOfByteCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public static System.ReadOnlyMemory DeepCopy(System.ReadOnlyMemory input, Cloning.CopyContext copyContext) { throw null; } System.ReadOnlyMemory Cloning.IDeepCopier>.DeepCopy(System.ReadOnlyMemory input, Cloning.CopyContext _) { throw null; } } public static partial class ReferenceCodec { public static uint CreateRecordPlaceholder(Session.SerializerSession session) { throw null; } public static void MarkValueField(Session.SerializerSession session) { } public static object ReadReference(ref Buffers.Reader reader, System.Type fieldType) { throw null; } public static T ReadReference(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void RecordObject(Session.SerializerSession session, object value, uint referenceId) { } public static void RecordObject(Session.SerializerSession session, object value) { } public static bool TryWriteReferenceField(ref Buffers.Writer writer, uint fieldId, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { throw null; } public static bool TryWriteReferenceField(ref Buffers.Writer writer, uint fieldId, System.Type expectedType, System.Type actualType, object value) where TBufferWriter : System.Buffers.IBufferWriter { throw null; } public static void WriteNullReference(ref Buffers.Writer writer, uint fieldId) where TBufferWriter : System.Buffers.IBufferWriter { } } public abstract partial class ReferenceTypeSurrogateCodec : IFieldCodec, IFieldCodec where TSurrogate : struct { protected ReferenceTypeSurrogateCodec(Serializers.IValueSerializer surrogateSerializer) { } public abstract TField ConvertFromSurrogate(ref TSurrogate surrogate); public abstract void ConvertToSurrogate(TField value, ref TSurrogate surrogate); public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class SByteCodec : IFieldCodec, IFieldCodec { sbyte IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, sbyte value) { } public static sbyte ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, sbyte value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, sbyte value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial class SkipFieldCodec : IFieldCodec { public object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } public static partial class SkipFieldExtension { public static void SkipField(this ref Buffers.Reader reader, WireProtocol.Field field) { } } [RegisterSerializer] public sealed partial class SortedDictionaryCodec : GeneralizedReferenceTypeSurrogateCodec, SortedDictionarySurrogate> { public SortedDictionaryCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Generic.SortedDictionary ConvertFromSurrogate(ref SortedDictionarySurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Generic.SortedDictionary value, ref SortedDictionarySurrogate surrogate) { } } [RegisterCopier] public sealed partial class SortedDictionaryCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public SortedDictionaryCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.SortedDictionary DeepCopy(System.Collections.Generic.SortedDictionary input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.SortedDictionary input, System.Collections.Generic.SortedDictionary output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct SortedDictionarySurrogate { [Id(1)] public System.Collections.Generic.IComparer Comparer; [Id(0)] public System.Collections.Generic.List> Values; } [RegisterSerializer] public sealed partial class SortedListCodec : GeneralizedReferenceTypeSurrogateCodec, SortedListSurrogate> { public SortedListCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Generic.SortedList ConvertFromSurrogate(ref SortedListSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Generic.SortedList value, ref SortedListSurrogate surrogate) { } } [RegisterCopier] public sealed partial class SortedListCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public SortedListCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.SortedList DeepCopy(System.Collections.Generic.SortedList input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.SortedList input, System.Collections.Generic.SortedList output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct SortedListSurrogate { [Id(1)] public System.Collections.Generic.IComparer Comparer; [Id(0)] public System.Collections.Generic.List> Values; } [RegisterSerializer] public sealed partial class SortedSetCodec : GeneralizedReferenceTypeSurrogateCodec, SortedSetSurrogate> { public SortedSetCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override System.Collections.Generic.SortedSet ConvertFromSurrogate(ref SortedSetSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Collections.Generic.SortedSet value, ref SortedSetSurrogate surrogate) { } } [RegisterCopier] public sealed partial class SortedSetCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public SortedSetCopier(Cloning.IDeepCopier elementCopier) { } public System.Collections.Generic.SortedSet DeepCopy(System.Collections.Generic.SortedSet input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.SortedSet input, System.Collections.Generic.SortedSet output, Cloning.CopyContext context) { } } [GenerateSerializer] public partial struct SortedSetSurrogate { [Id(1)] public System.Collections.Generic.IComparer Comparer; [Id(0)] public System.Collections.Generic.List Values; } [RegisterSerializer] public sealed partial class StackCodec : IFieldCodec>, IFieldCodec { public StackCodec(IFieldCodec fieldCodec) { } public System.Collections.Generic.Stack ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Collections.Generic.Stack value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class StackCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public StackCopier(Cloning.IDeepCopier valueCopier) { } public System.Collections.Generic.Stack DeepCopy(System.Collections.Generic.Stack input, Cloning.CopyContext context) { throw null; } public void DeepCopy(System.Collections.Generic.Stack input, System.Collections.Generic.Stack output, Cloning.CopyContext context) { } } [RegisterSerializer] public sealed partial class StringCodec : IFieldCodec, IFieldCodec { string IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, string value) { } public static string ReadRaw(ref Buffers.Reader reader, uint numBytes) { throw null; } public static string ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, string value) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteRaw(ref Buffers.Writer writer, string value, int numBytes) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TimeOnlyCodec : IFieldCodec, IFieldCodec { System.TimeOnly IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.TimeOnly value) { } public static System.TimeOnly ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.TimeOnly value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TimeSpanCodec : IFieldCodec, IFieldCodec { System.TimeSpan IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.TimeSpan value) { } public static System.TimeSpan ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.TimeSpan value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec valueCodec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class TupleCodec : IFieldCodec>, IFieldCodec { public TupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec, IFieldCodec item8Codec) { } public System.Tuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Tuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6, Cloning.IDeepCopier copier7) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterCopier] public sealed partial class TupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public TupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6, Cloning.IDeepCopier copier7, Cloning.IDeepCopier copier8) { } public System.Tuple DeepCopy(System.Tuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } } [RegisterSerializer] public sealed partial class TypeSerializerCodec : IFieldCodec, IFieldCodec, IDerivedTypeCodec { System.Type IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Type value) { } public static System.Type ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class UInt128Codec : IFieldCodec, IFieldCodec { System.UInt128 IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.UInt128 value) { } public static System.UInt128 ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.UInt128 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class UInt16Codec : IFieldCodec, IFieldCodec { ushort IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, ushort value) { } public static ushort ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, ushort value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, ushort value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class UInt32Codec : IFieldCodec, IFieldCodec { uint IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, uint value) { } public static uint ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, uint value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, uint value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class UInt64Codec : IFieldCodec, IFieldCodec { ulong IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, ulong value) { } public static ulong ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, ulong value, System.Type actualType) where TBufferWriter : System.Buffers.IBufferWriter { } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, ulong value) where TBufferWriter : System.Buffers.IBufferWriter { } } public sealed partial class UnknownFieldMarker { public UnknownFieldMarker(WireProtocol.Field field, long position) { } public WireProtocol.Field Field { get { throw null; } } public long Position { get { throw null; } } public override string ToString() { throw null; } } [RegisterSerializer] public sealed partial class UriCodec : IFieldCodec, IFieldCodec, IDerivedTypeCodec { System.Uri IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.Uri value) { } public static System.Uri ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public static void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Uri value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec, IFieldCodec { public System.ValueTuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ValueTuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec>, IFieldCodec { public ValueTupleCodec(IFieldCodec valueCodec) { } public System.ValueTuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ValueTuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec) { } public (T1, T2) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2, T3)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec) { } public (T1, T2, T3) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2, T3) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2, T3, T4)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec) { } public (T1, T2, T3, T4) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2, T3, T4) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2, T3, T4, T5)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec) { } public (T1, T2, T3, T4, T5) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2, T3, T4, T5) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2, T3, T4, T5, T6)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec) { } public (T1, T2, T3, T4, T5, T6) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2, T3, T4, T5, T6) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec<(T1, T2, T3, T4, T5, T6, T7)>, IFieldCodec { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec) { } public (T1, T2, T3, T4, T5, T6, T7) ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, (T1, T2, T3, T4, T5, T6, T7) value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterSerializer] public sealed partial class ValueTupleCodec : IFieldCodec>, IFieldCodec where T8 : struct { public ValueTupleCodec(IFieldCodec item1Codec, IFieldCodec item2Codec, IFieldCodec item3Codec, IFieldCodec item4Codec, IFieldCodec item5Codec, IFieldCodec item6Codec, IFieldCodec item7Codec, IFieldCodec item8Codec) { } public System.ValueTuple ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, System.ValueTuple value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public System.ValueTuple DeepCopy(System.ValueTuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier) { } public System.ValueTuple DeepCopy(System.ValueTuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2) { } public (T1, T2) DeepCopy((T1, T2) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2, T3)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3) { } public (T1, T2, T3) DeepCopy((T1, T2, T3) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2, T3, T4)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4) { } public (T1, T2, T3, T4) DeepCopy((T1, T2, T3, T4) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2, T3, T4, T5)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5) { } public (T1, T2, T3, T4, T5) DeepCopy((T1, T2, T3, T4, T5) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2, T3, T4, T5, T6)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6) { } public (T1, T2, T3, T4, T5, T6) DeepCopy((T1, T2, T3, T4, T5, T6) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier<(T1, T2, T3, T4, T5, T6, T7)>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6, Cloning.IDeepCopier copier7) { } public (T1, T2, T3, T4, T5, T6, T7) DeepCopy((T1, T2, T3, T4, T5, T6, T7) input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public sealed partial class ValueTupleCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IOptionalDeepCopier where T8 : struct { public ValueTupleCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6, Cloning.IDeepCopier copier7, Cloning.IDeepCopier copier8) { } public System.ValueTuple DeepCopy(System.ValueTuple input, Cloning.CopyContext context) { throw null; } public bool IsShallowCopyable() { throw null; } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class VersionCodec : GeneralizedReferenceTypeSurrogateCodec { public VersionCodec(Serializers.IValueSerializer surrogateSerializer) : base(default!) { } public override System.Version ConvertFromSurrogate(ref VersionSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(System.Version value, ref VersionSurrogate surrogate) { } } [GenerateSerializer] public partial struct VersionSurrogate { [Id(2)] public int Build; [Id(0)] public int Major; [Id(1)] public int Minor; [Id(3)] public int Revision; } [Alias("StringComparer")] public sealed partial class WellKnownStringComparerCodec : Serializers.IGeneralizedCodec, IFieldCodec { public bool IsSupportedType(System.Type type) { throw null; } public object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace Orleans.Serialization.Configuration { public partial interface ITypeManifestProvider : Microsoft.Extensions.Options.IConfigureOptions { } public sealed partial class TypeManifestOptions { public System.Collections.Generic.HashSet Activators { get { throw null; } } public bool AllowAllTypes { get { throw null; } set { } } public System.Collections.Generic.HashSet AllowedTypes { get { throw null; } } public TypeSystem.CompoundTypeAliasTree CompoundTypeAliases { get { throw null; } } public System.Collections.Generic.HashSet Converters { get { throw null; } } public System.Collections.Generic.HashSet Copiers { get { throw null; } } public bool? EnableConfigurationAnalysis { get { throw null; } set { } } public System.Collections.Generic.HashSet FieldCodecs { get { throw null; } } public System.Collections.Generic.HashSet InterfaceImplementations { get { throw null; } } public System.Collections.Generic.HashSet InterfaceProxies { get { throw null; } } public System.Collections.Generic.HashSet Interfaces { get { throw null; } } public System.Collections.Generic.HashSet Serializers { get { throw null; } } public System.Collections.Generic.Dictionary WellKnownTypeAliases { get { throw null; } } public System.Collections.Generic.Dictionary WellKnownTypeIds { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] public sealed partial class TypeManifestProviderAttribute : System.Attribute { public TypeManifestProviderAttribute(System.Type providerType) { } public System.Type ProviderType { get { throw null; } } } public abstract partial class TypeManifestProviderBase : ITypeManifestProvider, Microsoft.Extensions.Options.IConfigureOptions { public virtual object Key { get { throw null; } } protected abstract void ConfigureInner(TypeManifestOptions options); void Microsoft.Extensions.Options.IConfigureOptions.Configure(TypeManifestOptions options) { } } } namespace Orleans.Serialization.GeneratedCodeHelpers { public static partial class OrleansGeneratedCodeHelper { public static void ConsumeEndBaseOrEndObject(this ref Buffers.Reader reader, scoped ref WireProtocol.Field field) { } public static void ConsumeEndBaseOrEndObject(this ref Buffers.Reader reader) { } public static TField DeserializeUnexpectedType(this ref Buffers.Reader reader, scoped ref WireProtocol.Field field) where TField : class { throw null; } public static System.Reflection.MethodInfo GetMethodInfoOrDefault(System.Type interfaceType, string methodName, System.Type[] methodTypeParameters, System.Type[] parameterTypes) { throw null; } public static Cloning.IDeepCopier GetOptionalCopier(Cloning.IDeepCopier copier) { throw null; } public static TService GetService(object caller, Serializers.ICodecProvider codecProvider) { throw null; } public static object InvokableThrowArgumentOutOfRange(int index, int maxArgs) { throw null; } public static void SerializeUnexpectedType(this ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } public static TService UnwrapService(object caller, TService service) { throw null; } public abstract partial class ExceptionCopier : Cloning.IDeepCopier, Cloning.IDeepCopier, Cloning.IBaseCopier, Cloning.IBaseCopier where T : B where B : System.Exception { protected ExceptionCopier(Serializers.ICodecProvider codecProvider) { } public virtual void DeepCopy(T input, T output, Cloning.CopyContext context) { } public T DeepCopy(T original, Cloning.CopyContext context) { throw null; } } } } namespace Orleans.Serialization.Internal { public static partial class ReferencedAssemblyProvider { public static void AddAssembly(System.Collections.Generic.HashSet parts, System.Reflection.Assembly assembly) { } public static void AddFromAssemblyLoadContext(System.Collections.Generic.HashSet parts, System.Reflection.Assembly assembly = null) { } public static void AddFromAssemblyLoadContext(System.Collections.Generic.HashSet parts, System.Runtime.Loader.AssemblyLoadContext context) { } public static void AddFromDependencyContext(System.Collections.Generic.HashSet parts, System.Reflection.Assembly assembly = null) { } public static System.Collections.Generic.IEnumerable GetRelevantAssemblies() { throw null; } } } namespace Orleans.Serialization.Invocation { [GenerateSerializer] [Immutable] [UseActivator] [SuppressReferenceTracking] public sealed partial class CompletedResponse : Response { public override System.Exception? Exception { get { throw null; } set { } } public static CompletedResponse Instance { get { throw null; } } public override object? Result { get { throw null; } set { } } public override void Dispose() { } public override T GetResult() { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class ExceptionResponse : Response { [Id(0)] public override System.Exception? Exception { get { throw null; } set { } } public override object? Result { get { throw null; } set { } } public override void Dispose() { } public override T GetResult() { throw null; } public override string ToString() { throw null; } } public partial interface IInvokable : System.IDisposable { string GetActivityName(); object? GetArgument(int index); int GetArgumentCount(); System.Threading.CancellationToken GetCancellationToken(); System.TimeSpan? GetDefaultResponseTimeout(); string GetInterfaceName(); System.Type GetInterfaceType(); System.Reflection.MethodInfo GetMethod(); string GetMethodName(); object? GetTarget(); System.Threading.Tasks.ValueTask Invoke(); void SetArgument(int index, object value); void SetTarget(ITargetHolder holder); bool TryCancel(); } public static partial class InvokablePool { public static T Get() where T : class, IInvokable, new() { throw null; } public static void Return(T obj) where T : class, IInvokable, new() { } } public partial interface IResponseCompletionSource { void Complete(); void Complete(Response value); } public partial interface ITargetHolder { TComponent? GetComponent() where TComponent : class; TTarget? GetTarget() where TTarget : class; } [SerializerTransparent] public abstract partial class Response : System.IDisposable { public static Response Completed { get { throw null; } } public abstract System.Exception? Exception { get; set; } public abstract object? Result { get; set; } public abstract void Dispose(); public static Response FromException(System.Exception exception) { throw null; } public static Response FromResult(TResult value) { throw null; } public abstract T GetResult(); public virtual System.Type? GetSimpleResultType() { throw null; } public override string ToString() { throw null; } } public abstract partial class ResponseCodec { public abstract object ReadRaw(ref Buffers.Reader reader, scoped ref WireProtocol.Field field); public abstract void WriteRaw(ref Buffers.Writer writer, object value) where TBufferWriter : System.Buffers.IBufferWriter; } public sealed partial class ResponseCompletionSource : IResponseCompletionSource, System.Threading.Tasks.Sources.IValueTaskSource, System.Threading.Tasks.Sources.IValueTaskSource { public System.Threading.Tasks.ValueTask AsValueTask() { throw null; } public System.Threading.Tasks.ValueTask AsVoidValueTask() { throw null; } public void Complete() { } public void Complete(Response value) { } public Response GetResult(short token) { throw null; } public System.Threading.Tasks.Sources.ValueTaskSourceStatus GetStatus(short token) { throw null; } public void OnCompleted(System.Action continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags) { } public void Reset() { } public void SetException(System.Exception exception) { } public void SetResult(Response result) { } void System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token) { } } public static partial class ResponseCompletionSourcePool { public static ResponseCompletionSource Get() { throw null; } public static ResponseCompletionSource Get() { throw null; } public static void Return(ResponseCompletionSource obj) { } public static void Return(ResponseCompletionSource obj) { } } public sealed partial class ResponseCompletionSource : IResponseCompletionSource, System.Threading.Tasks.Sources.IValueTaskSource, System.Threading.Tasks.Sources.IValueTaskSource { public System.Threading.Tasks.ValueTask AsValueTask() { throw null; } public System.Threading.Tasks.ValueTask AsVoidValueTask() { throw null; } public void Complete() { } public void Complete(Response value) { } public void Complete(Response value) { } public TResult GetResult(short token) { throw null; } public System.Threading.Tasks.Sources.ValueTaskSourceStatus GetStatus(short token) { throw null; } public void OnCompleted(System.Action continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags) { } public void Reset() { } public void SetException(System.Exception exception) { } public void SetResult(TResult result) { } void System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token) { } } public static partial class ResponseExtensions { public static void ThrowIfExceptionResponse(this Response response) { } } public static partial class ResponsePool { public static Response Get() { throw null; } public static void Return(Response obj) { } } [UseActivator] [SuppressReferenceTracking] public sealed partial class Response : Response { public override System.Exception? Exception { get { throw null; } set { } } public override object? Result { get { throw null; } set { } } public TResult? TypedResult { get { throw null; } set { } } public override void Dispose() { } public override T GetResult() { throw null; } public override System.Type GetSimpleResultType() { throw null; } public override string ToString() { throw null; } } } namespace Orleans.Serialization.Serializers { public partial class AbstractTypeSerializer : Codecs.IFieldCodec { protected internal AbstractTypeSerializer(System.Type fieldType) { } public object ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial class AbstractTypeSerializer : AbstractTypeSerializer, Codecs.IFieldCodec, Codecs.IFieldCodec, IBaseCodec, IBaseCodec where TField : class { protected AbstractTypeSerializer() : base(default!) { } public virtual void Deserialize(ref Buffers.Reader reader, TField instance) { } public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public virtual void Serialize(ref Buffers.Writer writer, TField instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } public sealed partial class CodecProvider : ICodecProvider, IFieldCodecProvider, IBaseCodecProvider, IValueSerializerProvider, IActivatorProvider, Cloning.IDeepCopierProvider { public CodecProvider(System.IServiceProvider serviceProvider, Microsoft.Extensions.Options.IOptions codecConfiguration) { } public System.IServiceProvider Services { get { throw null; } } public Activators.IActivator GetActivator() { throw null; } public IBaseCodec GetBaseCodec() where TField : class { throw null; } public Cloning.IBaseCopier GetBaseCopier() where TField : class { throw null; } public Codecs.IFieldCodec GetCodec(System.Type fieldType) { throw null; } public Codecs.IFieldCodec GetCodec() { throw null; } public Cloning.IDeepCopier GetDeepCopier(System.Type fieldType) { throw null; } public Cloning.IDeepCopier GetDeepCopier() { throw null; } public IValueSerializer GetValueSerializer() where TField : struct { throw null; } public Codecs.IFieldCodec TryGetCodec(System.Type fieldType) { throw null; } public Codecs.IFieldCodec TryGetCodec() { throw null; } public Cloning.IDeepCopier TryGetDeepCopier(System.Type fieldType) { throw null; } public Cloning.IDeepCopier TryGetDeepCopier() { throw null; } } public sealed partial class ConcreteTypeSerializer : Codecs.IFieldCodec, Codecs.IFieldCodec where TField : class where TBaseCodec : IBaseCodec { public ConcreteTypeSerializer(Activators.IActivator activator, TBaseCodec serializer) { } public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public TField ReadValueSealed(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } public sealed partial class DelegateCodecSelector : ICodecSelector { public string CodecName { get { throw null; } init { } } public System.Func IsSupportedTypeDelegate { get { throw null; } init { } } public bool IsSupportedType(System.Type type) { throw null; } } public sealed partial class DelegateCopierSelector : ICopierSelector { public string CopierName { get { throw null; } init { } } public System.Func IsSupportedTypeDelegate { get { throw null; } init { } } public bool IsSupportedType(System.Type type) { throw null; } } public partial interface IActivatorProvider { Activators.IActivator GetActivator(); } public partial interface IBaseCodec { } public partial interface IBaseCodecProvider { IBaseCodec GetBaseCodec() where TField : class; } public partial interface IBaseCodec : IBaseCodec where T : class { void Deserialize(ref Buffers.Reader reader, T value); void Serialize(ref Buffers.Writer writer, T value) where TBufferWriter : System.Buffers.IBufferWriter; } public partial interface ICodecProvider : IFieldCodecProvider, IBaseCodecProvider, IValueSerializerProvider, IActivatorProvider, Cloning.IDeepCopierProvider { System.IServiceProvider Services { get; } } public partial interface ICodecSelector { string CodecName { get; } bool IsSupportedType(System.Type type); } public partial interface ICopierSelector { string CopierName { get; } bool IsSupportedType(System.Type type); } public partial interface IFieldCodecProvider { Codecs.IFieldCodec GetCodec(System.Type fieldType); Codecs.IFieldCodec GetCodec(); Codecs.IFieldCodec TryGetCodec(System.Type fieldType); Codecs.IFieldCodec TryGetCodec(); } public partial interface IGeneralizedBaseCodec : IBaseCodec, IBaseCodec { bool IsSupportedType(System.Type type); } public partial interface IGeneralizedCodec : Codecs.IFieldCodec { bool IsSupportedType(System.Type type); } public partial interface ISpecializableBaseCodec { IBaseCodec GetSpecializedCodec(System.Type type); bool IsSupportedType(System.Type type); } public partial interface ISpecializableCodec { Codecs.IFieldCodec GetSpecializedCodec(System.Type type); bool IsSupportedType(System.Type type); } public partial interface IValueSerializer { } public partial interface IValueSerializerProvider { IValueSerializer GetValueSerializer() where TField : struct; } public partial interface IValueSerializer : IValueSerializer where T : struct { void Deserialize(ref Buffers.Reader reader, scoped ref T value); void Serialize(ref Buffers.Writer writer, scoped ref T value) where TBufferWriter : System.Buffers.IBufferWriter; } public sealed partial class SurrogateCodec : Codecs.IFieldCodec, Codecs.IFieldCodec, Cloning.IDeepCopier, Cloning.IDeepCopier, IBaseCodec, IBaseCodec, Cloning.IBaseCopier, Cloning.IBaseCopier where TField : class where TSurrogate : struct where TConverter : IConverter { public SurrogateCodec(IValueSerializer surrogateSerializer, Cloning.IDeepCopier surrogateCopier, TConverter converter) { } public void DeepCopy(TField input, TField output, Cloning.CopyContext context) { } public TField DeepCopy(TField input, Cloning.CopyContext context) { throw null; } public void Deserialize(ref Buffers.Reader reader, TField value) { } public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } public sealed partial class ValueSerializer : Codecs.IFieldCodec, Codecs.IFieldCodec where TField : struct where TValueSerializer : IValueSerializer { public ValueSerializer(TValueSerializer serializer) { } public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } public sealed partial class ValueTypeSurrogateCodec : Codecs.IFieldCodec, Codecs.IFieldCodec, Cloning.IDeepCopier, Cloning.IDeepCopier, IValueSerializer, IValueSerializer where TField : struct where TSurrogate : struct where TConverter : IConverter { public ValueTypeSurrogateCodec(IValueSerializer surrogateSerializer, Cloning.IDeepCopier surrogateCopier, TConverter converter) { } public TField DeepCopy(TField input, Cloning.CopyContext context) { throw null; } public void Deserialize(ref Buffers.Reader reader, scoped ref TField value) { } public TField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void Serialize(ref Buffers.Writer writer, scoped ref TField value) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, TField value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace Orleans.Serialization.Session { public sealed partial class ReferencedObjectCollection { public uint CurrentReferenceId { get { throw null; } set { } } public System.Collections.Generic.Dictionary CopyIdTable() { throw null; } public System.Collections.Generic.Dictionary CopyReferenceTable() { throw null; } public bool GetOrAddReference(object value, out uint reference) { throw null; } public int GetReferenceIndex(object value) { throw null; } public void MarkValueField() { } public void RecordReferenceField(object value, uint referenceId) { } public void RecordReferenceField(object value) { } public void Reset() { } public object TryGetReferencedObject(uint reference) { throw null; } } public sealed partial class ReferencedTypeCollection { public uint GetOrAddTypeReference(System.Type type) { throw null; } public System.Type GetReferencedType(uint reference) { throw null; } public void RecordReferencedType(System.Type type) { } public void Reset() { } public bool TryGetReferencedType(uint reference, out System.Type type) { throw null; } public bool TryGetTypeReference(System.Type type, out uint reference) { throw null; } } public sealed partial class SerializerSession : System.IDisposable { public SerializerSession(TypeSystem.TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes, Serializers.CodecProvider codecProvider) { } public Serializers.CodecProvider CodecProvider { get { throw null; } } public ReferencedObjectCollection ReferencedObjects { get { throw null; } } public ReferencedTypeCollection ReferencedTypes { get { throw null; } } public TypeSystem.TypeCodec TypeCodec { get { throw null; } } public WellKnownTypeCollection WellKnownTypes { get { throw null; } } public void Dispose() { } public void PartialReset() { } public void Reset() { } } public sealed partial class SerializerSessionPool { public SerializerSessionPool(TypeSystem.TypeCodec typeCodec, WellKnownTypeCollection wellKnownTypes, Serializers.CodecProvider codecProvider) { } public Serializers.CodecProvider CodecProvider { get { throw null; } } public SerializerSession GetSession() { throw null; } } public sealed partial class WellKnownTypeCollection { public WellKnownTypeCollection(Microsoft.Extensions.Options.IOptions config) { } public System.Type GetWellKnownType(uint typeId) { throw null; } public bool TryGetWellKnownType(uint typeId, out System.Type type) { throw null; } public bool TryGetWellKnownTypeId(System.Type type, out uint typeId) { throw null; } } } namespace Orleans.Serialization.TypeSystem { public partial class ArrayTypeSpec : TypeSpec { public ArrayTypeSpec(TypeSpec elementType, int dimensions) { } public int Dimensions { get { throw null; } } public TypeSpec ElementType { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public partial class AssemblyQualifiedTypeSpec : TypeSpec { public AssemblyQualifiedTypeSpec(TypeSpec type, string? assembly) { } public string? Assembly { get { throw null; } } public TypeSpec Type { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public sealed partial class CachedTypeResolver : TypeResolver { public static string GetName(System.Reflection.Assembly assembly) { throw null; } public override System.Type ResolveType(string name) { throw null; } public override bool TryResolveType(string name, out System.Type type) { throw null; } } public partial class CompoundTypeAliasTree { internal CompoundTypeAliasTree() { } public object? Key { get { throw null; } } public System.Type? Value { get { throw null; } } public CompoundTypeAliasTree Add(string key, System.Type value) { throw null; } public CompoundTypeAliasTree Add(string key) { throw null; } public CompoundTypeAliasTree Add(System.Type key, System.Type value) { throw null; } public CompoundTypeAliasTree Add(System.Type key) { throw null; } public static CompoundTypeAliasTree Create() { throw null; } } public partial class ConstructedGenericTypeSpec : TypeSpec { public ConstructedGenericTypeSpec(TypeSpec unconstructedType, int arity, TypeSpec[] arguments) { } public TypeSpec[] Arguments { get { throw null; } } public TypeSpec UnconstructedType { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public sealed partial class DefaultTypeFilter : ITypeNameFilter { public bool? IsTypeNameAllowed(string typeName, string assemblyName) { throw null; } } public partial class LiteralTypeSpec : TypeSpec { public LiteralTypeSpec(string value) { } public string Value { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public partial class NamedTypeSpec : TypeSpec { public NamedTypeSpec(NamedTypeSpec containingType, string name, int arity) { } public int Arity { get { throw null; } } public NamedTypeSpec? ContainingType { get { throw null; } } public string Name { get { throw null; } } public override string Format() { throw null; } public string GetNamespaceQualifiedName() { throw null; } public override string ToString() { throw null; } } public partial class PointerTypeSpec : TypeSpec { public PointerTypeSpec(TypeSpec elementType) { } public TypeSpec ElementType { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public readonly partial struct QualifiedType { private readonly object _dummy; private readonly int _dummyPrimitive; public QualifiedType(string? assembly, string type) { } public string? Assembly { get { throw null; } } public static QualifiedTypeEqualityComparer EqualityComparer { get { throw null; } } public string Type { get { throw null; } } public readonly void Deconstruct(out string? assembly, out string type) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public static bool operator ==(QualifiedType left, QualifiedType right) { throw null; } public static implicit operator QualifiedType((string Assembly, string Type) args) { throw null; } public static bool operator !=(QualifiedType left, QualifiedType right) { throw null; } public sealed partial class QualifiedTypeEqualityComparer : System.Collections.Generic.IEqualityComparer { public bool Equals(QualifiedType x, QualifiedType y) { throw null; } public int GetHashCode(QualifiedType obj) { throw null; } } } public partial class ReferenceTypeSpec : TypeSpec { public ReferenceTypeSpec(TypeSpec elementType) { } public TypeSpec ElementType { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public static partial class RuntimeTypeNameFormatter { public static string Format(System.Type type) { throw null; } } public static partial class RuntimeTypeNameParser { public static TypeSpec Parse(System.ReadOnlySpan input) { throw null; } public static TypeSpec Parse(string input) { throw null; } } public partial class TupleTypeSpec : TypeSpec { public TupleTypeSpec(TypeSpec[] elements, int arity) { } public int Arity { get { throw null; } } public TypeSpec[] Elements { get { throw null; } } public override string Format() { throw null; } public override string ToString() { throw null; } } public sealed partial class TypeCodec { public TypeCodec(TypeConverter typeConverter) { } public System.Type ReadLengthPrefixed(ref Buffers.Reader reader) { throw null; } public System.Type TryRead(ref Buffers.Reader reader) { throw null; } public bool TryReadForAnalysis(ref Buffers.Reader reader, out System.Type type, out string typeString) { throw null; } public void WriteEncodedType(ref Buffers.Writer writer, System.Type type) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteLengthPrefixed(ref Buffers.Writer writer, System.Type type) where TBufferWriter : System.Buffers.IBufferWriter { } } public partial class TypeConverter { public TypeConverter(System.Collections.Generic.IEnumerable formatters, System.Collections.Generic.IEnumerable typeNameFilters, System.Collections.Generic.IEnumerable typeFilters, Microsoft.Extensions.Options.IOptions options, TypeResolver typeResolver) { } public string Format(System.Type type, bool allowAllTypes = false) { throw null; } public string Format(System.Type type, System.Func rewriter, bool allowAllTypes = false) { throw null; } public System.Type Parse(string formatted) { throw null; } public bool TryParse(string formatted, out System.Type result) { throw null; } } public abstract partial class TypeResolver { public abstract System.Type ResolveType(string name); public abstract bool TryResolveType(string name, out System.Type type); } public abstract partial class TypeSpec { public abstract string Format(); } } namespace Orleans.Serialization.Utilities { public static partial class BitStreamFormatter { public static string Format(Buffers.PooledBuffer.BufferSlice slice, Session.SerializerSession session) { throw null; } public static string Format(System.Buffers.ReadOnlySequence input, Session.SerializerSession session) { throw null; } public static string Format(byte[] array, Session.SerializerSession session) { throw null; } public static string Format(System.IO.Stream input, Session.SerializerSession session) { throw null; } public static string Format(System.ReadOnlyMemory input, Session.SerializerSession session) { throw null; } public static string Format(System.ReadOnlySpan input, Session.SerializerSession session) { throw null; } public static void Format(ref Buffers.Reader reader, System.Text.StringBuilder result) { } public static string Format(ref Buffers.Reader reader) { throw null; } } public static partial class FieldAccessor { public static System.Delegate GetGetter(System.Type declaringType, string fieldName) { throw null; } public static System.Delegate GetReferenceSetter(System.Type declaringType, string fieldName) { throw null; } public static System.Delegate GetValueGetter(System.Type declaringType, string fieldName) { throw null; } public static System.Delegate GetValueSetter(System.Type declaringType, string fieldName) { throw null; } } public delegate TField ValueTypeGetter(ref TDeclaring instance) where TDeclaring : struct; public delegate void ValueTypeSetter(ref TDeclaring instance, TField value) where TDeclaring : struct; } namespace Orleans.Serialization.Utilities.Internal { public static partial class InternalServiceCollectionExtensions { public static void AddFromExisting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type service, System.Type implementation) { } public static void AddFromExisting(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TImplementation : TService { } } } namespace Orleans.Serialization.WireProtocol { public enum ExtendedWireType : uint { EndTagDelimited = 0U, EndBaseFields = 8U } public partial struct Field { public uint FieldIdDeltaRaw; public System.Type FieldTypeRaw; public Tag Tag; public Field(Tag tag, uint extendedFieldIdDelta, System.Type type) { } public Field(Tag tag) { } public ExtendedWireType ExtendedWireType { get { throw null; } set { } } public uint FieldIdDelta { get { throw null; } set { } } public System.Type FieldType { get { throw null; } set { } } public bool HasExtendedFieldId { get { throw null; } } public bool HasExtendedSchemaType { get { throw null; } } public bool HasFieldId { get { throw null; } } public bool IsEndBaseFields { get { throw null; } } public bool IsEndBaseOrEndObject { get { throw null; } } public bool IsEndObject { get { throw null; } } public bool IsReference { get { throw null; } } public bool IsSchemaTypeValid { get { throw null; } } public SchemaType SchemaType { get { throw null; } set { } } public WireType WireType { get { throw null; } set { } } public void EnsureWireType(WireType expectedType) { } public void EnsureWireTypeTagDelimited() { } public override string ToString() { throw null; } } public enum SchemaType : uint { Expected = 0U, WellKnown = 8U, Encoded = 16U, Referenced = 24U } public partial struct Tag { private int _dummyPrimitive; public const byte ExtendedWireTypeMask = 24; public const byte FieldIdCompleteMask = 7; public const byte FieldIdMask = 7; public const int MaxEmbeddedFieldIdDelta = 6; public const byte SchemaTypeMask = 24; public const byte WireTypeMask = 224; public Tag(byte tag) { } public ExtendedWireType ExtendedWireType { get { throw null; } set { } } public uint FieldIdDelta { get { throw null; } set { } } public bool HasExtendedFieldId { get { throw null; } } public bool HasExtendedWireType { get { throw null; } } public bool IsFieldIdValid { get { throw null; } } public bool IsSchemaTypeValid { get { throw null; } } public SchemaType SchemaType { get { throw null; } set { } } public WireType WireType { get { throw null; } set { } } public static implicit operator byte(Tag tag) { throw null; } public static implicit operator Tag(byte tag) { throw null; } public void SetFieldIdInvalid() { } } public enum WireType : uint { VarInt = 0U, TagDelimited = 32U, LengthPrefixed = 64U, Fixed32 = 96U, Fixed64 = 128U, Reference = 192U, Extended = 224U } } namespace OrleansCodeGen.Orleans.Serialization { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CodecNotFoundException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_CodecNotFoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.CodecNotFoundException instance) { } public global::Orleans.Serialization.CodecNotFoundException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.CodecNotFoundException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.CodecNotFoundException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ExtendedWireTypeInvalidException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ExtendedWireTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.ExtendedWireTypeInvalidException instance) { } public global::Orleans.Serialization.ExtendedWireTypeInvalidException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.ExtendedWireTypeInvalidException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.ExtendedWireTypeInvalidException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FieldIdNotPresentException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_FieldIdNotPresentException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.FieldIdNotPresentException instance) { } public global::Orleans.Serialization.FieldIdNotPresentException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.FieldIdNotPresentException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FieldIdNotPresentException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FieldTypeInvalidException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_FieldTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.FieldTypeInvalidException instance) { } public global::Orleans.Serialization.FieldTypeInvalidException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.FieldTypeInvalidException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FieldTypeInvalidException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FieldTypeMissingException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_FieldTypeMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.FieldTypeMissingException instance) { } public global::Orleans.Serialization.FieldTypeMissingException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.FieldTypeMissingException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FieldTypeMissingException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_IllegalTypeException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_IllegalTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.IllegalTypeException instance) { } public global::Orleans.Serialization.IllegalTypeException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.IllegalTypeException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.IllegalTypeException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReferenceFieldNotSupportedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ReferenceFieldNotSupportedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.ReferenceFieldNotSupportedException instance) { } public global::Orleans.Serialization.ReferenceFieldNotSupportedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.ReferenceFieldNotSupportedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.ReferenceFieldNotSupportedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReferenceNotFoundException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ReferenceNotFoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.ReferenceNotFoundException instance) { } public global::Orleans.Serialization.ReferenceNotFoundException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.ReferenceNotFoundException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.ReferenceNotFoundException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RequiredFieldMissingException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_RequiredFieldMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.RequiredFieldMissingException instance) { } public global::Orleans.Serialization.RequiredFieldMissingException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.RequiredFieldMissingException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.RequiredFieldMissingException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SchemaTypeInvalidException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_SchemaTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.SchemaTypeInvalidException instance) { } public global::Orleans.Serialization.SchemaTypeInvalidException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.SchemaTypeInvalidException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.SchemaTypeInvalidException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SerializerException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_SerializerException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.SerializerException instance) { } public global::Orleans.Serialization.SerializerException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.SerializerException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.SerializerException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TypeMissingException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_TypeMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.TypeMissingException instance) { } public global::Orleans.Serialization.TypeMissingException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.TypeMissingException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.TypeMissingException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UnexpectedLengthPrefixValueException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_UnexpectedLengthPrefixValueException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.UnexpectedLengthPrefixValueException instance) { } public global::Orleans.Serialization.UnexpectedLengthPrefixValueException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.UnexpectedLengthPrefixValueException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.UnexpectedLengthPrefixValueException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UnknownReferencedTypeException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_UnknownReferencedTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.UnknownReferencedTypeException instance) { } public global::Orleans.Serialization.UnknownReferencedTypeException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.UnknownReferencedTypeException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.UnknownReferencedTypeException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UnknownWellKnownTypeException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_UnknownWellKnownTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.UnknownWellKnownTypeException instance) { } public global::Orleans.Serialization.UnknownWellKnownTypeException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.UnknownWellKnownTypeException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.UnknownWellKnownTypeException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UnsupportedWireTypeException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_UnsupportedWireTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.UnsupportedWireTypeException instance) { } public global::Orleans.Serialization.UnsupportedWireTypeException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.UnsupportedWireTypeException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.UnsupportedWireTypeException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_CodecNotFoundException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_CodecNotFoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ExtendedWireTypeInvalidException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ExtendedWireTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FieldIdNotPresentException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_FieldIdNotPresentException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FieldTypeInvalidException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_FieldTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FieldTypeMissingException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_FieldTypeMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_IllegalTypeException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_IllegalTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Serialization.IllegalTypeException input, global::Orleans.Serialization.IllegalTypeException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReferenceFieldNotSupportedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ReferenceFieldNotSupportedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Serialization.ReferenceFieldNotSupportedException input, global::Orleans.Serialization.ReferenceFieldNotSupportedException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReferenceNotFoundException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ReferenceNotFoundException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Serialization.ReferenceNotFoundException input, global::Orleans.Serialization.ReferenceNotFoundException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RequiredFieldMissingException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RequiredFieldMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SchemaTypeInvalidException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_SchemaTypeInvalidException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SerializerException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_SerializerException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TypeMissingException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_TypeMissingException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_UnexpectedLengthPrefixValueException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_UnexpectedLengthPrefixValueException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_UnknownReferencedTypeException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_UnknownReferencedTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Serialization.UnknownReferencedTypeException input, global::Orleans.Serialization.UnknownReferencedTypeException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_UnknownWellKnownTypeException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_UnknownWellKnownTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Serialization.UnknownWellKnownTypeException input, global::Orleans.Serialization.UnknownWellKnownTypeException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_UnsupportedWireTypeException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_UnsupportedWireTypeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Serialization.Codecs { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ArrayListSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ArrayListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ArrayListSurrogate instance) { } public global::Orleans.Serialization.Codecs.ArrayListSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ArrayListSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ArrayListSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ConcurrentDictionarySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ConcurrentDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate instance) { } public global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ConcurrentQueueSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ConcurrentQueueSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate instance) { } public global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CultureInfoSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.CultureInfoSurrogate instance) { } public global::Orleans.Serialization.Codecs.CultureInfoSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.CultureInfoSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.CultureInfoSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableArraySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableArraySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableArraySurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableArraySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableArraySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableArraySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableDictionarySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableHashSetSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableHashSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableListSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableListSurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableListSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableListSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableListSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableQueueSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableQueueSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableSortedDictionarySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableSortedDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableSortedSetSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableSortedSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ImmutableStackSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ImmutableStackSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ImmutableStackSurrogate instance) { } public global::Orleans.Serialization.Codecs.ImmutableStackSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ImmutableStackSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ImmutableStackSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_NameValueCollectionSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_NameValueCollectionSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate instance) { } public global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReadOnlyCollectionSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ReadOnlyCollectionSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate instance) { } public global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ReadOnlyDictionarySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ReadOnlyDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate instance) { } public global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SortedDictionarySurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_SortedDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.SortedDictionarySurrogate instance) { } public global::Orleans.Serialization.Codecs.SortedDictionarySurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.SortedDictionarySurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.SortedDictionarySurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SortedListSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_SortedListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.SortedListSurrogate instance) { } public global::Orleans.Serialization.Codecs.SortedListSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.SortedListSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.SortedListSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SortedSetSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_SortedSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.SortedSetSurrogate instance) { } public global::Orleans.Serialization.Codecs.SortedSetSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.SortedSetSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.SortedSetSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_VersionSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.Codecs.VersionSurrogate instance) { } public global::Orleans.Serialization.Codecs.VersionSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.Codecs.VersionSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Codecs.VersionSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ArrayListSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ArrayListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ArrayListSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ArrayListSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ConcurrentDictionarySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ConcurrentDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate DeepCopy(global::Orleans.Serialization.Codecs.ConcurrentDictionarySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ConcurrentQueueSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ConcurrentQueueSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ConcurrentQueueSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableArraySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableArraySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableArraySurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableArraySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableDictionarySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableDictionarySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableHashSetSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableHashSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableHashSetSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableListSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableListSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableListSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableQueueSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableQueueSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableQueueSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableSortedDictionarySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableSortedDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableSortedDictionarySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableSortedSetSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableSortedSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableSortedSetSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ImmutableStackSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ImmutableStackSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ImmutableStackSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ImmutableStackSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_NameValueCollectionSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_NameValueCollectionSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate DeepCopy(global::Orleans.Serialization.Codecs.NameValueCollectionSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReadOnlyCollectionSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ReadOnlyCollectionSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate DeepCopy(global::Orleans.Serialization.Codecs.ReadOnlyCollectionSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ReadOnlyDictionarySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_ReadOnlyDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate DeepCopy(global::Orleans.Serialization.Codecs.ReadOnlyDictionarySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SortedDictionarySurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_SortedDictionarySurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.SortedDictionarySurrogate DeepCopy(global::Orleans.Serialization.Codecs.SortedDictionarySurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SortedListSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_SortedListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.SortedListSurrogate DeepCopy(global::Orleans.Serialization.Codecs.SortedListSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SortedSetSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_SortedSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.Codecs.SortedSetSurrogate DeepCopy(global::Orleans.Serialization.Codecs.SortedSetSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Serialization.Invocation { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CompletedResponse : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_CompletedResponse(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.Invocation.CompletedResponse instance) { } public global::Orleans.Serialization.Invocation.CompletedResponse ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.Invocation.CompletedResponse instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Invocation.CompletedResponse value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ExceptionResponse : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ExceptionResponse(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.Invocation.ExceptionResponse instance) { } public global::Orleans.Serialization.Invocation.ExceptionResponse ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Serialization.Invocation.ExceptionResponse instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.Invocation.ExceptionResponse value) where TBufferWriter : System.Buffers.IBufferWriter { } } } ================================================ FILE: src/api/Orleans.Serialization.Abstractions/Orleans.Serialization.Abstractions.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum | System.AttributeTargets.Method | System.AttributeTargets.Interface, AllowMultiple = true)] public sealed partial class AliasAttribute : System.Attribute { public AliasAttribute(string alias) { } public string Alias { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] public sealed partial class ApplicationPartAttribute : System.Attribute { public ApplicationPartAttribute(string assemblyName) { } public string AssemblyName { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum | System.AttributeTargets.Interface, AllowMultiple = true)] public sealed partial class CompoundTypeAliasAttribute : System.Attribute { public CompoundTypeAliasAttribute(params object[] components) { } public object[] Components { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public sealed partial class DefaultInvokableBaseTypeAttribute : System.Attribute { public DefaultInvokableBaseTypeAttribute(System.Type returnType, System.Type invokableBaseType) { } public System.Type InvokableBaseType { get { throw null; } } public string ProxyInvokeMethodName { get { throw null; } init { } } public System.Type ReturnType { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public sealed partial class DefaultInvokeMethodNameAttribute : System.Attribute { public DefaultInvokeMethodNameAttribute(System.Type returnType, string methodName) { } public string MethodName { get { throw null; } } public System.Type ReturnType { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] public sealed partial class GenerateCodeForDeclaringAssemblyAttribute : System.Attribute { public GenerateCodeForDeclaringAssemblyAttribute(System.Type type) { } public System.Type Type { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Constructor)] public sealed partial class GeneratedActivatorConstructorAttribute : System.Attribute { } public enum GenerateFieldIds { None = 0, PublicProperties = 1 } [System.AttributeUsage(System.AttributeTargets.Interface, AllowMultiple = true)] public sealed partial class GenerateMethodSerializersAttribute : System.Attribute { public GenerateMethodSerializersAttribute(System.Type proxyBase, bool isExtension = false) { } public bool IsExtension { get { throw null; } } public System.Type ProxyBase { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum)] public sealed partial class GenerateSerializerAttribute : System.Attribute { public GenerateFieldIds GenerateFieldIds { get { throw null; } init { } } public bool IncludePrimaryConstructorParameters { get { throw null; } init { } } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class GetCompletionSourceMethodNameAttribute : System.Attribute { public GetCompletionSourceMethodNameAttribute(string methodName) { } public string MethodName { get { throw null; } } } public partial interface IConverter where TSurrogate : struct { TValue ConvertFromSurrogate(in TSurrogate surrogate); TSurrogate ConvertToSurrogate(in TValue value); } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum | System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field)] public sealed partial class IdAttribute : System.Attribute { public IdAttribute(uint id) { } public uint Id { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue, Inherited = false)] public sealed partial class ImmutableAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true)] public sealed partial class InvokableBaseTypeAttribute : System.Attribute { public InvokableBaseTypeAttribute(System.Type proxyBaseClass, System.Type returnType, System.Type invokableBaseType) { } public System.Type InvokableBaseType { get { throw null; } } public System.Type ProxyBaseClass { get { throw null; } } public string ProxyInvokeMethodName { get { throw null; } init { } } public System.Type ReturnType { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public sealed partial class InvokableCustomInitializerAttribute : System.Attribute { public InvokableCustomInitializerAttribute(string methodName, object methodArgumentValue) { } public InvokableCustomInitializerAttribute(string methodName) { } public int AttributeArgumentIndex { get { throw null; } init { } } public int AttributeArgumentName { get { throw null; } init { } } public object MethodArgumentValue { get { throw null; } } public string MethodName { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class InvokeMethodNameAttribute : System.Attribute { public InvokeMethodNameAttribute(string invokeMethodName) { } public string InvokeMethodName { get { throw null; } } } public partial interface IPopulator where TValue : class where TSurrogate : struct { void Populate(in TSurrogate surrogate, TValue value); } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class OmitDefaultMemberValuesAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Constructor)] public sealed partial class OrleansConstructorAttribute : Microsoft.Extensions.DependencyInjection.ActivatorUtilitiesConstructorAttribute { } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class RegisterActivatorAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class RegisterConverterAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class RegisterCopierAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] public sealed partial class RegisterProviderAttribute : System.Attribute { public RegisterProviderAttribute(string name, string kind, string target, System.Type type) { } public string Kind { get { throw null; } } public string Name { get { throw null; } } public string Target { get { throw null; } } public System.Type Type { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class RegisterSerializerAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method)] public sealed partial class ResponseTimeoutAttribute : System.Attribute { public ResponseTimeoutAttribute(string timeout) { } public System.TimeSpan? Timeout { get { throw null; } init { } } } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class SerializationCallbacksAttribute : System.Attribute { public SerializationCallbacksAttribute(System.Type hookType) { } public System.Type HookType { get { throw null; } } } [System.AttributeUsage(System.AttributeTargets.Class, Inherited = false)] public sealed partial class SerializerTransparentAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class SuppressReferenceTrackingAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public sealed partial class UseActivatorAttribute : System.Attribute { } } namespace Orleans.Invocation { [System.AttributeUsage(System.AttributeTargets.Class)] public sealed partial class ReturnValueProxyAttribute : System.Attribute { public ReturnValueProxyAttribute(string initializerMethodName) { } public string InitializerMethodName { get { throw null; } } } } namespace Orleans.Metadata { [System.AttributeUsage(System.AttributeTargets.Assembly)] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public sealed partial class FrameworkPartAttribute : System.Attribute { } } ================================================ FILE: src/api/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [RegisterSerializer] public partial class FSharpChoiceCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpChoiceCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec) { } Microsoft.FSharp.Core.FSharpChoice Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpChoice value) { } } [RegisterSerializer] public partial class FSharpChoiceCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpChoiceCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec, Codecs.IFieldCodec item3Codec) { } Microsoft.FSharp.Core.FSharpChoice Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpChoice value) { } } [RegisterSerializer] public partial class FSharpChoiceCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpChoiceCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec, Codecs.IFieldCodec item3Codec, Codecs.IFieldCodec item4Codec) { } Microsoft.FSharp.Core.FSharpChoice Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpChoice value) { } } [RegisterSerializer] public partial class FSharpChoiceCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpChoiceCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec, Codecs.IFieldCodec item3Codec, Codecs.IFieldCodec item4Codec, Codecs.IFieldCodec item5Codec) { } Microsoft.FSharp.Core.FSharpChoice Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpChoice value) { } } [RegisterSerializer] public partial class FSharpChoiceCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpChoiceCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec, Codecs.IFieldCodec item3Codec, Codecs.IFieldCodec item4Codec, Codecs.IFieldCodec item5Codec, Codecs.IFieldCodec item6Codec) { } Microsoft.FSharp.Core.FSharpChoice Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpChoice value) { } } [RegisterCopier] public partial class FSharpChoiceCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpChoiceCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2) { } public Microsoft.FSharp.Core.FSharpChoice DeepCopy(Microsoft.FSharp.Core.FSharpChoice input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public partial class FSharpChoiceCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpChoiceCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3) { } public Microsoft.FSharp.Core.FSharpChoice DeepCopy(Microsoft.FSharp.Core.FSharpChoice input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public partial class FSharpChoiceCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpChoiceCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4) { } public Microsoft.FSharp.Core.FSharpChoice DeepCopy(Microsoft.FSharp.Core.FSharpChoice input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public partial class FSharpChoiceCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpChoiceCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5) { } public Microsoft.FSharp.Core.FSharpChoice DeepCopy(Microsoft.FSharp.Core.FSharpChoice input, Cloning.CopyContext context) { throw null; } } [RegisterCopier] public partial class FSharpChoiceCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpChoiceCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2, Cloning.IDeepCopier copier3, Cloning.IDeepCopier copier4, Cloning.IDeepCopier copier5, Cloning.IDeepCopier copier6) { } public Microsoft.FSharp.Core.FSharpChoice DeepCopy(Microsoft.FSharp.Core.FSharpChoice input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public partial class FSharpListCodec : Codecs.GeneralizedReferenceTypeSurrogateCodec, FSharpListSurrogate> { public FSharpListCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override Microsoft.FSharp.Collections.FSharpList ConvertFromSurrogate(ref FSharpListSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(Microsoft.FSharp.Collections.FSharpList value, ref FSharpListSurrogate surrogate) { } } [RegisterCopier] public partial class FSharpListCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpListCopier(Cloning.IDeepCopier copier) { } public Microsoft.FSharp.Collections.FSharpList DeepCopy(Microsoft.FSharp.Collections.FSharpList input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct FSharpListSurrogate { private System.Collections.Generic.List _Value_k__BackingField; private object _dummy; private int _dummyPrimitive; [Id(0)] public System.Collections.Generic.List Value { get { throw null; } set { } } } [RegisterSerializer] public partial class FSharpMapCodec : Codecs.GeneralizedReferenceTypeSurrogateCodec, FSharpMapSurrogate> { public FSharpMapCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override Microsoft.FSharp.Collections.FSharpMap ConvertFromSurrogate(ref FSharpMapSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(Microsoft.FSharp.Collections.FSharpMap value, ref FSharpMapSurrogate surrogate) { } } [RegisterCopier] public partial class FSharpMapCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpMapCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public Microsoft.FSharp.Collections.FSharpMap DeepCopy(Microsoft.FSharp.Collections.FSharpMap input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct FSharpMapSurrogate { private System.Collections.Generic.List> _Value_k__BackingField; private object _dummy; private int _dummyPrimitive; [Id(0)] public System.Collections.Generic.List> Value { get { throw null; } set { } } } [RegisterSerializer] public sealed partial class FSharpOptionCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec { public FSharpOptionCodec(Codecs.IFieldCodec fieldCodec) { } public Microsoft.FSharp.Core.FSharpOption ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpOption value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class FSharpOptionCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpOptionCopier(Cloning.IDeepCopier valueCopier) { } public Microsoft.FSharp.Core.FSharpOption DeepCopy(Microsoft.FSharp.Core.FSharpOption input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public partial class FSharpRefCodec : Codecs.GeneralizedReferenceTypeSurrogateCodec, FSharpRefSurrogate> { public FSharpRefCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override Microsoft.FSharp.Core.FSharpRef ConvertFromSurrogate(ref FSharpRefSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(Microsoft.FSharp.Core.FSharpRef value, ref FSharpRefSurrogate surrogate) { } } [RegisterCopier] public partial class FSharpRefCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpRefCopier(Cloning.IDeepCopier copier) { } public Microsoft.FSharp.Core.FSharpRef DeepCopy(Microsoft.FSharp.Core.FSharpRef input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct FSharpRefSurrogate { private T _Value_k__BackingField; [Id(0)] public T Value { get { throw null; } set { } } } [RegisterSerializer] public partial class FSharpResultCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec, Codecs.IDerivedTypeCodec { public FSharpResultCodec(Codecs.IFieldCodec item1Codec, Codecs.IFieldCodec item2Codec) { } Microsoft.FSharp.Core.FSharpResult Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpResult value) { } } [RegisterCopier] public partial class FSharpResultCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IDerivedTypeCopier { public FSharpResultCopier(Cloning.IDeepCopier copier1, Cloning.IDeepCopier copier2) { } public Microsoft.FSharp.Core.FSharpResult DeepCopy(Microsoft.FSharp.Core.FSharpResult input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public partial class FSharpSetCodec : Codecs.GeneralizedReferenceTypeSurrogateCodec, FSharpSetSurrogate> { public FSharpSetCodec(Serializers.IValueSerializer> surrogateSerializer) : base(default!) { } public override Microsoft.FSharp.Collections.FSharpSet ConvertFromSurrogate(ref FSharpSetSurrogate surrogate) { throw null; } public override void ConvertToSurrogate(Microsoft.FSharp.Collections.FSharpSet value, ref FSharpSetSurrogate surrogate) { } } [RegisterCopier] public partial class FSharpSetCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpSetCopier(Cloning.IDeepCopier copier) { } public Microsoft.FSharp.Collections.FSharpSet DeepCopy(Microsoft.FSharp.Collections.FSharpSet input, Cloning.CopyContext context) { throw null; } } [GenerateSerializer] public partial struct FSharpSetSurrogate { private System.Collections.Generic.List _Value_k__BackingField; private object _dummy; private int _dummyPrimitive; [Id(0)] public System.Collections.Generic.List Value { get { throw null; } set { } } } [RegisterSerializer] public sealed partial class FSharpUnitCodec : Codecs.IFieldCodec, Codecs.IFieldCodec { public Microsoft.FSharp.Core.Unit ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.Unit value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class FSharpUnitCopier : Cloning.ShallowCopier { } [RegisterSerializer] public partial class FSharpValueOptionCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec { public FSharpValueOptionCodec(Codecs.IFieldCodec item1Codec) { } Microsoft.FSharp.Core.FSharpValueOption Codecs.IFieldCodec>.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec>.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Microsoft.FSharp.Core.FSharpValueOption value) { } } [RegisterCopier] public sealed partial class FSharpValueOptionCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier { public FSharpValueOptionCopier(Cloning.IDeepCopier valueCopier) { } public Microsoft.FSharp.Core.FSharpValueOption DeepCopy(Microsoft.FSharp.Core.FSharpValueOption input, Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Serialization { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FSharpListSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_FSharpListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.FSharpListSurrogate instance) { } public global::Orleans.Serialization.FSharpListSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.FSharpListSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FSharpListSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FSharpMapSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_FSharpMapSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.FSharpMapSurrogate instance) { } public global::Orleans.Serialization.FSharpMapSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.FSharpMapSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FSharpMapSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FSharpRefSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_FSharpRefSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.FSharpRefSurrogate instance) { } public global::Orleans.Serialization.FSharpRefSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.FSharpRefSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FSharpRefSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FSharpSetSurrogate : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer>, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_FSharpSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Serialization.FSharpSetSurrogate instance) { } public global::Orleans.Serialization.FSharpSetSurrogate ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Serialization.FSharpSetSurrogate instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Serialization.FSharpSetSurrogate value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FSharpListSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_FSharpListSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.FSharpListSurrogate DeepCopy(global::Orleans.Serialization.FSharpListSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FSharpMapSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_FSharpMapSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.FSharpMapSurrogate DeepCopy(global::Orleans.Serialization.FSharpMapSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FSharpRefSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_FSharpRefSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.FSharpRefSurrogate DeepCopy(global::Orleans.Serialization.FSharpRefSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FSharpSetSurrogate : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_FSharpSetSurrogate(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Serialization.FSharpSetSurrogate DeepCopy(global::Orleans.Serialization.FSharpSetSurrogate result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } ================================================ FILE: src/api/Orleans.Serialization.MemoryPack/Orleans.Serialization.MemoryPack.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [Alias("memorypack")] public partial class MemoryPackCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec, Cloning.IGeneralizedCopier, Cloning.IDeepCopier, ITypeFilter { public const string WellKnownAlias = "memorypack"; public MemoryPackCodec(System.Collections.Generic.IEnumerable serializableTypeSelectors, System.Collections.Generic.IEnumerable copyableTypeSelectors, Microsoft.Extensions.Options.IOptions options) { } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } bool Cloning.IGeneralizedCopier.IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } bool? ITypeFilter.IsTypeAllowed(System.Type type) { throw null; } bool Serializers.IGeneralizedCodec.IsSupportedType(System.Type type) { throw null; } } public partial class MemoryPackCodecOptions { public System.Func IsCopyableType { get { throw null; } set { } } public System.Func IsSerializableType { get { throw null; } set { } } public MemoryPack.MemoryPackSerializerOptions SerializerOptions { get { throw null; } set { } } } public static partial class SerializationHostingExtensions { public static ISerializerBuilder AddMemoryPackSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable = null, System.Func isCopyable = null, MemoryPack.MemoryPackSerializerOptions memoryPackSerializerOptions = null) { throw null; } public static ISerializerBuilder AddMemoryPackSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable, System.Func isCopyable, System.Action> configureOptions = null) { throw null; } } } ================================================ FILE: src/api/Orleans.Serialization.MessagePack/Orleans.Serialization.MessagePack.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [Alias("msgpack")] public partial class MessagePackCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec, Cloning.IGeneralizedCopier, Cloning.IDeepCopier, ITypeFilter { public const string WellKnownAlias = "msgpack"; public MessagePackCodec(System.Collections.Generic.IEnumerable serializableTypeSelectors, System.Collections.Generic.IEnumerable copyableTypeSelectors, Microsoft.Extensions.Options.IOptions options) { } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } bool Cloning.IGeneralizedCopier.IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } bool? ITypeFilter.IsTypeAllowed(System.Type type) { throw null; } bool Serializers.IGeneralizedCodec.IsSupportedType(System.Type type) { throw null; } } public partial class MessagePackCodecOptions { public bool AllowDataContractAttributes { get { throw null; } set { } } public System.Func IsCopyableType { get { throw null; } set { } } public System.Func IsSerializableType { get { throw null; } set { } } public MessagePack.MessagePackSerializerOptions SerializerOptions { get { throw null; } set { } } } public static partial class SerializationHostingExtensions { public static ISerializerBuilder AddMessagePackSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable = null, System.Func isCopyable = null, MessagePack.MessagePackSerializerOptions messagePackSerializerOptions = null) { throw null; } public static ISerializerBuilder AddMessagePackSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable, System.Func isCopyable, System.Action> configureOptions = null) { throw null; } } } ================================================ FILE: src/api/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [Alias("json.net")] public partial class NewtonsoftJsonCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec, Cloning.IGeneralizedCopier, Cloning.IDeepCopier, ITypeFilter { public const string WellKnownAlias = "json.net"; public NewtonsoftJsonCodec(System.Collections.Generic.IEnumerable serializableTypeSelectors, System.Collections.Generic.IEnumerable copyableTypeSelectors, Microsoft.Extensions.Options.IOptions options) { } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } bool Cloning.IGeneralizedCopier.IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } bool? ITypeFilter.IsTypeAllowed(System.Type type) { throw null; } bool Serializers.IGeneralizedCodec.IsSupportedType(System.Type type) { throw null; } } public partial class NewtonsoftJsonCodecOptions { public System.Func IsCopyableType { get { throw null; } set { } } public System.Func IsSerializableType { get { throw null; } set { } } public Newtonsoft.Json.JsonSerializerSettings SerializerSettings { get { throw null; } set { } } } public static partial class SerializationHostingExtensions { public static ISerializerBuilder AddNewtonsoftJsonSerializer(this ISerializerBuilder serializerBuilder, System.Func isSupported, Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = null) { throw null; } public static ISerializerBuilder AddNewtonsoftJsonSerializer(this ISerializerBuilder serializerBuilder, System.Func isSupported, System.Action> configureOptions) { throw null; } public static ISerializerBuilder AddNewtonsoftJsonSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable, System.Func isCopyable, System.Action> configureOptions) { throw null; } } } ================================================ FILE: src/api/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [Alias("json")] public partial class JsonCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec, Cloning.IGeneralizedCopier, Cloning.IDeepCopier, ITypeFilter { public const string WellKnownAlias = "json"; public JsonCodec(System.Collections.Generic.IEnumerable serializableTypeSelectors, System.Collections.Generic.IEnumerable copyableTypeSelectors, Microsoft.Extensions.Options.IOptions options) { } object Cloning.IDeepCopier.DeepCopy(object input, Cloning.CopyContext context) { throw null; } bool Cloning.IGeneralizedCopier.IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } bool? ITypeFilter.IsTypeAllowed(System.Type type) { throw null; } bool Serializers.IGeneralizedCodec.IsSupportedType(System.Type type) { throw null; } } public partial class JsonCodecOptions { public System.Func IsCopyableType { get { throw null; } set { } } public System.Func IsSerializableType { get { throw null; } set { } } public System.Text.Json.JsonReaderOptions ReaderOptions { get { throw null; } set { } } public System.Text.Json.JsonSerializerOptions SerializerOptions { get { throw null; } set { } } public System.Text.Json.JsonWriterOptions WriterOptions { get { throw null; } set { } } } public static partial class SerializationHostingExtensions { public static ISerializerBuilder AddJsonSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable, System.Func isCopyable, System.Action> configureOptions = null) { throw null; } public static ISerializerBuilder AddJsonSerializer(this ISerializerBuilder serializerBuilder, System.Func isSupported, System.Text.Json.JsonSerializerOptions jsonSerializerOptions = null) { throw null; } } } ================================================ FILE: src/api/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization.TestKit { [Xunit.Trait("Category", "BVT")] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public abstract partial class CopierTester where TCopier : class, Cloning.IDeepCopier { protected CopierTester(Xunit.Abstractions.ITestOutputHelper output) { } protected virtual bool IsImmutable { get { throw null; } } protected virtual bool IsPooled { get { throw null; } } protected System.Random Random { get { throw null; } } protected System.IServiceProvider ServiceProvider { get { throw null; } } protected abstract TValue[] TestValues { get; } protected virtual System.Action> ValueProvider { get { throw null; } } [Xunit.Fact] public void CanCopyCollectionViaSerializer() { } [Xunit.Fact] public void CanCopyCollectionViaUntypedSerializer() { } [Xunit.Fact] public void CanCopyTupleViaSerializer() { } [Xunit.Fact] public void CanCopyUntypedTupleViaSerializer() { } protected virtual void Configure(ISerializerBuilder builder) { } [Xunit.Fact] public void CopiedValuesAreEqual() { } protected virtual TCopier CreateCopier() { throw null; } protected abstract TValue CreateValue(); protected virtual bool Equals(TValue left, TValue right) { throw null; } [Xunit.Fact] public void ReferencesAreAddedToCopyContext() { } } [Xunit.Trait("Category", "BVT")] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public abstract partial class FieldCodecTester : System.IDisposable where TCodec : class, Codecs.IFieldCodec { protected FieldCodecTester(Xunit.Abstractions.ITestOutputHelper output) { } protected virtual int[] MaxSegmentSizes { get { throw null; } } protected System.Random Random { get { throw null; } } protected System.IServiceProvider ServiceProvider { get { throw null; } } protected Session.SerializerSessionPool SessionPool { get { throw null; } } protected abstract TValue[] TestValues { get; } protected virtual System.Action> ValueProvider { get { throw null; } } [Xunit.Fact] public void CanRoundTripCollectionViaSerializer() { } [Xunit.Fact] public void CanRoundTripDefaultValueViaCodec() { } [Xunit.Fact] public void CanRoundTripTupleViaSerializer() { } [Xunit.Fact] public void CanRoundTripViaObjectSerializer() { } [Xunit.Fact] public void CanRoundTripViaSerializer() { } [Xunit.Fact] public void CanRoundTripViaSerializer_Array() { } [Xunit.Fact] public void CanRoundTripViaSerializer_Memory() { } [Xunit.Fact] public void CanRoundTripViaSerializer_MemoryStream() { } [Xunit.Fact] public void CanRoundTripViaSerializer_ReadByteByByte() { } [Xunit.Fact] public void CanRoundTripViaSerializer_Span() { } [Xunit.Fact] public void CanRoundTripViaSerializer_StreamPooled() { } [Xunit.Fact] public void CanRoundTripWeaklyTypedCollectionViaSerializer() { } [Xunit.Fact] public void CanSkipDefaultValue() { } [Xunit.Fact] public void CanSkipValue() { } protected virtual void Configure(ISerializerBuilder builder) { } [Xunit.Fact] public void CorrectlyAdvancesReferenceCounter() { } [Xunit.Fact] public void CorrectlyAdvancesReferenceCounterStream() { } [Xunit.Fact] public void CorrectlyHandlesBuffers() { } protected virtual TCodec CreateCodec() { throw null; } protected abstract TValue CreateValue(); protected virtual bool Equals(TValue left, TValue right) { throw null; } protected virtual TValue GetWriteCopy(TValue input) { throw null; } [Xunit.Fact] public void ProducesValidBitStream() { } [Xunit.Fact] public void RoundTrippedValuesEqual() { } protected T RoundTripThroughCodec(T original) { throw null; } protected object RoundTripThroughUntypedSerializer(object original, out string formattedBitStream) { throw null; } void System.IDisposable.Dispose() { } [Xunit.Fact] public void WritersProduceSameResults() { } } public partial interface IOutputBuffer { System.Buffers.ReadOnlySequence GetReadOnlySequence(int maxSegmentSize); } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public static partial class ReadOnlySequenceHelper { public static System.Collections.Generic.IEnumerable Batch(this System.Collections.Generic.IEnumerable sequence, int batchSize) { throw null; } public static System.Buffers.ReadOnlySequence CreateReadOnlySequence(params byte[][] buffers) { throw null; } public static System.Buffers.ReadOnlySequence ToReadOnlySequence(this System.Collections.Generic.IEnumerable buffers) { throw null; } public static System.Buffers.ReadOnlySequence ToReadOnlySequence(this System.Collections.Generic.IEnumerable> buffers) { throw null; } } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public partial struct TestBufferWriterStruct : System.Buffers.IBufferWriter, IOutputBuffer { private object _dummy; private int _dummyPrimitive; public TestBufferWriterStruct(byte[] buffer) { } public void Advance(int bytes) { } public readonly System.Memory GetMemory(int sizeHint = 0) { throw null; } public readonly System.Buffers.ReadOnlySequence GetReadOnlySequence(int maxSegmentSize) { throw null; } public readonly System.Span GetSpan(int sizeHint) { throw null; } } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public partial class TestMultiSegmentBufferWriter : System.Buffers.IBufferWriter, IOutputBuffer { public TestMultiSegmentBufferWriter(int maxAllocationSize) { } public void Advance(int bytes) { } public System.Memory GetMemory(int sizeHint = 0) { throw null; } public System.Buffers.ReadOnlySequence GetReadOnlySequence(int maxSegmentSize) { throw null; } public System.Span GetSpan(int sizeHint) { throw null; } public System.Buffers.ReadOnlySequence PeekAllBuffers() { throw null; } } public abstract partial class ValueTypeFieldCodecTester : FieldCodecTester where TField : struct where TCodec : class, Codecs.IFieldCodec { protected ValueTypeFieldCodecTester(Xunit.Abstractions.ITestOutputHelper output) : base(default!) { } [Xunit.Fact] public void DirectAccessValueSerializerRoundTrip() { } [Xunit.Fact] public void ValueSerializerRoundTrip() { } } } ================================================ FILE: src/api/Orleans.Server/Orleans.Server.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ ================================================ FILE: src/api/Orleans.Streaming/Orleans.Streaming.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { public static partial class ClientStreamingExtensions { public static Streams.IStreamProvider GetStreamProvider(this IClusterClient client, string name) { throw null; } } public static partial class GrainStreamingExtensions { public static Streams.IStreamProvider GetStreamProvider(this Grain grain, string name) { throw null; } public static Streams.IStreamProvider GetStreamProvider(this IGrainBase grain, string name) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public partial class ImplicitStreamSubscriptionAttribute : System.Attribute, Metadata.IGrainBindingsProviderAttribute { public ImplicitStreamSubscriptionAttribute() { } public ImplicitStreamSubscriptionAttribute(Streams.IStreamNamespacePredicate predicate, string streamIdMapper = null) { } public ImplicitStreamSubscriptionAttribute(string streamNamespace, string streamIdMapper = null) { } public ImplicitStreamSubscriptionAttribute(System.Type predicateType, string streamIdMapper = null) { } public Streams.IStreamNamespacePredicate Predicate { get { throw null; } } public string StreamIdMapper { get { throw null; } init { } } public System.Collections.Generic.IEnumerable> GetBindings(System.IServiceProvider services, System.Type grainClass, Runtime.GrainType grainType) { throw null; } } [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] public sealed partial class RegexImplicitStreamSubscriptionAttribute : ImplicitStreamSubscriptionAttribute { public RegexImplicitStreamSubscriptionAttribute(string pattern) { } } } namespace Orleans.Configuration { public partial class DeploymentBasedQueueBalancerOptions { public static readonly System.TimeSpan DEFAULT_SILO_MATURITY_PERIOD; public bool IsFixed { get { throw null; } set { } } public System.TimeSpan SiloMaturityPeriod { get { throw null; } set { } } } public partial class HashRingStreamQueueMapperOptions { public const int DEFAULT_NUM_QUEUES = 8; public int TotalQueueCount { get { throw null; } set { } } } public partial class LeaseBasedQueueBalancerOptions { public const string DefaultLeaseCategory = "QueueBalancer"; public static readonly System.TimeSpan DefaultLeaseLength; public static readonly System.TimeSpan DefaultLeaseRenewPeriod; public static readonly System.TimeSpan DefaultMinLeaseAcquisitionPeriod; [System.Obsolete("Use DefaultMinLeaseAcquisitionPeriod instead.", true)] public static readonly System.TimeSpan DefaultMinLeaseAquisitionPeriod; public System.TimeSpan LeaseAcquisitionPeriod { get { throw null; } set { } } [System.Obsolete("Use LeaseAcquisitionPeriod instead.", true)] public System.TimeSpan LeaseAquisitionPeriod { get { throw null; } set { } } public string LeaseCategory { get { throw null; } set { } } public System.TimeSpan LeaseLength { get { throw null; } set { } } public System.TimeSpan LeaseRenewPeriod { get { throw null; } set { } } } public partial class SimpleQueueCacheOptions { public const int DEFAULT_CACHE_SIZE = 4096; public int CacheSize { get { throw null; } set { } } } public partial class SimpleQueueCacheOptionsValidator : IConfigurationValidator { internal SimpleQueueCacheOptionsValidator() { } public static IConfigurationValidator Create(System.IServiceProvider services, string name) { throw null; } public void ValidateConfiguration() { } } public partial class StreamCacheEvictionOptions { public static readonly System.TimeSpan DefaultDataMaxAgeInCache; public static readonly System.TimeSpan DefaultDataMinTimeInCache; public static readonly System.TimeSpan DefaultMetadataMinTimeInCache; public System.TimeSpan DataMaxAgeInCache { get { throw null; } set { } } public System.TimeSpan DataMinTimeInCache { get { throw null; } set { } } public System.TimeSpan? MetadataMinTimeInCache { get { throw null; } set { } } } public partial class StreamLifecycleOptions { public const int DEFAULT_INIT_STAGE = 10000; public const int DEFAULT_START_STAGE = 20000; public const RunState DEFAULT_STARTUP_STATE = 2; public int InitStage { get { throw null; } set { } } public int StartStage { get { throw null; } set { } } public RunState StartupState { get { throw null; } set { } } public enum RunState { None = 0, Initialized = 1, AgentsStarted = 2, AgentsStopped = 3 } } public partial class StreamPubSubOptions { public const Streams.StreamPubSubType DEFAULT_STREAM_PUBSUB_TYPE = 0; public Streams.StreamPubSubType PubSubType { get { throw null; } set { } } } public partial class StreamPullingAgentOptions { public static readonly int DEFAULT_BATCH_CONTAINER_BATCH_SIZE; public static readonly System.TimeSpan DEFAULT_GET_QUEUE_MESSAGES_TIMER_PERIOD; public static readonly System.TimeSpan DEFAULT_INIT_QUEUE_TIMEOUT; public static readonly System.TimeSpan DEFAULT_MAX_EVENT_DELIVERY_TIME; public static readonly System.TimeSpan DEFAULT_STREAM_INACTIVITY_PERIOD; public int BatchContainerBatchSize { get { throw null; } set { } } public System.TimeSpan GetQueueMsgsTimerPeriod { get { throw null; } set { } } public System.TimeSpan InitQueueTimeout { get { throw null; } set { } } public System.TimeSpan MaxEventDeliveryTime { get { throw null; } set { } } public System.TimeSpan StreamInactivityPeriod { get { throw null; } set { } } } public partial class StreamStatisticOptions { public static readonly System.TimeSpan DefaultStatisticMonitorWriteInterval; public System.TimeSpan StatisticMonitorWriteInterval { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class ClientBuilderStreamingExtensions { public static IClientBuilder AddMemoryStreams(this IClientBuilder builder, string name, System.Action configure = null) { throw null; } public static IClientBuilder AddMemoryStreams(this IClientBuilder builder, string name, System.Action configure = null) where TSerializer : class, Providers.IMemoryMessageBodySerializer { throw null; } public static IClientBuilder AddPersistentStreams(this IClientBuilder builder, string name, System.Func adapterFactory, System.Action configureStream) { throw null; } public static IClientBuilder AddStreaming(this IClientBuilder builder) { throw null; } } public partial class ClusterClientMemoryStreamConfigurator : ClusterClientPersistentStreamConfigurator, IClusterClientMemoryStreamConfigurator, IMemoryStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator where TSerializer : class, Providers.IMemoryMessageBodySerializer { public ClusterClientMemoryStreamConfigurator(string name, IClientBuilder builder) : base(default!, default!, default!) { } } public partial class ClusterClientPersistentStreamConfigurator : NamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { public ClusterClientPersistentStreamConfigurator(string name, IClientBuilder clientBuilder, System.Func adapterFactory) : base(default!, default!) { } } public static partial class ClusterClientPersistentStreamConfiguratorExtensions { public static void ConfigureLifecycle(this IClusterClientPersistentStreamConfigurator configurator, System.Action> configureOptions) { } } public partial interface IClusterClientMemoryStreamConfigurator : IMemoryStreamConfigurator, INamedServiceConfigurator, IClusterClientPersistentStreamConfigurator, IPersistentStreamConfigurator { } public partial interface IClusterClientPersistentStreamConfigurator : IPersistentStreamConfigurator, INamedServiceConfigurator { } public partial interface IMemoryStreamConfigurator : INamedServiceConfigurator { } public partial interface IPersistentStreamConfigurator : INamedServiceConfigurator { } public partial interface ISiloMemoryStreamConfigurator : IMemoryStreamConfigurator, INamedServiceConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator { } public partial interface ISiloPersistentStreamConfigurator : IPersistentStreamConfigurator, INamedServiceConfigurator { } public partial interface ISiloRecoverableStreamConfigurator : ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { } public static partial class MemoryStreamConfiguratorExtensions { public static void ConfigurePartitioning(this IMemoryStreamConfigurator configurator, int numOfQueues = 8) { } } public static partial class PersistentStreamConfiguratorExtensions { public static void ConfigureStreamPubSub(this IPersistentStreamConfigurator configurator, Streams.StreamPubSubType pubsubType = Streams.StreamPubSubType.ExplicitGrainBasedAndImplicit) { } } public partial class PersistentStreamStorageConfigurationValidator : IConfigurationValidator { internal PersistentStreamStorageConfigurationValidator() { } public static IConfigurationValidator Create(System.IServiceProvider services, string name) { throw null; } public void ValidateConfiguration() { } } public static partial class SiloBuilderMemoryStreamExtensions { public static ISiloBuilder AddMemoryStreams(this ISiloBuilder builder, string name, System.Action configure = null) { throw null; } public static ISiloBuilder AddMemoryStreams(this ISiloBuilder builder, string name, System.Action configure = null) where TSerializer : class, Providers.IMemoryMessageBodySerializer { throw null; } } public static partial class SiloBuilderStreamingExtensions { public static ISiloBuilder AddPersistentStreams(this ISiloBuilder builder, string name, System.Func adapterFactory, System.Action configureStream) { throw null; } public static IClientBuilder AddStreamFilter(this IClientBuilder builder, string name) where T : class, Streams.Filtering.IStreamFilter { throw null; } public static ISiloBuilder AddStreamFilter(this ISiloBuilder builder, string name) where T : class, Streams.Filtering.IStreamFilter { throw null; } public static ISiloBuilder AddStreaming(this ISiloBuilder builder) { throw null; } } public partial class SiloMemoryStreamConfigurator : SiloRecoverableStreamConfigurator, ISiloMemoryStreamConfigurator, IMemoryStreamConfigurator, INamedServiceConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator where TSerializer : class, Providers.IMemoryMessageBodySerializer { public SiloMemoryStreamConfigurator(string name, System.Action> configureServicesDelegate) : base(default!, default!, default!) { } } public partial class SiloPersistentStreamConfigurator : NamedServiceConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { public SiloPersistentStreamConfigurator(string name, System.Action> configureDelegate, System.Func adapterFactory) : base(default!, default!) { } } public static partial class SiloPersistentStreamConfiguratorExtension { public static void UseConsistentRingQueueBalancer(this ISiloPersistentStreamConfigurator configurator) { } public static void UseDynamicClusterConfigDeploymentBalancer(this ISiloPersistentStreamConfigurator configurator, System.TimeSpan? siloMaturityPeriod = null) { } public static void UseLeaseBasedQueueBalancer(this ISiloPersistentStreamConfigurator configurator, System.Action> configureOptions = null) { } public static void UseStaticClusterConfigDeploymentBalancer(this ISiloPersistentStreamConfigurator configurator, System.TimeSpan? siloMaturityPeriod = null) { } } public static partial class SiloPersistentStreamConfiguratorExtensions { public static void ConfigureBackoffProvider(this ISiloPersistentStreamConfigurator configurator, System.Func factory) { } public static void ConfigureBackoffProvider(this ISiloPersistentStreamConfigurator configurator, System.Func factory) { } public static void ConfigureLifecycle(this ISiloPersistentStreamConfigurator configurator, System.Action> configureOptions) { } public static void ConfigurePartitionBalancing(this ISiloPersistentStreamConfigurator configurator, System.Func factory) { } public static void ConfigurePartitionBalancing(this ISiloPersistentStreamConfigurator configurator, System.Func factory, System.Action> configureOptions) where TOptions : class, new() { } public static void ConfigurePullingAgent(this ISiloPersistentStreamConfigurator configurator, System.Action> configureOptions = null) { } } public partial class SiloRecoverableStreamConfigurator : SiloPersistentStreamConfigurator, ISiloRecoverableStreamConfigurator, ISiloPersistentStreamConfigurator, IPersistentStreamConfigurator, INamedServiceConfigurator { public SiloRecoverableStreamConfigurator(string name, System.Action> configureDelegate, System.Func adapterFactory) : base(default!, default!, default!) { } } public static partial class SiloRecoverableStreamConfiguratorExtensions { public static void ConfigureCacheEviction(this ISiloRecoverableStreamConfigurator configurator, System.Action> configureOptions) { } public static void ConfigureStatistics(this ISiloRecoverableStreamConfigurator configurator, System.Action> configureOptions) { } } [GenerateSerializer] public sealed partial class SimpleGeneratorOptions : Providers.Streams.Generator.IStreamGeneratorConfig { public const int DEFAULT_EVENTS_IN_STREAM = 100; [Id(1)] public int EventsInStream { get { throw null; } set { } } public System.Type StreamGeneratorType { get { throw null; } } [Id(0)] public string StreamNamespace { get { throw null; } set { } } } public partial class StaticClusterDeploymentOptions : Streams.IDeploymentConfiguration { public System.Collections.Generic.IList SiloNames { get { throw null; } set { } } System.Collections.Generic.IList Streams.IDeploymentConfiguration.GetAllSiloNames() { throw null; } } public static partial class StreamingServiceCollectionExtensions { public static void AddClientStreaming(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } public static void AddSiloStreaming(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddStreamFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name) where T : class, Streams.Filtering.IStreamFilter { throw null; } } } namespace Orleans.Providers { [GenerateSerializer] [Immutable] [SerializationCallbacks(typeof(Runtime.OnDeserializedCallbacks))] public sealed partial class DefaultMemoryMessageBodySerializer : IMemoryMessageBodySerializer, Serialization.IOnDeserialized { public DefaultMemoryMessageBodySerializer(Serialization.Serializer serializer) { } public MemoryMessageBody Deserialize(System.ArraySegment bodyBytes) { throw null; } void Serialization.IOnDeserialized.OnDeserialized(Serialization.DeserializationContext context) { } public System.ArraySegment Serialize(MemoryMessageBody body) { throw null; } } public partial interface IMemoryMessageBodySerializer { MemoryMessageBody Deserialize(System.ArraySegment bodyBytes); System.ArraySegment Serialize(MemoryMessageBody body); } public partial interface IMemoryStreamQueueGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { System.Threading.Tasks.Task> Dequeue(int maxCount); System.Threading.Tasks.Task Enqueue(MemoryMessageData data); } public partial class MemoryAdapterFactory : Orleans.Streams.IQueueAdapterFactory, Orleans.Streams.IQueueAdapter, Orleans.Streams.IQueueAdapterCache where TSerializer : class, IMemoryMessageBodySerializer { protected System.Func BlockPoolMonitorFactory; protected System.Func CacheMonitorFactory; protected System.Func ReceiverMonitorFactory; public MemoryAdapterFactory(string providerName, Configuration.StreamCacheEvictionOptions cacheOptions, Configuration.StreamStatisticOptions statisticOptions, Configuration.HashRingStreamQueueMapperOptions queueMapperOptions, System.IServiceProvider serviceProvider, IGrainFactory grainFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Orleans.Streams.StreamProviderDirection Direction { get { throw null; } } public bool IsRewindable { get { throw null; } } public string Name { get { throw null; } } protected System.Func> StreamFailureHandlerFactory { get { throw null; } set { } } public static MemoryAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public System.Threading.Tasks.Task CreateAdapter() { throw null; } public Orleans.Streams.IQueueCache CreateQueueCache(Orleans.Streams.QueueId queueId) { throw null; } public Orleans.Streams.IQueueAdapterReceiver CreateReceiver(Orleans.Streams.QueueId queueId) { throw null; } public System.Threading.Tasks.Task GetDeliveryFailureHandler(Orleans.Streams.QueueId queueId) { throw null; } public Orleans.Streams.IQueueAdapterCache GetQueueAdapterCache() { throw null; } public Orleans.Streams.IStreamQueueMapper GetStreamQueueMapper() { throw null; } public void Init() { } public System.Threading.Tasks.Task QueueMessageBatchAsync(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Orleans.Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } [GenerateSerializer] public sealed partial class MemoryMessageBody { public MemoryMessageBody(System.Collections.Generic.IEnumerable events, System.Collections.Generic.Dictionary requestContext) { } [Id(0)] public System.Collections.Generic.List Events { get { throw null; } } [Id(1)] public System.Collections.Generic.Dictionary RequestContext { get { throw null; } } } [GenerateSerializer] public partial struct MemoryMessageData { [Id(2)] public System.DateTime DequeueTimeUtc; [Id(3)] public System.DateTime EnqueueTimeUtc; [Id(4)] public System.ArraySegment Payload; [Id(1)] public long SequenceNumber; [Id(0)] public Runtime.StreamId StreamId; } public partial class MemoryPooledCache : Orleans.Streams.IQueueCache, Orleans.Streams.IQueueFlowController, Streams.Common.ICacheDataAdapter where TSerializer : class, IMemoryMessageBodySerializer { public MemoryPooledCache(Streams.Common.IObjectPool bufferPool, Streams.Common.TimePurgePredicate purgePredicate, Microsoft.Extensions.Logging.ILogger logger, TSerializer serializer, Streams.Common.ICacheMonitor cacheMonitor, System.TimeSpan? monitorWriteInterval, System.TimeSpan? purgeMetadataInterval) { } public void AddToCache(System.Collections.Generic.IList messages) { } public Orleans.Streams.IBatchContainer GetBatchContainer(ref Streams.Common.CachedMessage cachedMessage) { throw null; } public Orleans.Streams.IQueueCacheCursor GetCacheCursor(Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken token) { throw null; } public int GetMaxAddCount() { throw null; } public Orleans.Streams.StreamSequenceToken GetSequenceToken(ref Streams.Common.CachedMessage cachedMessage) { throw null; } public bool IsUnderPressure() { throw null; } public bool TryPurgeFromCache(out System.Collections.Generic.IList purgedItems) { throw null; } } public partial class MemoryStreamQueueGrain : Grain, IMemoryStreamQueueGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable, Runtime.IGrainMigrationParticipant { public System.Threading.Tasks.Task> Dequeue(int maxCount) { throw null; } public System.Threading.Tasks.Task Enqueue(MemoryMessageData data) { throw null; } void Runtime.IGrainMigrationParticipant.OnDehydrate(Runtime.IDehydrationContext dehydrationContext) { } void Runtime.IGrainMigrationParticipant.OnRehydrate(Runtime.IRehydrationContext rehydrationContext) { } } } namespace Orleans.Providers.Streams.Common { public partial class BlockPoolMonitorDimensions { public BlockPoolMonitorDimensions(string blockPoolId) { } public string BlockPoolId { get { throw null; } set { } } } public partial struct CachedMessage { public System.DateTime DequeueTimeUtc; public System.DateTime EnqueueTimeUtc; public int EventIndex; public System.ArraySegment Segment; public long SequenceNumber; public Runtime.StreamId StreamId; } public partial class CachedMessageBlock : PooledResource { public CachedMessageBlock(int blockSize = 16384) { } public bool HasCapacity { get { throw null; } } public bool IsEmpty { get { throw null; } } public CachedMessage this[int index] { get { throw null; } } public int ItemCount { get { throw null; } } public CachedMessage NewestMessage { get { throw null; } } public int NewestMessageIndex { get { throw null; } } public System.Collections.Generic.LinkedListNode Node { get { throw null; } } public CachedMessage OldestMessage { get { throw null; } } public int OldestMessageIndex { get { throw null; } } public void Add(CachedMessage message) { } public int GetIndexOfFirstMessageLessThanOrEqualTo(Orleans.Streams.StreamSequenceToken token) { throw null; } public Orleans.Streams.StreamSequenceToken GetNewestSequenceToken(ICacheDataAdapter dataAdapter) { throw null; } public Orleans.Streams.StreamSequenceToken GetOldestSequenceToken(ICacheDataAdapter dataAdapter) { throw null; } public Orleans.Streams.StreamSequenceToken GetSequenceToken(int index, ICacheDataAdapter dataAdapter) { throw null; } public override void OnResetState() { } public bool Remove() { throw null; } public bool TryFindFirstMessage(Runtime.StreamId streamId, ICacheDataAdapter dataAdapter, out int index) { throw null; } public bool TryFindNextMessage(int start, Runtime.StreamId streamId, ICacheDataAdapter dataAdapter, out int index) { throw null; } } public static partial class CachedMessageExtensions { public static int Compare(this ref CachedMessage cachedMessage, Orleans.Streams.StreamSequenceToken token) { throw null; } public static bool CompareStreamId(this ref CachedMessage cachedMessage, Runtime.StreamId streamId) { throw null; } } public partial class CacheMonitorDimensions : ReceiverMonitorDimensions { public CacheMonitorDimensions(string queueId, string blockPoolId) { } public string BlockPoolId { get { throw null; } set { } } } public partial class ChronologicalEvictionStrategy : IEvictionStrategy { protected readonly System.Collections.Generic.Queue inUseBuffers; public ChronologicalEvictionStrategy(Microsoft.Extensions.Logging.ILogger logger, TimePurgePredicate timePurage, ICacheMonitor cacheMonitor, System.TimeSpan? monitorWriteInterval) { } public System.Action OnPurged { get { throw null; } set { } } public IPurgeObservable PurgeObservable { set { } } public void OnBlockAllocated(FixedSizeBuffer newBlock) { } public void PerformPurge(System.DateTime nowUtc) { } protected virtual bool ShouldPurge(ref CachedMessage cachedMessage, ref CachedMessage newestCachedMessage, System.DateTime nowUtc) { throw null; } } public partial class DefaultBlockPoolMonitor : IBlockPoolMonitor { protected System.Collections.Generic.KeyValuePair[] _dimensions; public DefaultBlockPoolMonitor(BlockPoolMonitorDimensions dimensions) { } protected DefaultBlockPoolMonitor(System.Collections.Generic.KeyValuePair[] dimensions) { } public void Report(long totalMemoryInByte, long availableMemoryInByte, long claimedMemoryInByte) { } public void TrackMemoryAllocated(long allocatedMemoryInByte) { } public void TrackMemoryReleased(long releasedMemoryInByte) { } } public partial class DefaultCacheMonitor : ICacheMonitor { public DefaultCacheMonitor(CacheMonitorDimensions dimensions) { } protected DefaultCacheMonitor(System.Collections.Generic.KeyValuePair[] dimensions) { } public void ReportCacheSize(long totalCacheSizeInByte) { } public void ReportMessageStatistics(System.DateTime? oldestMessageEnqueueTimeUtc, System.DateTime? oldestMessageDequeueTimeUtc, System.DateTime? newestMessageEnqueueTimeUtc, long totalMessageCount) { } public void TrackCachePressureMonitorStatusChange(string pressureMonitorType, bool underPressure, double? cachePressureContributionCount, double? currentPressure, double? flowControlThreshold) { } public void TrackMemoryAllocated(int memoryInByte) { } public void TrackMemoryReleased(int memoryInByte) { } public void TrackMessagesAdded(long messageAdded) { } public void TrackMessagesPurged(long messagePurged) { } } public partial class DefaultQueueAdapterReceiverMonitor : IQueueAdapterReceiverMonitor { public DefaultQueueAdapterReceiverMonitor(ReceiverMonitorDimensions dimensions) { } protected DefaultQueueAdapterReceiverMonitor(System.Collections.Generic.KeyValuePair[] dimensions) { } public void TrackInitialization(bool success, System.TimeSpan callTime, System.Exception exception) { } public void TrackMessagesReceived(long count, System.DateTime? oldestMessageEnqueueTimeUtc, System.DateTime? newestMessageEnqueueTimeUtc) { } public void TrackRead(bool success, System.TimeSpan callTime, System.Exception exception) { } public void TrackShutdown(bool success, System.TimeSpan callTime, System.Exception exception) { } } [GenerateSerializer] public partial class EventSequenceToken : Orleans.Streams.StreamSequenceToken { [Newtonsoft.Json.JsonConstructor] public EventSequenceToken() { } public EventSequenceToken(long sequenceNumber, int eventIndex) { } public EventSequenceToken(long sequenceNumber) { } [Id(1)] [Newtonsoft.Json.JsonProperty] public override int EventIndex { get { throw null; } protected set { } } [Id(0)] [Newtonsoft.Json.JsonProperty] public override long SequenceNumber { get { throw null; } protected set { } } public override int CompareTo(Orleans.Streams.StreamSequenceToken other) { throw null; } public EventSequenceToken CreateSequenceTokenForEvent(int eventInd) { throw null; } public override bool Equals(Orleans.Streams.StreamSequenceToken other) { throw null; } public override bool Equals(object obj) { throw null; } public override int GetHashCode() { throw null; } public override string ToString() { throw null; } } [GenerateSerializer] public partial class EventSequenceTokenV2 : Orleans.Streams.StreamSequenceToken { public EventSequenceTokenV2() { } public EventSequenceTokenV2(long seqNumber, int eventInd) { } public EventSequenceTokenV2(long seqNumber) { } [Id(1)] [Newtonsoft.Json.JsonProperty] public override int EventIndex { get { throw null; } protected set { } } [Id(0)] [Newtonsoft.Json.JsonProperty] public override long SequenceNumber { get { throw null; } protected set { } } public override int CompareTo(Orleans.Streams.StreamSequenceToken other) { throw null; } public EventSequenceTokenV2 CreateSequenceTokenForEvent(int eventInd) { throw null; } public override bool Equals(Orleans.Streams.StreamSequenceToken other) { throw null; } public override bool Equals(object obj) { throw null; } public override int GetHashCode() { throw null; } public override string ToString() { throw null; } } public partial class FixedSizeBuffer : PooledResource { public readonly int SizeInByte; public FixedSizeBuffer(int blockSizeInByte) { } public object Id { get { throw null; } } public override void OnResetState() { } public bool TryGetSegment(int size, out System.ArraySegment value) { throw null; } } public partial interface IBlockPoolMonitor { void Report(long totalSizeInByte, long availableMemoryInByte, long claimedMemoryInByte); void TrackMemoryAllocated(long allocatedMemoryInBytes); void TrackMemoryReleased(long releasedMemoryInBytes); } public partial interface ICacheDataAdapter { Orleans.Streams.IBatchContainer GetBatchContainer(ref CachedMessage cachedMessage); Orleans.Streams.StreamSequenceToken GetSequenceToken(ref CachedMessage cachedMessage); } public partial interface ICacheMonitor { void ReportCacheSize(long totalCacheSizeInBytes); void ReportMessageStatistics(System.DateTime? oldestMessageEnqueueTimeUtc, System.DateTime? oldestMessageDequeueTimeUtc, System.DateTime? newestMessageEnqueueTimeUtc, long totalMessageCount); void TrackCachePressureMonitorStatusChange(string pressureMonitorType, bool underPressure, double? cachePressureContributionCount, double? currentPressure, double? flowControlThreshold); void TrackMemoryAllocated(int memoryInBytes); void TrackMemoryReleased(int memoryInBytes); void TrackMessagesAdded(long messagesAdded); void TrackMessagesPurged(long messagesPurged); } public partial interface IEvictionStrategy { System.Action OnPurged { get; set; } IPurgeObservable PurgeObservable { set; } void OnBlockAllocated(FixedSizeBuffer newBlock); void PerformPurge(System.DateTime utcNow); } public partial interface IObjectPoolMonitor { void Report(long totalObjects, long availableObjects, long claimedObjects); void TrackObjectAllocated(); void TrackObjectReleased(); } public partial interface IObjectPool where T : System.IDisposable { T Allocate(); void Free(T resource); } public partial interface IPurgeObservable { bool IsEmpty { get; } int ItemCount { get; } CachedMessage? Newest { get; } CachedMessage? Oldest { get; } void RemoveOldestMessage(); } public partial interface IQueueAdapterReceiverMonitor { void TrackInitialization(bool success, System.TimeSpan callTime, System.Exception exception); void TrackMessagesReceived(long count, System.DateTime? oldestMessageEnqueueTimeUtc, System.DateTime? newestMessageEnqueueTimeUtc); void TrackRead(bool success, System.TimeSpan callTime, System.Exception exception); void TrackShutdown(bool success, System.TimeSpan callTime, System.Exception exception); } public partial class ObjectPoolMonitorBridge : IObjectPoolMonitor { public ObjectPoolMonitorBridge(IBlockPoolMonitor blockPoolMonitor, int blockSizeInBytes) { } public void Report(long totalObjects, long availableObjects, long claimedObjects) { } public void TrackObjectAllocated() { } public void TrackObjectReleased() { } } public partial class ObjectPool : IObjectPool where T : PooledResource { public ObjectPool(System.Func factoryFunc, IObjectPoolMonitor monitor = null, System.TimeSpan? monitorWriteInterval = null) { } public virtual T Allocate() { throw null; } public virtual void Free(T resource) { } } public partial class PersistentStreamProvider : Orleans.Streams.IStreamProvider, IControllable, Orleans.Streams.Core.IStreamSubscriptionManagerRetriever, ILifecycleParticipant { public PersistentStreamProvider(string name, Configuration.StreamPubSubOptions pubsubOptions, Configuration.StreamLifecycleOptions lifeCycleOptions, IProviderRuntime runtime, Serialization.DeepCopier deepCopier, Microsoft.Extensions.Logging.ILogger logger) { } public bool IsRewindable { get { throw null; } } public string Name { get { throw null; } } public static Orleans.Streams.IStreamProvider Create(System.IServiceProvider services, string name) { throw null; } public System.Threading.Tasks.Task ExecuteCommand(int command, object arg) { throw null; } public Orleans.Streams.IAsyncStream GetStream(Runtime.StreamId streamId) { throw null; } public Orleans.Streams.Core.IStreamSubscriptionManager GetStreamSubscriptionManager() { throw null; } public void Participate(ILifecycleObservable lifecycle) { } public static ILifecycleParticipant ParticipateIn(System.IServiceProvider serviceProvider, string name) where TLifecycle : ILifecycleObservable { throw null; } } public enum PersistentStreamProviderCommand { None = 0, StartAgents = 1, StopAgents = 2, GetAgentsState = 3, GetNumberRunningAgents = 4, AdapterCommandStartRange = 10000, AdapterCommandEndRange = 19999, AdapterFactoryCommandStartRange = 20000, AdapterFactoryCommandEndRange = 29999 } public partial class PooledQueueCache : IPurgeObservable { public PooledQueueCache(ICacheDataAdapter cacheDataAdapter, Microsoft.Extensions.Logging.ILogger logger, ICacheMonitor cacheMonitor, System.TimeSpan? cacheMonitorWriteInterval, System.TimeSpan? purgeMetadataInterval = null) { } public bool IsEmpty { get { throw null; } } public int ItemCount { get { throw null; } } public CachedMessage? Newest { get { throw null; } } public CachedMessage? Oldest { get { throw null; } } public void Add(System.Collections.Generic.List messages, System.DateTime dequeueTime) { } public object GetCursor(Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken sequenceToken) { throw null; } public void RemoveOldestMessage() { } public bool TryGetNextMessage(object cursorObj, out Orleans.Streams.IBatchContainer message) { throw null; } } public abstract partial class PooledResource : System.IDisposable where T : PooledResource, System.IDisposable { public IObjectPool Pool { set { } } public void Dispose() { } public virtual void OnResetState() { } public virtual void SignalPurge() { } } public partial class ReceiverMonitorDimensions { public ReceiverMonitorDimensions() { } public ReceiverMonitorDimensions(string queueId) { } public string QueueId { get { throw null; } set { } } } public static partial class SegmentBuilder { public static void Append(System.ArraySegment segment, ref int writerOffset, System.ReadOnlySpan bytes) { } public static void Append(System.ArraySegment segment, ref int writerOffset, string str) { } public static int CalculateAppendSize(System.ReadOnlySpan memory) { throw null; } public static int CalculateAppendSize(string str) { throw null; } public static System.ArraySegment ReadNextBytes(System.ArraySegment segment, ref int readerOffset) { throw null; } public static string ReadNextString(System.ArraySegment segment, ref int readerOffset) { throw null; } } public partial class SimpleQueueAdapterCache : Orleans.Streams.IQueueAdapterCache { public const string CacheSizePropertyName = "CacheSize"; public SimpleQueueAdapterCache(Configuration.SimpleQueueCacheOptions options, string providerName, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Orleans.Streams.IQueueCache CreateQueueCache(Orleans.Streams.QueueId queueId) { throw null; } } public partial class SimpleQueueCache : Orleans.Streams.IQueueCache, Orleans.Streams.IQueueFlowController { public SimpleQueueCache(int cacheSize, Microsoft.Extensions.Logging.ILogger logger) { } public int Size { get { throw null; } } public virtual void AddToCache(System.Collections.Generic.IList msgs) { } public virtual Orleans.Streams.IQueueCacheCursor GetCacheCursor(Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken token) { throw null; } public int GetMaxAddCount() { throw null; } public virtual bool IsUnderPressure() { throw null; } public virtual bool TryPurgeFromCache(out System.Collections.Generic.IList purgedItems) { throw null; } } public partial class SimpleQueueCacheCursor : Orleans.Streams.IQueueCacheCursor, System.IDisposable { public SimpleQueueCacheCursor(SimpleQueueCache cache, Runtime.StreamId streamId, Microsoft.Extensions.Logging.ILogger logger) { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public virtual Orleans.Streams.IBatchContainer GetCurrent(out System.Exception exception) { throw null; } public virtual bool MoveNext() { throw null; } public void RecordDeliveryFailure() { } public virtual void Refresh(Orleans.Streams.StreamSequenceToken sequenceToken) { } public override string ToString() { throw null; } } public partial class TimePurgePredicate { public TimePurgePredicate(System.TimeSpan minTimeInCache, System.TimeSpan maxRelativeMessageAge) { } public virtual bool ShouldPurgeFromTime(System.TimeSpan timeInCache, System.TimeSpan relativeAge) { throw null; } } } namespace Orleans.Providers.Streams.Generator { [GenerateSerializer] public sealed partial class GeneratedBatchContainer : Orleans.Streams.IBatchContainer { public GeneratedBatchContainer(Runtime.StreamId streamId, object payload, Common.EventSequenceTokenV2 token) { } [Id(2)] public System.DateTime EnqueueTimeUtc { get { throw null; } } [Id(3)] public object Payload { get { throw null; } } [Id(1)] public Common.EventSequenceTokenV2 RealToken { get { throw null; } } public Orleans.Streams.StreamSequenceToken SequenceToken { get { throw null; } } [Id(0)] public Runtime.StreamId StreamId { get { throw null; } } public System.Collections.Generic.IEnumerable> GetEvents() { throw null; } public bool ImportRequestContext() { throw null; } } [GenerateSerializer] public sealed partial class GeneratedEvent { [Id(0)] public GeneratedEventType EventType { get { throw null; } set { } } [Id(1)] public int[] Payload { get { throw null; } set { } } public enum GeneratedEventType { Fill = 0, Report = 1 } } public partial class GeneratorAdapterFactory : Orleans.Streams.IQueueAdapterFactory, Orleans.Streams.IQueueAdapter, Orleans.Streams.IQueueAdapterCache, IControllable { protected System.Func BlockPoolMonitorFactory; protected System.Func CacheMonitorFactory; protected System.Func ReceiverMonitorFactory; public GeneratorAdapterFactory(string providerName, Configuration.HashRingStreamQueueMapperOptions queueMapperOptions, Configuration.StreamStatisticOptions statisticOptions, System.IServiceProvider serviceProvider, Serialization.Serializer serializer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Orleans.Streams.StreamProviderDirection Direction { get { throw null; } } public bool IsRewindable { get { throw null; } } public string Name { get { throw null; } } public static GeneratorAdapterFactory Create(System.IServiceProvider services, string name) { throw null; } public System.Threading.Tasks.Task CreateAdapter() { throw null; } public Orleans.Streams.IQueueCache CreateQueueCache(Orleans.Streams.QueueId queueId) { throw null; } public Orleans.Streams.IQueueAdapterReceiver CreateReceiver(Orleans.Streams.QueueId queueId) { throw null; } public System.Threading.Tasks.Task ExecuteCommand(int command, object arg) { throw null; } public System.Threading.Tasks.Task GetDeliveryFailureHandler(Orleans.Streams.QueueId queueId) { throw null; } public Orleans.Streams.IQueueAdapterCache GetQueueAdapterCache() { throw null; } public Orleans.Streams.IStreamQueueMapper GetStreamQueueMapper() { throw null; } public void Init() { } public System.Threading.Tasks.Task QueueMessageBatchAsync(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, Orleans.Streams.StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } public partial class GeneratorPooledCache : Orleans.Streams.IQueueCache, Orleans.Streams.IQueueFlowController, Common.ICacheDataAdapter { public GeneratorPooledCache(Common.IObjectPool bufferPool, Microsoft.Extensions.Logging.ILogger logger, Serialization.Serializer serializer, Common.ICacheMonitor cacheMonitor, System.TimeSpan? monitorWriteInterval) { } public void AddToCache(System.Collections.Generic.IList messages) { } public Orleans.Streams.IBatchContainer GetBatchContainer(ref Common.CachedMessage cachedMessage) { throw null; } public Orleans.Streams.IQueueCacheCursor GetCacheCursor(Runtime.StreamId streamId, Orleans.Streams.StreamSequenceToken token) { throw null; } public int GetMaxAddCount() { throw null; } public Orleans.Streams.StreamSequenceToken GetSequenceToken(ref Common.CachedMessage cachedMessage) { throw null; } public bool IsUnderPressure() { throw null; } public bool TryPurgeFromCache(out System.Collections.Generic.IList purgedItems) { throw null; } } public partial interface IStreamGenerator { void Configure(System.IServiceProvider serviceProvider, IStreamGeneratorConfig generatorConfig); bool TryReadEvents(System.DateTime utcNow, int maxCount, out System.Collections.Generic.List events); } public partial interface IStreamGeneratorConfig { System.Type StreamGeneratorType { get; } } public enum StreamGeneratorCommand { Configure = 20000 } } namespace Orleans.Runtime { [Immutable] [GenerateSerializer] public readonly partial struct QualifiedStreamId : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable { [Id(1)] public readonly string ProviderName; [Id(0)] public readonly StreamId StreamId; public QualifiedStreamId(string providerName, StreamId streamId) { } public readonly int CompareTo(QualifiedStreamId other) { throw null; } public readonly bool Equals(QualifiedStreamId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static bool operator ==(QualifiedStreamId s1, QualifiedStreamId s2) { throw null; } public static implicit operator StreamId(QualifiedStreamId internalStreamId) { throw null; } public static bool operator !=(QualifiedStreamId s1, QualifiedStreamId s2) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } } [Immutable] [GenerateSerializer] public readonly partial struct StreamId : System.IEquatable, System.IComparable, System.Runtime.Serialization.ISerializable, System.ISpanFormattable, System.IFormattable { private readonly object _dummy; private readonly int _dummyPrimitive; public System.ReadOnlyMemory FullKey { get { throw null; } } public System.ReadOnlyMemory Key { get { throw null; } } public System.ReadOnlyMemory Namespace { get { throw null; } } public readonly int CompareTo(StreamId other) { throw null; } public static StreamId Create(Streams.IStreamIdentity streamIdentity) { throw null; } public static StreamId Create(System.ReadOnlySpan ns, System.ReadOnlySpan key) { throw null; } public static StreamId Create(string ns, System.Guid key) { throw null; } public static StreamId Create(string ns, long key) { throw null; } public static StreamId Create(string ns, string key) { throw null; } public readonly bool Equals(StreamId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly string GetKeyAsString() { throw null; } public readonly string? GetNamespace() { throw null; } public readonly void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static bool operator ==(StreamId s1, StreamId s2) { throw null; } public static bool operator !=(StreamId s1, StreamId s2) { throw null; } public static StreamId Parse(System.ReadOnlySpan value) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } } } namespace Orleans.Runtime.Providers { public partial interface IMessageDeliveryBackoffProvider : Orleans.Internal.IBackoffProvider { } public partial interface IQueueReaderBackoffProvider : Orleans.Internal.IBackoffProvider { } } namespace Orleans.Streams { public partial class AggregatedQueueFlowController : System.Collections.Generic.List, IQueueFlowController { public AggregatedQueueFlowController(int defaultMaxAddCount) { } public int GetMaxAddCount() { throw null; } } public static partial class AsyncBatchObservableExtensions { public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncBatchObservable obs, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onErrorAsync, System.Func onCompletedAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncBatchObservable obs, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onErrorAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncBatchObservable obs, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onCompletedAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncBatchObservable obs, System.Func>, System.Threading.Tasks.Task> onNextAsync) { throw null; } } public static partial class AsyncObservableExtensions { public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, StreamSequenceToken token) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onErrorAsync, StreamSequenceToken token) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onErrorAsync, System.Func onCompletedAsync, StreamSequenceToken token) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onErrorAsync, System.Func onCompletedAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onErrorAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onCompletedAsync, StreamSequenceToken token) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync, System.Func onCompletedAsync) { throw null; } public static System.Threading.Tasks.Task> SubscribeAsync(this IAsyncObservable obs, System.Func onNextAsync) { throw null; } } [GenerateSerializer] public sealed partial class BatchContainerBatch : IBatchContainerBatch, IBatchContainer { public BatchContainerBatch(System.Collections.Generic.List batchContainers) { } [Id(2)] public System.Collections.Generic.List BatchContainers { get { throw null; } } [Id(1)] public StreamSequenceToken SequenceToken { get { throw null; } } [Id(0)] public Runtime.StreamId StreamId { get { throw null; } } public System.Collections.Generic.IEnumerable> GetEvents() { throw null; } public bool ImportRequestContext() { throw null; } } [GenerateSerializer] public sealed partial class CacheFullException : Runtime.OrleansException { public CacheFullException() { } public CacheFullException(string message, System.Exception inner) { } public CacheFullException(string message) { } } public partial class ConstructorStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider { public const string Prefix = "ctor"; public static string FormatPattern(System.Type predicateType, string constructorArgument) { throw null; } public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate) { throw null; } } [GenerateSerializer] public partial class DataNotAvailableException : Runtime.OrleansException { public DataNotAvailableException() { } [System.Obsolete] protected DataNotAvailableException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public DataNotAvailableException(string message, System.Exception inner) { } public DataNotAvailableException(string message) { } } public sealed partial class DefaultStreamIdMapper : IStreamIdMapper { public const string Name = "default"; public Runtime.IdSpan GetGrainKeyId(Metadata.GrainBindings grainBindings, Runtime.StreamId streamId) { throw null; } } public partial class DefaultStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider { public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate) { throw null; } } public partial class DeploymentBasedQueueBalancer : QueueBalancerBase, IStreamQueueBalancer { public DeploymentBasedQueueBalancer(Runtime.ISiloStatusOracle siloStatusOracle, IDeploymentConfiguration deploymentConfig, Configuration.DeploymentBasedQueueBalancerOptions options, System.IServiceProvider services, Microsoft.Extensions.Logging.ILogger logger) : base(default!, default!) { } public static IStreamQueueBalancer Create(System.IServiceProvider services, string name, IDeploymentConfiguration deploymentConfiguration) { throw null; } public override System.Collections.Generic.IEnumerable GetMyQueues() { throw null; } public override System.Threading.Tasks.Task Initialize(IStreamQueueMapper queueMapper) { throw null; } protected override void OnClusterMembershipChange(System.Collections.Generic.HashSet activeSilos) { } } [GenerateSerializer] public sealed partial class FaultedSubscriptionException : Runtime.OrleansException { public FaultedSubscriptionException() { } public FaultedSubscriptionException(string message, System.Exception innerException) { } public FaultedSubscriptionException(string message) { } } public sealed partial class HashRingBasedPartitionedStreamQueueMapper : HashRingBasedStreamQueueMapper { public HashRingBasedPartitionedStreamQueueMapper(System.Collections.Generic.IReadOnlyList partitionIds, string queueNamePrefix) : base(default!, default!) { } public string QueueToPartition(QueueId queue) { throw null; } } public partial class HashRingBasedStreamQueueMapper : IConsistentRingStreamQueueMapper, IStreamQueueMapper { public HashRingBasedStreamQueueMapper(Configuration.HashRingStreamQueueMapperOptions options, string queueNamePrefix) { } public System.Collections.Generic.IEnumerable GetAllQueues() { throw null; } public QueueId GetQueueForStream(Runtime.StreamId streamId) { throw null; } public System.Collections.Generic.IEnumerable GetQueuesForRange(Runtime.IRingRange range) { throw null; } public override string ToString() { throw null; } } public partial interface IAsyncBatchObservable { System.Threading.Tasks.Task> SubscribeAsync(IAsyncBatchObserver observer, StreamSequenceToken? token); System.Threading.Tasks.Task> SubscribeAsync(IAsyncBatchObserver observer); } public partial interface IAsyncBatchObserver { System.Threading.Tasks.Task OnCompletedAsync(); System.Threading.Tasks.Task OnErrorAsync(System.Exception ex); System.Threading.Tasks.Task OnNextAsync(System.Collections.Generic.IList> items); } public partial interface IAsyncBatchProducer : IAsyncObserver { System.Threading.Tasks.Task OnNextBatchAsync(System.Collections.Generic.IEnumerable batch, StreamSequenceToken token = null); } public partial interface IAsyncObservable { System.Threading.Tasks.Task> SubscribeAsync(IAsyncObserver observer, StreamSequenceToken? token, string? filterData = null); System.Threading.Tasks.Task> SubscribeAsync(IAsyncObserver observer); } public partial interface IAsyncObserver { System.Threading.Tasks.Task OnCompletedAsync(); System.Threading.Tasks.Task OnErrorAsync(System.Exception ex); System.Threading.Tasks.Task OnNextAsync(T item, StreamSequenceToken? token = null); } public partial interface IAsyncStream { bool IsRewindable { get; } string ProviderName { get; } Runtime.StreamId StreamId { get; } } public partial interface IAsyncStream : IAsyncStream, System.IEquatable>, System.IComparable>, IAsyncObservable, IAsyncBatchObservable, IAsyncBatchProducer, IAsyncObserver { System.Threading.Tasks.Task>> GetAllSubscriptionHandles(); } public partial interface IBatchContainer { StreamSequenceToken SequenceToken { get; } Runtime.StreamId StreamId { get; } System.Collections.Generic.IEnumerable> GetEvents(); bool ImportRequestContext(); } public partial interface IBatchContainerBatch : IBatchContainer { System.Collections.Generic.List BatchContainers { get; } } public partial interface IConsistentRingStreamQueueMapper : IStreamQueueMapper { System.Collections.Generic.IEnumerable GetQueuesForRange(Runtime.IRingRange range); } public partial interface IDeploymentConfiguration { System.Collections.Generic.IList GetAllSiloNames(); } public static partial class ImplicitConsumerGrainExtensions { public static StreamIdentity GetImplicitStreamIdentity(this IGrainWithGuidCompoundKey grain) { throw null; } } public partial interface IQueueAdapter { StreamProviderDirection Direction { get; } bool IsRewindable { get; } string Name { get; } IQueueAdapterReceiver CreateReceiver(QueueId queueId); System.Threading.Tasks.Task QueueMessageBatchAsync(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext); } public partial interface IQueueAdapterCache { IQueueCache CreateQueueCache(QueueId queueId); } public partial interface IQueueAdapterFactory { System.Threading.Tasks.Task CreateAdapter(); System.Threading.Tasks.Task GetDeliveryFailureHandler(QueueId queueId); IQueueAdapterCache GetQueueAdapterCache(); IStreamQueueMapper GetStreamQueueMapper(); } public partial interface IQueueAdapterReceiver { System.Threading.Tasks.Task> GetQueueMessagesAsync(int maxCount); System.Threading.Tasks.Task Initialize(System.TimeSpan timeout); System.Threading.Tasks.Task MessagesDeliveredAsync(System.Collections.Generic.IList messages); System.Threading.Tasks.Task Shutdown(System.TimeSpan timeout); } public partial interface IQueueCache : IQueueFlowController { void AddToCache(System.Collections.Generic.IList messages); IQueueCacheCursor GetCacheCursor(Runtime.StreamId streamId, StreamSequenceToken token); bool IsUnderPressure(); bool TryPurgeFromCache(out System.Collections.Generic.IList purgedItems); } public partial interface IQueueCacheCursor : System.IDisposable { IBatchContainer GetCurrent(out System.Exception exception); bool MoveNext(); void RecordDeliveryFailure(); void Refresh(StreamSequenceToken token); } public partial interface IQueueDataAdapter { TQueueMessage ToQueueMessage(Runtime.StreamId streamId, System.Collections.Generic.IEnumerable events, StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext); } public partial interface IQueueDataAdapter : IQueueDataAdapter { TMessageBatch FromQueueMessage(TQueueMessage queueMessage, long sequenceId); } public partial interface IQueueFlowController { int GetMaxAddCount(); } public partial interface IStreamFailureHandler { bool ShouldFaultSubsriptionOnError { get; } System.Threading.Tasks.Task OnDeliveryFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamIdentity, StreamSequenceToken sequenceToken); System.Threading.Tasks.Task OnSubscriptionFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamIdentity, StreamSequenceToken sequenceToken); } public partial interface IStreamIdentity { System.Guid Guid { get; } string Namespace { get; } } public partial interface IStreamIdMapper { Runtime.IdSpan GetGrainKeyId(Metadata.GrainBindings grainBindings, Runtime.StreamId streamId); } public partial interface IStreamNamespacePredicate { string PredicatePattern { get; } bool IsMatch(string streamNamespace); } public partial interface IStreamNamespacePredicateProvider { bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate); } public partial interface IStreamProvider { bool IsRewindable { get; } string Name { get; } IAsyncStream GetStream(Runtime.StreamId streamId); } public partial interface IStreamPubSub { System.Threading.Tasks.Task ConsumerCount(Runtime.QualifiedStreamId streamId); Runtime.GuidId CreateSubscriptionId(Runtime.QualifiedStreamId streamId, Runtime.GrainId streamConsumer); System.Threading.Tasks.Task FaultSubscription(Runtime.QualifiedStreamId streamId, Runtime.GuidId subscriptionId); System.Threading.Tasks.Task> GetAllSubscriptions(Runtime.QualifiedStreamId streamId, Runtime.GrainId streamConsumer = default); System.Threading.Tasks.Task ProducerCount(Runtime.QualifiedStreamId streamId); System.Threading.Tasks.Task RegisterConsumer(Runtime.GuidId subscriptionId, Runtime.QualifiedStreamId streamId, Runtime.GrainId streamConsumer, string filterData); System.Threading.Tasks.Task> RegisterProducer(Runtime.QualifiedStreamId streamId, Runtime.GrainId streamProducer); System.Threading.Tasks.Task UnregisterConsumer(Runtime.GuidId subscriptionId, Runtime.QualifiedStreamId streamId); System.Threading.Tasks.Task UnregisterProducer(Runtime.QualifiedStreamId streamId, Runtime.GrainId streamProducer); } public partial interface IStreamQueueBalanceListener { System.Threading.Tasks.Task QueueDistributionChangeNotification(); } public partial interface IStreamQueueBalancer { System.Collections.Generic.IEnumerable GetMyQueues(); System.Threading.Tasks.Task Initialize(IStreamQueueMapper queueMapper); System.Threading.Tasks.Task Shutdown(); bool SubscribeToQueueDistributionChangeEvents(IStreamQueueBalanceListener observer); bool UnSubscribeFromQueueDistributionChangeEvents(IStreamQueueBalanceListener observer); } public partial interface IStreamQueueCheckpointerFactory { System.Threading.Tasks.Task> Create(string partition); } public partial interface IStreamQueueCheckpointer { bool CheckpointExists { get; } System.Threading.Tasks.Task Load(); void Update(TCheckpoint offset, System.DateTime utcNow); } public partial interface IStreamQueueMapper { System.Collections.Generic.IEnumerable GetAllQueues(); QueueId GetQueueForStream(Runtime.StreamId streamId); } public partial class LeaseBasedQueueBalancer : QueueBalancerBase, IStreamQueueBalancer { public LeaseBasedQueueBalancer(string name, Configuration.LeaseBasedQueueBalancerOptions options, LeaseProviders.ILeaseProvider leaseProvider, System.IServiceProvider services, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.TimeProvider timeProvider) : base(default!, default!) { } public static IStreamQueueBalancer Create(System.IServiceProvider services, string name) { throw null; } public override System.Collections.Generic.IEnumerable GetMyQueues() { throw null; } public override System.Threading.Tasks.Task Initialize(IStreamQueueMapper queueMapper) { throw null; } protected override void OnClusterMembershipChange(System.Collections.Generic.HashSet activeSilos) { } public override System.Threading.Tasks.Task Shutdown() { throw null; } } public partial class LoadShedQueueFlowController : IQueueFlowController { internal LoadShedQueueFlowController() { } public static IQueueFlowController CreateAsPercentageOfCPU(int loadSheddingLimit, Configuration.LoadSheddingOptions options, Statistics.IEnvironmentStatisticsProvider environmentStatisticsProvider) { throw null; } public static IQueueFlowController CreateAsPercentOfLoadSheddingLimit(Configuration.LoadSheddingOptions options, Statistics.IEnvironmentStatisticsProvider environmentStatisticsProvider, int percentOfSiloSheddingLimit = 95) { throw null; } public int GetMaxAddCount() { throw null; } } public partial class NoOpStreamDeliveryFailureHandler : IStreamFailureHandler { public NoOpStreamDeliveryFailureHandler() { } public NoOpStreamDeliveryFailureHandler(bool faultOnError) { } public bool ShouldFaultSubsriptionOnError { get { throw null; } } public System.Threading.Tasks.Task OnDeliveryFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamId, StreamSequenceToken sequenceToken) { throw null; } public System.Threading.Tasks.Task OnSubscriptionFailure(Runtime.GuidId subscriptionId, string streamProviderName, Runtime.StreamId streamId, StreamSequenceToken sequenceToken) { throw null; } } [GenerateSerializer] public sealed partial class ProviderStartException : Runtime.OrleansException { public ProviderStartException() { } public ProviderStartException(string message, System.Exception innerException) { } public ProviderStartException(string message) { } } [Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)] [GenerateSerializer] public sealed partial class PubSubSubscriptionState : System.IEquatable { [Newtonsoft.Json.JsonProperty] [Id(2)] public Runtime.GrainId Consumer; [Newtonsoft.Json.JsonProperty] [Id(3)] public string FilterData; [Newtonsoft.Json.JsonProperty] [Id(4)] public SubscriptionStates state; [Newtonsoft.Json.JsonProperty] [Id(1)] public Runtime.QualifiedStreamId Stream; [Newtonsoft.Json.JsonProperty] [Id(0)] public Runtime.GuidId SubscriptionId; public PubSubSubscriptionState(Runtime.GuidId subscriptionId, Runtime.QualifiedStreamId streamId, Runtime.GrainId streamConsumer) { } [Newtonsoft.Json.JsonIgnore] public bool IsFaulted { get { throw null; } } public void AddFilter(string filterData) { } public bool Equals(Runtime.GuidId subscriptionId) { throw null; } public bool Equals(PubSubSubscriptionState other) { throw null; } public override bool Equals(object obj) { throw null; } public void Fault() { } public override int GetHashCode() { throw null; } public static bool operator ==(PubSubSubscriptionState left, PubSubSubscriptionState right) { throw null; } public static bool operator !=(PubSubSubscriptionState left, PubSubSubscriptionState right) { throw null; } public override string ToString() { throw null; } public enum SubscriptionStates { Active = 0, Faulted = 1 } } public static partial class QueueAdapterConstants { public const int UNLIMITED_GET_QUEUE_MSG = -1; } public static partial class QueueAdapterExtensions { public static System.Threading.Tasks.Task QueueMessageAsync(this IQueueAdapter adapter, Runtime.StreamId streamId, T evt, StreamSequenceToken token, System.Collections.Generic.Dictionary requestContext) { throw null; } } public abstract partial class QueueBalancerBase : IStreamQueueBalancer { protected QueueBalancerBase(System.IServiceProvider sp, Microsoft.Extensions.Logging.ILogger logger) { } protected System.Threading.CancellationToken Cancellation { get { throw null; } } protected Microsoft.Extensions.Logging.ILogger Logger { get { throw null; } } protected Runtime.SiloAddress SiloAddress { get { throw null; } } public abstract System.Collections.Generic.IEnumerable GetMyQueues(); public virtual System.Threading.Tasks.Task Initialize(IStreamQueueMapper queueMapper) { throw null; } protected System.Threading.Tasks.Task NotifyListeners() { throw null; } protected abstract void OnClusterMembershipChange(System.Collections.Generic.HashSet activeSilos); public virtual System.Threading.Tasks.Task Shutdown() { throw null; } public bool SubscribeToQueueDistributionChangeEvents(IStreamQueueBalanceListener observer) { throw null; } public bool UnSubscribeFromQueueDistributionChangeEvents(IStreamQueueBalanceListener observer) { throw null; } } [GenerateSerializer] public sealed partial class QueueCacheMissException : DataNotAvailableException { public QueueCacheMissException() { } public QueueCacheMissException(StreamSequenceToken requested, StreamSequenceToken low, StreamSequenceToken high) { } public QueueCacheMissException(string message, System.Exception innerException) { } public QueueCacheMissException(string requested, string low, string high) { } public QueueCacheMissException(string message) { } [Id(2)] public string High { get { throw null; } } [Id(1)] public string Low { get { throw null; } } [Id(0)] public string Requested { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [Immutable] [GenerateSerializer] public readonly partial struct QueueId : System.IEquatable, System.IComparable, System.ISpanFormattable, System.IFormattable { private readonly object _dummy; private readonly int _dummyPrimitive; public bool IsDefault { get { throw null; } } public readonly int CompareTo(QueueId other) { throw null; } public readonly bool Equals(QueueId other) { throw null; } public override readonly bool Equals(object? obj) { throw null; } public override readonly int GetHashCode() { throw null; } public readonly uint GetNumericId() { throw null; } public static QueueId GetQueueId(string queueName, uint queueId, uint hash) { throw null; } public readonly string GetStringNamePrefix() { throw null; } public readonly uint GetUniformHashCode() { throw null; } public static bool operator ==(QueueId left, QueueId right) { throw null; } public static bool operator !=(QueueId left, QueueId right) { throw null; } readonly string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } readonly bool System.ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public override readonly string ToString() { throw null; } public readonly string ToStringWithHashCode() { throw null; } } public partial class RegexStreamNamespacePredicate : IStreamNamespacePredicate { public RegexStreamNamespacePredicate(string regex) { } public string PredicatePattern { get { throw null; } } public bool IsMatch(string streamNameSpace) { throw null; } } public partial class SequentialItem { public SequentialItem(T item, StreamSequenceToken token) { } public T Item { get { throw null; } } public StreamSequenceToken Token { get { throw null; } } } [GenerateSerializer] public sealed partial class StreamEventDeliveryFailureException : Runtime.OrleansException { public StreamEventDeliveryFailureException() { } [System.Obsolete] public StreamEventDeliveryFailureException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public StreamEventDeliveryFailureException(string message, System.Exception innerException) { } public StreamEventDeliveryFailureException(string message) { } } [GenerateSerializer] [Immutable] public sealed partial class StreamIdentity : IStreamIdentity { public StreamIdentity(System.Guid streamGuid, string streamNamespace) { } [Id(0)] public System.Guid Guid { get { throw null; } } [Id(1)] public string Namespace { get { throw null; } } public override bool Equals(object obj) { throw null; } public override int GetHashCode() { throw null; } } public partial class StreamPosition { public StreamPosition(Runtime.StreamId streamId, StreamSequenceToken sequenceToken) { } public StreamSequenceToken SequenceToken { get { throw null; } } public Runtime.StreamId StreamId { get { throw null; } } } public enum StreamProviderDirection { None = 0, ReadOnly = 1, WriteOnly = 2, ReadWrite = 3 } public static partial class StreamProviderExtensions { public static IAsyncStream GetStream(this IStreamProvider streamProvider, System.Guid id) { throw null; } public static IAsyncStream GetStream(this IStreamProvider streamProvider, long id) { throw null; } public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, System.Guid id) { throw null; } public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, long id) { throw null; } public static IAsyncStream GetStream(this IStreamProvider streamProvider, string ns, string id) { throw null; } public static IAsyncStream GetStream(this IStreamProvider streamProvider, string id) { throw null; } } public enum StreamPubSubType { ExplicitGrainBasedAndImplicit = 0, ExplicitGrainBasedOnly = 1, ImplicitOnly = 2 } [GenerateSerializer] public abstract partial class StreamSequenceToken : System.IEquatable, System.IComparable { public abstract int EventIndex { get; protected set; } public abstract long SequenceNumber { get; protected set; } public abstract int CompareTo(StreamSequenceToken other); public abstract bool Equals(StreamSequenceToken other); } public static partial class StreamSequenceTokenUtilities { public static bool Newer(this StreamSequenceToken me, StreamSequenceToken other) { throw null; } public static bool Older(this StreamSequenceToken me, StreamSequenceToken other) { throw null; } } public static partial class StreamSubscriptionHandleExtensions { public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func onNextAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func onNextAsync, System.Func onErrorAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func onNextAsync, System.Func onErrorAsync, System.Func onCompletedAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func onNextAsync, System.Func onCompletedAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func>, System.Threading.Tasks.Task> onNextAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onErrorAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onErrorAsync, System.Func onCompletedAsync, StreamSequenceToken token = null) { throw null; } public static System.Threading.Tasks.Task> ResumeAsync(this StreamSubscriptionHandle handle, System.Func>, System.Threading.Tasks.Task> onNextAsync, System.Func onCompletedAsync, StreamSequenceToken token = null) { throw null; } } public partial class StreamSubscriptionHandlerFactory : Core.IStreamSubscriptionHandleFactory { public StreamSubscriptionHandlerFactory(IStreamProvider streamProvider, Runtime.StreamId streamId, string providerName, Runtime.GuidId subscriptionId) { } public string ProviderName { get { throw null; } } public Runtime.StreamId StreamId { get { throw null; } } public Runtime.GuidId SubscriptionId { get { throw null; } } public StreamSubscriptionHandle Create() { throw null; } } [GenerateSerializer] public abstract partial class StreamSubscriptionHandle : System.IEquatable> { public abstract System.Guid HandleId { get; } public abstract string ProviderName { get; } public abstract Runtime.StreamId StreamId { get; } public abstract bool Equals(StreamSubscriptionHandle other); public abstract System.Threading.Tasks.Task> ResumeAsync(IAsyncBatchObserver observer, StreamSequenceToken token = null); public abstract System.Threading.Tasks.Task> ResumeAsync(IAsyncObserver observer, StreamSequenceToken token = null); public abstract System.Threading.Tasks.Task UnsubscribeAsync(); } } namespace Orleans.Streams.Core { public partial interface IStreamSubscriptionHandleFactory { string ProviderName { get; } Runtime.StreamId StreamId { get; } Runtime.GuidId SubscriptionId { get; } StreamSubscriptionHandle Create(); } public partial interface IStreamSubscriptionManager { System.Threading.Tasks.Task AddSubscription(string streamProviderName, Runtime.StreamId streamId, Runtime.GrainReference grainRef); System.Threading.Tasks.Task> GetSubscriptions(string streamProviderName, Runtime.StreamId streamId); System.Threading.Tasks.Task RemoveSubscription(string streamProviderName, Runtime.StreamId streamId, System.Guid subscriptionId); } public partial interface IStreamSubscriptionManagerAdmin { IStreamSubscriptionManager GetStreamSubscriptionManager(string managerType); } public partial interface IStreamSubscriptionManagerRetriever { IStreamSubscriptionManager GetStreamSubscriptionManager(); } public partial interface IStreamSubscriptionObserver { System.Threading.Tasks.Task OnSubscribed(IStreamSubscriptionHandleFactory handleFactory); } [GenerateSerializer] [Immutable] public sealed partial class StreamSubscription { public StreamSubscription(System.Guid subscriptionId, string streamProviderName, Runtime.StreamId streamId, Runtime.GrainId grainId) { } [Id(3)] public Runtime.GrainId GrainId { get { throw null; } } [Id(2)] public Runtime.StreamId StreamId { get { throw null; } } [Id(1)] public string StreamProviderName { get { throw null; } } [Id(0)] public System.Guid SubscriptionId { get { throw null; } } } public static partial class StreamSubscriptionManagerType { public const string ExplicitSubscribeOnly = "ExplicitSubscribeOnly"; } } namespace Orleans.Streams.Filtering { public partial interface IStreamFilter { bool ShouldDeliver(Runtime.StreamId streamId, object item, string filterData); } } namespace Orleans.Streams.PubSub { public static partial class StreamSubscriptionManagerExtensions { public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, Runtime.GrainId grainId) where TGrainInterface : IGrainWithGuidKey { throw null; } public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, System.Guid primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidCompoundKey { throw null; } public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, System.Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey { throw null; } public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, long primaryKey, string keyExtension, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerCompoundKey { throw null; } public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey { throw null; } public static System.Threading.Tasks.Task AddSubscription(this Core.IStreamSubscriptionManager manager, IGrainFactory grainFactory, Runtime.StreamId streamId, string streamProviderName, string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey { throw null; } public static bool TryGetStreamSubscriptionManager(this IStreamProvider streamProvider, out Core.IStreamSubscriptionManager manager) { throw null; } } } namespace OrleansCodeGen.Orleans.Hosting { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SimpleGeneratorOptions : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Hosting.SimpleGeneratorOptions instance) { } public global::Orleans.Hosting.SimpleGeneratorOptions ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Hosting.SimpleGeneratorOptions instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Hosting.SimpleGeneratorOptions value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SimpleGeneratorOptions : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public global::Orleans.Hosting.SimpleGeneratorOptions DeepCopy(global::Orleans.Hosting.SimpleGeneratorOptions original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Providers { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DefaultMemoryMessageBodySerializer : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_DefaultMemoryMessageBodySerializer(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Runtime.OnDeserializedCallbacks _hook0) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.DefaultMemoryMessageBodySerializer instance) { } public global::Orleans.Providers.DefaultMemoryMessageBodySerializer ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.DefaultMemoryMessageBodySerializer instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.DefaultMemoryMessageBodySerializer value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 instance) { } public Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A instance) { } public Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MemoryMessageBody : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_MemoryMessageBody(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.MemoryMessageBody instance) { } public global::Orleans.Providers.MemoryMessageBody ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.MemoryMessageBody instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.MemoryMessageBody value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_MemoryMessageData : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_MemoryMessageData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Providers.MemoryMessageData instance) { } public global::Orleans.Providers.MemoryMessageData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Providers.MemoryMessageData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.MemoryMessageData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 DeepCopy(Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A DeepCopy(Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_MemoryMessageBody : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_MemoryMessageBody(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Providers.MemoryMessageBody DeepCopy(global::Orleans.Providers.MemoryMessageBody original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_MemoryMessageData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_MemoryMessageData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Providers.MemoryMessageData DeepCopy(global::Orleans.Providers.MemoryMessageData result, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Providers.IMemoryStreamQueueGrain), "74D60341" })] public sealed partial class Invokable_IMemoryStreamQueueGrain_GrainReference_74D60341 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Providers.MemoryMessageData arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Providers.IMemoryStreamQueueGrain), "7A8F8C1A" })] public sealed partial class Invokable_IMemoryStreamQueueGrain_GrainReference_7A8F8C1A : global::Orleans.Runtime.TaskRequest> { public int arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Providers.Streams.Common { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventSequenceToken : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventSequenceToken(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.Streams.Common.EventSequenceToken instance) { } public global::Orleans.Providers.Streams.Common.EventSequenceToken ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.Streams.Common.EventSequenceToken instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.Streams.Common.EventSequenceToken value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_EventSequenceTokenV2 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_EventSequenceTokenV2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 instance) { } public global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventSequenceToken : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventSequenceToken(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void DeepCopy(global::Orleans.Providers.Streams.Common.EventSequenceToken input, global::Orleans.Providers.Streams.Common.EventSequenceToken output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Providers.Streams.Common.EventSequenceToken DeepCopy(global::Orleans.Providers.Streams.Common.EventSequenceToken original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_EventSequenceTokenV2 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_EventSequenceTokenV2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void DeepCopy(global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 input, global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 output, global::Orleans.Serialization.Cloning.CopyContext context) { } public global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 DeepCopy(global::Orleans.Providers.Streams.Common.EventSequenceTokenV2 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Providers.Streams.Generator { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GeneratedBatchContainer : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GeneratedBatchContainer(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer instance) { } public global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GeneratedEvent : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_GeneratedEvent(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Providers.Streams.Generator.GeneratedEvent instance) { } public global::Orleans.Providers.Streams.Generator.GeneratedEvent ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Providers.Streams.Generator.GeneratedEvent instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Providers.Streams.Generator.GeneratedEvent value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GeneratedBatchContainer : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_GeneratedBatchContainer(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer DeepCopy(global::Orleans.Providers.Streams.Generator.GeneratedBatchContainer original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GeneratedEvent : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_GeneratedEvent(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Providers.Streams.Generator.GeneratedEvent DeepCopy(global::Orleans.Providers.Streams.Generator.GeneratedEvent original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Runtime { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_QualifiedStreamId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_QualifiedStreamId(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.QualifiedStreamId instance) { } public global::Orleans.Runtime.QualifiedStreamId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.QualifiedStreamId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.QualifiedStreamId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Runtime.StreamId instance) { } public global::Orleans.Runtime.StreamId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Runtime.StreamId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Runtime.StreamId value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Streams { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_BatchContainerBatch : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_BatchContainerBatch(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.BatchContainerBatch instance) { } public global::Orleans.Streams.BatchContainerBatch ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.BatchContainerBatch instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.BatchContainerBatch value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CacheFullException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_CacheFullException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.CacheFullException instance) { } public global::Orleans.Streams.CacheFullException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.CacheFullException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.CacheFullException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_DataNotAvailableException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_DataNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.DataNotAvailableException instance) { } public global::Orleans.Streams.DataNotAvailableException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.DataNotAvailableException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.DataNotAvailableException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FaultedSubscriptionException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_FaultedSubscriptionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.FaultedSubscriptionException instance) { } public global::Orleans.Streams.FaultedSubscriptionException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.FaultedSubscriptionException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.FaultedSubscriptionException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ProviderStartException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_ProviderStartException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.ProviderStartException instance) { } public global::Orleans.Streams.ProviderStartException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.ProviderStartException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.ProviderStartException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_PubSubSubscriptionState : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_PubSubSubscriptionState(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.PubSubSubscriptionState instance) { } public global::Orleans.Streams.PubSubSubscriptionState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.PubSubSubscriptionState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.PubSubSubscriptionState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_QueueCacheMissException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_QueueCacheMissException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.QueueCacheMissException instance) { } public global::Orleans.Streams.QueueCacheMissException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.QueueCacheMissException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.QueueCacheMissException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_QueueId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Streams.QueueId instance) { } public global::Orleans.Streams.QueueId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Streams.QueueId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.QueueId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamEventDeliveryFailureException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_StreamEventDeliveryFailureException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.StreamEventDeliveryFailureException instance) { } public global::Orleans.Streams.StreamEventDeliveryFailureException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.StreamEventDeliveryFailureException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.StreamEventDeliveryFailureException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamIdentity : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_StreamIdentity(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.StreamIdentity instance) { } public global::Orleans.Streams.StreamIdentity ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.StreamIdentity instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.StreamIdentity value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamSequenceToken : global::Orleans.Serialization.Serializers.AbstractTypeSerializer { } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamSubscriptionHandle : global::Orleans.Serialization.Serializers.AbstractTypeSerializer> { } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_BatchContainerBatch : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_BatchContainerBatch(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Streams.BatchContainerBatch DeepCopy(global::Orleans.Streams.BatchContainerBatch original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_CacheFullException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_CacheFullException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_DataNotAvailableException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_DataNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FaultedSubscriptionException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_FaultedSubscriptionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ProviderStartException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_ProviderStartException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_PubSubSubscriptionState : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_PubSubSubscriptionState(global::Orleans.Serialization.Activators.IActivator _activator) { } public global::Orleans.Streams.PubSubSubscriptionState DeepCopy(global::Orleans.Streams.PubSubSubscriptionState original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_QueueCacheMissException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_QueueCacheMissException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Streams.QueueCacheMissException input, global::Orleans.Streams.QueueCacheMissException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_StreamEventDeliveryFailureException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_StreamEventDeliveryFailureException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_StreamSequenceToken : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Streams.StreamSequenceToken DeepCopy(global::Orleans.Streams.StreamSequenceToken original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streams.StreamSequenceToken input, global::Orleans.Streams.StreamSequenceToken output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_StreamSubscriptionHandle : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier>, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Streams.StreamSubscriptionHandle DeepCopy(global::Orleans.Streams.StreamSubscriptionHandle original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Streams.StreamSubscriptionHandle input, global::Orleans.Streams.StreamSubscriptionHandle output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } namespace OrleansCodeGen.Orleans.Streams.Core { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_StreamSubscription : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_StreamSubscription(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Streams.Core.StreamSubscription instance) { } public global::Orleans.Streams.Core.StreamSubscription ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Streams.Core.StreamSubscription instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Streams.Core.StreamSubscription value) where TBufferWriter : System.Buffers.IBufferWriter { } } } ================================================ FILE: src/api/Orleans.Streaming.Abstractions/Orleans.Streaming.Abstractions.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ ================================================ FILE: src/api/Orleans.TestingHost/Orleans.TestingHost.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class FaultInjectionStorageServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddFaultInjectionMemoryStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action> configureOptions = null, System.Action> configureFaultInjectionOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddFaultInjectionMemoryStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions, System.Action configureFaultInjectionOptions) { throw null; } } } namespace Orleans.TestingHost { public enum ConnectionTransportType { TcpSocket = 0, InMemory = 1, UnixSocket = 2 } public partial class FaultInjectionGrainStorage : Storage.IGrainStorage, ILifecycleParticipant { public FaultInjectionGrainStorage(Storage.IGrainStorage realStorageProvider, string name, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IGrainFactory grainFactory, FaultInjectionGrainStorageOptions faultInjectionOptions) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class FaultInjectionGrainStorageFactory { public static Storage.IGrainStorage Create(System.IServiceProvider services, string name, System.Func injectedGrainStorageFactory) { throw null; } } public partial class FaultInjectionGrainStorageOptions { public static System.TimeSpan DEFAULT_LATENCY; public System.TimeSpan Latency { get { throw null; } set { } } } public partial interface IClientBuilderConfigurator { void Configure(Microsoft.Extensions.Configuration.IConfiguration configuration, Hosting.IClientBuilder clientBuilder); } public partial interface IHostConfigurator { void Configure(Microsoft.Extensions.Hosting.IHostBuilder hostBuilder); } public partial class InProcessSiloHandle : SiloHandle { public override bool IsActive { get { throw null; } } public System.IServiceProvider ServiceProvider { get { throw null; } } public Microsoft.Extensions.Hosting.IHost SiloHost { get { throw null; } init { } } public static System.Threading.Tasks.Task CreateAsync(string siloName, Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action postConfigureHostBuilder = null) { throw null; } protected override void Dispose(bool disposing) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public override System.Threading.Tasks.Task StopSiloAsync(bool stopGracefully) { throw null; } public override System.Threading.Tasks.Task StopSiloAsync(System.Threading.CancellationToken ct) { throw null; } } public sealed partial class InProcessTestCluster : System.IDisposable, System.IAsyncDisposable { public InProcessTestCluster(InProcessTestClusterOptions options, ITestClusterPortAllocator portAllocator) { } public IClusterClient Client { get { throw null; } } public InProcessTestClusterOptions Options { get { throw null; } } public ITestClusterPortAllocator PortAllocator { get { throw null; } } public System.Collections.ObjectModel.ReadOnlyCollection Silos { get { throw null; } } public System.Threading.Tasks.Task CreateSiloAsync(InProcessTestSiloSpecificOptions siloOptions) { throw null; } public System.Threading.Tasks.Task DeployAsync() { throw null; } public void Dispose() { } public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public System.Collections.Generic.IEnumerable GetActiveSilos() { throw null; } public static System.TimeSpan GetLivenessStabilizationTime(Configuration.ClusterMembershipOptions clusterMembershipOptions, bool didKill = false) { throw null; } public string GetLog() { throw null; } public InProcessSiloHandle GetSiloForAddress(Runtime.SiloAddress siloAddress) { throw null; } public System.IServiceProvider GetSiloServiceProvider(Runtime.SiloAddress silo = null) { throw null; } public System.Threading.Tasks.Task InitializeClientAsync() { throw null; } public System.Threading.Tasks.Task KillClientAsync() { throw null; } public System.Threading.Tasks.Task KillSiloAsync(InProcessSiloHandle instance) { throw null; } public System.Threading.Tasks.Task RestartSiloAsync(InProcessSiloHandle instance) { throw null; } public System.Threading.Tasks.Task RestartStoppedSecondarySiloAsync(string siloName) { throw null; } public InProcessSiloHandle StartAdditionalSilo() { throw null; } [System.Obsolete("Use overload which does not have a 'startAdditionalSiloOnNewPort' parameter.")] public InProcessSiloHandle StartAdditionalSilo(bool startAdditionalSiloOnNewPort) { throw null; } public System.Threading.Tasks.Task StartAdditionalSiloAsync() { throw null; } public System.Threading.Tasks.Task StartAdditionalSiloAsync(bool startAdditionalSiloOnNewPort) { throw null; } [System.Obsolete("Use the overload which does not have a 'startSiloOnNewPort' parameter.")] public static System.Threading.Tasks.Task StartSiloAsync(InProcessTestCluster cluster, int instanceNumber, InProcessTestClusterOptions clusterOptions, System.Collections.Generic.IReadOnlyList configurationOverrides, bool startSiloOnNewPort) { throw null; } public static System.Threading.Tasks.Task StartSiloAsync(InProcessTestCluster cluster, int instanceNumber, InProcessTestClusterOptions clusterOptions) { throw null; } [System.Obsolete("Use the overload which does not have a 'startSiloOnNewPort' parameter.")] public System.Threading.Tasks.Task StartSiloAsync(int instanceNumber, InProcessTestClusterOptions clusterOptions, System.Collections.Generic.IReadOnlyList configurationOverrides, bool startSiloOnNewPort) { throw null; } public System.Threading.Tasks.Task StartSiloAsync(int instanceNumber, InProcessTestClusterOptions clusterOptions) { throw null; } [System.Obsolete("Use overload which does not have a 'startAdditionalSiloOnNewPort' parameter.")] public System.Threading.Tasks.Task> StartSilosAsync(int silosToStart, bool startAdditionalSiloOnNewPort) { throw null; } public System.Threading.Tasks.Task> StartSilosAsync(int silosToStart) { throw null; } public void StopAllSilos() { } public System.Threading.Tasks.Task StopAllSilosAsync() { throw null; } public System.Threading.Tasks.Task StopClusterClientAsync() { throw null; } public System.Threading.Tasks.Task StopSiloAsync(InProcessSiloHandle instance) { throw null; } public System.Threading.Tasks.Task StopSilosAsync() { throw null; } public System.Threading.Tasks.Task WaitForLivenessToStabilizeAsync(bool didKill = false) { throw null; } } public sealed partial class InProcessTestClusterBuilder { public InProcessTestClusterBuilder() { } public InProcessTestClusterBuilder(short initialSilosCount) { } public InProcessTestClusterOptions Options { get { throw null; } } public ITestClusterPortAllocator PortAllocator { get { throw null; } } public InProcessTestCluster Build() { throw null; } public InProcessTestClusterBuilder ConfigureClient(System.Action configureClientDelegate) { throw null; } public InProcessTestClusterBuilder ConfigureClientHost(System.Action configureHostDelegate) { throw null; } public InProcessTestClusterBuilder ConfigureHost(System.Action configureDelegate) { throw null; } public InProcessTestClusterBuilder ConfigureSilo(System.Action configureSiloDelegate) { throw null; } public InProcessTestClusterBuilder ConfigureSiloHost(System.Action configureSiloHostDelegate) { throw null; } public static string CreateClusterId() { throw null; } } public sealed partial class InProcessTestClusterOptions { public bool AssumeHomogenousSilosForTesting { get { throw null; } set { } } public System.Collections.Generic.List> ClientHostConfigurationDelegates { get { throw null; } } public string ClusterId { get { throw null; } set { } } public bool ConfigureFileLogging { get { throw null; } set { } } public bool GatewayPerSilo { get { throw null; } set { } } public bool InitializeClientOnDeploy { get { throw null; } set { } } public short InitialSilosCount { get { throw null; } set { } } public string ServiceId { get { throw null; } set { } } public System.Collections.Generic.List> SiloHostConfigurationDelegates { get { throw null; } } public bool UseRealEnvironmentStatistics { get { throw null; } set { } } } public sealed partial class InProcessTestSiloSpecificOptions { public int GatewayPort { get { throw null; } set { } } public string SiloName { get { throw null; } set { } } public int SiloPort { get { throw null; } set { } } public static InProcessTestSiloSpecificOptions Create(InProcessTestCluster testCluster, InProcessTestClusterOptions testClusterOptions, int instanceNumber, bool assignNewPort = false) { throw null; } } public partial interface ISiloConfigurator { void Configure(Hosting.ISiloBuilder siloBuilder); } public partial interface IStorageFaultGrain : IGrainWithStringKey, IGrain, Runtime.IAddressable { System.Threading.Tasks.Task AddFaultOnClear(Runtime.GrainId grainId, System.Exception exception); System.Threading.Tasks.Task AddFaultOnRead(Runtime.GrainId grainId, System.Exception exception); System.Threading.Tasks.Task AddFaultOnWrite(Runtime.GrainId grainId, System.Exception exception); System.Threading.Tasks.Task OnClear(Runtime.GrainId grainId); System.Threading.Tasks.Task OnRead(Runtime.GrainId grainId); System.Threading.Tasks.Task OnWrite(Runtime.GrainId grainId); } public partial interface ITestClusterPortAllocator : System.IDisposable { (int, int) AllocateConsecutivePortPairs(int numPorts); } [GenerateSerializer] public sealed partial class RandomlyInjectedInconsistentStateException : Storage.InconsistentStateException { } [GenerateSerializer] public sealed partial class RandomlyInjectedStorageException : System.Exception { } public static partial class SiloBuilderExtensions { public static Hosting.ISiloBuilder AddFaultInjectionMemoryStorage(this Hosting.ISiloBuilder builder, string name, System.Action> configureOptions = null, System.Action> configureFaultInjectionOptions = null) { throw null; } public static Hosting.ISiloBuilder AddFaultInjectionMemoryStorage(this Hosting.ISiloBuilder builder, string name, System.Action configureOptions, System.Action configureFaultInjectionOptions) { throw null; } } public abstract partial class SiloHandle : System.IDisposable, System.IAsyncDisposable { public TestClusterOptions ClusterOptions { get { throw null; } set { } } public Runtime.SiloAddress GatewayAddress { get { throw null; } set { } } public short InstanceNumber { get { throw null; } set { } } public abstract bool IsActive { get; } public string Name { get { throw null; } set { } } public Runtime.SiloAddress SiloAddress { get { throw null; } set { } } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public abstract System.Threading.Tasks.ValueTask DisposeAsync(); ~SiloHandle() { } public abstract System.Threading.Tasks.Task StopSiloAsync(bool stopGracefully); public abstract System.Threading.Tasks.Task StopSiloAsync(System.Threading.CancellationToken ct); public override string ToString() { throw null; } } public partial class StandaloneSiloHandle : SiloHandle { public const string ExecutablePathConfigKey = "ExecutablePath"; public StandaloneSiloHandle(string siloName, Microsoft.Extensions.Configuration.IConfiguration configuration, string executablePath) { } public override bool IsActive { get { throw null; } } public static System.Threading.Tasks.Task Create(string siloName, Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } public static System.Func> CreateDelegate(string executablePath) { throw null; } public static System.Func> CreateForAssembly(System.Reflection.Assembly assembly) { throw null; } protected override void Dispose(bool disposing) { } public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public override System.Threading.Tasks.Task StopSiloAsync(bool stopGracefully) { throw null; } public override System.Threading.Tasks.Task StopSiloAsync(System.Threading.CancellationToken ct) { throw null; } } public static partial class StandaloneSiloHost { public const string GatewayAddressLog = "#### GATEWAY "; public const string ShutdownCommand = "#### SHUTDOWN"; public const string SiloAddressLog = "#### SILO "; public const string StartedLog = "#### STARTED"; public static System.Threading.Tasks.Task Main(string[] args) { throw null; } } public partial class StorageFaultGrain : Grain, IStorageFaultGrain, IGrainWithStringKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task AddFaultOnClear(Runtime.GrainId grainId, System.Exception exception) { throw null; } public System.Threading.Tasks.Task AddFaultOnRead(Runtime.GrainId grainId, System.Exception exception) { throw null; } public System.Threading.Tasks.Task AddFaultOnWrite(Runtime.GrainId grainId, System.Exception exception) { throw null; } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task OnClear(Runtime.GrainId grainId) { throw null; } public System.Threading.Tasks.Task OnRead(Runtime.GrainId grainId) { throw null; } public System.Threading.Tasks.Task OnWrite(Runtime.GrainId grainId) { throw null; } } public partial class TestCluster : System.IDisposable, System.IAsyncDisposable { public TestCluster(TestClusterOptions options, System.Collections.Generic.IReadOnlyList configurationSources, ITestClusterPortAllocator portAllocator) { } public IClusterClient Client { get { throw null; } } public System.Collections.Generic.IReadOnlyList ConfigurationSources { get { throw null; } } public System.Func> CreateSiloAsync { set { } } public IGrainFactory GrainFactory { get { throw null; } } public TestClusterOptions Options { get { throw null; } } public ITestClusterPortAllocator PortAllocator { get { throw null; } } public SiloHandle Primary { get { throw null; } } public System.Collections.Generic.IReadOnlyList SecondarySilos { get { throw null; } } public System.IServiceProvider ServiceProvider { get { throw null; } } public System.Collections.ObjectModel.ReadOnlyCollection Silos { get { throw null; } } public System.Threading.Tasks.Task DefaultCreateSiloAsync(string siloName, Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } public void Deploy() { } public System.Threading.Tasks.Task DeployAsync() { throw null; } public void Dispose() { } public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public System.Collections.Generic.IEnumerable GetActiveSilos() { throw null; } public static System.TimeSpan GetLivenessStabilizationTime(Configuration.ClusterMembershipOptions clusterMembershipOptions, bool didKill = false) { throw null; } public string GetLog() { throw null; } public SiloHandle GetSiloForAddress(Runtime.SiloAddress siloAddress) { throw null; } public System.IServiceProvider GetSiloServiceProvider(Runtime.SiloAddress silo = null) { throw null; } public System.Threading.Tasks.Task InitializeClientAsync() { throw null; } public System.Threading.Tasks.Task KillClientAsync() { throw null; } public System.Threading.Tasks.Task KillSiloAsync(SiloHandle instance) { throw null; } public System.Threading.Tasks.Task RestartSiloAsync(SiloHandle instance) { throw null; } public System.Threading.Tasks.Task RestartStoppedSecondarySiloAsync(string siloName) { throw null; } public SiloHandle StartAdditionalSilo(bool startAdditionalSiloOnNewPort = false) { throw null; } public System.Threading.Tasks.Task StartAdditionalSiloAsync(bool startAdditionalSiloOnNewPort = false) { throw null; } public System.Threading.Tasks.Task> StartAdditionalSilosAsync(int silosToStart, bool startAdditionalSiloOnNewPort = false) { throw null; } public static System.Threading.Tasks.Task StartSiloAsync(TestCluster cluster, int instanceNumber, TestClusterOptions clusterOptions, System.Collections.Generic.IReadOnlyList configurationOverrides = null, bool startSiloOnNewPort = false) { throw null; } public System.Threading.Tasks.Task StartSiloAsync(int instanceNumber, TestClusterOptions clusterOptions, System.Collections.Generic.IReadOnlyList configurationOverrides = null, bool startSiloOnNewPort = false) { throw null; } public void StopAllSilos() { } public System.Threading.Tasks.Task StopAllSilosAsync() { throw null; } public System.Threading.Tasks.Task StopClusterClientAsync() { throw null; } public System.Threading.Tasks.Task StopPrimarySiloAsync() { throw null; } public System.Threading.Tasks.Task StopSecondarySilosAsync() { throw null; } public System.Threading.Tasks.Task StopSiloAsync(SiloHandle instance) { throw null; } public System.Threading.Tasks.Task WaitForLivenessToStabilizeAsync(bool didKill = false) { throw null; } } public partial class TestClusterBuilder { public TestClusterBuilder() { } public TestClusterBuilder(short initialSilosCount) { } public System.Func> CreateSiloAsync { set { } } public TestClusterOptions Options { get { throw null; } } public ITestClusterPortAllocator PortAllocator { get { throw null; } set { } } public System.Collections.Generic.Dictionary Properties { get { throw null; } } public TestClusterBuilder AddClientBuilderConfigurator() where T : new() { throw null; } public TestClusterBuilder AddSiloBuilderConfigurator() where T : new() { throw null; } public TestCluster Build() { throw null; } public TestClusterBuilder ConfigureBuilder(System.Action configureDelegate) { throw null; } public TestClusterBuilder ConfigureHostConfiguration(System.Action configureDelegate) { throw null; } public static string CreateClusterId() { throw null; } } public static partial class TestClusterExtensions { public static Microsoft.Extensions.Configuration.IConfiguration GetConfiguration(this Microsoft.Extensions.Hosting.IHostBuilder builder) { throw null; } public static string GetConfigurationValue(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string key) { throw null; } public static TestClusterOptions GetTestClusterOptions(this Microsoft.Extensions.Configuration.IConfiguration config) { throw null; } public static TestClusterOptions GetTestClusterOptions(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder) { throw null; } } public partial class TestClusterHostFactory { public static Microsoft.Extensions.Hosting.IHost CreateClusterClient(string hostName, Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action postConfigureHostBuilder = null) { throw null; } public static Microsoft.Extensions.Hosting.IHost CreateSiloHost(string hostName, Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action postConfigureHostBuilder = null) { throw null; } public static Microsoft.Extensions.Configuration.IConfiguration DeserializeConfiguration(string serializedSources) { throw null; } public static string SerializeConfiguration(Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } } public partial class TestClusterOptions { public string ApplicationBaseDirectory { get { throw null; } set { } } public bool AssumeHomogenousSilosForTesting { get { throw null; } set { } } public int BaseGatewayPort { get { throw null; } set { } } public int BaseSiloPort { get { throw null; } set { } } public System.Collections.Generic.List ClientBuilderConfiguratorTypes { get { throw null; } } public string ClusterId { get { throw null; } set { } } public bool ConfigureFileLogging { get { throw null; } set { } } public ConnectionTransportType ConnectionTransport { get { throw null; } set { } } public bool GatewayPerSilo { get { throw null; } set { } } public bool InitializeClientOnDeploy { get { throw null; } set { } } public short InitialSilosCount { get { throw null; } set { } } public string ServiceId { get { throw null; } set { } } public System.Collections.Generic.List SiloBuilderConfiguratorTypes { get { throw null; } } public bool UseRealEnvironmentStatistics { get { throw null; } set { } } public bool UseTestClusterMembership { get { throw null; } set { } } public System.Collections.Generic.Dictionary ToDictionary() { throw null; } } public partial class TestClusterPortAllocator : ITestClusterPortAllocator, System.IDisposable { public (int, int) AllocateConsecutivePortPairs(int numPorts = 5) { throw null; } public void Dispose() { } protected virtual void Dispose(bool disposing) { } ~TestClusterPortAllocator() { } } public partial class TestSiloSpecificOptions { public int GatewayPort { get { throw null; } set { } } public System.Net.IPEndPoint PrimarySiloEndPoint { get { throw null; } set { } } public string SiloName { get { throw null; } set { } } public int SiloPort { get { throw null; } set { } } public static TestSiloSpecificOptions Create(TestCluster testCluster, TestClusterOptions testClusterOptions, int instanceNumber, bool assignNewPort = false) { throw null; } public System.Collections.Generic.Dictionary ToDictionary() { throw null; } } } namespace Orleans.TestingHost.Logging { public partial class FileLogger : Microsoft.Extensions.Logging.ILogger { public FileLogger(FileLoggingOutput output, string category) { } public System.IDisposable BeginScope(TState state) { throw null; } public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { throw null; } public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception exception, System.Func formatter) { } } public partial class FileLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable { public FileLoggerProvider(string filePath) { } public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { throw null; } public void Dispose() { } } public static partial class FileLoggerProviderExtensions { public static Microsoft.Extensions.Logging.ILoggingBuilder AddFile(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string filePathName) { throw null; } } public partial class FileLoggingOutput : System.IDisposable { public FileLoggingOutput(string fileName) { } public void Dispose() { } public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception exception, System.Func formatter, string category) { } } } namespace Orleans.TestingHost.UnixSocketTransport { public static partial class UnixSocketConnectionExtensions { public static Hosting.IClientBuilder UseUnixSocketConnection(this Hosting.IClientBuilder clientBuilder) { throw null; } public static Hosting.ISiloBuilder UseUnixSocketConnection(this Hosting.ISiloBuilder siloBuilder) { throw null; } } public partial class UnixSocketConnectionOptions { public System.Func ConvertEndpointToPath { get { throw null; } set { } } } } namespace Orleans.TestingHost.Utils { public partial class AsyncResultHandle { public bool Continue { get { throw null; } set { } } public bool Done { get { throw null; } set { } } public System.Exception Exception { get { throw null; } set { } } public object Result { get { throw null; } set { } } public virtual void Reset() { } public System.Threading.Tasks.Task WaitFor(System.TimeSpan timeout, System.Func checkFlag) { throw null; } public System.Threading.Tasks.Task WaitForContinue(System.TimeSpan timeout) { throw null; } public System.Threading.Tasks.Task WaitForFinished(System.TimeSpan timeout) { throw null; } } public static partial class StorageEmulator { public static bool Exists { get { throw null; } } public static string Help() { throw null; } public static bool IsStarted() { throw null; } public static bool Start() { throw null; } public static bool Stop() { throw null; } public static bool TryStart() { throw null; } } public static partial class TestingUtils { public static void ConfigureDefaultLoggingBuilder(Microsoft.Extensions.Logging.ILoggingBuilder builder, string filePath) { } public static void ConfigureThreadPoolSettingsForStorageTests(int numDotNetPoolThreads = 200) { } public static Microsoft.Extensions.Logging.ILoggerFactory CreateDefaultLoggerFactory(string filePath, Microsoft.Extensions.Logging.LoggerFilterOptions filters) { throw null; } public static Microsoft.Extensions.Logging.ILoggerFactory CreateDefaultLoggerFactory(string filePath) { throw null; } public static string CreateTraceFileName(string nodeName, string clusterId) { throw null; } public static System.TimeSpan Multiply(System.TimeSpan time, double value) { throw null; } public static System.Threading.Tasks.Task WaitUntilAsync(System.Func> predicate, System.TimeSpan timeout, System.TimeSpan? delayOnFail = null) { throw null; } } } namespace OrleansCodeGen.Orleans.TestingHost { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_1150D526 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_1150D526(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_1150D526 instance) { } public Invokable_IStorageFaultGrain_GrainReference_1150D526 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_1150D526 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_1150D526 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_1A607A31 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_1A607A31(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_1A607A31 instance) { } public Invokable_IStorageFaultGrain_GrainReference_1A607A31 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_1A607A31 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_1A607A31 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_5D91E1AF : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_5D91E1AF(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_5D91E1AF instance) { } public Invokable_IStorageFaultGrain_GrainReference_5D91E1AF ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_5D91E1AF instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_5D91E1AF value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_B9852E6E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_B9852E6E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_B9852E6E instance) { } public Invokable_IStorageFaultGrain_GrainReference_B9852E6E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_B9852E6E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_B9852E6E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_C94BA77C : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_C94BA77C(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_C94BA77C instance) { } public Invokable_IStorageFaultGrain_GrainReference_C94BA77C ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_C94BA77C instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_C94BA77C value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IStorageFaultGrain_GrainReference_E8594820 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IStorageFaultGrain_GrainReference_E8594820(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IStorageFaultGrain_GrainReference_E8594820 instance) { } public Invokable_IStorageFaultGrain_GrainReference_E8594820 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IStorageFaultGrain_GrainReference_E8594820 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IStorageFaultGrain_GrainReference_E8594820 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RandomlyInjectedInconsistentStateException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_RandomlyInjectedInconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.TestingHost.RandomlyInjectedInconsistentStateException instance) { } public global::Orleans.TestingHost.RandomlyInjectedInconsistentStateException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.TestingHost.RandomlyInjectedInconsistentStateException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.TestingHost.RandomlyInjectedInconsistentStateException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RandomlyInjectedStorageException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_RandomlyInjectedStorageException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.TestingHost.RandomlyInjectedStorageException instance) { } public global::Orleans.TestingHost.RandomlyInjectedStorageException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.TestingHost.RandomlyInjectedStorageException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.TestingHost.RandomlyInjectedStorageException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_1150D526 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_1150D526 DeepCopy(Invokable_IStorageFaultGrain_GrainReference_1150D526 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_1A607A31 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_1A607A31 DeepCopy(Invokable_IStorageFaultGrain_GrainReference_1A607A31 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_5D91E1AF : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_5D91E1AF DeepCopy(Invokable_IStorageFaultGrain_GrainReference_5D91E1AF original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_B9852E6E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_B9852E6E DeepCopy(Invokable_IStorageFaultGrain_GrainReference_B9852E6E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_C94BA77C : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_C94BA77C DeepCopy(Invokable_IStorageFaultGrain_GrainReference_C94BA77C original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IStorageFaultGrain_GrainReference_E8594820 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IStorageFaultGrain_GrainReference_E8594820 DeepCopy(Invokable_IStorageFaultGrain_GrainReference_E8594820 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RandomlyInjectedInconsistentStateException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RandomlyInjectedInconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RandomlyInjectedStorageException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RandomlyInjectedStorageException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "1150D526" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_1150D526 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public System.Exception arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "1A607A31" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_1A607A31 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public System.Exception arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "5D91E1AF" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_5D91E1AF : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "B9852E6E" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_B9852E6E : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public System.Exception arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "C94BA77C" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_C94BA77C : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.TestingHost.IStorageFaultGrain), "E8594820" })] public sealed partial class Invokable_IStorageFaultGrain_GrainReference_E8594820 : global::Orleans.Runtime.TaskRequest { public global::Orleans.Runtime.GrainId arg0; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } ================================================ FILE: src/api/Orleans.Transactions/Orleans.Transactions.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans { public partial interface ITransactionClient { System.Threading.Tasks.Task RunTransaction(TransactionOption transactionOption, System.Func> transactionDelegate); System.Threading.Tasks.Task RunTransaction(TransactionOption transactionOption, System.Func transactionDelegate); } [InvokableCustomInitializer("SetTransactionOptions")] [InvokableBaseType(typeof(Runtime.GrainReference), typeof(System.Threading.Tasks.ValueTask), typeof(TransactionRequest))] [InvokableBaseType(typeof(Runtime.GrainReference), typeof(System.Threading.Tasks.ValueTask<>), typeof(TransactionRequest<>))] [InvokableBaseType(typeof(Runtime.GrainReference), typeof(System.Threading.Tasks.Task), typeof(TransactionTaskRequest))] [InvokableBaseType(typeof(Runtime.GrainReference), typeof(System.Threading.Tasks.Task<>), typeof(TransactionTaskRequest<>))] [System.AttributeUsage(System.AttributeTargets.Method)] public sealed partial class TransactionAttribute : System.Attribute { public TransactionAttribute(TransactionOption requirement) { } public TransactionAttribute(TransactionOptionAlias alias) { } [System.Obsolete("Use [ReadOnly] attribute instead.")] public bool ReadOnly { get { throw null; } set { } } public TransactionOption Requirement { get { throw null; } } } public enum TransactionOption { Suppress = 0, CreateOrJoin = 1, Create = 2, Join = 3, Supported = 4, NotAllowed = 5 } public enum TransactionOptionAlias { Required = 1, RequiresNew = 2, Mandatory = 3, Suppress = 4, Never = 5 } [SerializerTransparent] public abstract partial class TransactionRequest : TransactionRequestBase { protected TransactionRequest(Serialization.Serializer exceptionSerializer, System.IServiceProvider serviceProvider) : base(default!, default!) { } protected sealed override System.Threading.Tasks.ValueTask BaseInvoke() { throw null; } protected abstract System.Threading.Tasks.ValueTask InvokeInner(); } [GenerateSerializer] public abstract partial class TransactionRequestBase : Runtime.RequestBase, IOutgoingGrainCallFilter, Serialization.IOnDeserialized { [GeneratedActivatorConstructor] protected TransactionRequestBase(Serialization.Serializer exceptionSerializer, System.IServiceProvider serviceProvider) { } public bool IsAmbientTransactionSuppressed { get { throw null; } } public bool IsTransactionRequired { get { throw null; } } [Id(1)] public Transactions.TransactionInfo TransactionInfo { get { throw null; } set { } } [Id(0)] public TransactionOption TransactionOption { get { throw null; } set { } } protected abstract System.Threading.Tasks.ValueTask BaseInvoke(); public override void Dispose() { } public override System.Threading.Tasks.ValueTask Invoke() { throw null; } System.Threading.Tasks.Task IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext context) { throw null; } void Serialization.IOnDeserialized.OnDeserialized(Serialization.DeserializationContext context) { } protected void SetTransactionOptions(TransactionOption txOption) { } protected void SetTransactionOptions(TransactionOptionAlias txOption) { } } [SerializerTransparent] public abstract partial class TransactionRequest : TransactionRequestBase { protected TransactionRequest(Serialization.Serializer exceptionSerializer, System.IServiceProvider serviceProvider) : base(default!, default!) { } protected sealed override System.Threading.Tasks.ValueTask BaseInvoke() { throw null; } protected abstract System.Threading.Tasks.ValueTask InvokeInner(); } [GenerateSerializer] public sealed partial class TransactionResponse : Serialization.Invocation.Response { public override System.Exception Exception { get { throw null; } set { } } public Serialization.Invocation.Response InnerResponse { get { throw null; } } public override object Result { get { throw null; } set { } } [Id(1)] public Transactions.TransactionInfo TransactionInfo { get { throw null; } set { } } public static TransactionResponse Create(Serialization.Invocation.Response response, Transactions.TransactionInfo transactionInfo) { throw null; } public override void Dispose() { } public System.Exception GetException() { throw null; } public override T GetResult() { throw null; } } [SerializerTransparent] public abstract partial class TransactionTaskRequest : TransactionRequestBase { protected TransactionTaskRequest(Serialization.Serializer exceptionSerializer, System.IServiceProvider serviceProvider) : base(default!, default!) { } protected sealed override System.Threading.Tasks.ValueTask BaseInvoke() { throw null; } protected abstract System.Threading.Tasks.Task InvokeInner(); } [SerializerTransparent] public abstract partial class TransactionTaskRequest : TransactionRequestBase { protected TransactionTaskRequest(Serialization.Serializer exceptionSerializer, System.IServiceProvider serviceProvider) : base(default!, default!) { } protected sealed override System.Threading.Tasks.ValueTask BaseInvoke() { throw null; } protected abstract System.Threading.Tasks.Task InvokeInner(); } } namespace Orleans.Configuration { public partial class TransactionalStateOptions { public const int DefaultConfirmationRetryLimit = 3; public static System.TimeSpan DefaultLockTimeout; public const int DefaultMaxLockGroupSize = 20; public static System.TimeSpan DefaultRemoteTransactionPingFrequency; public System.TimeSpan ConfirmationRetryDelay { get { throw null; } set { } } public static int ConfirmationRetryLimit { get { throw null; } set { } } public static System.TimeSpan DefaultLockAcquireTimeout { get { throw null; } } public static System.TimeSpan DefaultPrepareTimeout { get { throw null; } } public System.TimeSpan LockAcquireTimeout { get { throw null; } set { } } public System.TimeSpan LockTimeout { get { throw null; } set { } } public int MaxLockGroupSize { get { throw null; } set { } } public System.TimeSpan PrepareTimeout { get { throw null; } set { } } public System.TimeSpan RemoteTransactionPingFrequency { get { throw null; } set { } } } } namespace Orleans.Hosting { public static partial class ClientBuilderExtensions { public static IClientBuilder UseTransactions(this IClientBuilder builder) { throw null; } } public static partial class SiloBuilderExtensions { public static ISiloBuilder UseTransactions(this ISiloBuilder builder) { throw null; } } public static partial class TransactionsServiceCollectionExtensions { } } namespace Orleans.Transactions { public partial class CausalClock { public CausalClock(IClock clock) { } public System.DateTime Merge(System.DateTime timestamp) { throw null; } public System.DateTime MergeUtcNow(System.DateTime timestamp) { throw null; } public System.DateTime UtcNow() { throw null; } } public partial class Clock : IClock { public System.DateTime UtcNow() { throw null; } } public partial class DefaultTransactionDataCopier : Abstractions.ITransactionDataCopier { public DefaultTransactionDataCopier(Serialization.DeepCopier deepCopier) { } public TData DeepCopy(TData original) { throw null; } } public partial interface IClock { System.DateTime UtcNow(); } public partial interface ITransactionAgent { System.Threading.Tasks.Task Abort(TransactionInfo transactionInfo); System.Threading.Tasks.Task<(TransactionalStatus Status, System.Exception exception)> Resolve(TransactionInfo transactionInfo); System.Threading.Tasks.Task StartTransaction(bool readOnly, System.TimeSpan timeout); } public partial interface ITransactionalStateStorageEvents where TState : class, new() { void Cancel(long sequenceNumber); void Collect(System.Guid transactionId); void Commit(System.Guid transactionId, System.DateTime timestamp, System.Collections.Generic.List writeResources); void Confirm(long sequenceNumber); void Prepare(long sequenceNumber, System.Guid transactionId, System.DateTime timestamp, ParticipantId transactionManager, TState state); void Read(System.DateTime timestamp); } public partial interface ITransactionOverloadDetector { bool IsOverloaded(); } public partial class NamedTransactionalStateStorageFactory : Abstractions.INamedTransactionalStateStorageFactory { [System.Obsolete("Use the NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor) constructor.")] public NamedTransactionalStateStorageFactory(Runtime.IGrainContextAccessor contextAccessor, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public NamedTransactionalStateStorageFactory(Runtime.IGrainContextAccessor contextAccessor) { } public Abstractions.ITransactionalStateStorage Create(string storageName, string stateName) where TState : class, new() { throw null; } } [GenerateSerializer] public sealed partial class OrleansBrokenTransactionLockException : OrleansTransactionTransientFailureException { public OrleansBrokenTransactionLockException(string transactionId, string situation, System.Exception innerException) : base(default!, default(string)!) { } public OrleansBrokenTransactionLockException(string transactionId, string situation) : base(default!, default(string)!) { } } [GenerateSerializer] public sealed partial class OrleansCascadingAbortException : OrleansTransactionTransientFailureException { public OrleansCascadingAbortException(string transactionId, System.Exception innerException) : base(default!, default(string)!) { } public OrleansCascadingAbortException(string transactionId, string dependentId) : base(default!, default(string)!) { } public OrleansCascadingAbortException(string transactionId) : base(default!, default(string)!) { } [Id(0)] public string DependentTransactionId { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class OrleansOrphanCallException : OrleansTransactionAbortedException { public OrleansOrphanCallException(string transactionId, int pendingCalls) : base(default!, default(string)!) { } } [GenerateSerializer] public sealed partial class OrleansReadOnlyViolatedException : OrleansTransactionAbortedException { public OrleansReadOnlyViolatedException(string transactionId) : base(default!, default(string)!) { } } [GenerateSerializer] public sealed partial class OrleansStartTransactionFailedException : OrleansTransactionException { public OrleansStartTransactionFailedException(System.Exception innerException) { } } [GenerateSerializer] public partial class OrleansTransactionAbortedException : OrleansTransactionException { [System.Obsolete] protected OrleansTransactionAbortedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public OrleansTransactionAbortedException(string transactionId, System.Exception innerException) { } public OrleansTransactionAbortedException(string transactionId, string msg, System.Exception innerException) { } public OrleansTransactionAbortedException(string transactionId, string msg) { } [Id(0)] public string TransactionId { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public partial class OrleansTransactionException : Runtime.OrleansException { public OrleansTransactionException() { } [System.Obsolete] protected OrleansTransactionException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public OrleansTransactionException(string message, System.Exception innerException) { } public OrleansTransactionException(string message) { } } [GenerateSerializer] public sealed partial class OrleansTransactionInDoubtException : OrleansTransactionException { public OrleansTransactionInDoubtException(string transactionId, System.Exception exc) { } public OrleansTransactionInDoubtException(string transactionId, string msg, System.Exception innerException) { } public OrleansTransactionInDoubtException(string transactionId) { } [Id(0)] public string TransactionId { get { throw null; } } [System.Obsolete] public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public sealed partial class OrleansTransactionLockUpgradeException : OrleansTransactionTransientFailureException { public OrleansTransactionLockUpgradeException(string transactionId) : base(default!, default(string)!) { } } [GenerateSerializer] public sealed partial class OrleansTransactionOverloadException : OrleansTransactionException { } [GenerateSerializer] public sealed partial class OrleansTransactionPrepareTimeoutException : OrleansTransactionTransientFailureException { public OrleansTransactionPrepareTimeoutException(string transactionId, System.Exception innerException) : base(default!, default(string)!) { } } [GenerateSerializer] public sealed partial class OrleansTransactionsDisabledException : OrleansTransactionException { } [GenerateSerializer] public sealed partial class OrleansTransactionServiceNotAvailableException : OrleansTransactionException { } [GenerateSerializer] public partial class OrleansTransactionTransientFailureException : OrleansTransactionAbortedException { [System.Obsolete] protected OrleansTransactionTransientFailureException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(default!, default(string)!) { } public OrleansTransactionTransientFailureException(string transactionId, string msg, System.Exception innerException) : base(default!, default(string)!) { } public OrleansTransactionTransientFailureException(string transactionId, string msg) : base(default!, default(string)!) { } } [GenerateSerializer] [Immutable] public readonly partial struct ParticipantId { private readonly object _dummy; private readonly int _dummyPrimitive; public static readonly System.Collections.Generic.IEqualityComparer Comparer; public ParticipantId(string name, Runtime.GrainReference reference, Role supportedRoles) { } [Id(0)] public string Name { get { throw null; } } [Id(1)] public Runtime.GrainReference Reference { get { throw null; } } [Id(2)] public Role SupportedRoles { get { throw null; } } public override readonly string ToString() { throw null; } [GenerateSerializer] [Immutable] public sealed partial class IdComparer : System.Collections.Generic.IEqualityComparer { public bool Equals(ParticipantId x, ParticipantId y) { throw null; } public int GetHashCode(ParticipantId obj) { throw null; } } [GenerateSerializer] [System.Flags] public enum Role { Resource = 1, Manager = 2, PriorityManager = 4 } } public static partial class ParticipantRoleExtensions { public static bool IsManager(this ParticipantId participant) { throw null; } public static bool IsPriorityManager(this ParticipantId participant) { throw null; } public static bool IsResource(this ParticipantId participant) { throw null; } public static System.Collections.Generic.IEnumerable> SelectManagers(this System.Collections.Generic.IEnumerable> participants) { throw null; } public static System.Collections.Generic.IEnumerable SelectPriorityManagers(this System.Collections.Generic.IEnumerable participants) { throw null; } public static System.Collections.Generic.IEnumerable> SelectResources(this System.Collections.Generic.IEnumerable> participants) { throw null; } public static bool SupportsRoles(this ParticipantId participant, ParticipantId.Role role) { throw null; } } public partial class TransactionAgentStatistics : Abstractions.ITransactionAgentStatistics { public long TransactionsFailed { get { throw null; } } public long TransactionsStarted { get { throw null; } } public long TransactionsSucceeded { get { throw null; } } public long TransactionsThrottled { get { throw null; } } public static Abstractions.ITransactionAgentStatistics Copy(Abstractions.ITransactionAgentStatistics initialStatistics) { throw null; } public void TrackTransactionFailed() { } public void TrackTransactionStarted() { } public void TrackTransactionSucceeded() { } public void TrackTransactionThrottled() { } } public partial class TransactionalResourceExtension : Abstractions.ITransactionalResourceExtension, Runtime.IGrainExtension, Runtime.IAddressable { public TransactionalResourceExtension(Runtime.IGrainContextAccessor contextAccessor) { } public System.Threading.Tasks.Task Abort(string resourceId, System.Guid transactionId) { throw null; } public System.Threading.Tasks.Task Cancel(string resourceId, System.Guid transactionId, System.DateTime timeStamp, TransactionalStatus status) { throw null; } public System.Threading.Tasks.Task CommitReadOnly(string resourceId, System.Guid transactionId, Abstractions.AccessCounter accessCount, System.DateTime timeStamp) { throw null; } public System.Threading.Tasks.Task Confirm(string resourceId, System.Guid transactionId, System.DateTime timeStamp) { throw null; } public System.Threading.Tasks.Task Prepare(string resourceId, System.Guid transactionId, Abstractions.AccessCounter accessCount, System.DateTime timeStamp, ParticipantId transactionManager) { throw null; } } public partial class TransactionalStateAttributeMapper : TransactionalStateAttributeMapper { protected override Abstractions.TransactionalStateConfiguration AttributeToConfig(Abstractions.TransactionalStateAttribute attribute) { throw null; } } public abstract partial class TransactionalStateAttributeMapper : Runtime.IAttributeToFactoryMapper where TAttribute : IFacetMetadata, Abstractions.ITransactionalStateConfiguration { protected abstract Abstractions.TransactionalStateConfiguration AttributeToConfig(TAttribute attribute); public Factory GetFactory(System.Reflection.ParameterInfo parameter, TAttribute attribute) { throw null; } } public partial class TransactionalStateFactory : Abstractions.ITransactionalStateFactory { public TransactionalStateFactory(Runtime.IGrainContextAccessor contextAccessor) { } public Abstractions.ITransactionalState Create(Abstractions.TransactionalStateConfiguration config) where TState : class, new() { throw null; } public static Newtonsoft.Json.JsonSerializerSettings GetJsonSerializerSettings(System.IServiceProvider serviceProvider) { throw null; } } [GenerateSerializer] public sealed partial class TransactionalStateRecord where TState : class, new() { [Id(1)] public long CommittedSequenceId { get { throw null; } set { } } [Id(0)] public TState CommittedState { get { throw null; } set { } } [Id(2)] public Abstractions.TransactionalStateMetaData Metadata { get { throw null; } set { } } [Id(3)] public System.Collections.Generic.List> PendingStates { get { throw null; } set { } } } public partial class TransactionalState : Abstractions.ITransactionalState, ILifecycleParticipant where TState : class, new() { public TransactionalState(Abstractions.TransactionalStateConfiguration transactionalStateConfiguration, Runtime.IGrainContextAccessor contextAccessor, Abstractions.ITransactionDataCopier copier, Runtime.IGrainRuntime grainRuntime, Microsoft.Extensions.Logging.ILogger> logger) { } public string CurrentTransactionId { get { throw null; } } public void Participate(Runtime.IGrainLifecycle lifecycle) { } public System.Threading.Tasks.Task PerformRead(System.Func operation) { throw null; } public System.Threading.Tasks.Task PerformUpdate(System.Func updateAction) { throw null; } } public enum TransactionalStatus { Ok = 0, PrepareTimeout = 1, CascadingAbort = 2, BrokenLock = 3, LockValidationFailed = 4, ParticipantResponseTimeout = 5, TMResponseTimeout = 6, StorageConflict = 7, PresumedAbort = 8, UnknownException = 9, AssertionFailed = 10, CommitFailure = 11 } public static partial class TransactionalStatusExtensions { public static OrleansTransactionException ConvertToUserException(this TransactionalStatus status, string transactionId, System.Exception exception) { throw null; } public static bool DefinitelyAborted(this TransactionalStatus status) { throw null; } } public partial class TransactionCommitterFactory : Abstractions.ITransactionCommitterFactory { public TransactionCommitterFactory(Runtime.IGrainContextAccessor contextAccessor) { } public Abstractions.ITransactionCommitter Create(Abstractions.ITransactionCommitterConfiguration config) where TService : class { throw null; } } public partial class TransactionCommitter : Abstractions.ITransactionCommitter, ILifecycleParticipant where TService : class { public TransactionCommitter(Abstractions.ITransactionCommitterConfiguration config, Runtime.IGrainContextAccessor contextAccessor, Abstractions.ITransactionDataCopier copier, Runtime.IGrainRuntime grainRuntime, Microsoft.Extensions.Logging.ILogger> logger) { } public System.Threading.Tasks.Task OnCommit(Abstractions.ITransactionCommitOperation operation) { throw null; } public void Participate(Runtime.IGrainLifecycle lifecycle) { } [GenerateSerializer] public sealed partial class OperationState { [Id(0)] public Abstractions.ITransactionCommitOperation Operation { get { throw null; } set { } } } } public static partial class TransactionContext { public static string CurrentTransactionId { get { throw null; } } public static TransactionInfo GetRequiredTransactionInfo() { throw null; } public static TransactionInfo GetTransactionInfo() { throw null; } } [GenerateSerializer] public sealed partial class TransactionInfo { public int PendingCalls; public TransactionInfo() { } public TransactionInfo(TransactionInfo other) { } public TransactionInfo(System.Guid id, System.DateTime timeStamp, System.DateTime priority, bool readOnly = false) { } public string Id { get { throw null; } } [Id(3)] public bool IsReadOnly { get { throw null; } } [Id(4)] public byte[] OriginalException { get { throw null; } set { } } [Id(5)] public System.Collections.Generic.Dictionary Participants { get { throw null; } } [Id(2)] public System.DateTime Priority { get { throw null; } set { } } [Id(1)] public System.DateTime TimeStamp { get { throw null; } set { } } [Id(0)] public System.Guid TransactionId { get { throw null; } } [Id(6)] public bool TryToCommit { get { throw null; } } public TransactionInfo Fork() { throw null; } public void Join(TransactionInfo x) { } public OrleansTransactionAbortedException MustAbort(Serialization.Serializer serializer) { throw null; } public void ReconcilePending() { } public void RecordException(System.Exception e, Serialization.Serializer sm) { } public void RecordRead(ParticipantId id, System.DateTime minTime) { } public void RecordWrite(ParticipantId id, System.DateTime minTime) { } public override string ToString() { throw null; } } public partial class TransactionManagerExtension : Abstractions.ITransactionManagerExtension, Runtime.IGrainExtension, Runtime.IAddressable { public TransactionManagerExtension(Runtime.IGrainContextAccessor contextAccessor) { } public System.Threading.Tasks.Task Ping(string resourceId, System.Guid transactionId, System.DateTime timeStamp, ParticipantId resource) { throw null; } public System.Threading.Tasks.Task PrepareAndCommit(string resourceId, System.Guid transactionId, Abstractions.AccessCounter accessCount, System.DateTime timeStamp, System.Collections.Generic.List writeResources, int totalResources) { throw null; } public System.Threading.Tasks.Task Prepared(string resourceId, System.Guid transactionId, System.DateTime timestamp, ParticipantId resource, TransactionalStatus status) { throw null; } } public partial class TransactionOverloadDetector : ITransactionOverloadDetector { public TransactionOverloadDetector(Abstractions.ITransactionAgentStatistics statistics, Microsoft.Extensions.Options.IOptions options) { } public bool IsOverloaded() { throw null; } } public partial class TransactionRateLoadSheddingOptions { public const double DEFAULT_LIMIT = 700D; public bool Enabled { get { throw null; } set { } } public double Limit { get { throw null; } set { } } } } namespace Orleans.Transactions.Abstractions { [GenerateSerializer] public partial struct AccessCounter { [Id(0)] public int Reads; [Id(1)] public int Writes; public static AccessCounter operator +(AccessCounter c1, AccessCounter c2) { throw null; } } [GenerateSerializer] [Immutable] public sealed partial class CommitRecord { [Id(0)] public System.DateTime Timestamp { get { throw null; } set { } } [Id(1)] public System.Collections.Generic.List WriteParticipants { get { throw null; } set { } } } public partial interface INamedTransactionalStateStorageFactory { ITransactionalStateStorage Create(string storageName, string stateName) where TState : class, new(); } public partial interface ITransactionAgentStatistics { long TransactionsFailed { get; } long TransactionsStarted { get; } long TransactionsSucceeded { get; } long TransactionsThrottled { get; } void TrackTransactionFailed(); void TrackTransactionStarted(); void TrackTransactionSucceeded(); void TrackTransactionThrottled(); } public partial interface ITransactionalResource { System.Threading.Tasks.Task Abort(System.Guid transactionId); System.Threading.Tasks.Task Cancel(System.Guid transactionId, System.DateTime timeStamp, TransactionalStatus status); System.Threading.Tasks.Task CommitReadOnly(System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp); System.Threading.Tasks.Task Confirm(System.Guid transactionId, System.DateTime timeStamp); System.Threading.Tasks.Task Prepare(System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp, ParticipantId transactionManager); } public partial interface ITransactionalResourceExtension : Runtime.IGrainExtension, Runtime.IAddressable { [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task Abort(string resourceId, System.Guid transactionId); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task Cancel(string resourceId, System.Guid transactionId, System.DateTime timeStamp, TransactionalStatus status); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task CommitReadOnly(string resourceId, System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task Confirm(string resourceId, System.Guid transactionId, System.DateTime timeStamp); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [Concurrency.OneWay] System.Threading.Tasks.Task Prepare(string resourceId, System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp, ParticipantId transactionManager); } public partial interface ITransactionalStateConfiguration { string StateName { get; } string StorageName { get; } } public partial interface ITransactionalStateFactory { ITransactionalState Create(TransactionalStateConfiguration config) where TState : class, new(); } public partial interface ITransactionalStateStorageFactory { ITransactionalStateStorage Create(string stateName, Runtime.IGrainContext context) where TState : class, new(); } public partial interface ITransactionalStateStorage where TState : class, new() { System.Threading.Tasks.Task> Load(); System.Threading.Tasks.Task Store(string expectedETag, TransactionalStateMetaData metadata, System.Collections.Generic.List> statesToPrepare, long? commitUpTo, long? abortAfter); } public partial interface ITransactionalState where TState : class, new() { System.Threading.Tasks.Task PerformRead(System.Func readFunction); System.Threading.Tasks.Task PerformUpdate(System.Func updateFunction); } public partial interface ITransactionCommitOperation where TService : class { System.Threading.Tasks.Task Commit(System.Guid transactionId, TService service); } public partial interface ITransactionCommitterConfiguration { string ServiceName { get; } string StorageName { get; } } public partial interface ITransactionCommitterFactory { ITransactionCommitter Create(ITransactionCommitterConfiguration config) where TService : class; } public partial interface ITransactionCommitter where TService : class { System.Threading.Tasks.Task OnCommit(ITransactionCommitOperation operation); } public partial interface ITransactionDataCopier { TData DeepCopy(TData original); } public partial interface ITransactionManager { System.Threading.Tasks.Task Ping(System.Guid transactionId, System.DateTime timeStamp, ParticipantId resource); System.Threading.Tasks.Task PrepareAndCommit(System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp, System.Collections.Generic.List writerResources, int totalParticipants); System.Threading.Tasks.Task Prepared(System.Guid transactionId, System.DateTime timeStamp, ParticipantId resource, TransactionalStatus status); } public partial interface ITransactionManagerExtension : Runtime.IGrainExtension, Runtime.IAddressable { [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [Concurrency.OneWay] System.Threading.Tasks.Task Ping(string resourceId, System.Guid transactionId, System.DateTime timeStamp, ParticipantId resource); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task PrepareAndCommit(string resourceId, System.Guid transactionId, AccessCounter accessCount, System.DateTime timeStamp, System.Collections.Generic.List writeResources, int totalParticipants); [Concurrency.AlwaysInterleave] [Transaction(TransactionOption.Suppress)] [Concurrency.OneWay] System.Threading.Tasks.Task Prepared(string resourceId, System.Guid transactionId, System.DateTime timestamp, ParticipantId resource, TransactionalStatus status); } [GenerateSerializer] [Immutable] public sealed partial class PendingTransactionState where TState : class, new() { [Id(0)] public long SequenceId { get { throw null; } set { } } [Id(4)] public TState State { get { throw null; } set { } } [Id(2)] public System.DateTime TimeStamp { get { throw null; } set { } } [Id(1)] public string TransactionId { get { throw null; } set { } } [Id(3)] public ParticipantId TransactionManager { get { throw null; } set { } } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public partial class TransactionalStateAttribute : System.Attribute, IFacetMetadata, ITransactionalStateConfiguration { public TransactionalStateAttribute(string stateName, string storageName = null) { } public string StateName { get { throw null; } } public string StorageName { get { throw null; } } } public partial class TransactionalStateConfiguration : ITransactionalStateConfiguration { public TransactionalStateConfiguration(ITransactionalStateConfiguration config, ParticipantId.Role supportedRoles = ParticipantId.Role.Resource | ParticipantId.Role.Manager) { } public string StateName { get { throw null; } } public string StorageName { get { throw null; } } public ParticipantId.Role SupportedRoles { get { throw null; } } } public static partial class TransactionalStateExtensions { public static System.Threading.Tasks.Task PerformUpdate(this ITransactionalState transactionalState, System.Action updateAction) where TState : class, new() { throw null; } } [GenerateSerializer] public sealed partial class TransactionalStateMetaData { [Id(1)] public System.Collections.Generic.Dictionary CommitRecords { get { throw null; } set { } } [Id(0)] public System.DateTime TimeStamp { get { throw null; } set { } } } [GenerateSerializer] [Immutable] public sealed partial class TransactionalStorageLoadResponse where TState : class, new() { public TransactionalStorageLoadResponse() { } public TransactionalStorageLoadResponse(string etag, TState committedState, long committedSequenceId, TransactionalStateMetaData metadata, System.Collections.Generic.IReadOnlyList> pendingStates) { } [Id(2)] public long CommittedSequenceId { get { throw null; } set { } } [Id(1)] public TState CommittedState { get { throw null; } set { } } [Id(0)] public string ETag { get { throw null; } set { } } [Id(3)] public TransactionalStateMetaData Metadata { get { throw null; } set { } } [Id(4)] public System.Collections.Generic.IReadOnlyList> PendingStates { get { throw null; } set { } } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public partial class TransactionCommitterAttribute : System.Attribute, IFacetMetadata, ITransactionCommitterConfiguration { public TransactionCommitterAttribute(string serviceName, string storageName = null) { } public string ServiceName { get { throw null; } } public string StorageName { get { throw null; } } } } namespace OrleansCodeGen.Orleans { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionRequestBase : global::Orleans.Serialization.Serializers.AbstractTypeSerializer { public Codec_TransactionRequestBase(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public override void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.TransactionRequestBase instance) { } public override void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.TransactionRequestBase instance) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionResponse : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_TransactionResponse(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.TransactionResponse instance) { } public global::Orleans.TransactionResponse ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.TransactionResponse instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.TransactionResponse value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionRequestBase : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_TransactionRequestBase(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.TransactionRequestBase DeepCopy(global::Orleans.TransactionRequestBase original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.TransactionRequestBase input, global::Orleans.TransactionRequestBase output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionResponse : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_TransactionResponse(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.TransactionResponse DeepCopy(global::Orleans.TransactionResponse original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Transactions { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansBrokenTransactionLockException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansBrokenTransactionLockException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansBrokenTransactionLockException instance) { } public global::Orleans.Transactions.OrleansBrokenTransactionLockException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansBrokenTransactionLockException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansBrokenTransactionLockException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansCascadingAbortException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansCascadingAbortException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansCascadingAbortException instance) { } public global::Orleans.Transactions.OrleansCascadingAbortException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansCascadingAbortException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansCascadingAbortException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansOrphanCallException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansOrphanCallException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansOrphanCallException instance) { } public global::Orleans.Transactions.OrleansOrphanCallException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansOrphanCallException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansOrphanCallException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansReadOnlyViolatedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansReadOnlyViolatedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansReadOnlyViolatedException instance) { } public global::Orleans.Transactions.OrleansReadOnlyViolatedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansReadOnlyViolatedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansReadOnlyViolatedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansStartTransactionFailedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansStartTransactionFailedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansStartTransactionFailedException instance) { } public global::Orleans.Transactions.OrleansStartTransactionFailedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansStartTransactionFailedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansStartTransactionFailedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionAbortedException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_OrleansTransactionAbortedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionAbortedException instance) { } public global::Orleans.Transactions.OrleansTransactionAbortedException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionAbortedException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionAbortedException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_OrleansTransactionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionException instance) { } public global::Orleans.Transactions.OrleansTransactionException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionInDoubtException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionInDoubtException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionInDoubtException instance) { } public global::Orleans.Transactions.OrleansTransactionInDoubtException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionInDoubtException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionInDoubtException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionLockUpgradeException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionLockUpgradeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionLockUpgradeException instance) { } public global::Orleans.Transactions.OrleansTransactionLockUpgradeException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionLockUpgradeException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionLockUpgradeException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionOverloadException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionOverloadException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionOverloadException instance) { } public global::Orleans.Transactions.OrleansTransactionOverloadException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionOverloadException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionOverloadException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionPrepareTimeoutException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionPrepareTimeoutException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionPrepareTimeoutException instance) { } public global::Orleans.Transactions.OrleansTransactionPrepareTimeoutException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionPrepareTimeoutException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionPrepareTimeoutException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionsDisabledException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionsDisabledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionsDisabledException instance) { } public global::Orleans.Transactions.OrleansTransactionsDisabledException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionsDisabledException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionsDisabledException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionServiceNotAvailableException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_OrleansTransactionServiceNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionServiceNotAvailableException instance) { } public global::Orleans.Transactions.OrleansTransactionServiceNotAvailableException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionServiceNotAvailableException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionServiceNotAvailableException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OrleansTransactionTransientFailureException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_OrleansTransactionTransientFailureException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.OrleansTransactionTransientFailureException instance) { } public global::Orleans.Transactions.OrleansTransactionTransientFailureException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.OrleansTransactionTransientFailureException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.OrleansTransactionTransientFailureException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ParticipantId : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public Codec_ParticipantId(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Transactions.ParticipantId instance) { } public global::Orleans.Transactions.ParticipantId ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Transactions.ParticipantId instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.ParticipantId value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionalStateRecord : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TState : class, new() { public Codec_TransactionalStateRecord(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TransactionalStateRecord instance) { } public global::Orleans.Transactions.TransactionalStateRecord ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TransactionalStateRecord instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TransactionalStateRecord value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionInfo : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_TransactionInfo(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TransactionInfo instance) { } public global::Orleans.Transactions.TransactionInfo ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TransactionInfo instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TransactionInfo value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansBrokenTransactionLockException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansBrokenTransactionLockException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansCascadingAbortException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansCascadingAbortException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Transactions.OrleansCascadingAbortException input, global::Orleans.Transactions.OrleansCascadingAbortException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansOrphanCallException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansOrphanCallException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansReadOnlyViolatedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansReadOnlyViolatedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansStartTransactionFailedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansStartTransactionFailedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionAbortedException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionAbortedException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Transactions.OrleansTransactionAbortedException input, global::Orleans.Transactions.OrleansTransactionAbortedException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionInDoubtException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionInDoubtException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } public override void DeepCopy(global::Orleans.Transactions.OrleansTransactionInDoubtException input, global::Orleans.Transactions.OrleansTransactionInDoubtException output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionLockUpgradeException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionLockUpgradeException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionOverloadException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionOverloadException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionPrepareTimeoutException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionPrepareTimeoutException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionsDisabledException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionsDisabledException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionServiceNotAvailableException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionServiceNotAvailableException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OrleansTransactionTransientFailureException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_OrleansTransactionTransientFailureException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionalStateRecord : global::Orleans.Serialization.Cloning.IDeepCopier>, global::Orleans.Serialization.Cloning.IDeepCopier where TState : class, new() { public Copier_TransactionalStateRecord(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TransactionalStateRecord DeepCopy(global::Orleans.Transactions.TransactionalStateRecord original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionInfo : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_TransactionInfo(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TransactionInfo DeepCopy(global::Orleans.Transactions.TransactionInfo original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } namespace OrleansCodeGen.Orleans.Transactions.Abstractions { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AccessCounter : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Transactions.Abstractions.AccessCounter instance) { } public global::Orleans.Transactions.Abstractions.AccessCounter ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Transactions.Abstractions.AccessCounter instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.Abstractions.AccessCounter value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CommitRecord : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_CommitRecord(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.Abstractions.CommitRecord instance) { } public global::Orleans.Transactions.Abstractions.CommitRecord ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.Abstractions.CommitRecord instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.Abstractions.CommitRecord value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE instance) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 instance) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 instance) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 instance) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 instance) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 instance) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB instance) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 instance) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_PendingTransactionState : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TState : class, new() { public Codec_PendingTransactionState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.Abstractions.PendingTransactionState instance) { } public global::Orleans.Transactions.Abstractions.PendingTransactionState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.Abstractions.PendingTransactionState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.Abstractions.PendingTransactionState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionalStateMetaData : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_TransactionalStateMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.Abstractions.TransactionalStateMetaData instance) { } public global::Orleans.Transactions.Abstractions.TransactionalStateMetaData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.Abstractions.TransactionalStateMetaData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.Abstractions.TransactionalStateMetaData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionalStorageLoadResponse : global::Orleans.Serialization.Codecs.IFieldCodec>, global::Orleans.Serialization.Codecs.IFieldCodec where TState : class, new() { public Codec_TransactionalStorageLoadResponse(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.Abstractions.TransactionalStorageLoadResponse instance) { } public global::Orleans.Transactions.Abstractions.TransactionalStorageLoadResponse ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.Abstractions.TransactionalStorageLoadResponse instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.Abstractions.TransactionalStorageLoadResponse value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE DeepCopy(Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 DeepCopy(Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 DeepCopy(Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 DeepCopy(Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 DeepCopy(Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 DeepCopy(Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB DeepCopy(Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 DeepCopy(Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_PendingTransactionState : global::Orleans.Serialization.Cloning.ShallowCopier> where TState : class, new() { } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionalStateMetaData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_TransactionalStateMetaData(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.Abstractions.TransactionalStateMetaData DeepCopy(global::Orleans.Transactions.Abstractions.TransactionalStateMetaData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_TransactionalStorageLoadResponse : global::Orleans.Serialization.Cloning.ShallowCopier> where TState : class, new() { } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), "1BB071FE" })] public sealed partial class Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public global::Orleans.Transactions.Abstractions.AccessCounter arg2; public System.DateTime arg3; public Invokable_ITransactionalResourceExtension_GrainReference_Ext_1BB071FE(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), "2ADCC608" })] public sealed partial class Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public global::Orleans.Transactions.Abstractions.AccessCounter arg2; public System.DateTime arg3; public global::Orleans.Transactions.ParticipantId arg4; public Invokable_ITransactionalResourceExtension_GrainReference_Ext_2ADCC608(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), "5DDDE6F0" })] public sealed partial class Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public System.DateTime arg2; public Invokable_ITransactionalResourceExtension_GrainReference_Ext_5DDDE6F0(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), "80028AB9" })] public sealed partial class Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public System.DateTime arg2; public global::Orleans.Transactions.TransactionalStatus arg3; public Invokable_ITransactionalResourceExtension_GrainReference_Ext_80028AB9(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionalResourceExtension), "BD051D23" })] public sealed partial class Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public Invokable_ITransactionalResourceExtension_GrainReference_Ext_BD051D23(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), "12BEFA17" })] public sealed partial class Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public System.DateTime arg2; public global::Orleans.Transactions.ParticipantId arg3; public global::Orleans.Transactions.TransactionalStatus arg4; public Invokable_ITransactionManagerExtension_GrainReference_Ext_12BEFA17(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), "AC4A9AEB" })] public sealed partial class Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public System.DateTime arg2; public global::Orleans.Transactions.ParticipantId arg3; public Invokable_ITransactionManagerExtension_GrainReference_Ext_AC4A9AEB(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), "Ext", typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), typeof(global::Orleans.Transactions.Abstractions.ITransactionManagerExtension), "B024EFA6" })] public sealed partial class Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6 : global::Orleans.TransactionTaskRequest { public string arg0; public System.Guid arg1; public global::Orleans.Transactions.Abstractions.AccessCounter arg2; public System.DateTime arg3; public System.Collections.Generic.List arg4; public int arg5; public Invokable_ITransactionManagerExtension_GrainReference_Ext_B024EFA6(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Transactions.ParticipantId { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_IdComparer : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.ParticipantId.IdComparer instance) { } public global::Orleans.Transactions.ParticipantId.IdComparer ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.ParticipantId.IdComparer instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.ParticipantId.IdComparer value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Role : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Transactions.ParticipantId.Role ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.ParticipantId.Role value) where TBufferWriter : System.Buffers.IBufferWriter { } } } namespace OrleansCodeGen.Orleans.Transactions.TransactionCommitter { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_OperationState : global::Orleans.Serialization.Codecs.IFieldCodec.OperationState>, global::Orleans.Serialization.Codecs.IFieldCodec where TService : class { public Codec_OperationState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TransactionCommitter.OperationState instance) { } public global::Orleans.Transactions.TransactionCommitter.OperationState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TransactionCommitter.OperationState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TransactionCommitter.OperationState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_OperationState : global::Orleans.Serialization.Cloning.IDeepCopier.OperationState>, global::Orleans.Serialization.Cloning.IDeepCopier where TService : class { public Copier_OperationState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TransactionCommitter.OperationState DeepCopy(global::Orleans.Transactions.TransactionCommitter.OperationState original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } } ================================================ FILE: src/api/Orleans.Transactions.TestKit.Base/Orleans.Transactions.TestKit.Base.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class TransactionFaultInjectionServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseControlledFaultInjectionTransactionState(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } } } namespace Orleans.Transactions.TestKit { [GenerateSerializer] public partial class AddAndThrowException : System.Exception { public AddAndThrowException() { } [System.Obsolete] protected AddAndThrowException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public AddAndThrowException(string message, System.Exception innerException) { } public AddAndThrowException(string message) { } } public abstract partial class ConsistencyTransactionTestRunner : TransactionTestRunnerBase { protected ConsistencyTransactionTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } protected abstract bool StorageAdaptorHasLimitedCommitSpace { get; } protected abstract bool StorageErrorInjectionActive { get; } public virtual System.Threading.Tasks.Task RandomizedConsistency(int numGrains, int scale, bool avoidDeadlocks, bool avoidTimeouts, Consistency.ReadWriteDetermination readwrite) { throw null; } } public partial class ControlledFaultInjectionTransactionTestRunner : TransactionTestRunnerBase { public ControlledFaultInjectionTransactionTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task MultiGrainWriteTransaction_FaultInjection(TransactionFaultInjectPhase injectionPhase, FaultInjectionType injectionType) { throw null; } public virtual System.Threading.Tasks.Task SingleGrainReadTransaction() { throw null; } public virtual System.Threading.Tasks.Task SingleGrainWriteTransaction() { throw null; } } public partial class CreateAttributionGrain : Grain, ICreateAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } public partial class CreateOrJoinAttributionGrain : Grain, ICreateOrJoinAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } public abstract partial class DisabledTransactionsTestRunner : TransactionTestRunnerBase { protected DisabledTransactionsTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual void MultiTransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { } public virtual void TransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { } } public partial class DoubleStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public DoubleStateTransactionalGrain(Abstractions.ITransactionalState data1, Abstractions.ITransactionalState data2, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } [GenerateSerializer] public partial class FailOperation : Abstractions.ITransactionCommitOperation { public FailOperation(string data) { } [Id(0)] public string Data { get { throw null; } set { } } public System.Threading.Tasks.Task Commit(System.Guid transactionId, IRemoteCommitService service) { throw null; } } public partial class FaultInjectionAzureTableTransactionStateStorageFactory : Abstractions.ITransactionalStateStorageFactory, ILifecycleParticipant { public FaultInjectionAzureTableTransactionStateStorageFactory(AzureStorage.AzureTableTransactionalStateStorageFactory factory) { } public static Abstractions.ITransactionalStateStorageFactory Create(System.IServiceProvider services, string name) { throw null; } public Abstractions.ITransactionalStateStorage Create(string stateName, Runtime.IGrainContext context) where TState : class, new() { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } } public partial class FaultInjectionAzureTableTransactionStateStorage : Abstractions.ITransactionalStateStorage where TState : class, new() { public FaultInjectionAzureTableTransactionStateStorage(ITransactionFaultInjector faultInjector, AzureStorage.AzureTableTransactionalStateStorage azureStateStorage) { } public System.Threading.Tasks.Task> Load() { throw null; } public System.Threading.Tasks.Task Store(string expectedETag, Abstractions.TransactionalStateMetaData metadata, System.Collections.Generic.List> statesToPrepare, long? commitUpTo, long? abortAfter) { throw null; } } [GenerateSerializer] public partial class FaultInjectionControl { [Id(0)] public TransactionFaultInjectPhase FaultInjectionPhase; [Id(1)] public FaultInjectionType FaultInjectionType; public void Reset() { } } [System.AttributeUsage(System.AttributeTargets.Parameter)] public partial class FaultInjectionTransactionalStateAttribute : System.Attribute, IFacetMetadata, IFaultInjectionTransactionalStateConfiguration, Abstractions.ITransactionalStateConfiguration { public FaultInjectionTransactionalStateAttribute(string stateName, string storageName = null) { } public string StateName { get { throw null; } } public string StorageName { get { throw null; } } } public partial class FaultInjectionTransactionalStateAttributeMapper : Runtime.IAttributeToFactoryMapper { public Factory GetFactory(System.Reflection.ParameterInfo parameter, FaultInjectionTransactionalStateAttribute attribute) { throw null; } } public partial class FaultInjectionTransactionalStateFactory : IFaultInjectionTransactionalStateFactory { public FaultInjectionTransactionalStateFactory(Runtime.IGrainContextAccessor contextAccessor) { } public IFaultInjectionTransactionalState Create(IFaultInjectionTransactionalStateConfiguration config) where TState : class, new() { throw null; } } public partial class FaultInjectionTransactionCoordinatorGrain : Grain, IFaultInjectionTransactionCoordinatorGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task MultiGrainAddAndFaultInjection(System.Collections.Generic.List grains, int numberToAdd, FaultInjectionControl faultInjection = null) { throw null; } public System.Threading.Tasks.Task MultiGrainSet(System.Collections.Generic.List grains, int newValue) { throw null; } } public enum FaultInjectionType { None = 0, Deactivation = 1, ExceptionBeforeStore = 2, ExceptionAfterStore = 3 } public abstract partial class GoldenPathTransactionTestRunner : TransactionTestRunnerBase { protected GoldenPathTransactionTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task MultiGrainReadWriteTransaction(string grainStates, int grainCount) { throw null; } public virtual System.Threading.Tasks.Task MultiGrainWriteTransaction(string grainStates, int grainCount) { throw null; } public virtual System.Threading.Tasks.Task MultiWriteToSingleGrainTransaction(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task RepeatGrainReadWriteTransaction(string grainStates, int grainCount) { throw null; } public virtual System.Threading.Tasks.Task RWRWTest(string grainStates, int grainCount) { throw null; } public virtual System.Threading.Tasks.Task SingleGrainReadTransaction(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task SingleGrainWriteTransaction(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task WRWRTest(string grainStates, int grainCount) { throw null; } } [GenerateSerializer] public partial class GrainData { [Id(0)] public int Value { get { throw null; } set { } } } public abstract partial class GrainFaultTransactionTestRunner : TransactionTestRunnerBase { public GrainFaultTransactionTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task AbortTransactionExceptionInnerExceptionOnlyContainsOneRootCauseException(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task AbortTransactionOnExceptions(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task AbortTransactionOnOrphanCalls(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task AbortTransactionOnReadOnlyViolatedException(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task MultiGrainAbortTransactionOnExceptions(string grainStates) { throw null; } } public partial interface IControlledTransactionFaultInjector : ITransactionFaultInjector { bool InjectAfterStore { get; set; } bool InjectBeforeStore { get; set; } } public partial interface ICreateAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface ICreateOrJoinAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface IFaultInjectionTransactionalStateConfiguration : Abstractions.ITransactionalStateConfiguration { } public partial interface IFaultInjectionTransactionalStateFactory { IFaultInjectionTransactionalState Create(IFaultInjectionTransactionalStateConfiguration config) where TState : class, new(); } public partial interface IFaultInjectionTransactionalState : Abstractions.ITransactionalState where TState : class, new() { FaultInjectionControl FaultInjectionControl { get; set; } } public partial interface IFaultInjectionTransactionCoordinatorGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainAddAndFaultInjection(System.Collections.Generic.List grains, int numberToAdd, FaultInjectionControl faultInjection = null); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainSet(System.Collections.Generic.List grains, int numberToAdd); } public partial interface IFaultInjectionTransactionTestGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Add(int numberToAdd, FaultInjectionControl faultInjectionControl = null); System.Threading.Tasks.Task Deactivate(); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Get(); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Set(int newValue); } public partial interface IJoinAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOptionAlias.Mandatory)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface INoAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface INotAllowedAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.NotAllowed)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface IRemoteCommitService { System.Threading.Tasks.Task Fail(System.Guid transactionId, string data); System.Threading.Tasks.Task Pass(System.Guid transactionId, string data); System.Threading.Tasks.Task Throw(System.Guid transactionId, string data); } public partial interface ISupportedAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Supported)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface ISuppressAttributionGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Suppress)] System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface ITestState { int state { get; set; } } public partial interface ITransactionAttributionGrain { System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers); } public partial interface ITransactionCommitterTestGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Join)] System.Threading.Tasks.Task Commit(Abstractions.ITransactionCommitOperation operation); } public partial interface ITransactionCoordinatorGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task AddAndThrow(ITransactionTestGrain grain, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainAdd(ITransactionCommitterTestGrain committer, Abstractions.ITransactionCommitOperation operation, System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainAdd(System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainAddAndThrow(System.Collections.Generic.List grain, System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainDouble(System.Collections.Generic.List grains); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainDoubleByRWRW(System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainDoubleByWRWR(System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainSet(System.Collections.Generic.List grains, int numberToAdd); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task MultiGrainSetBit(System.Collections.Generic.List grains, int bitIndex); [Transaction(TransactionOption.Create)] System.Threading.Tasks.Task OrphanCallTransaction(); [Transaction(TransactionOption.Create)] [Concurrency.ReadOnly] System.Threading.Tasks.Task UpdateViolated(ITransactionTestGrain grains, int numberToAdd); } public partial interface ITransactionFaultInjector { void AfterStore(); void BeforeStore(); } public partial interface ITransactionTestGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Add(int numberToAdd); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task AddAndThrow(int numberToAdd); System.Threading.Tasks.Task Deactivate(); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Get(); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Set(int newValue); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task SetAndThrow(int numberToSet); } public partial class JoinAttributionGrain : Grain, IJoinAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } public partial class MaxStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public MaxStateTransactionalGrain(Abstractions.ITransactionalStateFactory stateFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } public partial class MultiStateTransactionalGrainBaseClass : Grain, ITransactionTestGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { protected Abstractions.ITransactionalState[] dataArray; protected Microsoft.Extensions.Logging.ILogger logger; public MultiStateTransactionalGrainBaseClass(Abstractions.ITransactionalState[] dataArray, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task Add(int numberToAdd) { throw null; } public System.Threading.Tasks.Task AddAndThrow(int numberToAdd) { throw null; } public System.Threading.Tasks.Task Deactivate() { throw null; } public System.Threading.Tasks.Task Get() { throw null; } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task Set(int newValue) { throw null; } public System.Threading.Tasks.Task SetAndThrow(int numberToSet) { throw null; } } public partial class NoAttributionGrain : Grain, INoAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } public partial class NoStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public NoStateTransactionalGrain(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } public partial class NotAllowedAttributionGrain : Grain, INotAllowedAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class PassOperation : Abstractions.ITransactionCommitOperation { public PassOperation(string data) { } [Id(0)] public string Data { get { throw null; } set { } } public System.Threading.Tasks.Task Commit(System.Guid transactionId, IRemoteCommitService service) { throw null; } } public partial class RandomErrorInjector : ITransactionFaultInjector { public RandomErrorInjector(double injectionProbability) { } public void AfterStore() { } public void BeforeStore() { } [GenerateSerializer] public partial class RandomlyInjectedInconsistentStateException : Storage.InconsistentStateException { public RandomlyInjectedInconsistentStateException() { } [System.Obsolete] protected RandomlyInjectedInconsistentStateException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } [GenerateSerializer] public partial class RandomlyInjectedStorageException : System.Exception { public RandomlyInjectedStorageException() { } [System.Obsolete] protected RandomlyInjectedStorageException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } } public partial class RemoteCommitService : IRemoteCommitService { public RemoteCommitService(Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task Fail(System.Guid transactionId, string data) { throw null; } public System.Threading.Tasks.Task Pass(System.Guid transactionId, string data) { throw null; } public System.Threading.Tasks.Task Throw(System.Guid transactionId, string data) { throw null; } } public abstract partial class ScopedTransactionsTestRunner : TransactionTestRunnerBase { protected ScopedTransactionsTestRunner(IGrainFactory grainFactory, ITransactionClient transactionClient, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task CreateNestedTransactionScopeAndSetValueAndInnerFailAndAssert(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task CreateTransactionScopeAndSetValue(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task CreateTransactionScopeAndSetValueAndAssert(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task CreateTransactionScopeAndSetValueWithFailure(string grainStates) { throw null; } } public static partial class SiloBuilderExtensions { public static Hosting.ISiloBuilder AddFaultInjectionAzureTableTransactionalStateStorage(this Hosting.ISiloBuilder builder, System.Action configureOptions) { throw null; } public static Hosting.ISiloBuilder AddFaultInjectionAzureTableTransactionalStateStorage(this Hosting.ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static Hosting.ISiloBuilder UseControlledFaultInjectionTransactionState(this Hosting.ISiloBuilder builder) { throw null; } } [GenerateSerializer] public partial class SimpleAzureStorageException : Azure.RequestFailedException { public SimpleAzureStorageException(int status, string message, System.Exception innerException) : base(default(string)!) { } public SimpleAzureStorageException(int status, string message, string errorCode, System.Exception innerException) : base(default(string)!) { } public SimpleAzureStorageException(int status, string message) : base(default(string)!) { } [System.Obsolete("TThe serialization constructor pattern was made obsolete in modern versions of .NET. Use the other constructors instead.")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] protected SimpleAzureStorageException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(default(string)!) { } public SimpleAzureStorageException(string message, System.Exception innerException) : base(default(string)!) { } public SimpleAzureStorageException(string message) : base(default(string)!) { } } public partial class SimpleAzureStorageExceptionInjector : IControlledTransactionFaultInjector, ITransactionFaultInjector { public SimpleAzureStorageExceptionInjector(Microsoft.Extensions.Logging.ILogger logger) { } public bool InjectAfterStore { get { throw null; } set { } } public bool InjectBeforeStore { get { throw null; } set { } } public void AfterStore() { } public void BeforeStore() { } } public partial class SingleStateFaultInjectionTransactionalGrain : Grain, IFaultInjectionTransactionTestGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public SingleStateFaultInjectionTransactionalGrain(IFaultInjectionTransactionalState data, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task Add(int numberToAdd, FaultInjectionControl faultInjectionControl = null) { throw null; } public System.Threading.Tasks.Task Deactivate() { throw null; } public System.Threading.Tasks.Task Get() { throw null; } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task Set(int newValue) { throw null; } } public partial class SingleStateTransactionalGrain : MultiStateTransactionalGrainBaseClass { public SingleStateTransactionalGrain(Abstractions.ITransactionalState data, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } public partial class SkewedClock : IClock { public SkewedClock(System.TimeSpan minSkew, System.TimeSpan maxSkew) { } public System.DateTime UtcNow() { throw null; } } public partial class SkewedClockConfigurator : TestingHost.ISiloConfigurator { public void Configure(Hosting.ISiloBuilder hostBuilder) { } } public partial class SupportedAttributionGrain : Grain, ISupportedAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } public partial class SuppressAttributionGrain : Grain, ISuppressAttributionGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class ThrowOperation : Abstractions.ITransactionCommitOperation { public ThrowOperation(string data) { } [Id(0)] public string Data { get { throw null; } set { } } public System.Threading.Tasks.Task Commit(System.Guid transactionId, IRemoteCommitService service) { throw null; } } public abstract partial class TocFaultTransactionTestRunner : TransactionTestRunnerBase { protected TocFaultTransactionTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task MultiGrainWriteTransactionWithCommitException(string grainStates, int grainCount) { throw null; } public virtual System.Threading.Tasks.Task MultiGrainWriteTransactionWithCommitFailure(string grainStates, int grainCount) { throw null; } } public abstract partial class TocGoldenPathTestRunner : TransactionTestRunnerBase { protected TocGoldenPathTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task MultiGrainWriteTransaction(string grainStates, int grainCount) { throw null; } } public abstract partial class TransactionalStateStorageTestRunner : TransactionTestRunnerBase where TState : class, new() { protected System.Func, FluentAssertions.Equivalency.EquivalencyOptions> assertConfig; protected System.Func stateFactory; protected System.Func>> stateStorageFactory; protected TransactionalStateStorageTestRunner(System.Func>> stateStorageFactory, System.Func stateFactory, IGrainFactory grainFactory, System.Action testOutput, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> assertConfig = null) : base(default!, default!) { } public virtual System.Threading.Tasks.Task CancelMany(int count) { throw null; } public virtual System.Threading.Tasks.Task CancelOne() { throw null; } public virtual System.Threading.Tasks.Task ConfirmMany(int count, bool useTwoSteps) { throw null; } public virtual System.Threading.Tasks.Task ConfirmOne(bool useTwoSteps) { throw null; } public virtual System.Threading.Tasks.Task ConfirmOneAndCancelOne(bool useTwoSteps = false, bool reverseOrder = false) { throw null; } public virtual System.Threading.Tasks.Task FirstTime_Load_ShouldReturnEmptyLoadResponse() { throw null; } public virtual System.Threading.Tasks.Task GrowingBatch() { throw null; } public virtual System.Threading.Tasks.Task PrepareMany(int count) { throw null; } public virtual System.Threading.Tasks.Task ReplaceMany(int count) { throw null; } public virtual System.Threading.Tasks.Task ReplaceOne() { throw null; } public virtual System.Threading.Tasks.Task ShrinkingBatch() { throw null; } public virtual System.Threading.Tasks.Task StoreWithoutChanges() { throw null; } public virtual System.Threading.Tasks.Task WrongEtags() { throw null; } } public static partial class TransactionAttributionGrainExtensions { public static ITransactionAttributionGrain GetTransactionAttributionGrain(this IGrainFactory grainFactory, System.Guid id, TransactionOption? option = null) { throw null; } [GenerateSerializer] public partial class CreateAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ICreateAttributionGrain grain; public CreateAttributionGrain(ICreateAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class CreateOrJoinAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ICreateOrJoinAttributionGrain grain; public CreateOrJoinAttributionGrain(ICreateOrJoinAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class JoinAttributionGrain : ITransactionAttributionGrain { [Id(0)] public IJoinAttributionGrain grain; public JoinAttributionGrain(IJoinAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class NoAttributionGrain : ITransactionAttributionGrain { [Id(0)] public INoAttributionGrain grain; public NoAttributionGrain(INoAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class NotAllowedAttributionGrain : ITransactionAttributionGrain { [Id(0)] public INotAllowedAttributionGrain grain; public NotAllowedAttributionGrain(INotAllowedAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class SupportedAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ISupportedAttributionGrain grain; public SupportedAttributionGrain(ISupportedAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } [GenerateSerializer] public partial class SuppressAttributionGrain : ITransactionAttributionGrain { [Id(0)] public ISuppressAttributionGrain grain; public SuppressAttributionGrain(ISuppressAttributionGrain grain) { } public System.Threading.Tasks.Task[]> GetNestedTransactionIds(int tier, System.Collections.Generic.List[] tiers) { throw null; } } } public partial class TransactionCommitterTestGrain : Grain, ITransactionCommitterTestGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { protected Abstractions.ITransactionCommitter committer; protected Microsoft.Extensions.Logging.ILogger logger; public TransactionCommitterTestGrain(Abstractions.ITransactionCommitter committer, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task Commit(Abstractions.ITransactionCommitOperation operation) { throw null; } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } } public abstract partial class TransactionConcurrencyTestRunner : TransactionTestRunnerBase { protected TransactionConcurrencyTestRunner(IGrainFactory grainFactory, System.Action output) : base(default!, default!) { } public virtual System.Threading.Tasks.Task SingleSharedGrainTest(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task TransactionChainTest(string grainStates) { throw null; } public virtual System.Threading.Tasks.Task TransactionTreeTest(string grainStates) { throw null; } } [Concurrency.StatelessWorker] public partial class TransactionCoordinatorGrain : Grain, ITransactionCoordinatorGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { public System.Threading.Tasks.Task AddAndThrow(ITransactionTestGrain grain, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainAdd(ITransactionCommitterTestGrain committer, Abstractions.ITransactionCommitOperation operation, System.Collections.Generic.List grains, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainAdd(System.Collections.Generic.List grains, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainAddAndThrow(System.Collections.Generic.List throwGrains, System.Collections.Generic.List grains, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainDouble(System.Collections.Generic.List grains) { throw null; } public System.Threading.Tasks.Task MultiGrainDoubleByRWRW(System.Collections.Generic.List grains, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainDoubleByWRWR(System.Collections.Generic.List grains, int numberToAdd) { throw null; } public System.Threading.Tasks.Task MultiGrainSet(System.Collections.Generic.List grains, int newValue) { throw null; } public System.Threading.Tasks.Task MultiGrainSetBit(System.Collections.Generic.List grains, int bitIndex) { throw null; } public System.Threading.Tasks.Task OrphanCallTransaction() { throw null; } public System.Threading.Tasks.Task UpdateViolated(ITransactionTestGrain grain, int numberToAdd) { throw null; } } [GenerateSerializer] public enum TransactionFaultInjectPhase { None = 0, AfterCommitReadOnly = 1, AfterPrepare = 2, AfterPrepareAndCommit = 3, AfterAbort = 4, AfterPrepared = 5, AfterCancel = 6, AfterConfirm = 7, AfterPing = 8, BeforeConfirm = 9, BeforePrepare = 10, BeforePrepareAndCommit = 11 } public partial class TransactionRecoveryTestsRunner : TransactionTestRunnerBase { public TransactionRecoveryTestsRunner(TestingHost.TestCluster testCluster, System.Action testOutput) : base(default!, default!) { } protected void Log(string message) { } protected virtual System.Threading.Tasks.Task TransactionWillRecoverAfterRandomSiloFailure(string transactionTestGrainClassName, int concurrent, bool gracefulShutdown) { throw null; } public virtual System.Threading.Tasks.Task TransactionWillRecoverAfterRandomSiloGracefulShutdown(string transactionTestGrainClassName, int concurrent) { throw null; } public virtual System.Threading.Tasks.Task TransactionWillRecoverAfterRandomSiloUnGracefulShutdown(string transactionTestGrainClassName, int concurrent) { throw null; } } public static partial class TransactionTestConstants { public const string DoubleStateTransactionalGrain = "DoubleStateTransactionalGrain"; public const int MaxCoordinatedTransactions = 8; public const string MaxStateTransactionalGrain = "MaxStateTransactionalGrain"; public const string NoStateTransactionalGrain = "NoStateTransactionalGrain"; public const string RemoteCommitService = "RemoteCommitService"; public const string SingleStateTransactionalGrain = "SingleStateTransactionalGrain"; public const string TransactionStore = "TransactionStore"; } public partial class TransactionTestRunnerBase { protected readonly IGrainFactory grainFactory; protected readonly System.Action testOutput; protected TransactionTestRunnerBase(IGrainFactory grainFactory, System.Action testOutput) { } protected ITransactionTestGrain RandomTestGrain(string transactionTestGrainClassNames) { throw null; } protected TGrainInterface RandomTestGrain(string transactionTestGrainClassNames) where TGrainInterface : IGrainWithGuidKey { throw null; } protected virtual ITransactionTestGrain TestGrain(string transactionTestGrainClassName, System.Guid id) { throw null; } protected virtual TGrainInterface TestGrain(string transactionTestGrainClassName, System.Guid id) where TGrainInterface : IGrainWithGuidKey { throw null; } } } namespace Orleans.Transactions.TestKit.Consistency { [Concurrency.Reentrant] public partial class ConsistencyTestGrain : Grain, IConsistencyTestGrain, IGrainWithIntegerKey, IGrain, Runtime.IAddressable { protected Abstractions.ITransactionalState data; public ConsistencyTestGrain(Abstractions.ITransactionalState data, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task Run(ConsistencyTestOptions options, int depth, string stack, int maxgrain, System.DateTime stopAfter) { throw null; } [GenerateSerializer] public partial class State { [Id(1)] public int SeqNo; [Id(0)] public string WriterTx; } } public partial class ConsistencyTestHarness { public const string InitialTx = "initial"; public ConsistencyTestHarness(IGrainFactory grainFactory, int numGrains, int seed, bool avoidDeadlocks, bool avoidTimeouts, ReadWriteDetermination readWrite, bool tolerateUnknownExceptions) { } public int NumAborted { get { throw null; } } public void CheckConsistency(bool tolerateGenericTimeouts = false, bool tolerateUnknownExceptions = false) { } public System.Threading.Tasks.Task RunRandomTransactionSequence(int partition, int count, IGrainFactory grainFactory, System.Action output) { throw null; } } [GenerateSerializer] public partial class ConsistencyTestOptions { public const int MaxGrains = 100000; [Id(3)] public bool AvoidDeadlocks { get { throw null; } set { } } [Id(4)] public bool AvoidTimeouts { get { throw null; } set { } } [Id(6)] public long GrainOffset { get { throw null; } set { } } [Id(2)] public int MaxDepth { get { throw null; } set { } } [Id(1)] public int NumGrains { get { throw null; } set { } } [Id(0)] public int RandomSeed { get { throw null; } set { } } [Id(5)] public ReadWriteDetermination ReadWrite { get { throw null; } set { } } } public partial interface IConsistencyTestGrain : IGrainWithIntegerKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task Run(ConsistencyTestOptions options, int depth, string stack, int max, System.DateTime stopAfter); } [GenerateSerializer] public partial struct Observation { private object _dummy; private int _dummyPrimitive; [Id(3)] public string ExecutingTx { get { throw null; } set { } } [Id(0)] public int Grain { get { throw null; } set { } } [Id(1)] public int SeqNo { get { throw null; } set { } } [Id(2)] public string WriterTx { get { throw null; } set { } } } public enum ReadWriteDetermination { PerTransaction = 0, PerGrain = 1, PerAccess = 2 } [GenerateSerializer] public partial class UserAbort : System.Exception { public UserAbort() { } [System.Obsolete] protected UserAbort(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } } namespace Orleans.Transactions.TestKit.Correctnesss { [GenerateSerializer] public partial class BitArrayState { public BitArrayState() { } public BitArrayState(BitArrayState other) { } public int this[int index] { get { throw null; } set { } } [Newtonsoft.Json.JsonIgnore] public int Length { get { throw null; } } [Newtonsoft.Json.JsonIgnore] public int[] Value { get { throw null; } } public static BitArrayState Apply(BitArrayState left, BitArrayState right, System.Func op) { throw null; } protected bool Equals(BitArrayState other) { throw null; } public override bool Equals(object obj) { throw null; } public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } public override int GetHashCode() { throw null; } public static BitArrayState operator &(BitArrayState left, BitArrayState right) { throw null; } public static BitArrayState operator |(BitArrayState left, BitArrayState right) { throw null; } public static bool operator ==(BitArrayState left, BitArrayState right) { throw null; } public static BitArrayState operator ^(BitArrayState left, BitArrayState right) { throw null; } public static bool operator !=(BitArrayState left, BitArrayState right) { throw null; } public void Set(int index, bool value) { } public override string ToString() { throw null; } } [GrainType("txn-correctness-DoubleStateTransactionalGrain")] public partial class DoubleStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public DoubleStateTransactionalGrain(Abstractions.ITransactionalState data1, Abstractions.ITransactionalState data2, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } public partial interface ITransactionalBitArrayGrain : IGrainWithGuidKey, IGrain, Runtime.IAddressable { [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task> Get(); System.Threading.Tasks.Task Ping(); [Transaction(TransactionOption.CreateOrJoin)] System.Threading.Tasks.Task SetBit(int newValue); } [GrainType("txn-correctness-MaxStateTransactionalGrain")] public partial class MaxStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public MaxStateTransactionalGrain(Abstractions.ITransactionalStateFactory stateFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } [GrainType("txn-correctness-MultiStateTransactionalBitArrayGrain")] public partial class MultiStateTransactionalBitArrayGrain : Grain, ITransactionalBitArrayGrain, IGrainWithGuidKey, IGrain, Runtime.IAddressable { protected Abstractions.ITransactionalState[] dataArray; protected Microsoft.Extensions.Logging.ILogger logger; public MultiStateTransactionalBitArrayGrain(Abstractions.ITransactionalState[] dataArray, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public System.Threading.Tasks.Task> Get() { throw null; } public override System.Threading.Tasks.Task OnActivateAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task Ping() { throw null; } public System.Threading.Tasks.Task SetBit(int index) { throw null; } } [GrainType("txn-correctness-SingleStateTransactionalGrain")] public partial class SingleStateTransactionalGrain : MultiStateTransactionalBitArrayGrain { public SingleStateTransactionalGrain(Abstractions.ITransactionalState data, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base(default!, default!) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_AddAndThrowException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_AddAndThrowException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.AddAndThrowException instance) { } public global::Orleans.Transactions.TestKit.AddAndThrowException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.AddAndThrowException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.AddAndThrowException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FailOperation : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_FailOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.FailOperation instance) { } public global::Orleans.Transactions.TestKit.FailOperation ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.FailOperation instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.FailOperation value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_FaultInjectionControl : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_FaultInjectionControl(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.FaultInjectionControl instance) { } public global::Orleans.Transactions.TestKit.FaultInjectionControl ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.FaultInjectionControl instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.FaultInjectionControl value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_GrainData : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.GrainData instance) { } public global::Orleans.Transactions.TestKit.GrainData ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.GrainData instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.GrainData value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D instance) { } public Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 instance) { } public Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 instance) { } public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 instance) { } public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A instance) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C instance) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E instance) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D instance) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IJoinAttributionGrain_GrainReference_B1619F67 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IJoinAttributionGrain_GrainReference_B1619F67(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IJoinAttributionGrain_GrainReference_B1619F67 instance) { } public Invokable_IJoinAttributionGrain_GrainReference_B1619F67 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IJoinAttributionGrain_GrainReference_B1619F67 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IJoinAttributionGrain_GrainReference_B1619F67 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_INoAttributionGrain_GrainReference_BC7E3A79 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_INoAttributionGrain_GrainReference_BC7E3A79(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_INoAttributionGrain_GrainReference_BC7E3A79 instance) { } public Invokable_INoAttributionGrain_GrainReference_BC7E3A79 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_INoAttributionGrain_GrainReference_BC7E3A79 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_INoAttributionGrain_GrainReference_BC7E3A79 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_INotAllowedAttributionGrain_GrainReference_891D027E : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_INotAllowedAttributionGrain_GrainReference_891D027E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_INotAllowedAttributionGrain_GrainReference_891D027E instance) { } public Invokable_INotAllowedAttributionGrain_GrainReference_891D027E ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_INotAllowedAttributionGrain_GrainReference_891D027E instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_INotAllowedAttributionGrain_GrainReference_891D027E value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A instance) { } public Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ISuppressAttributionGrain_GrainReference_5A02311D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ISuppressAttributionGrain_GrainReference_5A02311D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ISuppressAttributionGrain_GrainReference_5A02311D instance) { } public Invokable_ISuppressAttributionGrain_GrainReference_5A02311D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ISuppressAttributionGrain_GrainReference_5A02311D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ISuppressAttributionGrain_GrainReference_5A02311D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 instance) { } public Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F instance) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_25B066B5 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionTestGrain_GrainReference_25B066B5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_25B066B5 instance) { } public Invokable_ITransactionTestGrain_GrainReference_25B066B5 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_25B066B5 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_25B066B5 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_35C87F81 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionTestGrain_GrainReference_35C87F81(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_35C87F81 instance) { } public Invokable_ITransactionTestGrain_GrainReference_35C87F81 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_35C87F81 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_35C87F81 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_35D6FD32 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_35D6FD32 instance) { } public Invokable_ITransactionTestGrain_GrainReference_35D6FD32 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_35D6FD32 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_35D6FD32 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_8DAA79AA : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionTestGrain_GrainReference_8DAA79AA(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_8DAA79AA instance) { } public Invokable_ITransactionTestGrain_GrainReference_8DAA79AA ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_8DAA79AA instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_8DAA79AA value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_CE9EC80B : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionTestGrain_GrainReference_CE9EC80B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_CE9EC80B instance) { } public Invokable_ITransactionTestGrain_GrainReference_CE9EC80B ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_CE9EC80B instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_CE9EC80B value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionTestGrain_GrainReference_DC07DAEA : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionTestGrain_GrainReference_DC07DAEA(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionTestGrain_GrainReference_DC07DAEA instance) { } public Invokable_ITransactionTestGrain_GrainReference_DC07DAEA ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionTestGrain_GrainReference_DC07DAEA instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionTestGrain_GrainReference_DC07DAEA value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_PassOperation : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_PassOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.PassOperation instance) { } public global::Orleans.Transactions.TestKit.PassOperation ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.PassOperation instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.PassOperation value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SimpleAzureStorageException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_SimpleAzureStorageException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.SimpleAzureStorageException instance) { } public global::Orleans.Transactions.TestKit.SimpleAzureStorageException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.SimpleAzureStorageException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.SimpleAzureStorageException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ThrowOperation : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_ThrowOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.ThrowOperation instance) { } public global::Orleans.Transactions.TestKit.ThrowOperation ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.ThrowOperation instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.ThrowOperation value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_TransactionFaultInjectPhase : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public global::Orleans.Transactions.TestKit.TransactionFaultInjectPhase ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionFaultInjectPhase value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_AddAndThrowException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_AddAndThrowException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FailOperation : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_FailOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public global::Orleans.Transactions.TestKit.FailOperation DeepCopy(global::Orleans.Transactions.TestKit.FailOperation original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.FailOperation input, global::Orleans.Transactions.TestKit.FailOperation output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_FaultInjectionControl : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Transactions.TestKit.FaultInjectionControl DeepCopy(global::Orleans.Transactions.TestKit.FaultInjectionControl original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.FaultInjectionControl input, global::Orleans.Transactions.TestKit.FaultInjectionControl output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_GrainData : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Transactions.TestKit.GrainData DeepCopy(global::Orleans.Transactions.TestKit.GrainData original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.GrainData input, global::Orleans.Transactions.TestKit.GrainData output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D DeepCopy(Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 DeepCopy(Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 DeepCopy(Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 DeepCopy(Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A DeepCopy(Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C DeepCopy(Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E DeepCopy(Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D DeepCopy(Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IJoinAttributionGrain_GrainReference_B1619F67 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IJoinAttributionGrain_GrainReference_B1619F67(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IJoinAttributionGrain_GrainReference_B1619F67 DeepCopy(Invokable_IJoinAttributionGrain_GrainReference_B1619F67 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_INoAttributionGrain_GrainReference_BC7E3A79 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_INoAttributionGrain_GrainReference_BC7E3A79(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public Invokable_INoAttributionGrain_GrainReference_BC7E3A79 DeepCopy(Invokable_INoAttributionGrain_GrainReference_BC7E3A79 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_INotAllowedAttributionGrain_GrainReference_891D027E : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_INotAllowedAttributionGrain_GrainReference_891D027E(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_INotAllowedAttributionGrain_GrainReference_891D027E DeepCopy(Invokable_INotAllowedAttributionGrain_GrainReference_891D027E original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A DeepCopy(Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ISuppressAttributionGrain_GrainReference_5A02311D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ISuppressAttributionGrain_GrainReference_5A02311D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ISuppressAttributionGrain_GrainReference_5A02311D DeepCopy(Invokable_ISuppressAttributionGrain_GrainReference_5A02311D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 DeepCopy(Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F DeepCopy(Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_25B066B5 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionTestGrain_GrainReference_25B066B5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionTestGrain_GrainReference_25B066B5 DeepCopy(Invokable_ITransactionTestGrain_GrainReference_25B066B5 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_35C87F81 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionTestGrain_GrainReference_35C87F81(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionTestGrain_GrainReference_35C87F81 DeepCopy(Invokable_ITransactionTestGrain_GrainReference_35C87F81 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_35D6FD32 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_ITransactionTestGrain_GrainReference_35D6FD32 DeepCopy(Invokable_ITransactionTestGrain_GrainReference_35D6FD32 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_8DAA79AA : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionTestGrain_GrainReference_8DAA79AA(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionTestGrain_GrainReference_8DAA79AA DeepCopy(Invokable_ITransactionTestGrain_GrainReference_8DAA79AA original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_CE9EC80B : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionTestGrain_GrainReference_CE9EC80B(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionTestGrain_GrainReference_CE9EC80B DeepCopy(Invokable_ITransactionTestGrain_GrainReference_CE9EC80B original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionTestGrain_GrainReference_DC07DAEA : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionTestGrain_GrainReference_DC07DAEA(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionTestGrain_GrainReference_DC07DAEA DeepCopy(Invokable_ITransactionTestGrain_GrainReference_DC07DAEA original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_PassOperation : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_PassOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public global::Orleans.Transactions.TestKit.PassOperation DeepCopy(global::Orleans.Transactions.TestKit.PassOperation original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.PassOperation input, global::Orleans.Transactions.TestKit.PassOperation output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SimpleAzureStorageException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_SimpleAzureStorageException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ThrowOperation : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_ThrowOperation(global::Orleans.Serialization.Activators.IActivator _activator) { } public global::Orleans.Transactions.TestKit.ThrowOperation DeepCopy(global::Orleans.Transactions.TestKit.ThrowOperation original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.ThrowOperation input, global::Orleans.Transactions.TestKit.ThrowOperation output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ICreateAttributionGrain), "3EFBDD5D" })] public sealed partial class Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_ICreateAttributionGrain_GrainReference_3EFBDD5D(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ICreateOrJoinAttributionGrain), "C9B8ECB8" })] public sealed partial class Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8 : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_ICreateOrJoinAttributionGrain_GrainReference_C9B8ECB8(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionCoordinatorGrain), "70FF7C60" })] public sealed partial class Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_70FF7C60(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionCoordinatorGrain), "E67D54A5" })] public sealed partial class Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public global::Orleans.Transactions.TestKit.FaultInjectionControl arg2; public Invokable_IFaultInjectionTransactionCoordinatorGrain_GrainReference_E67D54A5(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionTestGrain), "8389970A" })] public sealed partial class Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_8389970A(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionTestGrain), "A4CAE05C" })] public sealed partial class Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C : global::Orleans.TransactionTaskRequest { public int arg0; public global::Orleans.Transactions.TestKit.FaultInjectionControl arg1; public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A4CAE05C(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionTestGrain), "A6C1652E" })] public sealed partial class Invokable_IFaultInjectionTransactionTestGrain_GrainReference_A6C1652E : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IFaultInjectionTransactionTestGrain), "C752DF7D" })] public sealed partial class Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D : global::Orleans.TransactionTaskRequest { public Invokable_IFaultInjectionTransactionTestGrain_GrainReference_C752DF7D(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.IJoinAttributionGrain), "B1619F67" })] public sealed partial class Invokable_IJoinAttributionGrain_GrainReference_B1619F67 : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_IJoinAttributionGrain_GrainReference_B1619F67(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.INoAttributionGrain), "BC7E3A79" })] public sealed partial class Invokable_INoAttributionGrain_GrainReference_BC7E3A79 : global::Orleans.Runtime.TaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.INotAllowedAttributionGrain), "891D027E" })] public sealed partial class Invokable_INotAllowedAttributionGrain_GrainReference_891D027E : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_INotAllowedAttributionGrain_GrainReference_891D027E(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ISupportedAttributionGrain), "BC7DBC0A" })] public sealed partial class Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_ISupportedAttributionGrain_GrainReference_BC7DBC0A(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ISuppressAttributionGrain), "5A02311D" })] public sealed partial class Invokable_ISuppressAttributionGrain_GrainReference_5A02311D : global::Orleans.TransactionTaskRequest[]> { public int arg0; public System.Collections.Generic.List[] arg1; public Invokable_ISuppressAttributionGrain_GrainReference_5A02311D(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task[]> InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCommitterTestGrain), "C44BE2A4" })] public sealed partial class Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4 : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.Abstractions.ITransactionCommitOperation arg0; public Invokable_ITransactionCommitterTestGrain_GrainReference_C44BE2A4(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "2760260D" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public System.Collections.Generic.List arg1; public int arg2; public Invokable_ITransactionCoordinatorGrain_GrainReference_2760260D(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "3A6B9237" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_3A6B9237(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "485592B2" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2 : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.TestKit.ITransactionTestGrain arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_485592B2(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "5FC2E7A1" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public Invokable_ITransactionCoordinatorGrain_GrainReference_5FC2E7A1(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "5FF4F216" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_5FF4F216(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "78D54907" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_78D54907(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "8EE5E563" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563 : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.TestKit.ITransactionCommitterTestGrain arg0; public global::Orleans.Transactions.Abstractions.ITransactionCommitOperation arg1; public System.Collections.Generic.List arg2; public int arg3; public Invokable_ITransactionCoordinatorGrain_GrainReference_8EE5E563(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "9EFEA7F3" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3 : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_9EFEA7F3(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "B013DBF6" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6 : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.TestKit.ITransactionTestGrain arg0; public Invokable_ITransactionCoordinatorGrain_GrainReference_B013DBF6(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "B4376B4D" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D : global::Orleans.TransactionTaskRequest { public System.Collections.Generic.List arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_B4376B4D(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionCoordinatorGrain), "D3EF444F" })] public sealed partial class Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.TestKit.ITransactionTestGrain arg0; public int arg1; public Invokable_ITransactionCoordinatorGrain_GrainReference_D3EF444F(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "25B066B5" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_25B066B5 : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_ITransactionTestGrain_GrainReference_25B066B5(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "35C87F81" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_35C87F81 : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_ITransactionTestGrain_GrainReference_35C87F81(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "35D6FD32" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_35D6FD32 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "8DAA79AA" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_8DAA79AA : global::Orleans.TransactionTaskRequest { public Invokable_ITransactionTestGrain_GrainReference_8DAA79AA(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "CE9EC80B" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_CE9EC80B : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_ITransactionTestGrain_GrainReference_CE9EC80B(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.ITransactionTestGrain), "DC07DAEA" })] public sealed partial class Invokable_ITransactionTestGrain_GrainReference_DC07DAEA : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_ITransactionTestGrain_GrainReference_DC07DAEA(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit.Consistency { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_ConsistencyTestOptions : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_ConsistencyTestOptions(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions instance) { } public global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_IConsistencyTestGrain_GrainReference_2EB318CB : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_IConsistencyTestGrain_GrainReference_2EB318CB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_IConsistencyTestGrain_GrainReference_2EB318CB instance) { } public Invokable_IConsistencyTestGrain_GrainReference_2EB318CB ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_IConsistencyTestGrain_GrainReference_2EB318CB instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_IConsistencyTestGrain_GrainReference_2EB318CB value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Observation : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IValueSerializer, global::Orleans.Serialization.Serializers.IValueSerializer { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, scoped ref global::Orleans.Transactions.TestKit.Consistency.Observation instance) { } public global::Orleans.Transactions.TestKit.Consistency.Observation ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, scoped ref global::Orleans.Transactions.TestKit.Consistency.Observation instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.Consistency.Observation value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_UserAbort : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_UserAbort(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.Consistency.UserAbort instance) { } public global::Orleans.Transactions.TestKit.Consistency.UserAbort ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.Consistency.UserAbort instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.Consistency.UserAbort value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_ConsistencyTestOptions : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions DeepCopy(global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions input, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_IConsistencyTestGrain_GrainReference_2EB318CB : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_IConsistencyTestGrain_GrainReference_2EB318CB(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_IConsistencyTestGrain_GrainReference_2EB318CB DeepCopy(Invokable_IConsistencyTestGrain_GrainReference_2EB318CB original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_UserAbort : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_UserAbort(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.Consistency.IConsistencyTestGrain), "2EB318CB" })] public sealed partial class Invokable_IConsistencyTestGrain_GrainReference_2EB318CB : global::Orleans.TransactionTaskRequest { public global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestOptions arg0; public int arg1; public string arg2; public int arg3; public System.DateTime arg4; public Invokable_IConsistencyTestGrain_GrainReference_2EB318CB(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_State : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State instance) { } public global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_State : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State DeepCopy(global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State input, global::Orleans.Transactions.TestKit.Consistency.ConsistencyTestGrain.State output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit.Correctnesss { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_BitArrayState : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_BitArrayState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState instance) { } public global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 instance) { } public Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 instance) { } public Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec { public Codec_Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 instance) { } public Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_BitArrayState : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_BitArrayState(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState DeepCopy(global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState input, global::Orleans.Transactions.TestKit.Correctnesss.BitArrayState output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 DeepCopy(Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 DeepCopy(Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier { public Copier_Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider, global::Orleans.Serialization.Activators.IActivator _activator) { } public Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 DeepCopy(Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.Correctnesss.ITransactionalBitArrayGrain), "0183C2F5" })] public sealed partial class Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5 : global::Orleans.TransactionTaskRequest { public int arg0; public Invokable_ITransactionalBitArrayGrain_GrainReference_0183C2F5(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override object GetArgument(int index) { throw null; } public override int GetArgumentCount() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetArgument(int index, object value) { } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.Correctnesss.ITransactionalBitArrayGrain), "9A5740F1" })] public sealed partial class Invokable_ITransactionalBitArrayGrain_GrainReference_9A5740F1 : global::Orleans.Runtime.TaskRequest { public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::Orleans.CompoundTypeAlias(new[] { "inv", typeof(global::Orleans.Runtime.GrainReference), typeof(global::Orleans.Transactions.TestKit.Correctnesss.ITransactionalBitArrayGrain), "B821F3B1" })] public sealed partial class Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1 : global::Orleans.TransactionTaskRequest> { public Invokable_ITransactionalBitArrayGrain_GrainReference_B821F3B1(global::Orleans.Serialization.Serializer base0, System.IServiceProvider base1) : base(default(Serialization.Serializer)!, default!) { } public override void Dispose() { } public override string GetActivityName() { throw null; } public override string GetInterfaceName() { throw null; } public override System.Type GetInterfaceType() { throw null; } public override System.Reflection.MethodInfo GetMethod() { throw null; } public override string GetMethodName() { throw null; } public override object GetTarget() { throw null; } protected override System.Threading.Tasks.Task> InvokeInner() { throw null; } public override void SetTarget(global::Orleans.Serialization.Invocation.ITargetHolder holder) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit.RandomErrorInjector { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RandomlyInjectedInconsistentStateException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_RandomlyInjectedInconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedInconsistentStateException instance) { } public global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedInconsistentStateException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedInconsistentStateException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedInconsistentStateException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RandomlyInjectedStorageException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_RandomlyInjectedStorageException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedStorageException instance) { } public global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedStorageException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedStorageException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.RandomErrorInjector.RandomlyInjectedStorageException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RandomlyInjectedInconsistentStateException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RandomlyInjectedInconsistentStateException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RandomlyInjectedStorageException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RandomlyInjectedStorageException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } namespace OrleansCodeGen.Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CreateAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_CreateAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_CreateOrJoinAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_CreateOrJoinAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_JoinAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_JoinAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_NoAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_NoAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_NotAllowedAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_NotAllowedAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SupportedAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_SupportedAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_SuppressAttributionGrain : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_SuppressAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain instance) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_CreateAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_CreateAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_CreateOrJoinAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_CreateOrJoinAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.CreateOrJoinAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_JoinAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_JoinAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.JoinAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_NoAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_NoAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NoAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_NotAllowedAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_NotAllowedAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.NotAllowedAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SupportedAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_SupportedAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SupportedAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_SuppressAttributionGrain : global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IDeepCopier, global::Orleans.Serialization.Cloning.IBaseCopier, global::Orleans.Serialization.Cloning.IBaseCopier { public Copier_SuppressAttributionGrain(global::Orleans.Serialization.Activators.IActivator _activator, global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) { } public global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain original, global::Orleans.Serialization.Cloning.CopyContext context) { throw null; } public void DeepCopy(global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain input, global::Orleans.Transactions.TestKit.TransactionAttributionGrainExtensions.SuppressAttributionGrain output, global::Orleans.Serialization.Cloning.CopyContext context) { } } } ================================================ FILE: src/api/Orleans.Transactions.TestKit.xUnit/Orleans.Transactions.TestKit.xUnit.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Transactions.TestKit.xUnit { public abstract partial class ConsistencyTransactionTestRunnerxUnit : ConsistencyTransactionTestRunner { public ConsistencyTransactionTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } protected override bool StorageAdaptorHasLimitedCommitSpace { get { throw null; } } protected override bool StorageErrorInjectionActive { get { throw null; } } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { 2, 2, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 3, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 4, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 5, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 2, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 3, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 4, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 5, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 2, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 3, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 4, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 5, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 2, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 3, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 4, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 5, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 2, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 3, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 4, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 5, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 2, 2, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 3, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 4, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 5, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 2, 2, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 3, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 4, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 5, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 2, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 3, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 4, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 2, 5, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 2, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 3, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 4, true, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 2, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 3, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 4, true, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 2, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 3, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 4, true, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 2, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 3, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 4, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 2, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 3, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 4, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 5, true, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 30, 2, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 3, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 4, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 5, true, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 30, 2, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 3, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 4, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 5, true, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 2, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 3, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 4, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 30, 5, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 2, false, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 3, false, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 4, false, true, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 2, false, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 3, false, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 4, false, true, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 2, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 3, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 4, false, true, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 2, false, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 3, false, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 4, false, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 5, false, false, Consistency.ReadWriteDetermination.PerGrain })] [Xunit.InlineData(new[] { 1000, 2, false, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 3, false, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 4, false, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 5, false, false, Consistency.ReadWriteDetermination.PerTransaction })] [Xunit.InlineData(new[] { 1000, 2, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 3, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 4, false, false, Consistency.ReadWriteDetermination.PerAccess })] [Xunit.InlineData(new[] { 1000, 5, false, false, Consistency.ReadWriteDetermination.PerAccess })] public override System.Threading.Tasks.Task RandomizedConsistency(int numGrains, int scale, bool avoidDeadlocks, bool avoidTimeouts, Consistency.ReadWriteDetermination readwrite) { throw null; } } public partial class ControlledFaultInjectionTransactionTestRunnerxUnit : ControlledFaultInjectionTransactionTestRunner { public ControlledFaultInjectionTransactionTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9551")] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.AfterPrepare, FaultInjectionType.Deactivation })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.AfterConfirm, FaultInjectionType.Deactivation })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.AfterPrepared, FaultInjectionType.Deactivation })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.AfterPrepareAndCommit, FaultInjectionType.Deactivation })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforePrepare, FaultInjectionType.ExceptionAfterStore })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforePrepare, FaultInjectionType.ExceptionBeforeStore })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforeConfirm, FaultInjectionType.ExceptionAfterStore })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforeConfirm, FaultInjectionType.ExceptionBeforeStore })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforePrepareAndCommit, FaultInjectionType.ExceptionAfterStore })] [Xunit.InlineData(new[] { TransactionFaultInjectPhase.BeforePrepareAndCommit, FaultInjectionType.ExceptionBeforeStore })] public override System.Threading.Tasks.Task MultiGrainWriteTransaction_FaultInjection(TransactionFaultInjectPhase injectionPhase, FaultInjectionType injectionType) { throw null; } [SkippableFact(new[] { })] public override System.Threading.Tasks.Task SingleGrainReadTransaction() { throw null; } [SkippableFact(new[] { })] public override System.Threading.Tasks.Task SingleGrainWriteTransaction() { throw null; } } public partial class DisabledTransactionsTestRunnerxUnit : DisabledTransactionsTestRunner { protected DisabledTransactionsTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "NoStateTransactionalGrain" })] public override void MultiTransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "NoStateTransactionalGrain" })] public override void TransactionGrainsThrowWhenTransactions(string transactionTestGrainClassName) { } } public abstract partial class GoldenPathTransactionTestRunnerxUnit : GoldenPathTransactionTestRunner { protected GoldenPathTransactionTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task MultiGrainReadWriteTransaction(string grainStates, int grainCount) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task MultiGrainWriteTransaction(string grainStates, int grainCount) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task MultiWriteToSingleGrainTransaction(string grainStates) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task RepeatGrainReadWriteTransaction(string grainStates, int grainCount) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task RWRWTest(string grainStates, int grainCount) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task SingleGrainReadTransaction(string grainStates) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task SingleGrainWriteTransaction(string grainStates) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9553")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task WRWRTest(string grainStates, int grainCount) { throw null; } } public partial class GrainFaultTransactionTestRunnerxUnit : GrainFaultTransactionTestRunner { public GrainFaultTransactionTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task AbortTransactionExceptionInnerExceptionOnlyContainsOneRootCauseException(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task AbortTransactionOnExceptions(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task AbortTransactionOnOrphanCalls(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task AbortTransactionOnReadOnlyViolatedException(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task MultiGrainAbortTransactionOnExceptions(string grainStates) { throw null; } } public abstract partial class ScopedTransactionsTestRunnerxUnit : ScopedTransactionsTestRunner { protected ScopedTransactionsTestRunnerxUnit(IGrainFactory grainFactory, ITransactionClient transactionFrame, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!, default!) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task CreateNestedTransactionScopeAndSetValueAndInnerFailAndAssert(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task CreateTransactionScopeAndSetValue(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task CreateTransactionScopeAndSetValueAndAssert(string grainStates) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task CreateTransactionScopeAndSetValueWithFailure(string grainStates) { throw null; } } public abstract partial class TocFaultTransactionTestRunnerxUnit : TocFaultTransactionTestRunner { protected TocFaultTransactionTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task MultiGrainWriteTransactionWithCommitException(string grainStates, int grainCount) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9556")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain", 1 })] public override System.Threading.Tasks.Task MultiGrainWriteTransactionWithCommitFailure(string grainStates, int grainCount) { throw null; } } public abstract partial class TocGoldenPathTestRunnerxUnit : TocGoldenPathTestRunner { protected TocGoldenPathTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9556")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 8 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 4 })] public override System.Threading.Tasks.Task MultiGrainWriteTransaction(string grainStates, int grainCount) { throw null; } } public abstract partial class TransactionalStateStorageTestRunnerxUnit : TransactionalStateStorageTestRunner where TState : class, new() { public TransactionalStateStorageTestRunnerxUnit(System.Func>> stateStorageFactory, System.Func stateFactory, IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper testOutput, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> assertConfig = null) : base(default!, default!, default!, default!, default!) { } [Xunit.Theory] [Xunit.InlineData(new[] { 99 })] [Xunit.InlineData(new[] { 100 })] [Xunit.InlineData(new[] { 200 })] public override System.Threading.Tasks.Task CancelMany(int count) { throw null; } [Xunit.Fact] public override System.Threading.Tasks.Task CancelOne() { throw null; } [Xunit.Theory] [Xunit.InlineData(new[] { 99, true })] [Xunit.InlineData(new[] { 99, false })] [Xunit.InlineData(new[] { 100, true })] [Xunit.InlineData(new[] { 100, false })] [Xunit.InlineData(new[] { 200, true })] [Xunit.InlineData(new[] { 200, false })] public override System.Threading.Tasks.Task ConfirmMany(int count, bool useTwoSteps) { throw null; } [Xunit.Theory] [Xunit.InlineData(new[] { true })] [Xunit.InlineData(new[] { false })] public override System.Threading.Tasks.Task ConfirmOne(bool useTwoSteps) { throw null; } [Xunit.Theory] [Xunit.InlineData(new[] { false, false })] [Xunit.InlineData(new[] { true, true })] [Xunit.InlineData(new[] { true, false })] public override System.Threading.Tasks.Task ConfirmOneAndCancelOne(bool useTwoSteps, bool reverseOrder) { throw null; } [Xunit.Fact] public override System.Threading.Tasks.Task FirstTime_Load_ShouldReturnEmptyLoadResponse() { throw null; } [Xunit.Fact] public override System.Threading.Tasks.Task GrowingBatch() { throw null; } [Xunit.Theory] [Xunit.InlineData(new[] { 99 })] [Xunit.InlineData(new[] { 100 })] [Xunit.InlineData(new[] { 200 })] public override System.Threading.Tasks.Task PrepareMany(int count) { throw null; } [Xunit.Theory] [Xunit.InlineData(new[] { 99 })] [Xunit.InlineData(new[] { 100 })] [Xunit.InlineData(new[] { 200 })] public override System.Threading.Tasks.Task ReplaceMany(int count) { throw null; } [Xunit.Fact] public override System.Threading.Tasks.Task ReplaceOne() { throw null; } [Xunit.Fact] public override System.Threading.Tasks.Task ShrinkingBatch() { throw null; } } public abstract partial class TransactionConcurrencyTestRunnerxUnit : TransactionConcurrencyTestRunner { protected TransactionConcurrencyTestRunnerxUnit(IGrainFactory grainFactory, Xunit.Abstractions.ITestOutputHelper output) : base(default!, default!) { } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9554")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task SingleSharedGrainTest(string grainStates) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9554")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task TransactionChainTest(string grainStates) { throw null; } [SkippableTheory(new[] { }, Skip = "https://github.com/dotnet/orleans/issues/9554")] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain" })] [Xunit.InlineData(new[] { "MaxStateTransactionalGrain" })] public override System.Threading.Tasks.Task TransactionTreeTest(string grainStates) { throw null; } } public partial class TransactionRecoveryTestsRunnerxUnit : TransactionRecoveryTestsRunner { public TransactionRecoveryTestsRunnerxUnit(TestingHost.TestCluster cluster, Xunit.Abstractions.ITestOutputHelper testOutput) : base(default!, default!) { } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 30 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 20 })] public override System.Threading.Tasks.Task TransactionWillRecoverAfterRandomSiloGracefulShutdown(string transactionTestGrainClassName, int concurrent) { throw null; } [SkippableTheory(new[] { })] [Xunit.InlineData(new[] { "SingleStateTransactionalGrain", 30 })] [Xunit.InlineData(new[] { "DoubleStateTransactionalGrain", 20 })] public override System.Threading.Tasks.Task TransactionWillRecoverAfterRandomSiloUnGracefulShutdown(string transactionTestGrainClassName, int concurrent) { throw null; } } } ================================================ FILE: src/api/README.md ================================================ # Generate API surface This directory contains generated files describing the public API surface area for all packable projects in this repository. The API surface are is generated by a scheduled workflow, [generate-api-diffs.yml](../../.github/workflows/generate-api-diffs.yml). ================================================ FILE: src/api/Redis/Orleans.Clustering.Redis/Orleans.Clustering.Redis.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.Extensions.Hosting { public static partial class RedisClusteringIClientBuilderExtensions { public static Orleans.Hosting.IClientBuilder UseRedisClustering(this Orleans.Hosting.IClientBuilder builder, System.Action configuration) { throw null; } public static Orleans.Hosting.IClientBuilder UseRedisClustering(this Orleans.Hosting.IClientBuilder builder, string redisConnectionString) { throw null; } } public static partial class RedisClusteringISiloBuilderExtensions { public static Orleans.Hosting.ISiloBuilder UseRedisClustering(this Orleans.Hosting.ISiloBuilder builder, System.Action configuration) { throw null; } public static Orleans.Hosting.ISiloBuilder UseRedisClustering(this Orleans.Hosting.ISiloBuilder builder, string redisConnectionString) { throw null; } } } namespace Orleans.Clustering.Redis { public partial class RedisClusteringException : System.Exception { public RedisClusteringException() { } [System.Obsolete] protected RedisClusteringException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public RedisClusteringException(string message, System.Exception innerException) { } public RedisClusteringException(string message) { } } public partial class RedisClusteringOptions { public StackExchange.Redis.ConfigurationOptions ConfigurationOptions { get { throw null; } set { } } public System.Func> CreateMultiplexer { get { throw null; } set { } } public System.Func CreateRedisKey { get { throw null; } set { } } public System.TimeSpan? EntryExpiry { get { throw null; } set { } } public static System.Threading.Tasks.Task DefaultCreateMultiplexer(RedisClusteringOptions options) { throw null; } public static StackExchange.Redis.RedisKey DefaultCreateRedisKey(Configuration.ClusterOptions clusterOptions) { throw null; } } public partial class RedisClusteringOptionsValidator : IConfigurationValidator { public RedisClusteringOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } } ================================================ FILE: src/api/Redis/Orleans.GrainDirectory.Redis/Orleans.GrainDirectory.Redis.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class RedisGrainDirectoryOptions { public StackExchange.Redis.ConfigurationOptions ConfigurationOptions { get { throw null; } set { } } public System.Func> CreateMultiplexer { get { throw null; } set { } } public System.TimeSpan? EntryExpiry { get { throw null; } set { } } public static System.Threading.Tasks.Task DefaultCreateMultiplexer(RedisGrainDirectoryOptions options) { throw null; } } public partial class RedisGrainDirectoryOptionsValidator : IConfigurationValidator { public RedisGrainDirectoryOptionsValidator(RedisGrainDirectoryOptions options, string name) { } public void ValidateConfiguration() { } } } namespace Orleans.GrainDirectory.Redis { public partial class RedisGrainDirectory : IGrainDirectory, ILifecycleParticipant { public RedisGrainDirectory(Configuration.RedisGrainDirectoryOptions directoryOptions, Microsoft.Extensions.Options.IOptions clusterOptions, Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task Initialize(System.Threading.CancellationToken ct = default) { throw null; } public System.Threading.Tasks.Task Lookup(Runtime.GrainId grainId) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task Register(Runtime.GrainAddress address, Runtime.GrainAddress? previousAddress) { throw null; } public System.Threading.Tasks.Task Register(Runtime.GrainAddress address) { throw null; } public System.Threading.Tasks.Task Unregister(Runtime.GrainAddress address) { throw null; } public System.Threading.Tasks.Task UnregisterSilos(System.Collections.Generic.List siloAddresses) { throw null; } } } namespace Orleans.Hosting { public static partial class RedisGrainDirectoryExtensions { public static ISiloBuilder AddRedisGrainDirectory(this ISiloBuilder builder, string name, System.Action> configureOptions) { throw null; } public static ISiloBuilder AddRedisGrainDirectory(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder UseRedisGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action> configureOptions) { throw null; } public static ISiloBuilder UseRedisGrainDirectoryAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } } } ================================================ FILE: src/api/Redis/Orleans.Persistence.Redis/Orleans.Persistence.Redis.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Hosting { public static partial class RedisGrainStorageServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRedisGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRedisGrainStorage(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureOptions) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRedisGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> configureOptions = null) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRedisGrainStorageAsDefault(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } } public static partial class RedisSiloBuilderExtensions { public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name, System.Action> configureOptionsBuilder) { throw null; } public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name, System.Action configureOptions) { throw null; } public static ISiloBuilder AddRedisGrainStorage(this ISiloBuilder builder, string name) { throw null; } public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder, System.Action> configureOptionsBuilder) { throw null; } public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder, System.Action configureOptions) { throw null; } public static ISiloBuilder AddRedisGrainStorageAsDefault(this ISiloBuilder builder) { throw null; } } } namespace Orleans.Persistence { public partial class RedisGrainStorage : Storage.IGrainStorage, ILifecycleParticipant { public RedisGrainStorage(string name, RedisStorageOptions options, Storage.IGrainStorageSerializer grainStorageSerializer, Microsoft.Extensions.Options.IOptions clusterOptions, Serialization.Serializers.IActivatorProvider activatorProvider, Microsoft.Extensions.Logging.ILogger logger) { } public System.Threading.Tasks.Task ClearStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public void Participate(Runtime.ISiloLifecycle lifecycle) { } public System.Threading.Tasks.Task ReadStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } public System.Threading.Tasks.Task WriteStateAsync(string grainType, Runtime.GrainId grainId, IGrainState grainState) { throw null; } } public static partial class RedisGrainStorageFactory { public static RedisGrainStorage Create(System.IServiceProvider services, string name) { throw null; } } public partial class RedisStorageOptions : Storage.IStorageProviderSerializerOptions { public StackExchange.Redis.ConfigurationOptions? ConfigurationOptions { get { throw null; } set { } } public System.Func> CreateMultiplexer { get { throw null; } set { } } public bool DeleteStateOnClear { get { throw null; } set { } } public System.TimeSpan? EntryExpiry { get { throw null; } set { } } public System.Func? GetStorageKey { get { throw null; } set { } } public Storage.IGrainStorageSerializer? GrainStorageSerializer { get { throw null; } set { } } public int InitStage { get { throw null; } set { } } public static System.Threading.Tasks.Task DefaultCreateMultiplexer(RedisStorageOptions options) { throw null; } } public static partial class RedisStorageOptionsExtensions { public static void UseGetRedisKeyIgnoringGrainType(this Microsoft.Extensions.Options.OptionsBuilder optionsBuilder) { } } } namespace Orleans.Persistence.Redis { [GenerateSerializer] public partial class RedisStorageException : System.Exception { public RedisStorageException() { } [System.Obsolete] protected RedisStorageException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public RedisStorageException(string message, System.Exception inner) { } public RedisStorageException(string message) { } } } namespace OrleansCodeGen.Orleans.Persistence.Redis { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RedisStorageException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_RedisStorageException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Persistence.Redis.RedisStorageException instance) { } public global::Orleans.Persistence.Redis.RedisStorageException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Persistence.Redis.RedisStorageException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Persistence.Redis.RedisStorageException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RedisStorageException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RedisStorageException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } ================================================ FILE: src/api/Redis/Orleans.Reminders.Redis/Orleans.Reminders.Redis.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Configuration { public partial class RedisReminderTableOptions { public StackExchange.Redis.ConfigurationOptions ConfigurationOptions { get { throw null; } set { } } public System.Func> CreateMultiplexer { get { throw null; } set { } } public System.TimeSpan? EntryExpiry { get { throw null; } set { } } public static System.Threading.Tasks.Task DefaultCreateMultiplexer(RedisReminderTableOptions options) { throw null; } } public partial class RedisReminderTableOptionsValidator : IConfigurationValidator { public RedisReminderTableOptionsValidator(Microsoft.Extensions.Options.IOptions options) { } public void ValidateConfiguration() { } } } namespace Orleans.Hosting { public static partial class SiloBuilderReminderExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection UseRedisReminderService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static ISiloBuilder UseRedisReminderService(this ISiloBuilder builder, System.Action configure) { throw null; } } } namespace Orleans.Reminders.Redis { [GenerateSerializer] public partial class RedisRemindersException : System.Exception { public RedisRemindersException() { } [System.Obsolete] protected RedisRemindersException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public RedisRemindersException(string message, System.Exception inner) { } public RedisRemindersException(string message) { } } } namespace OrleansCodeGen.Orleans.Reminders.Redis { [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Codec_RedisRemindersException : global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Codecs.IFieldCodec, global::Orleans.Serialization.Serializers.IBaseCodec, global::Orleans.Serialization.Serializers.IBaseCodec { public Codec_RedisRemindersException(global::Orleans.Serialization.Serializers.IBaseCodec _baseTypeSerializer) { } public void Deserialize(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Reminders.Redis.RedisRemindersException instance) { } public global::Orleans.Reminders.Redis.RedisRemindersException ReadValue(ref global::Orleans.Serialization.Buffers.Reader reader, global::Orleans.Serialization.WireProtocol.Field field) { throw null; } public void Serialize(ref global::Orleans.Serialization.Buffers.Writer writer, global::Orleans.Reminders.Redis.RedisRemindersException instance) where TBufferWriter : System.Buffers.IBufferWriter { } public void WriteField(ref global::Orleans.Serialization.Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, global::Orleans.Reminders.Redis.RedisRemindersException value) where TBufferWriter : System.Buffers.IBufferWriter { } } [System.CodeDom.Compiler.GeneratedCode("OrleansCodeGen", "9.0.0.0")] [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public sealed partial class Copier_RedisRemindersException : global::Orleans.Serialization.GeneratedCodeHelpers.OrleansGeneratedCodeHelper.ExceptionCopier { public Copier_RedisRemindersException(global::Orleans.Serialization.Serializers.ICodecProvider codecProvider) : base(default(Serialization.Serializers.ICodecProvider)!) { } } } ================================================ FILE: src/api/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Orleans.Serialization { [RegisterSerializer] public sealed partial class ByteStringCodec : Codecs.IFieldCodec, Codecs.IFieldCodec { Google.Protobuf.ByteString Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Google.Protobuf.ByteString value) { } } [RegisterCopier] public sealed partial class ByteStringCopier : Cloning.IDeepCopier, Cloning.IDeepCopier { public Google.Protobuf.ByteString DeepCopy(Google.Protobuf.ByteString input, Cloning.CopyContext context) { throw null; } } [RegisterSerializer] public sealed partial class MapFieldCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec { public MapFieldCodec(Codecs.IFieldCodec keyCodec, Codecs.IFieldCodec valueCodec) { } public Google.Protobuf.Collections.MapField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Google.Protobuf.Collections.MapField value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class MapFieldCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public MapFieldCopier(Cloning.IDeepCopier keyCopier, Cloning.IDeepCopier valueCopier) { } public void DeepCopy(Google.Protobuf.Collections.MapField input, Google.Protobuf.Collections.MapField output, Cloning.CopyContext context) { } public Google.Protobuf.Collections.MapField DeepCopy(Google.Protobuf.Collections.MapField input, Cloning.CopyContext context) { throw null; } } [Alias("protobuf")] public sealed partial class ProtobufCodec : Serializers.IGeneralizedCodec, Codecs.IFieldCodec, Cloning.IGeneralizedCopier, Cloning.IDeepCopier, ITypeFilter { public const string WellKnownAlias = "protobuf"; public ProtobufCodec(System.Collections.Generic.IEnumerable serializableTypeSelectors, System.Collections.Generic.IEnumerable copyableTypeSelectors) { } public object DeepCopy(object input, Cloning.CopyContext context) { throw null; } bool Cloning.IGeneralizedCopier.IsSupportedType(System.Type type) { throw null; } object Codecs.IFieldCodec.ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } void Codecs.IFieldCodec.WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, object value) { } bool? ITypeFilter.IsTypeAllowed(System.Type type) { throw null; } bool Serializers.IGeneralizedCodec.IsSupportedType(System.Type type) { throw null; } } [RegisterSerializer] public sealed partial class RepeatedFieldCodec : Codecs.IFieldCodec>, Codecs.IFieldCodec { public RepeatedFieldCodec(Codecs.IFieldCodec fieldCodec) { } public Google.Protobuf.Collections.RepeatedField ReadValue(ref Buffers.Reader reader, WireProtocol.Field field) { throw null; } public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, System.Type expectedType, Google.Protobuf.Collections.RepeatedField value) where TBufferWriter : System.Buffers.IBufferWriter { } } [RegisterCopier] public sealed partial class RepeatedFieldCopier : Cloning.IDeepCopier>, Cloning.IDeepCopier, Cloning.IBaseCopier>, Cloning.IBaseCopier { public RepeatedFieldCopier(Cloning.IDeepCopier valueCopier) { } public void DeepCopy(Google.Protobuf.Collections.RepeatedField input, Google.Protobuf.Collections.RepeatedField output, Cloning.CopyContext context) { } public Google.Protobuf.Collections.RepeatedField DeepCopy(Google.Protobuf.Collections.RepeatedField input, Cloning.CopyContext context) { throw null; } } public static partial class SerializationHostingExtensions { public static ISerializerBuilder AddProtobufSerializer(this ISerializerBuilder serializerBuilder, System.Func isSerializable, System.Func isCopyable) { throw null; } public static ISerializerBuilder AddProtobufSerializer(this ISerializerBuilder serializerBuilder) { throw null; } } } ================================================ FILE: test/Benchmarks/App.config ================================================ ================================================ FILE: test/Benchmarks/Benchmarks.csproj ================================================ Benchmarks Benchmarks net10.0;net8.0 Exe true true true true true false $(NoWarn);8981 all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: test/Benchmarks/Dashboard/DashboardGrainBenchmark.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using BenchmarkDotNet.Attributes; using Orleans.Dashboard.Metrics.History; using System.Collections; namespace Benchmarks.Dashboard { [ShortRunJob] [MemoryDiagnoser] internal class DashboardGrainBenchmark { [Params(10)] public int SiloCount { get; set; } [Params(50)] public int GrainTypeCount { get; set; } [Params(10)] public int GrainMethodCount { get; set; } [Params(100)] public int HistorySize { get; set; } [ParamsSource(nameof(Histories))] public ITraceHistory History { get; set; } public IEnumerable Histories { get { yield return new TraceHistory(HistorySize); } } [GlobalSetup] public void Setup() { var startTime = DateTime.UtcNow; Setup(startTime, History); testTraces = Helper.CreateTraces(startTime.AddSeconds(HistorySize), SiloCount, GrainTypeCount, GrainMethodCount).ToList(); } private List testTraces; [Benchmark] public void Test_Add_TraceHistory() { foreach (var trace in testTraces) { History.Add(trace.Time, trace.Silo, trace.Traces); } } [Benchmark] public ICollection Test_QueryAll_TraceHistory() { return History.QueryAll(); } [Benchmark] public ICollection Test_QuerySilo_TraceHistory() { return History.QuerySilo("SILO_0"); } [Benchmark] public ICollection Test_QueryGrain_TraceHistory() { return History.QueryGrain("GRAIN_0"); } [Benchmark] public ICollection Test_GroupByGrainAndSilo_TraceHistory() { return History.GroupByGrainAndSilo().ToList(); } [Benchmark] public ICollection Test_AggregateByGrainMethod_TraceHistory() { return History.AggregateByGrainMethod().ToList(); } private void Setup(DateTime startTime, ITraceHistory history) { for (var timeIndex = 0; timeIndex < HistorySize; timeIndex++) { var time = startTime.AddSeconds(timeIndex); foreach (var trace in Helper.CreateTraces(time, SiloCount, GrainTypeCount, GrainMethodCount)) { history.Add(trace.Time, trace.Silo, trace.Traces); } } } } } ================================================ FILE: test/Benchmarks/Dashboard/Helper.cs ================================================ using Orleans.Dashboard.Model; using System; using System.Collections.Generic; namespace Benchmarks.Dashboard { internal class Helper { public static IEnumerable CreateTraces(DateTime time, int siloCount, int grainCount, int methodCount) { for (var siloIndex = 0; siloIndex < siloCount; siloIndex++) { var trace = new List(); for (var grainIndex = 0; grainIndex < grainCount; grainIndex++) { for (var grainMethodIndex = 0; grainMethodIndex < methodCount; grainMethodIndex++) { trace.Add(new SiloGrainTraceEntry { ElapsedTime = 10, Count = 100, Method = $"METHOD_{grainMethodIndex}", Grain = $"GRAIN_{grainIndex}", ExceptionCount = 0 }); } } yield return new TestTraces(time, $"SILO_{siloIndex}", trace.ToArray()); } } } } ================================================ FILE: test/Benchmarks/Dashboard/ManualTests.cs ================================================ using Orleans.Dashboard.Metrics.History; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Benchmarks.Dashboard { internal class ManualTests { private const int HistorySize = 100; private readonly ITraceHistory history1 = new TraceHistory(HistorySize); private readonly List testTraces; public ManualTests() { var startTime = DateTime.UtcNow; Setup(startTime, history1); testTraces = Helper.CreateTraces(startTime.AddSeconds(HistorySize), 10, 50, 10).ToList(); } private static void Setup(DateTime startTime, ITraceHistory history) { for (var timeIndex = 0; timeIndex < HistorySize; timeIndex++) { var time = startTime.AddSeconds(timeIndex); foreach (var trace in Helper.CreateTraces(time, 10, 50, 10)) { history.Add(trace.Time, trace.Silo, trace.Traces); } } } public void Run() { Test("Add", history => { foreach (var trace in testTraces) { history.Add(trace.Time, trace.Silo, trace.Traces); } }); Test("Query All", history => { history.QueryAll(); }); Test("Query By Silo", history => { history.QuerySilo("SILO_0"); }); Test("Query By Grain", history => { history.QueryGrain("GRAIN_0"); }); Test("Query By Grain and Silo", history => { history.GroupByGrainAndSilo(); }); Test("Query Aggregated", history => { history.AggregateByGrainMethod(); }); } private void Test(string name, Action action) { const int NumIterations = 1; var watch = Stopwatch.StartNew(); for (var i = 0; i < NumIterations; i++) { action(history1); } watch.Start(); Console.WriteLine("{0} V1: {1}", name, watch.Elapsed / NumIterations); } } } ================================================ FILE: test/Benchmarks/Dashboard/TestTraces.cs ================================================ using Orleans.Dashboard.Model; using System; namespace Benchmarks.Dashboard { internal sealed record TestTraces(DateTime Time, string Silo, SiloGrainTraceEntry[] Traces) { } } ================================================ FILE: test/Benchmarks/GrainStorage/GrainStorageBenchmark.cs ================================================ using System.Diagnostics; using Orleans.TestingHost; using TestExtensions; using BenchmarkGrainInterfaces.GrainStorage; namespace Benchmarks.GrainStorage; /// /// Benchmarks grain storage providers by measuring read/write operations against different storage backends. /// public class GrainStorageBenchmark : IDisposable { private TestCluster host; private readonly int concurrent; private readonly int payloadSize; private readonly TimeSpan duration; public GrainStorageBenchmark(int concurrent, int payloadSize, TimeSpan duration) { this.concurrent = concurrent; this.payloadSize = payloadSize; this.duration = duration; } public void MemorySetup() { var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void AzureTableSetup() { var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void AzureBlobSetup() { var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void AdoNetSetup() { var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public class SiloMemoryStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddMemoryGrainStorageAsDefault(); } } public class SiloAzureTableStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddAzureTableGrainStorageAsDefault(options => { options.TableServiceClient = new(TestDefaultConfiguration.DataConnectionString); }); } } public class SiloAzureBlobStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddAzureBlobGrainStorageAsDefault(options => { options.BlobServiceClient = new(TestDefaultConfiguration.DataConnectionString); }); } } public class SiloAdoNetStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddAdoNetGrainStorageAsDefault(options => { options.ConnectionString = TestDefaultConfiguration.DataConnectionString; }); } } public async Task RunAsync() { Stopwatch sw = Stopwatch.StartNew(); bool running = true; bool isRunning() => running; var runTask = Task.WhenAll(Enumerable.Range(0, concurrent).Select(i => RunAsync(i, isRunning)).ToList()); Task[] waitTasks = { runTask, Task.Delay(duration) }; await Task.WhenAny(waitTasks); running = false; var runResults = await runTask; sw.Stop(); var reports = runResults.SelectMany(r => r).ToList(); var stored = reports.Count(r => r.Success); var failed = reports.Count(r => !r.Success); var calltimes = reports.Select(r => r.Elapsed.TotalMilliseconds); var calltime = calltimes.Sum(); var maxCalltime = calltimes.Max(); var averageCalltime = calltimes.Average(); Console.WriteLine($"Performed {stored} persist (read & write) operations with {failed} failures in {sw.ElapsedMilliseconds}ms."); Console.WriteLine($"Average time in ms per call was {averageCalltime}, with longest call taking {maxCalltime}ms."); Console.WriteLine($"Total time waiting for the persistent store was {calltime}ms."); } public async Task> RunAsync(int instance, Func running) { var persistentGrain = this.host.Client.GetGrain(Guid.NewGuid()); // activate grain await persistentGrain.Init(payloadSize); var iteration = instance % payloadSize; var reports = new List(5000); while (running()) { var report = await persistentGrain.TrySet(iteration); reports.Add(report); iteration = (iteration + 1) % payloadSize; } return reports; } public void Teardown() { host.StopAllSilos(); } public void Dispose() { host?.Dispose(); } } ================================================ FILE: test/Benchmarks/MapReduce/MapReduceBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using BenchmarkGrainInterfaces.MapReduce; using BenchmarkGrains.MapReduce; using Orleans.TestingHost; namespace Benchmarks.MapReduce; /// /// Benchmarks Orleans' capability to perform map-reduce operations with complex processing pipelines. /// public class MapReduceBenchmark : IDisposable { private static TestCluster _host; private readonly int _intermediateStagesCount = 15; private readonly int _pipelineParallelization = 4; private readonly int _repeats = 50000; private int _currentRepeat = 0; [GlobalSetup] public void BenchmarkSetup() { var builder = new TestClusterBuilder(1); _host = builder.Build(); _host.Deploy(); } [Benchmark] public async Task Bench() { var pipelines = Enumerable .Range(0, this._pipelineParallelization) .AsParallel() .WithDegreeOfParallelism(4) .Select(async i => { await BenchCore(); }); await Task.WhenAll(pipelines); } [GlobalCleanup] public void Teardown() { _host.StopAllSilos(); } private async Task BenchCore() { List initializationTasks = new List(); var mapper = _host.GrainFactory.GetGrain>>(Guid.NewGuid()); initializationTasks.Add(mapper.Initialize(new MapProcessor())); var reducer = _host.GrainFactory.GetGrain, Dictionary>>(Guid.NewGuid()); initializationTasks.Add(reducer.Initialize(new ReduceProcessor())); // used for imitation of complex processing pipelines var intermediateGrains = Enumerable .Range(0, this._intermediateStagesCount) .Select(i => { var intermediateProcessor = _host.GrainFactory.GetGrain, Dictionary>> (Guid.NewGuid()); initializationTasks.Add(intermediateProcessor.Initialize(new EmptyProcessor())); return intermediateProcessor; }); initializationTasks.Add(mapper.LinkTo(reducer)); var collector = _host.GrainFactory.GetGrain>>(Guid.NewGuid()); using (var e = intermediateGrains.GetEnumerator()) { ITransformGrain, Dictionary> previous = null; if (e.MoveNext()) { initializationTasks.Add(reducer.LinkTo(e.Current)); previous = e.Current; } while (e.MoveNext()) { initializationTasks.Add(previous.LinkTo(e.Current)); previous = e.Current; } initializationTasks.Add(previous.LinkTo(collector)); } await Task.WhenAll(initializationTasks); List> resultList = new List>(); while (Interlocked.Increment(ref this._currentRepeat) < this._repeats) { await mapper.SendAsync(this._text); while (!resultList.Any() || resultList.First().Count < 84) // rough way of checking of pipeline completition. { resultList = await collector.ReceiveAll(); } } } public void Dispose() { _host?.Dispose(); } private readonly string _text = @"Historically, the world of data and the world of objects" + @" have not been well integrated. Programmers work in C# or Visual Basic" + @" and also in SQL or XQuery. On the one side are concepts such as classes," + @" objects, fields, inheritance, and .NET Framework APIs. On the other side" + @" are tables, columns, rows, nodes, and separate languages for dealing with" + @" them. Data types often require translation between the two worlds; there are" + @" different standard functions. Because the object world has no notion of query, a" + @" query can only be represented as a string without compile-time type checking or" + @" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" + @" objects in memory is often tedious and error-prone. Historically, the world of data and the world of objects" + @" have not been well integrated. Programmers work in C# or Visual Basic" + @" and also in SQL or XQuery. On the one side are concepts such as classes," + @" objects, fields, inheritance, and .NET Framework APIs. On the other side" + @" are tables, columns, rows, nodes, and separate languages for dealing with" + @" them. Data types often require translation between the two worlds; there are" + @" different standard functions. Because the object world has no notion of query, a" + @" query can only be represented as a string without compile-time type checking or" + @" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" + @" objects in memory is often tedious and error-prone."; } ================================================ FILE: test/Benchmarks/MapReduce/MapReduceBenchmarkConfig.cs ================================================ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; namespace Benchmarks.MapReduce; public class MapReduceBenchmarkConfig : ManualConfig { public MapReduceBenchmarkConfig() { AddJob(new Job { Run = { LaunchCount = 1, IterationCount = 2, WarmupCount = 0 } }); } } ================================================ FILE: test/Benchmarks/Ping/ConcurrentLoadGenerator.cs ================================================ using System.Threading.Channels; using System.Diagnostics; namespace Benchmarks.Ping; public sealed class ConcurrentLoadGenerator { private static readonly double StopwatchTickPerSecond = Stopwatch.Frequency; private struct WorkBlock { public long StartTimestamp { get; set; } public long EndTimestamp { get; set; } public int Successes { get; set; } public int Failures { get; set; } public readonly int Completed => this.Successes + this.Failures; public readonly double ElapsedSeconds => (this.EndTimestamp - this.StartTimestamp) / StopwatchTickPerSecond; public readonly double RequestsPerSecond => this.Completed / this.ElapsedSeconds; } private Channel _completedBlocks; private readonly Func _issueRequest; private readonly Func _getStateForWorker; private readonly bool _logIntermediateResults; private readonly Task[] _tasks; private readonly TState[] _states; private readonly int _numWorkers; private readonly int _blocksPerWorker; private readonly int _requestsPerBlock; public ConcurrentLoadGenerator( int maxConcurrency, int blocksPerWorker, int requestsPerBlock, Func issueRequest, Func getStateForWorker, bool logIntermediateResults = false) { this._numWorkers = maxConcurrency; this._blocksPerWorker = blocksPerWorker; this._requestsPerBlock = requestsPerBlock; this._issueRequest = issueRequest; this._getStateForWorker = getStateForWorker; this._logIntermediateResults = logIntermediateResults; this._tasks = new Task[maxConcurrency]; this._states = new TState[maxConcurrency]; } public async Task Warmup() { this.ResetBetweenRuns(); var completedBlockReader = this._completedBlocks.Reader; for (var ree = 0; ree < this._numWorkers; ree++) { this._states[ree] = _getStateForWorker(ree); this._tasks[ree] = this.RunWorker(this._states[ree], this._requestsPerBlock, 3); } // Wait for warmup to complete. await Task.WhenAll(this._tasks); // Ignore warmup blocks. while (completedBlockReader.TryRead(out _)) ; GC.Collect(); } private void ResetBetweenRuns() { this._completedBlocks = Channel.CreateUnbounded( new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }); } public async Task Run() { this.ResetBetweenRuns(); var completedBlockReader = this._completedBlocks.Reader; // Start the run. for (var i = 0; i < this._numWorkers; i++) { this._tasks[i] = this.RunWorker(this._states[i], this._requestsPerBlock, this._blocksPerWorker); } _ = Task.Run(async () => { try { await Task.WhenAll(this._tasks); } catch { } finally { this._completedBlocks.Writer.Complete(); } }); var blocks = new List(this._numWorkers * this._blocksPerWorker); var blocksPerReport = this._numWorkers * this._blocksPerWorker / 5; var nextReportBlockCount = blocksPerReport; while (true) { var more = await completedBlockReader.WaitToReadAsync(); if (!more) break; while (completedBlockReader.TryRead(out var block)) { blocks.Add(block); } if (this._logIntermediateResults && blocks.Count >= nextReportBlockCount) { nextReportBlockCount += blocksPerReport; Console.WriteLine(" " + PrintReport(0)); } } if (this._logIntermediateResults) Console.WriteLine(" Total: " + PrintReport(0)); else Console.WriteLine(PrintReport(0)); string PrintReport(int statingBlockIndex) { if (blocks.Count == 0) return "No blocks completed"; var successes = 0; var failures = 0; long completed = 0; var reportBlocks = 0; long minStartTime = long.MaxValue; long maxEndTime = long.MinValue; for (var i = statingBlockIndex; i < blocks.Count; i++) { var b = blocks[i]; ++reportBlocks; successes += b.Successes; failures += b.Failures; completed += b.Completed; if (b.StartTimestamp < minStartTime) minStartTime = b.StartTimestamp; if (b.EndTimestamp > maxEndTime) maxEndTime = b.EndTimestamp; } var totalSeconds = (maxEndTime - minStartTime) / StopwatchTickPerSecond; var ratePerSecond = (long)(completed / totalSeconds); var failureString = failures == 0 ? string.Empty : $" with {failures} failures"; return $"{ratePerSecond,6}/s {successes,7} reqs in {totalSeconds,6:0.000}s{failureString}"; } } private async Task RunWorker(TState state, int requestsPerBlock, int numBlocks) { var completedBlockWriter = this._completedBlocks.Writer; while (numBlocks > 0) { var workBlock = new WorkBlock(); workBlock.StartTimestamp = Stopwatch.GetTimestamp(); while (workBlock.Completed < requestsPerBlock) { try { await this._issueRequest(state).ConfigureAwait(false); ++workBlock.Successes; } catch { ++workBlock.Failures; } } workBlock.EndTimestamp = Stopwatch.GetTimestamp(); await completedBlockWriter.WriteAsync(workBlock).ConfigureAwait(false); --numBlocks; } } } ================================================ FILE: test/Benchmarks/Ping/FanoutBenchmark.cs ================================================ using System.Net; using BenchmarkDotNet.Attributes; using BenchmarkGrainInterfaces.Ping; using BenchmarkGrains.Ping; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Orleans.Configuration; namespace Benchmarks.Ping; /// /// Benchmarks grain communication with fanout patterns across multiple silos. /// [MemoryDiagnoser] public class FanoutBenchmark : IDisposable { private readonly ConsoleCancelEventHandler _onCancelEvent; private readonly List hosts = new(); private readonly ITreeGrain grain; private readonly IClusterClient client; private readonly IHost clientHost; public FanoutBenchmark() : this(2, true) { } public FanoutBenchmark(int numSilos, bool startClient, bool grainsOnSecondariesOnly = false) { for (var i = 0; i < numSilos; ++i) { var primary = i == 0 ? null : new IPEndPoint(IPAddress.Loopback, 11111); var hostBuilder = new HostBuilder().UseOrleans((ctx, siloBuilder) => { #pragma warning disable ORLEANSEXP001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. siloBuilder.AddActivationRepartitioner(); #pragma warning restore ORLEANSEXP001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. siloBuilder.ConfigureLogging(l => { l.AddSimpleConsole(o => { o.UseUtcTimestamp = true; o.TimestampFormat = "HH:mm:ss "; o.ColorBehavior = LoggerColorBehavior.Enabled; }); l.AddFilter("Orleans.Runtime.Placement.Repartitioning", LogLevel.Debug); }); siloBuilder.Configure(o => { }); siloBuilder.UseLocalhostClustering( siloPort: 11111 + i, gatewayPort: 30000 + i, primarySiloEndpoint: primary); if (i == 0 && grainsOnSecondariesOnly) { siloBuilder.Configure(options => options.Classes.Remove(typeof(PingGrain))); } }); var host = hostBuilder.Build(); host.StartAsync().GetAwaiter().GetResult(); this.hosts.Add(host); } if (grainsOnSecondariesOnly) Thread.Sleep(4000); if (startClient) { var hostBuilder = new HostBuilder().UseOrleansClient((ctx, clientBuilder) => { if (numSilos == 1) { clientBuilder.UseLocalhostClustering(); } else { var gateways = Enumerable.Range(30000, numSilos).Select(i => new IPEndPoint(IPAddress.Loopback, i)).ToArray(); clientBuilder.UseStaticClustering(gateways); } }); this.clientHost = hostBuilder.Build(); this.clientHost.StartAsync().GetAwaiter().GetResult(); this.client = this.clientHost.Services.GetRequiredService(); var grainFactory = this.client; this.grain = grainFactory.GetGrain(0, keyExtension: "0"); this.grain.Ping().AsTask().GetAwaiter().GetResult(); } _onCancelEvent = CancelPressed; Console.CancelKeyPress += _onCancelEvent; AppDomain.CurrentDomain.FirstChanceException += (object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e) => Console.WriteLine("FIRST CHANCE EXCEPTION: " + LogFormatter.PrintException(e.Exception)); AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => Console.WriteLine("UNHANDLED EXCEPTION: " + LogFormatter.PrintException((Exception)e.ExceptionObject)); } private void CancelPressed(object sender, ConsoleCancelEventArgs e) { Environment.Exit(0); } [Benchmark] public ValueTask Ping() => grain.Ping(); public async Task PingForever() { while (true) { await grain.Ping(); } } public async Task Shutdown() { if (clientHost is { } client) { await client.StopAsync(); if (client is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { client.Dispose(); } } this.hosts.Reverse(); foreach (var host in this.hosts) { await host.StopAsync(); if (host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { host.Dispose(); } } } [GlobalCleanup] public void Dispose() { (this.client as IDisposable)?.Dispose(); this.hosts.ForEach(h => h.Dispose()); Console.CancelKeyPress -= _onCancelEvent; } } ================================================ FILE: test/Benchmarks/Ping/PingBenchmark.cs ================================================ using System.Net; using BenchmarkDotNet.Attributes; using BenchmarkGrainInterfaces.Ping; using BenchmarkGrains.Ping; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Configuration; namespace Benchmarks.Ping; /// /// Benchmarks grain-to-grain communication latency and throughput using simple ping operations. /// [MemoryDiagnoser] public class PingBenchmark : IDisposable { private readonly ConsoleCancelEventHandler _onCancelEvent; private readonly List hosts = new List(); private readonly IPingGrain grain; private readonly IClusterClient client; private readonly IHost clientHost; public PingBenchmark() : this(1, true) { } public PingBenchmark(int numSilos, bool startClient, bool grainsOnSecondariesOnly = false) { for (var i = 0; i < numSilos; ++i) { var primary = i == 0 ? null : new IPEndPoint(IPAddress.Loopback, 11111); var hostBuilder = new HostBuilder().UseOrleans((ctx, siloBuilder) => { siloBuilder.UseLocalhostClustering( siloPort: 11111 + i, gatewayPort: 30000 + i, primarySiloEndpoint: primary); if (i == 0 && grainsOnSecondariesOnly) { siloBuilder.Configure(options => options.Classes.Remove(typeof(PingGrain))); } }); var host = hostBuilder.Build(); host.StartAsync().GetAwaiter().GetResult(); this.hosts.Add(host); } if (grainsOnSecondariesOnly) Thread.Sleep(4000); if (startClient) { var hostBuilder = new HostBuilder().UseOrleansClient((ctx, clientBuilder) => { if (numSilos == 1) { clientBuilder.UseLocalhostClustering(); } else { var gateways = Enumerable.Range(30000, numSilos).Select(i => new IPEndPoint(IPAddress.Loopback, i)).ToArray(); clientBuilder.UseStaticClustering(gateways); } }); this.clientHost = hostBuilder.Build(); this.clientHost.StartAsync().GetAwaiter().GetResult(); this.client = this.clientHost.Services.GetRequiredService(); var grainFactory = this.client; this.grain = grainFactory.GetGrain(Guid.NewGuid().GetHashCode()); this.grain.Run().AsTask().GetAwaiter().GetResult(); } _onCancelEvent = CancelPressed; Console.CancelKeyPress += _onCancelEvent; } private void CancelPressed(object sender, ConsoleCancelEventArgs e) { Environment.Exit(0); } [Benchmark] public ValueTask Ping() => grain.Run(); public async Task PingForever() { while (true) { await grain.Run(); } } public Task PingConcurrentForever() => this.Run( runs: int.MaxValue, grainFactory: this.client, blocksPerWorker: 10); public Task PingConcurrent() => this.Run( runs: 3, grainFactory: this.client, blocksPerWorker: 10); public Task PingConcurrentHostedClient(int blocksPerWorker = 30) => this.Run( runs: 3, grainFactory: (IGrainFactory)this.hosts[0].Services.GetService(typeof(IGrainFactory)), blocksPerWorker: blocksPerWorker); private async Task Run(int runs, IGrainFactory grainFactory, int blocksPerWorker) { var loadGenerator = new ConcurrentLoadGenerator( maxConcurrency: 250, blocksPerWorker: blocksPerWorker, requestsPerBlock: 500, issueRequest: g => g.Run(), getStateForWorker: workerId => grainFactory.GetGrain(workerId)); await loadGenerator.Warmup(); while (runs-- > 0) await loadGenerator.Run(); } public async Task PingPongForever() { var other = this.client.GetGrain(Guid.NewGuid().GetHashCode()); while (true) { await grain.PingPongInterleave(other, 100); } } public async Task Shutdown() { if (clientHost is { } client) { await client.StopAsync(); if (client is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { client.Dispose(); } } this.hosts.Reverse(); foreach (var host in this.hosts) { await host.StopAsync(); if (host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { host.Dispose(); } } } [GlobalCleanup] public void Dispose() { (this.client as IDisposable)?.Dispose(); this.hosts.ForEach(h => h.Dispose()); Console.CancelKeyPress -= _onCancelEvent; } } ================================================ FILE: test/Benchmarks/Ping/StatelessWorkerBenchmark.cs ================================================ using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Orleans.Concurrency; namespace Benchmarks.Ping; /// /// Benchmarks stateless worker grain performance and adaptive worker pool management. /// public class StatelessWorkerBenchmark : IDisposable { private readonly IHost _host; private readonly IGrainFactory _grainFactory; public StatelessWorkerBenchmark() { _host = new HostBuilder() .UseOrleans((_, siloBuilder) => siloBuilder .UseLocalhostClustering()) .Build(); _host.StartAsync().GetAwaiter().GetResult(); _grainFactory = _host.Services.GetRequiredService(); } public void Dispose() { _host.StopAsync().GetAwaiter().GetResult(); _host.Dispose(); } public async Task RunAsync() { await Run(_grainFactory.GetGrain(0)); await Run(_grainFactory.GetGrain(0)); } private async static Task Run(T grain) where T : IProcessorGrain where H : BaseGrain { Console.WriteLine($"Executing benchmark for {typeof(H).Name}"); using var cts = new CancellationTokenSource(); var statsCollector = Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { await Task.Delay(1, cts.Token); BaseGrain.UpdateStats(); } }, cts.Token); var tasks = new List(); const int ConcurrencyLevel = 100; const double Lambda = 10.0d; for (var i = 0; i < ConcurrencyLevel; i++) { // For a Poisson process with rate λ (tasks / sec in our case), the time between arrivals is // exponentially distributed with density: f(t) = λe^(-λt), t >= 0; and the interarrival // time can be generated as: Δt = -ln(U) / λ, where U is uniformly distributed on (0, 1) var u = Random.Shared.NextDouble(); var delaySec = -Math.Log(u > 0 ? u : double.Epsilon) / Lambda; var delayMs = (int)(1000 * delaySec); await Task.Delay(delayMs); tasks.Add(grain.Process()); } await Task.WhenAll(tasks); const int CooldownCycles = 10; for (var i = 1; i <= CooldownCycles; i++) { var cooldownCycle = $"({i}/{CooldownCycles})"; Console.WriteLine($"\nWaiting for cooldown {cooldownCycle}\n"); var cooldownMs = (int)(0.1 * Math.Ceiling(BenchmarkConstants.ProcessDelayMs * ((double)ConcurrencyLevel / BenchmarkConstants.MaxWorkersLimit))); await Task.Delay(cooldownMs); Console.WriteLine($"Stats {cooldownCycle}:"); Console.WriteLine($" Active Workers: {BaseGrain.GetActiveWorkers()}"); Console.WriteLine($" Average Workers: {BaseGrain.GetAverageActiveWorkers()}"); Console.WriteLine($" Maximum Workers: {BaseGrain.GetMaxActiveWorkers()}"); Console.Write("\n---------------------------------------------------------------------\n"); } cts.Cancel(); try { await statsCollector; } catch (OperationCanceledException) { } BaseGrain.Stop(); } public static class BenchmarkConstants { public const int MaxWorkersLimit = 10; public const int ProcessDelayMs = 1000; } public interface IProcessorGrain : IGrainWithIntegerKey { Task Process(); } public interface IAdaptiveGrain : IProcessorGrain { } public interface IMonotonicGrain : IProcessorGrain { } [StatelessWorker(BenchmarkConstants.MaxWorkersLimit, removeIdleWorkers: false)] public class SWMonotonicGrain : BaseGrain, IMonotonicGrain { } [StatelessWorker(BenchmarkConstants.MaxWorkersLimit, removeIdleWorkers: true)] public class SWAdaptiveGrain : BaseGrain, IAdaptiveGrain { } public abstract class BaseGrain : Grain, IProcessorGrain where T : BaseGrain { private static int _activeWorkers = 0; private static int _maxActiveWorkers = 0; private static long _totalWorkerTicks = 0; private static long _lastUpdateTicks = 0; private static int _watchStarted = 0; private static int _watchStopped = 0; private static readonly Stopwatch Watch = new(); public override Task OnActivateAsync(CancellationToken cancellationToken) { if (Interlocked.CompareExchange(ref _watchStarted, 1, 0) == 0) { Watch.Start(); } Interlocked.Increment(ref _activeWorkers); UpdateStats(); return Task.CompletedTask; } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { Interlocked.Decrement(ref _activeWorkers); UpdateStats(); if (Volatile.Read(ref _activeWorkers) == 0 && Interlocked.CompareExchange(ref _watchStopped, 1, 0) == 0) { Watch.Stop(); } return Task.CompletedTask; } public Task Process() => Task.Delay(BenchmarkConstants.ProcessDelayMs); public static void UpdateStats() { var currentWorkers = Volatile.Read(ref _activeWorkers); int oldMax; do { oldMax = Volatile.Read(ref _maxActiveWorkers); if (currentWorkers <= oldMax) { break; } } while (Interlocked.CompareExchange(ref _maxActiveWorkers, currentWorkers, oldMax) != oldMax); var elapsedTicks = Watch.Elapsed.Ticks; var previousUpdate = Interlocked.Exchange(ref _lastUpdateTicks, elapsedTicks); var elapsedSinceLastUpdate = elapsedTicks - previousUpdate; Interlocked.Add(ref _totalWorkerTicks, currentWorkers * elapsedSinceLastUpdate); } public static void Stop() { UpdateStats(); Watch.Stop(); } public static int GetActiveWorkers() => Volatile.Read(ref _activeWorkers); public static int GetMaxActiveWorkers() => Volatile.Read(ref _maxActiveWorkers); public static double GetAverageActiveWorkers() { var totalTicks = Volatile.Read(ref _totalWorkerTicks); var elapsedTicks = Watch.Elapsed.Ticks; var result = elapsedTicks == 0 ? 0 : (double)totalTicks / elapsedTicks; return Math.Round(result, MidpointRounding.ToEven); } } } ================================================ FILE: test/Benchmarks/Program.cs ================================================ using System.Diagnostics; using BenchmarkDotNet.Running; using Benchmarks.MapReduce; using Benchmarks.Ping; using Benchmarks.Transactions; using Benchmarks.GrainStorage; namespace Benchmarks; internal class Program { private static readonly Dictionary> _benchmarks = new Dictionary> { ["MapReduce"] = _ => { RunBenchmark( "Running MapReduce benchmark", () => { var mapReduceBenchmark = new MapReduceBenchmark(); mapReduceBenchmark.BenchmarkSetup(); return mapReduceBenchmark; }, benchmark => benchmark.Bench().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Transactions.Memory"] = _ => { RunBenchmark( "Running Transactions benchmark", () => { var benchmark = new TransactionBenchmark(2, 20000, 5000); benchmark.MemorySetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Transactions.Memory.Throttled"] = _ => { RunBenchmark( "Running Transactions benchmark", () => { var benchmark = new TransactionBenchmark(2, 200000, 15000); benchmark.MemoryThrottledSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Transactions.Azure"] = _ => { RunBenchmark( "Running Transactions benchmark", () => { var benchmark = new TransactionBenchmark(2, 20000, 5000); benchmark.AzureSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Transactions.Azure.Throttled"] = _ => { RunBenchmark( "Running Transactions benchmark", () => { var benchmark = new TransactionBenchmark(2, 200000, 15000); benchmark.AzureThrottledSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Transactions.Azure.Overloaded"] = _ => { RunBenchmark( "Running Transactions benchmark", () => { var benchmark = new TransactionBenchmark(2, 200000, 15000); benchmark.AzureSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["SequentialPing"] = _ => { BenchmarkRunner.Run(); }, ["ConcurrentPing"] = _ => { { Console.WriteLine("## Client to Silo ##"); var test = new PingBenchmark(numSilos: 1, startClient: true); test.PingConcurrent().GetAwaiter().GetResult(); test.Shutdown().GetAwaiter().GetResult(); } GC.Collect(); { Console.WriteLine("## Client to 2 Silos ##"); var test = new PingBenchmark(numSilos: 2, startClient: true); test.PingConcurrent().GetAwaiter().GetResult(); test.Shutdown().GetAwaiter().GetResult(); } GC.Collect(); { Console.WriteLine("## Hosted Client ##"); var test = new PingBenchmark(numSilos: 1, startClient: false); test.PingConcurrentHostedClient().GetAwaiter().GetResult(); test.Shutdown().GetAwaiter().GetResult(); } GC.Collect(); { // All calls are cross-silo because the calling silo doesn't have any grain classes. Console.WriteLine("## Silo to Silo ##"); var test = new PingBenchmark(numSilos: 2, startClient: false, grainsOnSecondariesOnly: true); test.PingConcurrentHostedClient(blocksPerWorker: 10).GetAwaiter().GetResult(); test.Shutdown().GetAwaiter().GetResult(); } GC.Collect(); { Console.WriteLine("## Hosted Client ##"); var test = new PingBenchmark(numSilos: 1, startClient: false); test.PingConcurrentHostedClient().GetAwaiter().GetResult(); test.Shutdown().GetAwaiter().GetResult(); } }, ["ConcurrentPing_OneSilo"] = _ => { new PingBenchmark(numSilos: 1, startClient: true).PingConcurrent().GetAwaiter().GetResult(); }, ["ConcurrentPing_TwoSilos"] = _ => { new PingBenchmark(numSilos: 2, startClient: true).PingConcurrent().GetAwaiter().GetResult(); }, ["ConcurrentPing_TwoSilos_Forever"] = _ => { Console.WriteLine("## Client to 2 Silos ##"); var test = new PingBenchmark(numSilos: 2, startClient: true); test.PingConcurrentForever().GetAwaiter().GetResult(); }, ["ConcurrentPing_HostedClient"] = _ => { new PingBenchmark(numSilos: 1, startClient: false).PingConcurrentHostedClient().GetAwaiter().GetResult(); }, ["ConcurrentPing_HostedClient_Forever"] = _ => { var benchmark = new PingBenchmark(numSilos: 1, startClient: false); Console.WriteLine("Press any key to begin."); Console.ReadKey(); Console.WriteLine("Press any key to end."); Console.WriteLine("## Hosted Client ##"); while (!Console.KeyAvailable) { benchmark.PingConcurrentHostedClient().GetAwaiter().GetResult(); } Console.WriteLine("Interrupted by user"); }, ["ConcurrentPing_SiloToSilo"] = _ => { new PingBenchmark(numSilos: 2, startClient: false, grainsOnSecondariesOnly: true).PingConcurrentHostedClient(blocksPerWorker: 10).GetAwaiter().GetResult(); }, ["ConcurrentPing_SiloToSilo_Forever"] = _ => { //Console.WriteLine("Press any key to begin."); //Console.ReadKey(); Console.WriteLine("Press any key to end."); Console.WriteLine("## Silo to Silo ##"); while (!Console.KeyAvailable) { Console.WriteLine("Initializing"); var test = new PingBenchmark(numSilos: 2, startClient: false, grainsOnSecondariesOnly: true); Console.WriteLine("Starting"); test.PingConcurrentHostedClient(blocksPerWorker: 100).GetAwaiter().GetResult(); Console.WriteLine("Stopping"); test.Shutdown().GetAwaiter().GetResult(); Console.WriteLine("Stopped"); } Console.WriteLine("Interrupted by user"); }, ["ConcurrentPing_SiloToSilo_Long"] = _ => { new PingBenchmark(numSilos: 2, startClient: false, grainsOnSecondariesOnly: true).PingConcurrentHostedClient(blocksPerWorker: 1000).GetAwaiter().GetResult(); }, ["ConcurrentPing_OneSilo_Forever"] = _ => { new PingBenchmark(numSilos: 1, startClient: true).PingConcurrentForever().GetAwaiter().GetResult(); }, ["PingOnce"] = _ => { new PingBenchmark().Ping().GetAwaiter().GetResult(); }, ["PingForever"] = _ => { new PingBenchmark().PingForever().GetAwaiter().GetResult(); }, ["PingPongForever"] = _ => { new PingBenchmark().PingPongForever().GetAwaiter().GetResult(); }, ["PingForever_Min_Threads"] = _ => { ThreadPool.SetMaxThreads(1, 1); new PingBenchmark().PingForever().GetAwaiter().GetResult(); }, ["FanoutForever"] = _ => { new FanoutBenchmark().PingForever().GetAwaiter().GetResult(); }, ["StatelessWorker"] = _ => { RunBenchmark("", () => new StatelessWorkerBenchmark(), benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Dispose()); }, ["GrainStorage.Memory"] = _ => { RunBenchmark( "Running grain storage benchmark against memory", () => { var benchmark = new GrainStorageBenchmark(10, 10000, TimeSpan.FromSeconds( 30 )); benchmark.MemorySetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["GrainStorage.AzureTable"] = _ => { RunBenchmark( "Running grain storage benchmark against Azure Table", () => { var benchmark = new GrainStorageBenchmark(100, 10000, TimeSpan.FromSeconds( 30 )); benchmark.AzureTableSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["GrainStorage.AzureBlob"] = _ => { RunBenchmark( "Running grain storage benchmark against Azure Blob", () => { var benchmark = new GrainStorageBenchmark(10, 10000, TimeSpan.FromSeconds( 30 )); benchmark.AzureBlobSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["GrainStorage.AdoNet"] = _ => { RunBenchmark( "Running grain storage benchmark against AdoNet", () => { var benchmark = new GrainStorageBenchmark(100, 10000, TimeSpan.FromSeconds( 30 )); benchmark.AdoNetSetup(); return benchmark; }, benchmark => benchmark.RunAsync().GetAwaiter().GetResult(), benchmark => benchmark.Teardown()); }, ["Dashboard"] = _ => { BenchmarkRunner.Run(); }, ["Dashboard.Manual"] = _ => { new Benchmarks.Dashboard.ManualTests().Run(); }, ["suite"] = args => { _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } }; // requires benchmark name or 'All' word as first parameter public static void Main(string[] args) { var slicedArgs = args.Skip(1).ToArray(); if (args.Length > 0 && args[0].Equals("all", StringComparison.InvariantCultureIgnoreCase)) { Console.WriteLine("Running full benchmarks suite"); _benchmarks.Select(pair => pair.Value).ToList().ForEach(action => action(slicedArgs)); return; } if (args.Length == 0 || !_benchmarks.ContainsKey(args[0])) { Console.WriteLine("Please, select benchmark, list of available:"); _benchmarks .Select(pair => pair.Key) .ToList() .ForEach(Console.WriteLine); Console.WriteLine("All"); return; } _benchmarks[args[0]](slicedArgs); } private static void RunBenchmark(string name, Func init, Action benchmarkAction, Action tearDown) { Console.WriteLine(name); var bench = init(); var stopWatch = Stopwatch.StartNew(); benchmarkAction(bench); Console.WriteLine($"Elapsed milliseconds: {stopWatch.ElapsedMilliseconds}"); Console.WriteLine("Press any key to continue ..."); tearDown(bench); Console.ReadLine(); } } ================================================ FILE: test/Benchmarks/Properties/launchSettings.json ================================================ { "profiles": { "Benchmarks": { "commandName": "Project", "commandLineArgs": "FanoutForever" } } } ================================================ FILE: test/Benchmarks/Serialization/Comparison/ArrayDeserializeBenchmark.cs ================================================ using System.Buffers; using System.Text; using System.Text.Json; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; using MessagePack; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; namespace Benchmarks.Serialization.Comparison; #pragma warning disable IDE1006 // Naming Styles /// /// Compares Orleans deserialization performance against other popular serializers for array types. /// [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] public class ArrayDeserializeBenchmark { private static readonly MyVector3[] _value; private static readonly Serializer _orleansSerializer; private static readonly byte[] _stjPayload; private static readonly byte[] _protobufPayload; private static readonly byte[] _orleansPayload; private static readonly byte[] _messagePackPayload; static ArrayDeserializeBenchmark() { _value = Enumerable.Repeat(new MyVector3 { X = 10.3f, Y = 40.5f, Z = 13411.3f }, 1000).ToArray(); var serviceProvider = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); _orleansSerializer = serviceProvider.GetRequiredService>(); _orleansPayload = _orleansSerializer.SerializeToArray(_value); _messagePackPayload = MessagePackSerializer.Serialize(_value); var stream = new MemoryStream(); ProtoBuf.Serializer.Serialize(stream, _value); stream.Position = 0; _protobufPayload = stream.ToArray(); _stjPayload = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_value)); } [Benchmark(Baseline = true)] public MyVector3[] MessagePackDeserialize() => MessagePackSerializer.Deserialize(_messagePackPayload); [Benchmark] public MyVector3[] ProtobufNetDeserialize() => ProtoBuf.Serializer.Deserialize(_protobufPayload.AsSpan()); [Benchmark] public MyVector3[] SystemTextJsonDeserialize() => JsonSerializer.Deserialize(_stjPayload); [Benchmark] public MyVector3[] OrleansDeserialize() => _orleansSerializer.Deserialize(_orleansPayload); } #pragma warning restore IDE1006 // Naming Styles ================================================ FILE: test/Benchmarks/Serialization/Comparison/ArraySerializeBenchmark.cs ================================================ using System.Buffers; using System.IO.Pipelines; using System.Text; using System.Text.Json; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; using MessagePack; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Xunit; namespace Benchmarks.Serialization.Comparison; /// /// Compares Orleans serialization performance against other popular serializers for array types. /// [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] public class ArraySerializeBenchmark { private static readonly MyVector3[] _value; private static readonly Serializer _orleansSerializer; private static readonly SerializerSession _session; private static readonly ArrayBufferWriter _arrayBufferWriter; private static readonly Utf8JsonWriter _jsonWriter; private static readonly MemoryStream _stream; private static readonly Pipe _pipe; static ArraySerializeBenchmark() { _value = Enumerable.Repeat(new MyVector3 { X = 10.3f, Y = 40.5f, Z = 13411.3f }, 1000).ToArray(); var serviceProvider = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); _orleansSerializer = serviceProvider.GetRequiredService>(); _session = serviceProvider.GetRequiredService().GetSession(); // create buffers _stream = new MemoryStream(); var serialize1 = _orleansSerializer.SerializeToArray(_value); var serialize2 = MessagePackSerializer.Serialize(_value); ProtoBuf.Serializer.Serialize(_stream, _value); var serialize3 = _stream.ToArray(); _stream.Position = 0; var serialize4 = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_value)); _arrayBufferWriter = new ArrayBufferWriter(new[] { serialize1, serialize2, serialize3, serialize4 }.Max(x => x.Length)); _jsonWriter = new Utf8JsonWriter(_arrayBufferWriter); _pipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: 0)); } // return byte[] [Benchmark(Baseline = true), BenchmarkCategory(" byte[]")] public byte[] MessagePackSerialize() { return MessagePackSerializer.Serialize(_value); } [Benchmark, BenchmarkCategory(" byte[]")] public byte[] ProtobufNetSerialize() { ProtoBuf.Serializer.Serialize(_stream, _value); var array = _stream.ToArray(); _stream.Position = 0; return array; } [Benchmark, BenchmarkCategory(" byte[]")] public byte[] SystemTextJsonSerialize() { JsonSerializer.Serialize(_stream, _value); var array = _stream.ToArray(); _stream.Position = 0; return array; } [Benchmark, BenchmarkCategory(" byte[]")] public byte[] OrleansSerialize() { return _orleansSerializer.SerializeToArray(_value); } // use BufferWriter [Fact] [Benchmark(Baseline = true), BenchmarkCategory("BufferWriter")] public void MessagePackBufferWriter() { MessagePackSerializer.Serialize(_arrayBufferWriter, _value); _arrayBufferWriter.Clear(); } [Fact] [Benchmark, BenchmarkCategory("BufferWriter")] public void ProtobufNetBufferWriter() { ProtoBuf.Serializer.Serialize(_arrayBufferWriter, _value); _arrayBufferWriter.Clear(); } [Fact] [Benchmark, BenchmarkCategory("BufferWriter")] public void SystemTextJsonBufferWriter() { JsonSerializer.Serialize(_jsonWriter, _value); _jsonWriter.Flush(); _arrayBufferWriter.Clear(); _jsonWriter.Reset(_arrayBufferWriter); } [Fact] [Benchmark, BenchmarkCategory("BufferWriter")] public void OrleansBufferWriter() { var writer = Writer.CreatePooled(_session); try { _orleansSerializer.Serialize(_value, ref writer); } finally { writer.Dispose(); _session.Reset(); } } [Fact] [Benchmark, BenchmarkCategory("BufferWriter")] public void OrleansBufferWriter2() { // wrap ArrayBufferWriter var writer = _arrayBufferWriter.CreateWriter(_session); try { _orleansSerializer.Serialize(_value, ref writer); } finally { writer.Dispose(); _session.Reset(); } _arrayBufferWriter.Clear(); // clear ArrayBufferWriter } [Fact] [Benchmark] public void OrleansPipeWriter() { var writer = _pipe.Writer.CreateWriter(_session); _orleansSerializer.Serialize(_value, ref writer); _session.Reset(); _pipe.Writer.Complete(); _pipe.Reader.Complete(); _pipe.Reset(); } } ================================================ FILE: test/Benchmarks/Serialization/Comparison/ClassDeserializeBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using Benchmarks.Models; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Xunit; using SerializerSession = Orleans.Serialization.Session.SerializerSession; using Utf8JsonNS = Utf8Json; using Hyperion; using System.Buffers; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization.Comparison; /// /// Compares Orleans deserialization performance against other popular serializers for class types. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] //[DisassemblyDiagnoser(recursiveDepth: 2, printSource: true)] //[EtwProfiler] public class ClassDeserializeBenchmark { private static readonly MemoryStream ProtoInput; private static readonly ReadOnlySequence GoogleProtoInput; private static readonly byte[] MsgPackInput = MessagePack.MessagePackSerializer.Serialize(IntClass.Create()); private static readonly string NewtonsoftJsonInput = JsonConvert.SerializeObject(IntClass.Create()); private static readonly byte[] SpanJsonInput = SpanJson.JsonSerializer.Generic.Utf8.Serialize(IntClass.Create()); private static readonly Hyperion.Serializer HyperionSerializer = new(SerializerOptions.Default.WithKnownTypes(new[] { typeof(IntClass) })); private static readonly MemoryStream HyperionInput; private static readonly Serializer Serializer; private static readonly byte[] Input; private static readonly SerializerSession Session; private static readonly DeserializerSession HyperionSession; private static readonly Utf8JsonNS.IJsonFormatterResolver Utf8JsonResolver = Utf8JsonNS.Resolvers.StandardResolver.Default; private static readonly byte[] Utf8JsonInput; private static readonly byte[] SystemTextJsonInput; static ClassDeserializeBenchmark() { ProtoInput = new MemoryStream(); ProtoBuf.Serializer.Serialize(ProtoInput, IntClass.Create()); ProtoInput = new MemoryStream(); GoogleProtoInput = new ReadOnlySequence(Google.Protobuf.MessageExtensions.ToByteArray(ProtoIntClass.Create())); HyperionInput = new MemoryStream(); HyperionSession = HyperionSerializer.GetDeserializerSession(); HyperionSerializer.Serialize(IntClass.Create(), HyperionInput); // var services = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); Serializer = services.GetRequiredService>(); var bytes = new byte[1000]; Session = services.GetRequiredService().GetSession(); var writer = new SingleSegmentBuffer(bytes).CreateWriter(Session); Serializer.Serialize(IntClass.Create(), ref writer); Input = bytes; Utf8JsonInput = Utf8JsonNS.JsonSerializer.Serialize(IntClass.Create(), Utf8JsonResolver); var stream = new MemoryStream(); using (var jsonWriter = new System.Text.Json.Utf8JsonWriter(stream)) { System.Text.Json.JsonSerializer.Serialize(jsonWriter, IntClass.Create()); } SystemTextJsonInput = stream.ToArray(); } private static int SumResult(IntClass result) => result.MyProperty1 + result.MyProperty2 + result.MyProperty3 + result.MyProperty4 + result.MyProperty5 + result.MyProperty6 + result.MyProperty7 + result.MyProperty8 + result.MyProperty9; private static int SumResult(ProtoIntClass result) => result.MyProperty1 + result.MyProperty2 + result.MyProperty3 + result.MyProperty4 + result.MyProperty5 + result.MyProperty6 + result.MyProperty7 + result.MyProperty8 + result.MyProperty9; [Benchmark(Baseline = true)] public int Orleans() { Session.Reset(); var instance = Serializer.Deserialize(Input, Session); return SumResult(instance); } [Benchmark] public int Utf8Json() => SumResult(Utf8JsonNS.JsonSerializer.Deserialize(Utf8JsonInput, Utf8JsonResolver)); [Benchmark] public int SystemTextJson() => SumResult(System.Text.Json.JsonSerializer.Deserialize(SystemTextJsonInput)); [Benchmark] public int MessagePackCSharp() => SumResult(MessagePack.MessagePackSerializer.Deserialize(MsgPackInput)); [Benchmark] public int ProtobufNet() { ProtoInput.Position = 0; return SumResult(ProtoBuf.Serializer.Deserialize(ProtoInput)); } [Benchmark] public int GoogleProtobuf() { return SumResult(ProtoIntClass.Parser.ParseFrom(GoogleProtoInput)); } [Benchmark] public int Hyperion() { HyperionInput.Position = 0; return SumResult(HyperionSerializer.Deserialize(HyperionInput, HyperionSession)); } [Benchmark] public int NewtonsoftJson() => SumResult(JsonConvert.DeserializeObject(NewtonsoftJsonInput)); [Benchmark(Description = "SpanJson")] public int SpanJsonUtf8() => SumResult(SpanJson.JsonSerializer.Generic.Utf8.Deserialize(SpanJsonInput)); } ================================================ FILE: test/Benchmarks/Serialization/Comparison/ClassSerializeBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using Benchmarks.Models; using Orleans.Serialization; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using System.Text; using System.Text.Json; using Xunit; using SerializerSession = Orleans.Serialization.Session.SerializerSession; using Utf8JsonNS = Utf8Json; using Hyperion; using ZeroFormatter; using Google.Protobuf; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization.Comparison; /// /// Compares Orleans serialization performance against other popular serializers for class types. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] [PayloadSizeColumn] public class ClassSerializeBenchmark { private static readonly IntClass Input = IntClass.Create(); private static readonly VirtualIntsClass ZeroFormatterInput = VirtualIntsClass.Create(); private static readonly IBufferMessage ProtoInput = ProtoIntClass.Create(); private static readonly Hyperion.Serializer HyperionSerializer = new(SerializerOptions.Default.WithKnownTypes(new[] { typeof(IntClass) })); private static readonly Hyperion.SerializerSession HyperionSession; private static readonly MemoryStream HyperionBuffer = new(); private static readonly Serializer Serializer; private static readonly byte[] Data; private static readonly SerializerSession Session; private static readonly MemoryStream ProtoBuffer = new(); private static readonly ClassSingleSegmentBuffer ProtoSegmentBuffer; private static readonly MemoryStream Utf8JsonOutput = new(); private static readonly Utf8JsonNS.IJsonFormatterResolver Utf8JsonResolver = Utf8JsonNS.Resolvers.StandardResolver.Default; private static readonly MemoryStream SystemTextJsonOutput = new(); private static readonly Utf8JsonWriter SystemTextJsonWriter; static ClassSerializeBenchmark() { var services = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); Serializer = services.GetRequiredService>(); Session = services.GetRequiredService().GetSession(); Data = new byte[1000]; HyperionSession = HyperionSerializer.GetSerializerSession(); SystemTextJsonWriter = new Utf8JsonWriter(SystemTextJsonOutput); ProtoSegmentBuffer = new ClassSingleSegmentBuffer(Data); } [Benchmark(Baseline = true)] public long Orleans() { Session.Reset(); return Serializer.Serialize(Input, Data, Session); } [Benchmark] public long Utf8Json() { Utf8JsonOutput.Position = 0; Utf8JsonNS.JsonSerializer.Serialize(Utf8JsonOutput, Input, Utf8JsonResolver); return Utf8JsonOutput.Length; } [Benchmark] public long SystemTextJson() { SystemTextJsonOutput.Position = 0; System.Text.Json.JsonSerializer.Serialize(SystemTextJsonWriter, Input); SystemTextJsonWriter.Reset(); return SystemTextJsonOutput.Length; } [Benchmark] public int MessagePackCSharp() { var bytes = MessagePack.MessagePackSerializer.Serialize(Input); return bytes.Length; } [Benchmark] public long ProtobufNet() { ProtoBuffer.Position = 0; ProtoBuf.Serializer.Serialize(ProtoBuffer, Input); return ProtoBuffer.Length; } [Benchmark] public long GoogleProtobuf() { ProtoSegmentBuffer.Reset(); ProtoInput.WriteTo(ProtoSegmentBuffer); return ProtoSegmentBuffer.Length; } [Benchmark] public long Hyperion() { HyperionBuffer.Position = 0; HyperionSerializer.Serialize(Input, HyperionBuffer, HyperionSession); return HyperionBuffer.Length; } //[Benchmark] public int ZeroFormatter() { var bytes = ZeroFormatterSerializer.Serialize(ZeroFormatterInput); return bytes.Length; } [Benchmark] public int NewtonsoftJson() { var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(Input)); return bytes.Length; } [Benchmark(Description = "SpanJson")] public int SpanJsonUtf8() { var bytes = SpanJson.JsonSerializer.Generic.Utf8.Serialize(Input); return bytes.Length; } } ================================================ FILE: test/Benchmarks/Serialization/Comparison/CopierBenchmark.cs ================================================ using System.Buffers; using BenchmarkDotNet.Attributes; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization; using Orleans.Serialization.Session; namespace Benchmarks.Serialization.Comparison; /// /// Benchmarks Orleans deep copy performance for various object types including arrays and value types. /// [Config(typeof(BenchmarkConfig))] public class CopierBenchmark { private static readonly MyVector3[] _vectorArray; private static readonly DeepCopier _arrayCopier; private static readonly DeepCopier _structCopier; private static readonly IntStruct _intStruct; private static readonly DeepCopier _classCopier; private static readonly ImmutableVector3[] _arrayOfImmutableVectors; private static readonly IntClass _intClass; private static readonly DeepCopier _arrayOfImmutableVectorsCopier; private static readonly SerializerSession _session; static CopierBenchmark() { _vectorArray = Enumerable.Repeat(new MyVector3 { X = 10.3f, Y = 40.5f, Z = 13411.3f }, 1000).ToArray(); _arrayOfImmutableVectors = Enumerable.Repeat(new ImmutableVector3 { X = 10.3f, Y = 40.5f, Z = 13411.3f }, 1000).ToArray(); var serviceProvider = new ServiceCollection() .AddSerializer(builder => builder.AddAssembly(typeof(ArraySerializeBenchmark).Assembly)) .BuildServiceProvider(); _arrayCopier = serviceProvider.GetRequiredService>(); _structCopier = serviceProvider.GetRequiredService>(); _intStruct = IntStruct.Create(); _classCopier = serviceProvider.GetRequiredService>(); _intClass = IntClass.Create(); _arrayOfImmutableVectorsCopier = serviceProvider.GetRequiredService>(); _session = serviceProvider.GetRequiredService().GetSession(); } [Benchmark] public void VectorArray() => _arrayCopier.Copy(_vectorArray); [Benchmark] public void ImmutableVectorArray() => _arrayOfImmutableVectorsCopier.Copy(_arrayOfImmutableVectors); [Benchmark] public void Struct() => _structCopier.Copy(_intStruct); [Benchmark] public void Class() => _classCopier.Copy(_intClass); } ================================================ FILE: test/Benchmarks/Serialization/Comparison/StructDeserializeBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Xunit; using SerializerSession = Orleans.Serialization.Session.SerializerSession; using Utf8JsonNS = Utf8Json; using Hyperion; using ZeroFormatter; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization.Comparison; /// /// Compares Orleans deserialization performance against other popular serializers for struct types. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] //[DisassemblyDiagnoser(recursiveDepth: 4)] //[EtwProfiler] public class StructDeserializeBenchmark { private static readonly MemoryStream ProtoInput; private static readonly string NewtonsoftJsonInput = JsonConvert.SerializeObject(IntStruct.Create()); private static readonly byte[] SpanJsonInput = SpanJson.JsonSerializer.Generic.Utf8.Serialize(IntStruct.Create()); private static readonly byte[] MsgPackInput = MessagePack.MessagePackSerializer.Serialize(IntStruct.Create()); private static readonly byte[] ZeroFormatterInput = ZeroFormatterSerializer.Serialize(IntStruct.Create()); private static readonly Hyperion.Serializer HyperionSerializer = new(SerializerOptions.Default.WithKnownTypes(new[] { typeof(IntStruct) })); private static readonly MemoryStream HyperionInput; private static readonly DeserializerSession HyperionSession; private static readonly ValueSerializer Serializer; private static readonly byte[] Input; private static readonly SerializerSession Session; private static readonly Utf8JsonNS.IJsonFormatterResolver Utf8JsonResolver = Utf8JsonNS.Resolvers.StandardResolver.Default; private static readonly byte[] Utf8JsonInput; private static readonly byte[] SystemTextJsonInput; static StructDeserializeBenchmark() { ProtoInput = new MemoryStream(); ProtoBuf.Serializer.Serialize(ProtoInput, IntStruct.Create()); HyperionInput = new MemoryStream(); HyperionSerializer.Serialize(IntStruct.Create(), HyperionInput); // var services = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); Serializer = services.GetRequiredService>(); Session = services.GetRequiredService().GetSession(); var bytes = new byte[1000]; var writer = new SingleSegmentBuffer(bytes).CreateWriter(Session); IntStruct intStruct = IntStruct.Create(); Serializer.Serialize(ref intStruct, ref writer); Input = bytes; HyperionSession = HyperionSerializer.GetDeserializerSession(); Utf8JsonInput = Utf8JsonNS.JsonSerializer.Serialize(IntStruct.Create(), Utf8JsonResolver); var stream = new MemoryStream(); using (var jsonWriter = new System.Text.Json.Utf8JsonWriter(stream)) { System.Text.Json.JsonSerializer.Serialize(jsonWriter, IntStruct.Create()); } SystemTextJsonInput = stream.ToArray(); } private static int SumResult(in IntStruct result) => result.MyProperty1 + result.MyProperty2 + result.MyProperty3 + result.MyProperty4 + result.MyProperty5 + result.MyProperty6 + result.MyProperty7 + result.MyProperty8 + result.MyProperty9; [Benchmark(Baseline = true)] public int Orleans() { Session.Reset(); IntStruct result = default; Serializer.Deserialize(Input, ref result, Session); return SumResult(in result); } [Benchmark] public int Utf8Json() => SumResult(Utf8JsonNS.JsonSerializer.Deserialize(Utf8JsonInput, Utf8JsonResolver)); [Benchmark] public int SystemTextJson() => SumResult(System.Text.Json.JsonSerializer.Deserialize(SystemTextJsonInput)); [Benchmark] public int MessagePackCSharp() => SumResult(MessagePack.MessagePackSerializer.Deserialize(MsgPackInput)); [Benchmark] public int ProtobufNet() { ProtoInput.Position = 0; return SumResult(ProtoBuf.Serializer.Deserialize(ProtoInput)); } [Benchmark] public int Hyperion() { HyperionInput.Position = 0; return SumResult(HyperionSerializer.Deserialize(HyperionInput, HyperionSession)); } //[Benchmark] public int ZeroFormatter() => SumResult(ZeroFormatterSerializer.Deserialize(ZeroFormatterInput)); [Benchmark] public int NewtonsoftJson() => SumResult(JsonConvert.DeserializeObject(NewtonsoftJsonInput)); [Benchmark(Description = "SpanJson")] public int SpanJsonUtf8() => SumResult(SpanJson.JsonSerializer.Generic.Utf8.Deserialize(SpanJsonInput)); } ================================================ FILE: test/Benchmarks/Serialization/Comparison/StructSerializeBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using Orleans.Serialization; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using System.Text; using System.Text.Json; using Xunit; using SerializerSession = Orleans.Serialization.Session.SerializerSession; using Utf8JsonNS = Utf8Json; using Hyperion; using ZeroFormatter; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization.Comparison; /// /// Compares Orleans serialization performance against other popular serializers for struct types. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] [BenchmarkCategory("Serialization")] [PayloadSizeColumn] public class StructSerializeBenchmark { private static readonly IntStruct Input = IntStruct.Create(); private static readonly Hyperion.Serializer HyperionSerializer = new(SerializerOptions.Default.WithKnownTypes(new[] { typeof(IntStruct) })); private static readonly Hyperion.SerializerSession HyperionSession; private static readonly Serializer Serializer; private static readonly byte[] Data; private static readonly SerializerSession Session; private static readonly MemoryStream ProtoBuffer = new(); private static readonly MemoryStream HyperionBuffer = new(); private static readonly MemoryStream Utf8JsonOutput = new(); private static readonly Utf8JsonNS.IJsonFormatterResolver Utf8JsonResolver = Utf8JsonNS.Resolvers.StandardResolver.Default; private static readonly MemoryStream SystemTextJsonOutput = new(); private static readonly Utf8JsonWriter SystemTextJsonWriter; static StructSerializeBenchmark() { // var services = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); Serializer = services.GetRequiredService>(); Data = new byte[1000]; Session = services.GetRequiredService().GetSession(); HyperionSession = HyperionSerializer.GetSerializerSession(); SystemTextJsonWriter = new Utf8JsonWriter(SystemTextJsonOutput); } [Benchmark(Baseline = true)] public long Orleans() { Session.Reset(); return Serializer.Serialize(Input, Data, Session); } [Benchmark] public long Utf8Json() { Utf8JsonOutput.Position = 0; Utf8JsonNS.JsonSerializer.Serialize(Utf8JsonOutput, Input, Utf8JsonResolver); return Utf8JsonOutput.Length; } [Benchmark] public long SystemTextJson() { SystemTextJsonOutput.Position = 0; System.Text.Json.JsonSerializer.Serialize(SystemTextJsonWriter, Input); SystemTextJsonWriter.Reset(); return SystemTextJsonOutput.Length; } [Benchmark] public int MessagePackCSharp() { var bytes = MessagePack.MessagePackSerializer.Serialize(Input); return bytes.Length; } [Benchmark] public long ProtobufNet() { ProtoBuffer.Position = 0; ProtoBuf.Serializer.Serialize(ProtoBuffer, Input); return ProtoBuffer.Length; } [Benchmark] public long Hyperion() { HyperionBuffer.Position = 0; HyperionSerializer.Serialize(Input, HyperionBuffer, HyperionSession); return HyperionBuffer.Length; } //[Benchmark] public int ZeroFormatter() { var bytes = ZeroFormatterSerializer.Serialize(Input); return bytes.Length; } [Benchmark] public int NewtonsoftJson() { var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(Input)); return bytes.Length; } [Benchmark(Description = "SpanJson")] public int SpanJsonUtf8() { var bytes = SpanJson.JsonSerializer.Generic.Utf8.Serialize(Input); return bytes.Length; } } ================================================ FILE: test/Benchmarks/Serialization/ComplexTypeBenchmarks.cs ================================================ using System.Buffers; using System.IO.Pipelines; using BenchmarkDotNet.Attributes; using Benchmarks.Serialization.Models; using Benchmarks.Serialization.Utilities; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Networking.Shared; using Orleans.Runtime.Messaging; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Invocation; using Orleans.Serialization.Session; using Xunit; namespace Benchmarks.Serialization; /// /// Benchmarks Orleans serialization performance for complex object graphs with circular references. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] [MemoryDiagnoser] public class ComplexTypeBenchmarks { private static SingleSegmentBuffer Buffer = new(new byte[1000]); private readonly Serializer _structSerializer; private readonly DeepCopier _structCopier; private readonly Serializer _serializer; private readonly DeepCopier _copier; private readonly SerializerSessionPool _sessionPool; private readonly ComplexClass _value; private readonly SerializerSession _session; private readonly ReadOnlySequence _serializedPayload; private readonly long _readBytesLength; private readonly Pipe _pipe; private readonly MessageSerializer _messageSerializer; private readonly SimpleStruct _structValue; private readonly Message _message; private readonly Message _structMessage; public ComplexTypeBenchmarks() { var services = new ServiceCollection(); _ = services .AddSerializer(); var serviceProvider = services.BuildServiceProvider(); _serializer = serviceProvider.GetRequiredService>(); _copier = serviceProvider.GetRequiredService>(); _structSerializer = serviceProvider.GetRequiredService>(); _structCopier = serviceProvider.GetRequiredService>(); _sessionPool = serviceProvider.GetRequiredService(); _value = new ComplexClass { BaseInt = 192, Int = 501, String = "bananas", //Array = Enumerable.Range(0, 60).ToArray(), //MultiDimensionalArray = new[,] {{0, 2, 4}, {1, 5, 6}} }; _value.AlsoSelf = _value.BaseSelf = _value.Self = _value; _message = new() { BodyObject = new Response { TypedResult = _value } }; _structValue = new SimpleStruct { Int = 42, Bool = true, Guid = Guid.NewGuid() }; _structMessage = new() { BodyObject = new Response { TypedResult = _structValue } }; _session = _sessionPool.GetSession(); var writer = Buffer.CreateWriter(_session); _serializer.Serialize(_value, ref writer); var bytes = new byte[writer.Output.GetMemory().Length]; writer.Output.GetReadOnlySpan().CopyTo(bytes); _serializedPayload = new ReadOnlySequence(bytes); Buffer.Reset(); _readBytesLength = _serializedPayload.Length; _pipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: 0)); var memoryPool = new SharedMemoryPool(); _messageSerializer = new(_sessionPool, memoryPool, new SiloMessagingOptions()); } [Fact] public void SerializeComplex() { var writer = Buffer.CreateWriter(_session); _session.Reset(); _serializer.Serialize(_value, ref writer); _session.Reset(); var reader = Reader.Create(writer.Output.GetReadOnlySpan(), _session); _ = _serializer.Deserialize(ref reader); Buffer.Reset(); } [Fact] public void CopyComplex() { _copier.Copy(_value); } [Fact] public void CopyComplexStruct() { _structCopier.Copy(_structValue); } [Benchmark] public SimpleStruct OrleansStructRoundTrip() { var writer = Buffer.CreateWriter(_session); _session.Reset(); _structSerializer.Serialize(_structValue, ref writer); _session.Reset(); var reader = Reader.Create(writer.Output.GetReadOnlySpan(), _session); var result = _structSerializer.Deserialize(ref reader); Buffer.Reset(); return result; } /* [Fact] [Benchmark] public void OrleansMessageSerializerStructRoundTrip() { var buffer = new PooledBuffer(); var (headerLength, bodyLength) = _messageSerializer.Write(ref buffer, _structMessage); var readBuffer = buffer.Slice(); _messageSerializer.Read(in readBuffer, headerLength, bodyLength, out var result); ((Response)result.BodyObject).Dispose(); } */ //[Benchmark] public object OrleansClassRoundTrip() { var writer = Buffer.CreateWriter(_session); _session.Reset(); _serializer.Serialize(_value, ref writer); _session.Reset(); var reader = Reader.Create(writer.Output.GetReadOnlySpan(), _session); var result = _serializer.Deserialize(ref reader); Buffer.Reset(); return result; } /* [Fact] //[Benchmark] public void OrleansMessageSerializerClassRoundTrip() { var buffer = new PooledBuffer(); var (headerLength, bodyLength) = _messageSerializer.Write(ref buffer, _message); var readBuffer = buffer.Slice(); _messageSerializer.Read(in readBuffer, headerLength, bodyLength, out var result); ((Response)result.BodyObject).Dispose(); _pipe.Writer.Complete(); _pipe.Reader.Complete(); _pipe.Reset(); } */ //[Benchmark] public object OrleansSerialize() { var writer = Buffer.CreateWriter(_session); _session.Reset(); _serializer.Serialize(_value, ref writer); Buffer.Reset(); return _session; } //[Benchmark] public object OrleansDeserialize() { _session.Reset(); var reader = Reader.Create(_serializedPayload, _session); return _serializer.Deserialize(ref reader); } //[Benchmark] public int OrleansReadEachByte() { var sum = 0; var reader = Reader.Create(_serializedPayload, _session); for (var i = 0; i < _readBytesLength; i++) { sum ^= reader.ReadByte(); } return sum; } } ================================================ FILE: test/Benchmarks/Serialization/FieldHeaderBenchmarks.cs ================================================ using BenchmarkDotNet.Attributes; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Orleans.Serialization.WireProtocol; using Microsoft.Extensions.DependencyInjection; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization; /// /// Benchmarks Orleans wire protocol field header encoding and writing performance. /// [Config(typeof(BenchmarkConfig))] public class FieldHeaderBenchmarks { private static readonly SerializerSession Session; private static readonly byte[] OrleansBuffer = new byte[1000]; static FieldHeaderBenchmarks() { var services = new ServiceCollection().AddSerializer(); var serviceProvider = services.BuildServiceProvider(); var sessionPool = serviceProvider.GetRequiredService(); Session = sessionPool.GetSession(); } [Benchmark(Baseline = true)] public void WritePlainExpectedEmbeddedId() { var writer = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); // Use an expected type and a field id with a value small enough to be embedded. writer.WriteFieldHeader(4, typeof(uint), typeof(uint), WireType.VarInt); } [Benchmark] public void WritePlainExpectedExtendedId() { var writer = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); // Use a field id delta which is too large to be embedded. writer.WriteFieldHeader(Tag.MaxEmbeddedFieldIdDelta + 20, typeof(uint), typeof(uint), WireType.VarInt); } [Benchmark] public void WriteFastEmbedded() { var writer = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); // Use an expected type and a field id with a value small enough to be embedded. writer.WriteFieldHeaderExpected(4, WireType.VarInt); } [Benchmark] public void WriteFastExtended() { var writer = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); // Use a field id delta which is too large to be embedded. writer.WriteFieldHeaderExpected(Tag.MaxEmbeddedFieldIdDelta + 20, WireType.VarInt); } [Benchmark] public void CreateWriter() => _ = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); [Benchmark] public void WriteByte() { var writer = new SingleSegmentBuffer(OrleansBuffer).CreateWriter(Session); writer.WriteByte((byte)4); } } ================================================ FILE: test/Benchmarks/Serialization/MegaGraphBenchmark.cs ================================================ using BenchmarkDotNet.Attributes; using Orleans.Serialization; using Orleans.Serialization.Buffers; using Orleans.Serialization.Session; using Microsoft.Extensions.DependencyInjection; using System.Buffers; using System.Globalization; using System.IO.Pipelines; using Xunit; using SerializerSession = Orleans.Serialization.Session.SerializerSession; using Benchmarks.Serialization.Utilities; namespace Benchmarks.Serialization; /// /// Benchmarks Orleans serialization performance for very large object graphs with hundreds of thousands of items. /// [Trait("Category", "Benchmark")] [Config(typeof(BenchmarkConfig))] public class MegaGraphBenchmark { private static readonly Serializer> Serializer; private static readonly byte[] Input; private static readonly SerializerSession Session; private static readonly Dictionary Value; static MegaGraphBenchmark() { const int Size = 250_000; Value = new Dictionary(Size); for (var i = 0; i < Size; i++) { Value[i.ToString(CultureInfo.InvariantCulture)] = i; } var services = new ServiceCollection() .AddSerializer() .BuildServiceProvider(); Serializer = services.GetRequiredService>>(); Session = services.GetRequiredService().GetSession(); var pipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline)); var writer = pipe.Writer.CreateWriter(Session); Serializer.Serialize(Value, ref writer); pipe.Writer.FlushAsync(); pipe.Reader.TryRead(out var result); Input = result.Buffer.ToArray(); } [Benchmark] public object Deserialize() { Session.Reset(); var instance = Serializer.Deserialize(Input, Session); return instance; } [Benchmark] public int Serialize() { Session.Reset(); var writer = Writer.CreatePooled(Session); Serializer.Serialize(Value, ref writer); writer.Dispose(); return writer.Position; } } ================================================ FILE: test/Benchmarks/Serialization/Models/ComplexClass.cs ================================================ namespace Benchmarks.Serialization.Models; [Serializable] [GenerateSerializer] public class ComplexClass : SimpleClass { [Id(0)] public int Int { get; set; } [Id(1)] public string String { get; set; } [Id(2)] public ComplexClass Self { get; set; } [Id(3)] public object AlsoSelf { get; set; } [Id(4)] public SimpleClass BaseSelf { get; set; } [Id(5)] public int[] Array { get; set; } [Id(6)] public int[,] MultiDimensionalArray { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Models/IntClass.cs ================================================ using MessagePack; using ProtoBuf; namespace Benchmarks.Serialization.Models; [Serializable] [GenerateSerializer] [ProtoContract] [MessagePackObject] public sealed class IntClass { public static IntClass Create() { var result = new IntClass(); result.Initialize(); return result; } public void Initialize() => MyProperty1 = MyProperty2 = MyProperty3 = MyProperty4 = MyProperty5 = MyProperty6 = MyProperty7 = MyProperty8 = MyProperty9 = 10; [Id(0)] [ProtoMember(1)] [Key(0)] public int MyProperty1 { get; set; } [Id(1)] [ProtoMember(2)] [Key(1)] public int MyProperty2 { get; set; } [Id(2)] [ProtoMember(3)] [Key(2)] public int MyProperty3 { get; set; } [Id(3)] [ProtoMember(4)] [Key(3)] public int MyProperty4 { get; set; } [Id(4)] [ProtoMember(5)] [Key(4)] public int MyProperty5 { get; set; } [Id(5)] [ProtoMember(6)] [Key(5)] public int MyProperty6 { get; set; } [Id(6)] [ProtoMember(7)] [Key(6)] public int MyProperty7 { get; set; } [Id(7)] [ProtoMember(8)] [Key(7)] public int MyProperty8 { get; set; } [Id(8)] [ProtoMember(9)] [Key(8)] public int MyProperty9 { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Models/IntStruct.cs ================================================ using MessagePack; using ProtoBuf; using ZeroFormatter; namespace Benchmarks.Serialization.Models; [Serializable] [GenerateSerializer] [ProtoContract] [ZeroFormattable] [MessagePackObject] public struct IntStruct { public static IntStruct Create() { var result = new IntStruct(); result.Initialize(); return result; } public void Initialize() => MyProperty1 = MyProperty2 = MyProperty3 = MyProperty4 = MyProperty5 = MyProperty6 = MyProperty7 = MyProperty8 = MyProperty9 = 10; public IntStruct(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9) { MyProperty1 = p1; MyProperty2 = p2; MyProperty3 = p3; MyProperty4 = p4; MyProperty5 = p5; MyProperty6 = p6; MyProperty7 = p7; MyProperty8 = p8; MyProperty9 = p9; } [Id(0)] [Index(0)] [Key(0)] [ProtoMember(1)] public int MyProperty1 { get; set; } [Id(1)] [Index(1)] [Key(1)] [ProtoMember(2)] public int MyProperty2 { get; set; } [Id(2)] [Index(2)] [Key(2)] [ProtoMember(3)] public int MyProperty3 { get; set; } [Id(3)] [Index(3)] [Key(3)] [ProtoMember(4)] public int MyProperty4 { get; set; } [Id(4)] [Key(4)] [Index(4)] [ProtoMember(5)] public int MyProperty5 { get; set; } [Id(5)] [Key(5)] [Index(5)] [ProtoMember(6)] public int MyProperty6 { get; set; } [Id(6)] [Index(6)] [Key(6)] [ProtoMember(7)] public int MyProperty7 { get; set; } [Id(7)] [ProtoMember(8)] [Index(7)] [Key(7)] public int MyProperty8 { get; set; } [Id(8)] [ProtoMember(9)] [Index(8)] [Key(8)] public int MyProperty9 { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Models/ProtoIntClass.cs ================================================ namespace Benchmarks.Models; public partial class ProtoIntClass { public static ProtoIntClass Create() { var result = new ProtoIntClass(); result.Initialize(); return result; } public void Initialize() => MyProperty1 = MyProperty2 = MyProperty3 = MyProperty4 = MyProperty5 = MyProperty6 = MyProperty7 = MyProperty8 = MyProperty9 = 10; } ================================================ FILE: test/Benchmarks/Serialization/Models/ProtoIntClass.proto ================================================ syntax = "proto3"; package Benchmarks.Models; message ProtoIntClass { int32 my_property_1 = 1; int32 my_property_2 = 2; int32 my_property_3 = 3; int32 my_property_4 = 4; int32 my_property_5 = 5; int32 my_property_6 = 6; int32 my_property_7 = 7; int32 my_property_8 = 8; int32 my_property_9 = 9; } ================================================ FILE: test/Benchmarks/Serialization/Models/SimpleClass.cs ================================================ namespace Benchmarks.Serialization.Models; [Serializable] [GenerateSerializer] public class SimpleClass { [Id(0)] public int BaseInt { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Models/SimpleStruct.cs ================================================ namespace Benchmarks.Serialization.Models; [Serializable] [GenerateSerializer] public struct SimpleStruct { [Id(0)] public int Int { get; set; } [Id(1)] public bool Bool { get; set; } [Id(3)] public object AlwaysNull { get; set; } [Id(4)] public Guid Guid { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Models/Vector3.cs ================================================ using MessagePack; using ProtoBuf; namespace Benchmarks.Serialization.Models; [MessagePackObject] [ProtoContract] [GenerateSerializer] public struct MyVector3 { [Key(0)] [ProtoMember(1)] [Id(0)] public float X; [Key(1)] [ProtoMember(2)] [Id(1)] public float Y; [Key(2)] [ProtoMember(3)] [Id(2)] public float Z; } [Immutable] [GenerateSerializer] public struct ImmutableVector3 { [Id(0)] public float X; [Id(1)] public float Y; [Id(2)] public float Z; } ================================================ FILE: test/Benchmarks/Serialization/Models/VirtualIntsClass.cs ================================================ using ZeroFormatter; namespace Benchmarks.Serialization.Models; [ZeroFormattable] public class VirtualIntsClass { public static VirtualIntsClass Create() { var result = new VirtualIntsClass(); result.Initialize(); return result; } public void Initialize() => MyProperty1 = MyProperty2 = MyProperty3 = MyProperty4 = MyProperty5 = MyProperty6 = MyProperty7 = MyProperty8 = MyProperty9 = 10; [Index(0)] public virtual int MyProperty1 { get; set; } [Index(1)] public virtual int MyProperty2 { get; set; } [Index(2)] public virtual int MyProperty3 { get; set; } [Index(3)] public virtual int MyProperty4 { get; set; } [Index(4)] public virtual int MyProperty5 { get; set; } [Index(5)] public virtual int MyProperty6 { get; set; } [Index(6)] public virtual int MyProperty7 { get; set; } [Index(7)] public virtual int MyProperty8 { get; set; } [Index(8)] public virtual int MyProperty9 { get; set; } } ================================================ FILE: test/Benchmarks/Serialization/Utilities/BenchmarkConfig.cs ================================================ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; namespace Benchmarks.Serialization.Utilities; internal class BenchmarkConfig : ManualConfig { public BenchmarkConfig() { ArtifactsPath = ".\\BenchmarkDotNet.Aritfacts." + DateTime.Now.ToString("u").Replace(' ', '_').Replace(':', '-'); AddExporter(MarkdownExporter.GitHub); AddDiagnoser(MemoryDiagnoser.Default); Options |= ConfigOptions.KeepBenchmarkFiles; AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)); AddJob(Job.Default.WithRuntime(CoreRuntime.Core90)); #if NET10_0_OR_GREATER AddJob(Job.Default.WithRuntime(CoreRuntime.Core10_0)); #endif } } ================================================ FILE: test/Benchmarks/Serialization/Utilities/ClassSingleSegmentBuffer.cs ================================================ using System.Buffers; using System.Diagnostics.Contracts; using System.Text; namespace Benchmarks.Serialization.Utilities; public class ClassSingleSegmentBuffer : IBufferWriter { private readonly byte[] _buffer; private int _written; public ClassSingleSegmentBuffer(byte[] buffer) { _buffer = buffer; _written = 0; } public void Advance(int bytes) => _written += bytes; [Pure] public Memory GetMemory(int sizeHint = 0) => _buffer.AsMemory(_written); [Pure] public Span GetSpan(int sizeHint) => _buffer.AsSpan(_written); public byte[] ToArray() => _buffer.AsSpan(0, _written).ToArray(); public void Reset() => _written = 0; [Pure] public int Length => _written; [Pure] public ReadOnlySequence GetReadOnlySequence() => new(_buffer, 0, _written); public override string ToString() => Encoding.UTF8.GetString(_buffer.AsSpan(0, _written).ToArray()); } ================================================ FILE: test/Benchmarks/Serialization/Utilities/MethodResultColumn.cs ================================================ using BenchmarkDotNet.Columns; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; using System.Reflection; namespace Benchmarks.Serialization.Utilities; public class MethodResultColumn : IColumn { private readonly Func _formatter; public MethodResultColumn(string columnName, Func formatter, string legend = null) { ColumnName = columnName; _formatter = formatter; Legend = legend; } public string GetValue(Summary summary, BenchmarkCase benchmarkCase) => GetValue(summary, benchmarkCase, null); public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) => _formatter(CallMethod(benchmarkCase)); private static object CallMethod(BenchmarkCase benchmarkCase) { try { var descriptor = benchmarkCase.Descriptor; var instance = Activator.CreateInstance(descriptor.Type); TryInvoke(instance, descriptor.GlobalSetupMethod); TryInvoke(instance, descriptor.IterationSetupMethod); var result = descriptor.WorkloadMethod.Invoke(instance, Array.Empty()); TryInvoke(instance, descriptor.IterationCleanupMethod); TryInvoke(instance, descriptor.GlobalCleanupMethod); return result; static void TryInvoke(object target, MethodInfo method) { try { _ = (method?.Invoke(target, Array.Empty())); } catch { } } } catch { return null; } } public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; public bool IsAvailable(Summary summary) => summary.Reports.Any(r => CallMethod(r.BenchmarkCase) != null); public string Id => nameof(MethodResultColumn) + "_" + ColumnName; public string ColumnName { get; } public bool AlwaysShow => true; public ColumnCategory Category => ColumnCategory.Metric; public int PriorityInCategory => 0; public bool IsNumeric => true; public UnitType UnitType => UnitType.Size; public string Legend { get; } } ================================================ FILE: test/Benchmarks/Serialization/Utilities/PayloadSizeColumnAttribute.cs ================================================ using BenchmarkDotNet.Configs; namespace Benchmarks.Serialization.Utilities; [AttributeUsage(AttributeTargets.Class)] public class PayloadSizeColumnAttribute : Attribute, IConfigSource { public PayloadSizeColumnAttribute(string columnName = "Payload") { var config = ManualConfig.CreateEmpty(); config.AddColumn( new MethodResultColumn(columnName, val => { uint result; switch (val) { case int i: result = (uint)i; break; case uint i: result = i; break; case long i: result = (uint)i; break; case ulong i: result = (uint)i; break; default: return "Invalid"; } return result + " B"; })); Config = config; } public IConfig Config { get; } } ================================================ FILE: test/Benchmarks/Serialization/Utilities/SingleSegmentBuffer.cs ================================================ using System.Buffers; using System.Diagnostics.Contracts; using System.Text; namespace Benchmarks.Serialization.Utilities; public struct SingleSegmentBuffer : IBufferWriter { private readonly byte[] _buffer; private int _written; public SingleSegmentBuffer(byte[] buffer) { _buffer = buffer; _written = 0; } public void Advance(int bytes) => _written += bytes; [Pure] public readonly Memory GetMemory(int sizeHint = 0) => _buffer.AsMemory(_written); [Pure] public readonly Span GetSpan(int sizeHint) => _buffer.AsSpan(_written); public readonly byte[] ToArray() => _buffer.AsSpan(0, _written).ToArray(); public void Reset() => _written = 0; [Pure] public readonly int Length => _written; [Pure] public readonly ReadOnlySpan GetReadOnlySpan() => new(_buffer, 0, _written); public override readonly string ToString() => Encoding.UTF8.GetString(_buffer.AsSpan(0, _written).ToArray()); } ================================================ FILE: test/Benchmarks/TopK/BloomFilterBenchmark.cs ================================================ using System.Collections; using System.Diagnostics; using System.IO.Hashing; using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using Benchmarks.Serialization.Utilities; using Orleans.Runtime.Placement.Repartitioning; namespace Benchmarks.TopK; [MemoryDiagnoser] [FalsePositiveRateColumn] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn] public class BloomFilterBenchmark { private BloomFilter _bloomFilter; private BloomFilter _bloomFilterWithSamples; private OriginalBloomFilter _originalBloomFilter; private OriginalBloomFilter _originalBloomFilterWithSamples; private BlockedBloomFilter _blockedBloomFilter; private BlockedBloomFilter _blockedBloomFilterWithSamples; private GrainId[] _population; private HashSet _set; private ZipfRejectionSampler _sampler; private GrainId[] _samples; [Params(1_000_000, Priority = 4)] public int Pop { get; set; } [Params(/*0.2, 0.4, 0.6, 0.8, */1.02 /*, 1.2, 1.4, 1.6*/, Priority = 3)] public double Skew { get; set; } [Params(1_000_000, Priority = 1)] public int Cap { get; set; } [Params(0.01, 0.001, Priority = 2)] public double FP { get; set; } [Params(10_000, Priority = 5)] public int Samples { get; set; } [GlobalSetup] public void GlobalSetup() { _population = new GrainId[Pop]; _sampler = new(new Random(42), Pop, Skew); for (var i = 0; i < Pop; i++) { _population[i] = GrainId.Create($"grain_{i}", i.ToString()); } _bloomFilter = new(Cap, FP); _bloomFilterWithSamples = new(Cap, FP); _originalBloomFilter = new(); _originalBloomFilterWithSamples = new(); _blockedBloomFilter = new(Cap, FP); _blockedBloomFilterWithSamples = new(Cap, FP); _samples = new GrainId[Samples]; _set = new(Samples); for (var i = 0; i < Samples; i++) { //var sample = _sampler.Sample(); var value = _population[i]; _samples[i] = value; _set.Add(value); _bloomFilterWithSamples.Add(value); _originalBloomFilterWithSamples.Add(value); } } [Benchmark] [BenchmarkCategory("Add")] public void BloomFilter_Add() { foreach (var sample in _samples) { _bloomFilter.Add(sample); } } [Benchmark] [BenchmarkCategory("Contains")] public void BloomFilter_Contains() { foreach (var sample in _samples) { _bloomFilterWithSamples.Contains(sample); } } [Benchmark] [BenchmarkCategory("FP rate")] public int BloomFilter_FPR() { var correct = 0; var incorrect = 0; foreach (var sample in _population) { if (!_bloomFilterWithSamples.Contains(sample) == _set.Contains(sample)) { correct++; } else { incorrect++; } } return incorrect; } [Benchmark(Baseline = true)] [BenchmarkCategory("Add")] public void OriginalBloomFilter_Add() { foreach (var sample in _samples) { _originalBloomFilter.Add(sample); } } [Benchmark(Baseline = true)] [BenchmarkCategory("Contains")] public void OriginalBloomFilter_Contains() { foreach (var sample in _samples) { _originalBloomFilterWithSamples.Contains(sample); } } /* [Benchmark(Baseline = true)] [BenchmarkCategory("FP rate")] public int OriginalBloomFilter_FPR() { var correct = 0; var incorrect = 0; foreach (var sample in _population) { if (!_originalBloomFilterWithSamples.Contains(sample) == _set.Contains(sample)) { correct++; } else { incorrect++; } } return incorrect; } */ [Benchmark] [BenchmarkCategory("Add")] public void BlockedBloomFilter_Add() { foreach (var sample in _samples) { _blockedBloomFilter.Add(sample); } } [Benchmark] [BenchmarkCategory("Contains")] public void BlockedBloomFilter_Contains() { foreach (var sample in _samples) { _blockedBloomFilterWithSamples.Contains(sample); } } // This is expected to yield a slighly higher FP rate, due to tuning [Benchmark] [BenchmarkCategory("FP rate")] public int BlockedBloomFilter_FPR() { var correct = 0; var incorrect = 0; foreach (var sample in _population) { if (!_blockedBloomFilterWithSamples.Contains(sample) == _set.Contains(sample)) { correct++; } else { incorrect++; } } return incorrect; } } [AttributeUsage(AttributeTargets.Class)] public class FalsePositiveRateColumnAttribute : Attribute, IConfigSource { public FalsePositiveRateColumnAttribute(string columnName = "FP %") { var config = ManualConfig.CreateEmpty(); config.AddColumn( new MethodResultColumn(columnName, val => { return $"{val}"; })); Config = config; } public IConfig Config { get; } } public class OriginalBloomFilter { private const int bitArraySize = 1_198_132; // formula 8 * n / ln(2) -> for 1000 elements, 0.01% private readonly int[] hashFuncSeeds = Enumerable.Range(0, 6).Select(p => (int)unchecked(p * 0xFBA4C795 + 1)).ToArray(); private readonly BitArray filterBits = new(bitArraySize); public void Add(GrainId id) { foreach (int s in hashFuncSeeds) { uint i = XxHash32.HashToUInt32(id.Key.AsSpan(), s); filterBits.Set((int)(i % (uint)filterBits.Length), true); } } public bool Contains(GrainId id) { foreach (int s in hashFuncSeeds) { uint i = XxHash32.HashToUInt32(id.Key.AsSpan(), s); if (!filterBits.Get((int)(i % (uint)filterBits.Length))) { return false; } } return true; } } internal sealed class BloomFilter { private const double Ln2Squared = 0.4804530139182014246671025263266649717305529515945455; private const double Ln2 = 0.6931471805599453094172321214581765680755001343602552; private readonly ulong[] _hashFuncSeeds; private readonly int[] _filter; private readonly int _indexMask; public BloomFilter(int capacity, double falsePositiveRate) { // Calculate the ideal bloom filter size and hash code count for the given (estimated) capacity and desired false positive rate. // See https://en.wikipedia.org/wiki/Bloom_filter. var minBitCount = (int)(-1 / Ln2Squared * capacity * Math.Log(falsePositiveRate)) / 8; var arraySize = (int)CeilingPowerOfTwo((uint)(minBitCount - 1 + (1 << 5)) >> 5); _indexMask = arraySize - 1; _filter = new int[arraySize]; // Divide the hash count by 2 since we are using 64-bit hash codes split into two 32-bit hash codes. var hashFuncCount = (int)Math.Min(minBitCount * 8 / capacity * Ln2 / 2, 8); Debug.Assert(hashFuncCount > 0); _hashFuncSeeds = Enumerable.Range(0, hashFuncCount).Select(p => unchecked((ulong)p * 0xFBA4C795FBA4C795 + 1)).ToArray(); Debug.Assert(_hashFuncSeeds.Length == hashFuncCount); } public void Add(GrainId id) { var hash = XxHash3.HashToUInt64(id.Key.AsSpan(), id.GetUniformHashCode()); foreach (var seed in _hashFuncSeeds) { hash = Mix64(hash ^ seed); Set((int)hash); Set((int)(hash >> 32)); } } public bool Contains(GrainId id) { var hash = XxHash3.HashToUInt64(id.Key.AsSpan(), id.GetUniformHashCode()); foreach (var seed in _hashFuncSeeds) { hash = Mix64(hash ^ seed); var clear = IsClear((int)hash); clear |= IsClear((int)(hash >> 32)); if (clear) { return false; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsClear(int index) => (_filter[(index >> 5) & _indexMask] & (1 << index)) == 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Set(int index) => _filter[(index >> 5) & _indexMask] |= 1 << index; /// /// Computes Stafford variant 13 of 64-bit mix function. /// /// The input parameter. /// A bit mix of the input parameter. /// /// See http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html /// public static ulong Mix64(ulong z) { z = (z ^ z >> 30) * 0xbf58476d1ce4e5b9L; z = (z ^ z >> 27) * 0x94d049bb133111ebL; return z ^ z >> 31; } public void Reset() => Array.Clear(_filter); private static uint CeilingPowerOfTwo(uint x) => 1u << -BitOperations.LeadingZeroCount(x - 1); } ================================================ FILE: test/Benchmarks/TopK/TopKBenchmark.cs ================================================ using System.Diagnostics; using System.Net; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using Orleans.Placement.Repartitioning; using Orleans.Runtime.Placement.Repartitioning; namespace Benchmarks.TopK; [MemoryDiagnoser] public class TopKBenchmark { private ZipfRejectionSampler _sampler; private ulong[] ULongSamples; private Edge[] EdgeSamples; private EdgeClass[] EdgeClassSamples; private UlongFrequentItemCollection _fss; private EdgeClassFrequentItemCollection _fssClass; private EdgeFrequentItemCollection _fssEdge; private FrequencySink _sink; [Params(100_000, Priority = 3)] public int Pop { get; set; } [Params(0.2, 0.4, 0.6, 0.8, 1.02, 1.2, 1.4, 1.6, Priority = 2)] public double Skew { get; set; } [Params(10_000, Priority = 1)] public int Cap { get; set; } [Params(1_000_000, Priority = 4)] public int Samples { get; set; } [GlobalSetup] public void GlobalSetup() { _sampler = new(new Random(42), Pop, Skew); var silos = new SiloAddress[100]; for (var i = 0; i < silos.Length; i++) { silos[i] = SiloAddress.New(new IPEndPoint(IPAddress.Loopback, i), i); } var grains = new GrainId[Pop]; for (var i = 0; i < Pop; i++) { grains[i] = GrainId.Create("grain", i.ToString()); } var grainEdges = new Edge[Pop]; for (var i = 0; i < Pop; i++) { grainEdges[i] = new Edge(new(grains[i % grains.Length], silos[i % silos.Length], true), new(grains[(i + 1) % grains.Length], silos[(i + 1) % silos.Length], true)); } var grainEdgeClasses = new EdgeClass[Pop]; for (var i = 0; i < Pop; i++) { grainEdgeClasses[i] = new(grainEdges[i]); } ULongSamples = new ulong[Samples]; EdgeSamples = new Edge[Samples]; EdgeClassSamples = new EdgeClass[Samples]; for (var i = 0; i < Samples; i++) { var sample = _sampler.Sample(); ULongSamples[i] = (ulong)sample; EdgeSamples[i] = grainEdges[sample % grainEdges.Length]; EdgeClassSamples[i] = grainEdgeClasses[sample % grainEdgeClasses.Length]; } _fss = new UlongFrequentItemCollection(Cap); _fssClass = new EdgeClassFrequentItemCollection(Cap); _fssEdge = new EdgeFrequentItemCollection(Cap); _sink = new FrequencySink(Cap); } internal sealed record class EdgeClass(Edge Edge); [IterationSetup] public void IterationSetup() { /* _fss.Clear(); _fssEdge.Clear(); _fssClass.Clear(); */ //_sink = new FrequencySink(Cap); } /* [Benchmark] [BenchmarkCategory("Add")] public void FssULongAdd() { foreach (var sample in ULongSamples) { _fss.Add(sample); } } */ /* [Benchmark] [BenchmarkCategory("Add")] public void FssClassAdd() { foreach (var sample in EdgeClassSamples) { _fssClass.Add(sample); } } */ [Benchmark] [BenchmarkCategory("FSS")] public void FssAdd() { foreach (var sample in EdgeSamples) { _fssEdge.Add(sample); } } [Benchmark] [BenchmarkCategory("SS")] public void SinkAdd() { foreach (var sample in EdgeSamples) { _sink.Add(sample); } } private sealed class EdgeFrequentItemCollection(int capacity) : FrequentItemCollection(capacity) { protected override ulong GetKey(in Edge element) => (ulong)element.Source.Id.GetUniformHashCode() << 32 | element.Target.Id.GetUniformHashCode(); public void Clear() => ClearCore(); } private sealed class EdgeClassFrequentItemCollection(int capacity) : FrequentItemCollection(capacity) { static ulong GetKey(in Edge element) => (ulong)element.Source.Id.GetUniformHashCode() << 32 | element.Target.Id.GetUniformHashCode(); protected override ulong GetKey(in EdgeClass element) => GetKey(element.Edge); public void Clear() => ClearCore(); } private sealed class UlongFrequentItemCollection(int capacity) : FrequentItemCollection(capacity) { protected override ulong GetKey(in ulong element) => element; public void Remove(in ulong element) => Remove(GetKey(element)); public void Clear() => ClearCore(); } internal class EdgeCounter(ulong value, Edge edge) { public ulong Value { get; set; } = value; public Edge Edge { get; } = edge; } /// /// Implementation of the Space-Saving algorithm: https://www.cse.ust.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf /// internal sealed class FrequencySink(int capacity) { public ulong GetKey(in Edge element) => (ulong)element.Source.Id.GetUniformHashCode() << 32 | element.Target.Id.GetUniformHashCode(); private readonly Dictionary _counters = new(capacity); private readonly UpdateableMinHeap _heap = new(capacity); public int Capacity { get; } = capacity; public Dictionary.ValueCollection Counters => _counters.Values; public void Add(Edge edge) { var combinedHash = GetKey(edge); if (_counters.TryGetValue(combinedHash, out var counter)) { counter.Value++; _heap.Update(combinedHash, counter.Value); return; } if (_counters.Count == Capacity) { var minHash = _heap.Dequeue(); _counters.Remove(minHash); } _counters.Add(combinedHash, new EdgeCounter(1, edge)); _heap.Enqueue(combinedHash, _counters[combinedHash].Value); } public void Remove(uint sourceHash, uint targetHash) { var combinedHash = CombineHashes(sourceHash, targetHash); var reversedHash = CombineHashes(targetHash, sourceHash); if (_counters.Remove(combinedHash)) { _ = _heap.Remove(combinedHash); } if (_counters.Remove(reversedHash)) { _ = _heap.Remove(reversedHash); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong CombineHashes(uint sourceHash, uint targetHash) => (ulong)sourceHash << 32 | targetHash; // Inspired by: https://github.com/DesignEngrLab/TVGL/blob/master/TessellationAndVoxelizationGeometryLibrary/Miscellaneous%20Functions/UpdatablePriorityQueue.cs private class UpdateableMinHeap(int capacity) { private const int Arity = 4; private const int Log2Arity = 2; private readonly Dictionary _hashIndexes = new(capacity); private readonly (ulong Hash, ulong Value)[] _nodes = new (ulong, ulong)[capacity]; private int _size; public void Enqueue(ulong hash, ulong value) { var currentSize = _size; _size = currentSize + 1; MoveNodeUp((hash, value), currentSize); } public ulong Dequeue() { var hash = _nodes[0].Hash; _hashIndexes.Remove(hash); var lastNodeIndex = --_size; if (lastNodeIndex > 0) { var lastNode = _nodes[lastNodeIndex]; MoveNodeDown(lastNode, 0); } return hash; } public bool Remove(ulong hash) { if (!_hashIndexes.TryGetValue(hash, out var index)) { return false; } var nodes = _nodes; var newSize = --_size; if (index < newSize) { var lastNode = nodes[newSize]; MoveNodeDown(lastNode, index); } _hashIndexes.Remove(hash); nodes[newSize] = default; return true; } public void Update(ulong hash, ulong newValue) { Remove(hash); Enqueue(hash, newValue); } private void MoveNodeUp((ulong Hash, ulong Value) node, int nodeIndex) { Debug.Assert(0 <= nodeIndex && nodeIndex < _size); var nodes = _nodes; while (nodeIndex > 0) { var parentIndex = GetParentIndex(nodeIndex); var parent = nodes[parentIndex]; if (Comparer.Default.Compare(node.Value, parent.Value) < 0) { nodes[nodeIndex] = parent; _hashIndexes[parent.Hash] = nodeIndex; nodeIndex = parentIndex; } else { break; } } _hashIndexes[node.Hash] = nodeIndex; nodes[nodeIndex] = node; [MethodImpl(MethodImplOptions.AggressiveInlining)] static int GetParentIndex(int index) => (index - 1) >> Log2Arity; } private void MoveNodeDown((ulong Hash, ulong Value) node, int nodeIndex) { Debug.Assert(0 <= nodeIndex && nodeIndex < _size); var nodes = _nodes; var size = _size; int i; while ((i = GetFirstChildIndex(nodeIndex)) < size) { var minChild = nodes[i]; var minChildIndex = i; var childIndexUpperBound = Math.Min(i + Arity, size); while (++i < childIndexUpperBound) { var nextChild = nodes[i]; if (nextChild.Value < minChild.Value) { minChild = nextChild; minChildIndex = i; } } if (node.Value <= minChild.Value) { break; } nodes[nodeIndex] = minChild; _hashIndexes[minChild.Hash] = nodeIndex; nodeIndex = minChildIndex; } _hashIndexes[node.Hash] = nodeIndex; nodes[nodeIndex] = node; [MethodImpl(MethodImplOptions.AggressiveInlining)] static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1; } } } } // https://jasoncrease.medium.com/rejection-sampling-the-zipf-distribution-6b359792cffa internal sealed class ZipfRejectionSampler { private readonly Random _rand; private readonly double _skew; private readonly double _t; public ZipfRejectionSampler(Random random, long cardinality, double skew) { _rand = random; _skew = skew; _t = (Math.Pow(cardinality, 1 - skew) - skew) / (1 - skew); } public long Sample() { while (true) { double invB = bInvCdf(_rand.NextDouble()); long sampleX = (long)(invB + 1); double yRand = _rand.NextDouble(); double ratioTop = Math.Pow(sampleX, -_skew); double ratioBottom = sampleX <= 1 ? 1 / _t : Math.Pow(invB, -_skew) / _t; double rat = (ratioTop) / (ratioBottom * _t); if (yRand < rat) return sampleX; } } private double bInvCdf(double p) { if (p * _t <= 1) return p * _t; else return Math.Pow((p * _t) * (1 - _skew) + _skew, 1 / (1 - _skew)); } } ================================================ FILE: test/Benchmarks/Transactions/TransactionBenchmark.cs ================================================ using Orleans.TestingHost; using BenchmarkGrainInterfaces.Transaction; using TestExtensions; using Microsoft.Extensions.DependencyInjection; using Orleans.Transactions; namespace Benchmarks.Transactions; public class TransactionBenchmark : IDisposable { private TestCluster host; private readonly int runs; private readonly int transactionsPerRun; private readonly int concurrent; public TransactionBenchmark(int runs, int transactionsPerRun, int concurrent) { this.runs = runs; this.transactionsPerRun = transactionsPerRun; this.concurrent = concurrent; } public void MemorySetup() { var builder = new TestClusterBuilder(4); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void MemoryThrottledSetup() { var builder = new TestClusterBuilder(4); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void AzureSetup() { var builder = new TestClusterBuilder(4); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public void AzureThrottledSetup() { var builder = new TestClusterBuilder(4); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); builder.AddSiloBuilderConfigurator(); this.host = builder.Build(); this.host.Deploy(); } public class SiloMemoryStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddMemoryGrainStorageAsDefault(); } } public class SiloAzureStorageConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.AddAzureTableTransactionalStateStorageAsDefault(options => { options.TableServiceClient = new(TestDefaultConfiguration.DataConnectionString); }); } } public class SiloTransactionThrottlingConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.Configure(options => { options.Enabled = true; options.Limit = 50; }); } } public async Task RunAsync() { Console.WriteLine($"Cold Run."); await FullRunAsync(); for(int i=0; i RunAsync(i, transactionsPerRunner, runners))); Report finalReport = new Report(); foreach (Report report in reports) { finalReport.Succeeded += report.Succeeded; finalReport.Failed += report.Failed; finalReport.Throttled += report.Throttled; finalReport.Elapsed = TimeSpan.FromMilliseconds(Math.Max(finalReport.Elapsed.TotalMilliseconds, report.Elapsed.TotalMilliseconds)); } Console.WriteLine($"{finalReport.Succeeded} transactions in {finalReport.Elapsed.TotalMilliseconds}ms."); Console.WriteLine($"{(int)(finalReport.Succeeded * 1000 / finalReport.Elapsed.TotalMilliseconds)} transactions per second."); Console.WriteLine($"{finalReport.Failed} transactions failed."); Console.WriteLine($"{finalReport.Throttled} transactions were throttled."); } public async Task RunAsync(int run, int transactiosPerRun, int concurrentPerRun) { ILoadGrain load = this.host.Client.GetGrain(Guid.NewGuid()); await load.Generate(run, transactiosPerRun, concurrentPerRun); Report report = null; while (report == null) { await Task.Delay(TimeSpan.FromSeconds(10)); report = await load.TryGetReport(); } return report; } public void Teardown() { host.StopAllSilos(); } public void Dispose() { host?.Dispose(); } public sealed class SiloTransactionConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder hostBuilder) { hostBuilder.UseTransactions(); } } } ================================================ FILE: test/Benchmarks/run_test.cmd ================================================ pushd %~dp0 git log -n 1 git --no-pager diff dotnet run -c Release -- ConcurrentPing popd ================================================ FILE: test/Benchmarks.AdoNet/Benchmarks.AdoNet.csproj ================================================ Exe net8.0 enable enable true true ================================================ FILE: test/Benchmarks.AdoNet/Program.cs ================================================ BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); ================================================ FILE: test/Benchmarks.AdoNet/Properties/Usings.cs ================================================ global using BenchmarkDotNet.Running; ================================================ FILE: test/Benchmarks.AdoNet/Streaming/MessageDequeueingBenchmark.cs ================================================ using Microsoft.Data.SqlClient; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using Orleans.Streaming.AdoNet; using Orleans.Tests.SqlUtils; using UnitTests.General; using static System.String; namespace Benchmarks.AdoNet.Streaming; public class SqlServerMessageDequeuingBenchmark() : MessageDequeuingBenchmark(AdoNetInvariants.InvariantNameSqlServer, "OrleansStreamTest") { public override void GlobalSetup() { base.GlobalSetup(); SqlConnection.ClearAllPools(); } } /// /// This benchmark measures the performance of message queueing. /// [WarmupCount(1), IterationCount(3), InvocationCount(1), MarkdownExporter] public abstract class MessageDequeuingBenchmark(string invariant, string database) { private const int OperationsPerInvoke = 1000; private readonly Consumer _consumer = new(); private IRelationalStorage _storage = default!; private RelationalOrleansQueries _queries = default!; private byte[] _payload = []; private string[] _queueIds = default!; private AdoNetStreamMessageAck[] _acks = []; /// /// This highlights degradation from queue concurrency. /// [Params(1, 4, 8)] public int QueueCount { get; set; } /// /// This highlights degradation from payload size. /// [Params(10000)] public int PayloadSize { get; set; } /// /// This highlights variation according to batch size. /// [Params(1, 16, 32)] public int BatchSize { get; set; } /// /// This highlights variation from how full the table is. /// [Params(0, 0.5, 1)] public double FullnessRatio { get; set; } [GlobalSetup] public virtual void GlobalSetup() { Async().GetAwaiter().GetResult(); async Task Async() { // create an appropriate size payload _payload = new byte[PayloadSize]; Array.Fill(_payload, 0xFF); // define the set queues _queueIds = Enumerable.Range(0, QueueCount).Select(i => $"QueueId-{i}").ToArray(); // setup the test database var testing = await RelationalStorageForTesting.SetupInstance(invariant, database); if (IsNullOrEmpty(testing.CurrentConnectionString)) { throw new InvalidOperationException($"Database '{database}' not initialized"); } _storage = RelationalStorage.CreateInstance(invariant, testing.CurrentConnectionString); _queries = await RelationalOrleansQueries.CreateInstance(invariant, testing.CurrentConnectionString); } } [IterationSetup] public void IterationSetup() { Async().GetAwaiter().GetResult(); async Task Async() { await _storage.ExecuteAsync("TRUNCATE TABLE OrleansStreamMessage"); // generate test data to dequeue var count = (int)Math.Ceiling(OperationsPerInvoke * QueueCount * BatchSize * FullnessRatio); _acks = new AdoNetStreamMessageAck[count]; await Parallel.ForAsync(0, count, async (i, ct) => { // generate messages in round robin queue order to help simulate multiple agents var queueId = _queueIds[i % _queueIds.Length]; var ack = await _queries.QueueStreamMessageAsync("ServiceId-0", "ProviderId-0", queueId, _payload, 1000); _acks[i] = ack; }); } } [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] public async Task GetStreamMessages() { var count = OperationsPerInvoke * QueueCount; await Parallel.ForAsync(0, count, new ParallelOptions { MaxDegreeOfParallelism = QueueCount }, async (i, ct) => { // get a queue id in round robin order to help simulate multiple agents var queueId = _queueIds[i % _queueIds.Length]; // get messages for the queue of the ack // the queue may or may not have data to dequeue depending on the fullness and batch size parameters // we dequeue regardless in order to measure overhead var messages = await _queries.GetStreamMessagesAsync("ServiceId-0", "ProviderId-0", queueId, BatchSize, 1, 1000, 1000, 1000, 1000); _consumer.Consume(messages); }); } } ================================================ FILE: test/Benchmarks.AdoNet/Streaming/MessageQueueingBenchmark.cs ================================================ using Microsoft.Data.SqlClient; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using Orleans.Tests.SqlUtils; using UnitTests.General; using static System.String; namespace Benchmarks.AdoNet.Streaming; public class SqlServerMessageQueueingBenchmark() : MessageQueueingBenchmark(AdoNetInvariants.InvariantNameSqlServer, "OrleansStreamTest") { public override void GlobalSetup() { base.GlobalSetup(); SqlConnection.ClearAllPools(); } } /// /// This benchmark measures the performance of message queueing. /// [WarmupCount(1), IterationCount(3), InvocationCount(1), MarkdownExporter] public abstract class MessageQueueingBenchmark(string invariant, string database) { private const int OperationsPerInvoke = 1000; private readonly Consumer _consumer = new(); private IRelationalStorage _storage = default!; private RelationalOrleansQueries _queries = default!; private byte[] _payload = []; private string[] _queueIds = default!; /// /// This highlights degradation from database locking. /// [Params(1, 4, 8)] public int QueueCount { get; set; } /// /// This highlights degradation from payload size. /// [Params(1000, 10000, 100000)] public int PayloadSize { get; set; } /// /// This highlights degradation from concurrency. /// [Params(1, 4, 8)] public int Concurrency { get; set; } [GlobalSetup] public virtual void GlobalSetup() { _payload = new byte[PayloadSize]; Array.Fill(_payload, 0xFF); _queueIds = Enumerable.Range(0, QueueCount).Select(i => $"QueueId-{i}").ToArray(); var testing = RelationalStorageForTesting.SetupInstance(invariant, database).GetAwaiter().GetResult(); if (IsNullOrEmpty(testing.CurrentConnectionString)) { throw new InvalidOperationException($"Database '{database}' not initialized"); } _storage = RelationalStorage.CreateInstance(invariant, testing.CurrentConnectionString); _queries = RelationalOrleansQueries.CreateInstance(invariant, testing.CurrentConnectionString).GetAwaiter().GetResult(); } [IterationSetup] public void IterationSetup() => _storage.ExecuteAsync("TRUNCATE TABLE OrleansStreamMessage").GetAwaiter().GetResult(); [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] public Task QueueStreamMessage() { var count = OperationsPerInvoke * Concurrency; return Parallel.ForAsync(0, count, new ParallelOptions { MaxDegreeOfParallelism = Concurrency }, async (i, ct) => { var queueId = _queueIds[Random.Shared.Next(_queueIds.Length)]; var ack = await _queries.QueueStreamMessageAsync("ServiceId-0", "ProviderId-0", queueId, _payload, 1000); _consumer.Consume(ack); }); } } ================================================ FILE: test/Directory.Build.props ================================================ <_ParentDirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildPropsFile)')) false false false false $(NoWarn);FS2003;1591 true enable net8.0;net10.0 ================================================ FILE: test/Directory.Build.targets ================================================ <_ParentDirectoryBuildTargetsPath Condition="'$(_DirectoryBuildTargetsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildTargetsFile)')) ================================================ FILE: test/DistributedTests/DistributedTests.Client/Commands/ChaosAgentCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using DistributedTests.Common.MessageChannel; using Microsoft.Extensions.Logging; namespace DistributedTests.Client.Commands { public class ChaosAgentCommand : Command { private readonly ILogger _logger; private class Parameters { public string ServiceId { get; set; } public string ClusterId { get; set; } public Uri AzureTableUri { get; set; } public Uri AzureQueueUri { get; set; } public int Wait { get; set; } public int ServersPerRound { get; set; } public int Rounds { get; set; } public int RoundDelay { get; set; } public bool Graceful { get; set; } public bool Restart { get; set; } } public ChaosAgentCommand(ILogger logger) : base("chaosagent", "Shutdown/restart servers gracefully or not") { AddOption(OptionHelper.CreateOption("--serviceId", isRequired: true)); AddOption(OptionHelper.CreateOption("--clusterId", isRequired: true)); AddOption(OptionHelper.CreateOption("--azureTableUri", isRequired: true)); AddOption(OptionHelper.CreateOption("--azureQueueUri", isRequired: true)); AddOption(OptionHelper.CreateOption("--wait", defaultValue: 30)); AddOption(OptionHelper.CreateOption("--serversPerRound", defaultValue: 1)); AddOption(OptionHelper.CreateOption("--rounds", defaultValue: 5)); AddOption(OptionHelper.CreateOption("--roundDelay", defaultValue: 60)); AddOption(OptionHelper.CreateOption("--graceful", defaultValue: false)); AddOption(OptionHelper.CreateOption("--restart", defaultValue: false)); Handler = CommandHandler.Create(RunAsync); _logger = logger; } private async Task RunAsync(Parameters parameters) { var channel = await Channels.CreateSendChannel(parameters.ClusterId, parameters.AzureQueueUri); _logger.LogInformation("Waiting {WaitSeconds} seconds before starting...", parameters.Wait); await Task.Delay(TimeSpan.FromSeconds(parameters.Wait)); for (var i=0; i r.ServerName))); _logger.LogInformation("Round #{Round}: waiting {RoundDelay}", i + 1, parameters.RoundDelay); await Task.Delay(TimeSpan.FromSeconds(parameters.RoundDelay)); } List GetMessages() { var msgs = new List(); for (var i = 0; i < parameters.ServersPerRound; i++) { msgs.Add(new ServerMessage(parameters.Graceful, parameters.Restart)); } return msgs; } } } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/Commands/CounterCaptureCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using DistributedTests.Common; using DistributedTests.GrainInterfaces; using Microsoft.Crank.EventSources; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Orleans.Configuration; namespace DistributedTests.Client.Commands { public class CounterCaptureCommand : Command { private readonly ILogger _logger; private class Parameters { public string ServiceId { get; set; } public string ClusterId { get; set; } public Uri AzureTableUri { get; set; } public Uri AzureQueueUri { get; set; } public string CounterKey { get; set; } public List Counters { get; set; } } public CounterCaptureCommand(ILogger logger) : base("counter", "capture the counters in parameter") { AddOption(OptionHelper.CreateOption("--serviceId", isRequired: true)); AddOption(OptionHelper.CreateOption("--clusterId", isRequired: true)); AddOption(OptionHelper.CreateOption("--azureTableUri", isRequired: true)); AddOption(OptionHelper.CreateOption("--azureQueueUri", isRequired: true)); AddOption(OptionHelper.CreateOption("--counterKey", defaultValue: StreamingConstants.DefaultCounterGrain)); AddArgument(new Argument>("Counters") { Arity = ArgumentArity.OneOrMore }); Handler = CommandHandler.Create(RunAsync); _logger = logger; } private async Task RunAsync(Parameters parameters) { _logger.LogInformation("Connecting to cluster..."); var hostBuilder = new HostBuilder() .UseOrleansClient((ctx, builder) => { builder .Configure(options => { options.ClusterId = parameters.ClusterId; options.ServiceId = parameters.ServiceId; }) .UseAzureStorageClustering(options => options.TableServiceClient = parameters.AzureTableUri.CreateTableServiceClient()); }); using var host = hostBuilder.Build(); await host.StartAsync(); var client = host.Services.GetService(); var counterGrain = client.GetGrain(parameters.CounterKey); var duration = await counterGrain.GetRunDuration(); BenchmarksEventSource.Register("duration", Operations.First, Operations.Last, "duration", "duration", "n0"); BenchmarksEventSource.Measure("duration", duration.TotalSeconds); var initialWait = await counterGrain.WaitTimeForReport(); _logger.LogInformation("Counters should be ready in {InitialWait}", initialWait); await Task.Delay(initialWait); _logger.LogInformation("Counters ready"); foreach (var counter in parameters.Counters) { var value = await counterGrain.GetTotalCounterValue(counter); _logger.LogInformation("{Counter}: {Value}", counter, value); BenchmarksEventSource.Register(counter, Operations.First, Operations.Sum, counter, counter, "n0"); BenchmarksEventSource.Measure(counter, value); if (string.Equals(counter, "requests", StringComparison.InvariantCultureIgnoreCase)) { var rps = (float) value / duration.TotalSeconds; BenchmarksEventSource.Register("rps", Operations.First, Operations.Last, "rps", "Requests per second", "n0"); BenchmarksEventSource.Measure("rps", rps); } } await host.StopAsync(); } } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/Commands/ScenarioCommand.cs ================================================ using System.CommandLine; using System.CommandLine.Invocation; using DistributedTests.Client.LoadGeneratorScenario; using Microsoft.Extensions.Logging; namespace DistributedTests.Client.Commands { public class ScenarioCommand : Command { private readonly LoadGeneratorScenarioRunner _runner; public ScenarioCommand(ILoadGeneratorScenario scenario, ILoggerFactory loggerFactory) : base(scenario.Name) { _runner = new LoadGeneratorScenarioRunner(scenario, loggerFactory); // ClientParameters AddOption(OptionHelper.CreateOption("--serviceId", isRequired: true)); AddOption(OptionHelper.CreateOption("--clusterId", isRequired: true)); AddOption(OptionHelper.CreateOption("--connectionsPerEndpoint", defaultValue: 1, validator: OptionHelper.OnlyStrictlyPositive)); AddOption(OptionHelper.CreateOption("--azureQueueUri", isRequired: true)); AddOption(OptionHelper.CreateOption("--azureTableUri", isRequired: true)); // LoadGeneratorParameters AddOption(OptionHelper.CreateOption("--numWorkers", defaultValue: 250, validator: OptionHelper.OnlyStrictlyPositive)); AddOption(OptionHelper.CreateOption("--blocksPerWorker", defaultValue: 10)); AddOption(OptionHelper.CreateOption("--requestsPerBlock", defaultValue: 500, validator: OptionHelper.OnlyStrictlyPositive)); AddOption(OptionHelper.CreateOption("--duration", defaultValue: 0, validator: OptionHelper.OnlyPositiveOrZero)); Handler = CommandHandler.Create(_runner.Run); } } public static class Scenario { public static Command CreateCommand(ILoadGeneratorScenario scenario, ILoggerFactory loggerFactory) => new ScenarioCommand(scenario, loggerFactory); } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/DistributedTests.Client.csproj ================================================ Exe $(TestTargetFrameworks) true $(DistributedTestsOutputPath)/DistributedTests.Client PreserveNewest ================================================ FILE: test/DistributedTests/DistributedTests.Client/LoadGeneratorScenario/ConcurrentLoadGenerator.cs ================================================ using System.Threading.Channels; using System.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Crank.EventSources; namespace DistributedTests.Client { public struct LoadGeneratorReport { public long Completed { get; set; } public long Successes { get; set; } public long Failures { get; set; } public double TotalDuration { get; set; } public int BlocksCompleted { get; set; } public readonly long RatePerSecond => (long)(Completed / TotalDuration); public override readonly string ToString() { if (BlocksCompleted == 0) return "No blocks completed"; var failureString = Failures == 0 ? string.Empty : $" with {Failures} failures"; return $"{RatePerSecond,6}/s {Successes,7} reqs in {TotalDuration,6:0.000}s{failureString}"; } } public sealed class ConcurrentLoadGenerator { private static readonly double StopwatchTickPerSecond = Stopwatch.Frequency; private struct WorkBlock { public long StartTimestamp { get; set; } public long EndTimestamp { get; set; } public int Successes { get; set; } public int Failures { get; set; } public readonly int Completed => this.Successes + this.Failures; public readonly double ElapsedSeconds => (this.EndTimestamp - this.StartTimestamp) / StopwatchTickPerSecond; public readonly double RequestsPerSecond => this.Completed / this.ElapsedSeconds; } private Channel _completedBlocks; private readonly Func _issueRequest; private readonly Func _getStateForWorker; private readonly ILogger _logger; private readonly bool _logIntermediateResults; private readonly Task[] _tasks; private readonly TState[] _states; private readonly int _numWorkers; private readonly int _blocksPerWorker; private readonly int _requestsPerBlock; public ConcurrentLoadGenerator( int numWorkers, int blocksPerWorker, int requestsPerBlock, Func issueRequest, Func getStateForWorker, ILogger logger, bool logIntermediateResults = false) { this._numWorkers = numWorkers; this._blocksPerWorker = blocksPerWorker; this._requestsPerBlock = requestsPerBlock; this._issueRequest = issueRequest; this._getStateForWorker = getStateForWorker; this._logger = logger; this._logIntermediateResults = logIntermediateResults; this._tasks = new Task[numWorkers]; this._states = new TState[numWorkers]; } public async Task Warmup() { this.ResetBetweenRuns(); var completedBlockReader = this._completedBlocks.Reader; for (var ree = 0; ree < this._numWorkers; ree++) { this._states[ree] = _getStateForWorker(ree); this._tasks[ree] = this.RunWorker(this._states[ree], this._requestsPerBlock, 3, default); } // Wait for warmup to complete. await Task.WhenAll(this._tasks); // Ignore warmup blocks. while (completedBlockReader.TryRead(out _)) ; GC.Collect(); GC.Collect(); GC.Collect(); } private void ResetBetweenRuns() { this._completedBlocks = Channel.CreateUnbounded( new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }); } public async Task Run(CancellationToken ct) { this.ResetBetweenRuns(); var completedBlockReader = this._completedBlocks.Reader; // Start the run. for (var i = 0; i < this._numWorkers; i++) { this._tasks[i] = this.RunWorker(this._states[i], this._requestsPerBlock, this._blocksPerWorker, ct); } var completion = Task.WhenAll(this._tasks); _ = Task.Run(async () => { try { await completion; } catch { } finally { this._completedBlocks.Writer.Complete(); } }); // Do not allocated a list with a too high capacity var blocks = new List(this._numWorkers * Math.Min(100, this._blocksPerWorker)); var blocksPerReport = this._numWorkers * Math.Min(100, this._blocksPerWorker) / 5; var nextReportBlockCount = blocksPerReport; while (!completion.IsCompleted) { var more = await completedBlockReader.WaitToReadAsync(); if (!more) break; while (completedBlockReader.TryRead(out var block)) { // Register the measurement values BenchmarksEventSource.Measure("requests", block.Completed); BenchmarksEventSource.Measure("failures", block.Failures); BenchmarksEventSource.Measure("rps", block.RequestsPerSecond); blocks.Add(block); } if (this._logIntermediateResults && blocks.Count >= nextReportBlockCount) { nextReportBlockCount += blocksPerReport; _logger.LogInformation(" " + BuildReport(0)); } } var finalReport = BuildReport(0); if (this._logIntermediateResults) _logger.LogInformation(" Total: " + finalReport); else _logger.LogInformation(finalReport.ToString()); return finalReport; LoadGeneratorReport BuildReport(int statingBlockIndex) { if (blocks.Count == 0) return default; var successes = 0; var failures = 0; long completed = 0; var reportBlocks = 0; long minStartTime = long.MaxValue; long maxEndTime = long.MinValue; for (var i = statingBlockIndex; i < blocks.Count; i++) { var b = blocks[i]; ++reportBlocks; successes += b.Successes; failures += b.Failures; completed += b.Completed; if (b.StartTimestamp < minStartTime) minStartTime = b.StartTimestamp; if (b.EndTimestamp > maxEndTime) maxEndTime = b.EndTimestamp; } var totalSeconds = (maxEndTime - minStartTime) / StopwatchTickPerSecond; var ratePerSecond = (long)(completed / totalSeconds); return new LoadGeneratorReport { Completed = completed, Successes = successes, Failures = failures, BlocksCompleted = reportBlocks, TotalDuration = totalSeconds, }; } } private async Task RunWorker(TState state, int requestsPerBlock, int numBlocks, CancellationToken ct) { var completedBlockWriter = this._completedBlocks.Writer; while (numBlocks > 0 && !ct.IsCancellationRequested) { var workBlock = new WorkBlock(); workBlock.StartTimestamp = Stopwatch.GetTimestamp(); while (workBlock.Completed < requestsPerBlock) { try { await this._issueRequest(state).ConfigureAwait(false); ++workBlock.Successes; } catch { ++workBlock.Failures; } } workBlock.EndTimestamp = Stopwatch.GetTimestamp(); await completedBlockWriter.WriteAsync(workBlock); --numBlocks; } } } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/LoadGeneratorScenario/LoadGeneratorScenarioRunner.cs ================================================ using Azure.Identity; using DistributedTests.Common; using Microsoft.Crank.EventSources; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Orleans.Configuration; namespace DistributedTests.Client.LoadGeneratorScenario { public class ClientParameters { public string ServiceId { get; set; } public string ClusterId { get; set; } public int ConnectionsPerEndpoint { get; set; } public Uri AzureTableUri { get; set; } public Uri AzureQueueUri { get; set; } } public class LoadGeneratorParameters { public int NumWorkers { get; set; } public int BlocksPerWorker { get; set; } public int RequestsPerBlock { get; set; } public int Duration { get; set; } } public class LoadGeneratorScenarioRunner { private readonly ILoadGeneratorScenario _scenario; private readonly ILogger _logger; public LoadGeneratorScenarioRunner(ILoadGeneratorScenario scenario, ILoggerFactory loggerFactory) { _scenario = scenario; _logger = loggerFactory.CreateLogger(scenario.Name); } public async Task Run(ClientParameters clientParams, LoadGeneratorParameters loadParams) { Console.WriteLine($"AzureTableUri: {clientParams.AzureTableUri}"); // Register the measurements. n0 -> format as natural number BenchmarksEventSource.Register("requests", Operations.Sum, Operations.Sum, "Requests", "Number of requests completed", "n0"); BenchmarksEventSource.Register("failures", Operations.Sum, Operations.Sum, "Failures", "Number of failures", "n0"); BenchmarksEventSource.Register("rps", Operations.Sum, Operations.Median, "Median RPS", "Rate per second", "n0"); var hostBuilder = new HostBuilder().UseOrleansClient((ctx, builder) => builder.Configure(options => { options.ClusterId = clientParams.ClusterId; options.ServiceId = clientParams.ServiceId; }) .Configure(options => clientParams.ConnectionsPerEndpoint = 2) .UseAzureStorageClustering(options => options.TableServiceClient = clientParams.AzureTableUri.CreateTableServiceClient())); using var host = hostBuilder.Build(); _logger.LogInformation("Connecting to cluster..."); await host.StartAsync(); var client = host.Services.GetService(); var generator = new ConcurrentLoadGenerator( numWorkers: loadParams.NumWorkers, blocksPerWorker: loadParams.BlocksPerWorker != 0 ? loadParams.BlocksPerWorker : int.MaxValue, requestsPerBlock: loadParams.RequestsPerBlock, issueRequest: _scenario.IssueRequest, getStateForWorker: workerId => _scenario.GetStateForWorker(client, workerId), logger: _logger, logIntermediateResults: true); _logger.LogInformation("Warming-up..."); await generator.Warmup(); var cts = loadParams.Duration != 0 ? new CancellationTokenSource(TimeSpan.FromSeconds(loadParams.Duration)) : new CancellationTokenSource(); _logger.LogInformation("Running"); var report = await generator.Run(cts.Token); BenchmarksEventSource.Register("overall-rps", Operations.Last, Operations.Last, "Overall RPS", "RPS", "n0"); BenchmarksEventSource.Measure("overall-rps", report.RatePerSecond); await host.StopAsync(); } } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/LoadGeneratorScenario/LoadGeneratorScenarios.cs ================================================ using DistributedTests.GrainInterfaces; namespace DistributedTests.Client.LoadGeneratorScenario { public interface ILoadGeneratorScenario { string Name { get; } TState GetStateForWorker(IClusterClient client, int workerId); ValueTask IssueRequest(TState state); } public class PingScenario : ILoadGeneratorScenario { public string Name => "ping"; public IPingGrain GetStateForWorker(IClusterClient client, int workerId) => client.GetGrain(Guid.NewGuid()); public ValueTask IssueRequest(IPingGrain state) => state.Ping(); } public class FanOutScenario : ILoadGeneratorScenario { public string Name => "fan-out"; public ITreeGrain GetStateForWorker(IClusterClient client, int workerId) => client.GetGrain(primaryKey: 0, keyExtension: workerId.ToString()); public ValueTask IssueRequest(ITreeGrain root) => root.Ping(); } } ================================================ FILE: test/DistributedTests/DistributedTests.Client/Program.cs ================================================ using System.CommandLine; using System.CommandLine.Parsing; using DistributedTests.Client.Commands; using DistributedTests.Client.LoadGeneratorScenario; using Microsoft.Extensions.Logging; var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Information) .AddSimpleConsole(options => options.SingleLine = true)); var root = new RootCommand(); root.Add(Scenario.CreateCommand(new PingScenario(), loggerFactory)); root.Add(Scenario.CreateCommand(new FanOutScenario(), loggerFactory)); root.Add(new CounterCaptureCommand(loggerFactory.CreateLogger())); root.Add(new ChaosAgentCommand(loggerFactory.CreateLogger())); await root.InvokeAsync(args); ================================================ FILE: test/DistributedTests/DistributedTests.Common/DistributedTests.Common.csproj ================================================ $(TestTargetFrameworks) true ================================================ FILE: test/DistributedTests/DistributedTests.Common/GrainInterfaces/IPingGrain.cs ================================================ namespace DistributedTests.GrainInterfaces; public interface IPingGrain : IGrainWithGuidKey { ValueTask Ping(); } ================================================ FILE: test/DistributedTests/DistributedTests.Common/GrainInterfaces/IStreamingGrains.cs ================================================ namespace DistributedTests.GrainInterfaces { public static class StreamingConstants { public const string StreamingProvider = "TestStreamingProvider"; public const string StreamingNamespace = "TestStreamingNamespace"; public const string DefaultCounterGrain = "default"; } public class ReportingOptions { public DateTime ReportAt { get; set; } public int Duration { get; set; } } public interface IGrainWithCounter : IGrainWithGuidKey { Task GetCounterValue(string counterName); } public interface IImplicitSubscriberGrain : IGrainWithCounter { } public interface ICounterGrain : IGrainWithStringKey { Task Track(IGrainWithCounter grain); Task GetRunDuration(); Task WaitTimeForReport(); Task GetTotalCounterValue(string counterName); } } ================================================ FILE: test/DistributedTests/DistributedTests.Common/GrainInterfaces/ITreeGrain.cs ================================================ namespace DistributedTests.GrainInterfaces; public interface ITreeGrain : IGrainWithIntegerCompoundKey { public ValueTask Ping(); } ================================================ FILE: test/DistributedTests/DistributedTests.Common/MessageChannel/Channels.cs ================================================ using System.Diagnostics; using System.Text.Json; using Azure.Identity; using Azure.Storage.Queues; namespace DistributedTests.Common.MessageChannel { public interface ISendChannel { Task> SendMessages(List messages, CancellationToken cancellationToken); } public interface IReceiveChannel { Task WaitForMessage(CancellationToken cancellationToken); Task SendAck(ServerMessage message); } public class SendChannel : ISendChannel { private readonly QueueClient _writeQueue; private readonly QueueClient _readQueue; internal SendChannel(QueueClient writeQueue, QueueClient readQueue) { _writeQueue = writeQueue; _readQueue = readQueue; } public async Task> SendMessages(List messages, CancellationToken cancellationToken) { foreach (var msg in messages) { await _writeQueue.SendMessageAsync(JsonSerializer.Serialize(msg), cancellationToken); } var acks = new List(); for (var i = 0; i < messages.Count; i++) { var msg = await _readQueue.WaitForMessage(cancellationToken); acks.Add(msg); } return acks; } } public class ReceiveChannel : IReceiveChannel { private readonly QueueClient _writeQueue; private readonly QueueClient _readQueue; private readonly string _serverName; internal ReceiveChannel(QueueClient writeQueue, QueueClient readQueue, string serverName) { _writeQueue = writeQueue; _readQueue = readQueue; _serverName = serverName; } public async Task WaitForMessage(CancellationToken cancellationToken) => await _readQueue.WaitForMessage(cancellationToken); public async Task SendAck(ServerMessage message) { var ack = AckMessage.CreateAckMessage(message, _serverName); await _writeQueue.SendMessageAsync(JsonSerializer.Serialize(ack)); } } public static class Channels { private static readonly string CLIENT_TO_SERVER_QUEUE = "servers-{0}"; private static readonly string SILO_TO_CLIENT_QUEUE = "client-{0}"; public static Task CreateSendChannel(string clusterId, Uri azureQueueUri) => CreateSendChannel(clusterId, azureQueueUri.CreateQueueServiceClient()); public static async Task CreateSendChannel(string clusterId, QueueServiceClient queueServiceClient) { var writeQueue = queueServiceClient.GetQueueClient(string.Format(CLIENT_TO_SERVER_QUEUE, clusterId)); var readQueue = queueServiceClient.GetQueueClient(string.Format(SILO_TO_CLIENT_QUEUE, clusterId)); await writeQueue.CreateIfNotExistsAsync(); await readQueue.CreateIfNotExistsAsync(); return new SendChannel(writeQueue, readQueue); } public static Task CreateReceiveChannel(string serverName, string clusterId, Uri azureQueueUri) => CreateReceiveChannel(serverName, clusterId, azureQueueUri.CreateQueueServiceClient()); public static async Task CreateReceiveChannel(string serverName, string clusterId, QueueServiceClient queueServiceClient) { var writeQueue = queueServiceClient.GetQueueClient(string.Format(SILO_TO_CLIENT_QUEUE, clusterId)); var readQueue = queueServiceClient.GetQueueClient(string.Format(CLIENT_TO_SERVER_QUEUE, clusterId)); await writeQueue.CreateIfNotExistsAsync(); await readQueue.CreateIfNotExistsAsync(); return new ReceiveChannel(writeQueue, readQueue, serverName); } internal static async Task WaitForMessage(this QueueClient queueClient, CancellationToken ct) { while (!ct.IsCancellationRequested) { var result = await queueClient.ReceiveMessagesAsync(maxMessages: 1, cancellationToken: ct); var msg = result.Value?.FirstOrDefault(); if (msg != null) { await queueClient.DeleteMessageAsync(msg.MessageId, msg.PopReceipt); return JsonSerializer.Deserialize(msg.MessageText); } await Task.Delay(1000, ct); } ct.ThrowIfCancellationRequested(); throw new Exception("No message"); } } } ================================================ FILE: test/DistributedTests/DistributedTests.Common/MessageChannel/Messages.cs ================================================ namespace DistributedTests.Common.MessageChannel { public class ServerMessage { public Guid MessageId { get; } public bool IsGraceful { get; } public bool Restart { get; } public ServerMessage(bool isGraceful, bool restart) { MessageId = Guid.NewGuid(); IsGraceful = isGraceful; Restart = restart; } } public class AckMessage { public Guid MessageId { get; } public string ServerName { get; set; } public AckMessage(Guid messageId, string serverName) { MessageId = messageId; ServerName = serverName; } public static AckMessage CreateAckMessage(ServerMessage msg, string serverName) => new(msg.MessageId, serverName); } } ================================================ FILE: test/DistributedTests/DistributedTests.Common/OptionHelper.cs ================================================ using System.CommandLine; using System.CommandLine.Parsing; namespace DistributedTests { public static class OptionHelper { public static Option CreateOption(string alias, string description = null, bool isRequired = false, T defaultValue = default, Func validator = null) { var options = new Option(alias, description) { IsRequired = isRequired }; if (!isRequired) { options.SetDefaultValue(defaultValue); } if (validator != null) { options.AddValidator(result => Validator(result, validator)); } return options; } public static string Validator(OptionResult result, Func validator) { var value = result.GetValueOrDefault(); if (!validator(value)) { return $"Option {result.Token?.Value} cannot be set to {value}"; } return string.Empty; } public static bool OnlyStrictlyPositive(int value) => value > 0; public static bool OnlyPositiveOrZero(int value) => value >= 0; } } ================================================ FILE: test/DistributedTests/DistributedTests.Common/TokenCredentialHelper.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Azure.Core; using Azure.Data.Tables; using Azure.Identity; using Azure.Storage.Queues; namespace DistributedTests.Common; public static class TokenCredentialHelper { private static string EmulatorConnectionString = "UseDevelopmentStorage=true"; public static TableServiceClient CreateTableServiceClient(this Uri azureTableUri) { if (azureTableUri.IsLoopback) { // Assume it's the emulator/azurite return new TableServiceClient(EmulatorConnectionString); } return new TableServiceClient(azureTableUri, GetTokenCredential()); } public static QueueServiceClient CreateQueueServiceClient(this Uri azureQueueUri) { if (azureQueueUri.IsLoopback) { // Assume it's the emulator/azurite return new QueueServiceClient(EmulatorConnectionString); } return new QueueServiceClient(azureQueueUri, GetTokenCredential()); } public static TokenCredential GetTokenCredential() { var tenantId = Environment.GetEnvironmentVariable("TENANT_ID"); var clientId = Environment.GetEnvironmentVariable("CLIENT_ID"); if (tenantId != null && clientId != null) { // Uses Federated Id Creds, from here: // https://review.learn.microsoft.com/en-us/identity/microsoft-identity-platform/federated-identity-credentials?branch=main&tabs=dotnet#azure-sdk-for-net return new ClientAssertionCredential( tenantId, // Tenant ID for destination resource clientId, // Client ID of the app we're federating to () => GetManagedIdentityToken(null, "api://AzureADTokenExchange")) // null here for default MSI ; } else { return new DefaultAzureCredential(); } } /// /// Gets a token for the user-assigned Managed Identity. /// /// Client ID for the Managed Identity. /// Target audience. For public clouds should be api://AzureADTokenExchange. /// If successful, returns an access token. public static string GetManagedIdentityToken(string msiClientId, string audience) { var miCredential = new ManagedIdentityCredential(msiClientId); return miCredential.GetToken(new TokenRequestContext(new[] { $"{audience}/.default" })).Token; } } ================================================ FILE: test/DistributedTests/DistributedTests.Grains/CounterReportingGrain.cs ================================================ using DistributedTests.GrainInterfaces; using Microsoft.Extensions.Options; namespace DistributedTests.Grains { public class CounterGrain : Grain, ICounterGrain { private readonly ReportingOptions _options; private readonly List _trackedGrains = new(); public CounterGrain(IOptions options) { _options = options.Value; } public Task GetRunDuration() => Task.FromResult(TimeSpan.FromSeconds(_options.Duration)); public async Task GetTotalCounterValue(string counterName) { var counter = 0; foreach (var grain in _trackedGrains) { counter += await grain.GetCounterValue(counterName); } return counter; } public Task Track(IGrainWithCounter grain) { _trackedGrains.Add(grain); return Task.CompletedTask; } public Task WaitTimeForReport() { var ts = _options.ReportAt - DateTime.UtcNow; return ts < TimeSpan.Zero ? Task.FromResult(TimeSpan.Zero) : Task.FromResult(ts); } } } ================================================ FILE: test/DistributedTests/DistributedTests.Grains/DistributedTests.Grains.csproj ================================================ $(TestTargetFrameworks) true ================================================ FILE: test/DistributedTests/DistributedTests.Grains/ImplicitSubscriberGrain.cs ================================================ using DistributedTests.GrainInterfaces; using Microsoft.Extensions.Logging; using Orleans.Streams; using Orleans.Streams.Core; namespace DistributedTests.Grains { [ImplicitStreamSubscription(StreamingConstants.StreamingNamespace)] public class ImplicitSubscriberGrain : Grain, IImplicitSubscriberGrain, IStreamSubscriptionObserver, IAsyncObserver { private readonly ILogger _logger; private int _requestCounter; private int _errorCounter; public ImplicitSubscriberGrain(ILogger logger) { _logger = logger; } public Task GetCounterValue(string counterName) { return counterName switch { "requests" => Task.FromResult(_requestCounter), "errors" => Task.FromResult(_errorCounter), _ => throw new ArgumentOutOfRangeException(nameof(counterName)), }; } public Task OnCompletedAsync() => Task.CompletedTask; public Task OnErrorAsync(Exception ex) { _logger.LogError(ex, "OnErrorAsync"); _errorCounter++; return Task.CompletedTask; } public Task OnNextAsync(object item, StreamSequenceToken token = null) { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("OnNextAsync {Item}", item); _requestCounter++; return Task.CompletedTask; } public async Task OnSubscribed(IStreamSubscriptionHandleFactory handleFactory) { if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("OnSubscribed {StreamId}", handleFactory.StreamId); await GrainFactory .GetGrain(StreamingConstants.DefaultCounterGrain) .Track(this.AsReference()); await handleFactory .Create() .ResumeAsync(this); } } } ================================================ FILE: test/DistributedTests/DistributedTests.Grains/PingGrain.cs ================================================ using DistributedTests.GrainInterfaces; namespace DistributedTests.Grains; public class PingGrain : Grain, IPingGrain { public ValueTask Ping() => default; } ================================================ FILE: test/DistributedTests/DistributedTests.Grains/TreeGrain.cs ================================================ using DistributedTests.GrainInterfaces; namespace DistributedTests.Grains; public class TreeGrain : Grain, ITreeGrain { // 16^4 grains (~65K) public const int FanOutFactor = 16; public const int MaxLevel = 4; private readonly List _children; public TreeGrain() { var id = this.GetPrimaryKeyLong(out var forestName); var level = id == 0 ? 0 : (int)Math.Log(id, FanOutFactor); var numChildren = level < MaxLevel ? FanOutFactor : 0; _children = new List(numChildren); var childBase = (id + 1) * FanOutFactor; for (var i = 1; i <= numChildren; i++) { var child = GrainFactory.GetGrain(childBase + i, keyExtension: forestName); _children.Add(child); } } public async ValueTask Ping() { var tasks = new List(_children.Count); foreach (var child in _children) { tasks.Add(child.Ping()); } // Wait for the tasks to complete. foreach (var task in tasks) { await task; } } } ================================================ FILE: test/DistributedTests/DistributedTests.Server/Configurator/EventGeneratorStreamingSilo.cs ================================================ using System.CommandLine; using DistributedTests.GrainInterfaces; using Microsoft.Extensions.DependencyInjection; using Orleans.Configuration; using Orleans.Providers.Streams.Common; using Orleans.Providers.Streams.Generator; using Orleans.Runtime; using Orleans.Streams; namespace DistributedTests.Server.Configurator { public class EventGeneratorStreamingSilo : ISiloConfigurator { public class Parameter { public StreamPubSubType Type { get; set; } public int StreamsPerQueue { get; set; } public int QueueCount { get; set; } public int BatchSize { get; set; } public int Wait { get; set; } public int Duration { get; set; } } public string Name => nameof(EventGeneratorStreamingSilo); public List