├── .editorconfig ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build-workflow.yml │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Foundatio.All.code-workspace ├── Foundatio.All.sln ├── Foundatio.sln ├── LICENSE.txt ├── NuGet.config ├── README.md ├── build ├── Foundatio.snk ├── Update-Nito.ps1 ├── common.props └── foundatio-icon.png ├── global.json ├── media ├── colors.png ├── foundatio-dark-bg.svg ├── foundatio-icon.png ├── foundatio-icon.svg ├── foundatio-white.png ├── foundatio.png └── foundatio.svg ├── samples ├── Foundatio.AppHost │ ├── Extensions │ │ ├── ImageTags.cs │ │ └── RedisExtensions.cs │ ├── Foundatio.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json └── Foundatio.HostingSample │ ├── Foundatio.HostingSample.csproj │ ├── Jobs │ ├── EveryMinuteJob.cs │ ├── Sample1Job.cs │ ├── Sample2Job.cs │ └── SampleLockJob.cs │ ├── MyCriticalHealthCheck.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── ServiceDefaults.cs │ ├── Startup │ ├── MyStartupAction.cs │ └── OtherStartupAction.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── src ├── Directory.Build.props ├── Foundatio.DataProtection │ ├── Extensions │ │ └── DataProtectionBuilderExtensions.cs │ ├── Foundatio.DataProtection.csproj │ └── FoundatioStorageXmlRepository.cs ├── Foundatio.Extensions.Hosting │ ├── Cronos │ │ ├── CalendarHelper.cs │ │ ├── CronExpression.cs │ │ ├── CronExpressionFlag.cs │ │ ├── CronField.cs │ │ ├── CronFormat.cs │ │ ├── CronFormatException.cs │ │ └── TimeZoneHelper.cs │ ├── Foundatio.Extensions.Hosting.csproj │ ├── Jobs │ │ ├── Cron.cs │ │ ├── DynamicJob.cs │ │ ├── HostedJobOptions.cs │ │ ├── HostedJobService.cs │ │ ├── JobHostExtensions.cs │ │ ├── JobManager.cs │ │ ├── JobOptionsBuilder.cs │ │ ├── ScheduledJobInstance.cs │ │ ├── ScheduledJobOptions.cs │ │ ├── ScheduledJobOptionsBuilder.cs │ │ ├── ScheduledJobRegistration.cs │ │ ├── ScheduledJobService.cs │ │ └── ShutdownHostIfNoJobsRunningService.cs │ └── Startup │ │ ├── IStartupAction.cs │ │ ├── RunStartupActionsService.cs │ │ ├── StartupActionRegistration.cs │ │ ├── StartupActionsContext.cs │ │ ├── StartupExtensions.cs │ │ ├── StartupHealthcheck.cs │ │ ├── StartupPriorityAttribute.cs │ │ └── WaitForStartupActionsBeforeServingRequestsMiddleware.cs ├── Foundatio.JsonNet │ ├── Foundatio.JsonNet.csproj │ └── JsonNetSerializer.cs ├── Foundatio.MessagePack │ ├── Foundatio.MessagePack.csproj │ └── MessagePackSerializer.cs ├── Foundatio.TestHarness │ ├── Caching │ │ ├── CacheClientTestsBase.cs │ │ └── HybridCacheClientTests.cs │ ├── Extensions │ │ └── TaskExtensions.cs │ ├── Foundatio.TestHarness.csproj │ ├── GlobalSuppressions.cs │ ├── Jobs │ │ ├── HelloWorldJob.cs │ │ ├── JobQueueTestsBase.cs │ │ ├── SampleQueueJob.cs │ │ ├── ThrottledJob.cs │ │ ├── WithDependencyJob.cs │ │ └── WithLockingJob.cs │ ├── Locks │ │ └── LockTestBase.cs │ ├── Messaging │ │ ├── MessageBusTestBase.cs │ │ └── Samples.cs │ ├── Queue │ │ ├── QueueTestBase.cs │ │ └── Samples.cs │ ├── Serializer │ │ └── SerializerTestsBase.cs │ ├── Storage │ │ └── FileStorageTestsBase.cs │ └── Utility │ │ ├── BenchmarkToJson.cs │ │ ├── Configuration.cs │ │ ├── InMemoryMetrics.cs │ │ └── NonSeekableStream.cs ├── Foundatio.Utf8Json │ ├── Foundatio.Utf8Json.csproj │ └── Utf8JsonSerializer.cs ├── Foundatio.Xunit │ ├── Foundatio.Xunit.csproj │ ├── Logging │ │ ├── LogEntry.cs │ │ ├── LoggingExtensions.cs │ │ ├── TestLogger.cs │ │ ├── TestLoggerBase.cs │ │ ├── TestLoggerFixture.cs │ │ ├── TestLoggerLogger.cs │ │ ├── TestLoggerOptions.cs │ │ ├── TestLoggerProvider.cs │ │ └── TestWithLoggingBase.cs │ └── Retry │ │ ├── DelayedMessageBus.cs │ │ ├── RetryAttribute.cs │ │ ├── RetryFactDiscoverer.cs │ │ ├── RetryTestCase.cs │ │ ├── RetryTheoryAttribute.cs │ │ ├── RetryTheoryDiscoverer.cs │ │ └── RetryTheoryTestCase.cs └── Foundatio │ ├── Caching │ ├── CacheValue.cs │ ├── HybridCacheClient.cs │ ├── ICacheClient.cs │ ├── IMemoryCacheClient.cs │ ├── InMemoryCacheClient.cs │ ├── InMemoryCacheClientOptions.cs │ ├── NullCacheClient.cs │ └── ScopedCacheClient.cs │ ├── DeepCloner │ ├── DeepClonerExtensions.cs │ ├── Helpers │ │ ├── ClonerToExprGenerator.cs │ │ ├── DeepCloneState.cs │ │ ├── DeepClonerCache.cs │ │ ├── DeepClonerExprGenerator.cs │ │ ├── DeepClonerGenerator.cs │ │ ├── DeepClonerSafeTypes.cs │ │ ├── ReflectionHelper.cs │ │ ├── ShallowClonerGenerator.cs │ │ └── ShallowObjectCloner.cs │ └── LICENSE │ ├── Extensions │ ├── CacheClientExtensions.cs │ ├── CollectionExtensions.cs │ ├── ConcurrentDictionaryExtensions.cs │ ├── ConcurrentQueueExtensions.cs │ ├── DateTimeExtensions.cs │ ├── DictionaryExtensions.cs │ ├── EnumExtensions.cs │ ├── EnumerableExtensions.cs │ ├── ExceptionExtensions.cs │ ├── LoggerExtensions.cs │ ├── NumberExtensions.cs │ ├── ObjectExtensions.cs │ ├── StringExtensions.cs │ ├── TaskExtensions.cs │ ├── TimespanExtensions.cs │ └── TypeExtensions.cs │ ├── Foundatio.csproj │ ├── Jobs │ ├── IJob.cs │ ├── IQueueJob.cs │ ├── JobAttribute.cs │ ├── JobBase.cs │ ├── JobContext.cs │ ├── JobOptions.cs │ ├── JobResult.cs │ ├── JobRunner.cs │ ├── JobWithLockBase.cs │ ├── QueueEntryContext.cs │ ├── QueueJobBase.cs │ └── WorkItemJob │ │ ├── WorkItemContext.cs │ │ ├── WorkItemData.cs │ │ ├── WorkItemHandlers.cs │ │ ├── WorkItemJob.cs │ │ ├── WorkItemQueueExtensions.cs │ │ └── WorkItemStatus.cs │ ├── Lock │ ├── CacheLockProvider.cs │ ├── DisposableLock.cs │ ├── DisposableLockCollection.cs │ ├── ILockProvider.cs │ ├── ScopedLockProvider.cs │ └── ThrottlingLockProvider.cs │ ├── Messaging │ ├── IMessageBus.cs │ ├── IMessagePublisher.cs │ ├── IMessageSubscriber.cs │ ├── InMemoryMessageBus.cs │ ├── InMemoryMessageBusOptions.cs │ ├── Message.cs │ ├── MessageBusBase.cs │ ├── NullMessageBus.cs │ └── SharedMessageBusOptions.cs │ ├── Metrics │ └── IHaveSubMetricName.cs │ ├── Nito.AsyncEx.Coordination │ ├── AsyncAutoResetEvent.cs │ ├── AsyncConditionVariable.cs │ ├── AsyncCountdownEvent.cs │ ├── AsyncLazy.cs │ ├── AsyncLock.cs │ ├── AsyncManualResetEvent.cs │ ├── AsyncReaderWriterLock.cs │ ├── AsyncSemaphore.cs │ ├── AsyncWaitQueue.cs │ ├── IdManager.cs │ └── LICENSE │ ├── Nito.AsyncEx.Tasks │ ├── AwaitableDisposable.cs │ ├── CancellationTokenTaskSource.cs │ ├── ExceptionHelpers.cs │ ├── LICENSE │ ├── Synchronous │ │ └── TaskExtensions.cs │ ├── TaskCompletionSourceExtensions.cs │ ├── TaskConstants.cs │ └── TaskExtensions.cs │ ├── Nito.Collections.Deque │ ├── CollectionHelpers.cs │ ├── Deque.cs │ └── LICENSE │ ├── Nito.Disposables │ ├── AnonymousDisposable.cs │ ├── Internals │ │ └── BoundAction.cs │ ├── LICENSE │ └── SingleDisposable.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Queues │ ├── DuplicateDetectionQueueBehavior.cs │ ├── IQueue.cs │ ├── IQueueActivity.cs │ ├── IQueueEntry.cs │ ├── InMemoryQueue.cs │ ├── InMemoryQueueOptions.cs │ ├── QueueBase.cs │ ├── QueueBehaviour.cs │ ├── QueueEntry.cs │ └── SharedQueueOptions.cs │ ├── Serializer │ ├── IHaveSerializer.cs │ ├── ISerializer.cs │ └── SystemTextJsonSerializer.cs │ ├── Storage │ ├── ActionableStream.cs │ ├── FolderFileStorage.cs │ ├── FolderFileStorageOptions.cs │ ├── IFileStorage.cs │ ├── InMemoryFileStorage.cs │ ├── InMemoryFileStorageOptions.cs │ ├── ScopedFileStorage.cs │ ├── StorageException.cs │ └── StreamMode.cs │ └── Utility │ ├── AsyncDisposableAction.cs │ ├── AsyncEvent.cs │ ├── ConnectionStringParser.cs │ ├── DataDictionary.cs │ ├── DisposableAction.cs │ ├── EmptyDisposable.cs │ ├── FoundatioDiagnostics.cs │ ├── IAsyncDisposable.cs │ ├── IAsyncLifetime.cs │ ├── IHaveLogger.cs │ ├── IHaveTimeProvider.cs │ ├── InstrumentsValues.cs │ ├── MaintenanceBase.cs │ ├── OptionsBuilder.cs │ ├── PathHelper.cs │ ├── Run.cs │ ├── ScheduledTimer.cs │ ├── SharedOptions.cs │ ├── TimeUnit.cs │ └── TypeHelper.cs ├── start-all-services.ps1 ├── stop-all-services.ps1 └── tests ├── Directory.Build.props └── Foundatio.Tests ├── Caching ├── InMemoryCacheClientTests.cs └── InMemoryHybridCacheClientTests.cs ├── Foundatio.Tests.csproj ├── Hosting ├── HostingTests.cs └── TestServerExtensions.cs ├── Jobs ├── InMemoryJobQueueTests.cs ├── JobTests.cs └── WorkItemJobTests.cs ├── Locks └── InMemoryLockTests.cs ├── Messaging └── InMemoryMessageBusTests.cs ├── Properties └── AssemblyInfo.cs ├── Queue └── InMemoryQueueTests.cs ├── Serializer ├── CompressedMessagePackSerializerTests.cs ├── JsonNetSerializerTests.cs ├── MessagePackSerializerTests.cs ├── SystemTextJsonSerializerTests.cs └── Utf8JsonSerializerTests.cs ├── Storage ├── FolderFileStorageTests.cs ├── InMemoryFileStorageTests.cs ├── ScopedFolderFileStorageTests.cs └── ScopedInMemoryFileStorageTests.cs └── Utility ├── CloneTests.cs ├── ConnectionStringParserTests.cs ├── DataDictionaryTests.cs ├── RunTests.cs ├── ScheduledTimerTests.cs ├── TestLoggerTests.cs └── TestUdpListener.cs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: exceptionless 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: nuget 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | uses: ./.github/workflows/build-workflow.yml 7 | secrets: inherit 8 | with: 9 | solution: Foundatio.sln -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 1 * * 2' 6 | 7 | jobs: 8 | CodeQL-Build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | # We must fetch at least the immediate parents so that if this is 17 | # a pull request then we can checkout the head. 18 | fetch-depth: 2 19 | 20 | # If this run was triggered by a pull request event, then checkout 21 | # the head of the pull request instead of the merge commit. 22 | - run: git checkout HEAD^2 23 | if: ${{ github.event_name == 'pull_request' }} 24 | 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v1 27 | with: 28 | languages: csharp 29 | 30 | - name: Setup .NET Core 31 | uses: actions/setup-dotnet@v4 32 | with: 33 | dotnet-version: 8.0.x 34 | 35 | - name: Build 36 | run: dotnet build Foundatio.sln --configuration Release 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v1 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | *.suo 3 | *.user 4 | 5 | # Build results 6 | [Bb]in/ 7 | [Oo]bj/ 8 | artifacts 9 | .vs/ 10 | 11 | # MSTest test Results 12 | [Tt]est[Rr]esult*/ 13 | 14 | # ReSharper is a .NET coding add-in 15 | _ReSharper*/ 16 | *.[Rr]e[Ss]harper 17 | *.DotSettings.user 18 | 19 | # JustCode is a .NET coding addin-in 20 | .JustCode 21 | 22 | # DotCover is a Code Coverage Tool 23 | *.dotCover 24 | 25 | # NCrunch 26 | _NCrunch_* 27 | .*crunch*.local.xml 28 | 29 | # NuGet Packages 30 | *.nupkg 31 | 32 | .DS_Store 33 | 34 | # Rider 35 | .idea 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Hosting Sample", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/samples/Foundatio.HostingSample/bin/Debug/net8.0/Foundatio.HostingSample.dll", 14 | "args": [ ], 15 | "cwd": "${workspaceFolder}/samples/Foundatio.HostingSample", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "integratedTerminal", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Dgram", 4 | "Sachin", 5 | "Xunit", 6 | "mygauge", 7 | "unscoped" 8 | ], 9 | "msbuildProjectTools.nuget.includePreRelease": true 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "args": [ 13 | "build", 14 | "${workspaceFolder}/Foundatio.sln", 15 | "/p:GenerateFullPaths=true" 16 | ], 17 | "problemMatcher": "$msCompile" 18 | }, 19 | { 20 | "label": "test", 21 | "command": "dotnet", 22 | "type": "process", 23 | "group": { 24 | "kind": "test", 25 | "isDefault": true 26 | }, 27 | "args": [ 28 | "test", 29 | "${workspaceFolder}/Foundatio.sln", 30 | "/p:GenerateFullPaths=true" 31 | ], 32 | "problemMatcher": "$msCompile" 33 | }, 34 | { 35 | "label": "pack", 36 | "command": "dotnet pack -c Release -o ${workspaceFolder}/artifacts", 37 | "type": "shell", 38 | "problemMatcher": [] 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Foundatio.All.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "../Foundatio.Repositories" 8 | }, 9 | { 10 | "path": "../Foundatio.Redis" 11 | }, 12 | { 13 | "path": "../Foundatio.RabbitMQ" 14 | }, 15 | { 16 | "path": "../Foundatio.Parsers" 17 | }, 18 | { 19 | "path": "../Foundatio.AzureStorage" 20 | }, 21 | { 22 | "path": "../Foundatio.AzureServiceBus" 23 | }, 24 | { 25 | "path": "../Foundatio.AWS" 26 | }, 27 | { 28 | "path": "../Foundatio.Aliyun" 29 | }, 30 | { 31 | "path": "../Foundatio.Minio" 32 | }, 33 | { 34 | "path": "../Foundatio.Storage.SshNet" 35 | }, 36 | { 37 | "path": "../Foundatio.Kafka" 38 | } 39 | ], 40 | "settings": { 41 | "msbuildProjectTools.nuget.includePreRelease": true 42 | } 43 | } -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build/Foundatio.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/build/Foundatio.snk -------------------------------------------------------------------------------- /build/Update-Nito.ps1: -------------------------------------------------------------------------------- 1 | $work_dir = Resolve-Path "$PSScriptRoot" 2 | $src_dir = Resolve-Path "$PSScriptRoot\..\src\Foundatio" 3 | 4 | Function UpdateSource { 5 | param( [string]$sourceUrl, [string]$name ) 6 | 7 | If (Test-Path $work_dir\$name.zip) { 8 | Remove-Item $work_dir\$name.zip 9 | } 10 | Invoke-WebRequest $sourceUrl -OutFile $work_dir\$name.zip 11 | If (Test-Path $work_dir\$name) { 12 | Remove-Item $work_dir\$name -Recurse -Force 13 | } 14 | [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null 15 | [System.IO.Compression.ZipFile]::ExtractToDirectory("$work_dir\$name.zip", "$work_dir\$name") 16 | 17 | Remove-Item $work_dir\$name.zip 18 | 19 | If (Test-Path $src_dir\$name) { 20 | Remove-Item $src_dir\$name -Recurse -Force 21 | } 22 | 23 | $dir = (Get-ChildItem $work_dir\$name | Select-Object -First 1).FullName 24 | 25 | New-Item $src_dir\$name -Type Directory 26 | Copy-Item "$dir\LICENSE" -Destination "$src_dir\$name" -Recurse -Force 27 | 28 | Get-ChildItem -Path "$dir\src\$name" -Filter *.cs -Recurse | 29 | Foreach-Object { 30 | $c = ($_ | Get-Content) 31 | $c = $c -replace 'namespace Nito','namespace Foundatio' 32 | $c = $c -replace 'using Nito','using Foundatio' 33 | $c | Set-Content $_.FullName.Replace("$dir\src\$name", "$src_dir\$name") 34 | } 35 | 36 | Remove-Item $work_dir\$name -Recurse -Force 37 | } 38 | 39 | # TODO: Need to update this to pull from master repo and discard unused files. 40 | #UpdateSource "https://github.com/StephenClearyArchive/AsyncEx.Tasks/archive/v1.0.0-delta-4.zip" "Nito.AsyncEx.Tasks" 41 | #UpdateSource "https://github.com/StephenClearyArchive/AsyncEx.Coordination/archive/v1.0.2.zip" "Nito.AsyncEx.Coordination" 42 | UpdateSource "https://github.com/StephenCleary/Disposables/archive/v2.0.1.zip" "Nito.Disposables" -------------------------------------------------------------------------------- /build/common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net8.0 5 | Foundatio 6 | Pluggable foundation blocks for building distributed apps. 7 | https://github.com/FoundatioFx/Foundatio 8 | https://github.com/FoundatioFx/Foundatio/releases 9 | Queue;Messaging;Message;Bus;ServiceBus;Locking;Lock;Distributed;File;Storage;Blob;Jobs;Metrics;Stats;Azure;Redis;StatsD;Amazon;AWS;S3;broker;Logging;Log 10 | true 11 | v 12 | 13 | Copyright (c) 2025 Foundatio. All rights reserved. 14 | FoundatioFx 15 | $(NoWarn);CS1591;NU1701 16 | true 17 | latest 18 | true 19 | $(SolutionDir)artifacts 20 | foundatio-icon.png 21 | README.md 22 | Apache-2.0 23 | $(PackageProjectUrl) 24 | true 25 | true 26 | embedded 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | true 35 | $(MSBuildThisFileDirectory)Foundatio.snk 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /build/foundatio-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/build/foundatio-icon.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /media/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/media/colors.png -------------------------------------------------------------------------------- /media/foundatio-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/media/foundatio-icon.png -------------------------------------------------------------------------------- /media/foundatio-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/media/foundatio-white.png -------------------------------------------------------------------------------- /media/foundatio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoundatioFx/Foundatio/e79abf6efda75e5fc7006f06b0153e7dbfe31c4d/media/foundatio.png -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/Extensions/ImageTags.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.AppHost.Extensions; 2 | 3 | public static class ImageTags 4 | { 5 | public const string Redis = "7.4-alpine"; 6 | } 7 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/Extensions/RedisExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Diagnostics.HealthChecks; 2 | using StackExchange.Redis; 3 | 4 | namespace Foundatio.AppHost.Extensions; 5 | 6 | public static class RedisExtensions 7 | { 8 | public static IResourceBuilder WithClearCommand( 9 | this IResourceBuilder builder) 10 | { 11 | builder.WithCommand( 12 | "clear-cache", 13 | "Clear Cache", 14 | async _ => 15 | { 16 | var redisConnectionString = await builder.Resource.GetConnectionStringAsync() ?? 17 | throw new InvalidOperationException("Unable to get the Redis connection string."); 18 | 19 | await using var connection = await ConnectionMultiplexer.ConnectAsync(redisConnectionString); 20 | 21 | await connection.GetDatabase().ExecuteAsync("FLUSHALL"); 22 | 23 | return CommandResults.Success(); 24 | }, 25 | new CommandOptions 26 | { 27 | UpdateState = context => context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy ? ResourceCommandState.Enabled : ResourceCommandState.Disabled 28 | }); 29 | return builder; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/Foundatio.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Exe 7 | net8.0 8 | enable 9 | enable 10 | 96cfcbc9-36fb-452f-9b99-0165197e1978 11 | False 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.AppHost.Extensions; 2 | using Projects; 3 | #pragma warning disable ASPIREPROXYENDPOINTS001 4 | 5 | var builder = DistributedApplication.CreateBuilder(args); 6 | 7 | var cache = builder.AddRedis("Redis", port: 6379) 8 | .WithContainerName("Foundatio-Redis") 9 | .WithImageTag(ImageTags.Redis) 10 | .WithEndpointProxySupport(false) 11 | .WithClearCommand() 12 | .WithRedisInsight(b => b.WithEndpointProxySupport(false).WithContainerName("Foundatio-RedisInsight") 13 | .WithUrlForEndpoint("http", u => u.DisplayText = "Cache")); 14 | 15 | builder.AddProject("Foundatio-HostingSample") 16 | .WithExternalHttpEndpoints() 17 | .WithReplicas(3) 18 | .WithReference(cache) 19 | .WaitFor(cache) 20 | .WithArgs("all") 21 | .WithUrls(u => 22 | { 23 | u.Urls.Clear(); 24 | u.Urls.Add(new ResourceUrlAnnotation { Url = "/jobs/status", DisplayText = "Job Status", Endpoint = u.GetEndpoint("http") }); 25 | u.Urls.Add(new ResourceUrlAnnotation { Url = "/jobs/run", DisplayText = "Run Job", Endpoint = u.GetEndpoint("http") }); 26 | }); 27 | 28 | builder.Build().Run(); 29 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17176;http://localhost:15249", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21196", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22113" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15249", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19180", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20234" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Foundatio.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Foundatio.HostingSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | False 6 | false 7 | REDIS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Jobs/EveryMinuteJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Caching; 5 | using Foundatio.Jobs; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Foundatio.HostingSample; 9 | 10 | public class EveryMinuteJob : IJob 11 | { 12 | private readonly ICacheClient _cacheClient; 13 | private readonly ILogger _logger; 14 | 15 | public EveryMinuteJob(ILoggerFactory loggerFactory, ICacheClient cacheClient) 16 | { 17 | _cacheClient = cacheClient; 18 | _logger = loggerFactory.CreateLogger(); 19 | } 20 | 21 | public async Task RunAsync(CancellationToken cancellationToken = default) 22 | { 23 | var runCount = await _cacheClient.IncrementAsync("EveryMinuteJob"); 24 | 25 | _logger.LogInformation("EveryMinuteJob Run Count={Count} Thread={ManagedThreadId}", runCount, Thread.CurrentThread.ManagedThreadId); 26 | 27 | await Task.Delay(TimeSpan.FromSeconds(30)); 28 | 29 | _logger.LogInformation("EveryMinuteJob Complete"); 30 | 31 | return JobResult.Success; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Jobs/Sample1Job.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Foundatio.Jobs; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Foundatio.HostingSample; 7 | 8 | [Job(Description = "Sample 1 job", Interval = "5s", IterationLimit = 5)] 9 | public class Sample1Job : IJob 10 | { 11 | private readonly ILogger _logger; 12 | private int _iterationCount = 0; 13 | 14 | public Sample1Job(ILoggerFactory loggerFactory) 15 | { 16 | _logger = loggerFactory.CreateLogger(); 17 | } 18 | 19 | public Task RunAsync(CancellationToken cancellationToken = default) 20 | { 21 | Interlocked.Increment(ref _iterationCount); 22 | _logger.LogTrace("Sample1Job Run #{IterationCount} Thread={ManagedThreadId}", _iterationCount, Thread.CurrentThread.ManagedThreadId); 23 | 24 | return Task.FromResult(JobResult.Success); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Jobs/Sample2Job.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Jobs; 5 | using Microsoft.Extensions.Diagnostics.HealthChecks; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Foundatio.HostingSample; 9 | 10 | [Job(Description = "Sample 2 job", Interval = "15s", IterationLimit = 24)] 11 | public class Sample2Job : IJob, IHealthCheck 12 | { 13 | private readonly ILogger _logger; 14 | private int _iterationCount = 0; 15 | private DateTime? _lastRun = null; 16 | 17 | public Sample2Job(ILoggerFactory loggerFactory) 18 | { 19 | _logger = loggerFactory.CreateLogger(); 20 | } 21 | 22 | public Task RunAsync(CancellationToken cancellationToken = default) 23 | { 24 | _lastRun = DateTime.UtcNow; 25 | Interlocked.Increment(ref _iterationCount); 26 | _logger.LogTrace("Sample2Job Run #{IterationCount} Thread={ManagedThreadId}", _iterationCount, Thread.CurrentThread.ManagedThreadId); 27 | 28 | return Task.FromResult(JobResult.Success); 29 | } 30 | 31 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) 32 | { 33 | if (!_lastRun.HasValue) 34 | return Task.FromResult(HealthCheckResult.Healthy("Job has not been run yet.")); 35 | 36 | if (DateTime.UtcNow.Subtract(_lastRun.Value) > TimeSpan.FromSeconds(5)) 37 | return Task.FromResult(HealthCheckResult.Unhealthy("Job has not run in the last 5 seconds.")); 38 | 39 | return Task.FromResult(HealthCheckResult.Healthy("Job has run in the last 5 seconds.")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Jobs/SampleLockJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Caching; 5 | using Foundatio.Jobs; 6 | using Foundatio.Lock; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Foundatio.HostingSample; 10 | 11 | [Job(Description = "Sample lock job", Interval = "5s")] 12 | public class SampleLockJob : JobWithLockBase 13 | { 14 | private readonly ILockProvider _lockProvider; 15 | 16 | public SampleLockJob(ICacheClient cache) 17 | { 18 | _lockProvider = new ThrottlingLockProvider(cache, 1, TimeSpan.FromMinutes(1)); 19 | } 20 | 21 | protected override Task GetLockAsync(CancellationToken cancellationToken = default) 22 | { 23 | return _lockProvider.AcquireAsync(nameof(SampleLockJob), TimeSpan.FromMinutes(15), new CancellationToken(true)); 24 | } 25 | 26 | protected override Task RunInternalAsync(JobContext context) 27 | { 28 | _logger.LogTrace("SampleLockJob Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); 29 | 30 | return Task.FromResult(JobResult.Success); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/MyCriticalHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Diagnostics.HealthChecks; 5 | 6 | namespace Foundatio.HostingSample; 7 | 8 | public class MyCriticalHealthCheck : IHealthCheck 9 | { 10 | private static DateTime _startTime = DateTime.Now; 11 | 12 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) 13 | { 14 | return DateTime.Now.Subtract(_startTime) > TimeSpan.FromSeconds(3) ? 15 | Task.FromResult(HealthCheckResult.Healthy("Critical resource is available.")) 16 | : Task.FromResult(HealthCheckResult.Unhealthy("Critical resource not available.")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "commandLineArgs": "all", 7 | "dotnetRunMessages": true, 8 | "launchBrowser": false, 9 | "launchUrl": "jobstatus", 10 | "applicationUrl": "http://localhost:5324", 11 | "environmentVariables": { 12 | "ASPNETCORE_ENVIRONMENT": "Development" 13 | } 14 | }, 15 | "https": { 16 | "commandName": "Project", 17 | "commandLineArgs": "all", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": false, 20 | "launchUrl": "jobstatus", 21 | "applicationUrl": "https://localhost:7580;http://localhost:5324", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/ServiceDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using OpenTelemetry; 4 | using OpenTelemetry.Metrics; 5 | using OpenTelemetry.Trace; 6 | 7 | namespace Microsoft.Extensions.Hosting; 8 | 9 | public static class Extensions 10 | { 11 | public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder 12 | { 13 | builder.ConfigureOpenTelemetry(); 14 | 15 | return builder; 16 | } 17 | 18 | public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder 19 | { 20 | builder.Logging.AddOpenTelemetry(logging => 21 | { 22 | logging.IncludeFormattedMessage = true; 23 | logging.IncludeScopes = true; 24 | }); 25 | 26 | builder.Services.AddOpenTelemetry() 27 | .WithMetrics(metrics => 28 | { 29 | metrics.AddAspNetCoreInstrumentation() 30 | .AddHttpClientInstrumentation() 31 | .AddRuntimeInstrumentation() 32 | .AddMeter("Foundatio"); 33 | }) 34 | .WithTracing(tracing => 35 | { 36 | tracing.AddAspNetCoreInstrumentation() 37 | .AddHttpClientInstrumentation() 38 | .AddSource("Foundatio"); 39 | }); 40 | 41 | builder.AddOpenTelemetryExporters(); 42 | 43 | return builder; 44 | } 45 | 46 | private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder 47 | { 48 | var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); 49 | 50 | if (useOtlpExporter) 51 | { 52 | builder.Services.AddOpenTelemetry().UseOtlpExporter(); 53 | } 54 | 55 | return builder; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Startup/MyStartupAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Caching; 5 | using Foundatio.Extensions.Hosting.Jobs; 6 | using Foundatio.Extensions.Hosting.Startup; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Foundatio.HostingSample; 10 | 11 | public class MyStartupAction : IStartupAction 12 | { 13 | private readonly IJobManager _jobManager; 14 | private readonly ICacheClient _cacheClient; 15 | private readonly ILogger _logger; 16 | 17 | public MyStartupAction(IJobManager jobManager, ICacheClient cacheClient, ILogger logger) 18 | { 19 | _jobManager = jobManager; 20 | _cacheClient = cacheClient; 21 | _logger = logger; 22 | } 23 | 24 | public async Task RunAsync(CancellationToken cancellationToken = default) 25 | { 26 | // set next run to be far in the past so it runs immediately 27 | await _cacheClient.SetAsync("jobs:every_minute:nextrun", DateTime.UtcNow.AddDays(-1)); 28 | 29 | for (int i = 0; i < 5; i++) 30 | { 31 | _logger.LogTrace("MyStartupAction Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); 32 | await Task.Delay(500); 33 | } 34 | 35 | _jobManager.AddOrUpdate("MyJob", j => j.CronSchedule("* * * * *").JobAction(async () => 36 | { 37 | _logger.LogInformation("Running MyJob"); 38 | await Task.Delay(1000); 39 | _logger.LogInformation("MyJob Complete"); 40 | })); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/Startup/OtherStartupAction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Foundatio.Extensions.Hosting.Startup; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Foundatio.HostingSample; 7 | 8 | public class OtherStartupAction : IStartupAction 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public OtherStartupAction(ILogger logger) 13 | { 14 | _logger = logger; 15 | } 16 | 17 | public async Task RunAsync(CancellationToken cancellationToken = default) 18 | { 19 | for (int i = 0; i < 5; i++) 20 | { 21 | _logger.LogTrace("OtherStartupAction Run Thread={ManagedThreadId}", Thread.CurrentThread.ManagedThreadId); 22 | await Task.Delay(900); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Foundatio.HostingSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Foundatio": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Foundatio.DataProtection/Foundatio.DataProtection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(PackageTags);DataProtection 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Cronos/CronExpressionFlag.cs: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright (c) 2017 Sergey Odinokov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | using System; 24 | 25 | namespace Foundatio.Extensions.Hosting.Cronos; 26 | 27 | [Flags] 28 | internal enum CronExpressionFlag : byte 29 | { 30 | DayOfMonthLast = 0b00001, 31 | DayOfWeekLast = 0b00010, 32 | Interval = 0b00100, 33 | NearestWeekday = 0b01000, 34 | NthDayOfWeek = 0b10000 35 | } 36 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Cronos/CronFormat.cs: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright (c) 2017 Sergey Odinokov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | using System; 24 | 25 | namespace Foundatio.Extensions.Hosting.Cronos; 26 | 27 | /// 28 | /// Defines the cron format options that customize string parsing for . 29 | /// 30 | [Flags] 31 | public enum CronFormat 32 | { 33 | /// 34 | /// Parsing string must contain only 5 fields: minute, hour, day of month, month, day of week. 35 | /// 36 | Standard = 0, 37 | 38 | /// 39 | /// Second field must be specified in parsing string. 40 | /// 41 | IncludeSeconds = 1 42 | } 43 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Cronos/CronFormatException.cs: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright (c) 2017 Sergey Odinokov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | using System; 24 | 25 | namespace Foundatio.Extensions.Hosting.Cronos; 26 | 27 | /// 28 | /// Represents an exception that's thrown, when invalid Cron expression is given. 29 | /// 30 | #if !NETSTANDARD1_0 31 | [Serializable] 32 | #endif 33 | public class CronFormatException : FormatException 34 | { 35 | /// 36 | /// Initializes a new instance of the class with 37 | /// the given message. 38 | /// 39 | public CronFormatException(string message) : base(message) 40 | { 41 | } 42 | 43 | internal CronFormatException(CronField field, string message) : this($"{field}: {message}") 44 | { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Foundatio.Extensions.Hosting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | true 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Jobs/DynamicJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Jobs; 5 | using Foundatio.Utility; 6 | 7 | namespace Foundatio.Extensions.Hosting.Jobs; 8 | 9 | internal class DynamicJob : IJob 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | private readonly Func _action; 13 | 14 | public DynamicJob(IServiceProvider serviceProvider, Func action) 15 | { 16 | _serviceProvider = serviceProvider; 17 | _action = action; 18 | } 19 | 20 | public async Task RunAsync(CancellationToken cancellationToken = default) 21 | { 22 | await _action(_serviceProvider, cancellationToken).AnyContext(); 23 | 24 | return JobResult.Success; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Jobs/HostedJobOptions.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Jobs; 2 | 3 | namespace Foundatio.Extensions.Hosting.Jobs; 4 | 5 | public class HostedJobOptions : JobOptions 6 | { 7 | public bool WaitForStartupActions { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Jobs/JobOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Foundatio.Jobs; 3 | 4 | namespace Foundatio.Extensions.Hosting.Jobs; 5 | 6 | public class HostedJobOptionsBuilder 7 | { 8 | public HostedJobOptionsBuilder(HostedJobOptions target = null) 9 | { 10 | Target = target ?? new HostedJobOptions(); 11 | } 12 | 13 | public HostedJobOptions Target { get; } 14 | 15 | public HostedJobOptionsBuilder ApplyDefaults() where T : IJob 16 | { 17 | Target.ApplyDefaults(); 18 | return this; 19 | } 20 | 21 | public HostedJobOptionsBuilder ApplyDefaults(Type jobType) 22 | { 23 | JobOptions.ApplyDefaults(Target, jobType); 24 | return this; 25 | } 26 | 27 | public HostedJobOptionsBuilder Name(string value) 28 | { 29 | Target.Name = value; 30 | return this; 31 | } 32 | 33 | public HostedJobOptionsBuilder Description(string value) 34 | { 35 | Target.Description = value; 36 | return this; 37 | } 38 | 39 | public HostedJobOptionsBuilder JobFactory(Func value) 40 | { 41 | Target.JobFactory = value; 42 | return this; 43 | } 44 | 45 | public HostedJobOptionsBuilder RunContinuous(bool value = true) 46 | { 47 | Target.RunContinuous = value; 48 | return this; 49 | } 50 | 51 | public HostedJobOptionsBuilder Interval(TimeSpan? value) 52 | { 53 | Target.Interval = value; 54 | return this; 55 | } 56 | 57 | public HostedJobOptionsBuilder InitialDelay(TimeSpan? value) 58 | { 59 | Target.InitialDelay = value; 60 | return this; 61 | } 62 | 63 | public HostedJobOptionsBuilder IterationLimit(int value) 64 | { 65 | Target.IterationLimit = value; 66 | return this; 67 | } 68 | 69 | public HostedJobOptionsBuilder InstanceCount(int value) 70 | { 71 | Target.InstanceCount = value; 72 | return this; 73 | } 74 | 75 | public HostedJobOptionsBuilder WaitForStartupActions(bool value = true) 76 | { 77 | Target.WaitForStartupActions = value; 78 | return this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using Foundatio.Jobs; 5 | 6 | namespace Foundatio.Extensions.Hosting.Jobs; 7 | 8 | public class ScheduledJobOptions : INotifyPropertyChanged 9 | { 10 | private string _name; 11 | private string _description; 12 | private Func _jobFactory; 13 | private bool _waitForStartupActions; 14 | private string _cronSchedule; 15 | private TimeZoneInfo _cronTimeZone; 16 | private bool _isDistributed; 17 | private bool _isEnabled = true; 18 | 19 | public string Name 20 | { 21 | get => _name; 22 | set => SetField(ref _name, value); 23 | } 24 | 25 | public string Description 26 | { 27 | get => _description; 28 | set => SetField(ref _description, value); 29 | } 30 | 31 | public Func JobFactory 32 | { 33 | get => _jobFactory; 34 | set => SetField(ref _jobFactory, value); 35 | } 36 | 37 | public bool WaitForStartupActions 38 | { 39 | get => _waitForStartupActions; 40 | set => SetField(ref _waitForStartupActions, value); 41 | } 42 | 43 | public string CronSchedule 44 | { 45 | get => _cronSchedule; 46 | set => SetField(ref _cronSchedule, value); 47 | } 48 | 49 | public TimeZoneInfo CronTimeZone 50 | { 51 | get => _cronTimeZone; 52 | set => SetField(ref _cronTimeZone, value); 53 | } 54 | 55 | public bool IsDistributed 56 | { 57 | get => _isDistributed; 58 | set => SetField(ref _isDistributed, value); 59 | } 60 | 61 | public bool IsEnabled 62 | { 63 | get => _isEnabled; 64 | set => SetField(ref _isEnabled, value); 65 | } 66 | 67 | public event PropertyChangedEventHandler PropertyChanged; 68 | 69 | private bool SetField(ref T field, T value, [CallerMemberName] string propertyName = "") 70 | { 71 | if (Equals(field, value)) 72 | return false; 73 | 74 | field = value; 75 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 76 | return true; 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Extensions.Hosting.Jobs; 2 | 3 | public class ScheduledJobRegistration 4 | { 5 | public ScheduledJobRegistration(ScheduledJobOptions options) 6 | { 7 | Options = options; 8 | } 9 | 10 | public ScheduledJobOptions Options { get; private set; } 11 | } 12 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/IStartupAction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Foundatio.Extensions.Hosting.Startup; 5 | 6 | public interface IStartupAction 7 | { 8 | Task RunAsync(CancellationToken shutdownToken = default); 9 | } 10 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/RunStartupActionsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Utility; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace Foundatio.Extensions.Hosting.Startup; 8 | 9 | public class RunStartupActionsService : BackgroundService 10 | { 11 | private readonly StartupActionsContext _startupContext; 12 | private readonly IServiceProvider _serviceProvider; 13 | 14 | public RunStartupActionsService(StartupActionsContext startupContext, IServiceProvider serviceProvider) 15 | { 16 | _startupContext = startupContext; 17 | _serviceProvider = serviceProvider; 18 | } 19 | 20 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 21 | { 22 | var result = await _serviceProvider.RunStartupActionsAsync(stoppingToken).AnyContext(); 23 | _startupContext.MarkStartupComplete(result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/StartupActionRegistration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Foundatio.Utility; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Foundatio.Extensions.Hosting.Startup; 9 | 10 | public class StartupActionRegistration 11 | { 12 | private readonly Func _action; 13 | private readonly Type _actionType; 14 | private static int _currentAutoPriority; 15 | 16 | public StartupActionRegistration(string name, Type startupType, int? priority = null) 17 | { 18 | Name = name; 19 | _actionType = startupType; 20 | if (!priority.HasValue) 21 | { 22 | var priorityAttribute = _actionType.GetCustomAttributes(typeof(StartupPriorityAttribute), true).FirstOrDefault() as StartupPriorityAttribute; 23 | Priority = priorityAttribute?.Priority ?? Interlocked.Increment(ref _currentAutoPriority); 24 | } 25 | else 26 | { 27 | Priority = priority.Value; 28 | } 29 | } 30 | 31 | public StartupActionRegistration(string name, Func action, int? priority = null) 32 | { 33 | Name = name; 34 | _action = action; 35 | if (!priority.HasValue) 36 | priority = Interlocked.Increment(ref _currentAutoPriority); 37 | 38 | Priority = priority.Value; 39 | } 40 | 41 | public string Name { get; private set; } 42 | 43 | public int Priority { get; private set; } 44 | 45 | public async Task RunAsync(IServiceProvider serviceProvider, CancellationToken shutdownToken = default) 46 | { 47 | if (shutdownToken.IsCancellationRequested) 48 | return; 49 | 50 | if (_actionType != null) 51 | { 52 | if (serviceProvider.GetRequiredService(_actionType) is IStartupAction startup) 53 | await startup.RunAsync(shutdownToken).AnyContext(); 54 | } 55 | else 56 | { 57 | await _action(serviceProvider, shutdownToken).AnyContext(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/StartupActionsContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Utility; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Foundatio.Extensions.Hosting.Startup; 8 | 9 | public class StartupActionsContext 10 | { 11 | private readonly ILogger _logger; 12 | private int _waitCount = 0; 13 | 14 | public StartupActionsContext(ILogger logger) 15 | { 16 | _logger = logger; 17 | } 18 | 19 | public bool IsStartupComplete { get; private set; } 20 | public RunStartupActionsResult Result { get; private set; } 21 | 22 | internal void MarkStartupComplete(RunStartupActionsResult result) 23 | { 24 | IsStartupComplete = true; 25 | Result = result; 26 | } 27 | 28 | public async Task WaitForStartupAsync(CancellationToken cancellationToken, TimeSpan? maxTimeToWait = null) 29 | { 30 | bool isFirstWaiter = Interlocked.Increment(ref _waitCount) == 1; 31 | var startTime = DateTime.UtcNow; 32 | var lastStatus = DateTime.UtcNow; 33 | maxTimeToWait ??= TimeSpan.FromMinutes(5); 34 | 35 | while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow.Subtract(startTime) < maxTimeToWait) 36 | { 37 | if (IsStartupComplete) 38 | return Result; 39 | 40 | if (isFirstWaiter && DateTime.UtcNow.Subtract(lastStatus) > TimeSpan.FromSeconds(5)) 41 | { 42 | lastStatus = DateTime.UtcNow; 43 | _logger.LogInformation("Waiting for startup actions to be completed for {Duration:mm\\:ss}...", DateTime.UtcNow.Subtract(startTime)); 44 | } 45 | 46 | await Task.Delay(1000, cancellationToken).AnyContext(); 47 | } 48 | 49 | if (isFirstWaiter) 50 | _logger.LogError("Timed out waiting for startup actions to be completed after {Duration:mm\\:ss}", DateTime.UtcNow.Subtract(startTime)); 51 | 52 | return new RunStartupActionsResult { Success = false, ErrorMessage = $"Timed out waiting for startup actions to be completed after {DateTime.UtcNow.Subtract(startTime):mm\\:ss}" }; 53 | } 54 | } 55 | 56 | public class StartupActionsException : Exception 57 | { 58 | public StartupActionsException(string message) : base(message) { } 59 | } 60 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/StartupHealthcheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Diagnostics.HealthChecks; 6 | 7 | namespace Foundatio.Extensions.Hosting.Startup; 8 | 9 | public class StartupActionsHealthCheck : IHealthCheck 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | 13 | public StartupActionsHealthCheck(IServiceProvider serviceProvider) 14 | { 15 | _serviceProvider = serviceProvider; 16 | } 17 | 18 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) 19 | { 20 | var startupContext = _serviceProvider.GetService(); 21 | 22 | // no startup actions registered 23 | if (startupContext == null) 24 | return Task.FromResult(HealthCheckResult.Healthy("No startup actions registered")); 25 | 26 | if (startupContext.IsStartupComplete && startupContext.Result.Success) 27 | return Task.FromResult(HealthCheckResult.Healthy("All startup actions completed")); 28 | 29 | if (startupContext.IsStartupComplete && !startupContext.Result.Success) 30 | return Task.FromResult(HealthCheckResult.Unhealthy($"Startup action \"{startupContext.Result.FailedActionName}\" failed to complete: {startupContext.Result.ErrorMessage}")); 31 | 32 | return Task.FromResult(HealthCheckResult.Unhealthy("Startup actions have not completed")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/StartupPriorityAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Extensions.Hosting.Startup; 4 | 5 | public class StartupPriorityAttribute : Attribute 6 | { 7 | public StartupPriorityAttribute(int priority) 8 | { 9 | Priority = priority; 10 | } 11 | 12 | public int Priority { get; private set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Foundatio.Extensions.Hosting/Startup/WaitForStartupActionsBeforeServingRequestsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Utility; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace Foundatio.Extensions.Hosting.Startup; 9 | 10 | public class WaitForStartupActionsBeforeServingRequestsMiddleware 11 | { 12 | private readonly IServiceProvider _serviceProvider; 13 | private readonly RequestDelegate _next; 14 | private readonly IHostApplicationLifetime _applicationLifetime; 15 | 16 | public WaitForStartupActionsBeforeServingRequestsMiddleware(IServiceProvider serviceProvider, RequestDelegate next, IHostApplicationLifetime applicationLifetime) 17 | { 18 | _serviceProvider = serviceProvider; 19 | _next = next; 20 | _applicationLifetime = applicationLifetime; 21 | } 22 | 23 | public async Task Invoke(HttpContext httpContext) 24 | { 25 | var startupContext = _serviceProvider.GetService(); 26 | 27 | // no startup actions registered 28 | if (startupContext == null) 29 | { 30 | await _next(httpContext).AnyContext(); 31 | return; 32 | } 33 | 34 | if (startupContext.IsStartupComplete && startupContext.Result.Success) 35 | { 36 | await _next(httpContext).AnyContext(); 37 | } 38 | else if (startupContext.IsStartupComplete && !startupContext.Result.Success) 39 | { 40 | // kill the server if the startup actions failed 41 | _applicationLifetime.StopApplication(); 42 | } 43 | else 44 | { 45 | httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; 46 | httpContext.Response.Headers["Retry-After"] = "10"; 47 | await httpContext.Response.WriteAsync("Service Unavailable").AnyContext(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Foundatio.JsonNet/Foundatio.JsonNet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Foundatio.JsonNet/JsonNetSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | 5 | namespace Foundatio.Serializer; 6 | 7 | public class JsonNetSerializer : ITextSerializer 8 | { 9 | private readonly JsonSerializer _serializer; 10 | 11 | public JsonNetSerializer(JsonSerializerSettings settings = null) 12 | { 13 | _serializer = JsonSerializer.Create(settings ?? new JsonSerializerSettings()); 14 | } 15 | 16 | public void Serialize(object data, Stream outputStream) 17 | { 18 | var writer = new JsonTextWriter(new StreamWriter(outputStream)); 19 | _serializer.Serialize(writer, data, data.GetType()); 20 | writer.Flush(); 21 | } 22 | 23 | public object Deserialize(Stream inputStream, Type objectType) 24 | { 25 | using var sr = new StreamReader(inputStream); 26 | using var reader = new JsonTextReader(sr); 27 | return _serializer.Deserialize(reader, objectType); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Foundatio.MessagePack/Foundatio.MessagePack.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Foundatio.MessagePack/MessagePackSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using MessagePack; 4 | using MessagePack.Resolvers; 5 | 6 | namespace Foundatio.Serializer; 7 | 8 | public class MessagePackSerializer : ISerializer 9 | { 10 | private readonly MessagePackSerializerOptions _options; 11 | 12 | public MessagePackSerializer(MessagePackSerializerOptions options = null) 13 | { 14 | _options = options ?? MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance); 15 | } 16 | 17 | public void Serialize(object data, Stream output) 18 | { 19 | MessagePack.MessagePackSerializer.Serialize(data.GetType(), output, data, _options); 20 | } 21 | 22 | public object Deserialize(Stream input, Type objectType) 23 | { 24 | return MessagePack.MessagePackSerializer.Deserialize(objectType, input, _options); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Foundatio.AsyncEx; 5 | using Foundatio.Utility; 6 | 7 | namespace Foundatio.Tests.Extensions; 8 | 9 | public static class TaskExtensions 10 | { 11 | [DebuggerStepThrough] 12 | public static async Task WaitAsync(this AsyncManualResetEvent resetEvent, TimeSpan timeout) 13 | { 14 | using var timeoutCancellationTokenSource = timeout.ToCancellationTokenSource(); 15 | await resetEvent.WaitAsync(timeoutCancellationTokenSource.Token).AnyContext(); 16 | } 17 | 18 | [DebuggerStepThrough] 19 | public static async Task WaitAsync(this AsyncAutoResetEvent resetEvent, TimeSpan timeout) 20 | { 21 | using var timeoutCancellationTokenSource = timeout.ToCancellationTokenSource(); 22 | await resetEvent.WaitAsync(timeoutCancellationTokenSource.Token).AnyContext(); 23 | } 24 | 25 | public static Task WaitAsync(this AsyncCountdownEvent countdownEvent, TimeSpan timeout) 26 | { 27 | return Task.WhenAny(countdownEvent.WaitAsync(), Task.Delay(timeout)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Foundatio.TestHarness.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | true 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("AsyncUsage", "AsyncFixer02:Long running or blocking operations under an async method", Justification = "", Scope = "member", Target = "~M:Foundatio.Tests.Storage.FileStorageTestsBase.CanSaveFilesAsync~System.Threading.Tasks.Task")] 8 | 9 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Jobs; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Foundatio.Tests.Jobs; 8 | 9 | public class HelloWorldJob : JobBase 10 | { 11 | private readonly string _id; 12 | 13 | public HelloWorldJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) 14 | { 15 | _id = Guid.NewGuid().ToString("N").Substring(0, 10); 16 | } 17 | 18 | public static int GlobalRunCount; 19 | public int RunCount { get; set; } 20 | 21 | protected override Task RunInternalAsync(JobContext context) 22 | { 23 | RunCount++; 24 | Interlocked.Increment(ref GlobalRunCount); 25 | 26 | _logger.LogTrace("HelloWorld Running: instance={Id} runs={RunCount} global={GlobalRunCount}", _id, RunCount, GlobalRunCount); 27 | 28 | return Task.FromResult(JobResult.Success); 29 | } 30 | } 31 | 32 | public class FailingJob : JobBase 33 | { 34 | private readonly string _id; 35 | 36 | public int RunCount { get; set; } 37 | 38 | public FailingJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) 39 | { 40 | _id = Guid.NewGuid().ToString("N").Substring(0, 10); 41 | } 42 | 43 | protected override Task RunInternalAsync(JobContext context) 44 | { 45 | RunCount++; 46 | 47 | _logger.LogTrace("FailingJob Running: instance={Id} runs={RunCount}", _id, RunCount); 48 | 49 | return Task.FromResult(JobResult.FailedWithMessage("Test failure")); 50 | } 51 | } 52 | 53 | public class LongRunningJob : JobBase 54 | { 55 | private readonly string _id; 56 | private int _iterationCount; 57 | 58 | public LongRunningJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) 59 | { 60 | _id = Guid.NewGuid().ToString("N").Substring(0, 10); 61 | } 62 | 63 | public int IterationCount => _iterationCount; 64 | 65 | protected override Task RunInternalAsync(JobContext context) 66 | { 67 | do 68 | { 69 | Interlocked.Increment(ref _iterationCount); 70 | if (context.CancellationToken.IsCancellationRequested) 71 | break; 72 | 73 | if (_iterationCount % 10000 == 0) 74 | _logger.LogTrace("LongRunningJob Running: instance={Id} iterations={IterationCount}", _id, IterationCount); 75 | } while (true); 76 | 77 | return Task.FromResult(JobResult.Success); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Jobs/ThrottledJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Caching; 5 | using Foundatio.Jobs; 6 | using Foundatio.Lock; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Foundatio.Tests.Jobs; 10 | 11 | public class ThrottledJob : JobWithLockBase 12 | { 13 | public ThrottledJob(ICacheClient client, ILoggerFactory loggerFactory = null) : base(loggerFactory) 14 | { 15 | _locker = new ThrottlingLockProvider(client, 1, TimeSpan.FromMilliseconds(100), null, loggerFactory); 16 | } 17 | 18 | private readonly ILockProvider _locker; 19 | public int RunCount { get; set; } 20 | 21 | protected override Task GetLockAsync(CancellationToken cancellationToken = default) 22 | { 23 | return _locker.AcquireAsync(nameof(ThrottledJob), acquireTimeout: TimeSpan.Zero); 24 | } 25 | 26 | protected override Task RunInternalAsync(JobContext context) 27 | { 28 | RunCount++; 29 | _logger.LogDebug("Incremented Run Count: {RunCount}", RunCount); 30 | return Task.FromResult(JobResult.Success); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Foundatio.Jobs; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Foundatio.Tests.Jobs; 6 | 7 | public class WithDependencyJob : JobBase 8 | { 9 | public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = null) : base(null, loggerFactory) 10 | { 11 | Dependency = dependency; 12 | } 13 | 14 | public MyDependency Dependency { get; private set; } 15 | 16 | public int RunCount { get; set; } 17 | 18 | protected override Task RunInternalAsync(JobContext context) 19 | { 20 | RunCount++; 21 | 22 | return Task.FromResult(JobResult.Success); 23 | } 24 | } 25 | 26 | public class MyDependency 27 | { 28 | public int MyProperty { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Jobs/WithLockingJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Caching; 5 | using Foundatio.Jobs; 6 | using Foundatio.Lock; 7 | using Foundatio.Messaging; 8 | using Microsoft.Extensions.Logging; 9 | using Xunit; 10 | 11 | namespace Foundatio.Tests.Jobs; 12 | 13 | public class WithLockingJob : JobWithLockBase 14 | { 15 | private readonly ILockProvider _locker; 16 | 17 | public WithLockingJob(ILoggerFactory loggerFactory) : base(loggerFactory) 18 | { 19 | _locker = new CacheLockProvider(new InMemoryCacheClient(o => o.LoggerFactory(loggerFactory)), new InMemoryMessageBus(o => o.LoggerFactory(loggerFactory)), null, loggerFactory); 20 | } 21 | 22 | public int RunCount { get; set; } 23 | 24 | protected override Task GetLockAsync(CancellationToken cancellationToken = default(CancellationToken)) 25 | { 26 | return _locker.AcquireAsync(nameof(WithLockingJob), TimeSpan.FromSeconds(1), TimeSpan.Zero); 27 | } 28 | 29 | protected override async Task RunInternalAsync(JobContext context) 30 | { 31 | RunCount++; 32 | 33 | await Task.Delay(150, context.CancellationToken); 34 | Assert.True(await _locker.IsLockedAsync("WithLockingJob")); 35 | 36 | return JobResult.Success; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Messaging/Samples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Foundatio.Tests.Messaging; 3 | using Foundatio.Utility; 4 | 5 | namespace Foundatio.Tests.Messaging 6 | { 7 | public class SimpleMessageA : ISimpleMessage 8 | { 9 | public SimpleMessageA() 10 | { 11 | Items = new DataDictionary(); 12 | } 13 | public string Data { get; set; } 14 | public int Count { get; set; } 15 | 16 | public IDictionary Items { get; set; } 17 | } 18 | 19 | public class DerivedSimpleMessageA : SimpleMessageA { } 20 | public class Derived2SimpleMessageA : SimpleMessageA { } 21 | public class Derived3SimpleMessageA : SimpleMessageA { } 22 | public class Derived4SimpleMessageA : SimpleMessageA { } 23 | public class Derived5SimpleMessageA : SimpleMessageA { } 24 | public class Derived6SimpleMessageA : SimpleMessageA { } 25 | public class Derived7SimpleMessageA : SimpleMessageA { } 26 | public class Derived8SimpleMessageA : SimpleMessageA { } 27 | public class Derived9SimpleMessageA : SimpleMessageA { } 28 | public class Derived10SimpleMessageA : SimpleMessageA { } 29 | public class NeverPublishedMessage { } 30 | 31 | public class SimpleMessageB : ISimpleMessage 32 | { 33 | public string Data { get; set; } 34 | } 35 | 36 | public class SimpleMessageC 37 | { 38 | public string Data { get; set; } 39 | } 40 | 41 | public interface ISimpleMessage 42 | { 43 | string Data { get; set; } 44 | } 45 | } 46 | 47 | namespace Foundatio.Tests.MessagingAlt 48 | { 49 | public class SimpleMessageA : ISimpleMessage 50 | { 51 | public SimpleMessageA() 52 | { 53 | Items = new DataDictionary(); 54 | } 55 | public string Data { get; set; } 56 | public int Count { get; set; } 57 | 58 | public IDictionary Items { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Queue/Samples.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Metrics; 2 | using Foundatio.Queues; 3 | 4 | namespace Foundatio.Tests.Queue; 5 | 6 | public class SimpleWorkItem : IHaveSubMetricName, IHaveUniqueIdentifier 7 | { 8 | public string Data { get; set; } 9 | public int Id { get; set; } 10 | public string UniqueIdentifier { get; set; } 11 | public string SubMetricName { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Utility/BenchmarkToJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using BenchmarkDotNet.Exporters.Json; 4 | using BenchmarkDotNet.Loggers; 5 | using BenchmarkDotNet.Reports; 6 | 7 | namespace Foundatio.TestHarness.Utility; 8 | 9 | public class StringBenchmarkLogger : ILogger 10 | { 11 | private readonly StringBuilder _buffer = new(); 12 | 13 | public string Id => Guid.NewGuid().ToString(); 14 | public int Priority => 1; 15 | 16 | public void Write(LogKind logKind, string text) 17 | { 18 | _buffer.Append(text); 19 | } 20 | 21 | public void WriteLine() 22 | { 23 | _buffer.AppendLine(); 24 | } 25 | 26 | public void WriteLine(LogKind logKind, string text) 27 | { 28 | _buffer.AppendLine(text); 29 | } 30 | 31 | public override string ToString() 32 | { 33 | return _buffer.ToString(); 34 | } 35 | 36 | public void Flush() { } 37 | } 38 | 39 | public static class BenchmarkSummaryExtensions 40 | { 41 | public static string ToJson(this Summary summary, bool indentJson = true) 42 | { 43 | var exporter = new JsonExporter(indentJson: indentJson); 44 | var logger = new StringBenchmarkLogger(); 45 | exporter.ExportToLog(summary, logger); 46 | return logger.ToString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Utility/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Foundatio.Tests.Utility; 6 | 7 | public static class Configuration 8 | { 9 | private static readonly IConfiguration _configuration; 10 | static Configuration() 11 | { 12 | _configuration = new ConfigurationBuilder() 13 | .SetBasePath(GetBasePath()) 14 | .AddJsonFile("appsettings.json", true, true) 15 | .AddEnvironmentVariables() 16 | .Build(); 17 | } 18 | 19 | public static IConfigurationSection GetSection(string name) 20 | { 21 | return _configuration.GetSection(name); 22 | } 23 | 24 | public static string GetConnectionString(string name) 25 | { 26 | return _configuration.GetConnectionString(name); 27 | } 28 | 29 | private static string GetBasePath() 30 | { 31 | string basePath = Path.GetDirectoryName(typeof(Configuration).GetTypeInfo().Assembly.Location); 32 | 33 | for (int i = 0; i < 5; i++) 34 | { 35 | if (File.Exists(Path.Combine(basePath, "appsettings.json"))) 36 | return Path.GetFullPath(basePath); 37 | 38 | basePath += "..\\"; 39 | } 40 | 41 | return Path.GetFullPath("."); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Foundatio.TestHarness/Utility/NonSeekableStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Foundatio.Tests.Utility; 5 | 6 | public class NonSeekableStream : Stream 7 | { 8 | private readonly Stream _stream; 9 | 10 | public NonSeekableStream(Stream stream) 11 | { 12 | _stream = stream; 13 | } 14 | 15 | public override bool CanRead => _stream.CanRead; 16 | 17 | public override bool CanSeek => false; 18 | 19 | public override bool CanWrite => _stream.CanWrite; 20 | 21 | public override void Flush() 22 | { 23 | _stream.Flush(); 24 | } 25 | 26 | public override long Length => throw new NotSupportedException(); 27 | 28 | public override long Position 29 | { 30 | get => _stream.Position; 31 | set => throw new NotSupportedException(); 32 | } 33 | 34 | public override int Read(byte[] buffer, int offset, int count) 35 | { 36 | return _stream.Read(buffer, offset, count); 37 | } 38 | 39 | public override long Seek(long offset, SeekOrigin origin) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public override void SetLength(long value) 45 | { 46 | throw new NotSupportedException(); 47 | } 48 | 49 | public override void Write(byte[] buffer, int offset, int count) 50 | { 51 | _stream.Write(buffer, offset, count); 52 | } 53 | 54 | public override void Close() 55 | { 56 | _stream.Close(); 57 | base.Close(); 58 | } 59 | 60 | protected override void Dispose(bool disposing) 61 | { 62 | _stream.Dispose(); 63 | base.Dispose(disposing); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Foundatio.Utf8Json/Foundatio.Utf8Json.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(PackageTags);Utf8Json;Serializer 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Foundatio.Utf8Json/Utf8JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Utf8Json; 4 | using Utf8Json.Resolvers; 5 | 6 | namespace Foundatio.Serializer; 7 | 8 | public class Utf8JsonSerializer : ITextSerializer 9 | { 10 | private readonly IJsonFormatterResolver _formatterResolver; 11 | 12 | public Utf8JsonSerializer(IJsonFormatterResolver resolver = null) 13 | { 14 | _formatterResolver = resolver ?? StandardResolver.Default; 15 | } 16 | 17 | public void Serialize(object data, Stream output) 18 | { 19 | JsonSerializer.NonGeneric.Serialize(data.GetType(), output, data, _formatterResolver); 20 | } 21 | 22 | public object Deserialize(Stream input, Type objectType) 23 | { 24 | return JsonSerializer.NonGeneric.Deserialize(objectType, input, _formatterResolver); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Foundatio.Xunit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(PackageTags);Logging;Log;Xunit;Retry 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/LogEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Foundatio.Xunit; 6 | 7 | public class LogEntry 8 | { 9 | public DateTimeOffset Date { get; set; } 10 | public string CategoryName { get; set; } 11 | public LogLevel LogLevel { get; set; } 12 | public object[] Scopes { get; set; } 13 | public EventId EventId { get; set; } 14 | public object State { get; set; } 15 | public Exception Exception { get; set; } 16 | public Func Formatter { get; set; } 17 | public IDictionary Properties { get; set; } = new Dictionary(); 18 | 19 | public string Message => Formatter(State, Exception); 20 | 21 | public override string ToString() 22 | { 23 | return String.Concat("", Date.ToString("mm:ss.fff"), " ", LogLevel.ToString().Substring(0, 1).ToUpper(), ": ", Message, " - ", CategoryName); 24 | } 25 | 26 | public string ToString(bool useFullCategory) 27 | { 28 | string category = CategoryName; 29 | if (!useFullCategory) 30 | { 31 | int lastDot = category.LastIndexOf('.'); 32 | if (lastDot >= 0) 33 | category = category.Substring(lastDot + 1); 34 | } 35 | 36 | return String.Concat("", Date.ToString("mm:ss.fff"), " ", LogLevel.ToString().Substring(0, 1).ToUpper(), ": ", Message, " - ", category); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/TestLoggerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | namespace Foundatio.Xunit; 9 | 10 | public abstract class TestLoggerBase : IClassFixture, IAsyncLifetime 11 | { 12 | protected TestLoggerBase(ITestOutputHelper output, TestLoggerFixture fixture) 13 | { 14 | Fixture = fixture; 15 | fixture.Output = output; 16 | fixture.ConfigureServices(ConfigureServices); 17 | } 18 | 19 | protected TestLoggerFixture Fixture { get; } 20 | protected IServiceProvider Services => Fixture.Services; 21 | protected TestLogger TestLogger => Fixture.TestLogger; 22 | protected ILogger Log => Fixture.Log; 23 | 24 | protected virtual void ConfigureServices(IServiceCollection services) 25 | { 26 | } 27 | 28 | public virtual Task InitializeAsync() 29 | { 30 | return Task.CompletedTask; 31 | } 32 | 33 | public virtual Task DisposeAsync() 34 | { 35 | Fixture.TestLogger.Reset(); 36 | return Task.CompletedTask; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/TestLoggerFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace Foundatio.Xunit; 10 | 11 | public class TestLoggerFixture : IAsyncLifetime 12 | { 13 | private readonly List _disposables = []; 14 | private readonly List> _serviceRegistrations = []; 15 | private readonly Lazy _serviceProvider; 16 | private readonly Lazy _testLogger; 17 | private readonly Lazy _log; 18 | 19 | public TestLoggerFixture() 20 | { 21 | _serviceProvider = new Lazy(BuildServiceProvider); 22 | _testLogger = new Lazy(() => Services.GetTestLogger()); 23 | _log = new Lazy(() => Services.GetRequiredService().CreateLogger(GetType())); 24 | } 25 | 26 | public ITestOutputHelper Output { get; set; } 27 | 28 | public void ConfigureServices(Action registerServices) 29 | { 30 | _serviceRegistrations.Add(registerServices); 31 | } 32 | 33 | public IServiceProvider Services => _serviceProvider.Value; 34 | public TestLogger TestLogger => _testLogger.Value; 35 | public ILogger Log => _log.Value; 36 | 37 | protected virtual void ConfigureServices(IServiceCollection services) 38 | { 39 | services.AddLogging(c => c.AddTestLogger(() => Output)); 40 | foreach (var registration in _serviceRegistrations) 41 | registration(services); 42 | } 43 | 44 | protected virtual IServiceProvider BuildServiceProvider() 45 | { 46 | var services = new ServiceCollection(); 47 | ConfigureServices(services); 48 | var sp = services.BuildServiceProvider(); 49 | _disposables.Add(sp); 50 | return sp; 51 | } 52 | 53 | public virtual Task InitializeAsync() 54 | { 55 | return Task.CompletedTask; 56 | } 57 | 58 | public virtual async Task DisposeAsync() 59 | { 60 | foreach (object disposable in _disposables) 61 | { 62 | try 63 | { 64 | switch (disposable) 65 | { 66 | case IAsyncDisposable asyncDisposable: 67 | await asyncDisposable.DisposeAsync(); 68 | break; 69 | case IDisposable syncDisposable: 70 | syncDisposable.Dispose(); 71 | break; 72 | } 73 | } 74 | catch (ObjectDisposedException) { } 75 | catch (Exception ex) 76 | { 77 | Log?.LogError(ex, "Error disposing resource."); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/TestLoggerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit.Abstractions; 4 | 5 | namespace Foundatio.Xunit; 6 | 7 | public class TestLoggerOptions 8 | { 9 | public LogLevel DefaultMinimumLevel { get; set; } = LogLevel.Information; 10 | public int MaxLogEntriesToStore { get; set; } = 100; 11 | public int MaxLogEntriesToWrite { get; set; } = 1000; 12 | public bool IncludeScopes { get; set; } = true; 13 | public TimeProvider TimeProvider { get; set; } = TimeProvider.System; 14 | 15 | public void UseOutputHelper(Func getOutputHelper, Func formatLogEntry = null) 16 | { 17 | formatLogEntry ??= logEntry => logEntry.ToString(false); 18 | WriteLogEntryFunc = logEntry => 19 | { 20 | getOutputHelper?.Invoke()?.WriteLine(formatLogEntry(logEntry)); 21 | }; 22 | } 23 | 24 | public Action WriteLogEntryFunc { get; set; } 25 | internal void WriteLogEntry(LogEntry logEntry) => WriteLogEntryFunc?.Invoke(logEntry); 26 | 27 | public Func NowFunc { get; set; } 28 | internal DateTimeOffset GetNow() => NowFunc?.Invoke() ?? TimeProvider.GetUtcNow(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/TestLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Foundatio.Xunit; 5 | 6 | [ProviderAlias("Test")] 7 | public class TestLoggerProvider : ILoggerProvider 8 | { 9 | public TestLoggerProvider(TestLoggerOptions options) 10 | { 11 | Log = new TestLogger(options); 12 | } 13 | 14 | public TestLogger Log { get; } 15 | 16 | public virtual ILogger CreateLogger(string categoryName) 17 | { 18 | return Log.CreateLogger(categoryName); 19 | } 20 | 21 | public void Dispose() 22 | { 23 | Dispose(true); 24 | GC.SuppressFinalize(this); 25 | } 26 | 27 | protected virtual void Dispose(bool disposing) 28 | { 29 | } 30 | 31 | ~TestLoggerProvider() 32 | { 33 | Dispose(false); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Logging/TestWithLoggingBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Xunit.Abstractions; 3 | 4 | namespace Foundatio.Xunit; 5 | 6 | public abstract class TestWithLoggingBase 7 | { 8 | protected readonly ILogger _logger; 9 | 10 | protected TestWithLoggingBase(ITestOutputHelper output) 11 | { 12 | Log = new TestLogger(output); 13 | _logger = Log.CreateLogger(GetType()); 14 | } 15 | 16 | protected TestLogger Log { get; } 17 | } 18 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/DelayedMessageBus.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit.Abstractions; 3 | using Xunit.Sdk; 4 | 5 | namespace Foundatio.Xunit; 6 | 7 | /// 8 | /// Used to capture messages to potentially be forwarded later. Messages are forwarded by 9 | /// disposing of the message bus. 10 | /// 11 | public class DelayedMessageBus : IMessageBus 12 | { 13 | private readonly IMessageBus innerBus; 14 | private readonly List messages = new(); 15 | 16 | public DelayedMessageBus(IMessageBus innerBus) 17 | { 18 | this.innerBus = innerBus; 19 | } 20 | 21 | public bool QueueMessage(IMessageSinkMessage message) 22 | { 23 | lock (messages) 24 | messages.Add(message); 25 | 26 | // No way to ask the inner bus if they want to cancel without sending them the message, so 27 | // we just go ahead and continue always. 28 | return true; 29 | } 30 | 31 | public void Dispose() 32 | { 33 | foreach (var message in messages) 34 | innerBus.QueueMessage(message); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/RetryAttribute.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Xunit.Sdk; 3 | 4 | namespace Foundatio.Xunit; 5 | 6 | /// 7 | /// Works just like [Fact] except that failures are retried (by default, 3 times). 8 | /// 9 | [XunitTestCaseDiscoverer("Foundatio.Xunit.RetryFactDiscoverer", "Foundatio.TestHarness")] 10 | public class RetryFactAttribute : FactAttribute 11 | { 12 | public RetryFactAttribute(int maxRetries = 3) 13 | { 14 | MaxRetries = maxRetries; 15 | } 16 | 17 | /// 18 | /// Number of retries allowed for a failed test. If unset (or set less than 1), will 19 | /// default to 3 attempts. 20 | /// 21 | public int MaxRetries { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/RetryFactDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit.Abstractions; 3 | using Xunit.Sdk; 4 | 5 | namespace Foundatio.Xunit; 6 | 7 | public class RetryFactDiscoverer : IXunitTestCaseDiscoverer 8 | { 9 | readonly IMessageSink diagnosticMessageSink; 10 | 11 | public RetryFactDiscoverer(IMessageSink diagnosticMessageSink) 12 | { 13 | this.diagnosticMessageSink = diagnosticMessageSink; 14 | } 15 | 16 | public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) 17 | { 18 | var maxRetries = factAttribute.GetNamedArgument("MaxRetries"); 19 | if (maxRetries < 1) 20 | maxRetries = 3; 21 | 22 | yield return new RetryTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, maxRetries); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/RetryTestCase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Xunit.Abstractions; 6 | using Xunit.Sdk; 7 | 8 | namespace Foundatio.Xunit; 9 | 10 | [Serializable] 11 | public class RetryTestCase : XunitTestCase 12 | { 13 | private int maxRetries; 14 | 15 | [EditorBrowsable(EditorBrowsableState.Never)] 16 | [Obsolete("Called by the de-serializer", true)] 17 | public RetryTestCase() { } 18 | 19 | public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, TestMethodDisplayOptions testMethodDisplayOptions, ITestMethod testMethod, int maxRetries) 20 | : base(diagnosticMessageSink, testMethodDisplay, testMethodDisplayOptions, testMethod, testMethodArguments: null) 21 | { 22 | this.maxRetries = maxRetries; 23 | } 24 | 25 | // This method is called by the xUnit test framework classes to run the test case. We will do the 26 | // loop here, forwarding on to the implementation in XunitTestCase to do the heavy lifting. We will 27 | // continue to re-run the test until the aggregator has an error (meaning that some internal error 28 | // condition happened), or the test runs without failure, or we've hit the maximum number of tries. 29 | public override async Task RunAsync(IMessageSink diagnosticMessageSink, 30 | IMessageBus messageBus, 31 | object[] constructorArguments, 32 | ExceptionAggregator aggregator, 33 | CancellationTokenSource cancellationTokenSource) 34 | { 35 | var runCount = 0; 36 | 37 | while (true) 38 | { 39 | // This is really the only tricky bit: we need to capture and delay messages (since those will 40 | // contain run status) until we know we've decided to accept the final result; 41 | var delayedMessageBus = new DelayedMessageBus(messageBus); 42 | 43 | var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource); 44 | if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) 45 | { 46 | delayedMessageBus.Dispose(); // Sends all the delayed messages 47 | return summary; 48 | } 49 | 50 | diagnosticMessageSink.OnMessage(new DiagnosticMessage("Execution of '{0}' failed (attempt #{1}), retrying...", DisplayName, runCount)); 51 | } 52 | } 53 | 54 | public override void Serialize(IXunitSerializationInfo data) 55 | { 56 | base.Serialize(data); 57 | 58 | data.AddValue("MaxRetries", maxRetries); 59 | } 60 | 61 | public override void Deserialize(IXunitSerializationInfo data) 62 | { 63 | base.Deserialize(data); 64 | 65 | maxRetries = data.GetValue("MaxRetries"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/RetryTheoryAttribute.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Xunit.Sdk; 3 | 4 | namespace Foundatio.Xunit; 5 | 6 | /// 7 | /// Works just like [Fact] except that failures are retried (by default, 3 times). 8 | /// 9 | [XunitTestCaseDiscoverer("Foundatio.Xunit.RetryTheoryDiscoverer", "Foundatio.TestHarness")] 10 | public class RetryTheoryAttribute : TheoryAttribute 11 | { 12 | public RetryTheoryAttribute(int maxRetries = 3) 13 | { 14 | MaxRetries = maxRetries; 15 | } 16 | 17 | /// 18 | /// Number of retries allowed for a failed test. If unset (or set less than 1), will 19 | /// default to 3 attempts. 20 | /// 21 | public int MaxRetries { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /src/Foundatio.Xunit/Retry/RetryTheoryDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit.Abstractions; 3 | using Xunit.Sdk; 4 | 5 | namespace Foundatio.Xunit; 6 | 7 | public class RetryTheoryDiscoverer : IXunitTestCaseDiscoverer 8 | { 9 | readonly IMessageSink diagnosticMessageSink; 10 | 11 | public RetryTheoryDiscoverer(IMessageSink diagnosticMessageSink) 12 | { 13 | this.diagnosticMessageSink = diagnosticMessageSink; 14 | } 15 | 16 | public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) 17 | { 18 | var maxRetries = factAttribute.GetNamedArgument("MaxRetries"); 19 | if (maxRetries < 1) 20 | maxRetries = 3; 21 | 22 | yield return new RetryTheoryTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, maxRetries); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Foundatio/Caching/CacheValue.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Caching; 2 | 3 | public class CacheValue 4 | { 5 | public CacheValue(T value, bool hasValue) 6 | { 7 | Value = value; 8 | HasValue = hasValue; 9 | } 10 | 11 | public bool HasValue { get; } 12 | 13 | public bool IsNull => Value == null; 14 | 15 | public T Value { get; } 16 | 17 | public static CacheValue Null { get; } = new CacheValue(default, true); 18 | 19 | public static CacheValue NoValue { get; } = new CacheValue(default, false); 20 | 21 | public override string ToString() 22 | { 23 | return Value?.ToString() ?? ""; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Foundatio/Caching/ICacheClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Caching; 6 | 7 | public interface ICacheClient : IDisposable 8 | { 9 | Task RemoveAsync(string key); 10 | Task RemoveIfEqualAsync(string key, T expected); 11 | Task RemoveAllAsync(IEnumerable keys = null); 12 | Task RemoveByPrefixAsync(string prefix); 13 | Task> GetAsync(string key); 14 | Task>> GetAllAsync(IEnumerable keys); 15 | Task AddAsync(string key, T value, TimeSpan? expiresIn = null); 16 | Task SetAsync(string key, T value, TimeSpan? expiresIn = null); 17 | Task SetAllAsync(IDictionary values, TimeSpan? expiresIn = null); 18 | Task ReplaceAsync(string key, T value, TimeSpan? expiresIn = null); 19 | Task ReplaceIfEqualAsync(string key, T value, T expected, TimeSpan? expiresIn = null); 20 | Task IncrementAsync(string key, double amount, TimeSpan? expiresIn = null); 21 | Task IncrementAsync(string key, long amount, TimeSpan? expiresIn = null); 22 | Task ExistsAsync(string key); 23 | Task GetExpirationAsync(string key); 24 | Task SetExpirationAsync(string key, TimeSpan expiresIn); 25 | Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null); 26 | Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null); 27 | Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null); 28 | Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null); 29 | Task ListAddAsync(string key, IEnumerable values, TimeSpan? expiresIn = null); 30 | Task ListRemoveAsync(string key, IEnumerable values, TimeSpan? expiresIn = null); 31 | Task>> GetListAsync(string key, int? page = null, int pageSize = 100); 32 | } 33 | -------------------------------------------------------------------------------- /src/Foundatio/Caching/IMemoryCacheClient.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Caching; 2 | 3 | public interface IMemoryCacheClient : ICacheClient 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/Foundatio/Caching/InMemoryCacheClientOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Caching; 2 | 3 | public class InMemoryCacheClientOptions : SharedOptions 4 | { 5 | /// 6 | /// The maximum number of items to store in the cache 7 | /// 8 | public int? MaxItems { get; set; } = 10000; 9 | 10 | /// 11 | /// Whether or not values should be cloned during get and set to make sure that any cache entry changes are isolated 12 | /// 13 | public bool CloneValues { get; set; } = false; 14 | 15 | /// 16 | /// Whether or not an error when deserializing a cache value should result in an exception being thrown or if it should just return an empty cache value 17 | /// 18 | public bool ShouldThrowOnSerializationError { get; set; } = true; 19 | } 20 | 21 | public class InMemoryCacheClientOptionsBuilder : SharedOptionsBuilder 22 | { 23 | public InMemoryCacheClientOptionsBuilder MaxItems(int? maxItems) 24 | { 25 | Target.MaxItems = maxItems; 26 | return this; 27 | } 28 | 29 | public InMemoryCacheClientOptionsBuilder CloneValues(bool cloneValues) 30 | { 31 | Target.CloneValues = cloneValues; 32 | return this; 33 | } 34 | 35 | public InMemoryCacheClientOptionsBuilder ShouldThrowOnSerializationError(bool shouldThrow) 36 | { 37 | Target.ShouldThrowOnSerializationError = shouldThrow; 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Foundatio/DeepCloner/DeepClonerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security; 3 | 4 | using Foundatio.Force.DeepCloner.Helpers; 5 | 6 | namespace Foundatio.Force.DeepCloner; 7 | 8 | /// 9 | /// Extensions for object cloning 10 | /// 11 | internal static class DeepClonerExtensions 12 | { 13 | /// 14 | /// Performs deep (full) copy of object and related graph 15 | /// 16 | public static T DeepClone(this T obj) 17 | { 18 | return DeepClonerGenerator.CloneObject(obj); 19 | } 20 | 21 | /// 22 | /// Performs deep (full) copy of object and related graph to existing object 23 | /// 24 | /// existing filled object 25 | /// Method is valid only for classes, classes should be descendants in reality, not in declaration 26 | public static TTo DeepCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom 27 | { 28 | return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, true); 29 | } 30 | 31 | /// 32 | /// Performs shallow copy of object to existing object 33 | /// 34 | /// existing filled object 35 | /// Method is valid only for classes, classes should be descendants in reality, not in declaration 36 | public static TTo ShallowCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom 37 | { 38 | return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, false); 39 | } 40 | 41 | /// 42 | /// Performs shallow (only new object returned, without cloning of dependencies) copy of object 43 | /// 44 | public static T ShallowClone(this T obj) 45 | { 46 | return ShallowClonerGenerator.CloneObject(obj); 47 | } 48 | 49 | static DeepClonerExtensions() 50 | { 51 | if (!PermissionCheck()) 52 | { 53 | throw new SecurityException("DeepCloner should have enough permissions to run. Grant FullTrust or Reflection permission"); 54 | } 55 | } 56 | 57 | private static bool PermissionCheck() 58 | { 59 | // best way to check required permission: execute something and receive exception 60 | // .net security policy is weird for normal usage 61 | try 62 | { 63 | new object().ShallowClone(); 64 | } 65 | catch (VerificationException) 66 | { 67 | return false; 68 | } 69 | catch (MemberAccessException) 70 | { 71 | return false; 72 | } 73 | 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Foundatio/DeepCloner/Helpers/ShallowClonerGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Force.DeepCloner.Helpers; 4 | 5 | internal static class ShallowClonerGenerator 6 | { 7 | public static T CloneObject(T obj) 8 | { 9 | // this is faster than typeof(T).IsValueType 10 | if (obj is ValueType) 11 | { 12 | if (typeof(T) == obj.GetType()) 13 | return obj; 14 | 15 | // we're here so, we clone value type obj as object type T 16 | // so, we need to copy it, bcs we have a reference, not real object. 17 | return (T)ShallowObjectCloner.CloneObject(obj); 18 | } 19 | 20 | if (ReferenceEquals(obj, null)) 21 | return (T)(object)null; 22 | 23 | if (DeepClonerSafeTypes.CanReturnSameObject(obj.GetType())) 24 | return obj; 25 | 26 | return (T)ShallowObjectCloner.CloneObject(obj); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Foundatio/DeepCloner/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 force 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Foundatio.Utility; 6 | 7 | internal static class CollectionExtensions 8 | { 9 | public static ICollection ReduceTimeSeries(this ICollection items, Func dateSelector, Func, DateTime, T> reducer, int dataPoints) 10 | { 11 | if (items.Count <= dataPoints) 12 | return items; 13 | 14 | var minTicks = items.Min(dateSelector).Ticks; 15 | var maxTicks = items.Max(dateSelector).Ticks; 16 | 17 | var bucketSize = (maxTicks - minTicks) / dataPoints; 18 | var buckets = new List(); 19 | long currentTick = minTicks; 20 | while (currentTick < maxTicks) 21 | { 22 | buckets.Add(currentTick); 23 | currentTick += bucketSize; 24 | } 25 | 26 | buckets.Reverse(); 27 | 28 | return items.GroupBy(i => buckets.First(b => dateSelector(i).Ticks >= b)).Select(g => reducer(g.ToList(), new DateTime(g.Key))).ToList(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/ConcurrentDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | internal static class ConcurrentDictionaryExtensions 7 | { 8 | public static bool TryUpdate(this ConcurrentDictionary concurrentDictionary, TKey key, Func updateValueFactory) 9 | { 10 | if (key == null) 11 | throw new ArgumentNullException(nameof(key)); 12 | 13 | if (updateValueFactory == null) 14 | throw new ArgumentNullException(nameof(updateValueFactory)); 15 | 16 | TValue comparisonValue; 17 | TValue newValue; 18 | do 19 | { 20 | if (!concurrentDictionary.TryGetValue(key, out comparisonValue)) 21 | return false; 22 | 23 | newValue = updateValueFactory(key, comparisonValue); 24 | } while (!concurrentDictionary.TryUpdate(key, newValue, comparisonValue)); 25 | 26 | return true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/ConcurrentQueueExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | internal static class ConcurrentQueueExtensions 6 | { 7 | public static void Clear(this ConcurrentQueue queue) 8 | { 9 | while (queue.TryDequeue(out var _)) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | internal static class DateTimeExtensions 6 | { 7 | public static DateTime GetUtcNowDateTime(this TimeProvider timeProvider, bool includeMilliseconds = true) 8 | { 9 | var now = timeProvider.GetUtcNow(); 10 | return includeMilliseconds ? now.UtcDateTime : now.UtcDateTime.AddTicks(-(now.UtcDateTime.Ticks % TimeSpan.TicksPerSecond)); 11 | } 12 | 13 | public static DateTime Floor(this DateTime date, TimeSpan interval) 14 | { 15 | return date.AddTicks(-(date.Ticks % interval.Ticks)); 16 | } 17 | 18 | public static DateTime Ceiling(this DateTime date, TimeSpan interval) 19 | { 20 | return date.AddTicks(interval.Ticks - (date.Ticks % interval.Ticks)); 21 | } 22 | 23 | public static long ToUnixTimeMilliseconds(this DateTime date) 24 | { 25 | return new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeMilliseconds(); 26 | } 27 | 28 | public static DateTime FromUnixTimeMilliseconds(this long timestamp) 29 | { 30 | return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).UtcDateTime; 31 | } 32 | 33 | public static long ToUnixTimeSeconds(this DateTime date) 34 | { 35 | return new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeSeconds(); 36 | } 37 | 38 | public static DateTimeOffset FromUnixTimeSeconds(this long timestamp) 39 | { 40 | return DateTimeOffset.FromUnixTimeSeconds(timestamp); 41 | } 42 | 43 | public static DateTime SafeAdd(this DateTime date, TimeSpan value) 44 | { 45 | if (date.Ticks + value.Ticks < DateTime.MinValue.Ticks) 46 | return DateTime.MinValue; 47 | 48 | if (date.Ticks + value.Ticks > DateTime.MaxValue.Ticks) 49 | return DateTime.MaxValue; 50 | 51 | return date.Add(value); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Foundatio.Utility; 7 | 8 | namespace Foundatio.Extensions; 9 | 10 | internal static class DictionaryExtensions 11 | { 12 | /// 13 | /// Streams a dictionary to in fixed‑size batches 14 | /// with a single rented buffer (constant memory). 15 | /// 16 | /// Key type. 17 | /// Value type. 18 | /// The dictionary to process. 19 | /// Number of items per batch (≥ 1). 20 | /// 21 | /// Async callback that consumes a slice 22 | /// representing one full or partial batch of . 23 | /// 24 | /// Total item count streamed. 25 | public static async Task BatchAsync( 26 | this IReadOnlyDictionary source, 27 | int batchSize, 28 | Func>, ValueTask> handler) 29 | { 30 | if (source is null) throw new ArgumentNullException(nameof(source)); 31 | if (handler is null) throw new ArgumentNullException(nameof(handler)); 32 | if (batchSize <= 0) throw new ArgumentOutOfRangeException(nameof(batchSize)); 33 | 34 | // Fast path for very small dictionaries 35 | if (source.Count == 0) 36 | return 0; 37 | 38 | if (batchSize >= source.Count) 39 | { 40 | var oneShot = source.ToArray(); 41 | await handler(oneShot.AsMemory()).AnyContext(); 42 | return oneShot.Length; 43 | } 44 | 45 | var buffer = ArrayPool>.Shared.Rent(batchSize); 46 | int filled = 0, total = 0; 47 | 48 | try 49 | { 50 | foreach (var kvp in source) 51 | { 52 | buffer[filled++] = kvp; 53 | 54 | if (filled == batchSize) 55 | { 56 | await handler(buffer.AsMemory(0, filled)).AnyContext(); 57 | total += filled; 58 | filled = 0; 59 | } 60 | } 61 | 62 | if (filled > 0) 63 | { 64 | await handler(buffer.AsMemory(0, filled)).AnyContext(); 65 | total += filled; 66 | } 67 | } 68 | finally 69 | { 70 | ArrayPool>.Shared.Return(buffer, clearArray: true); 71 | } 72 | 73 | return total; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | internal static class EnumExtensions 7 | { 8 | /// 9 | /// Will try and parse an enum and it's default type. 10 | /// 11 | /// 12 | /// 13 | /// True if the enum value is defined. 14 | public static bool TryEnumIsDefined(Type type, object value) 15 | { 16 | if (type == null || value == null || !type.GetTypeInfo().IsEnum) 17 | return false; 18 | 19 | // Return true if the value is an enum and is a matching type. 20 | if (type == value.GetType()) 21 | return true; 22 | 23 | if (TryEnumIsDefined(type, value)) 24 | return true; 25 | if (TryEnumIsDefined(type, value)) 26 | return true; 27 | if (TryEnumIsDefined(type, value)) 28 | return true; 29 | if (TryEnumIsDefined(type, value)) 30 | return true; 31 | if (TryEnumIsDefined(type, value)) 32 | return true; 33 | if (TryEnumIsDefined(type, value)) 34 | return true; 35 | if (TryEnumIsDefined(type, value)) 36 | return true; 37 | if (TryEnumIsDefined(type, value)) 38 | return true; 39 | if (TryEnumIsDefined(type, value)) 40 | return true; 41 | 42 | return false; 43 | } 44 | 45 | public static bool TryEnumIsDefined(Type type, object value) 46 | { 47 | // Catch any casting errors that can occur or if 0 is not defined as a default value. 48 | try 49 | { 50 | if (value is T && Enum.IsDefined(type, (T)value)) 51 | return true; 52 | } 53 | catch (Exception) { } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | internal static class EnumerableExtensions 7 | { 8 | public static void ForEach(this IEnumerable collection, Action action) 9 | { 10 | if (collection == null || action == null) 11 | return; 12 | 13 | foreach (var item in collection) 14 | action(item); 15 | } 16 | 17 | public static void AddRange(this ICollection list, IEnumerable range) 18 | { 19 | if (list == null || range == null) 20 | return; 21 | 22 | foreach (var r in range) 23 | list.Add(r); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | internal static class ExceptionExtensions 7 | { 8 | public static Exception GetInnermostException(this Exception exception) 9 | { 10 | if (exception == null) 11 | return null; 12 | 13 | Exception current = exception; 14 | while (current.InnerException != null) 15 | current = current.InnerException; 16 | 17 | return current; 18 | } 19 | 20 | public static string GetMessage(this Exception exception) 21 | { 22 | if (exception == null) 23 | return String.Empty; 24 | 25 | if (exception is AggregateException aggregateException) 26 | return String.Join(Environment.NewLine, aggregateException.Flatten().InnerExceptions.Where(ex => !String.IsNullOrEmpty(ex.GetInnermostException().Message)).Select(ex => ex.GetInnermostException().Message)); 27 | 28 | return exception.GetInnermostException().Message; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/NumberExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | internal static class NumericExtensions 6 | { 7 | public static string ToFileSizeDisplay(this int i) 8 | { 9 | return ToFileSizeDisplay((long)i, 2); 10 | } 11 | 12 | public static string ToFileSizeDisplay(this int i, int decimals) 13 | { 14 | return ToFileSizeDisplay((long)i, decimals); 15 | } 16 | 17 | public static string ToFileSizeDisplay(this long i) 18 | { 19 | return ToFileSizeDisplay(i, 2); 20 | } 21 | 22 | public static string ToFileSizeDisplay(this long i, int decimals) 23 | { 24 | if (i < 1024 * 1024 * 1024) // 1 GB 25 | { 26 | string value = Math.Round((decimal)i / 1024m / 1024m, decimals).ToString("N" + decimals); 27 | if (decimals > 0 && value.EndsWith(new string('0', decimals))) 28 | value = value.Substring(0, value.Length - decimals - 1); 29 | 30 | return String.Concat(value, " MB"); 31 | } 32 | else 33 | { 34 | string value = Math.Round((decimal)i / 1024m / 1024m / 1024m, decimals).ToString("N" + decimals); 35 | if (decimals > 0 && value.EndsWith(new string('0', decimals))) 36 | value = value.Substring(0, value.Length - decimals - 1); 37 | 38 | return String.Concat(value, " GB"); 39 | } 40 | } 41 | 42 | public static string ToOrdinal(this int num) 43 | { 44 | switch (num % 100) 45 | { 46 | case 11: 47 | case 12: 48 | case 13: 49 | return num.ToString("#,###0") + "th"; 50 | } 51 | 52 | switch (num % 10) 53 | { 54 | case 1: 55 | return num.ToString("#,###0") + "st"; 56 | case 2: 57 | return num.ToString("#,###0") + "nd"; 58 | case 3: 59 | return num.ToString("#,###0") + "rd"; 60 | default: 61 | return num.ToString("#,###0") + "th"; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Force.DeepCloner.Helpers; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | public static class ObjectExtensions 6 | { 7 | public static T DeepClone(this T original) 8 | { 9 | return DeepClonerGenerator.CloneObject(original); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace Foundatio.Extensions; 6 | 7 | internal static class StringExtensions 8 | { 9 | public static string NormalizePath(this string path) 10 | { 11 | if (String.IsNullOrEmpty(path)) 12 | return path; 13 | 14 | if (Path.DirectorySeparatorChar == '\\') 15 | path = path.Replace('/', Path.DirectorySeparatorChar); 16 | else if (Path.DirectorySeparatorChar == '/') 17 | path = path.Replace('\\', Path.DirectorySeparatorChar); 18 | 19 | return path; 20 | } 21 | 22 | public static string ToSpacedWords(this string text, bool preserveAcronyms = true) 23 | { 24 | if (String.IsNullOrWhiteSpace(text)) 25 | return String.Empty; 26 | 27 | var sb = new StringBuilder(text.Length * 2); 28 | sb.Append(text[0]); 29 | 30 | for (int i = 1; i < text.Length; i++) 31 | { 32 | if (Char.IsUpper(text[i])) 33 | if ((text[i - 1] != ' ' && !Char.IsUpper(text[i - 1])) || 34 | (preserveAcronyms && Char.IsUpper(text[i - 1]) && 35 | i < text.Length - 1 && !Char.IsUpper(text[i + 1]))) 36 | sb.Append(' '); 37 | 38 | sb.Append(text[i]); 39 | } 40 | 41 | return sb.ToString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Foundatio.AsyncEx; 8 | 9 | namespace Foundatio.Utility; 10 | 11 | internal static class TaskExtensions 12 | { 13 | [DebuggerStepThrough] 14 | public static ConfiguredTaskAwaitable AnyContext(this Task task) 15 | { 16 | return task.ConfigureAwait(continueOnCapturedContext: false); 17 | } 18 | 19 | [DebuggerStepThrough] 20 | public static ConfiguredCancelableAsyncEnumerable AnyContext(this IAsyncEnumerable source) 21 | { 22 | return source.ConfigureAwait(continueOnCapturedContext: false); 23 | } 24 | 25 | [DebuggerStepThrough] 26 | public static ConfiguredAsyncDisposable AnyContext(this IAsyncDisposable source) 27 | { 28 | return source.ConfigureAwait(continueOnCapturedContext: false); 29 | } 30 | 31 | [DebuggerStepThrough] 32 | public static ConfiguredTaskAwaitable AnyContext(this Task task) 33 | { 34 | return task.ConfigureAwait(continueOnCapturedContext: false); 35 | } 36 | 37 | [DebuggerStepThrough] 38 | public static ConfiguredValueTaskAwaitable AnyContext(this ValueTask task) 39 | { 40 | return task.ConfigureAwait(continueOnCapturedContext: false); 41 | } 42 | 43 | [DebuggerStepThrough] 44 | public static ConfiguredTaskAwaitable AnyContext(this AwaitableDisposable task) where TResult : IDisposable 45 | { 46 | return task.ConfigureAwait(continueOnCapturedContext: false); 47 | } 48 | 49 | public static async Task SafeDelay(this TimeProvider timeProvider, TimeSpan delay, CancellationToken cancellationToken = default) 50 | { 51 | try 52 | { 53 | await timeProvider.Delay(delay, cancellationToken); 54 | } 55 | catch (OperationCanceledException) 56 | { 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Foundatio/Extensions/TimespanExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | internal static class TimeSpanExtensions 7 | { 8 | public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan timeout) 9 | { 10 | if (timeout == TimeSpan.Zero) 11 | { 12 | var source = new CancellationTokenSource(); 13 | source.Cancel(); 14 | return source; 15 | } 16 | 17 | if (timeout.Ticks > 0) 18 | return new CancellationTokenSource(timeout); 19 | 20 | return new CancellationTokenSource(); 21 | } 22 | 23 | public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout) 24 | { 25 | if (timeout.HasValue) 26 | return timeout.Value.ToCancellationTokenSource(); 27 | 28 | return new CancellationTokenSource(); 29 | } 30 | 31 | public static CancellationTokenSource ToCancellationTokenSource(this TimeSpan? timeout, TimeSpan defaultTimeout) 32 | { 33 | return (timeout ?? defaultTimeout).ToCancellationTokenSource(); 34 | } 35 | 36 | public static TimeSpan Min(this TimeSpan source, TimeSpan other) 37 | { 38 | return source.Ticks > other.Ticks ? other : source; 39 | } 40 | 41 | public static TimeSpan Max(this TimeSpan source, TimeSpan other) 42 | { 43 | return source.Ticks < other.Ticks ? other : source; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Foundatio/Foundatio.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/IQueueJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Queues; 5 | using Foundatio.Utility; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Foundatio.Jobs; 9 | 10 | public interface IQueueJob : IJob where T : class 11 | { 12 | /// 13 | /// Processes a queue entry and returns the result. This method is typically called from RunAsync() 14 | /// but can also be called from a function passing in the queue entry. 15 | /// 16 | Task ProcessAsync(IQueueEntry queueEntry, CancellationToken cancellationToken); 17 | IQueue Queue { get; } 18 | } 19 | 20 | public static class QueueJobExtensions 21 | { 22 | /// 23 | /// Will run until the queue is empty or the wait time is exceeded. 24 | /// 25 | /// The amount of queue items processed. 26 | public static async Task RunUntilEmptyAsync(this IQueueJob job, TimeSpan waitTimeout, 27 | CancellationToken cancellationToken = default) where T : class 28 | { 29 | if (waitTimeout <= TimeSpan.Zero) 30 | throw new ArgumentException("Acquire timeout must be greater than zero", nameof(waitTimeout)); 31 | 32 | using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 33 | linkedCancellationTokenSource.CancelAfter(waitTimeout); 34 | 35 | // NOTE: This has to be awaited otherwise the linkedCancellationTokenSource cancel timer will not fire. 36 | return await job.RunUntilEmptyAsync(linkedCancellationTokenSource.Token).AnyContext(); 37 | } 38 | 39 | /// 40 | /// Will wait up to thirty seconds if queue is empty, otherwise will run until the queue is empty or cancelled. 41 | /// 42 | /// The amount of queue items processed. 43 | public static Task RunUntilEmptyAsync(this IQueueJob job, CancellationToken cancellationToken = default) where T : class 44 | { 45 | var logger = job.GetLogger(); 46 | 47 | return job.RunContinuousAsync(cancellationToken: cancellationToken, continuationCallback: async () => 48 | { 49 | // Allow abandoned items to be added in a background task. 50 | Thread.Yield(); 51 | 52 | var stats = await job.Queue.GetQueueStatsAsync().AnyContext(); 53 | logger.LogTrace("RunUntilEmpty continuation: Queued={Queued}, Working={Working}, Abandoned={Abandoned}", stats.Queued, stats.Working, stats.Abandoned); 54 | return stats.Queued + stats.Working > 0; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/JobAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Jobs; 4 | 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 6 | public class JobAttribute : Attribute 7 | { 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public bool IsContinuous { get; set; } = true; 11 | public string Interval { get; set; } 12 | public string InitialDelay { get; set; } 13 | public int IterationLimit { get; set; } = -1; 14 | public int InstanceCount { get; set; } = 1; 15 | } 16 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/JobBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Utility; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Logging.Abstractions; 7 | 8 | namespace Foundatio.Jobs; 9 | 10 | public abstract class JobBase : IJob, IHaveLogger, IHaveTimeProvider 11 | { 12 | protected readonly TimeProvider _timeProvider; 13 | protected readonly ILogger _logger; 14 | 15 | public JobBase(ILoggerFactory loggerFactory = null) : this(null, loggerFactory) 16 | { 17 | } 18 | 19 | public JobBase(TimeProvider timeProvider, ILoggerFactory loggerFactory = null) 20 | { 21 | _timeProvider = timeProvider ?? TimeProvider.System; 22 | _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; 23 | } 24 | 25 | public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); 26 | ILogger IHaveLogger.Logger => _logger; 27 | TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; 28 | 29 | public virtual Task RunAsync(CancellationToken cancellationToken = default) 30 | { 31 | return RunInternalAsync(new JobContext(cancellationToken)); 32 | } 33 | 34 | protected abstract Task RunInternalAsync(JobContext context); 35 | } 36 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/JobContext.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Foundatio.Lock; 4 | 5 | namespace Foundatio.Jobs; 6 | 7 | public class JobContext 8 | { 9 | public JobContext(CancellationToken cancellationToken, ILock lck = null) 10 | { 11 | Lock = lck; 12 | CancellationToken = cancellationToken; 13 | } 14 | 15 | public ILock Lock { get; } 16 | public CancellationToken CancellationToken { get; } 17 | 18 | public virtual Task RenewLockAsync() 19 | { 20 | if (Lock != null) 21 | return Lock.RenewAsync(); 22 | 23 | return Task.CompletedTask; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/JobResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Foundatio.Jobs; 5 | 6 | public class JobResult 7 | { 8 | public bool IsCancelled { get; set; } 9 | public Exception Error { get; set; } 10 | public string Message { get; set; } 11 | public bool IsSuccess { get; set; } 12 | 13 | public static readonly JobResult None = new() 14 | { 15 | IsSuccess = true, 16 | Message = String.Empty 17 | }; 18 | 19 | public static readonly JobResult Cancelled = new() 20 | { 21 | IsCancelled = true 22 | }; 23 | 24 | public static readonly JobResult Success = new() 25 | { 26 | IsSuccess = true 27 | }; 28 | 29 | public static JobResult FromException(Exception exception, string message = null) 30 | { 31 | return new JobResult 32 | { 33 | Error = exception, 34 | IsSuccess = false, 35 | Message = message ?? exception.Message 36 | }; 37 | } 38 | 39 | public static JobResult CancelledWithMessage(string message) 40 | { 41 | return new JobResult 42 | { 43 | IsCancelled = true, 44 | Message = message 45 | }; 46 | } 47 | 48 | public static JobResult SuccessWithMessage(string message) 49 | { 50 | return new JobResult 51 | { 52 | IsSuccess = true, 53 | Message = message 54 | }; 55 | } 56 | 57 | public static JobResult FailedWithMessage(string message) 58 | { 59 | return new JobResult 60 | { 61 | IsSuccess = false, 62 | Message = message 63 | }; 64 | } 65 | } 66 | 67 | public static class JobResultExtensions 68 | { 69 | public static void LogJobResult(this ILogger logger, JobResult result, string jobName) 70 | { 71 | if (result == null) 72 | { 73 | logger.LogError("Null job run result for {JobName}", jobName); 74 | return; 75 | } 76 | 77 | if (result.IsCancelled) 78 | logger.LogWarning(result.Error, "Job run {JobName} cancelled: {Message}", jobName, result.Message); 79 | else if (!result.IsSuccess) 80 | logger.LogError(result.Error, "Job run {JobName} failed: {Message}", jobName, result.Message); 81 | else if (!String.IsNullOrEmpty(result.Message)) 82 | logger.LogInformation("Job run {JobName} succeeded: {Message}", jobName, result.Message); 83 | else 84 | logger.LogDebug("Job run {JobName} succeeded", jobName); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/JobWithLockBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Lock; 5 | using Foundatio.Utility; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Logging.Abstractions; 8 | 9 | namespace Foundatio.Jobs; 10 | 11 | public abstract class JobWithLockBase : IJobWithOptions, IHaveLogger, IHaveTimeProvider 12 | { 13 | protected readonly ILogger _logger; 14 | private readonly TimeProvider _timeProvider; 15 | private readonly string _jobName; 16 | 17 | public JobWithLockBase(ILoggerFactory loggerFactory = null) 18 | { 19 | _jobName = GetType().Name; 20 | _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; 21 | } 22 | 23 | public JobWithLockBase(TimeProvider timeProvider, ILoggerFactory loggerFactory = null) 24 | { 25 | _jobName = GetType().Name; 26 | _timeProvider = timeProvider ?? TimeProvider.System; 27 | _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; 28 | } 29 | 30 | public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); 31 | ILogger IHaveLogger.Logger => _logger; 32 | TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; 33 | public JobOptions Options { get; set; } 34 | 35 | public virtual async Task RunAsync(CancellationToken cancellationToken = default) 36 | { 37 | ILock lockValue; 38 | using (var lockActivity = FoundatioDiagnostics.ActivitySource.StartActivity("Job Lock: " + Options?.Name ?? _jobName)) 39 | { 40 | lockActivity?.AddTag("job.id", JobId); 41 | 42 | lockValue = await GetLockAsync(cancellationToken).AnyContext(); 43 | if (lockValue is null) 44 | { 45 | return JobResult.CancelledWithMessage("Unable to acquire job lock"); 46 | } 47 | } 48 | 49 | try 50 | { 51 | return await RunInternalAsync(new JobContext(cancellationToken, lockValue)).AnyContext(); 52 | } 53 | finally 54 | { 55 | await lockValue.ReleaseAsync().AnyContext(); 56 | } 57 | } 58 | 59 | protected abstract Task RunInternalAsync(JobContext context); 60 | 61 | protected abstract Task GetLockAsync(CancellationToken cancellationToken = default); 62 | } 63 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/QueueEntryContext.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Foundatio.Lock; 4 | using Foundatio.Queues; 5 | using Foundatio.Utility; 6 | 7 | namespace Foundatio.Jobs; 8 | 9 | public class QueueEntryContext : JobContext where T : class 10 | { 11 | public QueueEntryContext(IQueueEntry queueEntry, ILock queueEntryLock, CancellationToken cancellationToken = default) : base(cancellationToken, queueEntryLock) 12 | { 13 | QueueEntry = queueEntry; 14 | } 15 | 16 | public IQueueEntry QueueEntry { get; private set; } 17 | 18 | public override async Task RenewLockAsync() 19 | { 20 | if (QueueEntry != null) 21 | await QueueEntry.RenewLockAsync().AnyContext(); 22 | 23 | await base.RenewLockAsync().AnyContext(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/WorkItemJob/WorkItemContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Lock; 5 | 6 | namespace Foundatio.Jobs; 7 | 8 | public class WorkItemContext 9 | { 10 | private readonly Func _progressCallback; 11 | 12 | public WorkItemContext(object data, string jobId, ILock workItemLock, CancellationToken cancellationToken, Func progressCallback) 13 | { 14 | Data = data; 15 | JobId = jobId; 16 | WorkItemLock = workItemLock; 17 | CancellationToken = cancellationToken; 18 | _progressCallback = progressCallback; 19 | } 20 | 21 | public object Data { get; private set; } 22 | public string JobId { get; private set; } 23 | public ILock WorkItemLock { get; private set; } 24 | public JobResult Result { get; set; } = JobResult.Success; 25 | public CancellationToken CancellationToken { get; private set; } 26 | 27 | public Task ReportProgressAsync(int progress, string message = null) 28 | { 29 | return _progressCallback(progress, message); 30 | } 31 | 32 | public Task RenewLockAsync() 33 | { 34 | if (WorkItemLock != null) 35 | return WorkItemLock.RenewAsync(); 36 | 37 | return Task.CompletedTask; 38 | } 39 | 40 | public T GetData() where T : class 41 | { 42 | return Data as T; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/WorkItemJob/WorkItemData.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Metrics; 2 | using Foundatio.Queues; 3 | 4 | namespace Foundatio.Jobs; 5 | 6 | public class WorkItemData : IHaveSubMetricName, IHaveUniqueIdentifier 7 | { 8 | public string WorkItemId { get; set; } 9 | public string Type { get; set; } 10 | public byte[] Data { get; set; } 11 | public bool SendProgressReports { get; set; } 12 | public string UniqueIdentifier { get; set; } 13 | public string SubMetricName { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/WorkItemJob/WorkItemQueueExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Metrics; 4 | using Foundatio.Queues; 5 | using Foundatio.Serializer; 6 | using Foundatio.Utility; 7 | 8 | namespace Foundatio.Jobs; 9 | 10 | public static class WorkItemQueueExtensions 11 | { 12 | public static async Task EnqueueAsync(this IQueue queue, T workItemData, bool includeProgressReporting = false) 13 | { 14 | string jobId = Guid.NewGuid().ToString("N"); 15 | var bytes = queue.Serializer.SerializeToBytes(workItemData); 16 | 17 | var data = new WorkItemData 18 | { 19 | Data = bytes, 20 | WorkItemId = jobId, 21 | Type = typeof(T).AssemblyQualifiedName, 22 | SendProgressReports = includeProgressReporting 23 | }; 24 | 25 | if (workItemData is IHaveUniqueIdentifier haveUniqueIdentifier) 26 | data.UniqueIdentifier = haveUniqueIdentifier.UniqueIdentifier; 27 | 28 | if (workItemData is IHaveSubMetricName haveSubMetricName && haveSubMetricName.SubMetricName != null) 29 | data.SubMetricName = haveSubMetricName.SubMetricName; 30 | else 31 | data.SubMetricName = GetDefaultSubMetricName(data); 32 | 33 | await queue.EnqueueAsync(data).AnyContext(); 34 | 35 | return jobId; 36 | } 37 | 38 | private static string GetDefaultSubMetricName(WorkItemData data) 39 | { 40 | if (String.IsNullOrEmpty(data.Type)) 41 | return null; 42 | 43 | string type = GetTypeName(data.Type); 44 | if (type != null && type.EndsWith("WorkItem")) 45 | type = type.Substring(0, type.Length - 8); 46 | 47 | return type?.ToLowerInvariant(); 48 | } 49 | 50 | private static string GetTypeName(string assemblyQualifiedName) 51 | { 52 | if (String.IsNullOrEmpty(assemblyQualifiedName)) 53 | return null; 54 | 55 | var parts = assemblyQualifiedName.Split(','); 56 | int i = parts[0].LastIndexOf('.'); 57 | 58 | return i < 0 ? null : parts[0].Substring(i + 1); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Foundatio/Jobs/WorkItemJob/WorkItemStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Jobs; 2 | 3 | public class WorkItemStatus 4 | { 5 | public string WorkItemId { get; set; } 6 | public int Progress { get; set; } 7 | public string Message { get; set; } 8 | public string Type { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Foundatio/Lock/ScopedLockProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Foundatio.Utility; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Foundatio.Lock; 8 | 9 | public class ScopedLockProvider : ILockProvider, IHaveLogger 10 | { 11 | private string _keyPrefix; 12 | private bool _isLocked; 13 | private readonly object _lock = new(); 14 | 15 | public ScopedLockProvider(ILockProvider lockProvider, string scope = null) 16 | { 17 | UnscopedLockProvider = lockProvider; 18 | _isLocked = scope != null; 19 | Scope = !String.IsNullOrWhiteSpace(scope) ? scope.Trim() : null; 20 | 21 | _keyPrefix = Scope != null ? String.Concat(Scope, ":") : String.Empty; 22 | } 23 | 24 | public ILockProvider UnscopedLockProvider { get; } 25 | public string Scope { get; private set; } 26 | ILogger IHaveLogger.Logger => UnscopedLockProvider.GetLogger(); 27 | 28 | public void SetScope(string scope) 29 | { 30 | if (_isLocked) 31 | throw new InvalidOperationException("Scope can't be changed after it has been set"); 32 | 33 | lock (_lock) 34 | { 35 | if (_isLocked) 36 | throw new InvalidOperationException("Scope can't be changed after it has been set"); 37 | 38 | _isLocked = true; 39 | Scope = !String.IsNullOrWhiteSpace(scope) ? scope.Trim() : null; 40 | _keyPrefix = Scope != null ? String.Concat(Scope, ":") : String.Empty; 41 | } 42 | } 43 | 44 | protected string GetScopedLockProviderKey(string key) 45 | { 46 | return String.Concat(_keyPrefix, key); 47 | } 48 | 49 | public Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) 50 | { 51 | return UnscopedLockProvider.AcquireAsync(GetScopedLockProviderKey(resource), timeUntilExpires, releaseOnDispose, cancellationToken); 52 | } 53 | 54 | public Task IsLockedAsync(string resource) 55 | { 56 | return UnscopedLockProvider.IsLockedAsync(GetScopedLockProviderKey(resource)); 57 | } 58 | 59 | public Task ReleaseAsync(string resource, string lockId) 60 | { 61 | return UnscopedLockProvider.ReleaseAsync(resource, lockId); 62 | } 63 | 64 | public Task ReleaseAsync(string resource) 65 | { 66 | return UnscopedLockProvider.ReleaseAsync(resource); 67 | } 68 | 69 | public Task RenewAsync(string resource, string lockId, TimeSpan? timeUntilExpires = null) 70 | { 71 | return UnscopedLockProvider.RenewAsync(resource, lockId, timeUntilExpires); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/IMessageBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Foundatio.Messaging; 5 | 6 | public interface IMessageBus : IMessagePublisher, IMessageSubscriber, IDisposable { } 7 | 8 | public class MessageOptions 9 | { 10 | public string UniqueId { get; set; } 11 | public string CorrelationId { get; set; } 12 | public TimeSpan? DeliveryDelay { get; set; } 13 | public IDictionary Properties { get; set; } = new Dictionary(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/IMessagePublisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Messaging; 6 | 7 | public interface IMessagePublisher 8 | { 9 | Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default); 10 | } 11 | 12 | public static class MessagePublisherExtensions 13 | { 14 | public static Task PublishAsync(this IMessagePublisher publisher, T message, MessageOptions options = null) where T : class 15 | { 16 | return publisher.PublishAsync(typeof(T), message, options); 17 | } 18 | 19 | public static Task PublishAsync(this IMessagePublisher publisher, T message, TimeSpan delay, CancellationToken cancellationToken = default) where T : class 20 | { 21 | return publisher.PublishAsync(typeof(T), message, new MessageOptions { DeliveryDelay = delay }, cancellationToken); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/IMessageSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Messaging; 6 | 7 | public interface IMessageSubscriber 8 | { 9 | Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class; 10 | } 11 | 12 | public static class MessageBusExtensions 13 | { 14 | public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) where T : class 15 | { 16 | return subscriber.SubscribeAsync((msg, token) => handler(msg), cancellationToken); 17 | } 18 | 19 | public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) where T : class 20 | { 21 | return subscriber.SubscribeAsync((msg, token) => 22 | { 23 | handler(msg); 24 | return Task.CompletedTask; 25 | }, cancellationToken); 26 | } 27 | 28 | public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) 29 | { 30 | return subscriber.SubscribeAsync((msg, token) => handler(msg, token), cancellationToken); 31 | } 32 | 33 | public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func handler, CancellationToken cancellationToken = default) 34 | { 35 | return subscriber.SubscribeAsync((msg, token) => handler(msg), cancellationToken); 36 | } 37 | 38 | public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action handler, CancellationToken cancellationToken = default) 39 | { 40 | return subscriber.SubscribeAsync((msg, token) => 41 | { 42 | handler(msg); 43 | return Task.CompletedTask; 44 | }, cancellationToken); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/InMemoryMessageBusOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Messaging; 2 | 3 | public class InMemoryMessageBusOptions : SharedMessageBusOptions { } 4 | 5 | public class InMemoryMessageBusOptionsBuilder : SharedMessageBusOptionsBuilder { } 6 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace Foundatio.Messaging; 6 | 7 | public interface IMessage 8 | { 9 | string UniqueId { get; } 10 | string CorrelationId { get; } 11 | string Type { get; } 12 | Type ClrType { get; } 13 | byte[] Data { get; } 14 | object GetBody(); 15 | IDictionary Properties { get; } 16 | } 17 | 18 | public interface IMessage : IMessage where T : class 19 | { 20 | T Body { get; } 21 | } 22 | 23 | [DebuggerDisplay("Type: {Type}")] 24 | public class Message : IMessage 25 | { 26 | private readonly Func _getBody; 27 | 28 | public Message(byte[] data, Func getBody) 29 | { 30 | Data = data; 31 | _getBody = getBody; 32 | } 33 | 34 | public string UniqueId { get; set; } 35 | public string CorrelationId { get; set; } 36 | public string Type { get; set; } 37 | public Type ClrType { get; set; } 38 | public IDictionary Properties { get; set; } = new Dictionary(); 39 | public byte[] Data { get; set; } 40 | public object GetBody() => _getBody(this); 41 | } 42 | 43 | public class Message : IMessage where T : class 44 | { 45 | private readonly IMessage _message; 46 | 47 | public Message(IMessage message) 48 | { 49 | _message = message; 50 | } 51 | 52 | public byte[] Data => _message.Data; 53 | 54 | public T Body => (T)GetBody(); 55 | 56 | public string UniqueId => _message.UniqueId; 57 | 58 | public string CorrelationId => _message.CorrelationId; 59 | 60 | public string Type => _message.Type; 61 | 62 | public Type ClrType => _message.ClrType; 63 | 64 | public IDictionary Properties => _message.Properties; 65 | 66 | public object GetBody() => _message.GetBody(); 67 | } 68 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/NullMessageBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Messaging; 6 | 7 | public class NullMessageBus : IMessageBus 8 | { 9 | public static readonly NullMessageBus Instance = new(); 10 | 11 | public Task PublishAsync(Type messageType, object message, MessageOptions options = null, CancellationToken cancellationToken = default) 12 | { 13 | return Task.CompletedTask; 14 | } 15 | 16 | public Task SubscribeAsync(Func handler, CancellationToken cancellationToken = default) where T : class 17 | { 18 | return Task.CompletedTask; 19 | } 20 | 21 | public void Dispose() { } 22 | } 23 | -------------------------------------------------------------------------------- /src/Foundatio/Messaging/SharedMessageBusOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Foundatio.Messaging; 5 | 6 | public class SharedMessageBusOptions : SharedOptions 7 | { 8 | /// 9 | /// The topic name 10 | /// 11 | public string Topic { get; set; } = "messages"; 12 | 13 | /// 14 | /// Controls which types messages are mapped to. 15 | /// 16 | public Dictionary MessageTypeMappings { get; set; } = new Dictionary(); 17 | } 18 | 19 | public class SharedMessageBusOptionsBuilder : SharedOptionsBuilder 20 | where TOptions : SharedMessageBusOptions, new() 21 | where TBuilder : SharedMessageBusOptionsBuilder 22 | { 23 | public TBuilder Topic(string topic) 24 | { 25 | if (string.IsNullOrEmpty(topic)) 26 | throw new ArgumentNullException(nameof(topic)); 27 | Target.Topic = topic; 28 | return (TBuilder)this; 29 | } 30 | 31 | public TBuilder MapMessageType(string name) 32 | { 33 | if (Target.MessageTypeMappings == null) 34 | Target.MessageTypeMappings = new Dictionary(); 35 | 36 | Target.MessageTypeMappings[name] = typeof(T); 37 | return (TBuilder)this; 38 | } 39 | 40 | public TBuilder MapMessageTypeToClassName() 41 | { 42 | if (Target.MessageTypeMappings == null) 43 | Target.MessageTypeMappings = new Dictionary(); 44 | 45 | Target.MessageTypeMappings[typeof(T).Name] = typeof(T); 46 | return (TBuilder)this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Foundatio/Metrics/IHaveSubMetricName.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Metrics; 2 | 3 | public interface IHaveSubMetricName 4 | { 5 | string SubMetricName { get; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Coordination/IdManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace Foundatio.AsyncEx; 4 | 5 | /// 6 | /// Allocates Ids for instances on demand. 0 is an invalid/unassigned Id. Ids may be non-unique in very long-running systems. This is similar to the Id system used by and . 7 | /// 8 | /// The type for which ids are generated. 9 | // ReSharper disable UnusedTypeParameter 10 | internal static class IdManager 11 | // ReSharper restore UnusedTypeParameter 12 | { 13 | /// 14 | /// The last id generated for this type. This is 0 if no ids have been generated. 15 | /// 16 | // ReSharper disable StaticFieldInGenericType 17 | private static int _lastId; 18 | // ReSharper restore StaticFieldInGenericType 19 | 20 | /// 21 | /// Returns the id, allocating it if necessary. 22 | /// 23 | /// A reference to the field containing the id. 24 | public static int GetId(ref int id) 25 | { 26 | // If the Id has already been assigned, just use it. 27 | if (id != 0) 28 | return id; 29 | 30 | // Determine the new Id without modifying "id", since other threads may also be determining the new Id at the same time. 31 | int newId; 32 | 33 | // The Increment is in a while loop to ensure we get a non-zero Id: 34 | // If we are incrementing -1, then we want to skip over 0. 35 | // If there are tons of Id allocations going on, we want to skip over 0 no matter how many times we get it. 36 | do 37 | { 38 | newId = Interlocked.Increment(ref _lastId); 39 | } while (newId == 0); 40 | 41 | // Update the Id unless another thread already updated it. 42 | Interlocked.CompareExchange(ref id, newId, 0); 43 | 44 | // Return the current Id, regardless of whether it's our new Id or a new Id from another thread. 45 | return id; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Coordination/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stephen Cleary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Tasks/AwaitableDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.AsyncEx; 6 | 7 | /// 8 | /// An awaitable wrapper around a task whose result is disposable. The wrapper is not disposable, so this prevents usage errors like "using (MyAsync())" when the appropriate usage should be "using (await MyAsync())". 9 | /// 10 | /// The type of the result of the underlying task. 11 | public struct AwaitableDisposable where T : IDisposable 12 | { 13 | /// 14 | /// The underlying task. 15 | /// 16 | private readonly Task _task; 17 | 18 | /// 19 | /// Initializes a new awaitable wrapper around the specified task. 20 | /// 21 | /// The underlying task to wrap. This may not be null. 22 | public AwaitableDisposable(Task task) 23 | { 24 | if (task == null) 25 | throw new ArgumentNullException(nameof(task)); 26 | _task = task; 27 | } 28 | 29 | /// 30 | /// Returns the underlying task. 31 | /// 32 | public Task AsTask() 33 | { 34 | return _task; 35 | } 36 | 37 | /// 38 | /// Implicit conversion to the underlying task. 39 | /// 40 | /// The awaitable wrapper. 41 | public static implicit operator Task(AwaitableDisposable source) 42 | { 43 | return source.AsTask(); 44 | } 45 | 46 | /// 47 | /// Infrastructure. Returns the task awaiter for the underlying task. 48 | /// 49 | public TaskAwaiter GetAwaiter() 50 | { 51 | return _task.GetAwaiter(); 52 | } 53 | 54 | /// 55 | /// Infrastructure. Returns a configured task awaiter for the underlying task. 56 | /// 57 | /// Whether to attempt to marshal the continuation back to the captured context. 58 | public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) 59 | { 60 | return _task.ConfigureAwait(continueOnCapturedContext); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.AsyncEx; 6 | 7 | /// 8 | /// Holds the task for a cancellation token, as well as the token registration. The registration is disposed when this instance is disposed. 9 | /// 10 | public sealed class CancellationTokenTaskSource : IDisposable 11 | { 12 | /// 13 | /// The cancellation token registration, if any. This is null if the registration was not necessary. 14 | /// 15 | private readonly IDisposable _registration; 16 | 17 | /// 18 | /// Creates a task for the specified cancellation token, registering with the token if necessary. 19 | /// 20 | /// The cancellation token to observe. 21 | public CancellationTokenTaskSource(CancellationToken cancellationToken) 22 | { 23 | if (cancellationToken.IsCancellationRequested) 24 | { 25 | Task = System.Threading.Tasks.Task.FromCanceled(cancellationToken); 26 | return; 27 | } 28 | var tcs = new TaskCompletionSource(); 29 | _registration = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken), useSynchronizationContext: false); 30 | Task = tcs.Task; 31 | } 32 | 33 | /// 34 | /// Gets the task for the source cancellation token. 35 | /// 36 | public Task Task { get; private set; } 37 | 38 | /// 39 | /// Disposes the cancellation token registration, if any. Note that this may cause to never complete. 40 | /// 41 | public void Dispose() 42 | { 43 | _registration?.Dispose(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Tasks/ExceptionHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ExceptionServices; 3 | 4 | internal static class ExceptionHelpers 5 | { 6 | /// 7 | /// Attempts to prepare the exception for re-throwing by preserving the stack trace. The returned exception should be immediately thrown. 8 | /// 9 | /// The exception. May not be null. 10 | /// The that was passed into this method. 11 | public static Exception PrepareForRethrow(Exception exception) 12 | { 13 | ExceptionDispatchInfo.Capture(exception).Throw(); 14 | 15 | // The code cannot ever get here. We just return a value to work around a badly-designed API (ExceptionDispatchInfo.Throw): 16 | // https://connect.microsoft.com/VisualStudio/feedback/details/689516/exceptiondispatchinfo-api-modifications (http://www.webcitation.org/6XQ7RoJmO) 17 | return exception; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Tasks/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stephen Cleary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.AsyncEx.Tasks/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.AsyncEx; 6 | 7 | /// 8 | /// Provides extension methods for the and types. 9 | /// 10 | public static class TaskExtensions 11 | { 12 | /// 13 | /// Asynchronously waits for the task to complete, or for the cancellation token to be canceled. 14 | /// 15 | /// The task to wait for. May not be null. 16 | /// The cancellation token that cancels the wait. 17 | public static Task WaitAsync(this Task @this, CancellationToken cancellationToken) 18 | { 19 | if (@this == null) 20 | throw new ArgumentNullException(nameof(@this)); 21 | 22 | if (!cancellationToken.CanBeCanceled) 23 | return @this; 24 | if (cancellationToken.IsCancellationRequested) 25 | return Task.FromCanceled(cancellationToken); 26 | return DoWaitAsync(@this, cancellationToken); 27 | } 28 | 29 | private static async Task DoWaitAsync(Task task, CancellationToken cancellationToken) 30 | { 31 | using var cancelTaskSource = new CancellationTokenTaskSource(cancellationToken); 32 | await await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.Collections.Deque/CollectionHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Foundatio.Collections; 6 | 7 | internal static class CollectionHelpers 8 | { 9 | public static IReadOnlyCollection ReifyCollection(IEnumerable source) 10 | { 11 | if (source == null) 12 | throw new ArgumentNullException(nameof(source)); 13 | 14 | var result = source as IReadOnlyCollection; 15 | if (result != null) 16 | return result; 17 | var collection = source as ICollection; 18 | if (collection != null) 19 | return new CollectionWrapper(collection); 20 | var nongenericCollection = source as ICollection; 21 | if (nongenericCollection != null) 22 | return new NongenericCollectionWrapper(nongenericCollection); 23 | 24 | return new List(source); 25 | } 26 | 27 | private sealed class NongenericCollectionWrapper : IReadOnlyCollection 28 | { 29 | private readonly ICollection _collection; 30 | 31 | public NongenericCollectionWrapper(ICollection collection) 32 | { 33 | if (collection == null) 34 | throw new ArgumentNullException(nameof(collection)); 35 | _collection = collection; 36 | } 37 | 38 | public int Count 39 | { 40 | get 41 | { 42 | return _collection.Count; 43 | } 44 | } 45 | 46 | public IEnumerator GetEnumerator() 47 | { 48 | foreach (T item in _collection) 49 | yield return item; 50 | } 51 | 52 | IEnumerator IEnumerable.GetEnumerator() 53 | { 54 | return _collection.GetEnumerator(); 55 | } 56 | } 57 | 58 | private sealed class CollectionWrapper : IReadOnlyCollection 59 | { 60 | private readonly ICollection _collection; 61 | 62 | public CollectionWrapper(ICollection collection) 63 | { 64 | if (collection == null) 65 | throw new ArgumentNullException(nameof(collection)); 66 | _collection = collection; 67 | } 68 | 69 | public int Count 70 | { 71 | get 72 | { 73 | return _collection.Count; 74 | } 75 | } 76 | 77 | public IEnumerator GetEnumerator() 78 | { 79 | return _collection.GetEnumerator(); 80 | } 81 | 82 | IEnumerator IEnumerable.GetEnumerator() 83 | { 84 | return _collection.GetEnumerator(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.Collections.Deque/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stephen Cleary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.Disposables/AnonymousDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Disposables; 4 | 5 | /// 6 | /// A disposable that executes a delegate when disposed. 7 | /// 8 | public sealed class AnonymousDisposable : SingleDisposable 9 | { 10 | /// 11 | /// Creates a new disposable that executes when disposed. 12 | /// 13 | /// The delegate to execute when disposed. If this is null, then this instance does nothing when it is disposed. 14 | public AnonymousDisposable(Action dispose) 15 | : base(dispose) 16 | { 17 | } 18 | 19 | /// 20 | protected override void Dispose(Action context) => context?.Invoke(); 21 | 22 | /// 23 | /// Adds a delegate to be executed when this instance is disposed. If this instance is already disposed or disposing, then is executed immediately. 24 | /// If this method is called multiple times concurrently at the same time this instance is disposed, then the different arguments may be disposed concurrently. 25 | /// 26 | /// The delegate to add. May be null to indicate no additional action. 27 | public void Add(Action dispose) 28 | { 29 | if (dispose == null) 30 | return; 31 | if (TryUpdateContext(x => x + dispose)) 32 | return; 33 | 34 | // Wait for our disposal to complete; then call the additional delegate. 35 | Dispose(); 36 | dispose(); 37 | } 38 | 39 | /// 40 | /// Creates a new disposable that executes when disposed. 41 | /// 42 | /// The delegate to execute when disposed. If this is null, then this instance does nothing when it is disposed. 43 | public static AnonymousDisposable Create(Action dispose) => new(dispose); 44 | } 45 | -------------------------------------------------------------------------------- /src/Foundatio/Nito.Disposables/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Stephen Cleary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Foundatio/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Foundatio.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9357232b9bcad78fd310297fdb41bf42816ee2ca9ccdace999889de2badb6f06df2de1d9f2c8cb17b21f5311f11d6bb328d55e0dd9fe8adc5e2dc4610028c1bdacb3355d2e239b81d0bb0ac83e615fc641f8a3ec49e4fad8e305994953d448ef7b38e8c256601e54af19c035b562e3e5e5461c2a93b8dd11936e451b05034a2")] 4 | [assembly: InternalsVisibleTo("Foundatio.TestHarness, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9357232b9bcad78fd310297fdb41bf42816ee2ca9ccdace999889de2badb6f06df2de1d9f2c8cb17b21f5311f11d6bb328d55e0dd9fe8adc5e2dc4610028c1bdacb3355d2e239b81d0bb0ac83e615fc641f8a3ec49e4fad8e305994953d448ef7b38e8c256601e54af19c035b562e3e5e5461c2a93b8dd11936e451b05034a2")] 5 | -------------------------------------------------------------------------------- /src/Foundatio/Queues/DuplicateDetectionQueueBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Caching; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Foundatio.Queues; 7 | 8 | public class DuplicateDetectionQueueBehavior : QueueBehaviorBase where T : class 9 | { 10 | private readonly ICacheClient _cacheClient; 11 | private readonly ILoggerFactory _loggerFactory; 12 | private readonly TimeSpan _detectionWindow; 13 | 14 | public DuplicateDetectionQueueBehavior(ICacheClient cacheClient, ILoggerFactory loggerFactory, TimeSpan? detectionWindow = null) 15 | { 16 | _cacheClient = cacheClient; 17 | _loggerFactory = loggerFactory; 18 | _detectionWindow = detectionWindow ?? TimeSpan.FromMinutes(10); 19 | } 20 | 21 | protected override async Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) 22 | { 23 | string uniqueIdentifier = GetUniqueIdentifier(enqueuingEventArgs.Data); 24 | if (String.IsNullOrEmpty(uniqueIdentifier)) 25 | return; 26 | 27 | bool success = await _cacheClient.AddAsync(uniqueIdentifier, true, _detectionWindow); 28 | if (!success) 29 | { 30 | var logger = _loggerFactory.CreateLogger(); 31 | logger.LogInformation("Discarding queue entry due to duplicate {UniqueIdentifier}", uniqueIdentifier); 32 | enqueuingEventArgs.Cancel = true; 33 | } 34 | } 35 | 36 | protected override async Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) 37 | { 38 | string uniqueIdentifier = GetUniqueIdentifier(dequeuedEventArgs.Entry.Value); 39 | if (String.IsNullOrEmpty(uniqueIdentifier)) 40 | return; 41 | 42 | await _cacheClient.RemoveAsync(uniqueIdentifier); 43 | } 44 | 45 | private string GetUniqueIdentifier(T data) 46 | { 47 | var haveUniqueIdentifier = data as IHaveUniqueIdentifier; 48 | return haveUniqueIdentifier?.UniqueIdentifier; 49 | } 50 | } 51 | 52 | public interface IHaveUniqueIdentifier 53 | { 54 | string UniqueIdentifier { get; } 55 | } 56 | -------------------------------------------------------------------------------- /src/Foundatio/Queues/IQueueActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Queues; 4 | 5 | public interface IQueueActivity 6 | { 7 | DateTimeOffset? LastEnqueueActivity { get; } 8 | DateTimeOffset? LastDequeueActivity { get; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Foundatio/Queues/IQueueEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Queues; 6 | 7 | public interface IQueueEntry 8 | { 9 | string Id { get; } 10 | string CorrelationId { get; } 11 | IDictionary Properties { get; } 12 | Type EntryType { get; } 13 | object GetValue(); 14 | bool IsCompleted { get; } 15 | bool IsAbandoned { get; } 16 | int Attempts { get; } 17 | void MarkAbandoned(); 18 | void MarkCompleted(); 19 | Task RenewLockAsync(); 20 | Task AbandonAsync(); 21 | Task CompleteAsync(); 22 | ValueTask DisposeAsync(); 23 | } 24 | 25 | public interface IQueueEntry : IQueueEntry where T : class 26 | { 27 | T Value { get; } 28 | } 29 | -------------------------------------------------------------------------------- /src/Foundatio/Queues/InMemoryQueueOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Queues; 4 | 5 | public class InMemoryQueueOptions : SharedQueueOptions where T : class 6 | { 7 | public TimeSpan RetryDelay { get; set; } = TimeSpan.FromMinutes(1); 8 | public int CompletedEntryRetentionLimit { get; set; } = 100; 9 | public int[] RetryMultipliers { get; set; } = { 1, 3, 5, 10 }; 10 | } 11 | 12 | public class InMemoryQueueOptionsBuilder : SharedQueueOptionsBuilder, InMemoryQueueOptionsBuilder> where T : class 13 | { 14 | public InMemoryQueueOptionsBuilder RetryDelay(TimeSpan retryDelay) 15 | { 16 | if (retryDelay < TimeSpan.Zero) 17 | throw new ArgumentOutOfRangeException(nameof(retryDelay)); 18 | 19 | Target.RetryDelay = retryDelay; 20 | return this; 21 | } 22 | 23 | public InMemoryQueueOptionsBuilder CompletedEntryRetentionLimit(int retentionCount) 24 | { 25 | if (retentionCount < 0) 26 | throw new ArgumentOutOfRangeException(nameof(retentionCount)); 27 | 28 | Target.CompletedEntryRetentionLimit = retentionCount; 29 | return this; 30 | } 31 | 32 | public InMemoryQueueOptionsBuilder RetryMultipliers(int[] multipliers) 33 | { 34 | if (multipliers == null) 35 | throw new ArgumentNullException(nameof(multipliers)); 36 | 37 | foreach (int multiplier in multipliers) 38 | { 39 | if (multiplier < 1) 40 | throw new ArgumentOutOfRangeException(nameof(multipliers)); 41 | } 42 | 43 | Target.RetryMultipliers = multipliers; 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Foundatio/Queues/QueueBehaviour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Queues; 6 | 7 | public interface IQueueBehavior where T : class 8 | { 9 | void Attach(IQueue queue); 10 | } 11 | 12 | public abstract class QueueBehaviorBase : IQueueBehavior, IDisposable where T : class 13 | { 14 | protected IQueue _queue; 15 | private readonly List _disposables = new(); 16 | 17 | public virtual void Attach(IQueue queue) 18 | { 19 | _queue = queue; 20 | 21 | _disposables.Add(_queue.Enqueuing.AddHandler(OnEnqueuing)); 22 | _disposables.Add(_queue.Enqueued.AddHandler(OnEnqueued)); 23 | _disposables.Add(_queue.Dequeued.AddHandler(OnDequeued)); 24 | _disposables.Add(_queue.LockRenewed.AddHandler(OnLockRenewed)); 25 | _disposables.Add(_queue.Completed.AddHandler(OnCompleted)); 26 | _disposables.Add(_queue.Abandoned.AddHandler(OnAbandoned)); 27 | } 28 | 29 | protected virtual Task OnEnqueuing(object sender, EnqueuingEventArgs enqueuingEventArgs) 30 | { 31 | return Task.CompletedTask; 32 | } 33 | 34 | protected virtual Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) 35 | { 36 | return Task.CompletedTask; 37 | } 38 | 39 | protected virtual Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) 40 | { 41 | return Task.CompletedTask; 42 | } 43 | 44 | protected virtual Task OnLockRenewed(object sender, LockRenewedEventArgs dequeuedEventArgs) 45 | { 46 | return Task.CompletedTask; 47 | } 48 | 49 | protected virtual Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) 50 | { 51 | return Task.CompletedTask; 52 | } 53 | 54 | protected virtual Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) 55 | { 56 | return Task.CompletedTask; 57 | } 58 | 59 | public virtual void Dispose() 60 | { 61 | foreach (var disposable in _disposables) 62 | disposable.Dispose(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Foundatio/Serializer/IHaveSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Serializer; 2 | 3 | public interface IHaveSerializer 4 | { 5 | ISerializer Serializer { get; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Foundatio/Serializer/ISerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace Foundatio.Serializer; 6 | 7 | public interface ISerializer 8 | { 9 | object Deserialize(Stream data, Type objectType); 10 | void Serialize(object value, Stream output); 11 | } 12 | 13 | public interface ITextSerializer : ISerializer { } 14 | 15 | public static class DefaultSerializer 16 | { 17 | public static ISerializer Instance { get; set; } = new SystemTextJsonSerializer(); 18 | } 19 | 20 | public static class SerializerExtensions 21 | { 22 | public static T Deserialize(this ISerializer serializer, Stream data) 23 | { 24 | return (T)serializer.Deserialize(data, typeof(T)); 25 | } 26 | 27 | public static T Deserialize(this ISerializer serializer, byte[] data) 28 | { 29 | return (T)serializer.Deserialize(new MemoryStream(data), typeof(T)); 30 | } 31 | 32 | public static object Deserialize(this ISerializer serializer, byte[] data, Type objectType) 33 | { 34 | return serializer.Deserialize(new MemoryStream(data), objectType); 35 | } 36 | 37 | public static T Deserialize(this ISerializer serializer, string data) 38 | { 39 | byte[] bytes; 40 | if (data == null) 41 | bytes = Array.Empty(); 42 | else if (serializer is ITextSerializer) 43 | bytes = Encoding.UTF8.GetBytes(data); 44 | else 45 | bytes = Convert.FromBase64String(data); 46 | 47 | return (T)serializer.Deserialize(new MemoryStream(bytes), typeof(T)); 48 | } 49 | 50 | public static object Deserialize(this ISerializer serializer, string data, Type objectType) 51 | { 52 | byte[] bytes; 53 | if (data == null) 54 | bytes = Array.Empty(); 55 | else if (serializer is ITextSerializer) 56 | bytes = Encoding.UTF8.GetBytes(data); 57 | else 58 | bytes = Convert.FromBase64String(data); 59 | 60 | return serializer.Deserialize(new MemoryStream(bytes), objectType); 61 | } 62 | 63 | public static string SerializeToString(this ISerializer serializer, T value) 64 | { 65 | if (value == null) 66 | return null; 67 | 68 | var bytes = serializer.SerializeToBytes(value); 69 | if (serializer is ITextSerializer) 70 | return Encoding.UTF8.GetString(bytes); 71 | 72 | return Convert.ToBase64String(bytes); 73 | } 74 | 75 | public static byte[] SerializeToBytes(this ISerializer serializer, T value) 76 | { 77 | if (value == null) 78 | return null; 79 | 80 | var stream = new MemoryStream(); 81 | serializer.Serialize(value, stream); 82 | 83 | return stream.ToArray(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Foundatio/Serializer/SystemTextJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Foundatio.Serializer; 7 | 8 | public class SystemTextJsonSerializer : ITextSerializer 9 | { 10 | private readonly JsonSerializerOptions _serializeOptions; 11 | private readonly JsonSerializerOptions _deserializeOptions; 12 | 13 | public SystemTextJsonSerializer(JsonSerializerOptions serializeOptions = null, JsonSerializerOptions deserializeOptions = null) 14 | { 15 | if (serializeOptions != null) 16 | { 17 | _serializeOptions = serializeOptions; 18 | } 19 | else 20 | { 21 | _serializeOptions = new JsonSerializerOptions(); 22 | } 23 | 24 | if (deserializeOptions != null) 25 | { 26 | _deserializeOptions = deserializeOptions; 27 | } 28 | else 29 | { 30 | _deserializeOptions = new JsonSerializerOptions(); 31 | _deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter()); 32 | } 33 | } 34 | 35 | public void Serialize(object data, Stream outputStream) 36 | { 37 | var writer = new Utf8JsonWriter(outputStream); 38 | JsonSerializer.Serialize(writer, data, data.GetType(), _serializeOptions); 39 | writer.Flush(); 40 | } 41 | 42 | public object Deserialize(Stream inputStream, Type objectType) 43 | { 44 | using var reader = new StreamReader(inputStream); 45 | return JsonSerializer.Deserialize(reader.ReadToEnd(), objectType, _deserializeOptions); 46 | } 47 | } 48 | 49 | public class ObjectToInferredTypesConverter : JsonConverter 50 | { 51 | public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 52 | { 53 | if (reader.TokenType == JsonTokenType.True) 54 | return true; 55 | 56 | if (reader.TokenType == JsonTokenType.False) 57 | return false; 58 | 59 | if (reader.TokenType == JsonTokenType.Number) 60 | return reader.TryGetInt64(out long number) ? number : (object)reader.GetDouble(); 61 | 62 | if (reader.TokenType == JsonTokenType.String) 63 | return reader.TryGetDateTime(out var datetime) ? datetime : (object)reader.GetString(); 64 | 65 | using var document = JsonDocument.ParseValue(ref reader); 66 | 67 | return document.RootElement.Clone(); 68 | } 69 | 70 | public override void Write(Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options) 71 | { 72 | throw new InvalidOperationException(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Foundatio/Storage/FolderFileStorageOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Storage; 4 | 5 | public class FolderFileStorageOptions : SharedOptions 6 | { 7 | public string Folder { get; set; } 8 | } 9 | 10 | public class FolderFileStorageOptionsBuilder : SharedOptionsBuilder 11 | { 12 | public FolderFileStorageOptionsBuilder Folder(string folder) 13 | { 14 | if (string.IsNullOrEmpty(folder)) 15 | throw new ArgumentNullException(nameof(folder)); 16 | Target.Folder = folder; 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Foundatio/Storage/InMemoryFileStorageOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Storage; 2 | 3 | public class InMemoryFileStorageOptions : SharedOptions 4 | { 5 | public long MaxFileSize { get; set; } = 1024 * 1024 * 256; 6 | public int MaxFiles { get; set; } = 100; 7 | } 8 | 9 | public class InMemoryFileStorageOptionsBuilder : SharedOptionsBuilder 10 | { 11 | public InMemoryFileStorageOptionsBuilder MaxFileSize(long maxFileSize) 12 | { 13 | Target.MaxFileSize = maxFileSize; 14 | return this; 15 | } 16 | 17 | public InMemoryFileStorageOptionsBuilder MaxFiles(int maxFiles) 18 | { 19 | Target.MaxFiles = maxFiles; 20 | return this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Foundatio/Storage/StorageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Storage; 4 | 5 | public class StorageException : Exception 6 | { 7 | public StorageException(string message) : base(message) 8 | { 9 | } 10 | 11 | public StorageException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Foundatio/Storage/StreamMode.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio.Storage; 2 | 3 | /// 4 | /// Tells what the stream will be used for 5 | /// 6 | public enum StreamMode 7 | { 8 | Read, 9 | Write 10 | } 11 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/AsyncDisposableAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Utility; 6 | 7 | /// 8 | /// A class that will call an when Disposed. 9 | /// 10 | public sealed class AsyncDisposableAction : IAsyncDisposable 11 | { 12 | private Func _exitTask; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The exit action. 18 | public AsyncDisposableAction(Func exitTask) 19 | { 20 | _exitTask = exitTask; 21 | } 22 | 23 | /// 24 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 25 | /// 26 | async ValueTask IAsyncDisposable.DisposeAsync() 27 | { 28 | var exitAction = Interlocked.Exchange(ref _exitTask, null); 29 | if (exitAction is not null) 30 | await exitAction().AnyContext(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/DisposableAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | /// 7 | /// A class that will call an when Disposed. 8 | /// 9 | public sealed class DisposableAction : IDisposable 10 | { 11 | private Action _exitAction; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The exit action. 17 | public DisposableAction(Action exitAction) 18 | { 19 | _exitAction = exitAction; 20 | } 21 | 22 | /// 23 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 24 | /// 25 | void IDisposable.Dispose() 26 | { 27 | var exitAction = Interlocked.Exchange(ref _exitAction, null); 28 | exitAction?.Invoke(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/EmptyDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Lock; 4 | 5 | namespace Foundatio.Utility; 6 | 7 | public class EmptyDisposable : IDisposable 8 | { 9 | public void Dispose() { } 10 | } 11 | 12 | public class EmptyLock : ILock 13 | { 14 | public string LockId => String.Empty; 15 | 16 | public string Resource => String.Empty; 17 | 18 | public DateTime AcquiredTimeUtc => DateTime.MinValue; 19 | 20 | public TimeSpan TimeWaitedForLock => TimeSpan.Zero; 21 | 22 | public int RenewalCount => 0; 23 | 24 | public ValueTask DisposeAsync() 25 | { 26 | return new ValueTask(); 27 | } 28 | 29 | public Task RenewAsync(TimeSpan? lockExtension = null) 30 | { 31 | return Task.CompletedTask; 32 | } 33 | 34 | public Task ReleaseAsync() 35 | { 36 | return Task.CompletedTask; 37 | } 38 | } 39 | 40 | public static class Disposable 41 | { 42 | public static IDisposable Empty = new EmptyDisposable(); 43 | public static ILock EmptyLock = new EmptyLock(); 44 | } 45 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/FoundatioDiagnostics.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.Metrics; 3 | using System.Reflection; 4 | 5 | namespace Foundatio; 6 | 7 | public static class FoundatioDiagnostics 8 | { 9 | internal static readonly AssemblyName AssemblyName = typeof(FoundatioDiagnostics).Assembly.GetName(); 10 | internal static readonly string AssemblyVersion = typeof(FoundatioDiagnostics).Assembly.GetCustomAttribute()?.InformationalVersion ?? AssemblyName.Version.ToString(); 11 | public static readonly ActivitySource ActivitySource = new(AssemblyName.Name, AssemblyVersion); 12 | public static readonly Meter Meter = new("Foundatio", AssemblyVersion); 13 | } 14 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/IAsyncDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ExceptionServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace Foundatio.Utility; 6 | 7 | public static class Async 8 | { 9 | public static async Task Using(TResource resource, Func> body) 10 | where TResource : IAsyncDisposable 11 | { 12 | Exception exception = null; 13 | var result = default(TReturn); 14 | try 15 | { 16 | result = await body(resource).AnyContext(); 17 | } 18 | catch (Exception ex) 19 | { 20 | exception = ex; 21 | } 22 | 23 | await resource.DisposeAsync().AnyContext(); 24 | if (exception != null) 25 | { 26 | var info = ExceptionDispatchInfo.Capture(exception); 27 | info.Throw(); 28 | } 29 | 30 | return result; 31 | } 32 | 33 | public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable 34 | { 35 | return Using(resource, r => body()); 36 | } 37 | 38 | public static Task Using(TResource resource, Action body) where TResource : IAsyncDisposable 39 | { 40 | return Using(resource, r => 41 | { 42 | body(); 43 | return Task.CompletedTask; 44 | }); 45 | } 46 | 47 | public static Task Using(TResource resource, Func body) where TResource : IAsyncDisposable 48 | { 49 | return Using(resource, async r => 50 | { 51 | await body(resource).AnyContext(); 52 | return Task.CompletedTask; 53 | }); 54 | } 55 | 56 | public static Task Using(TResource resource, Func> body) where TResource : IAsyncDisposable 57 | { 58 | return Using(resource, r => body()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/IAsyncLifetime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | public interface IAsyncLifetime : IAsyncDisposable 7 | { 8 | Task InitializeAsync(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/IHaveLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Abstractions; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | public interface IHaveLogger 7 | { 8 | ILogger Logger { get; } 9 | } 10 | 11 | public static class LoggerExtensions 12 | { 13 | public static ILogger GetLogger(this object target) 14 | { 15 | return target is IHaveLogger accessor ? accessor.Logger ?? NullLogger.Instance : NullLogger.Instance; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/IHaveTimeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | public interface IHaveTimeProvider 6 | { 7 | TimeProvider TimeProvider { get; } 8 | } 9 | 10 | public static class TimeProviderExtensions 11 | { 12 | public static TimeProvider GetTimeProvider(this object target) 13 | { 14 | return target is IHaveTimeProvider accessor ? accessor.TimeProvider ?? TimeProvider.System : TimeProvider.System; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/InstrumentsValues.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | public class InstrumentsValues 7 | where T1 : struct 8 | where T2 : struct 9 | where T3 : struct 10 | { 11 | private readonly object _lock = new(); 12 | private int _readCount; 13 | private T1? _value1; 14 | private T2? _value2; 15 | private T3? _value3; 16 | private readonly Func<(T1, T2, T3)> _readValuesFunc; 17 | private readonly ILogger _logger; 18 | 19 | public InstrumentsValues(Func<(T1, T2, T3)> readValuesFunc, ILogger logger) 20 | { 21 | _readValuesFunc = readValuesFunc; 22 | _logger = logger; 23 | } 24 | 25 | private void EnsureValues() 26 | { 27 | lock (_lock) 28 | { 29 | if (_readCount == 0) 30 | { 31 | _logger.LogDebug("Getting values"); 32 | (_value1, _value2, _value3) = _readValuesFunc(); 33 | } 34 | 35 | // get values every 3 reads 36 | if (_readCount == 2) 37 | _readCount = 0; 38 | else 39 | _readCount++; 40 | } 41 | } 42 | 43 | public T1 GetValue1() 44 | { 45 | EnsureValues(); 46 | 47 | return _value1 ?? default; 48 | } 49 | 50 | public T2 GetValue2() 51 | { 52 | EnsureValues(); 53 | 54 | return _value2 ?? default; 55 | } 56 | 57 | public T3 GetValue3() 58 | { 59 | EnsureValues(); 60 | 61 | return _value3 ?? default; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/MaintenanceBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Logging.Abstractions; 5 | 6 | namespace Foundatio.Utility; 7 | 8 | public class MaintenanceBase : IDisposable 9 | { 10 | private ScheduledTimer _maintenanceTimer; 11 | private readonly ILoggerFactory _loggerFactory; 12 | protected readonly TimeProvider _timeProvider; 13 | protected readonly ILogger _logger; 14 | 15 | public MaintenanceBase(TimeProvider timeProvider, ILoggerFactory loggerFactory) 16 | { 17 | _timeProvider = timeProvider ?? TimeProvider.System; 18 | _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; 19 | _logger = _loggerFactory.CreateLogger(GetType()); 20 | } 21 | 22 | protected void InitializeMaintenance(TimeSpan? dueTime = null, TimeSpan? intervalTime = null) 23 | { 24 | _maintenanceTimer = new ScheduledTimer(DoMaintenanceAsync, dueTime, intervalTime, _timeProvider, _loggerFactory); 25 | } 26 | 27 | protected void ScheduleNextMaintenance(DateTime utcDate) 28 | { 29 | _maintenanceTimer.ScheduleNext(utcDate); 30 | } 31 | 32 | protected virtual Task DoMaintenanceAsync() 33 | { 34 | return Task.FromResult(DateTime.MaxValue); 35 | } 36 | 37 | public virtual void Dispose() 38 | { 39 | _maintenanceTimer?.Dispose(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/OptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Foundatio; 2 | 3 | public interface IOptionsBuilder 4 | { 5 | object Target { get; } 6 | } 7 | 8 | public interface IOptionsBuilder : IOptionsBuilder 9 | { 10 | T Build(); 11 | } 12 | 13 | public static class OptionsBuilderExtensions 14 | { 15 | public static T Target(this IOptionsBuilder builder) 16 | { 17 | return (T)builder.Target; 18 | } 19 | } 20 | 21 | public class OptionsBuilder : IOptionsBuilder where T : class, new() 22 | { 23 | public T Target { get; } = new T(); 24 | object IOptionsBuilder.Target => Target; 25 | 26 | public virtual T Build() 27 | { 28 | return Target; 29 | } 30 | } 31 | 32 | public delegate TBuilder Builder(TBuilder builder) where TBuilder : class, IOptionsBuilder, new(); 33 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/PathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Foundatio.Utility; 5 | 6 | public static class PathHelper 7 | { 8 | private const string DATA_DIRECTORY = "|DataDirectory|"; 9 | 10 | public static string ExpandPath(string path) 11 | { 12 | if (String.IsNullOrEmpty(path)) 13 | return path; 14 | 15 | path = path.Replace('/', Path.DirectorySeparatorChar); 16 | path = path.Replace('\\', Path.DirectorySeparatorChar); 17 | 18 | if (!path.StartsWith(DATA_DIRECTORY, StringComparison.OrdinalIgnoreCase)) 19 | return Path.GetFullPath(path); 20 | 21 | string dataDirectory = GetDataDirectory(); 22 | int length = DATA_DIRECTORY.Length; 23 | if (path.Length <= length) 24 | return dataDirectory; 25 | 26 | string relativePath = path.Substring(length); 27 | char c = relativePath[0]; 28 | 29 | if (c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar) 30 | relativePath = relativePath.Substring(1); 31 | 32 | string fullPath = Path.Combine(dataDirectory ?? String.Empty, relativePath); 33 | fullPath = Path.GetFullPath(fullPath); 34 | 35 | return fullPath; 36 | } 37 | 38 | public static string GetDataDirectory() 39 | { 40 | try 41 | { 42 | string dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string; 43 | if (String.IsNullOrEmpty(dataDirectory)) 44 | dataDirectory = AppContext.BaseDirectory; 45 | 46 | if (!String.IsNullOrEmpty(dataDirectory)) 47 | return Path.GetFullPath(dataDirectory); 48 | } 49 | catch (Exception) 50 | { 51 | return null; 52 | } 53 | 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/SharedOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Foundatio.Serializer; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Foundatio; 6 | 7 | public class SharedOptions 8 | { 9 | public TimeProvider TimeProvider { get; set; } = TimeProvider.System; 10 | public ISerializer Serializer { get; set; } 11 | public ILoggerFactory LoggerFactory { get; set; } 12 | } 13 | 14 | public class SharedOptionsBuilder : OptionsBuilder 15 | where TOption : SharedOptions, new() 16 | where TBuilder : SharedOptionsBuilder 17 | { 18 | public TBuilder TimeProvider(TimeProvider timeProvider) 19 | { 20 | Target.TimeProvider = timeProvider; 21 | return (TBuilder)this; 22 | } 23 | 24 | public TBuilder Serializer(ISerializer serializer) 25 | { 26 | Target.Serializer = serializer; 27 | return (TBuilder)this; 28 | } 29 | 30 | public TBuilder LoggerFactory(ILoggerFactory loggerFactory) 31 | { 32 | Target.LoggerFactory = loggerFactory; 33 | return (TBuilder)this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Foundatio/Utility/TimeUnit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Foundatio.Utility; 4 | 5 | public static class TimeUnit 6 | { 7 | public static TimeSpan Parse(string value) 8 | { 9 | if (String.IsNullOrEmpty(value)) 10 | throw new ArgumentNullException(nameof(value)); 11 | 12 | var time = ParseTime(value); 13 | if (time.HasValue) 14 | return time.Value; 15 | 16 | throw new ArgumentException($"Unable to parse value '{value}' as a valid time value"); 17 | } 18 | 19 | public static bool TryParse(string value, out TimeSpan? time) 20 | { 21 | time = null; 22 | if (String.IsNullOrEmpty(value)) 23 | return false; 24 | 25 | time = ParseTime(value); 26 | return time.HasValue; 27 | } 28 | 29 | private static TimeSpan? ParseTime(string value) 30 | { 31 | // compare using the original value as uppercase M could mean months. 32 | var normalized = value.ToLowerInvariant().Trim(); 33 | if (value.EndsWith("m")) 34 | { 35 | int minutes = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); 36 | return new TimeSpan(0, minutes, 0); 37 | } 38 | 39 | if (normalized.EndsWith("h")) 40 | { 41 | int hours = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); 42 | return new TimeSpan(hours, 0, 0); 43 | } 44 | 45 | if (normalized.EndsWith("d")) 46 | { 47 | int days = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); 48 | return new TimeSpan(days, 0, 0, 0); 49 | } 50 | 51 | if (normalized.EndsWith("nanos")) 52 | { 53 | long nanoseconds = Int64.Parse(normalized.Substring(0, normalized.Length - 5)); 54 | return new TimeSpan((int)Math.Round(nanoseconds / 100d)); 55 | } 56 | 57 | if (normalized.EndsWith("micros")) 58 | { 59 | long microseconds = Int64.Parse(normalized.Substring(0, normalized.Length - 6)); 60 | return new TimeSpan(microseconds * 10); 61 | } 62 | 63 | if (normalized.EndsWith("ms")) 64 | { 65 | int milliseconds = Int32.Parse(normalized.Substring(0, normalized.Length - 2)); 66 | return new TimeSpan(0, 0, 0, 0, milliseconds); 67 | } 68 | 69 | if (normalized.EndsWith("s")) 70 | { 71 | int seconds = Int32.Parse(normalized.Substring(0, normalized.Length - 1)); 72 | return new TimeSpan(0, 0, seconds); 73 | } 74 | 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /start-all-services.ps1: -------------------------------------------------------------------------------- 1 | $parentDirectory = Split-Path -Path $PSScriptRoot -Parent 2 | $directories = Get-ChildItem -Path $parentDirectory -Directory -Filter "Foundatio.*" 3 | 4 | foreach ($directory in $directories) { 5 | $startScriptPath = Join-Path -Path $directory.FullName -ChildPath "start-services.ps1" 6 | $composeFilePath = Join-Path -Path $directory.FullName -ChildPath "docker-compose.yml" 7 | 8 | if (Test-Path -Path $startScriptPath) { 9 | Set-Location -Path $directory.FullName 10 | Write-Host "Starting services in directory using start-services.ps1: $($directory.FullName)" 11 | & $startScriptPath 12 | } 13 | elseif (Test-Path -Path $composeFilePath) { 14 | Set-Location -Path $directory.FullName 15 | Write-Host "Starting services in directory: $($directory.FullName)" 16 | docker compose -f $composeFilePath up --detach 17 | } 18 | } 19 | 20 | Write-Host "Services started successfully." 21 | Set-Location -Path $PSScriptRoot 22 | -------------------------------------------------------------------------------- /stop-all-services.ps1: -------------------------------------------------------------------------------- 1 | $parentDirectory = Split-Path -Path $PSScriptRoot -Parent 2 | $directories = Get-ChildItem -Path $parentDirectory -Directory -Filter "Foundatio.*" 3 | 4 | foreach ($directory in $directories) { 5 | $composeFilePath = Join-Path -Path $directory.FullName -ChildPath "docker-compose.yml" 6 | if (Test-Path -Path $composeFilePath) { 7 | Set-Location -Path $directory.FullName 8 | Write-Host "Stopping services in directory: $($directory.FullName)" 9 | docker compose -f $composeFilePath down --remove-orphans 10 | } 11 | } 12 | 13 | Write-Host "Stopped services" 14 | Set-Location -Path $PSScriptRoot 15 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | False 6 | $(NoWarn);CS1591;NU1701 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Foundatio.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Hosting/TestServerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Extensions.Hosting.Startup; 4 | using Microsoft.AspNetCore.TestHost; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Foundatio.Tests.Hosting; 8 | 9 | public static class TestServerExtensions 10 | { 11 | public static async Task WaitForReadyAsync(this TestServer server, TimeSpan? maxWaitTime = null) 12 | { 13 | var startupContext = server.Host.Services.GetService(); 14 | maxWaitTime ??= TimeSpan.FromSeconds(5); 15 | 16 | var client = server.CreateClient(); 17 | var startTime = DateTime.Now; 18 | do 19 | { 20 | if (startupContext != null && startupContext.IsStartupComplete && startupContext.Result.Success == false) 21 | throw new OperationCanceledException($"Startup action \"{startupContext.Result.FailedActionName}\" failed"); 22 | 23 | var response = await client.GetAsync("/ready"); 24 | if (response.IsSuccessStatusCode) 25 | break; 26 | 27 | if (DateTime.Now.Subtract(startTime) > maxWaitTime) 28 | throw new TimeoutException("Failed waiting for server to be ready"); 29 | 30 | await Task.Delay(TimeSpan.FromMilliseconds(100)); 31 | } while (true); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Jobs/InMemoryJobQueueTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Foundatio.Queues; 4 | 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | namespace Foundatio.Tests.Jobs; 9 | 10 | public class InMemoryJobQueueTests : JobQueueTestsBase 11 | { 12 | public InMemoryJobQueueTests(ITestOutputHelper output) : base(output) { } 13 | 14 | protected override IQueue GetSampleWorkItemQueue(int retries, TimeSpan retryDelay) 15 | { 16 | return new InMemoryQueue(o => o.RetryDelay(retryDelay).Retries(retries).LoggerFactory(Log)); 17 | } 18 | 19 | [Fact] 20 | public override Task CanRunMultipleQueueJobsAsync() 21 | { 22 | return base.CanRunMultipleQueueJobsAsync(); 23 | } 24 | 25 | [Fact] 26 | public override Task CanRunQueueJobWithLockFailAsync() 27 | { 28 | return base.CanRunQueueJobWithLockFailAsync(); 29 | } 30 | 31 | [Fact] 32 | public override Task CanRunQueueJobAsync() 33 | { 34 | return base.CanRunQueueJobAsync(); 35 | } 36 | 37 | [Fact] 38 | public override Task ActivityWillFlowThroughQueueJobAsync() 39 | { 40 | return base.ActivityWillFlowThroughQueueJobAsync(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: Xunit.CollectionBehaviorAttribute(DisableTestParallelization = true, MaxParallelThreads = 1)] 2 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Serializer/CompressedMessagePackSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Serializer; 2 | using Foundatio.TestHarness.Utility; 3 | using MessagePack.Resolvers; 4 | using Microsoft.Extensions.Logging; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | namespace Foundatio.Tests.Serializer; 9 | 10 | public class CompressedMessagePackSerializerTests : SerializerTestsBase 11 | { 12 | public CompressedMessagePackSerializerTests(ITestOutputHelper output) : base(output) { } 13 | 14 | protected override ISerializer GetSerializer() 15 | { 16 | return new MessagePackSerializer(MessagePack.MessagePackSerializerOptions.Standard 17 | .WithCompression(MessagePack.MessagePackCompression.Lz4Block) 18 | .WithResolver(ContractlessStandardResolver.Instance)); 19 | } 20 | 21 | [Fact] 22 | public override void CanRoundTripBytes() 23 | { 24 | base.CanRoundTripBytes(); 25 | } 26 | 27 | [Fact] 28 | public override void CanRoundTripString() 29 | { 30 | base.CanRoundTripString(); 31 | } 32 | 33 | [Fact] 34 | public override void CanHandlePrimitiveTypes() 35 | { 36 | base.CanHandlePrimitiveTypes(); 37 | } 38 | 39 | [Fact(Skip = "Skip benchmarks for now")] 40 | public virtual void Benchmark() 41 | { 42 | var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); 43 | _logger.LogInformation(summary.ToJson()); 44 | } 45 | } 46 | 47 | public class CompressedMessagePackSerializerBenchmark : SerializerBenchmarkBase 48 | { 49 | protected override ISerializer GetSerializer() 50 | { 51 | return new MessagePackSerializer(MessagePack.MessagePackSerializerOptions.Standard 52 | .WithCompression(MessagePack.MessagePackCompression.Lz4Block) 53 | .WithResolver(ContractlessStandardResolver.Instance)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Serializer/JsonNetSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Serializer; 2 | using Foundatio.TestHarness.Utility; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Foundatio.Tests.Serializer; 8 | 9 | public class JsonNetSerializerTests : SerializerTestsBase 10 | { 11 | public JsonNetSerializerTests(ITestOutputHelper output) : base(output) { } 12 | 13 | protected override ISerializer GetSerializer() 14 | { 15 | return new JsonNetSerializer(); 16 | } 17 | 18 | [Fact] 19 | public override void CanRoundTripBytes() 20 | { 21 | base.CanRoundTripBytes(); 22 | } 23 | 24 | [Fact] 25 | public override void CanRoundTripString() 26 | { 27 | base.CanRoundTripString(); 28 | } 29 | 30 | [Fact] 31 | public override void CanHandlePrimitiveTypes() 32 | { 33 | base.CanHandlePrimitiveTypes(); 34 | } 35 | 36 | [Fact(Skip = "Skip benchmarks for now")] 37 | public virtual void Benchmark() 38 | { 39 | var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); 40 | _logger.LogInformation(summary.ToJson()); 41 | } 42 | } 43 | 44 | public class JsonNetSerializerBenchmark : SerializerBenchmarkBase 45 | { 46 | protected override ISerializer GetSerializer() 47 | { 48 | return new JsonNetSerializer(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Serializer/MessagePackSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Serializer; 2 | using Foundatio.TestHarness.Utility; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Foundatio.Tests.Serializer; 8 | 9 | public class MessagePackSerializerTests : SerializerTestsBase 10 | { 11 | public MessagePackSerializerTests(ITestOutputHelper output) : base(output) { } 12 | 13 | protected override ISerializer GetSerializer() 14 | { 15 | return new MessagePackSerializer(); 16 | } 17 | 18 | [Fact] 19 | public override void CanRoundTripBytes() 20 | { 21 | base.CanRoundTripBytes(); 22 | } 23 | 24 | [Fact] 25 | public override void CanRoundTripString() 26 | { 27 | base.CanRoundTripString(); 28 | } 29 | 30 | [Fact] 31 | public override void CanHandlePrimitiveTypes() 32 | { 33 | base.CanHandlePrimitiveTypes(); 34 | } 35 | 36 | [Fact(Skip = "Skip benchmarks for now")] 37 | public virtual void Benchmark() 38 | { 39 | var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); 40 | _logger.LogInformation(summary.ToJson()); 41 | } 42 | } 43 | 44 | public class MessagePackSerializerBenchmark : SerializerBenchmarkBase 45 | { 46 | protected override ISerializer GetSerializer() 47 | { 48 | return new MessagePackSerializer(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Serializer/SystemTextJsonSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Serializer; 2 | using Foundatio.TestHarness.Utility; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Foundatio.Tests.Serializer; 8 | 9 | public class SystemTextJsonSerializerTests : SerializerTestsBase 10 | { 11 | public SystemTextJsonSerializerTests(ITestOutputHelper output) : base(output) { } 12 | 13 | protected override ISerializer GetSerializer() 14 | { 15 | return new SystemTextJsonSerializer(); 16 | } 17 | 18 | [Fact] 19 | public override void CanRoundTripBytes() 20 | { 21 | base.CanRoundTripBytes(); 22 | } 23 | 24 | [Fact] 25 | public override void CanRoundTripString() 26 | { 27 | base.CanRoundTripString(); 28 | } 29 | 30 | [Fact] 31 | public override void CanHandlePrimitiveTypes() 32 | { 33 | base.CanHandlePrimitiveTypes(); 34 | } 35 | 36 | [Fact(Skip = "Skip benchmarks for now")] 37 | public virtual void Benchmark() 38 | { 39 | var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); 40 | _logger.LogInformation(summary.ToJson()); 41 | } 42 | } 43 | 44 | public class SystemTextJsonSerializerBenchmark : SerializerBenchmarkBase 45 | { 46 | protected override ISerializer GetSerializer() 47 | { 48 | return new SystemTextJsonSerializer(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Serializer/Utf8JsonSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using Foundatio.Serializer; 2 | using Foundatio.TestHarness.Utility; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Foundatio.Tests.Serializer; 8 | 9 | public class Utf8JsonSerializerTests : SerializerTestsBase 10 | { 11 | public Utf8JsonSerializerTests(ITestOutputHelper output) : base(output) { } 12 | 13 | protected override ISerializer GetSerializer() 14 | { 15 | return new Utf8JsonSerializer(); 16 | } 17 | 18 | [Fact] 19 | public override void CanRoundTripBytes() 20 | { 21 | base.CanRoundTripBytes(); 22 | } 23 | 24 | [Fact] 25 | public override void CanRoundTripString() 26 | { 27 | base.CanRoundTripString(); 28 | } 29 | 30 | [Fact(Skip = "Skip benchmarks for now")] 31 | public virtual void Benchmark() 32 | { 33 | var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run(); 34 | _logger.LogInformation(summary.ToJson()); 35 | } 36 | } 37 | 38 | public class Utf8JsonSerializerBenchmark : SerializerBenchmarkBase 39 | { 40 | protected override ISerializer GetSerializer() 41 | { 42 | return new Utf8JsonSerializer(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Foundatio.Tests/Utility/ConnectionStringParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Foundatio.Utility; 3 | using Xunit; 4 | 5 | namespace Foundatio.Tests.Utility; 6 | 7 | public class ConfigurationTests 8 | { 9 | [Fact] 10 | public void CanParseConnectionString() 11 | { 12 | const string connectionString = "provider=azurestorage;DefaultEndpointsProtocol=https;AccountName=test;AccountKey=nx4TKwaaaaaaaaaa8t51oPyOErc/4N0TOjrMy6aaaaaabDMbFiK+Gf5rLr6XnU1aaaaaqiX2Yik7tvLcwp4lw==;EndpointSuffix=core.windows.net"; 13 | var data = connectionString.ParseConnectionString(); 14 | Assert.Equal(5, data.Count); 15 | Assert.Equal("azurestorage", data["provider"]); 16 | Assert.Equal("https", data["DefaultEndpointsProtocol"]); 17 | Assert.Equal("test", data["AccountName"]); 18 | Assert.Equal("nx4TKwaaaaaaaaaa8t51oPyOErc/4N0TOjrMy6aaaaaabDMbFiK+Gf5rLr6XnU1aaaaaqiX2Yik7tvLcwp4lw==", data["AccountKey"]); 19 | Assert.Equal("core.windows.net", data["EndpointSuffix"]); 20 | 21 | Assert.Equal(connectionString, data.BuildConnectionString()); 22 | } 23 | 24 | [Fact] 25 | public void WillThrowOnInvalidConnectionStrings() 26 | { 27 | string connectionString = "provider = azurestorage; = ; DefaultEndpointsProtocol = https ;"; 28 | Assert.Throws(() => connectionString.ParseConnectionString()); 29 | 30 | connectionString = "http://localhost:9200"; 31 | Assert.Throws(() => connectionString.ParseConnectionString()); 32 | } 33 | 34 | [Fact] 35 | public void CanParseQuotedConnectionString() 36 | { 37 | const string connectionString = "Blah=\"Hey \"\"now\"\" stuff\""; 38 | var data = connectionString.ParseConnectionString(); 39 | Assert.Single(data); 40 | Assert.Equal("Hey \"now\" stuff", data["blah"]); 41 | 42 | Assert.Equal(connectionString, data.BuildConnectionString()); 43 | } 44 | 45 | [Fact] 46 | public void CanParseSimpleConnectionString() 47 | { 48 | const string connectionString = "localhost,6379"; 49 | var data = connectionString.ParseConnectionString(defaultKey: "server"); 50 | Assert.Single(data); 51 | Assert.Equal("localhost,6379", data["server"]); 52 | 53 | Assert.Equal("server=localhost,6379", data.BuildConnectionString()); 54 | } 55 | 56 | [Fact] 57 | public void CanParseComplexQuotedConnectionString() 58 | { 59 | const string connectionString = "Blah=\"foo1=\"\"my value\"\";foo2 =\"\"my value\"\";\""; 60 | var data = connectionString.ParseConnectionString(); 61 | Assert.Single(data); 62 | Assert.Equal("foo1=\"my value\";foo2 =\"my value\";", data["Blah"]); 63 | 64 | Assert.Equal(connectionString, data.BuildConnectionString()); 65 | } 66 | } 67 | --------------------------------------------------------------------------------