8 Commits

Author SHA1 Message Date
Austin Winstanley 7706a2e799 upgraded to Visual Studio 2017 and pushed version 2.2.0 to Nuget 2017-03-17 01:47:44 -05:00
Austin Winstanley 1f58085aa6 Merge pull request #21 from mjohnson0580/master
Updated to latest stable versions of Hangfire (1.6.9) and RavenDB (3.5.3)
2017-03-15 19:57:40 -05:00
Michael Johnson e284ad7cf4 Updated to latest stable versions of Hangfire (1.6.9) and RavenDB (3.5.3) 2017-03-11 16:57:26 +11:00
Austin Winstanley ad39bb3f49 Merge pull request #19 from mjohnson0580/master
Added Hangfire.Raven.Tests project. Nice work @mjohnson0580  and @xinix00
2017-03-07 15:19:12 -06:00
Michael Johnson 1002c28ddd Added Hangfire.Raven.Tests project
- Refactored Repository and RavenStorage to allow injection of a TestRepository (using an in-memory database for unit tests)
- Made several fixes to comply with unit tests (notably RavenDistributedLock and RavenJobQueue)
- Merged fixes from xinix00 pull request #11 (https://github.com/RefreshingIO/hangfire-ravendb/pull/11)
2017-03-07 18:38:18 +11:00
Austin Winstanley 4107954dec merge to fix #14 2016-12-08 21:16:09 -06:00
Austin Winstanley 382e502c6b Merge pull request #16 from mjohnson0580/master
Revert back to using InvocationData
2016-12-08 20:59:39 -06:00
Michael Johnson 7aa06fe914 Revert back to using InvocationData, as the following functionality has been lost, but is already handled by Hangfire
- Overloaded method calls e.g. Console.WriteLine(String) vs Console.WriteLine(Int32) Console.WriteLine(Boolean)
- Generic method calls
- Nullable parameters in method calls
- Special handling of CancellationToken arguments during serialization/deserialization
2016-12-09 12:52:17 +11:00
49 changed files with 5687 additions and 22464 deletions
+56 -50
View File
@@ -1,50 +1,56 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{8CF2D2F6-BEFE-47FE-B141-0017FC6F97D8}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
global.json = global.json
README.md = README.md
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Hangfire.Raven", "src\Hangfire.Raven\Hangfire.Raven.xproj", "{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF9BFE2C-0386-4EC4-83EC-D7E315F56735}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{54466714-23AC-4D87-90EB-EAD235F84F71}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Hangfire.Raven.Examples.Console", "examples\Hangfire.Raven.Examples.Console\Hangfire.Raven.Examples.Console.xproj", "{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Hangfire.Raven.Examples.AspNetCore", "examples\Hangfire.Raven.Examples.AspNetCore\Hangfire.Raven.Examples.AspNetCore.xproj", "{82A516D0-287C-4DD8-A153-2B3AFFB88241}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Release|Any CPU.Build.0 = Release|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Release|Any CPU.Build.0 = Release|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5} = {EF9BFE2C-0386-4EC4-83EC-D7E315F56735}
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2} = {54466714-23AC-4D87-90EB-EAD235F84F71}
{82A516D0-287C-4DD8-A153-2B3AFFB88241} = {54466714-23AC-4D87-90EB-EAD235F84F71}
EndGlobalSection
EndGlobal
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{8CF2D2F6-BEFE-47FE-B141-0017FC6F97D8}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF9BFE2C-0386-4EC4-83EC-D7E315F56735}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{54466714-23AC-4D87-90EB-EAD235F84F71}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Raven", "src\Hangfire.Raven\Hangfire.Raven.csproj", "{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Raven.Examples.Console", "examples\Hangfire.Raven.Examples.Console\Hangfire.Raven.Examples.Console.csproj", "{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Raven.Examples.AspNetCore", "examples\Hangfire.Raven.Examples.AspNetCore\Hangfire.Raven.Examples.AspNetCore.csproj", "{82A516D0-287C-4DD8-A153-2B3AFFB88241}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Raven.Tests", "src\Hangfire.Raven.Tests\Hangfire.Raven.Tests.csproj", "{2F2C50B7-C556-4A60-81AC-019DAF261556}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5}.Release|Any CPU.Build.0 = Release|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2}.Release|Any CPU.Build.0 = Release|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82A516D0-287C-4DD8-A153-2B3AFFB88241}.Release|Any CPU.Build.0 = Release|Any CPU
{2F2C50B7-C556-4A60-81AC-019DAF261556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F2C50B7-C556-4A60-81AC-019DAF261556}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F2C50B7-C556-4A60-81AC-019DAF261556}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F2C50B7-C556-4A60-81AC-019DAF261556}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BA18F6BF-5613-4B9D-A57A-C05C51148EC5} = {EF9BFE2C-0386-4EC4-83EC-D7E315F56735}
{3955F2F1-99AF-4CC0-88F5-C9B69757A1B2} = {54466714-23AC-4D87-90EB-EAD235F84F71}
{82A516D0-287C-4DD8-A153-2B3AFFB88241} = {54466714-23AC-4D87-90EB-EAD235F84F71}
{2F2C50B7-C556-4A60-81AC-019DAF261556} = {EF9BFE2C-0386-4EC4-83EC-D7E315F56735}
EndGlobalSection
EndGlobal
@@ -0,0 +1,62 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Description>RavenDB job storage for Hangfire - AspNetCore Example</Description>
<Copyright>Copyright @ 2016 Cady, LLC</Copyright>
<AssemblyTitle>Hangfire.Raven.Examples.AspNetCore</AssemblyTitle>
<VersionPrefix>2.1.0</VersionPrefix>
<Authors>Cady &lt;contact@cady.io&gt;</Authors>
<TargetFramework>netcoreapp1.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Hangfire.Raven.Examples.AspNetCore</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Hangfire.Raven.Examples.AspNetCore</PackageId>
<PackageTags>Hangfire;AspNet;MVC;OWIN;RavenDB;Long-Running;Background;Fire-And-Forget;Delayed;Recurring;Tasks;Jobs;Scheduler;Threading;Queues</PackageTags>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/7880472?v=3&amp;s=200</PackageIconUrl>
<PackageProjectUrl>https://github.com/cady-io/hangfire-ravendb</PackageProjectUrl>
<PackageLicenseUrl>http://www.gnu.org/licenses/lgpl-3.0.html</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/cady-io/hangfire-ravendb</RepositoryUrl>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<None Update="wwwroot\**\*">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hangfire.Raven\Hangfire.Raven.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.9" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.0.1" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<Exec Command="bower install" />
<Exec Command="dotnet bundle" />
</Target>
<ItemGroup>
<DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.2.301" />
</ItemGroup>
</Project>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>82a516d0-287c-4dd8-a153-2b3affb88241</ProjectGuid>
<RootNamespace>Hangfire.Raven.Examples.AspNetCore</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
@@ -62,8 +62,7 @@ namespace Hangfire.Raven.Examples.AspNetCore
app.UseHangfireDashboard();
// Run once
// BUG: https://github.com/cady-io/hangfire-ravendb/issues/14
//BackgroundJob.Enqueue(() => System.Console.WriteLine("Background Job: Hello, world!"));
BackgroundJob.Enqueue(() => System.Console.WriteLine("Background Job: Hello, world!"));
BackgroundJob.Enqueue(() => Test());
@@ -1,105 +0,0 @@
{
"title": "Hangfire.Raven.Examples.AspNetCore",
"version": "2.1.0",
"description": "RavenDB job storage for Hangfire - AspNetCore Example",
"copyright": "Copyright @ 2016 Cady, LLC",
"authors": [
"Cady <contact@cady.io>"
],
"buildOptions": {
"allowUnsafe": true,
"debugType": "portable",
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"packOptions": {
"tags": [
"Hangfire",
"AspNet",
"MVC",
"OWIN",
"RavenDB",
"Long-Running",
"Background",
"Fire-And-Forget",
"Delayed",
"Recurring",
"Tasks",
"Jobs",
"Scheduler",
"Threading",
"Queues"
],
"iconUrl": "https://avatars2.githubusercontent.com/u/7880472?v=3&s=200",
"projectUrl": "https://github.com/cady-io/hangfire-ravendb",
"licenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.html",
"owners": [
"Cady <contact@cady.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/cady-io/hangfire-ravendb"
},
"summary": "RavenDB job storage for Hangfire - AspNetCore Example"
},
"dependencies": {
"Hangfire.AspNetCore": "1.6.7",
"Hangfire.Raven": "2.1.0",
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0"
},
"tools": {
"BundlerMinifier.Core": "2.0.238",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"**/*.cshtml",
"appsettings.json",
"web.config"
]
},
"scripts": {
"prepublish": [ "bower install", "dotnet bundle" ],
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>RavenDB job storage for Hangfire - Console Example</Description>
<Copyright>Copyright @ 2016 Cady, LLC</Copyright>
<AssemblyTitle>Hangfire.Raven.Examples.Console</AssemblyTitle>
<VersionPrefix>2.1.0</VersionPrefix>
<Authors>Cady &lt;contact@cady.io&gt;</Authors>
<TargetFramework>net452</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Hangfire.Raven.Examples.Console</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Hangfire.Raven.Examples.Console</PackageId>
<PackageTags>Hangfire;AspNet;MVC;OWIN;RavenDB;Long-Running;Background;Fire-And-Forget;Delayed;Recurring;Tasks;Jobs;Scheduler;Threading;Queues</PackageTags>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/7880472?v=3&amp;s=200</PackageIconUrl>
<PackageProjectUrl>https://github.com/cady-io/hangfire-ravendb</PackageProjectUrl>
<PackageLicenseUrl>http://www.gnu.org/licenses/lgpl-3.0.html</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/cady-io/hangfire-ravendb</RepositoryUrl>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hangfire.Raven\Hangfire.Raven.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>3955f2f1-99af-4cc0-88f5-c9b69757a1b2</ProjectGuid>
<RootNamespace>Hangfire.Raven.Examples.Console</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
@@ -29,9 +29,7 @@ namespace Hangfire.Raven.Examples.Console
var client = new BackgroundJobServer();
// Run once
// BUG: https://github.com/cady-io/hangfire-ravendb/issues/14
//BackgroundJob.Enqueue(() => System.Console.WriteLine("Background Job: Hello, world!"));
BackgroundJob.Enqueue(() => System.Console.WriteLine("Background Job: Hello, world!"));
BackgroundJob.Enqueue(() => Test());
@@ -1,58 +0,0 @@
{
"title": "Hangfire.Raven.Examples.Console",
"version": "2.1.0",
"description": "RavenDB job storage for Hangfire - Console Example",
"copyright": "Copyright @ 2016 Cady, LLC",
"authors": [
"Cady <contact@cady.io>"
],
"buildOptions": {
"allowUnsafe": true,
"debugType": "portable",
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"packOptions": {
"tags": [
"Hangfire",
"AspNet",
"MVC",
"OWIN",
"RavenDB",
"Long-Running",
"Background",
"Fire-And-Forget",
"Delayed",
"Recurring",
"Tasks",
"Jobs",
"Scheduler",
"Threading",
"Queues"
],
"include": [
"app.config"
],
"iconUrl": "https://avatars2.githubusercontent.com/u/7880472?v=3&s=200",
"projectUrl": "https://github.com/cady-io/hangfire-ravendb",
"licenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.html",
"owners": [
"Cady <contact@cady.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/cady-io/hangfire-ravendb"
},
"summary": "RavenDB job storage for Hangfire - Console Example"
},
"runtimes": {
"win10-x64": {}
},
"frameworks": {
"net452": { }
},
"dependencies": {
"Hangfire.Raven": "2.1.0",
"System.ComponentModel.Primitives": "4.3.0"
}
}
@@ -1,286 +0,0 @@
{
"locked": false,
"version": 2,
"targets": {
".NETFramework,Version=v4.5.2": {
"Hangfire.Core/1.6.4": {
"type": "package",
"dependencies": {
"Newtonsoft.Json": "5.0.0",
"Owin": "1.0.0"
},
"compile": {
"lib/net45/Hangfire.Core.dll": {}
},
"runtime": {
"lib/net45/Hangfire.Core.dll": {}
}
},
"Newtonsoft.Json/9.0.1": {
"type": "package",
"compile": {
"lib/net45/Newtonsoft.Json.dll": {}
},
"runtime": {
"lib/net45/Newtonsoft.Json.dll": {}
}
},
"Owin/1.0.0": {
"type": "package",
"compile": {
"lib/net40/Owin.dll": {}
},
"runtime": {
"lib/net40/Owin.dll": {}
}
},
"RavenDB.Client/3.5.0-rc-35169": {
"type": "package",
"frameworkAssemblies": [
"System.ComponentModel.Composition"
],
"compile": {
"lib/net45/Raven.Abstractions.dll": {},
"lib/net45/Raven.Client.Lightweight.dll": {}
},
"runtime": {
"lib/net45/Raven.Abstractions.dll": {},
"lib/net45/Raven.Client.Lightweight.dll": {}
}
},
"System.ComponentModel.Primitives/4.3.0": {
"type": "package",
"frameworkAssemblies": [
"System",
"mscorlib"
],
"compile": {
"ref/net45/System.ComponentModel.Primitives.dll": {}
},
"runtime": {
"lib/net45/System.ComponentModel.Primitives.dll": {}
}
},
"Hangfire.Raven/2.1.0": {
"type": "project",
"framework": ".NETFramework,Version=v4.5",
"dependencies": {
"Hangfire.Core": "1.6.4",
"Newtonsoft.Json": "9.0.1",
"RavenDB.Client": "3.5.0-rc-35169"
},
"frameworkAssemblies": [
"System.Data",
"System.Reflection"
],
"compile": {
"net45/Hangfire.Raven.dll": {}
},
"runtime": {
"net45/Hangfire.Raven.dll": {}
}
}
},
".NETFramework,Version=v4.5.2/win10-x64": {
"Hangfire.Core/1.6.4": {
"type": "package",
"dependencies": {
"Newtonsoft.Json": "5.0.0",
"Owin": "1.0.0"
},
"compile": {
"lib/net45/Hangfire.Core.dll": {}
},
"runtime": {
"lib/net45/Hangfire.Core.dll": {}
}
},
"Newtonsoft.Json/9.0.1": {
"type": "package",
"compile": {
"lib/net45/Newtonsoft.Json.dll": {}
},
"runtime": {
"lib/net45/Newtonsoft.Json.dll": {}
}
},
"Owin/1.0.0": {
"type": "package",
"compile": {
"lib/net40/Owin.dll": {}
},
"runtime": {
"lib/net40/Owin.dll": {}
}
},
"RavenDB.Client/3.5.0-rc-35169": {
"type": "package",
"frameworkAssemblies": [
"System.ComponentModel.Composition"
],
"compile": {
"lib/net45/Raven.Abstractions.dll": {},
"lib/net45/Raven.Client.Lightweight.dll": {}
},
"runtime": {
"lib/net45/Raven.Abstractions.dll": {},
"lib/net45/Raven.Client.Lightweight.dll": {}
}
},
"System.ComponentModel.Primitives/4.3.0": {
"type": "package",
"frameworkAssemblies": [
"System",
"mscorlib"
],
"compile": {
"ref/net45/System.ComponentModel.Primitives.dll": {}
},
"runtime": {
"lib/net45/System.ComponentModel.Primitives.dll": {}
}
},
"Hangfire.Raven/2.1.0": {
"type": "project",
"framework": ".NETFramework,Version=v4.5",
"dependencies": {
"Hangfire.Core": "1.6.4",
"Newtonsoft.Json": "9.0.1",
"RavenDB.Client": "3.5.0-rc-35169"
},
"frameworkAssemblies": [
"System.Data",
"System.Reflection"
],
"compile": {
"net45/Hangfire.Raven.dll": {}
},
"runtime": {
"net45/Hangfire.Raven.dll": {}
}
}
}
},
"libraries": {
"Hangfire.Core/1.6.4": {
"sha512": "QM+WNZ/B7t3BzOBumLb2NU9aoh76hb/Jdt9pV95+HSHZbPsvms7Jk9kUC9PLopdh9gpUL7L6dmnhEnpQJBBs+Q==",
"type": "package",
"path": "Hangfire.Core/1.6.4",
"files": [
"Hangfire.Core.1.6.4.nupkg.sha512",
"Hangfire.Core.nuspec",
"lib/net45/Hangfire.Core.dll",
"lib/net45/Hangfire.Core.xml",
"lib/netstandard1.3/Hangfire.Core.dll",
"lib/netstandard1.3/Hangfire.Core.xml"
]
},
"Newtonsoft.Json/9.0.1": {
"sha512": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==",
"type": "package",
"path": "Newtonsoft.Json/9.0.1",
"files": [
"Newtonsoft.Json.9.0.1.nupkg.sha512",
"Newtonsoft.Json.nuspec",
"lib/net20/Newtonsoft.Json.dll",
"lib/net20/Newtonsoft.Json.xml",
"lib/net35/Newtonsoft.Json.dll",
"lib/net35/Newtonsoft.Json.xml",
"lib/net40/Newtonsoft.Json.dll",
"lib/net40/Newtonsoft.Json.xml",
"lib/net45/Newtonsoft.Json.dll",
"lib/net45/Newtonsoft.Json.xml",
"lib/netstandard1.0/Newtonsoft.Json.dll",
"lib/netstandard1.0/Newtonsoft.Json.xml",
"lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll",
"lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.xml",
"lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll",
"lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.xml",
"tools/install.ps1"
]
},
"Owin/1.0.0": {
"sha512": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==",
"type": "package",
"path": "Owin/1.0.0",
"files": [
"Owin.1.0.0.nupkg.sha512",
"Owin.nuspec",
"lib/net40/Owin.dll"
]
},
"RavenDB.Client/3.5.0-rc-35169": {
"sha512": "xP23wh0U4h8/MmuiFEYhCB+y9AA87DvsJOifmfynPrYYV9jOjFLLWa55iOmy/RNWakDso7td0m3AcfGygULXQw==",
"type": "package",
"path": "RavenDB.Client/3.5.0-rc-35169",
"files": [
"RavenDB.Client.3.5.0-rc-35169.nupkg.sha512",
"RavenDB.Client.nuspec",
"lib/net45/Raven.Abstractions.dll",
"lib/net45/Raven.Abstractions.pdb",
"lib/net45/Raven.Abstractions.xml",
"lib/net45/Raven.Client.Lightweight.XML",
"lib/net45/Raven.Client.Lightweight.dll",
"lib/net45/Raven.Client.Lightweight.pdb",
"lib/netstandard1.6/Raven.Abstractions.dll",
"lib/netstandard1.6/Raven.Abstractions.pdb",
"lib/netstandard1.6/Raven.Client.Lightweight.deps.json",
"lib/netstandard1.6/Raven.Client.Lightweight.dll",
"lib/netstandard1.6/Raven.Client.Lightweight.pdb",
"lib/netstandard1.6/Sparrow.dll",
"lib/netstandard1.6/Sparrow.pdb"
]
},
"System.ComponentModel.Primitives/4.3.0": {
"sha512": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
"type": "package",
"path": "System.ComponentModel.Primitives/4.3.0",
"files": [
"System.ComponentModel.Primitives.4.3.0.nupkg.sha512",
"System.ComponentModel.Primitives.nuspec",
"ThirdPartyNotices.txt",
"dotnet_library_license.txt",
"lib/MonoAndroid10/_._",
"lib/MonoTouch10/_._",
"lib/net45/System.ComponentModel.Primitives.dll",
"lib/netstandard1.0/System.ComponentModel.Primitives.dll",
"lib/xamarinios10/_._",
"lib/xamarinmac20/_._",
"lib/xamarintvos10/_._",
"lib/xamarinwatchos10/_._",
"ref/MonoAndroid10/_._",
"ref/MonoTouch10/_._",
"ref/net45/System.ComponentModel.Primitives.dll",
"ref/netstandard1.0/System.ComponentModel.Primitives.dll",
"ref/netstandard1.0/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/de/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/es/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/fr/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/it/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/ja/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/ko/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/ru/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/zh-hans/System.ComponentModel.Primitives.xml",
"ref/netstandard1.0/zh-hant/System.ComponentModel.Primitives.xml",
"ref/xamarinios10/_._",
"ref/xamarinmac20/_._",
"ref/xamarintvos10/_._",
"ref/xamarinwatchos10/_._"
]
},
"Hangfire.Raven/2.1.0": {
"type": "project",
"path": "../../src/Hangfire.Raven/project.json",
"msbuildProject": "../../src/Hangfire.Raven/Hangfire.Raven.xproj"
}
},
"projectFileDependencyGroups": {
"": [
"Hangfire.Raven >= 2.1.0",
"System.ComponentModel.Primitives >= 4.3.0"
],
".NETFramework,Version=v4.5.2": []
},
"tools": {},
"projectFileToolGroups": {}
}
-3
View File
@@ -1,3 +0,0 @@
{
"projects": [ "examples", "src" ]
}
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net452</TargetFramework>
<AssemblyName>Hangfire.Raven.Tests</AssemblyName>
<PackageId>Hangfire.Raven.Tests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Authors>Refreshing Collective &lt;contact@refreshing.io&gt;</Authors>
<Version>2.2.0</Version>
<Copyright>Copyright @ 2017 Refreshing Collective LLC</Copyright>
<PackageLicenseUrl>http://www.gnu.org/licenses/lgpl-3.0.html</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/RefreshingIO/hangfire-ravendb</PackageProjectUrl>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/7880472?v=3&amp;s=200</PackageIconUrl>
<RepositoryUrl>https://github.com/RefreshingIO/hangfire-ravendb</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>Hangfire;AspNet;MVC;OWIN;RavenDB;Long-Running;Background;Fire-And-Forget;Delayed;Recurring;Tasks;Jobs;Scheduler;Threading;Queues</PackageTags>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Hangfire.Raven\Hangfire.Raven.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" />
<PackageReference Include="Moq" Version="4.7.1" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="RavenDB.Database" Version="3.5.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
@@ -0,0 +1,99 @@
using System;
using System.Linq;
using Moq;
using Xunit;
using Hangfire.Raven.JobQueues;
namespace Hangfire.Raven.Tests
{
public class PersistentJobQueueProviderCollectionFacts
{
private static readonly string[] Queues = { "default", "critical" };
private readonly Mock<IPersistentJobQueueProvider> _defaultProvider;
private readonly Mock<IPersistentJobQueueProvider> _provider;
public PersistentJobQueueProviderCollectionFacts()
{
_defaultProvider = new Mock<IPersistentJobQueueProvider>();
_provider = new Mock<IPersistentJobQueueProvider>();
}
[Fact]
public void Ctor_ThrowsAnException_WhenDefaultProviderIsNull()
{
Assert.Throws<ArgumentNullException>(
() => new PersistentJobQueueProviderCollection(null));
}
[Fact]
public void Enumeration_IncludesTheDefaultProvider()
{
var collection = CreateCollection();
var result = collection.ToArray();
Assert.Equal(1, result.Length);
Assert.Same(_defaultProvider.Object, result[0]);
}
[Fact]
public void GetProvider_ReturnsTheDefaultProvider_WhenProviderCanNotBeResolvedByQueue()
{
var collection = CreateCollection();
var provider = collection.GetProvider("queue");
Assert.Same(_defaultProvider.Object, provider);
}
[Fact]
public void Add_ThrowsAnException_WhenProviderIsNull()
{
var collection = CreateCollection();
var exception = Assert.Throws<ArgumentNullException>(
() => collection.Add(null, Queues));
Assert.Equal("provider", exception.ParamName);
}
[Fact]
public void Add_ThrowsAnException_WhenQueuesCollectionIsNull()
{
var collection = CreateCollection();
var exception = Assert.Throws<ArgumentNullException>(
() => collection.Add(_provider.Object, null));
Assert.Equal("queues", exception.ParamName);
}
[Fact]
public void Enumeration_ContainsAddedProvider()
{
var collection = CreateCollection();
collection.Add(_provider.Object, Queues);
Assert.Contains(_provider.Object, collection);
}
[Fact]
public void GetProvider_CanBeResolved_ByAnyQueue()
{
var collection = CreateCollection();
collection.Add(_provider.Object, Queues);
var provider1 = collection.GetProvider("default");
var provider2 = collection.GetProvider("critical");
Assert.NotSame(_defaultProvider.Object, provider1);
Assert.Same(provider1, provider2);
}
private PersistentJobQueueProviderCollection CreateCollection()
{
return new PersistentJobQueueProviderCollection(_defaultProvider.Object);
}
}
}
@@ -0,0 +1,22 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Refreshing Collective")]
[assembly: AssemblyProduct("Hangfire.Raven.Tests")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2f2c50b7-c556-4a60-81ac-019daf261556")]
[assembly: CollectionBehavior(DisableTestParallelization = true)]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,179 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Hangfire.Storage;
using Xunit;
using Hangfire.Raven.DistributedLocks;
using Hangfire.Raven.Storage;
using Hangfire.Raven.Entities;
using Raven.Client.Linq;
using System.Linq;
namespace Hangfire.Raven.Tests
{
public class RavenDistributedLockFacts
{
[Fact]
public void Ctor_ThrowsAnException_WhenResourceIsNull()
{
UseStorage(storage =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new RavenDistributedLock(storage, null, TimeSpan.Zero, new RavenStorageOptions()));
Assert.Equal("resource", exception.ParamName);
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new RavenDistributedLock(null, "resource1", TimeSpan.Zero, new RavenStorageOptions()));
Assert.Equal("storage", exception.ParamName);
}
[Fact]
public void Ctor_SetLock_WhenResourceIsNotLocked()
{
UseStorage(storage =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
using (var session = storage.Repository.OpenSession())
{
var locksCount = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(1, locksCount);
}
});
}
[Fact]
public void Ctor_SetReleaseLock_WhenResourceIsNotLocked()
{
UseStorage(storage =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
using (var session = storage.Repository.OpenSession())
{
var locksCount = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(1, locksCount);
}
using (var session = storage.Repository.OpenSession())
{
var locksCountAfter = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(0, locksCountAfter);
}
});
}
[Fact]
public void Ctor_AcquireLockWithinSameThread_WhenResourceIsLocked()
{
UseStorage(storage =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
{
int locksCount;
using (var session = storage.Repository.OpenSession())
{
locksCount = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(1, locksCount);
}
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
using (var session = storage.Repository.OpenSession())
{
locksCount = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(1, locksCount);
}
}
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenResourceIsLocked()
{
UseStorage(storage =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
using (var session = storage.Repository.OpenSession())
{
var locksCount = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").Count();
Assert.Equal(1, locksCount);
Task.Run(() =>
{
Assert.Throws<DistributedLockTimeoutException>(() =>
new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()));
}).Wait();
}
});
}
[Fact]
public void Ctor_WaitForLock_SignaledAtLockRelease()
{
UseStorage(storage =>
{
Task.Run(() =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions()))
{
Thread.Sleep(TimeSpan.FromSeconds(5));
}
});
// Wait just a bit to make sure the above lock is acuired
Thread.Sleep(TimeSpan.FromSeconds(1));
// Record when we try to aquire the lock
var startTime = DateTime.Now;
using (new RavenDistributedLock(storage, "resource1", TimeSpan.FromSeconds(30), new RavenStorageOptions()))
{
Assert.InRange(DateTime.Now - startTime, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsIsNull()
{
UseStorage(storage =>
{
var exception = Assert.Throws<ArgumentNullException>(() =>
new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, null));
Assert.Equal("options", exception.ParamName);
});
}
[Fact]
public void Ctor_SetLockExpireAtWorks_WhenResourceIsNotLocked()
{
UseStorage(storage =>
{
using (new RavenDistributedLock(storage, "resource1", TimeSpan.Zero, new RavenStorageOptions { DistributedLockLifetime = TimeSpan.FromSeconds(3) }))
using (var session = storage.Repository.OpenSession())
{
DateTime initialExpireAt = DateTime.UtcNow;
Thread.Sleep(TimeSpan.FromSeconds(5));
DistributedLock lockEntry = session.Query<DistributedLock>().Where(_ => _.Resource == "resource1").FirstOrDefault();
Assert.NotNull(lockEntry);
var expireAt = session.Advanced.GetExpire(lockEntry);
Assert.True(expireAt > initialExpireAt);
}
});
}
private static void UseStorage(Action<RavenStorage> action)
{
using (var repository = new TestRepository())
{
var storage = new RavenStorage(repository);
action(storage);
}
}
}
}
@@ -0,0 +1,166 @@
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
using System;
using System.Linq;
using Xunit;
namespace Hangfire.Raven.Tests
{
public class RavenFetchedJobFacts
{
private const string JobId = "id";
private const string Queue = "queue";
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
UseStorage(storage =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new RavenFetchedJob(null, new JobQueue()));
Assert.Equal("storage", exception.ParamName);
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenJobQueueIsNull()
{
UseStorage(storage =>
{
var exception = Assert.Throws<ArgumentNullException>(() => new RavenFetchedJob(storage, null));
Assert.Equal("jobQueue", exception.ParamName);
});
}
[Fact]
public void Ctor_CorrectlySets_AllInstanceProperties()
{
UseStorage(storage =>
{
var fetchedJob = new RavenFetchedJob(storage, new JobQueue { Id = "1", JobId = JobId, Queue = Queue });
Assert.Equal("1", fetchedJob.Id);
Assert.Equal(JobId, fetchedJob.JobId);
Assert.Equal(Queue, fetchedJob.Queue);
});
}
[Fact]
public void RemoveFromQueue_ReallyDeletesTheJobFromTheQueue()
{
UseStorage(storage =>
{
// Arrange
var id = CreateJobQueueRecord(storage, "1", "default");
var processingJob = new RavenFetchedJob(storage, new JobQueue { Id = id, JobId = "1", Queue = "default" });
// Act
processingJob.RemoveFromQueue();
// Assert
using (var session = storage.Repository.OpenSession())
{
var count = session.Query<JobQueue>().Count();
Assert.Equal(0, count);
}
});
}
[Fact]
public void RemoveFromQueue_DoesNotDelete_UnrelatedJobs()
{
UseStorage(storage =>
{
// Arrange
CreateJobQueueRecord(storage, "1", "default");
CreateJobQueueRecord(storage, "1", "critical");
CreateJobQueueRecord(storage, "2", "default");
var fetchedJob = new RavenFetchedJob(storage, new JobQueue { Id = "999", JobId = "1", Queue = "default" });
// Act
fetchedJob.RemoveFromQueue();
// Assert
using (var session = storage.Repository.OpenSession())
{
var count = session.Query<JobQueue>().Count();
Assert.Equal(3, count);
}
});
}
[Fact]
public void Requeue_SetsFetchedAtValueToNull()
{
UseStorage(storage =>
{
// Arrange
var id = CreateJobQueueRecord(storage, "1", "default");
var processingJob = new RavenFetchedJob(storage, new JobQueue { Id = id, JobId = "1", Queue = "default" });
// Act
processingJob.Requeue();
// Assert
using (var session = storage.Repository.OpenSession())
{
var record = session.Query<JobQueue>().Single();
Assert.Null(record.FetchedAt);
}
});
}
[Fact]
public void Dispose_SetsFetchedAtValueToNull_IfThereWereNoCallsToComplete()
{
UseStorage(storage =>
{
// Arrange
var id = CreateJobQueueRecord(storage, "1", "default");
var processingJob = new RavenFetchedJob(storage, new JobQueue { Id = id, JobId = "1", Queue = "default" });
// Act
processingJob.Dispose();
// Assert
using (var session = storage.Repository.OpenSession())
{
var record = session.Query<JobQueue>().Single();
Assert.Null(record.FetchedAt);
}
});
}
private static string CreateJobQueueRecord(RavenStorage storage, string jobId, string queue)
{
var jobQueue = new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), queue, jobId),
JobId = jobId,
Queue = queue,
FetchedAt = DateTime.UtcNow
};
using (var session = storage.Repository.OpenSession())
{
session.Store(jobQueue);
session.SaveChanges();
}
return jobQueue.Id;
}
private static void UseStorage(Action<RavenStorage> action)
{
using (var repository = new TestRepository())
{
var storage = new RavenStorage(repository);
action(storage);
}
}
}
}
@@ -0,0 +1,394 @@
using Hangfire.Raven.Entities;
using Hangfire.Raven.JobQueues;
using Hangfire.Raven.Storage;
using System;
using System.Linq;
using System.Threading;
using Xunit;
namespace Hangfire.Raven.Tests
{
public class RavenJobQueueFacts
{
private static readonly string[] DefaultQueues = { "default" };
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new RavenJobQueue(null, new RavenStorageOptions()));
Assert.Equal("storage", exception.ParamName);
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
{
UseStorage(storage =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new RavenJobQueue(storage, null));
Assert.Equal("options", exception.ParamName);
});
}
[Fact]
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsNull()
{
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
var exception = Assert.Throws<ArgumentNullException>(
() => queue.Dequeue(null, CreateTimingOutCancellationToken()));
Assert.Equal("queues", exception.ParamName);
});
}
[Fact]
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty()
{
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
var exception = Assert.Throws<ArgumentException>(
() => queue.Dequeue(new string[0], CreateTimingOutCancellationToken()));
Assert.Equal("queues", exception.ParamName);
});
}
[Fact]
public void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning()
{
UseStorage(storage =>
{
var cts = new CancellationTokenSource();
cts.Cancel();
var queue = CreateJobQueue(storage);
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
});
}
[Fact]
public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs()
{
UseStorage(storage =>
{
var cts = new CancellationTokenSource(200);
var queue = CreateJobQueue(storage);
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
});
}
[Fact]
public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue()
{
// Arrange
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
var jobQueue = new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", "1"),
JobId = "1",
Queue = "default"
};
using (var session = storage.Repository.OpenSession())
{
session.Store(jobQueue);
session.SaveChanges();
}
// Act
RavenFetchedJob payload = (RavenFetchedJob)queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
var id = jobQueue.Id;
Assert.Equal(id, payload.Id);
Assert.Equal("1", payload.JobId);
Assert.Equal("default", payload.Queue);
});
}
[Fact]
public void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue()
{
// Arrange
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
var jobId = Guid.NewGuid().ToString();
var job = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), jobId),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
var jobQueue = new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", jobId),
JobId = jobId,
Queue = "default"
};
using (var session = storage.Repository.OpenSession())
{
session.Store(job);
session.Store(jobQueue);
session.SaveChanges();
}
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
Assert.NotNull(payload);
using (var session = storage.Repository.OpenSession())
{
var fetchedAt = session.Query<JobQueue>().Where(_ => _.JobId == payload.JobId).FirstOrDefault().FetchedAt;
Assert.NotNull(fetchedAt);
Assert.True(fetchedAt > DateTime.UtcNow.AddMinutes(-1));
}
});
}
[Fact]
public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue()
{
// Arrange
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
var jobId = Guid.NewGuid().ToString();
var job = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), jobId),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
var jobQueue = new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", jobId),
JobId = jobId,
Queue = "default",
FetchedAt = DateTime.UtcNow.AddDays(-1)
};
using (var session = storage.Repository.OpenSession())
{
session.Store(job);
session.Store(jobQueue);
session.SaveChanges();
}
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
Assert.NotEmpty(payload.JobId);
});
}
[Fact]
public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob()
{
UseStorage(storage =>
{
// Arrange
var queue = CreateJobQueue(storage);
using (var session = storage.Repository.OpenSession())
{
var job1Id = Guid.NewGuid().ToString();
var job1 = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), job1Id),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
session.Store(job1);
var job2Id = Guid.NewGuid().ToString();
var job2 = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), job2Id),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
session.Store(job2);
session.Store(new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", job1Id),
JobId = job1Id,
Queue = "default"
});
session.Store(new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", job2Id),
JobId = job2Id,
Queue = "default"
});
session.SaveChanges();
}
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
using (var session = storage.Repository.OpenSession())
{
var otherJobFetchedAt = session.Query<JobQueue>().Where(_ => _.JobId != payload.JobId).FirstOrDefault().FetchedAt;
Assert.Null(otherJobFetchedAt);
}
});
}
[Fact]
public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues()
{
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
using (var session = storage.Repository.OpenSession())
{
var job1Id = Guid.NewGuid().ToString();
var job1 = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), job1Id),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
session.Store(job1);
session.Store(new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "critical", job1Id),
JobId = job1Id,
Queue = "critical"
});
session.SaveChanges();
}
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken()));
});
}
[Fact]
public void Dequeue_ShouldFetchJobs_FromMultipleQueuesBasedOnQueuePriority()
{
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
using (var session = storage.Repository.OpenSession())
{
var criticalJobId = Guid.NewGuid().ToString();
var criticalJob = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), criticalJobId),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
session.Store(criticalJob);
var defaultJobId = Guid.NewGuid().ToString();
var defaultJob = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), defaultJobId),
InvocationData = null,
CreatedAt = DateTime.UtcNow
};
session.Store(defaultJob);
session.Store(new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "default", defaultJobId),
JobId = defaultJobId,
Queue = "default"
});
session.Store(new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), "critical", criticalJobId),
JobId = criticalJobId,
Queue = "critical"
});
session.SaveChanges();
}
var critical = (RavenFetchedJob)queue.Dequeue(
new[] { "critical", "default" },
CreateTimingOutCancellationToken());
Assert.NotNull(critical.JobId);
Assert.Equal("critical", critical.Queue);
var @default = (RavenFetchedJob)queue.Dequeue(
new[] { "critical", "default" },
CreateTimingOutCancellationToken());
Assert.NotNull(@default.JobId);
Assert.Equal("default", @default.Queue);
});
}
[Fact]
public void Enqueue_AddsAJobToTheQueue()
{
UseStorage(storage =>
{
var queue = CreateJobQueue(storage);
queue.Enqueue("default", "1");
using (var session = storage.Repository.OpenSession())
{
var record = session.Query<JobQueue>().Single();
Assert.Equal("1", record.JobId.ToString());
Assert.Equal("default", record.Queue);
Assert.Null(record.FetchedAt);
}
});
}
private static CancellationToken CreateTimingOutCancellationToken()
{
var source = new CancellationTokenSource(TimeSpan.FromSeconds(10));
return source.Token;
}
private static RavenJobQueue CreateJobQueue(RavenStorage storage)
{
return new RavenJobQueue(storage, new RavenStorageOptions());
}
private static void UseStorage(Action<RavenStorage> action)
{
using (var repository = new TestRepository())
{
var storage = new RavenStorage(repository);
action(storage);
}
}
}
}
@@ -0,0 +1,323 @@
using Hangfire.Raven.Entities;
using Hangfire.Raven.JobQueues;
using Hangfire.Raven.Storage;
using Hangfire.Storage;
using System;
using System.Linq;
using Xunit;
namespace Hangfire.Raven.Tests
{
public class RavenJobQueueMonitoringApiFacts
{
private const string QueueName1 = "queueName1";
private const string QueueName2 = "queueName2";
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new RavenJobQueueMonitoringApi(null));
Assert.Equal("storage", exception.ParamName);
}
[Fact]
public void GetQueues_ShouldReturnEmpty_WhenNoQueuesExist()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var queues = ravenJobQueueMonitoringApi.GetQueues();
Assert.Empty(queues);
});
}
[Fact]
public void GetQueues_ShouldReturnOneQueue_WhenOneQueueExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
CreateJobQueue(storage, QueueName1, false);
var queues = ravenJobQueueMonitoringApi.GetQueues().ToList();
Assert.Equal(1, queues.Count);
Assert.Equal(QueueName1, queues.First());
});
}
[Fact]
public void GetQueues_ShouldReturnTwoUniqueQueues_WhenThreeNonUniqueQueuesExist()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
CreateJobQueue(storage, QueueName1, false);
CreateJobQueue(storage, QueueName1, false);
CreateJobQueue(storage, QueueName2, false);
var queues = ravenJobQueueMonitoringApi.GetQueues().ToList();
Assert.Equal(2, queues.Count);
Assert.True(queues.Contains(QueueName1));
Assert.True(queues.Contains(QueueName2));
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnEmpty_WheNoQueuesExist()
{
UseStorage(storage =>
{
var mongoJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var enqueuedJobIds = mongoJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 10);
Assert.Empty(enqueuedJobIds);
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnEmpty_WhenOneJobWithAFetchedStateExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
CreateJobQueue(storage, QueueName1, true);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 10).ToList();
Assert.Empty(enqueuedJobIds);
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnOneJobId_WhenOneJobExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, false);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(1, enqueuedJobIds.Count);
Assert.Equal(jobQueue.JobId, enqueuedJobIds.First());
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnThreeJobIds_WhenThreeJobsExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, false);
var jobQueue2 = CreateJobQueue(storage, QueueName1, false);
var jobQueue3 = CreateJobQueue(storage, QueueName1, false);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(3, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue3.JobId));
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnTwoJobIds_WhenThreeJobsExistsButOnlyTwoInRequestedQueue()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, false);
var jobQueue2 = CreateJobQueue(storage, QueueName1, false);
CreateJobQueue(storage, QueueName2, false);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(2, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
});
}
[Fact]
public void GetEnqueuedJobIds_ShouldReturnTwoJobIds_WhenThreeJobsExistsButLimitIsSet()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, false);
var jobQueue2 = CreateJobQueue(storage, QueueName1, false);
CreateJobQueue(storage, QueueName1, false);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetEnqueuedJobIds(QueueName1, 0, 2).ToList();
Assert.Equal(2, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnEmpty_WheNoQueuesExist()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 10);
Assert.Empty(enqueuedJobIds);
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnEmpty_WhenOneJobWithNonFetchedStateExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
CreateJobQueue(storage, QueueName1, false);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 10).ToList();
Assert.Empty(enqueuedJobIds);
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnOneJobId_WhenOneJobExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, true);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(1, enqueuedJobIds.Count);
Assert.Equal(jobQueue.JobId, enqueuedJobIds.First());
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnThreeJobIds_WhenThreeJobsExists()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, true);
var jobQueue2 = CreateJobQueue(storage, QueueName1, true);
var jobQueue3 = CreateJobQueue(storage, QueueName1, true);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(3, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue3.JobId));
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnTwoJobIds_WhenThreeJobsExistsButOnlyTwoInRequestedQueue()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, true);
var jobQueue2 = CreateJobQueue(storage, QueueName1, true);
CreateJobQueue(storage, QueueName2, true);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 10).ToList();
Assert.Equal(2, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
});
}
[Fact]
public void GetFetchedJobIds_ShouldReturnTwoJobIds_WhenThreeJobsExistsButLimitIsSet()
{
UseStorage(storage =>
{
var ravenJobQueueMonitoringApi = CreateRavenJobQueueMonitoringApi(storage);
var jobQueue = CreateJobQueue(storage, QueueName1, true);
var jobQueue2 = CreateJobQueue(storage, QueueName1, true);
CreateJobQueue(storage, QueueName1, true);
var enqueuedJobIds = ravenJobQueueMonitoringApi.GetFetchedJobIds(QueueName1, 0, 2).ToList();
Assert.Equal(2, enqueuedJobIds.Count);
Assert.True(enqueuedJobIds.Contains(jobQueue.JobId));
Assert.True(enqueuedJobIds.Contains(jobQueue2.JobId));
});
}
private static JobQueue CreateJobQueue(RavenStorage storage, string queue, bool isFetched)
{
var jobId = Guid.NewGuid().ToString();
var job = new RavenJob
{
Id = storage.Repository.GetId(typeof(RavenJob), jobId),
CreatedAt = DateTime.UtcNow,
StateData = new StateData()
};
var jobQueue = new JobQueue
{
Id = storage.Repository.GetId(typeof(JobQueue), queue, jobId),
Queue = queue,
JobId = jobId
};
if (isFetched)
{
jobQueue.FetchedAt = DateTime.UtcNow.AddDays(-1);
}
using (var session = storage.Repository.OpenSession())
{
session.Store(job);
session.Store(jobQueue);
session.SaveChanges();
}
return jobQueue;
}
private static RavenJobQueueMonitoringApi CreateRavenJobQueueMonitoringApi(RavenStorage storage)
{
return new RavenJobQueueMonitoringApi(storage);
}
private static void UseStorage(Action<RavenStorage> action)
{
using (var repository = new TestRepository())
{
var storage = new RavenStorage(repository);
action(storage);
}
}
}
}
@@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using Moq;
using Xunit;
using Hangfire.Raven.JobQueues;
using Hangfire.Raven.Storage;
using Hangfire.Raven.Entities;
using System.Linq;
namespace Hangfire.Raven.Tests
{
public class RavenMonitoringApiFacts
{
private const string DefaultQueue = "default";
private const string FetchedStateName = "Fetched";
private const int From = 0;
private const int PerPage = 5;
private readonly Mock<IPersistentJobQueue> _queue;
private readonly Mock<IPersistentJobQueueProvider> _provider;
private readonly Mock<IPersistentJobQueueMonitoringApi> _persistentJobQueueMonitoringApi;
private readonly PersistentJobQueueProviderCollection _providers;
public RavenMonitoringApiFacts()
{
_queue = new Mock<IPersistentJobQueue>();
_persistentJobQueueMonitoringApi = new Mock<IPersistentJobQueueMonitoringApi>();
_provider = new Mock<IPersistentJobQueueProvider>();
_provider.Setup(x => x.GetJobQueue()).Returns(_queue.Object);
_provider.Setup(x => x.GetJobQueueMonitoringApi())
.Returns(_persistentJobQueueMonitoringApi.Object);
_providers = new PersistentJobQueueProviderCollection(_provider.Object);
}
[Fact]
public void GetStatistics_ReturnsZero_WhenNoJobsExist()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var result = monitoringApi.GetStatistics();
Assert.Equal(0, result.Enqueued);
Assert.Equal(0, result.Failed);
Assert.Equal(0, result.Processing);
Assert.Equal(0, result.Scheduled);
});
}
[Fact]
public void GetStatistics_ReturnsExpectedCounts_WhenJobsExist()
{
UseMonitoringApi((repository, monitoringApi) =>
{
CreateJobInState(repository, "1", EnqueuedState.StateName);
CreateJobInState(repository, "2", EnqueuedState.StateName);
CreateJobInState(repository, "4", FailedState.StateName);
CreateJobInState(repository, "5", ProcessingState.StateName);
CreateJobInState(repository, "6", ScheduledState.StateName);
CreateJobInState(repository, "7", ScheduledState.StateName);
var result = monitoringApi.GetStatistics();
Assert.Equal(2, result.Enqueued);
Assert.Equal(1, result.Failed);
Assert.Equal(1, result.Processing);
Assert.Equal(2, result.Scheduled);
});
}
[Fact]
public void JobDetails_ReturnsNull_WhenThereIsNoSuchJob()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var result = monitoringApi.JobDetails("547527");
Assert.Null(result);
});
}
[Fact]
public void JobDetails_ReturnsResult_WhenJobExists()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var job1 = CreateJobInState(repository, "1", EnqueuedState.StateName);
var result = monitoringApi.JobDetails(job1.Id.Split(new[] { '/' }, 2)[1]);
Assert.NotNull(result);
Assert.NotNull(result.Job);
Assert.Equal("Arguments", result.Job.Args[0]);
Assert.True(DateTime.UtcNow.AddMinutes(-1) < result.CreatedAt);
Assert.True(result.CreatedAt < DateTime.UtcNow.AddMinutes(1));
});
}
[Fact]
public void EnqueuedJobs_ReturnsEmpty_WhenThereIsNoJobs()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var jobIds = new List<string>();
_persistentJobQueueMonitoringApi.Setup(x => x
.GetEnqueuedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds);
var resultList = monitoringApi.EnqueuedJobs(DefaultQueue, From, PerPage);
Assert.Empty(resultList);
});
}
[Fact]
public void EnqueuedJobs_ReturnsSingleJob_WhenOneJobExistsThatIsNotFetched()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var unfetchedJob = CreateJobInState(repository, "1", EnqueuedState.StateName);
var jobIds = new List<string> { unfetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetEnqueuedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.EnqueuedJobs(DefaultQueue, From, PerPage);
Assert.Equal(1, resultList.Count);
});
}
[Fact]
public void EnqueuedJobs_ReturnsEmpty_WhenOneJobExistsThatIsFetched()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var fetchedJob = CreateJobInState(repository, "1", FetchedStateName);
var jobIds = new List<string> { fetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetEnqueuedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.EnqueuedJobs(DefaultQueue, From, PerPage);
Assert.Empty(resultList);
});
}
[Fact]
public void EnqueuedJobs_ReturnsUnfetchedJobsOnly_WhenMultipleJobsExistsInFetchedAndUnfetchedStates()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var unfetchedJob = CreateJobInState(repository, "1", EnqueuedState.StateName);
var unfetchedJob2 = CreateJobInState(repository, "2", EnqueuedState.StateName);
var fetchedJob = CreateJobInState(repository, "3", FetchedStateName);
var jobIds = new List<string> { unfetchedJob.Id, unfetchedJob2.Id, fetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetEnqueuedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.EnqueuedJobs(DefaultQueue, From, PerPage);
Assert.Equal(2, resultList.Count);
});
}
[Fact]
public void FetchedJobs_ReturnsEmpty_WhenThereIsNoJobs()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var jobIds = new List<string>();
_persistentJobQueueMonitoringApi.Setup(x => x
.GetFetchedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds);
var resultList = monitoringApi.FetchedJobs(DefaultQueue, From, PerPage);
Assert.Empty(resultList);
});
}
[Fact]
public void FetchedJobs_ReturnsSingleJob_WhenOneJobExistsThatIsFetched()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var fetchedJob = CreateJobInState(repository, "1", FetchedStateName);
var jobIds = new List<string> { fetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetFetchedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.FetchedJobs(DefaultQueue, From, PerPage);
Assert.Equal(1, resultList.Count);
});
}
[Fact]
public void FetchedJobs_ReturnsEmpty_WhenOneJobExistsThatIsNotFetched()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var unfetchedJob = CreateJobInState(repository, "1", EnqueuedState.StateName);
var jobIds = new List<string> { unfetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetFetchedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.FetchedJobs(DefaultQueue, From, PerPage);
Assert.Empty(resultList);
});
}
[Fact]
public void FetchedJobs_ReturnsFetchedJobsOnly_WhenMultipleJobsExistsInFetchedAndUnfetchedStates()
{
UseMonitoringApi((repository, monitoringApi) =>
{
var fetchedJob = CreateJobInState(repository, "1", FetchedStateName);
var fetchedJob2 = CreateJobInState(repository, "2", FetchedStateName);
var unfetchedJob = CreateJobInState(repository, "3", EnqueuedState.StateName);
var jobIds = new List<string> { fetchedJob.Id, fetchedJob2.Id, unfetchedJob.Id };
_persistentJobQueueMonitoringApi.Setup(x => x
.GetFetchedJobIds(DefaultQueue, From, PerPage))
.Returns(jobIds.Select(x => x.Split(new[] { '/' }, 2)[1]));
var resultList = monitoringApi.FetchedJobs(DefaultQueue, From, PerPage);
Assert.Equal(2, resultList.Count);
});
}
public static void SampleMethod(string arg)
{
Debug.WriteLine(arg);
}
private void UseMonitoringApi(Action<IRepository, RavenStorageMonitoringApi> action)
{
using (var repository = new TestRepository())
{
var storage = new Mock<RavenStorage>(repository);
storage.Setup(x => x.QueueProviders).Returns(_providers);
action(repository, new RavenStorageMonitoringApi(storage.Object));
}
}
private RavenJob CreateJobInState(IRepository repository, string jobId, string stateName)
{
var job = Job.FromExpression(() => SampleMethod("Arguments"));
var ravenJob = new RavenJob
{
Id = repository.GetId(typeof(RavenJob), jobId),
InvocationData = InvocationData.Serialize(job),
CreatedAt = DateTime.UtcNow,
StateData = new StateData
{
Name = stateName,
Data =
stateName == EnqueuedState.StateName
? new Dictionary<string, string> { ["EnqueuedAt"] = $"{DateTime.UtcNow:o}" }
: new Dictionary<string, string>(),
}
};
var jobQueue = new JobQueue
{
FetchedAt = null,
Id = repository.GetId(typeof(JobQueue), DefaultQueue, jobId),
JobId = jobId,
Queue = DefaultQueue
};
if (stateName == FetchedStateName)
{
jobQueue.FetchedAt = DateTime.UtcNow;
}
using (var session = repository.OpenSession())
{
session.Store(ravenJob);
session.Store(jobQueue);
session.SaveChanges();
}
return ravenJob;
}
}
}
@@ -0,0 +1,59 @@
using System;
using Hangfire.Storage;
using Xunit;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.Tests
{
public class RavenStorageFacts
{
[Fact]
public void Ctor_ThrowsAnException_WhenRepositoryIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new RavenStorage((IRepository)null, new RavenStorageOptions()));
Assert.Equal("repository", exception.ParamName);
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
{
using (var repository = new TestRepository())
{
var exception = Assert.Throws<ArgumentNullException>(() => new RavenStorage(repository, null));
Assert.Equal("options", exception.ParamName);
}
}
[Fact]
public void GetMonitoringApi_ReturnsNonNullInstance()
{
UseStorage(storage =>
{
IMonitoringApi api = storage.GetMonitoringApi();
Assert.NotNull(api);
});
}
[Fact]
public void GetConnection_ReturnsNonNullInstance()
{
UseStorage(storage =>
{
using (IStorageConnection connection = storage.GetConnection())
{
Assert.NotNull(connection);
}
});
}
private static void UseStorage(Action<RavenStorage> action)
{
using (var repository = new TestRepository())
{
action(new RavenStorage(repository));
}
}
}
}
@@ -0,0 +1,90 @@
using Hangfire.Raven.Storage;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Hangfire.Raven.Tests
{
public class RavenStorageOptionsFacts
{
[Fact]
public void Ctor_SetsTheDefaultOptions()
{
RavenStorageOptions options = new RavenStorageOptions();
Assert.True(options.InvisibilityTimeout > TimeSpan.Zero);
}
[Fact]
public void Ctor_SetsTheDefaultOptions_ShouldGenerateClientId()
{
var options = new RavenStorageOptions();
Assert.False(String.IsNullOrWhiteSpace(options.ClientId));
}
[Fact]
public void Ctor_SetsTheDefaultOptions_ShouldGenerateUniqueClientId()
{
var options1 = new RavenStorageOptions();
var options2 = new RavenStorageOptions();
var options3 = new RavenStorageOptions();
IEnumerable<string> result = new[] { options1.ClientId, options2.ClientId, options3.ClientId }.Distinct();
Assert.Equal(3, result.Count());
}
[Fact]
public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsEqualToZero()
{
var options = new RavenStorageOptions();
Assert.Throws<ArgumentException>(
() => options.QueuePollInterval = TimeSpan.Zero);
}
[Fact]
public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsNegative()
{
var options = new RavenStorageOptions();
Assert.Throws<ArgumentException>(
() => options.QueuePollInterval = TimeSpan.FromSeconds(-1));
}
[Fact]
public void Set_QueuePollInterval_SetsTheValue()
{
var options = new RavenStorageOptions
{
QueuePollInterval = TimeSpan.FromSeconds(1)
};
Assert.Equal(TimeSpan.FromSeconds(1), options.QueuePollInterval);
}
[Fact]
public void Set_DistributedLockLifetime_ShouldThrowAnException_WhenGivenValueIsEqualToZero()
{
var options = new RavenStorageOptions();
Assert.Throws<ArgumentException>(
() => options.DistributedLockLifetime = TimeSpan.Zero);
}
[Fact]
public void Set_DistributedLockLifetime_ShouldThrowAnException_WhenGivenValueIsNegative()
{
var options = new RavenStorageOptions();
Assert.Throws<ArgumentException>(
() => options.DistributedLockLifetime = TimeSpan.FromSeconds(-1));
}
[Fact]
public void Set_DistributedLockLifetime_SetsTheValue()
{
var options = new RavenStorageOptions
{
DistributedLockLifetime = TimeSpan.FromSeconds(1)
};
Assert.Equal(TimeSpan.FromSeconds(1), options.DistributedLockLifetime);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Raven.Abstractions.Data;
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Client.Embedded;
using Hangfire.Raven.Listeners;
namespace Hangfire.Raven.Tests
{
public class TestRepository : IRepository
{
private readonly EmbeddableDocumentStore _documentStore;
public TestRepository()
{
_documentStore = new EmbeddableDocumentStore
{
RunInMemory = true,
DefaultDatabase = "Hangfire-Raven-Tests",
DataDirectory = @"~\Databases\Hangfire-Raven-Tests"
};
_documentStore.Listeners.RegisterListener(new NoStaleQueriesListener());
_documentStore.Listeners.RegisterListener(new TakeNewestConflictResolutionListener());
_documentStore.Initialize();
new RavenDocumentsByEntityName().Execute(_documentStore.DatabaseCommands, _documentStore.Conventions);
}
public void Create()
{
}
public void Destroy()
{
}
public void Dispose()
{
_documentStore.Dispose();
}
public IDisposable DocumentChange(Type documentType, Action<DocumentChangeNotification> action)
{
return _documentStore.Changes().ForDocumentsStartingWith(GetId(documentType, ""))
.Subscribe(new RepositoryObserver<DocumentChangeNotification>(action));
}
public IDisposable DocumentChange(Type documentType, string suffix, Action<DocumentChangeNotification> action)
{
return _documentStore.Changes().ForDocumentsStartingWith(GetId(documentType, string.Format("{0}/", suffix)))
.Subscribe(new RepositoryObserver<DocumentChangeNotification>(action));
}
public void ExecuteIndexes(List<AbstractIndexCreationTask> indexes)
{
_documentStore.ExecuteIndexes(indexes);
}
public FacetResults GetFacets(string index, IndexQuery query, List<Facet> facets)
{
return _documentStore.DatabaseCommands.GetFacets(index, query, facets);
}
public string GetId(Type type, params string[] id)
{
return _documentStore.Conventions.FindFullDocumentKeyFromNonStringIdentifier(string.Join("/", id), type, false);
}
public IAsyncDocumentSession OpenAsyncSession()
{
return _documentStore.OpenAsyncSession();
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
}
-54
View File
@@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
#if !NET45
using Microsoft.Extensions.DependencyModel;
#endif
namespace Hangfire.Raven
{
/// <summary>
/// Helper class for getting assemblies
/// </summary>
public static class Assemblies
{
/// <summary>
/// Get all loaded assemblies
/// </summary>
/// <returns></returns>
public static IEnumerable<Assembly> Get()
{
#if NET45
return AppDomain.CurrentDomain.GetAssemblies().Where(a => a != null);
#else
return (from compilationLibrary in DependencyContext.Default.CompileLibraries
where compilationLibrary.Name.Contains(typeof(Assemblies).Namespace)
select Assembly.Load(new AssemblyName(compilationLibrary.Name)))
.ToList();
#endif
}
/// <summary>
/// Get class by name within an assembly
/// </summary>
/// <param name="assembly">Assembly</param>
/// <param name="type">Name of type</param>
/// <returns></returns>
public static Type GetClass(Assembly assembly, string type)
{
return (from t in assembly.GetTypes()
where t.FullName == type //&& !t.IsAbstract && t.IsClass
select t).FirstOrDefault();
}
public static string GetAssemblyName(MemberInfo value)
{
#if NET45
return value.DeclaringType.Assembly.GetName().Name;
#else
return value.DeclaringType.GetTypeInfo().Assembly.GetName().Name;
#endif
}
}
}
@@ -1,116 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Hangfire.Annotations;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.DistributedLocks
{
public class RavenDistributedLock : IDisposable
{
private const int CommandTimeoutAdditionSeconds = 1;
private readonly object _lock = new object();
private RavenStorage _storage;
private string _resource;
private DistributedLock _distributedLock;
private Timer _heartbeatTimer;
private TimeSpan _timeout;
private readonly List<string> _skipLocks = new List<string>()
{
"HangFire/job:"
};
public RavenDistributedLock([NotNull] RavenStorage storage, [NotNull] string resource, TimeSpan timeout)
{
storage.ThrowIfNull("storage");
resource.ThrowIfNull("resource");
_timeout = timeout;
_storage = storage;
_resource = resource;
// -- Skip some locks
if (!_skipLocks.Any(a => _resource.StartsWith(a)))
Lock();
}
public void Dispose()
{
Release();
}
private void Lock()
{
using (var session = _storage.Repository.OpenSession()) {
_distributedLock = new DistributedLock() {
ClientId = _storage.Options.ClientId,
Resource = _resource
};
session.Store(_distributedLock);
session.Advanced.AddExpire(_distributedLock, DateTime.UtcNow + _timeout);
try {
// Blocking session!
session.Advanced.UseOptimisticConcurrency = true;
session.SaveChanges();
} catch (Exception e) {
_distributedLock = null;
throw new RavenDistributedLockException("Lock already given.", e);
}
}
Heartbeat();
}
private void Release()
{
lock (_lock) {
if (_distributedLock != null) {
// Non blocking session!
try {
using (var session = _storage.Repository.OpenSession()) {
session.Delete(_distributedLock.Id);
session.SaveChanges();
}
} catch {
Console.WriteLine("Unable to delete lock: {0}", _resource);
}
_distributedLock = null;
}
// Stop timer
if (_heartbeatTimer != null) {
_heartbeatTimer.Dispose();
_heartbeatTimer = null;
}
}
}
private void Heartbeat()
{
Console.WriteLine(".Starting heartbeat for resource: {0}", _resource);
TimeSpan timerInterval = TimeSpan.FromMilliseconds(_timeout.TotalMilliseconds / 3);
_heartbeatTimer = new Timer(state => {
try {
Console.WriteLine("..Heartbeat for resource {0}", _resource);
using (var session = _storage.Repository.OpenSession()) {
var distributedLock = session.Load<DistributedLock>(_distributedLock.Id);
session.Advanced.AddExpire(distributedLock, DateTime.UtcNow + _timeout);
session.SaveChanges();
}
} catch (Exception ex) {
Console.WriteLine("...Unable to update heartbeat on the resource '{0}'", ex, _resource);
Release();
}
}, null, timerInterval, timerInterval);
}
}
using System;
using System.Collections.Generic;
using System.Threading;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
using Hangfire.Logging;
using Hangfire.Storage;
using Raven.Abstractions.Exceptions;
namespace Hangfire.Raven.DistributedLocks
{
public class RavenDistributedLock : IDisposable
{
private static readonly ILog Logger = LogProvider.For<RavenDistributedLock>();
private static readonly ThreadLocal<Dictionary<string, int>> AcquiredLocks
= new ThreadLocal<Dictionary<string, int>>(() => new Dictionary<string, int>());
private RavenStorage _storage;
private string _resource;
private readonly RavenStorageOptions _options;
private DistributedLock _distributedLock;
private Timer _heartbeatTimer;
private bool _completed;
private readonly object _lockObject = new object();
private string EventWaitHandleName => $@"{GetType().FullName}.{_resource}";
public RavenDistributedLock(RavenStorage storage, string resource, TimeSpan timeout, RavenStorageOptions options)
{
storage.ThrowIfNull("storage");
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentNullException(nameof(resource));
}
if (timeout.TotalSeconds > int.MaxValue)
{
throw new ArgumentException($"The timeout specified is too large. Please supply a timeout equal to or less than {int.MaxValue} seconds", nameof(timeout));
}
options.ThrowIfNull("options");
_storage = storage;
_resource = resource;
_options = options;
if (!AcquiredLocks.Value.ContainsKey(_resource) || AcquiredLocks.Value[_resource] == 0)
{
Acquire(timeout);
AcquiredLocks.Value[_resource] = 1;
StartHeartBeat();
}
else
{
AcquiredLocks.Value[_resource]++;
}
}
public void Dispose()
{
if (_completed)
{
return;
}
_completed = true;
if (!AcquiredLocks.Value.ContainsKey(_resource))
{
return;
}
AcquiredLocks.Value[_resource]--;
if (AcquiredLocks.Value[_resource] > 0)
{
return;
}
lock (_lockObject)
{
AcquiredLocks.Value.Remove(_resource);
if (_heartbeatTimer != null)
{
_heartbeatTimer.Dispose();
_heartbeatTimer = null;
}
Release();
}
}
private void Acquire(TimeSpan timeout)
{
try
{
var isLockAcquired = false;
var now = DateTime.Now;
var lockTimeoutTime = now.Add(timeout);
while (!isLockAcquired && (lockTimeoutTime >= now))
{
using (var session = _storage.Repository.OpenSession())
{
_distributedLock = new DistributedLock()
{
ClientId = _storage.Options.ClientId,
Resource = _resource
};
session.Store(_distributedLock);
session.Advanced.AddExpire(_distributedLock, DateTime.UtcNow.Add(_options.DistributedLockLifetime));
try
{
// Blocking session!
session.Advanced.UseOptimisticConcurrency = true;
session.SaveChanges();
isLockAcquired = true;
}
catch (ConcurrencyException)
{
_distributedLock = null;
try
{
var eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, EventWaitHandleName);
eventWaitHandle.WaitOne((int)timeout.TotalMilliseconds / 10);
}
catch (PlatformNotSupportedException)
{
Thread.Sleep((int)timeout.TotalMilliseconds / 10);
}
now = DateTime.Now;
}
}
}
if (!isLockAcquired)
{
throw new DistributedLockTimeoutException(_resource);
}
}
catch (DistributedLockTimeoutException)
{
throw;
}
catch (Exception ex)
{
throw new RavenDistributedLockException($"Could not place a lock on the resource \'{_resource}\': Check inner exception for details.", ex);
}
}
private void Release()
{
try
{
if (_distributedLock != null)
{
// Non blocking session!
using (var session = _storage.Repository.OpenSession())
{
session.Delete(_distributedLock.Id);
session.SaveChanges();
_distributedLock = null;
}
}
EventWaitHandle eventWaitHandler;
if (EventWaitHandle.TryOpenExisting(EventWaitHandleName, out eventWaitHandler))
{
eventWaitHandler.Set();
}
}
catch (PlatformNotSupportedException)
{
}
catch (Exception ex)
{
_distributedLock = null;
throw new RavenDistributedLockException($"Could not release a lock on the resource \'{_resource}\': Check inner exception for details.", ex);
}
}
private void StartHeartBeat()
{
Logger.InfoFormat(".Starting heartbeat for resource: {0}", _resource);
TimeSpan timerInterval = TimeSpan.FromMilliseconds(_options.DistributedLockLifetime.TotalMilliseconds / 5);
_heartbeatTimer = new Timer(state =>
{
lock (_lockObject)
{
try
{
Logger.InfoFormat("..Heartbeat for resource {0}", _resource);
using (var session = _storage.Repository.OpenSession())
{
var distributedLock = session.Load<DistributedLock>(_distributedLock.Id);
session.Advanced.AddExpire(distributedLock, DateTime.UtcNow.Add(_options.DistributedLockLifetime));
session.SaveChanges();
}
}
catch (Exception ex)
{
Logger.ErrorFormat("...Unable to update heartbeat on the resource '{0}'. {1}", _resource, ex);
Release();
}
}
}, null, timerInterval, timerInterval);
}
}
}
@@ -2,14 +2,7 @@
{
public class DistributedLock
{
public string Id {
get {
return string.Format("DistributedLocks/{0}", Resource);
}
set {
}
}
public string Id => $"DistributedLocks/{Resource}";
public string Resource { get; set; }
public string ClientId { get; set; }
+1 -25
View File
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Hangfire.Common;
using Hangfire.Storage;
using Hangfire.Storage.Monitoring;
@@ -15,31 +13,9 @@ namespace Hangfire.Raven.Entities
this.Parameters = new Dictionary<string, string>();
this.History = new List<StateHistoryDto>();
}
public class JobWrapper
{
public IEnumerable<object> Arguments { get; set; }
public Type Type { get; set; }
public MethodInfo Method { get; set; }
public Job GetJob()
{
return new Job(this.Type, this.Method, this.Arguments.ToArray());
}
public static JobWrapper Create(Job job)
{
var toReturn = new JobWrapper();
toReturn.Arguments = job.Args;
toReturn.Method = job.Method;
toReturn.Type = job.Type;
return toReturn;
}
}
public string Id { get; set; }
public JobWrapper Job { get; set; }
public InvocationData InvocationData { get; set; }
public IDictionary<string, string> Parameters { get; set; }
public DateTime CreatedAt { get; set; }
+9 -5
View File
@@ -1,11 +1,15 @@
namespace Hangfire.Raven.Entities
using System.Collections.Generic;
namespace Hangfire.Raven.Entities
{
public class RavenList
{
public RavenList()
{
Values = new List<string>();
}
public string Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public List<string> Values { get; set; }
}
}
@@ -6,7 +6,7 @@ namespace Hangfire.Raven.Extensions
{
public static bool DatabaseExists(this IDocumentStore documentStore, string database)
{
var result = documentStore.DatabaseCommands.Head("Raven/Databases/" + database);
var result = documentStore.DatabaseCommands.ForSystemDatabase().Head("Raven/Databases/" + database);
return (result != null);
}
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>RavenDB job storage for Hangfire</Description>
<Copyright>Copyright @ 2017 Refreshing Collective LLC</Copyright>
<AssemblyTitle>Hangfire.Raven</AssemblyTitle>
<VersionPrefix>2.1.1</VersionPrefix>
<Authors>Refreshing Collective &lt;contact@refreshing.io&gt;</Authors>
<TargetFrameworks>net45;netcoreapp1.0;netstandard1.6</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Hangfire.Raven</AssemblyName>
<PackageId>Hangfire.Raven</PackageId>
<PackageTags>Hangfire;AspNet;MVC;OWIN;RavenDB;Long-Running;Background;Fire-And-Forget;Delayed;Recurring;Tasks;Jobs;Scheduler;Threading;Queues</PackageTags>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/7880472?v=3&amp;s=200</PackageIconUrl>
<PackageProjectUrl>https://github.com/RefreshingIO/hangfire-ravendb</PackageProjectUrl>
<PackageLicenseUrl>http://www.gnu.org/licenses/lgpl-3.0.html</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/RefreshingIO/hangfire-ravendb</RepositoryUrl>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">1.0.4</RuntimeFrameworkVersion>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">1.6.0</NetStandardImplicitPackageVersion>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.6' ">$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.6' ">1.6.0</NetStandardImplicitPackageVersion>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Version>2.2.0</Version>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hangfire.Core" Version="1.6.9" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="RavenDB.Client" Version="3.5.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Data" />
<Reference Include="System.Reflection" />
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.0" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.0" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
</ItemGroup>
</Project>
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ba18f6bf-5613-4b9d-a57a-c05c51148ec5</ProjectGuid>
<RootNamespace>Hangfire.Raven</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using Raven.Abstractions.Data;
using Raven.Client;
using Raven.Client.Indexes;
namespace Hangfire.Raven
{
public interface IRepository : IDisposable
{
void Create();
void Destroy();
IDisposable DocumentChange(Type documentType, Action<DocumentChangeNotification> action);
IDisposable DocumentChange(Type documentType, string suffix, Action<DocumentChangeNotification> action);
void ExecuteIndexes(List<AbstractIndexCreationTask> indexes);
FacetResults GetFacets(string index, IndexQuery query, List<Facet> facets);
string GetId(Type type, params string[] id);
IAsyncDocumentSession OpenAsyncSession();
IDocumentSession OpenSession();
}
}
@@ -0,0 +1,32 @@
using Hangfire.Raven.Entities;
using Raven.Abstractions.Indexing;
using Raven.Client.Indexes;
using System;
using System.Linq;
namespace Hangfire.Raven.Indexes
{
public class Hangfire_JobQueues
: AbstractIndexCreationTask<JobQueue>
{
public class Mapping
{
public DateTime? FetchedAt { get; set; }
public string Queue { get; set; }
public string JobId { get; set; }
}
public Hangfire_JobQueues()
{
Map = results => from result in results
select new Mapping
{
Queue = result.Queue,
FetchedAt = result.FetchedAt,
JobId = result.JobId
};
Analyze("Queue", "WhitespaceAnalyzer");
Sort("FetchedAt", SortOptions.String);
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Linq;
using Hangfire.Raven.Entities;
using Raven.Client.Indexes;
using Raven.Abstractions.Indexing;
namespace Hangfire.Raven.Indexes
{
public class Hangfire_RavenServers
: AbstractIndexCreationTask<RavenServer>
{
public class Mapping
{
public DateTime LastHeartbeat { get; set; }
}
public Hangfire_RavenServers()
{
Map = results => from result in results
select new Mapping {
LastHeartbeat = result.LastHeartbeat
};
Sort("LastHeartbeat", SortOptions.String);
}
}
}
+122 -106
View File
@@ -1,106 +1,122 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using Hangfire.Annotations;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
using Hangfire.Storage;
using Raven.Abstractions.Data;
namespace Hangfire.Raven.JobQueues
{
public class RavenJobQueue
: IPersistentJobQueue
{
private readonly RavenStorage _storage;
private readonly RavenStorageOptions _options;
private BlockingCollection<string> _queue;
public RavenJobQueue([NotNull] RavenStorage storage, RavenStorageOptions options)
{
storage.ThrowIfNull("storage");
options.ThrowIfNull("options");
_storage = storage;
_options = options;
_queue = new BlockingCollection<string>();
using (var session = _storage.Repository.OpenSession()) {
var missed = session.Advanced.LoadStartingWith<JobQueue>(Repository.GetId(typeof(JobQueue), ""));
foreach (var miss in missed) {
_queue.Add(miss.Id);
}
}
// -- Queue listening
if (options.QueueNames == null) {
Console.WriteLine("Starting on ALL Queue's, this is not recommended, please specify using RavenStorageOptions. Use an empty IEnumerable (such as List<string> or string[0]) to not listen to any queue.");
_storage.Repository.DocumentChange(typeof(JobQueue), QueueFiller);
} else {
foreach (var queue in options.QueueNames) {
Console.WriteLine("Starting on queue: {0}", queue);
_storage.Repository.DocumentChange(typeof(JobQueue), queue, QueueFiller);
}
}
}
private void QueueFiller(DocumentChangeNotification notification)
{
if (notification.Type == DocumentChangeTypes.Put)
_queue.Add(notification.Id);
}
[NotNull]
public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken)
{
queues.ThrowIfNull("queues");
if (queues.Length == 0) {
throw new ArgumentException("Queue array must be non-empty.", "queues");
}
JobQueue fetchedJob = null;
do {
var jobId = _queue.Take(cancellationToken);
using (var repository = _storage.Repository.OpenSession()) {
fetchedJob = repository.Load<JobQueue>(jobId);
if (fetchedJob != null &&
fetchedJob.FetchedAt == null &&
queues.Contains(fetchedJob.Queue)) {
fetchedJob.FetchedAt = DateTime.UtcNow;
repository.Store(fetchedJob);
try {
// Did someone else already picked it up?
repository.Advanced.UseOptimisticConcurrency = true;
repository.SaveChanges();
} catch {
fetchedJob = null;
}
} else {
fetchedJob = null;
}
}
}
while (fetchedJob == null);
return new RavenFetchedJob(_storage, fetchedJob);
}
public void Enqueue(string queue, string jobId)
{
using (var repository = _storage.Repository.OpenSession()) {
var jobQueue = new JobQueue {
Id = Repository.GetId(typeof(JobQueue), queue, jobId),
JobId = jobId,
Queue = queue
};
repository.Store(jobQueue);
repository.SaveChanges();
}
}
}
}
using System;
using System.Linq;
using System.Threading;
using Hangfire.Annotations;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
using Hangfire.Storage;
using Raven.Abstractions.Exceptions;
using Raven.Client.Linq;
using Hangfire.Logging;
using Hangfire.Raven.Indexes;
using System.Linq.Expressions;
namespace Hangfire.Raven.JobQueues
{
public class RavenJobQueue : IPersistentJobQueue
{
private static readonly ILog Logger = LogProvider.For<RavenJobQueue>();
private readonly RavenStorage _storage;
private readonly RavenStorageOptions _options;
public RavenJobQueue([NotNull] RavenStorage storage, RavenStorageOptions options)
{
storage.ThrowIfNull("storage");
options.ThrowIfNull("options");
_storage = storage;
_options = options;
}
[NotNull]
public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken)
{
queues.ThrowIfNull("queues");
if (queues.Length == 0)
{
throw new ArgumentException("Queue array must be non-empty.", "queues");
}
JobQueue fetchedJob = null;
var fetchConditions = new Expression<Func<Hangfire_JobQueues.Mapping, bool>>[]
{
job => job.FetchedAt == null,
job => job.FetchedAt < DateTime.UtcNow.AddSeconds(_options.InvisibilityTimeout.Negate().TotalSeconds)
};
var currentQueryIndex = 0;
do
{
cancellationToken.ThrowIfCancellationRequested();
var fetchCondition = fetchConditions[currentQueryIndex];
foreach (var queue in queues)
{
using (var repository = _storage.Repository.OpenSession())
{
foreach (var job in repository.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>()
.Where(fetchCondition)
.Where(job => job.Queue == queue)
.OfType<JobQueue>())
{
job.FetchedAt = DateTime.UtcNow;
try
{
// Did someone else already picked it up?
repository.Advanced.UseOptimisticConcurrency = true;
repository.SaveChanges();
fetchedJob = job;
break;
}
catch (ConcurrencyException)
{
repository.Advanced.Evict(job); // Avoid subsequent concurrency exceptions
}
}
}
if (fetchedJob != null)
{
break;
}
}
if (fetchedJob == null)
{
if (currentQueryIndex == fetchConditions.Length - 1)
{
cancellationToken.WaitHandle.WaitOne(_options.QueuePollInterval);
cancellationToken.ThrowIfCancellationRequested();
}
}
currentQueryIndex = (currentQueryIndex + 1) % fetchConditions.Length;
}
while (fetchedJob == null);
return new RavenFetchedJob(_storage, fetchedJob);
}
public void Enqueue(string queue, string jobId)
{
using (var repository = _storage.Repository.OpenSession())
{
var jobQueue = new JobQueue
{
Id = _storage.Repository.GetId(typeof(JobQueue), queue, jobId),
JobId = jobId,
Queue = queue
};
repository.Store(jobQueue);
repository.SaveChanges();
}
}
}
}
@@ -3,10 +3,11 @@ using System.Linq;
using Hangfire.Annotations;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
using Hangfire.Raven.Indexes;
namespace Hangfire.Raven.JobQueues
{
internal class RavenJobQueueMonitoringApi
public class RavenJobQueueMonitoringApi
: IPersistentJobQueueMonitoringApi
{
private RavenStorage _storage;
@@ -28,68 +29,59 @@ namespace Hangfire.Raven.JobQueues
}
}
public IEnumerable<string> GetEnqueuedJobIds(string queue, int @from, int perPage)
public IEnumerable<string> GetEnqueuedJobIds(string queue, int pageFrom, int perPage)
{
int start = @from + 1;
int end = from + perPage;
using (var repository = _storage.Repository.OpenSession()) {
var jobs = repository.Query<JobQueue>()
.Where(t => t.Queue == queue && t.FetchedAt == null)
.Select((data, i) => new { Index = i + 1, Data = data })
.Where(_ => (_.Index >= start) && (_.Index <= end))
.Select(x => x.Data)
using (var repository = _storage.Repository.OpenSession())
{
return repository.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>()
.Where(a => a.Queue == queue && a.FetchedAt == null)
.Skip(pageFrom)
.Take(perPage)
.Select(a => a.JobId)
.ToList()
.Where(jobId =>
{
var job = repository.Load<RavenJob>(_storage.Repository.GetId(typeof(RavenJob), jobId));
return (job != null) && (job.StateData != null);
})
.ToList();
var results = new List<RavenJob>();
foreach (var item in jobs) {
var job = repository.Load<RavenJob>(item.JobId);
if (job.StateData != null)
results.Add(job);
}
return results.Select(t => t.Id.Split(new char[] { '/' }, 2)[1]).ToList();
}
}
public IEnumerable<string> GetFetchedJobIds(string queue, int @from, int perPage)
public IEnumerable<string> GetFetchedJobIds(string queue, int pageFrom, int perPage)
{
int start = @from + 1;
int end = from + perPage;
using (var repository = _storage.Repository.OpenSession()) {
var jobs = repository.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt != null)
.Select((data, i) => new { Index = i + 1, Data = data })
.Where(_ => (_.Index >= start) && (_.Index <= end))
.Select(x => x.Data);
var results = new List<string>();
foreach (var item in jobs) {
var job = repository.Load<RavenJob>(item.JobId);
if (job != null) {
results.Add(job.Id.Split(new char[] { '/' }, 2)[1]);
}
}
return results;
using (var repository = _storage.Repository.OpenSession())
{
return repository.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>()
.Where(a => a.Queue == queue && a.FetchedAt != null)
.Skip(pageFrom)
.Take(perPage)
.Select(a => a.JobId)
.ToList()
.Where(jobId =>
{
var job = repository.Load<RavenJob>(_storage.Repository.GetId(typeof(RavenJob), jobId));
return job != null;
})
.ToList();
}
}
public EnqueuedAndFetchedCount GetEnqueuedAndFetchedCount(string queue)
{
using (var repository = _storage.Repository.OpenSession()) {
int enqueuedCount = repository.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt == null).Count();
int fetchedCount = repository.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt != null).Count();
return new EnqueuedAndFetchedCount {
EnqueuedCount = enqueuedCount,
FetchedCount = fetchedCount
};
using (var repository = _storage.Repository.OpenSession())
{
var fetchedQuery = _storage.GetJobQueueFacets(repository, a => a.FetchedAt != null && a.Queue == queue);
var fetchedCount = fetchedQuery.Results["Queue"].Values.Sum(a => a.Hits);
var enqueuedQuery = _storage.GetJobQueueFacets(repository, a => a.FetchedAt == null && a.Queue == queue);
var enqueuedCount = enqueuedQuery.Results["Queue"].Values.Sum(a => a.Hits);
return new EnqueuedAndFetchedCount
{
EnqueuedCount = enqueuedCount,
FetchedCount = fetchedCount
};
}
}
}
@@ -0,0 +1,13 @@
using Raven.Client;
using Raven.Client.Listeners;
namespace Hangfire.Raven.Listeners
{
public class NoStaleQueriesListener : IDocumentQueryListener
{
public void BeforeQueryExecuted(IDocumentQueryCustomization queryCustomization)
{
queryCustomization.WaitForNonStaleResults();
}
}
}
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCompany("Refreshing Collective")]
[assembly: AssemblyProduct("Hangfire.Raven.Core")]
[assembly: AssemblyTrademark("")]
+119 -54
View File
@@ -25,23 +25,24 @@ using Hangfire.Raven.Storage;
using Hangfire.Server;
using Hangfire.Storage;
using static Hangfire.Raven.Entities.RavenJob;
using Hangfire.Raven.Indexes;
namespace Hangfire.Raven
{
public class RavenConnection : JobStorageConnection
{
private readonly RavenStorage _storage;
public RavenConnection(RavenStorage ravenStorage)
public RavenConnection(RavenStorage storage)
{
ravenStorage.ThrowIfNull("RavenStorage");
storage.ThrowIfNull("storage");
_storage = ravenStorage;
_storage = storage;
}
public override IWriteOnlyTransaction CreateWriteTransaction() => new RavenWriteOnlyTransaction(_storage);
public override IDisposable AcquireDistributedLock(string resource, TimeSpan timeout) => new RavenDistributedLock(_storage, string.Format("HangFire/{0}", resource), timeout);
public override IDisposable AcquireDistributedLock(string resource, TimeSpan timeout) => new RavenDistributedLock(_storage, string.Format("HangFire/{0}", resource), timeout, _storage.Options);
public override IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken)
{
@@ -77,11 +78,13 @@ namespace Hangfire.Raven
parameters.ThrowIfNull("parameters");
using (var repository = _storage.Repository.OpenSession()) {
var invocationData = InvocationData.Serialize(job);
var guid = Guid.NewGuid().ToString();
var ravenJob = new RavenJob {
Id = Repository.GetId(typeof(RavenJob), guid),
Job = JobWrapper.Create(job),
Id = _storage.Repository.GetId(typeof(RavenJob), guid),
InvocationData = invocationData,
CreatedAt = createdAt,
Parameters = parameters
};
@@ -100,19 +103,30 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenJob), key);
var id = _storage.Repository.GetId(typeof(RavenJob), key);
var jobData = repository.Load<RavenJob>(id);
if (jobData == null) {
return null;
}
var job = jobData.Job.GetJob();
Job job = null;
JobLoadException loadException = null;
try
{
job = jobData.InvocationData.Deserialize();
}
catch (JobLoadException ex)
{
loadException = ex;
}
return new JobData {
Job = job,
State = jobData.StateData?.Name,
CreatedAt = jobData.CreatedAt
CreatedAt = jobData.CreatedAt,
LoadException = loadException
};
}
}
@@ -122,7 +136,7 @@ namespace Hangfire.Raven
jobId.ThrowIfNull("jobId");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var job = repository.Load<RavenJob>(id);
if (job == null) {
@@ -139,7 +153,7 @@ namespace Hangfire.Raven
name.ThrowIfNull("name");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var job = repository.Load<RavenJob>(id);
job.Parameters[name] = value;
@@ -154,9 +168,13 @@ namespace Hangfire.Raven
name.ThrowIfNull("name");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var job = repository.Load<RavenJob>(id);
if (job == null) {
return null;
}
string value;
if (!job.Parameters.TryGetValue(name, out value)) {
@@ -179,11 +197,11 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = repository.Load<RavenSet>(id);
if (set == null) {
return null;
return new HashSet<string>();
}
return new HashSet<string>(set.Scores.Keys);
@@ -199,14 +217,14 @@ namespace Hangfire.Raven
}
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = repository.Load<RavenSet>(id);
if (set == null) {
return null;
}
return set.Scores.OrderBy(a => a.Value).Select(a => a.Key).FirstOrDefault();
return set.Scores.Where(a => a.Value >= fromScore && a.Value <= toScore).OrderBy(a => a.Value).Select(a => a.Key).FirstOrDefault();
}
}
@@ -216,7 +234,7 @@ namespace Hangfire.Raven
keyValuePairs.ThrowIfNull("keyValuePairs");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenHash), key);
var id = _storage.Repository.GetId(typeof(RavenHash), key);
var result = repository.Load<RavenHash>(id);
if (result == null) {
@@ -240,7 +258,7 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var result = repository.Load<RavenHash>(Repository.GetId(typeof(RavenHash), key));
var result = repository.Load<RavenHash>(_storage.Repository.GetId(typeof(RavenHash), key));
return result?.Fields;
}
@@ -252,21 +270,24 @@ namespace Hangfire.Raven
context.ThrowIfNull("context");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenServer), serverId);
var id = _storage.Repository.GetId(typeof(RavenServer), serverId);
var server = repository.Load<RavenServer>(id);
if (server == null) {
server = new RavenServer() {
Id = id,
Data = new RavenServer.ServerData()
Data = new RavenServer.ServerData()
{
StartedAt = DateTime.UtcNow
}
};
repository.Store(server);
}
// Merge Queues and WorkerCount
server.Data.WorkerCount += context.WorkerCount;
server.Data.Queues = context.Queues.Concat(server.Data.Queues ?? new List<string>()).Distinct();
server.Data.WorkerCount = context.WorkerCount;
server.Data.Queues = context.Queues;
server.Data.StartedAt = DateTime.UtcNow;
server.LastHeartbeat = DateTime.UtcNow;
@@ -279,7 +300,7 @@ namespace Hangfire.Raven
serverId.ThrowIfNull("serverId");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenServer), serverId);
var id = _storage.Repository.GetId(typeof(RavenServer), serverId);
repository.Delete(id);
@@ -292,13 +313,15 @@ namespace Hangfire.Raven
serverId.ThrowIfNull("serverId");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenServer), serverId);
var id = _storage.Repository.GetId(typeof(RavenServer), serverId);
var server = repository.Load<RavenServer>(id);
if (server == null) {
server = new RavenServer {
Id = id
};
repository.Store(server);
}
server.LastHeartbeat = DateTime.UtcNow;
@@ -316,8 +339,9 @@ namespace Hangfire.Raven
using (var repository = _storage.Repository.OpenSession()) {
var heartBeatCutOff = DateTime.UtcNow.Add(timeOut.Negate());
var results = repository.Query<RavenServer>()
var results = repository.Query<Hangfire_RavenServers.Mapping, Hangfire_RavenServers>()
.Where(t => t.LastHeartbeat < heartBeatCutOff)
.OfType<RavenServer>()
.ToList();
foreach (var item in results) {
@@ -335,7 +359,7 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = repository.Load<RavenSet>(id);
if (set == null) {
@@ -351,14 +375,14 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = repository.Load<RavenSet>(id);
if (set == null) {
return new List<string>();
}
return set.Scores.Skip(startingFrom - 1)
return set.Scores.Skip(startingFrom)
.Take(endingAt - startingFrom + 1)
.Select(t => t.Key)
.ToList();
@@ -370,8 +394,12 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = repository.Load<RavenSet>(id);
if (set == null) {
return TimeSpan.FromSeconds(-1);
}
var expireAt = repository.Advanced.GetExpire(set);
@@ -388,12 +416,14 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var values = repository.Query<AggregatedCounter>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.ToArray();
var id = _storage.Repository.GetId(typeof(Counter), key);
var counter = repository.Load<Counter>(id);
return values.Any() ? values.Sum() : 0;
if (counter == null) {
return 0;
}
return counter.Value;
}
}
@@ -402,7 +432,11 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var ravenHash = repository.Load<RavenHash>(Repository.GetId(typeof(RavenHash), key));
var ravenHash = repository.Load<RavenHash>(_storage.Repository.GetId(typeof(RavenHash), key));
if (ravenHash == null) {
return 0;
}
return ravenHash.Fields.Count;
}
@@ -413,7 +447,11 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
var ravenHash = repository.Load<RavenHash>(Repository.GetId(typeof(RavenHash), key));
var ravenHash = repository.Load<RavenHash>(_storage.Repository.GetId(typeof(RavenHash), key));
if (ravenHash == null) {
return TimeSpan.FromSeconds(-1);
}
var expireAt = repository.Advanced.GetExpire(ravenHash);
@@ -431,7 +469,11 @@ namespace Hangfire.Raven
name.ThrowIfNull("name");
using (var repository = _storage.Repository.OpenSession()) {
var ravenHash = repository.Load<RavenHash>(Repository.GetId(typeof(RavenHash), key));
var ravenHash = repository.Load<RavenHash>(_storage.Repository.GetId(typeof(RavenHash), key));
if (ravenHash == null) {
return null;
}
string result;
@@ -448,9 +490,14 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
return repository.Query<RavenList>()
.Where(t => t.Key == key)
.Count();
var id = _storage.Repository.GetId(typeof(RavenList), key);
var list = repository.Load<RavenList>(id);
if (list == null) {
return 0;
}
return list.Values.Count;
}
}
@@ -459,9 +506,19 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
// TODO: Implement!
var list = repository.Load<RavenList>(_storage.Repository.GetId(typeof(RavenList), key));
if (list == null) {
return TimeSpan.FromSeconds(-1);
}
return TimeSpan.FromSeconds(-1);
var expireAt = repository.Advanced.GetExpire(list);
if (!expireAt.HasValue) {
return TimeSpan.FromSeconds(-1);
}
return expireAt.Value - DateTime.UtcNow;
}
}
@@ -470,12 +527,16 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
return repository.Query<RavenList>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.Skip(startingFrom - 1)
.Take(endingAt - startingFrom + 1)
.ToList();
var id = _storage.Repository.GetId(typeof(RavenList), key);
var list = repository.Load<RavenList>(id);
if (list == null) {
return new List<string>();
}
return list.Values.Skip(startingFrom)
.Take(endingAt - startingFrom + 1)
.ToList();
}
}
@@ -484,10 +545,14 @@ namespace Hangfire.Raven
key.ThrowIfNull("key");
using (var repository = _storage.Repository.OpenSession()) {
return repository.Query<RavenList>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.ToList();
var id = _storage.Repository.GetId(typeof(RavenList), key);
var list = repository.Load<RavenList>(id);
if (list == null) {
return new List<string>();
}
return list.Values;
}
}
}
-133
View File
@@ -1,133 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using Raven.Imports.Newtonsoft.Json;
using Raven.Imports.Newtonsoft.Json.Linq;
namespace Hangfire.Raven
{
/// <summary>
/// Base class for Raven Json Converters
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class RavenJsonConverterBase<T>
: JsonConverter
{
public RavenJsonConverterBase()
{
}
/// <summary>
/// Read Json and convert to T
/// </summary>
/// <param name="type"></param>
/// <param name="value">Object to convert</param>
/// <param name="serializer">JsonSerializer</param>
/// <returns>Object</returns>
protected abstract T Read(Type type, object value, JsonSerializer serializer);
/// <summary>
/// Write Json and convert T to text
/// </summary>
/// <param name="value">Input</param>
/// <returns>Input as text</returns>
protected abstract object Write(T value, JsonSerializer serializer);
/// <summary>
/// Overriding the CanConvert of JsonConvert
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public override bool CanConvert(Type type) => typeof(T).IsAssignableFrom(type);
/// <summary>
/// Overriding the ReadJson of JsonConvert
/// </summary>
/// <param name="reader"></param>
/// <param name="type"></param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override object ReadJson(JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
{
T toReturn;
switch (reader.TokenType) {
case JsonToken.StartArray:
toReturn = Read(type, JArray.Load(reader), serializer);
break;
case JsonToken.StartObject:
toReturn = Read(type, JObject.Load(reader), serializer);
break;
default:
toReturn = Read(type, reader.Value, serializer);
break;
}
return toReturn;
}
/// <summary>
/// Overriding the WriteJson of JsonConvert
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, Write((T)value, serializer));
}
}
public class RavenJsonMethodConverter
: RavenJsonConverterBase<MethodInfo>
{
protected override MethodInfo Read(Type type, object value, JsonSerializer serializer)
{
if (value == null) {
return null;
}
var splitted = value.ToString().Split(';');
if (splitted.Count() == 3) {
var assembly = Assemblies.Get().Where(a => a.GetName().Name == splitted[0]).First();
var assemblyClass = Assemblies.GetClass(assembly, splitted[1]);
if (assemblyClass != null) {
return assemblyClass.GetMethod(splitted[2]);
}
}
return null;
}
protected override object Write(MethodInfo value, JsonSerializer serializer) => $"{value.DeclaringType.GetTypeInfo().Assembly.GetName().Name};{value.DeclaringType.FullName};{value.Name}";
}
public class RavenJsonPropertyConverter
: RavenJsonConverterBase<PropertyInfo>
{
protected override PropertyInfo Read(Type type, object value, JsonSerializer serializer)
{
if (value == null) {
return null;
}
var splitted = value.ToString().Split(';');
if (splitted.Count() == 3) {
var assembly = Assemblies.Get().Where(a => a.GetName().Name == splitted[0]).First();
var assemblyType = Assemblies.GetClass(assembly, splitted[1]);
if (assemblyType != null) {
return assemblyType.GetProperty(splitted[2]);
}
}
return null;
}
protected override object Write(PropertyInfo value, JsonSerializer serializer) => $"{value.DeclaringType.GetTypeInfo().Assembly.GetName().Name};{value.DeclaringType.FullName};{value.Name}";
}
}
+84 -38
View File
@@ -9,12 +9,15 @@ using Hangfire.Storage;
using Hangfire.Storage.Monitoring;
using Raven.Abstractions.Data;
using Raven.Client;
using Hangfire.Logging;
namespace Hangfire.Raven
{
internal class RavenWriteOnlyTransaction
public class RavenWriteOnlyTransaction
: JobStorageTransaction
{
private static readonly ILog Logger = LogProvider.For<RavenWriteOnlyTransaction>();
private readonly RavenStorage _storage;
private IDocumentSession _session;
private List<KeyValuePair<string, PatchRequest>> _patchRequests;
@@ -41,7 +44,7 @@ namespace Hangfire.Raven
_session.Advanced.DocumentStore.DatabaseCommands.Patch(item.Key, item.ToArray());
}
} catch {
Console.WriteLine("- Concurrency exception");
Logger.Error("- Concurrency exception");
throw;
}
@@ -49,7 +52,7 @@ namespace Hangfire.Raven
public override void ExpireJob(string jobId, TimeSpan expireIn)
{
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var result = _session.Load<RavenJob>(id);
_session.Advanced.AddExpire(result, DateTime.UtcNow + expireIn);
@@ -57,7 +60,7 @@ namespace Hangfire.Raven
public override void PersistJob(string jobId)
{
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var result = _session.Load<RavenJob>(id);
_session.Advanced.RemoveExpire(result);
@@ -65,7 +68,7 @@ namespace Hangfire.Raven
public override void SetJobState(string jobId, IState state)
{
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var result = _session.Load<RavenJob>(id);
result.History.Insert(0, new StateHistoryDto() {
@@ -87,6 +90,24 @@ namespace Hangfire.Raven
SetJobState(jobId, state);
}
public override void AddRangeToSet(string key, IList<string> items)
{
key.ThrowIfNull("key");
items.ThrowIfNull("items");
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = _session.Load<RavenSet>(id) ?? new RavenSet() {
Id = id
};
_session.Store(set);
foreach (var item in items) {
set.Scores[item] = 0.0;
}
}
public override void AddToQueue(string queue, string jobId)
{
var provider = _storage.QueueProviders.GetProvider(queue);
@@ -102,7 +123,7 @@ namespace Hangfire.Raven
public override void IncrementCounter(string key, TimeSpan expireIn)
{
var id = Repository.GetId(typeof(Counter), key);
var id = _storage.Repository.GetId(typeof(Counter), key);
if (_session.Load<Counter>(id) == null) {
var counter = new Counter() {
@@ -131,7 +152,7 @@ namespace Hangfire.Raven
public override void DecrementCounter(string key, TimeSpan expireIn)
{
var id = Repository.GetId(typeof(Counter), key);
var id = _storage.Repository.GetId(typeof(Counter), key);
if (_session.Load<Counter>(id) == null) {
var counter = new Counter() {
@@ -160,7 +181,7 @@ namespace Hangfire.Raven
public override void AddToSet(string key, string value, double score)
{
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = _session.Load<RavenSet>(id) ?? new RavenSet() {
Id = id
@@ -173,10 +194,14 @@ namespace Hangfire.Raven
public override void RemoveFromSet(string key, string value)
{
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = _session.Load<RavenSet>(id);
if (set == null) {
return;
}
set.Scores.Remove(value);
if (set.Scores.Count == 0) {
@@ -188,7 +213,7 @@ namespace Hangfire.Raven
{
key.ThrowIfNull("key");
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
_session.Delete(id);
}
@@ -197,7 +222,7 @@ namespace Hangfire.Raven
{
key.ThrowIfNull("key");
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = _session.Load<RavenSet>(id);
_session.Advanced.AddExpire(set, DateTime.UtcNow + expireIn);
@@ -207,7 +232,7 @@ namespace Hangfire.Raven
{
key.ThrowIfNull("key");
var id = Repository.GetId(typeof(RavenSet), key);
var id = _storage.Repository.GetId(typeof(RavenSet), key);
var set = _session.Load<RavenSet>(id);
_session.Advanced.RemoveExpire(set);
@@ -216,56 +241,77 @@ namespace Hangfire.Raven
public override void InsertToList(string key, string value)
{
var list = _session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
var id = _storage.Repository.GetId(typeof(RavenList), key);
if (list == null) {
list = new RavenList {
Id = Guid.NewGuid().ToString(),
Key = key
};
}
list.Value = value;
var list = _session.Load<RavenList>(id) ?? new RavenList() {
Id = id
};
_session.Store(list);
list.Values.Add(value);
}
public override void RemoveFromList(string key, string value)
{
var list = _session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
var id = _storage.Repository.GetId(typeof(RavenList), key);
if (list == null) {
return;
var list = _session.Load<RavenList>(id);
if (list == null) {
return;
}
_session.Delete(list);
list.Values.RemoveAll(v => v == value);
if (list.Values.Count == 0) {
_session.Delete(list);
}
}
public override void TrimList(string key, int keepStartingFrom, int keepEndingAt)
{
var list = _session.Query<RavenList>().Where(t => t.Key == key).ToList();
var id = _storage.Repository.GetId(typeof(RavenList), key);
if (!list.Any()) {
return;
var list = _session.Load<RavenList>(id);
if (list == null) {
return;
}
for (var i = 0; i < list.Count; ++i) {
if (i >= keepStartingFrom && i <= keepEndingAt) {
continue;
}
list.Values = list.Values.Skip(keepStartingFrom).Take(keepEndingAt - keepStartingFrom + 1).ToList();
_session.Delete(list[i]);
if (list.Values.Count == 0) {
_session.Delete(list);
}
}
public override void ExpireList(string key, TimeSpan expireIn)
{
key.ThrowIfNull("key");
var id = _storage.Repository.GetId(typeof(RavenList), key);
var list = _session.Load<RavenList>(id);
_session.Advanced.AddExpire(list, DateTime.UtcNow + expireIn);
}
public override void PersistList(string key)
{
key.ThrowIfNull("key");
var id = _storage.Repository.GetId(typeof(RavenList), key);
var list = _session.Load<RavenList>(id);
_session.Advanced.RemoveExpire(list);
}
public override void SetRangeInHash(string key, IEnumerable<KeyValuePair<string, string>> keyValuePairs)
{
key.ThrowIfNull("key");
keyValuePairs.ThrowIfNull("keyValuePairs");
var id = Repository.GetId(typeof(RavenHash), key);
var id = _storage.Repository.GetId(typeof(RavenHash), key);
var result = _session.Load<RavenHash>(id);
// Not exists? Insert
@@ -286,14 +332,14 @@ namespace Hangfire.Raven
{
key.ThrowIfNull("key");
_session.Delete(Repository.GetId(typeof(RavenHash), key));
_session.Delete(_storage.Repository.GetId(typeof(RavenHash), key));
}
public override void ExpireHash(string key, TimeSpan expireIn)
{
key.ThrowIfNull("key");
var id = Repository.GetId(typeof(RavenHash), key);
var id = _storage.Repository.GetId(typeof(RavenHash), key);
var set = _session.Load<RavenHash>(id);
_session.Advanced.AddExpire(set, DateTime.UtcNow + expireIn);
@@ -303,7 +349,7 @@ namespace Hangfire.Raven
{
key.ThrowIfNull("key");
var id = Repository.GetId(typeof(RavenHash), key);
var id = _storage.Repository.GetId(typeof(RavenHash), key);
var set = _session.Load<RavenHash>(id);
_session.Advanced.RemoveExpire(set);
+40 -39
View File
@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using Hangfire.Raven.Extensions;
using Hangfire.Raven.Indexes;
using Hangfire.Raven.Listeners;
using Raven.Abstractions.Data;
using Raven.Client;
using Raven.Client.Document;
using Raven.Client.Indexes;
using Raven.Imports.Newtonsoft.Json;
using System.IO;
namespace Hangfire.Raven
{
public class RepositoryConfig
@@ -26,7 +25,7 @@ namespace Hangfire.Raven
public RepositoryObserver(Action<T> input)
{
this._action = input;
_action = input;
}
public void OnCompleted()
@@ -41,47 +40,34 @@ namespace Hangfire.Raven
public void OnNext(T value)
{
this._action.Invoke(value);
_action.Invoke(value);
}
}
public class Repository
public class Repository : IRepository
{
private RepositoryConfig _config;
private static IDocumentStore _documentStore;
private DocumentStore _documentStore;
private string _database;
public Repository(RepositoryConfig config)
{
_config = config;
if (!string.IsNullOrEmpty(_config.ConnectionStringName)) {
if (!string.IsNullOrEmpty(config.ConnectionStringName)) {
_documentStore = new DocumentStore {
ConnectionStringName = _config.ConnectionStringName,
Conventions = new DocumentConvention() {
CustomizeJsonSerializer = delegate (JsonSerializer input) {
input.Converters.Add(new RavenJsonPropertyConverter());
input.Converters.Add(new RavenJsonMethodConverter());
}
}
ConnectionStringName = config.ConnectionStringName
};
} else {
_documentStore = new DocumentStore {
Url = _config.ConnectionUrl,
ApiKey = _config.ApiKey,
DefaultDatabase = _config.Database,
Conventions = new DocumentConvention() {
CustomizeJsonSerializer = delegate (JsonSerializer input) {
input.Converters.Add(new RavenJsonPropertyConverter());
input.Converters.Add(new RavenJsonMethodConverter());
}
}
Url = config.ConnectionUrl,
ApiKey = config.ApiKey,
DefaultDatabase = config.Database
};
}
_documentStore.Listeners.RegisterListener(new TakeNewestConflictResolutionListener());
_documentStore.Initialize();
_documentStore.Initialize(ensureDatabaseExists: false);
new Hangfire_RavenJobs().Execute(_documentStore);
_database = _documentStore.DefaultDatabase;
}
public FacetResults GetFacets(string index, IndexQuery query, List<Facet> facets)
@@ -94,38 +80,48 @@ namespace Hangfire.Raven
_documentStore.ExecuteIndexes(indexes);
}
public static string GetId(Type type, params string[] id)
public string GetId(Type type, params string[] id)
{
return _documentStore.Conventions.FindFullDocumentKeyFromNonStringIdentifier(string.Join("/", id), type, false);
}
public void Destroy()
{
if (!_documentStore.DatabaseExists(_config.Database)) {
if (_database == null || !_documentStore.DatabaseExists(_database)) {
return;
}
_documentStore.DatabaseCommands.GlobalAdmin.DeleteDatabase(_config.Database, hardDelete: true);
_documentStore.DatabaseCommands.GlobalAdmin.DeleteDatabase(_database, hardDelete: true);
}
public void Create()
{
if (_documentStore.DatabaseExists(_config.Database)) {
if (_database == null || _documentStore.DatabaseExists(_database)) {
return;
}
_documentStore.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(_config.Database);
_documentStore
.DatabaseCommands
.GlobalAdmin
.CreateDatabase(new DatabaseDocument {
Id = "Raven/Databases/" + _database,
Settings = {
{ "Raven/ActiveBundles", "DocumentExpiration" },
{ "Raven/StorageTypeName", "voron" },
{ "Raven/DataDir", Path.Combine("~", _database) },
}
});
}
public IDisposable DocumentChange(Type documentType, Action<DocumentChangeNotification> action)
{
return _documentStore.Changes(_config.Database).ForDocumentsStartingWith(GetId(documentType, ""))
return _documentStore.Changes(_database).ForDocumentsStartingWith(GetId(documentType, ""))
.Subscribe(new RepositoryObserver<DocumentChangeNotification>(action));
}
public IDisposable DocumentChange(Type documentType, string suffix, Action<DocumentChangeNotification> action)
{
return _documentStore.Changes(_config.Database).ForDocumentsStartingWith(
return _documentStore.Changes(_database).ForDocumentsStartingWith(
GetId(documentType, string.Format("{0}/", suffix))
)
.Subscribe(new RepositoryObserver<DocumentChangeNotification>(action));
@@ -133,12 +129,17 @@ namespace Hangfire.Raven
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession(_config.Database);
return _documentStore.OpenSession(_database);
}
public IAsyncDocumentSession OpenAsyncSession()
{
return _documentStore.OpenAsyncSession(_config.Database);
}
return _documentStore.OpenAsyncSession(_database);
}
public void Dispose()
{
_documentStore.Dispose();
}
}
}
+62 -11
View File
@@ -1,16 +1,21 @@
using System.Collections.Generic;
using Hangfire.Logging;
using Hangfire.Raven.Indexes;
using Hangfire.Raven.JobQueues;
using Hangfire.Storage;
using Raven.Client.Indexes;
using System;
using System.Collections.Generic;
using Hangfire.Logging;
using Hangfire.Raven.Indexes;
using Hangfire.Raven.JobQueues;
using Hangfire.Storage;
using Raven.Client.Indexes;
using Raven.Abstractions.Data;
using System.Linq.Expressions;
using Raven.Client;
using Raven.Client.Linq;
namespace Hangfire.Raven.Storage
{
public class RavenStorage : JobStorage
{
private readonly RavenStorageOptions _options;
private readonly Repository _repository;
private readonly IRepository _repository;
public RavenStorage(RepositoryConfig config)
: this(config, new RavenStorageOptions())
@@ -23,22 +28,36 @@ namespace Hangfire.Raven.Storage
/// <param name="config"></param>
/// <param name="options"></param>
public RavenStorage(RepositoryConfig config, RavenStorageOptions options)
: this(new Repository(config), options)
{
}
public RavenStorage(IRepository repository)
: this(repository, new RavenStorageOptions())
{
}
public RavenStorage(IRepository repository, RavenStorageOptions options)
{
repository.ThrowIfNull("repository");
options.ThrowIfNull("options");
_options = options;
_repository = new Repository(config);
_repository = repository;
_repository.Create();
_repository.ExecuteIndexes(new List<AbstractIndexCreationTask>()
{
new Hangfire_RavenJobs()
new Hangfire_RavenJobs(),
new Hangfire_JobQueues(),
new Hangfire_RavenServers()
});
InitializeQueueProviders();
}
public RavenStorageOptions Options { get { return _options; } }
public Repository Repository { get { return _repository; } }
public IRepository Repository { get { return _repository; } }
public virtual PersistentJobQueueProviderCollection QueueProviders { get; private set; }
@@ -55,6 +74,38 @@ namespace Hangfire.Raven.Storage
public override void WriteOptionsToLog(ILog logger)
{
logger.Info("Using the following options for Raven job storage:");
}
public FacetResults GetRavenJobFacets(
IDocumentSession session,
Expression<Func<Hangfire_RavenJobs.Mapping, bool>> clause)
{
var query = session.Query<Hangfire_RavenJobs.Mapping, Hangfire_RavenJobs>();
if (clause != null)
query = query.Where(clause);
return query.ToFacets(new[] {
new Facet
{
Name = "StateName"
}
});
}
public FacetResults GetJobQueueFacets(
IDocumentSession session,
Expression<Func<Hangfire_JobQueues.Mapping, bool>> clause)
{
var query = session.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>();
if (clause != null)
query = query.Where(clause);
return query.ToFacets(new[] {
new Facet
{
Name = "Queue"
}
});
}
private void InitializeQueueProviders()
@@ -11,7 +11,8 @@ using Hangfire.Storage.Monitoring;
using Raven.Abstractions.Data;
using Raven.Client;
using Raven.Client.Linq;
using Hangfire.Raven.JobQueues;
namespace Hangfire.Raven.Storage
{
public class RavenStorageMonitoringApi
@@ -28,29 +29,18 @@ namespace Hangfire.Raven.Storage
public long EnqueuedCount(string queue)
{
using (var repository = _storage.Repository.OpenSession()) {
RavenQueryStatistics stats;
repository.Query<JobQueue>()
.Where(a => a.FetchedAt == null && a.Queue == queue)
.Take(0)
.Statistics(out stats)
.ToList();
return stats.TotalResults;
}
var queueApi = GetQueueApi(queue);
var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
return counters.EnqueuedCount ?? 0;
}
public long FetchedCount(string queue)
{
using (var repository = _storage.Repository.OpenSession()) {
RavenQueryStatistics stats;
repository.Query<JobQueue>()
.Where(a => a.FetchedAt != null && a.Queue == queue)
.Take(0)
.Statistics(out stats)
.ToList();
{
var queueApi = GetQueueApi(queue);
var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
return counters.FetchedCount ?? 0;
return stats.TotalResults;
}
}
public long DeletedListCount()
{
@@ -74,15 +64,12 @@ namespace Hangfire.Raven.Storage
}
private long GetNumberOfJobsByStateName(string stateName)
{
using (var repository = _storage.Repository.OpenSession()) {
RavenQueryStatistics stats;
repository.Query<RavenJob>()
.Where(a => a.StateData.Name == stateName)
.Take(0)
.Statistics(out stats)
.ToList();
using (var repository = _storage.Repository.OpenSession())
{
var facetResults = _storage.GetRavenJobFacets(repository, a => a.StateName == stateName);
var getFacetValues = facetResults.Results["StateName"].Values;
return stats.TotalResults;
return getFacetValues.Sum(a => a.Hits);
}
}
@@ -132,7 +119,7 @@ namespace Hangfire.Raven.Storage
var stats = new Dictionary<DateTime, long>();
using (var repository = _storage.Repository.OpenSession()) {
foreach (var item in dates) {
var id = Repository.GetId(typeof(Counter), formatorAction(item));
var id = _storage.Repository.GetId(typeof(Counter), formatorAction(item));
var counters = repository.Load<Counter>(id);
if (counters != null)
@@ -148,30 +135,22 @@ namespace Hangfire.Raven.Storage
{
using (var transaction = _storage.Repository.OpenSession()) {
var stat = new RavenQueryStatistics();
transaction.Query<Raven_DocumentsByEntityName.Mapping, Raven_DocumentsByEntityName>()
.Where(a => a.Tag == "RavenServers")
transaction.Query<RavenServer>()
.Take(0)
.Statistics(out stat)
.ToList();
var recurringJobs = transaction.Load<RavenSet>("RavenSets/recurring-jobs");
FacetResults facetResults = _storage.Repository
.GetFacets(
"Hangfire/RavenJobs",
new IndexQuery(),
new List<Facet>
{
new Facet
{
Name = "StateName"
}
});
var facetResults = _storage.GetRavenJobFacets(transaction, null);
var getFacetValues = facetResults.Results["StateName"].Values;
var facetJobResults = _storage.GetJobQueueFacets(transaction, null);
var getJobFacetValues = facetJobResults.Results["Queue"].Values;
return new StatisticsDto() {
Servers = stat.TotalResults,
Queues = 2,
Queues = getJobFacetValues.Count,
Recurring = recurringJobs?.Scores?.Count ?? 0,
Succeeded = getFacetValues.FirstOrDefault(a => a.Range == SucceededState.StateName)?.Hits ?? 0,
Scheduled = getFacetValues.FirstOrDefault(a => a.Range == ScheduledState.StateName)?.Hits ?? 0,
@@ -199,15 +178,10 @@ namespace Hangfire.Raven.Storage
}
public JobList<EnqueuedJobDto> EnqueuedJobs(string queue, int from, int perPage)
{
using (var repository = _storage.Repository.OpenSession()) {
var results = repository.Query<JobQueue>()
.Where(a => a.FetchedAt == null && a.Queue == queue)
.Skip(from)
.Take(perPage)
.Select(a => a.JobId);
var queueApi = GetQueueApi(queue);
var enqueuedJobIds = queueApi.GetEnqueuedJobIds(queue, from, perPage);
return EnqueuedJobs(results);
}
return EnqueuedJobs(enqueuedJobIds);
}
public JobList<FailedJobDto> FailedJobs(int from, int count)
{
@@ -224,15 +198,10 @@ namespace Hangfire.Raven.Storage
}
public JobList<FetchedJobDto> FetchedJobs(string queue, int from, int perPage)
{
using (var repository = _storage.Repository.OpenSession()) {
var results = repository.Query<JobQueue>()
.Where(a => a.FetchedAt != null && a.Queue == queue)
.Skip(from)
.Take(perPage)
.Select(a => a.JobId);
var queueApi = GetQueueApi(queue);
var fetchedJobIds = queueApi.GetFetchedJobIds(queue, from, perPage);
return FetchedJobs(results);
}
return FetchedJobs(fetchedJobIds);
}
public JobList<ScheduledJobDto> ScheduledJobs(int from, int count)
{
@@ -279,13 +248,17 @@ namespace Hangfire.Raven.Storage
jobId.ThrowIfNull("jobId");
using (var repository = _storage.Repository.OpenSession()) {
var id = Repository.GetId(typeof(RavenJob), jobId);
var id = _storage.Repository.GetId(typeof(RavenJob), jobId);
var job = repository.Load<RavenJob>(id);
if (job == null) {
return null;
}
return new JobDetailsDto {
CreatedAt = job.CreatedAt,
ExpireAt = repository.Advanced.GetExpire(job),
Job = job.Job.GetJob(),
Job = DeserializeJob(job.InvocationData),
History = job.History,
Properties = job.Parameters
};
@@ -319,7 +292,7 @@ namespace Hangfire.Raven.Storage
var query =
from server in servers
select new ServerDto {
Name = server.Id,
Name = server.Id.Split(new[] { '/' }, 2)[1],
Heartbeat = server.LastHeartbeat,
Queues = server.Data.Queues.ToList(),
StartedAt = server.Data.StartedAt ?? DateTime.MinValue,
@@ -337,11 +310,14 @@ namespace Hangfire.Raven.Storage
string stateName,
Func<RavenJob, Job, Dictionary<string, string>, TDto> selector)
{
using (var repository = _storage.Repository.OpenSession()) {
var jobs = repository.Query<RavenJob>().Where(a => a.StateData.Name == stateName)
.OrderBy(a => a.CreatedAt)
.Skip(from)
.Take(count)
using (var repository = _storage.Repository.OpenSession())
{
var jobs = repository.Query<Hangfire_RavenJobs.Mapping, Hangfire_RavenJobs>()
.Where(a => a.StateName == stateName)
.OrderByDescending(a => a.CreatedAt)
.Skip(from)
.Take(count)
.OfType<RavenJob>()
.ToList();
return DeserializeJobs(jobs, selector);
@@ -351,14 +327,22 @@ namespace Hangfire.Raven.Storage
{
using (var repository = _storage.Repository.OpenSession()) {
var jobs = repository.Load<RavenJob>(
jobIds.Select(a => Repository.GetId(typeof(RavenJob), a))
jobIds.Select(a => _storage.Repository.GetId(typeof(RavenJob), a))
)
.Where(a => a != null && a.StateData != null);
.Where(a => a != null)
.ToList();
return DeserializeJobs(jobs, (jsonJob, job, stateData) => new FetchedJobDto {
Dictionary<string, JobQueue> jobIdToJobQueueMap = repository.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>()
.Where(a => a.JobId.In(jobs.Select(job => job.Id.Split(new char[] { '/' }, 2)[1])) && a.FetchedAt != null)
.OfType<JobQueue>()
.ToDictionary(jobQueue => jobQueue.JobId);
IEnumerable<RavenJob> filteredJobs = jobs.Where(job => jobIdToJobQueueMap.ContainsKey(job.Id.Split(new char[] { '/' }, 2)[1]));
return DeserializeJobs(filteredJobs, (jsonJob, job, stateData) => new FetchedJobDto {
Job = job,
State = jsonJob.StateData.Name,
FetchedAt = jsonJob.StateData.Name == ProcessingState.StateName
State = jsonJob.StateData?.Name,
FetchedAt = jsonJob.StateData?.Name == ProcessingState.StateName
? JobHelper.DeserializeNullableDateTime(stateData["StartedAt"])
: null
});
@@ -367,13 +351,23 @@ namespace Hangfire.Raven.Storage
private JobList<EnqueuedJobDto> EnqueuedJobs(IEnumerable<string> jobIds)
{
using (var repository = _storage.Repository.OpenSession()) {
var jobs = repository.Load<RavenJob>(jobIds.Select(a => Repository.GetId(typeof(RavenJob), a)))
.Where(a => a != null && a.StateData != null);
var jobs = repository.Load<RavenJob>(
jobIds.Select(a => _storage.Repository.GetId(typeof(RavenJob), a))
)
.Where(a => a != null)
.ToList();
return DeserializeJobs(jobs, (jsonJob, job, stateData) => new EnqueuedJobDto {
Dictionary<string, JobQueue> jobIdToJobQueueMap = repository.Query<Hangfire_JobQueues.Mapping, Hangfire_JobQueues>()
.Where(a => a.JobId.In(jobs.Select(job => job.Id.Split(new char[] { '/' }, 2)[1])) && a.FetchedAt == null)
.OfType<JobQueue>()
.ToDictionary(jobQueue => jobQueue.JobId);
IEnumerable<RavenJob> filteredJobs = jobs.Where(job => jobIdToJobQueueMap.ContainsKey(job.Id.Split(new char[] { '/' }, 2)[1]));
return DeserializeJobs(filteredJobs, (jsonJob, job, stateData) => new EnqueuedJobDto {
Job = job,
State = jsonJob.StateData.Name,
EnqueuedAt = jsonJob.StateData.Name == EnqueuedState.StateName
State = jsonJob.StateData?.Name,
EnqueuedAt = jsonJob.StateData?.Name == EnqueuedState.StateName
? JobHelper.DeserializeNullableDateTime(stateData["EnqueuedAt"])
: null
});
@@ -396,13 +390,21 @@ namespace Hangfire.Raven.Storage
Func<RavenJob, Job, Dictionary<string, string>, TDto> selector)
{
var result = from job in jobs
let stateData = job.StateData.Data != null
let stateData = (job.StateData != null) && (job.StateData.Data != null)
? new Dictionary<string, string>(job.StateData.Data, StringComparer.OrdinalIgnoreCase)
: null
let dto = selector(job, job.Job.GetJob(), stateData)
let dto = selector(job, DeserializeJob(job.InvocationData), stateData)
select new KeyValuePair<string, TDto>(job.Id.Split(new char[] { '/' }, 2)[1], dto);
return new JobList<TDto>(result);
}
private IPersistentJobQueueMonitoringApi GetQueueApi(string queueName)
{
var provider = _storage.QueueProviders.GetProvider(queueName);
var monitoringApi = provider.GetJobQueueMonitoringApi();
return monitoringApi;
}
}
}
@@ -7,15 +7,42 @@ namespace Hangfire.Raven.Storage
{
private readonly string _clientId = null;
private TimeSpan _queuePollInterval;
private TimeSpan _distributedLockLifetime;
public RavenStorageOptions()
{
QueuePollInterval = TimeSpan.FromSeconds(15);
InvisibilityTimeout = TimeSpan.FromMinutes(30);
JobExpirationCheckInterval = TimeSpan.FromHours(1);
CountersAggregateInterval = TimeSpan.FromMinutes(5);
TransactionTimeout = TimeSpan.FromMinutes(1);
DistributedLockLifetime = TimeSpan.FromSeconds(30);
_clientId = Guid.NewGuid().ToString().Replace("-", string.Empty);
}
public TimeSpan QueuePollInterval
{
get { return _queuePollInterval; }
set
{
var message = $"The QueuePollInterval property value should be positive. Given: {value}.";
if (value == TimeSpan.Zero)
{
throw new ArgumentException(message, nameof(value));
}
if (value != value.Duration())
{
throw new ArgumentException(message, nameof(value));
}
_queuePollInterval = value;
}
}
public TimeSpan InvisibilityTimeout { get; set; }
public TimeSpan JobExpirationCheckInterval { get; set; }
@@ -23,6 +50,26 @@ namespace Hangfire.Raven.Storage
public TimeSpan TransactionTimeout { get; set; }
public TimeSpan DistributedLockLifetime
{
get { return _distributedLockLifetime; }
set
{
var message = $"The DistributedLockLifetime property value should be positive. Given: {value}.";
if (value == TimeSpan.Zero)
{
throw new ArgumentException(message, nameof(value));
}
if (value != value.Duration())
{
throw new ArgumentException(message, nameof(value));
}
_distributedLockLifetime = value;
}
}
public IEnumerable<string> QueueNames { get; set; }
public string ClientId {
-85
View File
@@ -1,85 +0,0 @@
{
"title": "Hangfire.Raven",
"version": "2.1.0",
"description": "RavenDB job storage for Hangfire",
"copyright": "Copyright @ 2016 Cady, LLC",
"authors": [
"Cady <contact@cady.io>"
],
"buildOptions": {
"allowUnsafe": true,
"debugType": "portable",
"preserveCompilationContext": true
},
"packOptions": {
"tags": [
"Hangfire",
"AspNet",
"MVC",
"OWIN",
"RavenDB",
"Long-Running",
"Background",
"Fire-And-Forget",
"Delayed",
"Recurring",
"Tasks",
"Jobs",
"Scheduler",
"Threading",
"Queues"
],
"iconUrl": "https://avatars2.githubusercontent.com/u/7880472?v=3&s=200",
"projectUrl": "https://github.com/cady-io/hangfire-ravendb",
"licenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.html",
"owners": [
"Cady <contact@cady.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/cady-io/hangfire-ravendb"
},
"summary": "RavenDB job storage for Hangfire"
},
"dependencies": {
"Hangfire.Core": "1.6.4",
"Newtonsoft.Json": "9.0.1",
"RavenDB.Client": "3.5.0-rc-35169"
},
"frameworks": {
"net45": {
"frameworkAssemblies": {
"System.Data": "4.0.0.0",
"System.Reflection": "4.0.0.0"
}
},
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
],
"dependencies": {
"Microsoft.Extensions.DependencyModel": "1.1.0",
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"NETStandard.Library": "1.6.0",
"System.Data.Common": "4.3.0"
}
},
"netstandard1.6": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
],
"dependencies": {
"Microsoft.Extensions.DependencyModel": "1.1.0",
"NETStandard.Library": "1.6.0",
"System.Data.Common": "4.3.0"
}
}
}
}
File diff suppressed because it is too large Load Diff