init commit, hangfire.raven is working

This commit is contained in:
Andrew Markley
2015-10-16 16:07:20 -05:00
parent 7ca1dd3843
commit eeb90e94ba
114 changed files with 11428 additions and 202 deletions
+5
View File
@@ -0,0 +1,5 @@
root = true
[*.cs,*.ps1]
indent_style = space
indent_size = 4
+22
View File
@@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=unset
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
BIN
View File
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="7-Zip.CommandLine" version="9.20.0" />
<package id="coveralls.io" version="1.3.4" />
<package id="Hangfire.Build" version="0.1.3" />
<package id="ilmerge" version="2.14.1208" />
<package id="OpenCover" version="4.5.3723" />
<package id="psake" version="4.4.1" />
<package id="xunit.runners" version="1.9.2" />
</packages>
+57
View File
@@ -0,0 +1,57 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Core", "src\Hangfire.Core\Hangfire.Core.csproj", "{C995EA9E-56EE-4951-8260-D94260A7F4C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer", "src\Hangfire.SqlServer\Hangfire.SqlServer.csproj", "{A523C0E3-097D-4869-977F-15A717EA3E83}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.Msmq", "src\Hangfire.SqlServer.Msmq\Hangfire.SqlServer.Msmq.csproj", "{762BE479-0AEC-47E0-8F9C-34FA54641749}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Core.Tests", "tests\Hangfire.Core.Tests\Hangfire.Core.Tests.csproj", "{E13C3543-39A3-475C-BB43-2E311E634843}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.RabbitMq", "src\Hangfire.SqlServer.RabbitMq\Hangfire.SqlServer.RabbitMq.csproj", "{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.RabbitMq.Tests", "tests\Hangfire.SqlServer.RabbitMq.Tests\Hangfire.SqlServer.RabbitMq.Tests.csproj", "{2654D751-33D5-411D-A187-D057FED9CD25}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.Build.0 = Release|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.Build.0 = Release|Any CPU
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.Build.0 = Release|Any CPU
{E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.Build.0 = Release|Any CPU
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.Build.0 = Release|Any CPU
{2654D751-33D5-411D-A187-D057FED9CD25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2654D751-33D5-411D-A187-D057FED9CD25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2654D751-33D5-411D-A187-D057FED9CD25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2654D751-33D5-411D-A187-D057FED9CD25}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E13C3543-39A3-475C-BB43-2E311E634843} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
{25909EE6-2A56-4EAC-9D0E-436FF2435E88} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
{2654D751-33D5-411D-A187-D057FED9CD25} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = src\Hangfire.Core\Hangfire.Core.csproj
EndGlobalSection
EndGlobal
+45
View File
@@ -0,0 +1,45 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSample", "samples\ConsoleSample\ConsoleSample.csproj", "{C02BB718-2AE4-434C-8668-C894FF663FCE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{119DA7FA-B94C-4B63-AEC9-428EF834E0D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Raven", "src\Hangfire.SqlServer\Hangfire.Raven.csproj", "{A523C0E3-097D-4869-977F-15A717EA3E83}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{766BE831-F758-46BC-AFD3-BBEEFE0F686F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspecs", "nuspecs", "{5E687025-A525-4534-8869-CB685C7B11C4}"
ProjectSection(SolutionItems) = preProject
nuspecs\Hangfire.Raven.nuspec = nuspecs\Hangfire.Raven.nuspec
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F710C9CF-69F9-4373-9B74-ABB185BF985B}"
ProjectSection(SolutionItems) = preProject
.nuget\packages.config = .nuget\packages.config
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.Build.0 = Release|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C02BB718-2AE4-434C-8668-C894FF663FCE} = {119DA7FA-B94C-4B63-AEC9-428EF834E0D8}
EndGlobalSection
EndGlobal
+6
View File
@@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002ERedis_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002ESqlServer_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="Hangfire.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;Filter ModuleMask="Hangfire.SqlServer.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String></wpf:ResourceDictionary>
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+170
View File
@@ -0,0 +1,170 @@
This is a work on progress! Don't use it yet.
Hangfire
=========
[![Official Site](https://img.shields.io/badge/site-hangfire.io-blue.svg)](http://hangfire.io) [![Latest version](https://img.shields.io/nuget/v/Hangfire.svg)](https://www.nuget.org/packages?q=hangfire) [![Build status](https://ci.appveyor.com/api/projects/status/70m632jkycqpnsp9?svg=true)](https://ci.appveyor.com/project/odinserj/hangfire-525) [![Build Status](https://travis-ci.org/HangfireIO/Hangfire.svg)](https://travis-ci.org/HangfireIO/Hangfire) [![License LGPLv3](https://img.shields.io/badge/license-LGPLv3-green.svg)](http://www.gnu.org/licenses/lgpl-3.0.html)
Incredibly easy way to perform **fire-and-forget**, **delayed** and **recurring jobs** inside **ASP.NET applications**. CPU and I/O intensive, long-running and short-running jobs are supported. No Windows Service / Task Scheduler required. Backed by Redis, SQL Server, SQL Azure or MSMQ.
Hangfire provides unified programming model to handle background tasks in a **reliable way** and run them on shared hosting, dedicated hosting or in cloud. You can start with a simple setup and grow computational power for background jobs with time for these scenarios:
- mass notifications/newsletter;
- batch import from xml, csv, json;
- creation of archives;
- firing off web hooks;
- deleting users;
- building different graphs;
- image/video processing;
- purge temporary files;
- recurring automated reports;
- database maintenance;
- *…and so on.*
Hangfire is a .NET Framework alternative to [Resque](https://github.com/resque/resque), [Sidekiq](http://sidekiq.org), [delayed_job](https://github.com/collectiveidea/delayed_job), [Celery](http://www.celeryproject.org).
![Hangfire Dashboard](http://hangfire.io/img/ui/dashboard-sm.png)
Installation
-------------
Hangfire is available as a NuGet package. So, you can install it using the NuGet Package Console window:
```
PM> Install-Package Hangfire
```
After install, update your existing [OWIN Startup](http://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection) file with the following lines of code. If you do not have this class in your project or don't know what is it, please read the [Quick start](http://docs.hangfire.io/en/latest/quickstart.html) guide to learn about how to install Hangfire.
```csharp
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.UseSqlServerStorage("<connection string or its name>");
app.UseHangfireServer();
app.UseHangfireDashboard();
}
```
Usage
------
This is incomplete list of features, to see all of them, check the [official site](http://hangfire.io) and the [documentation](http://docs.hangfire.io).
[**Fire-and-forget tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/calling-methods-in-background.html)
Enqueued background jobs are being executed inside a dedicated worker pool threads as soon as possible, shortening your request processing time.
```csharp
BackgroundJob.Enqueue(() => Console.WriteLine("Simple!"));
```
[**Delayed tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/calling-methods-with-delay.html)
Scheduled background jobs are being executed only after given amount of time.
```csharp
BackgroundJob.Schedule(() => Console.WriteLine("Reliable!"), TimeSpan.FromDays(7));
```
[**Recurring tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/performing-recurrent-tasks.html)
Recurring jobs were never been simpler, just call the following method to perform any kind of recurring task using the [CRON expressions](http://en.wikipedia.org/wiki/Cron#CRON_expression).
```csharp
RecurringJob.AddOrUpdate(() => Console.WriteLine("Transparent!"), Cron.Daily);
```
**Continuations**
Continuations allow you to define complex workflows by chaining multiple background jobs together.
```csharp
var id = BackgroundJob.Enqueue(() => Console.WriteLine("Hello, "));
BackgroundJob.ContinueWith(id, () => Console.WriteLine("world!"));
```
**Process them inside a web application…**
You can process background tasks in any OWIN compatible application frameworks, including [ASP.NET MVC](http://www.asp.net/mvc), [ASP.NET Web API](http://www.asp.net/web-api), [FubuMvc](http://fubu-project.org), [Nancy](http://nancyfx.org), etc. Forget about [AppDomain unloads, Web Garden & Web Farm issues](http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/) Hangfire is reliable for web applications from scratch, even on shared hosting.
```csharp
app.UseHangfireServer();
```
**… or anywhere else**
In console application, Windows Service, Azure Worker Role, etc.
```csharp
using (new BackgroundJobServer())
{
Console.WriteLine("Hangfire Server started. Press ENTER to exit...");
Console.ReadLine();
}
```
Questions? Problems?
---------------------
Open-source project are developing more smoothly, when all discussions are held in public.
If you have any questions, problems related to the Hangfire usage or want to discuss new features, please visit the [discussion forum](http://discuss.hangfire.io). You can sign in there using your existing Google or GitHub account, so it's very simple to start using it.
If you've discovered a bug, please report it to the [Hangfire GitHub Issues](https://github.com/odinserj/Hangfire/issues?state=open). Detailed reports with stack traces, actual and expected behavours are welcome.
Related Projects
-----------------
Please see the [Extensions](http://hangfire.io/extensions.html) page on official site.
Building the sources
---------------------
To build a solution and get assembly files, just run the following command. All build artifacts, including `*.pdb` files will be placed into the `build` folder. **Before proposing a pull request, please use this command to ensure everything is ok.** Btw, you can execute this command from Package Manager Console window.
```
build
```
To build NuGet packages as well as an archive file, use the `pack` command as shown below. You can find the result files in the `build` folder.
```
build pack
```
To see the full list of avalable commands, pass the `-docs` switch:
```
build -docs
```
Hangfire uses [psake](https://github.com/psake/psake) build automation tool. All psake tasks and functions defined in `psake-build.ps1` (for this project) and `psake-common.ps1` (for other Hangfire projects) files. Thanks to the psake project, they are very simple to use and modify!
License
--------
Copyright © 2013-2014 Sergey Odinokov.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses).
Legal
------
By submitting a Pull Request, you disavow any rights or claims to any changes submitted to the Hangfire project and assign the copyright of those changes to Sergey Odinokov.
If you cannot or do not want to reassign those rights (your employment contract for your employer may not allow this), you should not submit a PR. Open an issue and someone else can do the work.
This is a legal way of saying "If you submit a PR to us, that code becomes ours". 99.9% of the time that's what you intend anyways; we hope it doesn't scare you away from contributing.
+37
View File
@@ -0,0 +1,37 @@
Please see http://docs.hangfire.io for more information on using Hangfire. The
`Hangfire` package is using SQL Server as a job storage and intended to run in
any OWIN-based web application.
To run Hangfire in your application, update the existing OWIN Startup class or
create the file `Startup.cs` in the root folder of your application as shown
below. Please see also http://docs.hangfire.io/en/latest/quickstart.html.
!!! DASHBOARD REQUIRES AUTH CONFIGURATION !!!
By default, ONLY LOCAL requests are allowed to access the Dashboard. Please
see the `Configuring Dashboard authorization` section in Hangfire documentation:
http://docs.hangfire.io/en/latest/configuration/configuring-authorization.html
Sample OWIN Startup class
--------------------------
using Hangfire;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MyWebApplication.Startup))]
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage("<name or connection string>");
app.UseHangfireDashboard();
app.UseHangfireServer();
}
}
}
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Hangfire.Core</id>
<version>0.0.0</version>
<title>Hangfire Core Components</title>
<authors>Sergey Odinokov</authors>
<owners>odinserj</owners>
<projectUrl>http://hangfire.io/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
<description>Core components for Hangfire (background job system for ASP.NET applications).</description>
<copyright>Copyright © 2013-2015 Sergey Odinokov</copyright>
<tags>Hangfire OWIN Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues</tags>
<releaseNotes>http://hangfire.io/blog/
1.5.0
* Added `IBackgroundProcess` interface that allows to implement custom background processes.
* Added `BackgroundProcessingServer` class for creating a server instance with completely custom background processes.
* Added Support for running multiple `BackgroundJobServer` instances in the same AppDomain without explicit server names.
* Added `JobActivatorScope` class to support custom IoC scopes/lifetimes.
* Added Allow to specify a custom queue for recurring jobs (by @maleet).
* Added Allow to use CancellationToken type as a job method parameter.
* Added It is now possible to query the storage in state applying filters.
* Added Allow to pass custom filters to `BackgroundJobServer` via options.
* Added Option to hide the "Back to site" link (by @dradovic).
* Changed Server components and background processes now use `Task` with `TaskCreationOptions.LongRunning`.
* Changed ServerIds are now generated without ServerName, server names are deprecated.
* Changed Arguments Renderer now supports TimeSpan and DateTime (by @yngndrw).
* Changed Minor improvements for Dashboard navigation (by @pwelter34).
* Changed Worker does not process a job when a transition to the Processing state failed (by @yngndrw).
* Changed `BackgroundJobClientException` is thrown instead of `CreateJobFailedException`.
* Changed WorkerId is now auto-generated and based on a GUID.
* Changed Use StackTraceFormatter package for marking-up stack traces (by @atifaziz).
* Changed Updated Dashboard CSS to Twitter Bootstrap 3.3.5.
* Fixed Prevent worker from fetching and processing a job on shutdown.
* Fixed `TaskCanceledException` thrown from a job method leads to failed job as expected.
* Fixed Make `DelayedJobScheduler` resilient to empty background job ids.
* Fixed Logical error in SqlServerDistributedLockTimeout value check (by @Tom-Kelly).
* Fixed `OverflowException` in Dashboard when job duration exceeds int.MaxValue (by @mavenius).
* Fixed "More details" button in Dashboard changes to "Less details" when collapsed (by @pascalberger).
</releaseNotes>
<dependencies>
<dependency id="Owin" version="1.0" />
<dependency id="Newtonsoft.Json" version="5.0.0" />
</dependencies>
</metadata>
<files>
<file src="Net45\Hangfire.Core.dll" target="lib\net45" />
<file src="Net45\Hangfire.Core.xml" target="lib\net45" />
<file src="Net45\Hangfire.Core.pdb" target="lib\net45" />
<file src="..\src\Hangfire.Core\**\*.cs;..\src\Hangfire.Core\**\*.cshtml" target="src" />
</files>
</package>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<package >
<metadata>
<dependencies>
<dependency id="Hangfire.Core" version="[0.0.0]" />
</dependencies>
</metadata>
<files>
<file src="Net45\Hangfire.SqlServer.dll" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.xml" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.pdb" target="lib\net45" />
<file src="Tools\DefaultInstall.sql" target="tools\install.sql" />
<file src="..\src\Hangfire.SqlServer\**\*.cs" target="src" />
</files>
</package>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Hangfire.SqlServer.MSMQ</id>
<version>0.0.0</version>
<title>Hangfire MSMQ Queues for SQL Server Storage</title>
<authors>Sergey Odinokov</authors>
<owners>odinserj</owners>
<projectUrl>http://hangfire.io/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
<description>MSMQ queues support for SQL Server job storage implementation for Hangfire (background job system for ASP.NET applications).</description>
<copyright>Copyright © 2014-2015 Sergey Odinokov</copyright>
<tags>Hangfire SqlServer MSMQ</tags>
<releaseNotes>http://hangfire.io/blog/
1.5.0
* Added Support for remote MSMQ queues through DTC transactions.
1.4.0
* Changed Speed up `GetCount` method with native implementation.
* Fixed Incorrect order of fetching when multiple queues used.
</releaseNotes>
<dependencies>
<dependency id="Hangfire.Core" version="[0.0.0]" />
<dependency id="Hangfire.SqlServer" version="1.2.2" />
</dependencies>
</metadata>
<files>
<file src="Net45\Hangfire.SqlServer.Msmq.dll" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.Msmq.xml" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.Msmq.pdb" target="lib\net45" />
<file src="..\src\Hangfire.SqlServer.Msmq\**\*.cs" target="src" />
</files>
</package>
@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Hangfire.SqlServer.RabbitMQ</id>
<version>0.0.0</version>
<title>Hangfire RabbitMQ Queues for SQL Server Storage</title>
<authors>Denny Ferrassoli</authors>
<owners>dennyferra</owners>
<projectUrl>http://hangfire.io/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
<description>RabbitMQ queues support for SQL Server job storage implementation for Hangfire (background job system for ASP.NET applications).</description>
<copyright>Copyright © 2014 Denny Ferrassoli</copyright>
<tags>Hangfire SqlServer RabbitMQ</tags>
<releaseNotes>http://hangfire.io/blog/
1.4.2
* Fixed Incompatibility issue with RabbitMQ.Client >= 3.4.0 (by @justmara).
1.4.0
* Added Allow a URI to be used for `RabbitMQConnectionConfiguration` (by @dennyferra).
</releaseNotes>
<dependencies>
<dependency id="Hangfire.Core" version="[0.0.0]" />
<dependency id="Hangfire.SqlServer" version="[0.0.0]" />
<dependency id="RabbitMQ.Client" version="3.3.0" />
</dependencies>
</metadata>
<files>
<file src="Net45\Hangfire.SqlServer.RabbitMQ.dll" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.RabbitMQ.xml" target="lib\net45" />
<file src="Net45\Hangfire.SqlServer.RabbitMQ.xml" target="lib\net45" />
<file src="..\src\Hangfire.SqlServer.RabbitMQ\**\*.cs" target="src" />
</files>
</package>
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Hangfire</id>
<version>0.0.0</version>
<title>Hangfire</title>
<authors>Sergey Odinokov</authors>
<owners>odinserj</owners>
<projectUrl>http://hangfire.io/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
<description>
An easy and reliable way to perform fire-and-forget, delayed and recurring long-running, short-running, CPU or I/O intensive tasks inside ASP.NET application. No Windows Service / Task Scheduler required. Even ASP.NET is not required.
Backed by Redis, SQL Server, SQL Azure or MSMQ. This is a .NET alternative to Sidekiq, Resque and Celery.
http://hangfire.io/
</description>
<copyright>Copyright © 2013-2015 Sergey Odinokov</copyright>
<tags>Hangfire AspNet MVC OWIN SqlServer Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues</tags>
<releaseNotes>http://hangfire.io/blog/
1.5.0
Hangfire.Core
* Added `IBackgroundProcess` interface that allows to implement custom background processes.
* Added `BackgroundProcessingServer` class for creating a server instance with completely custom background processes.
* Added Support for running multiple `BackgroundJobServer` instances in the same AppDomain without explicit server names.
* Added `JobActivatorScope` class to support custom IoC scopes/lifetimes.
* Added Allow to specify a custom queue for recurring jobs (by @maleet).
* Added Allow to use CancellationToken type as a job method parameter.
* Added It is now possible to query the storage in state applying filters.
* Added Allow to pass custom filters to `BackgroundJobServer` via options.
* Added Option to hide the "Back to site" link (by @dradovic).
* Changed Server components and background processes now use `Task` with `TaskCreationOptions.LongRunning`.
* Changed ServerIds are now generated without ServerName, server names are deprecated.
* Changed Arguments Renderer now supports TimeSpan and DateTime (by @yngndrw).
* Changed Minor improvements for Dashboard navigation (by @pwelter34).
* Changed Worker does not process a job when a transition to the Processing state failed (by @yngndrw).
* Changed `BackgroundJobClientException` is thrown instead of `CreateJobFailedException`.
* Changed WorkerId is now auto-generated and based on a GUID.
* Changed Use StackTraceFormatter package for marking-up stack traces (by @atifaziz).
* Changed Updated Dashboard CSS to Twitter Bootstrap 3.3.5.
* Fixed Prevent worker from fetching and processing a job on shutdown.
* Fixed `TaskCanceledException` thrown from a job method leads to failed job as expected.
* Fixed Make `DelayedJobScheduler` resilient to empty background job ids.
* Fixed Logical error in SqlServerDistributedLockTimeout value check (by @Tom-Kelly).
* Fixed `OverflowException` in Dashboard when job duration exceeds int.MaxValue (by @mavenius).
* Fixed "More details" button in Dashboard changes to "Less details" when collapsed (by @pascalberger).
Hangfire.SqlServer
* Added Ability to specify the schema for SQL Server job storage (by @ryanmwright).
* Added `TransactionTimeout` option for configuring the `TransactionScope` timeout.
* Added `DashboardJobListLimit` option to prevent full table scans in Dashboard.
* Changed `SqlServerDistributedLock` class is now public.
* Changed Use `rowlock` table hint in job fetch query to prevent escalation.
* Changed Dramatically improved page load times in Dashboard.
* Changed Instant re-queue for background jobs even after ungraceful shutdown.
* Changed `JobQueue.Queue` and `Server.Id` column length increased to 50 and 100 respectively (by @maleet).
Hangfire.SqlServer.Msmq
* Added Support for remote MSMQ queues through DTC transactions.
</releaseNotes>
<dependencies>
<dependency id="Hangfire.Core" version="[0.0.0]" />
<dependency id="Hangfire.SqlServer" version="[0.0.0]" />
<dependency id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" />
</dependencies>
</metadata>
<files>
<file src="Content\readme.txt" target="readme.txt" />
</files>
</package>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C02BB718-2AE4-434C-8668-C894FF663FCE}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ConsoleSample</RootNamespace>
<AssemblyName>ConsoleSample</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>89909a41</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="Hangfire.Core, Version=1.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.Core.1.5.1\lib\net45\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="GenericServices.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hangfire.SqlServer\Hangfire.Raven.csproj">
<Project>{a523c0e3-097d-4869-977f-15a717ea3e83}</Project>
<Name>Hangfire.Raven</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+12
View File
@@ -0,0 +1,12 @@
using System;
namespace ConsoleSample
{
public class GenericServices<TType>
{
public void Method<TMethod>(TType arg1, TMethod arg2)
{
Console.WriteLine("Arg1: {0}, Arg2: {1}", arg1, arg2);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using Hangfire;
using HangFire.Raven.Storage;
namespace ConsoleSample
{
public static class Program
{
public static int x = 0;
public static void Main()
{
// you can use Raven Storage and specify the connection string and database name
GlobalConfiguration.Configuration
.UseColouredConsoleLogProvider()
.UseRavenStorage("http://localhost:9090", "hangfire2");
// you can use Raven Embedded Storage which runs in memory!
//GlobalConfiguration.Configuration
// .UseColouredConsoleLogProvider()
// .UseEmbeddedRavenStorage();
//you have to create an instance of background job server at least once for background jobs to run
var client = new BackgroundJobServer();
BackgroundJob.Enqueue(() => Console.WriteLine("Background Job: Hello, world!"));
RecurringJob.AddOrUpdate(() => test(), Cron.Minutely);
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
public static void test()
{
Console.WriteLine($"{x++} Cron Job: Hello, world!");
}
}
}
@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// 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: AssemblyTitle("Hangfire.Hosts.Console")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Hangfire.Hosts.Console")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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("3c3451fc-663d-4e33-ab72-d92beae38a97")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+110
View File
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using Hangfire;
namespace ConsoleSample
{
public class Services
{
private static readonly Random _random = new Random();
public void EmptyDefault()
{
}
[Queue("critical")]
public void EmptyCritical()
{
}
[AutomaticRetry(Attempts = 0)]
public void Error()
{
Console.WriteLine("Beginning error task...");
throw new InvalidOperationException(null, new FileLoadException());
}
[Queue("critical")]
public void Random(int number)
{
int time;
lock (_random)
{
time = _random.Next(10);
}
if (time < 5)
{
throw new Exception();
}
Thread.Sleep(TimeSpan.FromSeconds(5 + time));
Console.WriteLine("Finished task: " + number);
}
public void Cancelable(int iterationCount, IJobCancellationToken token)
{
try
{
for (var i = 1; i <= iterationCount; i++)
{
Thread.Sleep(1000);
Console.WriteLine("Performing step {0} of {1}...", i, iterationCount);
token.ThrowIfCancellationRequested();
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancellation requested, exiting...");
throw;
}
}
[DisplayName("Name: {0}")]
public void Args(string name, int authorId, DateTime createdAt)
{
Console.WriteLine("{0}, {1}, {2}", name, authorId, createdAt);
}
public void Custom(int id, string[] values, CustomObject objects, DayOfWeek dayOfWeek)
{
}
public void FullArgs(
bool b,
int i,
char c,
DayOfWeek e,
string s,
TimeSpan t,
DateTime d,
CustomObject o,
string[] sa,
int[] ia,
long[] ea,
object[] na,
List<string> sl)
{
}
public class CustomObject
{
public int Id { get; set; }
public CustomObject[] Children { get; set; }
}
public void Write(char character)
{
Console.Write(character);
}
public void WriteBlankLine()
{
Console.WriteLine();
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<connectionStrings>
<add name="RavenDebug" connectionString="URL=http://localhost:9090" />
</connectionStrings>
</configuration>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Hangfire.Core" version="1.5.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
</packages>
@@ -0,0 +1,12 @@
using System.Web.Mvc;
namespace MvcSample
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
}
@@ -0,0 +1,19 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcSample
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
+61
View File
@@ -0,0 +1,61 @@
body {
font-size: .85em;
font-family: "Segoe UI", Verdana, Helvetica, Sans-Serif;
color: #232323;
background-color: #fff;
}
header, footer, nav, section {
display: block;
}
/* Styles for basic forms
-----------------------------------------------------------*/
fieldset {
border: 1px solid #ddd;
padding: 0 1.4em 1.4em 1.4em;
margin: 0 0 1.5em 0;
}
legend {
font-size: 1.2em;
font-weight: bold;
}
textarea {
min-height: 75px;
}
.editor-label {
margin: 1em 0 0 0;
}
.editor-field {
margin: 0.5em 0 0 0;
}
/* Styles for validation helpers
-----------------------------------------------------------*/
.field-validation-error {
color: #f00;
}
.field-validation-valid {
display: none;
}
.input-validation-error {
border: 1px solid #f00;
background-color: #fee;
}
.validation-summary-errors {
font-weight: bold;
color: #f00;
}
.validation-summary-valid {
display: none;
}
+1
View File
@@ -0,0 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="MvcSample.MvcApplication" Language="C#" %>
+19
View File
@@ -0,0 +1,19 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcSample
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
}
+203
View File
@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{FE5EAE49-2627-42B6-8C2B-EBE4D45B4D97}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MvcSample</RootNamespace>
<AssemblyName>MvcSample</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<MvcProjectUpgradeChecked>true</MvcProjectUpgradeChecked>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>C:\Users\odinserj\Documents\GitHub\Hangfire\Backup6\</UpgradeBackupLocation>
<OldToolsVersion>4.0</OldToolsVersion>
<UseGlobalApplicationHostFile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<NuGetPackageImportStamp>5fc7aa54</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http.Extensions">
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.13\lib\net45\System.Net.Http.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Primitives">
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.13\lib\net45\System.Net.Http.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http">
</Reference>
<Reference Include="System.Net.Http.WebRequest">
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\FilterConfig.cs" />
<Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Startup.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Global.asax" />
<Content Include="Content\Site.css" />
<Content Include="Web.config" />
<Content Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon>
</Content>
<Content Include="Web.Release.config">
<DependentUpon>Web.config</DependentUpon>
</Content>
<Content Include="Views\Web.config" />
<Content Include="Views\_ViewStart.cshtml" />
<Content Include="Views\Shared\Error.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" />
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
<Folder Include="Controllers\" />
<Folder Include="Models\" />
<Folder Include="Scripts\" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hangfire.SqlServer\Hangfire.Raven.csproj">
<Project>{a523c0e3-097d-4869-977f-15a717ea3e83}</Project>
<Name>Hangfire.Raven</Name>
</ProjectReference>
<ProjectReference Include="..\ConsoleSample\ConsoleSample.csproj">
<Project>{c02bb718-2ae4-434c-8668-c894ff663fce}</Project>
<Name>ConsoleSample</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>0</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:56909/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target> -->
</Project>
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=App_005FStart/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.InteropServices;
// 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: AssemblyTitle("Hangfire.MvcSample")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Hangfire.MvcSample")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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("1579df48-9733-47a5-93b6-8b459e9912fc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+26
View File
@@ -0,0 +1,26 @@
using Hangfire;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Owin;
using MvcSample;
using Owin;
[assembly: OwinStartup(typeof(Startup))]
namespace MvcSample
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage(@"Server=.\sqlexpress;Database=Hangfire.Sample;Trusted_Connection=True;")
.UseMsmqQueues(@".\Private$\hangfire{0}", "default", "critical")
.UseDashboardMetric(SqlServerStorage.ActiveConnections)
.UseDashboardMetric(SqlServerStorage.TotalConnections)
.UseDashboardMetric(DashboardMetrics.FailedCount);
app.UseHangfireDashboard();
}
}
}
@@ -0,0 +1,17 @@
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Error</title>
</head>
<body>
<hgroup>
<h1>Error.</h1>
<h2>An error occurred while processing your request.</h2>
</hgroup>
</body>
</html>
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<controls>
<add assembly="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
</configuration>
@@ -0,0 +1,3 @@
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<!-- For more information on using Web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an atrribute "name" that has a value of "MyDB".
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your Web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<!-- For more information on using Web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an atrribute "name" that has a value of "MyDB".
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your Web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=152368
-->
<configuration>
<appSettings>
<add key="webpages:Version" value="2.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="PreserveLoginUrl" value="true" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
<system.web>
<httpRuntime targetFramework="4.5" />
<compilation debug="true" targetFramework="4.5" />
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
<pages>
<namespaces>
<add namespace="System.Web.Helpers" />
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages" />
</namespaces>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="EntityFramework" publicKeyToken="b77a5c561934e089" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="5.0.0" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Net.Http" version="2.2.13" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" userInstalled="true" />
<package id="Owin" version="1.0" targetFramework="net45" userInstalled="true" />
</packages>
@@ -0,0 +1,208 @@
// This file is part of Hangfire.
// Copyright 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Data;
using System.Threading;
using Hangfire.Annotations;
using HangFire.Raven;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.DistributedLock
{
public class RavenDistributedLock : IDisposable
{
private const string LockMode = "Exclusive";
private const string LockOwner = "Session";
private const int CommandTimeoutAdditionSeconds = 1;
private static readonly IDictionary<int, string> LockErrorMessages = new Dictionary<int, string>
{
{ -1, "The lock request timed out" },
{ -2, "The lock request was canceled" },
{ -3, "The lock request was chosen as a deadlock victim" },
{ -999, "Indicates a parameter validation or other call error" }
};
private static readonly ThreadLocal<Dictionary<string, int>> AcquiredLocks = new ThreadLocal<Dictionary<string, int>>(() => new Dictionary<string, int>());
private static RavenStorage _storage;
private static string _resource;
private static RavenStorageOptions _options;
private static Timer _heartbeatTimer = null;
private bool _completed;
public RavenDistributedLock([NotNull] RavenStorage storage, [NotNull] string resource, TimeSpan timeout, RavenStorageOptions options)
{
storage.ThrowIfNull("storage");
resource.ThrowIfNull("resource");
if ((timeout.TotalSeconds + CommandTimeoutAdditionSeconds) > int.MaxValue) {
throw new ArgumentException(string.Format("The timeout specified is too large. Please supply a timeout equal to or less than {0} seconds",
int.MaxValue - CommandTimeoutAdditionSeconds),
"timeout");
}
_storage = storage;
_resource = resource;
_options = options;
if (!AcquiredLocks.Value.ContainsKey(_resource)) {
Acquire(_resource, timeout);
AcquiredLocks.Value[_resource] = 1;
} 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;
Release(_resource);
AcquiredLocks.Value.Remove(_resource);
}
internal static void Acquire(string resource, TimeSpan timeout)
{
try {
RemoveDeadLocks();
// Check lock
DateTime lockTimeoutTime = DateTime.Now.Add(timeout);
bool isLockedBySomeoneElse;
bool isFirstAttempt = true;
do {
using (var repository = new Repository()) {
isLockedBySomeoneElse = repository.Session.Query<DistributedLocks>()
.FirstOrDefault(t => t.Resource == resource && t.ClientId == _options.ClientId) != null;
}
if (isFirstAttempt == true) {
isFirstAttempt = false;
} else {
Thread.Sleep((int)timeout.TotalMilliseconds / 10);
}
}
while ((isLockedBySomeoneElse == true) && (lockTimeoutTime >= DateTime.Now));
// Set lock
if (isLockedBySomeoneElse == false) {
using (var repository = new Repository()) {
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == resource).ToList();
if (!distributedLocks.Any()) {
distributedLocks.Add(new DistributedLocks
{
Resource = resource,
});
}
foreach (var distributedLock in distributedLocks) {
distributedLock.ClientId = _options.ClientId;
distributedLock.LockCount = 1;
distributedLock.Heartbeat = DateTime.UtcNow;
repository.Save(distributedLock);
}
}
StartHeartBeat();
} else {
throw new RavenDistributedLockException(string.Format("Could not place a lock on the resource '{0}': {1}.", _resource, "The lock request timed out"));
}
} catch (Exception ex) {
if (ex is RavenDistributedLockException) {
throw;
} else {
throw new RavenDistributedLockException(string.Format("Could not place a lock on the resource '{0}': {1}.", _resource, "Check inner exception for details"), ex);
}
}
}
internal static void Release(string resource)
{
try {
RemoveDeadLocks();
// Remove resource lock
using (var repository = new Repository()) {
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.ClientId == _options.ClientId).ToList();
foreach (var distributedLock in distributedLocks) {
repository.Delete(distributedLock);
}
}
if (_heartbeatTimer != null) {
_heartbeatTimer.Dispose();
_heartbeatTimer = null;
}
} catch (Exception ex) {
throw new RavenDistributedLockException(string.Format("Could not release a lock on the resource '{0}': {1}.", _resource, "Check inner exception for details"), ex);
}
}
private static void StartHeartBeat()
{
TimeSpan timerInterval = TimeSpan.FromMilliseconds(_options.DistributedLockLifetime.TotalMilliseconds / 5);
_heartbeatTimer = new Timer(state =>
{
try {
using (var repository = new Repository()) {
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.ClientId == _options.ClientId).ToList();
foreach (var distributedLock in distributedLocks) {
distributedLock.Heartbeat = DateTime.UtcNow;
repository.Save(distributedLock);
}
}
} catch (Exception ex) {
Console.WriteLine("Unable to update heartbeat on the resource '{0}'", ex, _resource);
}
}, null, timerInterval, timerInterval);
}
private static void RemoveDeadLocks()
{
using (var repository = new Repository()) {
var heartBeat = DateTime.UtcNow.Subtract(_options.DistributedLockLifetime);
var deadLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.Heartbeat == heartBeat).ToList();
foreach (var deadlock in deadLocks) {
repository.Delete(deadlock);
}
}
}
}
}
@@ -0,0 +1,30 @@
using System;
namespace Hangfire.Raven.DistributedLock
{
/// <summary>
/// Represents exceptions for distributed lock implementation for MongoDB
/// </summary>
[Serializable]
public class RavenDistributedLockException : Exception
{
/// <summary>
/// Creates exception
/// </summary>
/// <param name="message">Exception message</param>
public RavenDistributedLockException(string message)
: base(message)
{
}
/// <summary>
/// Creates exception with inner exception
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public RavenDistributedLockException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
@@ -0,0 +1,15 @@
using System;
namespace Hangfire.Raven.Entities
{
public class AggregatedCounter
{
public string Id { get; set; }
public string Key { get; set; }
public long Value { get; set; }
public DateTime? ExpireAt { get; set; }
}
}
@@ -0,0 +1,18 @@
using Hangfire.Raven.Entities.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Hangfire.Raven.Entities
{
public class Counter : BaseEntity
{
public string Key { get; set; }
public int Value { get; set; }
public DateTime? ExpireAt { get; set; }
}
}
@@ -0,0 +1,16 @@
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class DistributedLocks : BaseEntity
{
public string Resource { get; set; }
public string ClientId { get; set; }
public int LockCount { get; set; }
public DateTime Heartbeat { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace Hangfire.Raven.Entities
{
public class EnqueuedAndFetchedCount
{
public int? EnqueuedCount { get; set; }
public int? FetchedCount { get; set; }
}
}
@@ -0,0 +1,7 @@
namespace Hangfire.Raven.Entities.Identity
{
public class BaseEntity
{
public string Id { get; set; }
}
}
@@ -0,0 +1,27 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Raven.Entities.Identity;
namespace Hangfire.Raven.Entities
{
internal class JobParameter : BaseEntity
{
public string JobId { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
}
@@ -0,0 +1,12 @@
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class JobQueue : BaseEntity
{
public string JobId { get; set; }
public string Queue { get; set; }
public DateTime? FetchedAt { get; set; }
}
}
@@ -0,0 +1,88 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Annotations;
using Hangfire.Storage;
using HangFire.Raven;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.Entities
{
public class RavenFetchedJob : IFetchedJob
{
private readonly RavenStorage _storage;
private bool _requeued { get; set; }
private bool _removedFromQueue { get; set; }
private bool _disposed { get; set; }
public string Id { get; set; }
public string JobId { get; set; }
public string Queue { get; set; }
public RavenFetchedJob(
[NotNull] RavenStorage storage,
JobQueue jobQueue)
{
storage.ThrowIfNull("storage");
jobQueue.ThrowIfNull("jobQueue");
_storage = storage;
JobId = jobQueue.JobId;
Queue = jobQueue.Queue;
Id = jobQueue.Id;
}
public void RemoveFromQueue()
{
using (var repository = new Repository()) {
var job = repository.Session.Load<JobQueue>(Id);
if (job != null) {
repository.Delete(job);
}
}
_removedFromQueue = true;
}
public void Requeue()
{
using (var repository = new Repository()) {
var job = repository.Session.Load<JobQueue>(Id);
job.FetchedAt = null;
repository.Save(job);
}
_requeued = true;
}
public void Dispose()
{
if (_disposed) {
return;
}
if (!_removedFromQueue && !_requeued) {
Requeue();
}
_disposed = true;
}
}
}
@@ -0,0 +1,31 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class RavenHash : BaseEntity
{
public string Key { get; set; }
public string Field { get; set; }
public string Value { get; set; }
public DateTime? ExpireAt { get; set; }
public string JobId { get; set; }
}
}
@@ -0,0 +1,34 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
internal class RavenJob : BaseEntity
{
public string InvocationData { get; set; }
public string Arguments { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ExpireAt { get; set; }
public string StateId { get; set; }
public string StateName { get; set; }
public string StateReason { get; set; }
public string StateData { get; set; }
}
}
@@ -0,0 +1,14 @@
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class RavenList : BaseEntity
{
public string Key { get; set; }
public string Value { get; set; }
public DateTime ExpireAt { get; set; }
}
}
@@ -0,0 +1,27 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class RavenServer : BaseEntity
{
public string Data { get; set; }
public DateTime LastHeartbeat { get; set; }
}
}
@@ -0,0 +1,16 @@
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class RavenSet : BaseEntity
{
public string Key { get; set; }
public double Score { get; set; }
public string Value { get; set; }
public DateTime? ExpireAt { get; set; }
}
}
@@ -0,0 +1,27 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace Hangfire.Raven.Entities
{
public class ServerData
{
public int WorkerCount { get; set; }
public string[] Queues { get; set; }
public DateTime? StartedAt { get; set; }
}
}
+30
View File
@@ -0,0 +1,30 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using Hangfire.Raven.Entities.Identity;
using System;
namespace Hangfire.Raven.Entities
{
public class State : BaseEntity
{
public string JobId { get; set; }
public string Name { get; set; }
public string Reason { get; set; }
public DateTime CreatedAt { get; set; }
public string Data { get; set; }
}
}
@@ -0,0 +1,74 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Threading;
using Hangfire.Logging;
using Hangfire.Server;
using System.Linq;
using HangFire.Raven;
using Hangfire.Raven.Entities;
namespace Hangfire.Raven
{
public class ExpirationManager : IServerComponent
{
private static readonly ILog Logger = LogProvider.GetCurrentClassLogger();
private readonly TimeSpan _checkInterval;
public ExpirationManager()
: this(TimeSpan.FromHours(1))
{
}
public ExpirationManager(TimeSpan checkInterval)
{
_checkInterval = checkInterval;
}
public void Execute(CancellationToken cancellationToken)
{
using (var repository = new Repository()) {
var session = repository.Session;
var now = DateTime.UtcNow;
var counters = session.Query<Counter>().Where(t => t.ExpireAt <= now).ToList();
counters.ForEach(t => session.Delete(t));
var jobs = session.Query<RavenJob>().Where(t => t.ExpireAt <= now).ToList();
jobs.ForEach(t => session.Delete(t));
var hashes = session.Query<RavenHash>().Where(t => t.ExpireAt <= now).ToList();
hashes.ForEach(t => session.Delete(t));
var lists = session.Query<RavenList>().Where(t => t.ExpireAt <= now).ToList();
lists.ForEach(t => session.Delete(t));
var sets = session.Query<RavenSet>().Where(t => t.ExpireAt <= now).ToList();
sets.ForEach(t => session.Delete(t));
}
cancellationToken.WaitHandle.WaitOne(_checkInterval);
}
public override string ToString()
{
return "Raven Expiration Manager";
}
}
}
@@ -0,0 +1,14 @@
using Raven.Client;
namespace Hangfire.Raven.Extensions
{
public static class DatabaseExtensions
{
public static bool DatabaseExists(this IDocumentStore documentStore, string database)
{
var result = documentStore.DatabaseCommands.Head("Raven/Databases/" + database);
return (result != null);
}
}
}
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=DataTypes/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A523C0E3-097D-4869-977F-15A717EA3E83}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Hangfire.Raven</RootNamespace>
<AssemblyName>Hangfire.Raven</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>5f064ca7</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Hangfire.Raven.xml</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>1591</NoWarn>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Hangfire.Raven.xml</DocumentationFile>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dewey.Net, Version=2.7.8.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Dewey.Net.2.7.8.0\lib\net45\Dewey.Net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Hangfire.Core">
<HintPath>..\..\packages\Hangfire.Core.1.5.0\lib\net45\Hangfire.Core.dll</HintPath>
</Reference>
<Reference Include="Hangfire.SqlServer">
<HintPath>..\..\packages\Hangfire.SqlServer.1.5.0\lib\net45\Hangfire.SqlServer.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin">
<HintPath>..\..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb">
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="Raven.Abstractions">
<HintPath>..\..\packages\RavenDB.Database.3.0.3800\lib\net45\Raven.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Raven.Client.Lightweight">
<HintPath>..\..\packages\RavenDB.Client.3.0.3800\lib\net45\Raven.Client.Lightweight.dll</HintPath>
</Reference>
<Reference Include="Raven.Database">
<HintPath>..\..\packages\RavenDB.Database.3.0.3800\lib\net45\Raven.Database.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="DistributedLocks\RavenDistributedLock.cs" />
<Compile Include="DistributedLocks\RavenDistributedLockException.cs" />
<Compile Include="Entities\AggregatedCounter.cs" />
<Compile Include="Entities\Counter.cs" />
<Compile Include="Entities\DistributedLocks.cs" />
<Compile Include="Entities\EnqueuedAndFetchedCount.cs" />
<Compile Include="Entities\JobQueue.cs" />
<Compile Include="Entities\RavenHash.cs" />
<Compile Include="Entities\Identity\BaseEntity.cs" />
<Compile Include="Entities\RavenList.cs" />
<Compile Include="Entities\RavenSet.cs" />
<Compile Include="ExpirationManager.cs" />
<Compile Include="Entities\JobParameter.cs" />
<Compile Include="Entities\RavenJob.cs" />
<Compile Include="Entities\State.cs" />
<Compile Include="Entities\RavenServer.cs" />
<Compile Include="Entities\ServerData.cs" />
<Compile Include="Extensions\DatabaseExtensions.cs" />
<Compile Include="JobQueues\IPersistentJobQueue.cs" />
<Compile Include="JobQueues\IPersistentJobQueueMonitoringApi.cs" />
<Compile Include="JobQueues\IPersistentJobQueueProvider.cs" />
<Compile Include="Listeners\TakeNewestConflictResolutionListener.cs" />
<Compile Include="JobQueues\PersistentJobQueueProviderCollection.cs" />
<Compile Include="JobQueues\RavenJobQueue.cs" />
<Compile Include="Entities\RavenFetchedJob.cs" />
<Compile Include="ObjectExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="JobQueues\RavenJobQueueProvider.cs" />
<Compile Include="Repository.cs" />
<Compile Include="SqlServerMonitoringApi.cs" />
<Compile Include="JobQueues\RavenJobQueueMonitoringApi.cs" />
<Compile Include="Storage\RavenStorage.cs" />
<Compile Include="Storage\RavenStorageExtensions.cs" />
<Compile Include="Storage\RavenStorageOptions.cs" />
<Compile Include="RavenConnection.cs" />
<Compile Include="RavenWriteOnlyTransaction.cs" />
<Compile Include="StateHandlers\DeletedStateHandler.cs" />
<Compile Include="StateHandlers\FailedStateHandler.cs" />
<Compile Include="StateHandlers\ProcessingStateHandler.cs" />
<Compile Include="StateHandlers\SucceededStateHandler.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<None Include="Raven.Studio.Html5.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -0,0 +1,27 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System.Threading;
using Hangfire.Storage;
namespace Hangfire.Raven.JobQueues
{
public interface IPersistentJobQueue
{
IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken);
void Enqueue(string queue, string jobId);
}
}
@@ -0,0 +1,32 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System.Collections.Generic;
using Hangfire.Raven.Entities;
namespace Hangfire.Raven.JobQueues
{
public interface IPersistentJobQueueMonitoringApi
{
IEnumerable<string> GetQueues();
IEnumerable<string> GetEnqueuedJobIds(string queue, int from, int perPage);
IEnumerable<string> GetFetchedJobIds(string queue, int from, int perPage);
EnqueuedAndFetchedCount GetEnqueuedAndFetchedCount(string queue);
}
}
@@ -0,0 +1,24 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
namespace Hangfire.Raven.JobQueues
{
public interface IPersistentJobQueueProvider
{
IPersistentJobQueue GetJobQueue();
IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi();
}
}
@@ -0,0 +1,71 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections;
using System.Collections.Generic;
namespace Hangfire.Raven.JobQueues
{
public class PersistentJobQueueProviderCollection : IEnumerable<IPersistentJobQueueProvider>
{
private readonly List<IPersistentJobQueueProvider> _providers
= new List<IPersistentJobQueueProvider>();
private readonly Dictionary<string, IPersistentJobQueueProvider> _providersByQueue
= new Dictionary<string, IPersistentJobQueueProvider>(StringComparer.OrdinalIgnoreCase);
private readonly IPersistentJobQueueProvider _defaultProvider;
public PersistentJobQueueProviderCollection(IPersistentJobQueueProvider defaultProvider)
{
defaultProvider.ThrowIfNull("defaultProvider");
_defaultProvider = defaultProvider;
_providers.Add(_defaultProvider);
}
public void Add(IPersistentJobQueueProvider provider, IEnumerable<string> queues)
{
provider.ThrowIfNull("provider");
queues.ThrowIfNull("queues");
_providers.Add(provider);
foreach (var queue in queues)
{
_providersByQueue.Add(queue, provider);
}
}
public IPersistentJobQueueProvider GetProvider(string queue)
{
return _providersByQueue.ContainsKey(queue)
? _providersByQueue[queue]
: _defaultProvider;
}
public IEnumerator<IPersistentJobQueueProvider> GetEnumerator()
{
return _providers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,102 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Threading;
using Hangfire.Annotations;
using Hangfire.Storage;
using HangFire.Raven;
using Raven.Client.Linq;
using System.Linq;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.JobQueues
{
public class RavenJobQueue : IPersistentJobQueue
{
private readonly RavenStorage _storage;
private readonly RavenStorageOptions _options;
private static object _lockMethod = new object();
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");
}
lock (_lockMethod) {
JobQueue fetchedJob;
cancellationToken.ThrowIfCancellationRequested();
do {
using (var repository = new Repository()) {
fetchedJob = repository.Session.Query<JobQueue>().FirstOrDefault(t => t.FetchedAt == null
&& t.Queue.In(queues));
if (fetchedJob == null) {
fetchedJob = repository.Session.Query<JobQueue>().FirstOrDefault(t =>
t.FetchedAt < DateTime.UtcNow.AddSeconds(_options.InvisibilityTimeout.Negate().TotalSeconds)
&& t.Queue.In(queues));
if (fetchedJob == null) {
cancellationToken.WaitHandle.WaitOne(_options.QueuePollInterval);
cancellationToken.ThrowIfCancellationRequested();
continue;
}
}
fetchedJob.FetchedAt = DateTime.UtcNow;
repository.Save(fetchedJob);
}
} while (fetchedJob == null);
return new RavenFetchedJob(_storage, fetchedJob);
}
}
public void Enqueue(string queue, string jobId)
{
using (var repository = new Repository()) {
var jobQueue = new JobQueue
{
JobId = jobId,
Queue = queue
};
repository.Save(jobQueue);
}
}
}
}
@@ -0,0 +1,103 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using Hangfire.Raven.Entities;
using HangFire.Raven;
using Hangfire.Raven.JobQueues;
namespace Hangfire.Raven.JobQueues
{
internal class RavenJobQueueMonitoringApi : IPersistentJobQueueMonitoringApi
{
public IEnumerable<string> GetQueues()
{
using (var repository = new Repository()) {
return repository.Session.Query<JobQueue>()
.Select(x => x.Queue)
.Distinct()
.ToList();
}
}
public IEnumerable<string> GetEnqueuedJobIds(string queue, int @from, int perPage)
{
int start = @from + 1;
int end = from + perPage;
using (var repository = new Repository()) {
var jobs = repository.Session.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)
.ToList();
var results = new List<RavenJob>();
foreach (var item in jobs) {
var job = repository.Session.Query<RavenJob>().FirstOrDefault(t => t.Id == item.JobId);
if (job != null && repository.Session.Query<State>().FirstOrDefault(t => t.Id == job.StateId) != null) {
results.Add(job);
}
}
return results.Select(t => t.Id).ToList();
}
}
public IEnumerable<string> GetFetchedJobIds(string queue, int @from, int perPage)
{
int start = @from + 1;
int end = from + perPage;
using (var repository = new Repository()) {
var jobs = repository.Session.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.Session.Query<RavenJob>().FirstOrDefault(t => t.Id == item.JobId);
if (job != null) {
results.Add(job.Id);
}
}
return results;
}
}
public EnqueuedAndFetchedCount GetEnqueuedAndFetchedCount(string queue)
{
using (var repository = new Repository()) {
int enqueuedCount = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt == null).Count();
int fetchedCount = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt != null).Count();
return new EnqueuedAndFetchedCount
{
EnqueuedCount = enqueuedCount,
FetchedCount = fetchedCount
};
}
}
}
}
@@ -0,0 +1,47 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using Hangfire.Annotations;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven.JobQueues
{
public class RavenJobQueueProvider : IPersistentJobQueueProvider
{
private readonly IPersistentJobQueue _jobQueue;
private readonly IPersistentJobQueueMonitoringApi _monitoringApi;
public RavenJobQueueProvider([NotNull] RavenStorage storage, [NotNull] RavenStorageOptions options)
{
storage.ThrowIfNull("storage");
options.ThrowIfNull("options");
_jobQueue = new RavenJobQueue(storage, options);
//_monitoringApi = new RavenJobQueueMonitoringApi(storage);
}
public IPersistentJobQueue GetJobQueue()
{
return _jobQueue;
}
public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi()
{
return _monitoringApi;
}
}
}
@@ -0,0 +1,24 @@
using Raven.Abstractions.Data;
using Raven.Client.Listeners;
using System.Linq;
namespace Hangfire.Raven.Listeners
{
public class TakeNewestConflictResolutionListener : IDocumentConflictListener
{
public bool TryResolveConflict(string key, JsonDocument[] conflictedDocs, out JsonDocument resolvedDocument)
{
var maxDate = conflictedDocs.Max(x => x.LastModified);
resolvedDocument = conflictedDocs
.FirstOrDefault(x => x.LastModified == maxDate);
if (resolvedDocument == null)
return false;
resolvedDocument.Metadata.Remove("@id");
resolvedDocument.Metadata.Remove("@etag");
return true;
}
}
}
@@ -0,0 +1,14 @@
using System;
namespace Hangfire.Raven
{
public static class ObjectExtensions
{
public static void ThrowIfNull(this object value, string name)
{
if (value == null) {
throw new ArgumentNullException(name);
}
}
}
}
@@ -0,0 +1,11 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Hangfire.Raven")]
[assembly: AssemblyDescription("Raven job storage for Hangfire")]
[assembly: Guid("3d96bf2f-8854-4872-aee3-faf81d121a4d")]
[assembly: InternalsVisibleTo("Hangfire.Raven.Tests")]
// Allow the generation of mocks for internal types
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Binary file not shown.
+536
View File
@@ -0,0 +1,536 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Hangfire.Common;
using Hangfire.Server;
using Hangfire.Storage;
using Raven.Client;
using Hangfire.Raven.DistributedLock;
using HangFire.Raven;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven
{
public class RavenConnection : JobStorageConnection
{
private readonly RavenStorageOptions _options;
private readonly RavenStorage _storage;
public RavenConnection(RavenStorage ravenStorage)
{
ravenStorage.ThrowIfNull("RavenStorage");
_storage = ravenStorage;
_options = new RavenStorageOptions();
}
public RavenConnection(RavenStorage ravenStorage, RavenStorageOptions options)
: this(ravenStorage)
{
options.ThrowIfNull("options");
_storage = ravenStorage;
_options = options;
}
public override IWriteOnlyTransaction CreateWriteTransaction()
{
return new RavenWriteOnlyTransaction(_storage);
}
public override IDisposable AcquireDistributedLock(string resource, TimeSpan timeout)
{
return new RavenDistributedLock(_storage, string.Format("HangFire:{0}", resource), timeout, _options);
}
public static IDocumentStore _documentStore;
public override IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken)
{
queues.ThrowIfNull("queues");
if (queues.Length == 0) {
throw new ArgumentNullException("queues");
}
var providers = queues
.Select(queue => _storage.QueueProviders.GetProvider(queue))
.Distinct()
.ToArray();
if (providers.Length != 1) {
throw new InvalidOperationException(string.Format(
"Multiple provider instances registered for queues: {0}. You should choose only one type of persistent queues per server instance.",
string.Join(", ", queues)));
}
var persistentQueue = providers[0].GetJobQueue();
return persistentQueue.Dequeue(queues, cancellationToken);
}
public override string CreateExpiredJob(
Job job,
IDictionary<string, string> parameters,
DateTime createdAt,
TimeSpan expireIn)
{
job.ThrowIfNull("job");
parameters.ThrowIfNull("parameters");
var invocationData = InvocationData.Serialize(job);
var ravenJob = new RavenJob
{
Id = Guid.NewGuid().ToString(),
InvocationData = JobHelper.ToJson(invocationData),
Arguments = invocationData.Arguments,
CreatedAt = createdAt,
ExpireAt = createdAt.Add(expireIn)
};
using (var repository = new Repository()) {
repository.Save(ravenJob);
if (parameters.Count > 0) {
foreach (var parameter in parameters) {
repository.Save(new JobParameter
{
JobId = ravenJob.Id,
Name = parameter.Key,
Value = parameter.Value
});
}
}
return ravenJob.Id.ToString();
}
}
public override JobData GetJobData(string id)
{
id.ThrowIfNull("id");
using (var repository = new Repository()) {
var jobData = repository.Session.Load<RavenJob>(id);
if (jobData == null) {
return null;
}
var invocationData = JobHelper.FromJson<InvocationData>(jobData.InvocationData);
invocationData.Arguments = jobData.Arguments;
Job job = null;
JobLoadException loadException = null;
try {
job = invocationData.Deserialize();
} catch (JobLoadException ex) {
loadException = ex;
}
return new JobData
{
Job = job,
State = jobData.StateName,
CreatedAt = jobData.CreatedAt,
LoadException = loadException
};
}
}
public override StateData GetStateData(string jobId)
{
jobId.ThrowIfNull("jobId");
using (var repository = new Repository()) {
RavenJob job = repository.Session.Load<RavenJob>(jobId);
if (job == null) {
return null;
}
State state = repository.Session.Query<State>().FirstOrDefault(t => t.Id == job.StateId);
if (state == null) {
return null;
}
return new StateData
{
Name = state.Name,
Reason = state.Reason,
Data = JobHelper.FromJson<Dictionary<string, string>>(state.Data)
};
}
}
public override void SetJobParameter(string id, string name, string value)
{
id.ThrowIfNull("id");
name.ThrowIfNull("name");
using (var repository = new Repository()) {
var results = repository.Session.Query<JobParameter>().Where(t => t.JobId == id && t.Name == name).ToList();
results.ForEach(t => {
t.Value = value;
repository.Save(t);
});
}
}
public override string GetJobParameter(string id, string name)
{
id.ThrowIfNull("id");
name.ThrowIfNull("name");
using (var repository = new Repository()) {
var jobParameter = repository.Session.Query<JobParameter>().FirstOrDefault(t => t.JobId == id && t.Name == name);
return jobParameter != null ? jobParameter.Value : null;
}
}
public override HashSet<string> GetAllItemsFromSet(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var results = repository.Session.Query<RavenSet>().Where(t => t.Key == key).Select(t => t.Value).ToList();
return new HashSet<string>(results);
}
}
public override string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore)
{
key.ThrowIfNull("key");
if (toScore < fromScore) {
throw new ArgumentException("The `toScore` value must be higher or equal to the `fromScore` value.");
}
using (var repository = new Repository()) {
var set = repository.Session.Query<RavenSet>()
.Where(t => t.Key == key)
.Where(t => t.Score >= fromScore)
.Where(t => t.Score <= toScore)
.OrderBy(t => t.Score)
.FirstOrDefault();
return set != null ? set.Value : null;
}
}
public override void SetRangeInHash(string key, IEnumerable<KeyValuePair<string, string>> keyValuePairs)
{
key.ThrowIfNull("key");
keyValuePairs.ThrowIfNull("keyValuePairs");
using (var repository = new Repository()) {
foreach (var keyValuePair in keyValuePairs) {
var results = repository.Session.Query<RavenHash>().Where(t => t.Key == key && t.Field == keyValuePair.Key).ToList();
results.ForEach(t =>
{
t.Value = keyValuePair.Value;
repository.Save(t);
});
}
}
}
public override Dictionary<string, string> GetAllEntriesFromHash(string key)
{
key.ThrowIfNull("key");
using (var session = new Repository().Session) {
var result = session.Query<RavenHash>().Where(t => t.Key == key)
.ToDictionary(x => x.Field, x => x.Value);
return result.Count != 0 ? result : null;
}
}
public override void AnnounceServer(string serverId, ServerContext context)
{
serverId.ThrowIfNull("serverId");
context.ThrowIfNull("context");
var data = new ServerData
{
WorkerCount = context.WorkerCount,
Queues = context.Queues,
StartedAt = DateTime.UtcNow,
};
using (var repository = new Repository()) {
var servers = repository.Session.Query<RavenServer>()
.Where(t => t.Id == serverId)
.ToList();
var queues = servers.SelectMany(t => JobHelper.FromJson<ServerData>(t.Data).Queues).ToList();
queues.AddRange(data.Queues.Select(t => t).ToList());
data.Queues = queues.ToArray();
data.WorkerCount += servers.Select(t => JobHelper.FromJson<ServerData>(t.Data).WorkerCount).Sum(t => t);
foreach (var item in servers) {
repository.Delete(item);
}
var server = new RavenServer
{
Data = JobHelper.ToJson(data),
Id = serverId,
LastHeartbeat = DateTime.UtcNow
};
repository.Save(server);
}
}
public override void RemoveServer(string serverId)
{
serverId.ThrowIfNull("serverId");
using (var repository = new Repository()) {
var servers = repository.Session.Query<RavenServer>()
.Where(t => t.Id == serverId)
.ToList();
foreach (var item in servers) {
repository.Delete(item);
}
}
}
public override void Heartbeat(string serverId)
{
serverId.ThrowIfNull("serverId");
using (var repository = new Repository()) {
var server = repository.Session.Load<RavenServer>(serverId);
if (server == null) {
server = new RavenServer
{
Id = serverId
};
}
server.LastHeartbeat = DateTime.UtcNow;
repository.Save(server);
}
}
public override int RemoveTimedOutServers(TimeSpan timeOut)
{
if (timeOut.Duration() != timeOut) {
throw new ArgumentException("The `timeOut` value must be positive.", "timeOut");
}
using (var repository = new Repository()) {
var heartBeatCutOff = DateTime.UtcNow.Add(timeOut.Negate());
var results = repository.Session.Query<RavenServer>()
.Where(t => t.LastHeartbeat < heartBeatCutOff)
.ToList();
foreach (var item in results) {
repository.Delete(item);
}
return results.Count;
}
}
public override long GetSetCount(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var count = repository.Session.Query<RavenSet>()
.Where(t => t.Key == key)
.Count();
return count;
}
}
public override List<string> GetRangeFromSet(string key, int startingFrom, int endingAt)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var values = repository.Session.Query<RavenSet>()
.Where(t => t.Key == key)
.Skip(startingFrom - 1)
.Take(endingAt - startingFrom + 1)
.Select(t => t.Value)
.ToList();
return values;
}
}
public override TimeSpan GetSetTtl(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var expireAt = repository.Session.Query<RavenSet>()
.Where(t => t.Key == key)
.Select(t => t.ExpireAt)
.ToArray();
if (expireAt.Any() == false) {
return TimeSpan.FromSeconds(-1);
}
return expireAt.Where(t => t.HasValue).Min(t => t.Value) - DateTime.UtcNow;
}
}
public override long GetCounter(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var values = repository.Session.Query<AggregatedCounter>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.ToArray();
return values.Any() ? values.Sum() : 0;
}
}
public override long GetHashCount(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var count = repository.Session.Query<RavenHash>()
.Where(t => t.Key == key)
.Count();
return count;
}
}
public override TimeSpan GetHashTtl(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var hashes = repository.Session.Query<RavenHash>()
.Where(t => t.Key == key)
.ToList();
DateTime? result = hashes.Any() ? hashes.Min(x => x.ExpireAt) : null;
if (!result.HasValue) {
return TimeSpan.FromSeconds(-1);
}
return result.Value - DateTime.UtcNow;
}
}
public override string GetValueFromHash(string key, string name)
{
key.ThrowIfNull("key");
name.ThrowIfNull("name");
using (var repository = new Repository()) {
var result = repository.Session.Query<RavenHash>()
.Where(t => t.Key == key)
.Where(t => t.Field == name)
.FirstOrDefault();
return result != null ? result.Value : null;
}
}
public override long GetListCount(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var count = repository.Session.Query<RavenList>()
.Where(t => t.Key == key)
.Count();
return count;
}
}
public override TimeSpan GetListTtl(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var results = repository.Session.Query<RavenList>()
.Where(t => t.Key == key)
.ToList();
DateTime? ttl = results.Any() ? results.Min(t => t.ExpireAt) : (DateTime?)null;
if (!ttl.HasValue) {
return TimeSpan.FromSeconds(-1);
}
return ttl.Value - DateTime.UtcNow;
}
}
public override List<string> GetRangeFromList(string key, int startingFrom, int endingAt)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var results = repository.Session.Query<RavenList>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.Skip(startingFrom - 1)
.Take(endingAt - startingFrom + 1)
.ToList();
return results;
}
}
public override List<string> GetAllItemsFromList(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var results = repository.Session.Query<RavenList>()
.Where(t => t.Key == key)
.Select(t => t.Value)
.ToList();
return results;
}
}
}
}
@@ -0,0 +1,373 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using Hangfire.Annotations;
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using HangFire.Raven;
using Hangfire.Raven.Entities;
using Hangfire.Raven.Storage;
namespace Hangfire.Raven
{
internal class RavenWriteOnlyTransaction : JobStorageTransaction
{
private readonly Queue<Action<RavenConnection>> _commandQueue
= new Queue<Action<RavenConnection>>();
private readonly SortedSet<string> _lockedResources = new SortedSet<string>();
private readonly RavenStorage _storage;
public RavenWriteOnlyTransaction([NotNull] RavenStorage storage)
{
storage.ThrowIfNull("storage");
_storage = storage;
}
public override void Commit()
{
foreach (var command in _commandQueue) {
command(new RavenConnection(_storage));
}
}
public override void ExpireJob(string jobId, TimeSpan expireIn)
{
using (var repository = new Repository()) {
var result = repository.Session.Load<RavenJob>(jobId);
result.ExpireAt = DateTime.UtcNow.Add(expireIn);
repository.Save(result);
}
}
public override void PersistJob(string jobId)
{
using (var repository = new Repository()) {
var result = repository.Session.Load<RavenJob>(jobId);
result.ExpireAt = null;
repository.Save(result);
}
}
public override void SetJobState(string jobId, IState state)
{
using (var repository = new Repository()) {
var jobState = repository.Session.Query<State>().FirstOrDefault(t => t.JobId == jobId);
if (jobState == null) {
jobState = new State
{
CreatedAt = DateTime.UtcNow,
JobId = jobId
};
}
jobState.Reason = state.Reason;
jobState.Name = state.Name;
jobState.Data = JobHelper.ToJson(state.SerializeData());
repository.Save(jobState);
var result = repository.Session.Load<RavenJob>(jobId);
result.StateId = jobState.Id;
result.StateName = state.Name;
result.StateData = JobHelper.ToJson(state.SerializeData());
result.StateReason = state.Reason;
repository.Save(result);
}
}
public override void AddJobState(string jobId, IState state)
{
using (var repository = new Repository()) {
var jobState = new State
{
Id = Guid.NewGuid().ToString(),
JobId = jobId,
Name = state.Name,
Reason = state.Reason,
CreatedAt = DateTime.UtcNow,
Data = JobHelper.ToJson(state.SerializeData())
};
repository.Save(jobState);
}
}
public override void AddToQueue(string queue, string jobId)
{
var provider = _storage.QueueProviders.GetProvider(queue);
var persistentQueue = provider.GetJobQueue();
persistentQueue.Enqueue(queue, jobId);
}
public override void IncrementCounter(string key)
{
using (var repository = new Repository()) {
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
if (counter == null) {
counter = new Counter
{
Id = Guid.NewGuid().ToString(),
Key = key,
Value = 0
};
}
counter.Value += 1;
repository.Save(counter);
}
}
public override void IncrementCounter(string key, TimeSpan expireIn)
{
using (var repository = new Repository()) {
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
if (counter == null) {
counter = new Counter
{
Id = Guid.NewGuid().ToString(),
ExpireAt = DateTime.UtcNow.Add(expireIn),
Key = key,
Value = 0
};
}
counter.Value += 1;
repository.Save(counter);
}
}
public override void DecrementCounter(string key)
{
using (var repository = new Repository()) {
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
if (counter == null) {
counter = new Counter
{
Id = Guid.NewGuid().ToString(),
Key = key,
Value = 0
};
}
counter.Value -= 1;
repository.Save(counter);
}
}
public override void DecrementCounter(string key, TimeSpan expireIn)
{
using (var repository = new Repository()) {
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
if (counter == null) {
counter = new Counter
{
Id = Guid.NewGuid().ToString(),
ExpireAt = DateTime.UtcNow.Add(expireIn),
Key = key,
Value = 0
};
}
counter.Value -= 1;
repository.Save(counter);
}
}
public override void AddToSet(string key, string value)
{
AddToSet(key, value, 0.0);
}
public override void AddToSet(string key, string value, double score)
{
using (var repository = new Repository()) {
var set = repository.Session.Query<RavenSet>().FirstOrDefault(t => t.Key == key && t.Value == value);
if (set == null) {
set = new RavenSet
{
Id = Guid.NewGuid().ToString(),
Key = key,
Value = value
};
}
set.Score = score;
repository.Save(set);
}
}
public override void RemoveFromSet(string key, string value)
{
using (var repository = new Repository()) {
var set = repository.Session.Query<RavenSet>().FirstOrDefault(t => t.Key == key && t.Value == value);
if (set == null) {
return;
}
repository.Delete(set);
}
}
public override void InsertToList(string key, string value)
{
using (var repository = new Repository()) {
var list = repository.Session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
if (list == null) {
list = new RavenList
{
Id = Guid.NewGuid().ToString(),
Key = key
};
}
list.Value = value;
repository.Save(list);
}
}
public override void RemoveFromList(string key, string value)
{
using (var repository = new Repository()) {
var list = repository.Session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
if (list == null) {
return;
}
repository.Delete(list);
}
}
public override void TrimList(string key, int keepStartingFrom, int keepEndingAt)
{
using (var repository = new Repository()) {
var list = repository.Session.Query<RavenList>().Where(t => t.Key == key).ToList();
if (!list.Any()) {
return;
}
for (var i = 0; i < list.Count; ++i) {
if (i >= keepStartingFrom && i <= keepEndingAt) {
continue;
}
repository.Delete(list[i]);
}
}
}
public override void SetRangeInHash(string key, IEnumerable<KeyValuePair<string, string>> keyValuePairs)
{
key.ThrowIfNull("key");
keyValuePairs.ThrowIfNull("keyValuePairs");
using (var repository = new Repository()) {
foreach (var keyValuePair in keyValuePairs) {
var pair = keyValuePair;
var results = repository.Session.Query<RavenHash>().Where(t => t.Key == key && t.Field == pair.Key).ToList();
if (!results.Any()) {
results.Add(new RavenHash
{
Key = key,
Field = pair.Key,
});
}
foreach (var item in results) {
item.Value = pair.Value;
repository.Save(item);
}
}
}
}
public override void RemoveHash(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var result = repository.Session.Query<RavenHash>().Where(t => t.Key == key).ToList();
foreach (var item in result) {
repository.Session.Delete(item);
}
}
}
public override void RemoveSet(string key)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var result = repository.Session.Query<RavenSet>().Where(t => t.Key == key).ToList();
foreach (var item in result) {
repository.Session.Delete(item);
}
}
}
public override void ExpireHash(string key, TimeSpan expireIn)
{
key.ThrowIfNull("key");
using (var repository = new Repository()) {
var result = repository.Session.Query<RavenSet>().Where(t => t.Key == key).ToList();
foreach (var item in result) {
item.ExpireAt = DateTime.UtcNow.Add(expireIn);
repository.Save(item);
}
}
}
internal void QueueCommand(Action<RavenConnection> action)
{
_commandQueue.Enqueue(action);
}
}
}
+358
View File
@@ -0,0 +1,358 @@
using Raven.Client;
using System;
using Dewey.Net.Types;
using Raven.Client.Document;
using Raven.Client.Embedded;
using System.Threading.Tasks;
using System.Collections.Generic;
using Dewey.Net.Generic;
using Raven.Abstractions.Data;
using System.Collections.Concurrent;
using Raven.Client.Linq;
using System.Linq;
using System.Linq.Expressions;
using Hangfire.Raven.Entities.Identity;
using Hangfire.Raven.Extensions;
using Hangfire.Raven.Listeners;
namespace HangFire.Raven
{
public class Repository : IDisposable
{
public static string ConnectionUrl { get; set; }
public static string DefaultDatabase { get; set; }
private static IDocumentStore _documentStore;
private IDocumentSession _session;
private IAsyncDocumentSession _asyncSession;
private string _database;
public static bool Embedded { get; set; }
public string Database => _database ?? DefaultDatabase;
public void Destroy()
{
if (!DocumentStore.DatabaseExists(Database)) {
return;
}
DocumentStore.DatabaseCommands.GlobalAdmin.DeleteDatabase(Database, hardDelete: true);
}
public void Create()
{
if (DocumentStore.DatabaseExists(Database)) {
return;
}
DocumentStore.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(Database);
}
public IDocumentStore DocumentStore
{
get
{
if (_documentStore == null) {
if (Embedded) {
_documentStore = new EmbeddableDocumentStore
{
RunInMemory = true
};
((EmbeddableDocumentStore)_documentStore).Configuration.Storage.Voron.AllowOn32Bits = true;
} else {
_documentStore = new DocumentStore
{
Url = ConnectionUrl
};
}
_documentStore.Conventions.DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite;
_documentStore.Listeners.RegisterListener(new TakeNewestConflictResolutionListener());
_documentStore.Conventions.DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite;
_documentStore.Initialize();
}
return _documentStore;
}
}
public IDocumentSession Session => _session ?? (_session = DocumentStore.OpenSession(Database));
public IAsyncDocumentSession AsyncSession => _asyncSession ?? (_asyncSession = DocumentStore.OpenAsyncSession(Database));
#region constructor
public Repository()
{
_database = DefaultDatabase;
}
public Repository(string database)
{
if (database.IsEmpty()) {
_database = DefaultDatabase;
} else {
_database = database;
}
}
#endregion
#region Bulk Insert
private BulkInsertOptions GetBulkInsertOptions(bool overwrite = true) => new BulkInsertOptions
{
BatchSize = 100,
WriteTimeoutMilliseconds = 360000,
OverwriteExisting = overwrite
};
public void BulkInsert<T>(ConcurrentBag<T> entities, bool overwrite = true) where T : BaseEntity
{
if (_database.IsEmpty()) {
_database = DefaultDatabase;
}
using (var bulkInsert = DocumentStore.BulkInsert(Database, GetBulkInsertOptions(overwrite))) {
foreach (var item in entities) {
if (item == null) {
continue;
}
if (item.Id.IsEmpty()) {
item.Id = Guid.NewGuid().ToString();
}
bulkInsert.Store(item);
}
}
}
public void BulkInsert<T>(List<T> entities, bool overwrite = true) where T : BaseEntity
{
var concurrentBag = new ConcurrentBag<T>();
entities.ForEach(t => concurrentBag.Add(t));
BulkInsert(concurrentBag, overwrite);
}
#endregion
#region Find
public T FindById<T>(string id) where T : BaseEntity
{
if (id == null) {
throw new ArgumentException("Id must be provided.", nameof(id));
}
return Session.Load<T>(id);
}
public async Task<T> FindByIdAsync<T>(string id) where T : BaseEntity
{
if (id == null) {
throw new ArgumentException("Id must be provided.", nameof(id));
}
return await AsyncSession.LoadAsync<T>(id);
}
#endregion
#region Save
public void Save<T>(T entity) where T : BaseEntity
{
if (entity == null) {
throw new ArgumentException("Entity must be provided.", nameof(entity));
}
if (entity.Id.IsEmpty()) {
entity.Id = Guid.NewGuid().ToString();
}
Session.Store(entity);
Session.SaveChanges();
}
public async Task SaveAsync<T>(T entity) where T : BaseEntity
{
if (entity == null) {
throw new ArgumentException("Entity must be provided.", nameof(entity));
}
if (entity.Id.IsEmpty()) {
entity.Id = Guid.NewGuid().ToString();
}
await AsyncSession.StoreAsync(entity);
await AsyncSession.SaveChangesAsync();
}
public async Task SaveNoValidateAsync<T>(T entity) where T : BaseEntity
{
if (entity == null) {
throw new ArgumentException("Entity must be provided.", nameof(entity));
}
if (entity.Id.IsEmpty()) {
entity.Id = Guid.NewGuid().ToString();
}
await AsyncSession.StoreAsync(entity);
await AsyncSession.SaveChangesAsync();
}
public void SaveAll<T>(List<T> entities) where T : BaseEntity
{
if (entities.IsEmpty()) {
throw new ArgumentException("Data must be provided.", nameof(entities));
}
foreach (var item in entities) {
Save(item);
}
}
public async Task SaveAllAsync<T>(List<T> entities) where T : BaseEntity
{
if (entities.IsEmpty()) {
throw new ArgumentException("Data must be provided.", nameof(entities));
}
foreach (var item in entities) {
await SaveAsync(item);
}
}
#endregion
#region Delete
public void Delete(string id)
{
if (id == null) {
throw new ArgumentException("Id must be provided.", nameof(id));
}
Session.Delete(id);
Session.SaveChanges();
}
public async Task DeleteAsync(string id)
{
if (id == null) {
throw new ArgumentException("Id must be provided.", nameof(id));
}
AsyncSession.Delete(id);
await AsyncSession.SaveChangesAsync();
}
public void Delete<T>(T entity) where T : BaseEntity
{
if (entity == null) {
throw new ArgumentException("Entity must be provided.", nameof(entity));
}
Session.Delete(entity.Id);
Session.SaveChanges();
}
public async Task DeleteAsync<T>(T entity) where T : BaseEntity
{
if (entity == null) {
throw new ArgumentException("Entity must be provided.", nameof(entity));
}
AsyncSession.Delete(entity.Id);
await AsyncSession.SaveChangesAsync();
}
#endregion
#region Query
public IRavenQueryable<T> Query<T>() where T : BaseEntity
{
return Session.Query<T>();
}
public IRavenQueryable<T> QueryAsync<T>() where T : BaseEntity
{
return AsyncSession.Query<T>();
}
#endregion
#region Index
public IRavenQueryable<T> Index<T>(string indexName, bool isMapReduce = false) where T : BaseEntity
{
return Session.Query<T>(indexName, isMapReduce);
}
public IRavenQueryable<T> IndexAsync<T>(string indexName, bool isMapReduce = false) where T : BaseEntity
{
return AsyncSession.Query<T>(indexName, isMapReduce);
}
#endregion
#region Count
public int Count<T>() where T : BaseEntity
{
return Session.Query<T>().Count();
}
public async Task<int> CountAsync<T>() where T : BaseEntity
{
return await AsyncSession.Query<T>().CountAsync();
}
public int Count<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
{
return Session.Query<T>().Where(predicate).Count();
}
public async Task<int> CountAsync<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
{
return await AsyncSession.Query<T>().Where(predicate).CountAsync();
}
#endregion
#region IDisposable
private bool _disposed = false;
protected void Dispose(bool disposing)
{
if (!_disposed) {
if (disposing) {
_session = null;
_asyncSession = null;
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}
@@ -0,0 +1,562 @@
//// This file is part of Hangfire.
//// Copyright © 2013-2014 Sergey Odinokov.
////
//// Hangfire is free software: you can redistribute it and/or modify
//// it under the terms of the GNU Lesser General Public License as
//// published by the Free Software Foundation, either version 3
//// of the License, or any later version.
////
//// Hangfire is distributed in the hope that it will be useful,
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//// GNU Lesser General Public License for more details.
////
//// You should have received a copy of the GNU Lesser General Public
//// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
//using System;
//using System.Collections.Generic;
//using System.Data.SqlClient;
//using System.Linq;
//using System.Transactions;
//using Dapper;
//using Hangfire.Annotations;
//using Hangfire.Common;
//using Hangfire.SqlServer.Entities;
//using Hangfire.States;
//using Hangfire.Storage;
//using Hangfire.Storage.Monitoring;
//namespace Hangfire.SqlServer
//{
// internal class SqlServerMonitoringApi : IMonitoringApi
// {
// private readonly RavenStorage _storage;
// private readonly int? _jobListLimit;
// public SqlServerMonitoringApi([NotNull] RavenStorage storage, int? jobListLimit)
// {
// if (storage == null) throw new ArgumentNullException("storage");
// _storage = storage;
// _jobListLimit = jobListLimit;
// }
// public long ScheduledCount()
// {
// return UseConnection(connection =>
// GetNumberOfJobsByStateName(connection, ScheduledState.StateName));
// }
// public long EnqueuedCount(string queue)
// {
// var queueApi = GetQueueApi(queue);
// var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
// return counters.EnqueuedCount ?? 0;
// }
// public long FetchedCount(string queue)
// {
// var queueApi = GetQueueApi(queue);
// var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
// return counters.FetchedCount ?? 0;
// }
// public long FailedCount()
// {
// return UseConnection(connection =>
// GetNumberOfJobsByStateName(connection, FailedState.StateName));
// }
// public long ProcessingCount()
// {
// return UseConnection(connection =>
// GetNumberOfJobsByStateName(connection, ProcessingState.StateName));
// }
// public JobList<ProcessingJobDto> ProcessingJobs(int @from, int count)
// {
// return UseConnection(connection => GetJobs(
// connection,
// from, count,
// ProcessingState.StateName,
// (sqlJob, job, stateData) => new ProcessingJobDto
// {
// Job = job,
// ServerId = stateData.ContainsKey("ServerId") ? stateData["ServerId"] : stateData["ServerName"],
// StartedAt = JobHelper.DeserializeDateTime(stateData["StartedAt"]),
// }));
// }
// public JobList<ScheduledJobDto> ScheduledJobs(int @from, int count)
// {
// return UseConnection(connection => GetJobs(
// connection,
// from, count,
// ScheduledState.StateName,
// (sqlJob, job, stateData) => new ScheduledJobDto
// {
// Job = job,
// EnqueueAt = JobHelper.DeserializeDateTime(stateData["EnqueueAt"]),
// ScheduledAt = JobHelper.DeserializeDateTime(stateData["ScheduledAt"])
// }));
// }
// public IDictionary<DateTime, long> SucceededByDatesCount()
// {
// return UseConnection(connection =>
// GetTimelineStats(connection, "succeeded"));
// }
// public IDictionary<DateTime, long> FailedByDatesCount()
// {
// return UseConnection(connection =>
// GetTimelineStats(connection, "failed"));
// }
// public IList<ServerDto> Servers()
// {
// return UseConnection<IList<ServerDto>>(connection =>
// {
// var servers = connection.Query<Entities.Server>(
// string.Format(@"select * from [{0}].Server", _storage.GetSchemaName()))
// .ToList();
// var result = new List<ServerDto>();
// foreach (var server in servers)
// {
// var data = JobHelper.FromJson<ServerData>(server.Data);
// result.Add(new ServerDto
// {
// Name = server.Id,
// Heartbeat = server.LastHeartbeat,
// Queues = data.Queues,
// StartedAt = data.StartedAt.HasValue ? data.StartedAt.Value : DateTime.MinValue,
// WorkersCount = data.WorkerCount
// });
// }
// return result;
// });
// }
// public JobList<FailedJobDto> FailedJobs(int @from, int count)
// {
// return UseConnection(connection => GetJobs(
// connection,
// from,
// count,
// FailedState.StateName,
// (sqlJob, job, stateData) => new FailedJobDto
// {
// Job = job,
// Reason = sqlJob.StateReason,
// ExceptionDetails = stateData["ExceptionDetails"],
// ExceptionMessage = stateData["ExceptionMessage"],
// ExceptionType = stateData["ExceptionType"],
// FailedAt = JobHelper.DeserializeNullableDateTime(stateData["FailedAt"])
// }));
// }
// public JobList<SucceededJobDto> SucceededJobs(int @from, int count)
// {
// return UseConnection(connection => GetJobs(
// connection,
// from,
// count,
// SucceededState.StateName,
// (sqlJob, job, stateData) => new SucceededJobDto
// {
// Job = job,
// Result = stateData.ContainsKey("Result") ? stateData["Result"] : null,
// TotalDuration = stateData.ContainsKey("PerformanceDuration") && stateData.ContainsKey("Latency")
// ? (long?)long.Parse(stateData["PerformanceDuration"]) + (long?)long.Parse(stateData["Latency"])
// : null,
// SucceededAt = JobHelper.DeserializeNullableDateTime(stateData["SucceededAt"])
// }));
// }
// public JobList<DeletedJobDto> DeletedJobs(int @from, int count)
// {
// return UseConnection(connection => GetJobs(
// connection,
// from,
// count,
// DeletedState.StateName,
// (sqlJob, job, stateData) => new DeletedJobDto
// {
// Job = job,
// DeletedAt = JobHelper.DeserializeNullableDateTime(stateData["DeletedAt"])
// }));
// }
// public IList<QueueWithTopEnqueuedJobsDto> Queues()
// {
// var tuples = _storage.QueueProviders
// .Select(x => x.GetJobQueueMonitoringApi())
// .SelectMany(x => x.GetQueues(), (monitoring, queue) => new { Monitoring = monitoring, Queue = queue })
// .OrderBy(x => x.Queue)
// .ToArray();
// var result = new List<QueueWithTopEnqueuedJobsDto>(tuples.Length);
// foreach (var tuple in tuples)
// {
// var enqueuedJobIds = tuple.Monitoring.GetEnqueuedJobIds(tuple.Queue, 0, 5);
// var counters = tuple.Monitoring.GetEnqueuedAndFetchedCount(tuple.Queue);
// var firstJobs = UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds));
// result.Add(new QueueWithTopEnqueuedJobsDto
// {
// Name = tuple.Queue,
// Length = counters.EnqueuedCount ?? 0,
// Fetched = counters.FetchedCount,
// FirstJobs = firstJobs
// });
// }
// return result;
// }
// public JobList<EnqueuedJobDto> EnqueuedJobs(string queue, int @from, int perPage)
// {
// var queueApi = GetQueueApi(queue);
// var enqueuedJobIds = queueApi.GetEnqueuedJobIds(queue, from, perPage);
// return UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds));
// }
// public JobList<FetchedJobDto> FetchedJobs(string queue, int @from, int perPage)
// {
// var queueApi = GetQueueApi(queue);
// var fetchedJobIds = queueApi.GetFetchedJobIds(queue, from, perPage);
// return UseConnection(connection => FetchedJobs(connection, fetchedJobIds));
// }
// public IDictionary<DateTime, long> HourlySucceededJobs()
// {
// return UseConnection(connection =>
// GetHourlyTimelineStats(connection, "succeeded"));
// }
// public IDictionary<DateTime, long> HourlyFailedJobs()
// {
// return UseConnection(connection =>
// GetHourlyTimelineStats(connection, "failed"));
// }
// public JobDetailsDto JobDetails(string jobId)
// {
// return UseConnection(connection =>
// {
// string sql = string.Format(@"
//select * from [{0}].Job where Id = @id
//select * from [{0}].JobParameter where JobId = @id
//select * from [{0}].State where JobId = @id order by Id desc", _storage.GetSchemaName());
// using (var multi = connection.QueryMultiple(sql, new { id = jobId }))
// {
// var job = multi.Read<SqlJob>().SingleOrDefault();
// if (job == null) return null;
// var parameters = multi.Read<JobParameter>().ToDictionary(x => x.Name, x => x.Value);
// var history =
// multi.Read<SqlState>()
// .ToList()
// .Select(x => new StateHistoryDto
// {
// StateName = x.Name,
// CreatedAt = x.CreatedAt,
// Reason = x.Reason,
// Data = new Dictionary<string, string>(
// JobHelper.FromJson<Dictionary<string, string>>(x.Data),
// StringComparer.OrdinalIgnoreCase),
// })
// .ToList();
// return new JobDetailsDto
// {
// CreatedAt = job.CreatedAt,
// ExpireAt = job.ExpireAt,
// Job = DeserializeJob(job.InvocationData, job.Arguments),
// History = history,
// Properties = parameters
// };
// }
// });
// }
// public long SucceededListCount()
// {
// return UseConnection(connection =>
// GetNumberOfJobsByStateName(connection, SucceededState.StateName));
// }
// public long DeletedListCount()
// {
// return UseConnection(connection =>
// GetNumberOfJobsByStateName(connection, DeletedState.StateName));
// }
// public StatisticsDto GetStatistics()
// {
// string sql = string.Format(@"
//select count(Id) from [{0}].Job where StateName = N'Enqueued';
//select count(Id) from [{0}].Job where StateName = N'Failed';
//select count(Id) from [{0}].Job where StateName = N'Processing';
//select count(Id) from [{0}].Job where StateName = N'Scheduled';
//select count(Id) from [{0}].Server;
//select sum(s.[Value]) from (
// select sum([Value]) as [Value] from [{0}].Counter where [Key] = N'stats:succeeded'
// union all
// select [Value] from [{0}].AggregatedCounter where [Key] = N'stats:succeeded'
//) as s;
//select sum(s.[Value]) from (
// select sum([Value]) as [Value] from [{0}].Counter where [Key] = N'stats:deleted'
// union all
// select [Value] from [{0}].AggregatedCounter where [Key] = N'stats:deleted'
//) as s;
//select count(*) from [{0}].[Set] where [Key] = N'recurring-jobs';
//", _storage.GetSchemaName());
// var statistics = UseConnection(connection =>
// {
// var stats = new StatisticsDto();
// using (var multi = connection.QueryMultiple(sql))
// {
// stats.Enqueued = multi.Read<int>().Single();
// stats.Failed = multi.Read<int>().Single();
// stats.Processing = multi.Read<int>().Single();
// stats.Scheduled = multi.Read<int>().Single();
// stats.Servers = multi.Read<int>().Single();
// stats.Succeeded = multi.Read<long?>().SingleOrDefault() ?? 0;
// stats.Deleted = multi.Read<long?>().SingleOrDefault() ?? 0;
// stats.Recurring = multi.Read<int>().Single();
// }
// return stats;
// });
// statistics.Queues = _storage.QueueProviders
// .SelectMany(x => x.GetJobQueueMonitoringApi().GetQueues())
// .Count();
// return statistics;
// }
// private Dictionary<DateTime, long> GetHourlyTimelineStats(
// SqlConnection connection,
// string type)
// {
// var endDate = DateTime.UtcNow;
// var dates = new List<DateTime>();
// for (var i = 0; i < 24; i++)
// {
// dates.Add(endDate);
// endDate = endDate.AddHours(-1);
// }
// var keyMaps = dates.ToDictionary(x => String.Format("stats:{0}:{1}", type, x.ToString("yyyy-MM-dd-HH")), x => x);
// return GetTimelineStats(connection, keyMaps);
// }
// private Dictionary<DateTime, long> GetTimelineStats(
// SqlConnection connection,
// string type)
// {
// var endDate = DateTime.UtcNow.Date;
// var dates = new List<DateTime>();
// for (var i = 0; i < 7; i++)
// {
// dates.Add(endDate);
// endDate = endDate.AddDays(-1);
// }
// var keyMaps = dates.ToDictionary(x => String.Format("stats:{0}:{1}", type, x.ToString("yyyy-MM-dd")), x => x);
// return GetTimelineStats(connection, keyMaps);
// }
// private Dictionary<DateTime, long> GetTimelineStats(SqlConnection connection,
// IDictionary<string, DateTime> keyMaps)
// {
// string sqlQuery = string.Format(@"
//select [Key], [Value] as [Count] from [{0}].AggregatedCounter
//where [Key] in @keys", _storage.GetSchemaName());
// var valuesMap = connection.Query(
// sqlQuery,
// new { keys = keyMaps.Keys })
// .ToDictionary(x => (string)x.Key, x => (long)x.Count);
// foreach (var key in keyMaps.Keys)
// {
// if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0);
// }
// var result = new Dictionary<DateTime, long>();
// for (var i = 0; i < keyMaps.Count; i++)
// {
// var value = valuesMap[keyMaps.ElementAt(i).Key];
// result.Add(keyMaps.ElementAt(i).Value, value);
// }
// return result;
// }
// private IPersistentJobQueueMonitoringApi GetQueueApi(string queueName)
// {
// var provider = _storage.QueueProviders.GetProvider(queueName);
// var monitoringApi = provider.GetJobQueueMonitoringApi();
// return monitoringApi;
// }
// private T UseConnection<T>(Func<SqlConnection, T> action)
// {
// return _storage.UseTransaction(action, IsolationLevel.ReadUncommitted);
// }
// private JobList<EnqueuedJobDto> EnqueuedJobs(
// SqlConnection connection,
// IEnumerable<int> jobIds)
// {
// string enqueuedJobsSql = string.Format(@"
//select j.*, s.Reason as StateReason, s.Data as StateData
//from [{0}].Job j
//left join [{0}].State s on s.Id = j.StateId
//where j.Id in @jobIds", _storage.GetSchemaName());
// var jobs = connection.Query<Job>(
// enqueuedJobsSql,
// new { jobIds = jobIds })
// .ToList();
// return DeserializeJobs(
// jobs,
// (sqlJob, job, stateData) => new EnqueuedJobDto
// {
// Job = job,
// State = sqlJob.StateName,
// EnqueuedAt = sqlJob.StateName == EnqueuedState.StateName
// ? JobHelper.DeserializeNullableDateTime(stateData["EnqueuedAt"])
// : null
// });
// }
// private long GetNumberOfJobsByStateName(SqlConnection connection, string stateName)
// {
// var sqlQuery = _jobListLimit.HasValue
// ? string.Format(@"select count(j.Id) from (select top (@limit) Id from [{0}].Job where StateName = @state) as j", _storage.GetSchemaName())
// : string.Format(@"select count(Id) from [{0}].Job where StateName = @state", _storage.GetSchemaName());
// var count = connection.Query<int>(
// sqlQuery,
// new { state = stateName, limit = _jobListLimit })
// .Single();
// return count;
// }
// private static Job DeserializeJob(string invocationData, string arguments)
// {
// var data = JobHelper.FromJson<InvocationData>(invocationData);
// data.Arguments = arguments;
// try
// {
// return data.Deserialize();
// }
// catch (JobLoadException)
// {
// return null;
// }
// }
// private JobList<TDto> GetJobs<TDto>(
// SqlConnection connection,
// int from,
// int count,
// string stateName,
// Func<Job, Job, Dictionary<string, string>, TDto> selector)
// {
// string jobsSql = string.Format(@"
//select * from (
// select j.*, s.Reason as StateReason, s.Data as StateData, row_number() over (order by j.Id desc) as row_num
// from [{0}].Job j with (forceseek)
// left join [{0}].State s on j.StateId = s.Id
// where j.StateName = @stateName
//) as j where j.row_num between @start and @end
//", _storage.GetSchemaName());
// var jobs = connection.Query<Job>(
// jobsSql,
// new { stateName = stateName, start = @from + 1, end = @from + count })
// .ToList();
// return DeserializeJobs(jobs, selector);
// }
// private static JobList<TDto> DeserializeJobs<TDto>(
// ICollection<Job> jobs,
// Func<Job, Job, Dictionary<string, string>, TDto> selector)
// {
// var result = new List<KeyValuePair<string, TDto>>(jobs.Count);
// foreach (var job in jobs)
// {
// var deserializedData = JobHelper.FromJson<Dictionary<string, string>>(job.StateData);
// var stateData = deserializedData != null
// ? new Dictionary<string, string>(deserializedData, StringComparer.OrdinalIgnoreCase)
// : null;
// var dto = selector(job, DeserializeJob(job.InvocationData, job.Arguments), stateData);
// result.Add(new KeyValuePair<string, TDto>(
// job.Id.ToString(), dto));
// }
// return new JobList<TDto>(result);
// }
// private JobList<FetchedJobDto> FetchedJobs(
// SqlConnection connection,
// IEnumerable<int> jobIds)
// {
// string fetchedJobsSql = string.Format(@"
//select j.*, s.Reason as StateReason, s.Data as StateData
//from [{0}].Job j
//left join [{0}].State s on s.Id = j.StateId
//where j.Id in @jobIds", _storage.GetSchemaName());
// var jobs = connection.Query<Job>(
// fetchedJobsSql,
// new { jobIds = jobIds })
// .ToList();
// var result = new List<KeyValuePair<string, FetchedJobDto>>(jobs.Count);
// foreach (var job in jobs)
// {
// result.Add(new KeyValuePair<string, FetchedJobDto>(
// job.Id.ToString(),
// new FetchedJobDto
// {
// Job = DeserializeJob(job.InvocationData, job.Arguments),
// State = job.StateName,
// }));
// }
// return new JobList<FetchedJobDto>(result);
// }
// }
//}
@@ -0,0 +1,24 @@
using Hangfire.States;
using Hangfire.Storage;
namespace Hangfire.Raven.StateHandlers
{
public class DeletedStateHandler : IStateHandler
{
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.InsertToList("deleted", context.JobId);
transaction.TrimList("deleted", 0, 99);
}
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.RemoveFromList("deleted", context.JobId);
}
public string StateName
{
get { return DeletedState.StateName; }
}
}
}
@@ -0,0 +1,25 @@
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using System;
namespace Hangfire.Raven.StateHandlers
{
public class FailedStateHandler : IStateHandler
{
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.AddToSet("failed", context.JobId, JobHelper.ToTimestamp(DateTime.UtcNow));
}
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.RemoveFromSet("failed", context.JobId);
}
public string StateName
{
get { return FailedState.StateName; }
}
}
}
@@ -0,0 +1,25 @@
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using System;
namespace Hangfire.Raven.StateHandlers
{
public class ProcessingStateHandler : IStateHandler
{
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.AddToSet("processing", context.JobId, JobHelper.ToTimestamp(DateTime.UtcNow));
}
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.RemoveFromSet("processing", context.JobId);
}
public string StateName
{
get { return ProcessingState.StateName; }
}
}
}
@@ -0,0 +1,24 @@
using Hangfire.States;
using Hangfire.Storage;
namespace Hangfire.Raven.StateHandlers
{
public class SucceededStateHandler : IStateHandler
{
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.InsertToList("succeeded", context.JobId);
transaction.TrimList("succeeded", 0, 99);
}
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
transaction.RemoveFromList("succeeded", context.JobId);
}
public string StateName
{
get { return SucceededState.StateName; }
}
}
}
@@ -0,0 +1,87 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using Hangfire.Logging;
using Hangfire.Server;
using Hangfire.Storage;
using Raven.Client.Document;
using Hangfire.Raven.JobQueues;
namespace Hangfire.Raven.Storage
{
public class RavenStorage : JobStorage
{
private readonly RavenStorageOptions _options;
public RavenStorage()
: this(new RavenStorageOptions())
{
}
/// <summary>
/// Initializes RavenStorage from the provided SqlServerStorageOptions and either the provided connection
/// string or the connection string with provided name pulled from the application config file.
/// </summary>
/// <param name="connectionString"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"><paramref name="connectionString"/> argument is null.</exception>
/// <exception cref="ArgumentNullException"><paramref name="options"/> argument is null.</exception>
/// <exception cref="ArgumentException"><paramref name="connectionString"/> argument is neither
/// a valid Raven connection string nor the name of a connection string in the application
/// config file.</exception>
public RavenStorage(RavenStorageOptions options)
{
options.ThrowIfNull("options");
_options = options;
InitializeQueueProviders();
}
public virtual PersistentJobQueueProviderCollection QueueProviders { get; private set; }
public override IMonitoringApi GetMonitoringApi()
{
//return new SqlServerMonitoringApi(this, _options.DashboardJobListLimit);
return null;
}
public override IStorageConnection GetConnection()
{
return new RavenConnection(this);
}
public override IEnumerable<IServerComponent> GetComponents()
{
yield return new ExpirationManager(_options.JobExpirationCheckInterval);
}
public override void WriteOptionsToLog(ILog logger)
{
logger.Info("Using the following options for Raven job storage:");
logger.InfoFormat("Queue poll interval: {0}.", _options.QueuePollInterval);
}
private void InitializeQueueProviders()
{
var defaultQueueProvider = new RavenJobQueueProvider(this, _options);
QueueProviders = new PersistentJobQueueProviderCollection(defaultQueueProvider);
}
}
}
@@ -0,0 +1,87 @@
// This file is part of Hangfire.
// Copyright © 2015 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
using Hangfire;
using Hangfire.Raven.Storage;
using Hangfire.Raven;
// ReSharper disable once CheckNamespace
namespace HangFire.Raven.Storage
{
public static class SqlServerStorageExtensions
{
public static IGlobalConfiguration<RavenStorage> UseRavenStorage(this IGlobalConfiguration configuration, string connectionUrl, string database)
{
configuration.ThrowIfNull("configuration");
connectionUrl.ThrowIfNull("connectionUrl");
database.ThrowIfNull("database");
if (!connectionUrl.StartsWith("http")) {
throw new ArgumentException("Connection Url must begin with http or https!");
}
Repository.ConnectionUrl = connectionUrl;
Repository.DefaultDatabase = database;
var storage = new RavenStorage();
return configuration.UseStorage(storage);
}
public static IGlobalConfiguration<RavenStorage> UseRavenStorage(this IGlobalConfiguration configuration, string connectionUrl, string database, RavenStorageOptions options)
{
configuration.ThrowIfNull("configuration");
connectionUrl.ThrowIfNull("connectionUrl");
database.ThrowIfNull("database");
options.ThrowIfNull("options");
if (!connectionUrl.StartsWith("http")) {
throw new ArgumentException("Connection Url must begin with http or https!");
}
Repository.ConnectionUrl = connectionUrl;
Repository.DefaultDatabase = database;
var storage = new RavenStorage(options);
return configuration.UseStorage(storage);
}
public static IGlobalConfiguration<RavenStorage> UseEmbeddedRavenStorage(this IGlobalConfiguration configuration)
{
configuration.ThrowIfNull("configuration");
Repository.Embedded = true;
var storage = new RavenStorage();
return configuration.UseStorage(storage);
}
public static IGlobalConfiguration<RavenStorage> UseEmbeddedRavenStorage(this IGlobalConfiguration configuration, RavenStorageOptions options)
{
configuration.ThrowIfNull("configuration");
options.ThrowIfNull("options");
Repository.Embedded = true;
var storage = new RavenStorage(options);
return configuration.UseStorage(storage);
}
}
}
@@ -0,0 +1,80 @@
// This file is part of Hangfire.
// Copyright © 2013-2014 Sergey Odinokov.
//
// Hangfire is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3
// of the License, or any later version.
//
// Hangfire is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace Hangfire.Raven.Storage
{
public class RavenStorageOptions
{
private readonly string _clientId = null;
private TimeSpan _queuePollInterval;
public RavenStorageOptions()
{
QueuePollInterval = TimeSpan.FromSeconds(15);
InvisibilityTimeout = TimeSpan.FromMinutes(30);
JobExpirationCheckInterval = TimeSpan.FromHours(1);
CountersAggregateInterval = TimeSpan.FromMinutes(5);
PrepareSchemaIfNecessary = true;
DashboardJobListLimit = 50000;
TransactionTimeout = TimeSpan.FromMinutes(1);
DistributedLockLifetime = TimeSpan.FromSeconds(30);
_clientId = Guid.NewGuid().ToString().Replace("-", String.Empty);
}
public TimeSpan QueuePollInterval
{
get { return _queuePollInterval; }
set
{
var message = string.Format(
"The QueuePollInterval property value should be positive. Given: {0}.",
value);
if (value == TimeSpan.Zero)
{
throw new ArgumentException(message, "value");
}
if (value != value.Duration())
{
throw new ArgumentException(message, "value");
}
_queuePollInterval = value;
}
}
[Obsolete("Does not make sense anymore. Background jobs re-queued instantly even after ungraceful shutdown now. Will be removed in 2.0.0.")]
public TimeSpan InvisibilityTimeout { get; set; }
public bool PrepareSchemaIfNecessary { get; set; }
public TimeSpan JobExpirationCheckInterval { get; set; }
public TimeSpan CountersAggregateInterval { get; set; }
public int? DashboardJobListLimit { get; set; }
public TimeSpan TransactionTimeout { get; set; }
public TimeSpan DistributedLockLifetime { get; set; }
public string ClientId
{
get { return _clientId; }
}
}
}
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Dewey.Net" version="2.7.8.0" targetFramework="net45" />
<package id="Hangfire" version="1.5.0" targetFramework="net45" />
<package id="Hangfire.Core" version="1.5.0" targetFramework="net45" />
<package id="Hangfire.SqlServer" version="1.5.0" targetFramework="net45" />
<package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" targetFramework="net45" />
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="RavenDB.Client" version="3.0.3800" targetFramework="net45" />
<package id="RavenDB.Database" version="3.0.3800" targetFramework="net45" />
<package id="RavenDB.Embedded" version="3.0.3800" targetFramework="net45" />
</packages>
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyProduct("Hangfire")]
[assembly: AssemblyCompany("Sergey Odinokov")]
[assembly: AssemblyCopyright("Copyright © 2013-2015 Sergey Odinokov")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: CLSCompliant(false)]
// Don't edit manually! Use `build.bat version` command instead!
[assembly: AssemblyVersion("1.5.0")]
@@ -0,0 +1,230 @@
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.Tests.Utils;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using System;
using System.Threading;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class ExpirationManagerFacts
{
private readonly MongoStorage _storage;
private readonly CancellationToken _token;
public ExpirationManagerFacts()
{
_storage = new MongoStorage(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName());
_token = new CancellationToken(true);
}
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
Assert.Throws<ArgumentNullException>(() => new ExpirationManager(null));
}
[Fact, CleanDatabase]
public void Execute_RemovesOutdatedRecords()
{
using (var connection = ConnectionUtils.CreateConnection())
{
var entryId = CreateExpirationEntry(connection, connection.GetServerTimeUtc().AddMonths(-1));
var manager = CreateManager();
manager.Execute(_token);
Assert.True(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
{
using (var connection = ConnectionUtils.CreateConnection())
{
var entryId = CreateExpirationEntry(connection, null);
var manager = CreateManager();
manager.Execute(_token);
Assert.False(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
{
using (var connection = ConnectionUtils.CreateConnection())
{
var entryId = CreateExpirationEntry(connection, DateTime.Now.AddMonths(1));
var manager = CreateManager();
manager.Execute(_token);
Assert.False(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_Processes_CounterTable()
{
using (var connection = ConnectionUtils.CreateConnection())
{
// Arrange
connection.Counter.Insert(new CounterDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Value = 1,
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
});
var manager = CreateManager();
// Act
manager.Execute(_token);
// Assert
var count = connection.Counter.Count();
Assert.Equal(0, count);
}
}
[Fact, CleanDatabase]
public void Execute_Processes_JobTable()
{
using (var connection = ConnectionUtils.CreateConnection())
{
// Arrange
connection.Job.Insert(new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc(),
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1),
});
var manager = CreateManager();
// Act
manager.Execute(_token);
// Assert
var count = connection.Job.Count();
Assert.Equal(0, count);
}
}
[Fact, CleanDatabase]
public void Execute_Processes_ListTable()
{
using (var connection = ConnectionUtils.CreateConnection())
{
// Arrange
connection.List.Insert(new ListDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
});
var manager = CreateManager();
// Act
manager.Execute(_token);
// Assert
var count = connection.List.Count();
Assert.Equal(0, count);
}
}
[Fact, CleanDatabase]
public void Execute_Processes_SetTable()
{
using (var connection = ConnectionUtils.CreateConnection())
{
// Arrange
connection.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Score = 0,
Value = "",
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
});
var manager = CreateManager();
// Act
manager.Execute(_token);
// Assert
var count = connection.Set.Count();
Assert.Equal(0, count);
}
}
[Fact, CleanDatabase]
public void Execute_Processes_HashTable()
{
using (var connection = ConnectionUtils.CreateConnection())
{
// Arrange
connection.Hash.Insert(new HashDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Field = "field",
Value = "",
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
});
var manager = CreateManager();
// Act
manager.Execute(_token);
// Assert
var count = connection.Hash.Count();
Assert.Equal(0, count);
}
}
private static ObjectId CreateExpirationEntry(HangfireDbContext connection, DateTime? expireAt)
{
var counter = new CounterDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Value = 1,
ExpireAt = expireAt
};
connection.Counter.Insert(counter);
var id = counter.Id;
return id;
}
private static bool IsEntryExpired(HangfireDbContext connection, ObjectId entryId)
{
var count = connection.Counter.Count(Query<CounterDto>.EQ(_ => _.Id, entryId));
return count == 0;
}
private ExpirationManager CreateManager()
{
return new ExpirationManager(_storage);
}
}
}
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{237F15A4-26DB-4FBD-86AF-81E87AD5762A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Hangfire.Mongo.Tests</RootNamespace>
<AssemblyName>Hangfire.Mongo.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Hangfire.Core, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Hangfire.Core.1.3.0\lib\net45\Hangfire.Core.dll</HintPath>
</Reference>
<Reference Include="MongoDB.Bson, Version=1.9.2.235, Culture=neutral, PublicKeyToken=f686731cfb9cc103, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Bson.dll</HintPath>
</Reference>
<Reference Include="MongoDB.Driver, Version=1.9.2.235, Culture=neutral, PublicKeyToken=f686731cfb9cc103, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Driver.dll</HintPath>
</Reference>
<Reference Include="Moq">
<HintPath>..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xunit">
<HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ExpirationManagerFacts.cs" />
<Compile Include="MongoConnectionFacts.cs" />
<Compile Include="MongoDistributedLockFacts.cs" />
<Compile Include="MongoFetchedJobFacts.cs" />
<Compile Include="MongoJobQueueFacts.cs" />
<Compile Include="MongoStorageFacts.cs" />
<Compile Include="MongoStorageOptionsFacts.cs" />
<Compile Include="MongoWriteOnlyTransactionFacts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\CleanDatabaseAttribute.cs" />
<Compile Include="Utils\ConnectionUtils.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Hangfire.Mongo\Hangfire.Mongo.csproj">
<Project>{1fe9c86e-db7a-4c1b-b73a-42b993d68688}</Project>
<Name>Hangfire.Mongo</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -0,0 +1,869 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Hangfire.Common;
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.PersistentJobQueue;
using Hangfire.Mongo.Tests.Utils;
using Hangfire.Server;
using Hangfire.Storage;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
using Moq;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoConnectionFacts
{
private readonly Mock<IPersistentJobQueue> _queue;
private readonly Mock<IPersistentJobQueueProvider> _provider;
private readonly PersistentJobQueueProviderCollection _providers;
public MongoConnectionFacts()
{
_queue = new Mock<IPersistentJobQueue>();
_provider = new Mock<IPersistentJobQueueProvider>();
_provider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>())).Returns(_queue.Object);
_providers = new PersistentJobQueueProviderCollection(_provider.Object);
}
[Fact]
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoConnection(null, _providers));
Assert.Equal("database", exception.ParamName);
}
[Fact, CleanDatabase]
public void Ctor_ThrowsAnException_WhenProvidersCollectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoConnection(ConnectionUtils.CreateConnection(), null));
Assert.Equal("queueProviders", exception.ParamName);
}
[Fact, CleanDatabase]
public void FetchNextJob_DelegatesItsExecution_ToTheQueue()
{
UseConnection((database, connection) =>
{
var token = new CancellationToken();
var queues = new[] { "default" };
connection.FetchNextJob(queues, token);
_queue.Verify(x => x.Dequeue(queues, token));
});
}
[Fact, CleanDatabase]
public void FetchNextJob_Throws_IfMultipleProvidersResolved()
{
UseConnection((database, connection) =>
{
var token = new CancellationToken();
var anotherProvider = new Mock<IPersistentJobQueueProvider>();
_providers.Add(anotherProvider.Object, new[] { "critical" });
Assert.Throws<InvalidOperationException>(
() => connection.FetchNextJob(new[] { "critical", "default" }, token));
});
}
[Fact, CleanDatabase]
public void CreateWriteTransaction_ReturnsNonNullInstance()
{
UseConnection((database, connection) =>
{
var transaction = connection.CreateWriteTransaction();
Assert.NotNull(transaction);
});
}
[Fact, CleanDatabase]
public void AcquireLock_ReturnsNonNullInstance()
{
UseConnection((database, connection) =>
{
var @lock = connection.AcquireDistributedLock("1", TimeSpan.FromSeconds(1));
Assert.NotNull(@lock);
});
}
[Fact, CleanDatabase]
public void CreateExpiredJob_ThrowsAnException_WhenJobIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.CreateExpiredJob(
null,
new Dictionary<string, string>(),
database.GetServerTimeUtc(),
TimeSpan.Zero));
Assert.Equal("job", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void CreateExpiredJob_ThrowsAnException_WhenParametersCollectionIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.CreateExpiredJob(
Job.FromExpression(() => SampleMethod("hello")),
null,
database.GetServerTimeUtc(),
TimeSpan.Zero));
Assert.Equal("parameters", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void CreateExpiredJob_CreatesAJobInTheStorage_AndSetsItsParameters()
{
UseConnection((database, connection) =>
{
var createdAt = new DateTime(2012, 12, 12, 0, 0, 0, 0, DateTimeKind.Utc);
var jobId = connection.CreateExpiredJob(Job.FromExpression(() => SampleMethod("Hello")),
new Dictionary<string, string> { { "Key1", "Value1" }, { "Key2", "Value2" } },
createdAt,
TimeSpan.FromDays(1));
Assert.NotNull(jobId);
Assert.NotEmpty(jobId);
var databaseJob = database.Job.FindAll().Single();
Assert.Equal(jobId, databaseJob.Id.ToString());
Assert.Equal(createdAt, databaseJob.CreatedAt);
Assert.Equal(ObjectId.Empty, databaseJob.StateId);
Assert.Equal(null, databaseJob.StateName);
var invocationData = JobHelper.FromJson<InvocationData>((string)databaseJob.InvocationData);
invocationData.Arguments = databaseJob.Arguments;
var job = invocationData.Deserialize();
Assert.Equal(typeof(MongoConnectionFacts), job.Type);
Assert.Equal("SampleMethod", job.Method.Name);
Assert.Equal("\"Hello\"", job.Arguments[0]);
Assert.True(createdAt.AddDays(1).AddMinutes(-1) < databaseJob.ExpireAt);
Assert.True(databaseJob.ExpireAt < createdAt.AddDays(1).AddMinutes(1));
var parameters = database.JobParameter.Find(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)))
.ToDictionary(x => x.Name, x => x.Value);
Assert.Equal("Value1", parameters["Key1"]);
Assert.Equal("Value2", parameters["Key2"]);
});
}
[Fact, CleanDatabase]
public void GetJobData_ThrowsAnException_WhenJobIdIsNull()
{
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
() => connection.GetJobData(null)));
}
[Fact, CleanDatabase]
public void GetJobData_ReturnsNull_WhenThereIsNoSuchJob()
{
UseConnection((database, connection) =>
{
var result = connection.GetJobData("547527b4c6b6cc26a02d021d");
Assert.Null(result);
});
}
[Fact, CleanDatabase]
public void GetJobData_ReturnsResult_WhenJobExists()
{
UseConnection((database, connection) =>
{
var job = Job.FromExpression(() => SampleMethod("wrong"));
var jobDto = new JobDto
{
Id = 1,
InvocationData = JobHelper.ToJson(InvocationData.Serialize(job)),
Arguments = "['Arguments']",
StateName = "Succeeded",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
var result = connection.GetJobData(jobDto.Id.ToString());
Assert.NotNull(result);
Assert.NotNull(result.Job);
Assert.Equal("Succeeded", result.State);
Assert.Equal("Arguments", result.Job.Arguments[0]);
Assert.Null(result.LoadException);
Assert.True(database.GetServerTimeUtc().AddMinutes(-1) < result.CreatedAt);
Assert.True(result.CreatedAt < DateTime.UtcNow.AddMinutes(1));
});
}
[Fact, CleanDatabase]
public void GetStateData_ThrowsAnException_WhenJobIdIsNull()
{
UseConnection(
(database, connection) => Assert.Throws<ArgumentNullException>(
() => connection.GetStateData(null)));
}
[Fact, CleanDatabase]
public void GetStateData_ReturnsNull_IfThereIsNoSuchState()
{
UseConnection((database, connection) =>
{
var result = connection.GetStateData("547527b4c6b6cc26a02d021d");
Assert.Null(result);
});
}
[Fact, CleanDatabase]
public void GetStateData_ReturnsCorrectData()
{
UseConnection((database, connection) =>
{
var data = new Dictionary<string, string>
{
{ "Key", "Value" }
};
var jobDto = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
StateName = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
var jobId = jobDto.Id;
database.State.Insert(new StateDto
{
Id = ObjectId.GenerateNewId(),
JobId = jobId,
Name = "old-state",
CreatedAt = database.GetServerTimeUtc()
});
var stateDto = new StateDto
{
Id = ObjectId.GenerateNewId(),
JobId = jobId,
Name = "Name",
Reason = "Reason",
Data = JobHelper.ToJson(data),
CreatedAt = database.GetServerTimeUtc()
};
database.State.Insert(stateDto);
jobDto.StateId = stateDto.Id;
database.Job.Save(jobDto);
var result = connection.GetStateData(jobId.ToString());
Assert.NotNull(result);
Assert.Equal("Name", result.Name);
Assert.Equal("Reason", result.Reason);
Assert.Equal("Value", result.Data["Key"]);
});
}
[Fact, CleanDatabase]
public void GetJobData_ReturnsJobLoadException_IfThereWasADeserializationException()
{
UseConnection((database, connection) =>
{
var jobDto = new JobDto
{
Id = 1,
InvocationData = JobHelper.ToJson(new InvocationData(null, null, null, null)),
Arguments = "['Arguments']",
StateName = "Succeeded",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
var jobId = jobDto.Id;
var result = connection.GetJobData(jobId.ToString());
Assert.NotNull(result.LoadException);
});
}
[Fact, CleanDatabase]
public void SetParameter_ThrowsAnException_WhenJobIdIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.SetJobParameter(null, "name", "value"));
Assert.Equal("id", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetParameter_ThrowsAnException_WhenNameIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.SetJobParameter("547527b4c6b6cc26a02d021d", null, "value"));
Assert.Equal("name", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetParameters_CreatesNewParameter_WhenParameterWithTheGivenNameDoesNotExists()
{
UseConnection((database, connection) =>
{
var jobDto = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
string jobId = jobDto.Id.ToString();
connection.SetJobParameter(jobId, "Name", "Value");
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
Assert.Equal("Value", parameter.Value);
});
}
[Fact, CleanDatabase]
public void SetParameter_UpdatesValue_WhenParameterWithTheGivenName_AlreadyExists()
{
UseConnection((database, connection) =>
{
var jobDto = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
string jobId = jobDto.Id.ToString();
connection.SetJobParameter(jobId, "Name", "Value");
connection.SetJobParameter(jobId, "Name", "AnotherValue");
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
Assert.Equal("AnotherValue", parameter.Value);
});
}
[Fact, CleanDatabase]
public void SetParameter_CanAcceptNulls_AsValues()
{
UseConnection((database, connection) =>
{
var jobDto = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
string jobId = jobDto.Id.ToString();
connection.SetJobParameter(jobId, "Name", null);
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
Assert.Equal(null, parameter.Value);
});
}
[Fact, CleanDatabase]
public void GetParameter_ThrowsAnException_WhenJobIdIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.GetJobParameter(null, "hello"));
Assert.Equal("id", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void GetParameter_ThrowsAnException_WhenNameIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.GetJobParameter("547527b4c6b6cc26a02d021d", null));
Assert.Equal("name", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void GetParameter_ReturnsNull_WhenParameterDoesNotExists()
{
UseConnection((database, connection) =>
{
var value = connection.GetJobParameter("1", "hello");
Assert.Null(value);
});
}
[Fact, CleanDatabase]
public void GetParameter_ReturnsParameterValue_WhenJobExists()
{
UseConnection((database, connection) =>
{
var jobDto = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(jobDto);
string jobId = jobDto.Id.ToString();
database.JobParameter.Insert(new JobParameterDto
{
Id = ObjectId.GenerateNewId(),
JobId = int.Parse(jobId),
Name = "name",
Value = "value"
});
var value = connection.GetJobParameter(jobId, "name");
Assert.Equal("value", value);
});
}
[Fact, CleanDatabase]
public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.GetFirstByLowestScoreFromSet(null, 0, 1));
Assert.Equal("key", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore()
{
UseConnection((database, connection) => Assert.Throws<ArgumentException>(
() => connection.GetFirstByLowestScoreFromSet("key", 0, -1)));
}
[Fact, CleanDatabase]
public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist()
{
UseConnection((database, connection) =>
{
var result = connection.GetFirstByLowestScoreFromSet(
"key", 0, 1);
Assert.Null(result);
});
}
[Fact, CleanDatabase]
public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore()
{
UseConnection((database, connection) =>
{
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Score = 1.0,
Value = "1.0"
});
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Score = -1.0,
Value = "-1.0"
});
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "key",
Score = -5.0,
Value = "-5.0"
});
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "another-key",
Score = -2.0,
Value = "-2.0"
});
var result = connection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0);
Assert.Equal("-1.0", result);
});
}
[Fact, CleanDatabase]
public void AnnounceServer_ThrowsAnException_WhenServerIdIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.AnnounceServer(null, new ServerContext()));
Assert.Equal("serverId", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void AnnounceServer_ThrowsAnException_WhenContextIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.AnnounceServer("server", null));
Assert.Equal("context", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void AnnounceServer_CreatesOrUpdatesARecord()
{
UseConnection((database, connection) =>
{
var context1 = new ServerContext
{
Queues = new[] { "critical", "default" },
WorkerCount = 4
};
connection.AnnounceServer("server", context1);
var server = database.Server.FindAll().Single();
Assert.Equal("server", server.Id);
Assert.True(((string)server.Data).StartsWith(
"{\"WorkerCount\":4,\"Queues\":[\"critical\",\"default\"],\"StartedAt\":"),
server.Data);
Assert.NotNull(server.LastHeartbeat);
var context2 = new ServerContext
{
Queues = new[] { "default" },
WorkerCount = 1000
};
connection.AnnounceServer("server", context2);
var sameServer = database.Server.FindAll().Single();
Assert.Equal("server", sameServer.Id);
Assert.Contains("1000", sameServer.Data);
});
}
[Fact, CleanDatabase]
public void RemoveServer_ThrowsAnException_WhenServerIdIsNull()
{
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
() => connection.RemoveServer(null)));
}
[Fact, CleanDatabase]
public void RemoveServer_RemovesAServerRecord()
{
UseConnection((database, connection) =>
{
database.Server.Insert(new ServerDto
{
Id = "Server1",
Data = "",
LastHeartbeat = database.GetServerTimeUtc()
});
database.Server.Insert(new ServerDto
{
Id = "Server2",
Data = "",
LastHeartbeat = database.GetServerTimeUtc()
});
connection.RemoveServer("Server1");
var server = database.Server.FindAll().Single();
Assert.NotEqual("Server1", server.Id, StringComparer.OrdinalIgnoreCase);
});
}
[Fact, CleanDatabase]
public void Heartbeat_ThrowsAnException_WhenServerIdIsNull()
{
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
() => connection.Heartbeat(null)));
}
[Fact, CleanDatabase]
public void Heartbeat_UpdatesLastHeartbeat_OfTheServerWithGivenId()
{
UseConnection((database, connection) =>
{
database.Server.Insert(new ServerDto
{
Id = "server1",
Data = "",
LastHeartbeat = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc)
});
database.Server.Insert(new ServerDto
{
Id = "server2",
Data = "",
LastHeartbeat = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc)
});
connection.Heartbeat("server1");
var servers = database.Server.FindAll()
.ToDictionary(x => x.Id, x => x.LastHeartbeat);
Assert.NotEqual(2012, servers["server1"].Year);
Assert.Equal(2012, servers["server2"].Year);
});
}
[Fact, CleanDatabase]
public void RemoveTimedOutServers_ThrowsAnException_WhenTimeOutIsNegative()
{
UseConnection((database, connection) => Assert.Throws<ArgumentException>(
() => connection.RemoveTimedOutServers(TimeSpan.FromMinutes(-5))));
}
[Fact, CleanDatabase]
public void RemoveTimedOutServers_DoItsWorkPerfectly()
{
UseConnection((database, connection) =>
{
database.Server.Insert(new ServerDto
{
Id = "server1",
Data = "",
LastHeartbeat = database.GetServerTimeUtc().AddDays(-1)
});
database.Server.Insert(new ServerDto
{
Id = "server2",
Data = "",
LastHeartbeat = database.GetServerTimeUtc().AddHours(-12)
});
connection.RemoveTimedOutServers(TimeSpan.FromHours(15));
var liveServer = database.Server.FindAll().Single();
Assert.Equal("server2", liveServer.Id);
});
}
[Fact, CleanDatabase]
public void GetAllItemsFromSet_ThrowsAnException_WhenKeyIsNull()
{
UseConnection((database, connection) =>
Assert.Throws<ArgumentNullException>(() => connection.GetAllItemsFromSet(null)));
}
[Fact, CleanDatabase]
public void GetAllItemsFromSet_ReturnsEmptyCollection_WhenKeyDoesNotExist()
{
UseConnection((database, connection) =>
{
var result = connection.GetAllItemsFromSet("some-set");
Assert.NotNull(result);
Assert.Equal(0, result.Count);
});
}
[Fact, CleanDatabase]
public void GetAllItemsFromSet_ReturnsAllItems()
{
UseConnection((database, connection) =>
{
// Arrange
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "some-set",
Score = 0.0,
Value = "1"
});
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "some-set",
Score = 0.0,
Value = "2"
});
database.Set.Insert(new SetDto
{
Id = ObjectId.GenerateNewId(),
Key = "another-set",
Score = 0.0,
Value = "3"
});
// Act
var result = connection.GetAllItemsFromSet("some-set");
// Assert
Assert.Equal(2, result.Count);
Assert.Contains("1", result);
Assert.Contains("2", result);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.SetRangeInHash(null, new Dictionary<string, string>()));
Assert.Equal("key", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull()
{
UseConnection((database, connection) =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => connection.SetRangeInHash("some-hash", null));
Assert.Equal("keyValuePairs", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_MergesAllRecords()
{
UseConnection((database, connection) =>
{
connection.SetRangeInHash("some-hash", new Dictionary<string, string>
{
{ "Key1", "Value1" },
{ "Key2", "Value2" }
});
var result = database.Hash.Find(Query<HashDto>.EQ(_=>_.Key, "some-hash"))
.ToDictionary(x => x.Field, x => x.Value);
Assert.Equal("Value1", result["Key1"]);
Assert.Equal("Value2", result["Key2"]);
});
}
[Fact, CleanDatabase]
public void GetAllEntriesFromHash_ThrowsAnException_WhenKeyIsNull()
{
UseConnection((database, connection) =>
Assert.Throws<ArgumentNullException>(() => connection.GetAllEntriesFromHash(null)));
}
[Fact, CleanDatabase]
public void GetAllEntriesFromHash_ReturnsNull_IfHashDoesNotExist()
{
UseConnection((database, connection) =>
{
var result = connection.GetAllEntriesFromHash("some-hash");
Assert.Null(result);
});
}
[Fact, CleanDatabase]
public void GetAllEntriesFromHash_ReturnsAllKeysAndTheirValues()
{
UseConnection((database, connection) =>
{
// Arrange
database.Hash.Insert(new HashDto
{
Id = ObjectId.GenerateNewId(),
Key = "some-hash",
Field = "Key1",
Value = "Value1"
});
database.Hash.Insert(new HashDto
{
Id = ObjectId.GenerateNewId(),
Key = "some-hash",
Field = "Key2",
Value = "Value2"
});
database.Hash.Insert(new HashDto
{
Id = ObjectId.GenerateNewId(),
Key = "another-hash",
Field = "Key3",
Value = "Value3"
});
// Act
var result = connection.GetAllEntriesFromHash("some-hash");
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Count);
Assert.Equal("Value1", result["Key1"]);
Assert.Equal("Value2", result["Key2"]);
});
}
private void UseConnection(Action<HangfireDbContext, MongoConnection> action)
{
using (var database = ConnectionUtils.CreateConnection())
{
using (var connection = new MongoConnection(database, _providers))
{
action(database, connection);
}
}
}
public static void SampleMethod(string arg)
{
}
}
}
@@ -0,0 +1,119 @@
using System;
using System.Threading;
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.Tests.Utils;
using MongoDB.Driver.Builders;
using Xunit;
using Hangfire.Mongo.DistributedLock;
namespace Hangfire.Mongo.Tests
{
public class MongoDistributedLockFacts
{
[Fact]
public void Ctor_ThrowsAnException_WhenResourceIsNull()
{
UseConnection(database =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoDistributedLock(null, TimeSpan.FromSeconds(1), database, new MongoStorageOptions()));
Assert.Equal("resource", exception.ParamName);
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), null, new MongoStorageOptions()));
Assert.Equal("database", exception.ParamName);
}
[Fact, CleanDatabase]
public void Ctor_SetLock_WhenResourceIsNotLocked()
{
UseConnection(database =>
{
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
{
var locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
Assert.Equal(1, locksCount);
}
});
}
[Fact, CleanDatabase]
public void Ctor_SetReleaseLock_WhenResourceIsNotLocked()
{
UseConnection(database =>
{
long locksCount;
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
{
locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
Assert.Equal(1, locksCount);
}
locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
Assert.Equal(0, locksCount);
});
}
[Fact, CleanDatabase]
public void Ctor_ThrowsAnException_WhenResourceIsLocked()
{
UseConnection(database =>
{
using (MongoDistributedLock @lock1 = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
{
var locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
Assert.Equal(1, locksCount);
Assert.Throws<MongoDistributedLockException>(() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()));
}
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsIsNull()
{
UseConnection(database =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, null));
Assert.Equal("options", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void Ctor_SetLockHeartbeatWorks_WhenResourceIsNotLocked()
{
UseConnection(database =>
{
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions() { DistributedLockLifetime = TimeSpan.FromSeconds(10)}))
{
DateTime initialHeartBeat = database.GetServerTimeUtc();
Thread.Sleep(TimeSpan.FromSeconds(3));
Thread.Sleep(TimeSpan.FromSeconds(60));
DistributedLockDto lockEntry = database.DistributedLock.FindOne(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
Assert.NotNull(lockEntry);
Assert.True(lockEntry.Heartbeat > initialHeartBeat);
}
});
}
private static void UseConnection(Action<HangfireDbContext> action)
{
using (var connection = ConnectionUtils.CreateConnection())
{
action(connection);
}
}
}
}
@@ -0,0 +1,164 @@
using System;
using System.Data;
using System.Linq;
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.Tests.Utils;
using Moq;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoFetchedJobFacts
{
private const string JobId = "id";
private const string Queue = "queue";
[Fact]
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
{
UseConnection(connection =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoFetchedJob(null, 1, JobId, Queue));
Assert.Equal("connection", exception.ParamName);
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenJobIdIsNull()
{
UseConnection(connection =>
{
var exception = Assert.Throws<ArgumentNullException>(() => new MongoFetchedJob(connection, 1, null, Queue));
Assert.Equal("jobId", exception.ParamName);
});
}
[Fact]
public void Ctor_ThrowsAnException_WhenQueueIsNull()
{
UseConnection(connection =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoFetchedJob(connection, 1, JobId, null));
Assert.Equal("queue", exception.ParamName);
});
}
[Fact]
public void Ctor_CorrectlySets_AllInstanceProperties()
{
UseConnection(connection =>
{
var fetchedJob = new MongoFetchedJob(connection, 1, JobId, Queue);
Assert.Equal(1, fetchedJob.Id);
Assert.Equal(JobId, fetchedJob.JobId);
Assert.Equal(Queue, fetchedJob.Queue);
});
}
[Fact, CleanDatabase]
public void RemoveFromQueue_ReallyDeletesTheJobFromTheQueue()
{
UseConnection(connection =>
{
// Arrange
var id = CreateJobQueueRecord(connection, "1", "default");
var processingJob = new MongoFetchedJob(connection, id, "1", "default");
// Act
processingJob.RemoveFromQueue();
// Assert
var count = connection.JobQueue.Count();
Assert.Equal(0, count);
});
}
[Fact, CleanDatabase]
public void RemoveFromQueue_DoesNotDelete_UnrelatedJobs()
{
UseConnection(connection =>
{
// Arrange
CreateJobQueueRecord(connection, "1", "default");
CreateJobQueueRecord(connection, "1", "critical");
CreateJobQueueRecord(connection, "2", "default");
var fetchedJob = new MongoFetchedJob(connection, 999, "1", "default");
// Act
fetchedJob.RemoveFromQueue();
// Assert
var count = connection.JobQueue.Count();
Assert.Equal(3, count);
});
}
[Fact, CleanDatabase]
public void Requeue_SetsFetchedAtValueToNull()
{
UseConnection(connection =>
{
// Arrange
var id = CreateJobQueueRecord(connection, "1", "default");
var processingJob = new MongoFetchedJob(connection, id, "1", "default");
// Act
processingJob.Requeue();
// Assert
var record = connection.JobQueue.FindAll().Single();
Assert.Null(record.FetchedAt);
});
}
[Fact, CleanDatabase]
public void Dispose_SetsFetchedAtValueToNull_IfThereWereNoCallsToComplete()
{
UseConnection(sql =>
{
// Arrange
var id = CreateJobQueueRecord(sql, "1", "default");
var processingJob = new MongoFetchedJob(sql, id, "1", "default");
// Act
processingJob.Dispose();
// Assert
var record = sql.JobQueue.FindAll().Single();
Assert.Null(record.FetchedAt);
});
}
private static int CreateJobQueueRecord(HangfireDbContext connection, string jobId, string queue)
{
var jobQueue = new JobQueueDto
{
JobId = int.Parse(jobId),
Queue = queue,
FetchedAt = connection.GetServerTimeUtc()
};
connection.JobQueue.Insert(jobQueue);
return jobQueue.Id;
}
private static void UseConnection(Action<HangfireDbContext> action)
{
using (var connection = ConnectionUtils.CreateConnection())
{
action(connection);
}
}
}
}
@@ -0,0 +1,344 @@
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.PersistentJobQueue.Mongo;
using Hangfire.Mongo.Tests.Utils;
using MongoDB.Driver.Builders;
using System;
using System.Linq;
using System.Threading;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoJobQueueFacts
{
private static readonly string[] DefaultQueues = { "default" };
[Fact]
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoJobQueue(null, new MongoStorageOptions()));
Assert.Equal("connection", exception.ParamName);
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
{
UseConnection(connection =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => new MongoJobQueue(connection, null));
Assert.Equal("options", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsNull()
{
UseConnection(connection =>
{
var queue = CreateJobQueue(connection);
var exception = Assert.Throws<ArgumentNullException>(
() => queue.Dequeue(null, CreateTimingOutCancellationToken()));
Assert.Equal("queues", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty()
{
UseConnection(connection =>
{
var queue = CreateJobQueue(connection);
var exception = Assert.Throws<ArgumentException>(
() => queue.Dequeue(new string[0], CreateTimingOutCancellationToken()));
Assert.Equal("queues", exception.ParamName);
});
}
[Fact]
public void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning()
{
UseConnection(connection =>
{
var cts = new CancellationTokenSource();
cts.Cancel();
var queue = CreateJobQueue(connection);
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs()
{
UseConnection(connection =>
{
var cts = new CancellationTokenSource(200);
var queue = CreateJobQueue(connection);
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue()
{
// Arrange
UseConnection(connection =>
{
var jobQueue = new JobQueueDto
{
JobId = 1,
Queue = "default"
};
connection.JobQueue.Insert(jobQueue);
var id = jobQueue.Id;
var queue = CreateJobQueue(connection);
// Act
MongoFetchedJob payload = (MongoFetchedJob)queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
Assert.Equal(id, payload.Id);
Assert.Equal("1", payload.JobId);
Assert.Equal("default", payload.Queue);
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue()
{
// Arrange
UseConnection(connection =>
{
var job = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job);
var jobQueue = new JobQueueDto
{
JobId = job.Id,
Queue = "default"
};
connection.JobQueue.Insert(jobQueue);
var queue = CreateJobQueue(connection);
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
Assert.NotNull(payload);
var fetchedAt = connection.JobQueue.FindOne(Query<JobQueueDto>.EQ(_ => _.JobId, int.Parse(payload.JobId))).FetchedAt;
Assert.NotNull(fetchedAt);
Assert.True(fetchedAt > DateTime.UtcNow.AddMinutes(-1));
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue()
{
// Arrange
UseConnection(connection =>
{
var job = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job);
var jobQueue = new JobQueueDto
{
JobId = job.Id,
Queue = "default",
FetchedAt = connection.GetServerTimeUtc().AddDays(-1)
};
connection.JobQueue.Insert(jobQueue);
var queue = CreateJobQueue(connection);
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
Assert.NotEmpty(payload.JobId);
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob()
{
// Arrange
UseConnection(connection =>
{
var job1 = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job1);
var job2 = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job2);
connection.JobQueue.Insert(new JobQueueDto
{
JobId = job1.Id,
Queue = "default"
});
connection.JobQueue.Insert(new JobQueueDto
{
JobId = job2.Id,
Queue = "default"
});
var queue = CreateJobQueue(connection);
// Act
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
// Assert
var otherJobFetchedAt = connection.JobQueue.FindOne(Query<JobQueueDto>.NE(_ => _.JobId, int.Parse(payload.JobId))).FetchedAt;
Assert.Null(otherJobFetchedAt);
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues()
{
UseConnection(connection =>
{
var job1 = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job1);
connection.JobQueue.Insert(new JobQueueDto
{
JobId = job1.Id,
Queue = "critical"
});
var queue = CreateJobQueue(connection);
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken()));
});
}
[Fact, CleanDatabase]
public void Dequeue_ShouldFetchJobs_FromMultipleQueues()
{
UseConnection(connection =>
{
var job1 = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job1);
var job2 = new JobDto
{
InvocationData = "",
Arguments = "",
CreatedAt = connection.GetServerTimeUtc()
};
connection.Job.Insert(job2);
connection.JobQueue.Insert(new JobQueueDto
{
JobId = job1.Id,
Queue = "critical"
});
connection.JobQueue.Insert(new JobQueueDto
{
JobId = job2.Id,
Queue = "default"
});
var queue = CreateJobQueue(connection);
var critical = (MongoFetchedJob)queue.Dequeue(
new[] { "critical", "default" },
CreateTimingOutCancellationToken());
Assert.NotNull(critical.JobId);
Assert.Equal("critical", critical.Queue);
var @default = (MongoFetchedJob)queue.Dequeue(
new[] { "critical", "default" },
CreateTimingOutCancellationToken());
Assert.NotNull(@default.JobId);
Assert.Equal("default", @default.Queue);
});
}
[Fact, CleanDatabase]
public void Enqueue_AddsAJobToTheQueue()
{
UseConnection(connection =>
{
var queue = CreateJobQueue(connection);
queue.Enqueue("default", "1");
var record = connection.JobQueue.FindAll().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 MongoJobQueue CreateJobQueue(HangfireDbContext connection)
{
return new MongoJobQueue(connection, new MongoStorageOptions());
}
private static void UseConnection(Action<HangfireDbContext> action)
{
using (var connection = ConnectionUtils.CreateConnection())
{
action(connection);
}
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Hangfire.Mongo.Tests.Utils;
using Hangfire.Server;
using Hangfire.Storage;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoStorageFacts
{
[Fact]
public void Ctor_ThrowsAnException_WhenConnectionStringIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage(null, "database"));
Assert.Equal("connectionString", exception.ParamName);
}
[Fact]
public void Ctor_ThrowsAnException_WhenDatabaseNameIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage("localhost", null));
Assert.Equal("databaseName", exception.ParamName);
}
[Fact]
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage("localhost", "database", null));
Assert.Equal("options", exception.ParamName);
}
[Fact, CleanDatabase]
public void GetMonitoringApi_ReturnsNonNullInstance()
{
MongoStorage storage = CreateStorage();
IMonitoringApi api = storage.GetMonitoringApi();
Assert.NotNull(api);
}
[Fact, CleanDatabase]
public void GetConnection_ReturnsNonNullInstance()
{
MongoStorage storage = CreateStorage();
using (IStorageConnection connection = storage.GetConnection())
{
Assert.NotNull(connection);
}
}
[Fact]
public void GetComponents_ReturnsAllNeededComponents()
{
MongoStorage storage = CreateStorage();
IEnumerable<IServerComponent> components = storage.GetComponents();
Type[] componentTypes = components.Select(x => x.GetType()).ToArray();
Assert.Contains(typeof(ExpirationManager), componentTypes);
}
private static MongoStorage CreateStorage()
{
return new MongoStorage(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName());
}
}
}
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoStorageOptionsFacts
{
[Fact]
public void Ctor_SetsTheDefaultOptions()
{
MongoStorageOptions options = new MongoStorageOptions();
Assert.Equal("hangfire", options.Prefix);
Assert.True(options.InvisibilityTimeout > TimeSpan.Zero);
}
[Fact]
public void Ctor_SetsTheDefaultOptions_ShouldGenerateClientId()
{
var options = new MongoStorageOptions();
Assert.False(String.IsNullOrWhiteSpace(options.ClientId));
}
[Fact]
public void Ctor_SetsTheDefaultOptions_ShouldGenerateUniqueClientId()
{
var options1 = new MongoStorageOptions();
var options2 = new MongoStorageOptions();
var options3 = new MongoStorageOptions();
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 MongoStorageOptions();
Assert.Throws<ArgumentException>(
() => options.QueuePollInterval = TimeSpan.Zero);
}
[Fact]
public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsNegative()
{
var options = new MongoStorageOptions();
Assert.Throws<ArgumentException>(
() => options.QueuePollInterval = TimeSpan.FromSeconds(-1));
}
[Fact]
public void Set_QueuePollInterval_SetsTheValue()
{
var options = new MongoStorageOptions();
options.QueuePollInterval = TimeSpan.FromSeconds(1);
Assert.Equal(TimeSpan.FromSeconds(1), options.QueuePollInterval);
}
}
}
@@ -0,0 +1,735 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using Hangfire.Mongo.MongoUtils;
using Hangfire.Mongo.PersistentJobQueue;
using Hangfire.Mongo.Tests.Utils;
using Hangfire.States;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
using Moq;
using Xunit;
namespace Hangfire.Mongo.Tests
{
public class MongoWriteOnlyTransactionFacts
{
private readonly PersistentJobQueueProviderCollection _queueProviders;
public MongoWriteOnlyTransactionFacts()
{
Mock<IPersistentJobQueueProvider> defaultProvider = new Mock<IPersistentJobQueueProvider>();
defaultProvider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>()))
.Returns(new Mock<IPersistentJobQueue>().Object);
_queueProviders = new PersistentJobQueueProviderCollection(defaultProvider.Object);
}
[Fact]
public void Ctor_ThrowsAnException_IfConnectionIsNull()
{
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => new MongoWriteOnlyTransaction(null, _queueProviders));
Assert.Equal("connection", exception.ParamName);
}
[Fact, CleanDatabase]
public void Ctor_ThrowsAnException_IfProvidersCollectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(() => new MongoWriteOnlyTransaction(ConnectionUtils.CreateConnection(), null));
Assert.Equal("queueProviders", exception.ParamName);
}
[Fact, CleanDatabase]
public void ExpireJob_SetsJobExpirationData()
{
UseConnection(database =>
{
JobDto job = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(job);
JobDto anotherJob = new JobDto
{
Id = 2,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(anotherJob);
var jobId = job.Id;
var anotherJobId = anotherJob.Id;
Commit(database, x => x.ExpireJob(jobId.ToString(), TimeSpan.FromDays(1)));
var testJob = GetTestJob(database, jobId);
Assert.True(database.GetServerTimeUtc().AddMinutes(-1) < testJob.ExpireAt && testJob.ExpireAt <= database.GetServerTimeUtc().AddDays(1));
var anotherTestJob = GetTestJob(database, anotherJobId);
Assert.Null(anotherTestJob.ExpireAt);
});
}
[Fact, CleanDatabase]
public void PersistJob_ClearsTheJobExpirationData()
{
UseConnection(database =>
{
JobDto job = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc(),
ExpireAt = database.GetServerTimeUtc()
};
database.Job.Insert(job);
JobDto anotherJob = new JobDto
{
Id = 2,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc(),
ExpireAt = database.GetServerTimeUtc()
};
database.Job.Insert(anotherJob);
var jobId = job.Id;
var anotherJobId = anotherJob.Id;
Commit(database, x => x.PersistJob(jobId.ToString()));
var testjob = GetTestJob(database, jobId);
Assert.Null(testjob.ExpireAt);
var anotherTestJob = GetTestJob(database, anotherJobId);
Assert.NotNull(anotherTestJob.ExpireAt);
});
}
[Fact, CleanDatabase]
public void SetJobState_AppendsAStateAndSetItToTheJob()
{
UseConnection(database =>
{
JobDto job = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(job);
JobDto anotherJob = new JobDto
{
Id = 2,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(anotherJob);
var jobId = job.Id;
var anotherJobId = anotherJob.Id;
var state = new Mock<IState>();
state.Setup(x => x.Name).Returns("State");
state.Setup(x => x.Reason).Returns("Reason");
state.Setup(x => x.SerializeData())
.Returns(new Dictionary<string, string> { { "Name", "Value" } });
Commit(database, x => x.SetJobState(jobId.ToString(), state.Object));
var testJob = GetTestJob(database, jobId);
Assert.Equal("State", testJob.StateName);
Assert.NotNull(testJob.StateId);
var anotherTestJob = GetTestJob(database, anotherJobId);
Assert.Null(anotherTestJob.StateName);
Assert.Equal(ObjectId.Empty, anotherTestJob.StateId);
StateDto jobState = database.State.FindAll().Single();
Assert.Equal(jobId, jobState.JobId);
Assert.Equal("State", jobState.Name);
Assert.Equal("Reason", jobState.Reason);
Assert.NotNull(jobState.CreatedAt);
Assert.Equal("{\"Name\":\"Value\"}", jobState.Data);
});
}
[Fact, CleanDatabase]
public void AddJobState_JustAddsANewRecordInATable()
{
UseConnection(database =>
{
JobDto job = new JobDto
{
Id = 1,
InvocationData = "",
Arguments = "",
CreatedAt = database.GetServerTimeUtc()
};
database.Job.Insert(job);
var jobId = job.Id;
var state = new Mock<IState>();
state.Setup(x => x.Name).Returns("State");
state.Setup(x => x.Reason).Returns("Reason");
state.Setup(x => x.SerializeData())
.Returns(new Dictionary<string, string> { { "Name", "Value" } });
Commit(database, x => x.AddJobState(jobId.ToString(), state.Object));
var testJob = GetTestJob(database, jobId);
Assert.Null(testJob.StateName);
Assert.Equal(ObjectId.Empty, testJob.StateId);
StateDto jobState = database.State.FindAll().Single();
Assert.Equal(jobId, jobState.JobId);
Assert.Equal("State", jobState.Name);
Assert.Equal("Reason", jobState.Reason);
Assert.NotNull(jobState.CreatedAt);
Assert.Equal("{\"Name\":\"Value\"}", jobState.Data);
});
}
[Fact, CleanDatabase]
public void AddToQueue_CallsEnqueue_OnTargetPersistentQueue()
{
UseConnection(database =>
{
var correctJobQueue = new Mock<IPersistentJobQueue>();
var correctProvider = new Mock<IPersistentJobQueueProvider>();
correctProvider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>()))
.Returns(correctJobQueue.Object);
_queueProviders.Add(correctProvider.Object, new[] { "default" });
Commit(database, x => x.AddToQueue("default", "1"));
correctJobQueue.Verify(x => x.Enqueue("default", "1"));
});
}
[Fact, CleanDatabase]
public void IncrementCounter_AddsRecordToCounterTable_WithPositiveValue()
{
UseConnection(database =>
{
Commit(database, x => x.IncrementCounter("my-key"));
CounterDto record = database.Counter.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal(1, record.Value);
Assert.Equal(null, record.ExpireAt);
});
}
[Fact, CleanDatabase]
public void IncrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet()
{
UseConnection(database =>
{
Commit(database, x => x.IncrementCounter("my-key", TimeSpan.FromDays(1)));
CounterDto record = database.Counter.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal(1, record.Value);
Assert.NotNull(record.ExpireAt);
var expireAt = (DateTime)record.ExpireAt;
Assert.True(database.GetServerTimeUtc().AddHours(23) < expireAt);
Assert.True(expireAt < database.GetServerTimeUtc().AddHours(25));
});
}
[Fact, CleanDatabase]
public void IncrementCounter_WithExistingKey_AddsAnotherRecord()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.IncrementCounter("my-key");
x.IncrementCounter("my-key");
});
var recordCount = database.Counter.Count();
Assert.Equal(2, recordCount);
});
}
[Fact, CleanDatabase]
public void DecrementCounter_AddsRecordToCounterTable_WithNegativeValue()
{
UseConnection(database =>
{
Commit(database, x => x.DecrementCounter("my-key"));
CounterDto record = database.Counter.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal(-1, record.Value);
Assert.Equal(null, record.ExpireAt);
});
}
[Fact, CleanDatabase]
public void DecrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet()
{
UseConnection(database =>
{
Commit(database, x => x.DecrementCounter("my-key", TimeSpan.FromDays(1)));
CounterDto record = database.Counter.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal(-1, record.Value);
Assert.NotNull(record.ExpireAt);
var expireAt = (DateTime)record.ExpireAt;
Assert.True(database.GetServerTimeUtc().AddHours(23) < expireAt);
Assert.True(expireAt < database.GetServerTimeUtc().AddHours(25));
});
}
[Fact, CleanDatabase]
public void DecrementCounter_WithExistingKey_AddsAnotherRecord()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.DecrementCounter("my-key");
x.DecrementCounter("my-key");
});
var recordCount = database.Counter.Count();
Assert.Equal(2, recordCount);
});
}
[Fact, CleanDatabase]
public void AddToSet_AddsARecord_IfThereIsNo_SuchKeyAndValue()
{
UseConnection(database =>
{
Commit(database, x => x.AddToSet("my-key", "my-value"));
var record = database.Set.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal("my-value", record.Value);
Assert.Equal(0.0, record.Score, 2);
});
}
[Fact, CleanDatabase]
public void AddToSet_AddsARecord_WhenKeyIsExists_ButValuesAreDifferent()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.AddToSet("my-key", "another-value");
});
var recordCount = database.Set.Count();
Assert.Equal(2, recordCount);
});
}
[Fact, CleanDatabase]
public void AddToSet_DoesNotAddARecord_WhenBothKeyAndValueAreExist()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.AddToSet("my-key", "my-value");
});
var recordCount = database.Set.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void AddToSet_WithScore_AddsARecordWithScore_WhenBothKeyAndValueAreNotExist()
{
UseConnection(database =>
{
Commit(database, x => x.AddToSet("my-key", "my-value", 3.2));
var record = database.Set.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal("my-value", record.Value);
Assert.Equal(3.2, record.Score, 3);
});
}
[Fact, CleanDatabase]
public void AddToSet_WithScore_UpdatesAScore_WhenBothKeyAndValueAreExist()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.AddToSet("my-key", "my-value", 3.2);
});
var record = database.Set.FindAll().Single();
Assert.Equal(3.2, record.Score, 3);
});
}
[Fact, CleanDatabase]
public void RemoveFromSet_RemovesARecord_WithGivenKeyAndValue()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.RemoveFromSet("my-key", "my-value");
});
var recordCount = database.Set.Count();
Assert.Equal(0, recordCount);
});
}
[Fact, CleanDatabase]
public void RemoveFromSet_DoesNotRemoveRecord_WithSameKey_AndDifferentValue()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.RemoveFromSet("my-key", "different-value");
});
var recordCount = database.Set.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void RemoveFromSet_DoesNotRemoveRecord_WithSameValue_AndDifferentKey()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.AddToSet("my-key", "my-value");
x.RemoveFromSet("different-key", "my-value");
});
var recordCount = database.Set.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void InsertToList_AddsARecord_WithGivenValues()
{
UseConnection(database =>
{
Commit(database, x => x.InsertToList("my-key", "my-value"));
var record = database.List.FindAll().Single();
Assert.Equal("my-key", record.Key);
Assert.Equal("my-value", record.Value);
});
}
[Fact, CleanDatabase]
public void InsertToList_AddsAnotherRecord_WhenBothKeyAndValueAreExist()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "my-value");
x.InsertToList("my-key", "my-value");
});
var recordCount = database.List.Count();
Assert.Equal(2, recordCount);
});
}
[Fact, CleanDatabase]
public void RemoveFromList_RemovesAllRecords_WithGivenKeyAndValue()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "my-value");
x.InsertToList("my-key", "my-value");
x.RemoveFromList("my-key", "my-value");
});
var recordCount = database.List.Count();
Assert.Equal(0, recordCount);
});
}
[Fact, CleanDatabase]
public void RemoveFromList_DoesNotRemoveRecords_WithSameKey_ButDifferentValue()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "my-value");
x.RemoveFromList("my-key", "different-value");
});
var recordCount = database.List.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void RemoveFromList_DoesNotRemoveRecords_WithSameValue_ButDifferentKey()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "my-value");
x.RemoveFromList("different-key", "my-value");
});
var recordCount = database.List.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void TrimList_TrimsAList_ToASpecifiedRange()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "0");
x.InsertToList("my-key", "1");
x.InsertToList("my-key", "2");
x.InsertToList("my-key", "3");
x.TrimList("my-key", 1, 2);
});
var records = database.List.FindAll().ToArray();
Assert.Equal(2, records.Length);
Assert.Equal("1", records[0].Value);
Assert.Equal("2", records[1].Value);
});
}
[Fact, CleanDatabase]
public void TrimList_RemovesRecordsToEnd_IfKeepAndingAt_GreaterThanMaxElementIndex()
{
UseConnection(sql =>
{
Commit(sql, x =>
{
x.InsertToList("my-key", "0");
x.InsertToList("my-key", "1");
x.InsertToList("my-key", "2");
x.TrimList("my-key", 1, 100);
});
var recordCount = sql.List.Count();
Assert.Equal(2, recordCount);
});
}
[Fact, CleanDatabase]
public void TrimList_RemovesAllRecords_WhenStartingFromValue_GreaterThanMaxElementIndex()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "0");
x.TrimList("my-key", 1, 100);
});
var recordCount = database.List.Count();
Assert.Equal(0, recordCount);
});
}
[Fact, CleanDatabase]
public void TrimList_RemovesAllRecords_IfStartFromGreaterThanEndingAt()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "0");
x.TrimList("my-key", 1, 0);
});
var recordCount = database.List.Count();
Assert.Equal(0, recordCount);
});
}
[Fact, CleanDatabase]
public void TrimList_RemovesRecords_OnlyOfAGivenKey()
{
UseConnection(database =>
{
Commit(database, x =>
{
x.InsertToList("my-key", "0");
x.TrimList("another-key", 1, 0);
});
var recordCount = database.List.Count();
Assert.Equal(1, recordCount);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull()
{
UseConnection(database =>
{
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(
() => Commit(database, x => x.SetRangeInHash(null, new Dictionary<string, string>())));
Assert.Equal("key", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull()
{
UseConnection(database =>
{
var exception = Assert.Throws<ArgumentNullException>(
() => Commit(database, x => x.SetRangeInHash("some-hash", null)));
Assert.Equal("keyValuePairs", exception.ParamName);
});
}
[Fact, CleanDatabase]
public void SetRangeInHash_MergesAllRecords()
{
UseConnection(database =>
{
Commit(database, x => x.SetRangeInHash("some-hash", new Dictionary<string, string>
{
{ "Key1", "Value1" },
{ "Key2", "Value2" }
}));
var result = database.Hash.Find(Query<HashDto>.EQ(_ => _.Key, "some-hash"))
.ToDictionary(x => x.Field, x => x.Value);
Assert.Equal("Value1", result["Key1"]);
Assert.Equal("Value2", result["Key2"]);
});
}
[Fact, CleanDatabase]
public void RemoveHash_ThrowsAnException_WhenKeyIsNull()
{
UseConnection(database =>
{
Assert.Throws<ArgumentNullException>(
() => Commit(database, x => x.RemoveHash(null)));
});
}
[Fact, CleanDatabase]
public void RemoveHash_RemovesAllHashRecords()
{
UseConnection(database =>
{
// Arrange
Commit(database, x => x.SetRangeInHash("some-hash", new Dictionary<string, string>
{
{ "Key1", "Value1" },
{ "Key2", "Value2" }
}));
// Act
Commit(database, x => x.RemoveHash("some-hash"));
// Assert
var count = database.Hash.Count();
Assert.Equal(0, count);
});
}
private static dynamic GetTestJob(HangfireDbContext database, int jobId)
{
return database.Job.FindOneById(jobId);
}
private void UseConnection(Action<HangfireDbContext> action)
{
using (HangfireDbContext connection = ConnectionUtils.CreateConnection())
{
action(connection);
}
}
private void Commit(HangfireDbContext connection, Action<MongoWriteOnlyTransaction> action)
{
using (MongoWriteOnlyTransaction transaction = new MongoWriteOnlyTransaction(connection, _queueProviders))
{
action(transaction);
transaction.Commit();
}
}
}
}
@@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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: AssemblyTitle("Hangfire.Mongo.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Hangfire.Mongo.Tests")]
[assembly: AssemblyCopyright("Copyright © 2014 Sergey Zwezdin")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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("c9ee6131-b199-40d2-ba5d-bb51da90ba08")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,67 @@
using System;
using System.Data.SqlClient;
using System.Reflection;
using System.Threading;
using Hangfire.Mongo.Database;
using Hangfire.Mongo.Dto;
using MongoDB.Driver;
using Xunit;
namespace Hangfire.Mongo.Tests.Utils
{
public class CleanDatabaseAttribute : BeforeAfterTestAttribute
{
private static readonly object GlobalLock = new object();
private static bool _sqlObjectInstalled;
public CleanDatabaseAttribute()
{
}
public override void Before(MethodInfo methodUnderTest)
{
Monitor.Enter(GlobalLock);
if (_sqlObjectInstalled == false)
{
RecreateDatabaseAndInstallObjects();
_sqlObjectInstalled = true;
}
}
public override void After(MethodInfo methodUnderTest)
{
Monitor.Exit(GlobalLock);
}
private static void RecreateDatabaseAndInstallObjects()
{
using (HangfireDbContext context = new HangfireDbContext(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName()))
{
context.Init();
MongoCollection[] collections =
{
context.DistributedLock,
context.Counter,
context.Hash,
context.Job,
context.JobParameter,
context.JobQueue,
context.List,
context.Server,
context.Set,
context.State
};
foreach (MongoCollection collection in collections)
{
WriteConcernResult result = collection.RemoveAll();
if (result.Ok == false)
throw new InvalidOperationException("Unable to cleanup database.");
}
}
}
}
}
@@ -0,0 +1,35 @@
using System;
using Hangfire.Mongo.Database;
namespace Hangfire.Mongo.Tests.Utils
{
public class ConnectionUtils
{
private const string DatabaseVariable = "Hangfire_Mongo_DatabaseName";
private const string ConnectionStringTemplateVariable = "Hangfire_Mongo_ConnectionStringTemplate";
private const string DefaultDatabaseName = @"Hangfire-Mongo-Tests";
private const string DefaultConnectionStringTemplate = @"mongodb://localhost";
public static string GetDatabaseName()
{
return Environment.GetEnvironmentVariable(DatabaseVariable) ?? DefaultDatabaseName;
}
public static string GetConnectionString()
{
return String.Format(GetConnectionStringTemplate(), GetDatabaseName());
}
private static string GetConnectionStringTemplate()
{
return Environment.GetEnvironmentVariable(ConnectionStringTemplateVariable) ?? DefaultConnectionStringTemplate;
}
public static HangfireDbContext CreateConnection()
{
HangfireDbContext connection = new HangfireDbContext(GetConnectionString(), GetDatabaseName());
return connection;
}
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Hangfire.Core" version="1.3.0" targetFramework="net451" />
<package id="mongocsharpdriver" version="1.9.2" targetFramework="net451" />
<package id="Moq" version="4.2.1409.1722" targetFramework="net451" />
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net451" />
<package id="Owin" version="1.0" targetFramework="net451" />
<package id="xunit" version="1.9.2" targetFramework="net451" />
</packages>
@@ -0,0 +1,47 @@
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using Dapper;
using Xunit;
namespace Hangfire.SqlServer.Tests
{
public class CountersAggregatorFacts
{
[Fact, CleanDatabase]
public void CountersAggregatorExecutesProperly()
{
const string createSql = @"
insert into HangFire.Counter ([Key], [Value], ExpireAt)
values ('key', 1, @expireAt)";
using (var connection = CreateConnection())
{
// Arrange
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddHours(1) });
var aggregator = CreateAggregator(connection);
var cts = new CancellationTokenSource();
cts.Cancel();
// Act
aggregator.Execute(cts.Token);
// Assert
Assert.Equal(1, connection.Query<int>(@"select count(*) from HangFire.AggregatedCounter").Single());
}
}
private static SqlConnection CreateConnection()
{
return ConnectionUtils.CreateConnection();
}
private static CountersAggregator CreateAggregator(SqlConnection connection)
{
var storage = new SqlServerStorage(connection);
return new CountersAggregator(storage, TimeSpan.Zero);
}
}
}
@@ -0,0 +1,204 @@
using System;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using Dapper;
using Xunit;
namespace Hangfire.SqlServer.Tests
{
public class ExpirationManagerFacts
{
private readonly CancellationToken _token;
public ExpirationManagerFacts()
{
var cts = new CancellationTokenSource();
_token = cts.Token;
}
[Fact]
public void Ctor_ThrowsAnException_WhenStorageIsNull()
{
Assert.Throws<ArgumentNullException>(() => new ExpirationManager(null));
}
[Fact, CleanDatabase]
public void Execute_RemovesOutdatedRecords()
{
using (var connection = CreateConnection())
{
var entryId = CreateExpirationEntry(connection, DateTime.UtcNow.AddMonths(-1));
var manager = CreateManager(connection);
manager.Execute(_token);
Assert.True(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
{
using (var connection = CreateConnection())
{
var entryId = CreateExpirationEntry(connection, null);
var manager = CreateManager(connection);
manager.Execute(_token);
Assert.False(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
{
using (var connection = CreateConnection())
{
var entryId = CreateExpirationEntry(connection, DateTime.Now.AddMonths(1));
var manager = CreateManager(connection);
manager.Execute(_token);
Assert.False(IsEntryExpired(connection, entryId));
}
}
[Fact, CleanDatabase]
public void Execute_Processes_AggregatedCounterTable()
{
using (var connection = CreateConnection())
{
// Arrange
const string createSql = @"
insert into HangFire.AggregatedCounter ([Key], [Value], ExpireAt)
values ('key', 1, @expireAt)";
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
var manager = CreateManager(connection);
// Act
manager.Execute(_token);
// Assert
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Counter").Single());
}
}
[Fact, CleanDatabase]
public void Execute_Processes_JobTable()
{
using (var connection = CreateConnection())
{
// Arrange
const string createSql = @"
insert into HangFire.Job (InvocationData, Arguments, CreatedAt, ExpireAt)
values ('', '', getutcdate(), @expireAt)";
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
var manager = CreateManager(connection);
// Act
manager.Execute(_token);
// Assert
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Job").Single());
}
}
[Fact, CleanDatabase]
public void Execute_Processes_ListTable()
{
using (var connection = CreateConnection())
{
// Arrange
const string createSql = @"
insert into HangFire.List ([Key], ExpireAt)
values ('key', @expireAt)";
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
var manager = CreateManager(connection);
// Act
manager.Execute(_token);
// Assert
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.List").Single());
}
}
[Fact, CleanDatabase]
public void Execute_Processes_SetTable()
{
using (var connection = CreateConnection())
{
// Arrange
const string createSql = @"
insert into HangFire.[Set] ([Key], [Score], [Value], ExpireAt)
values ('key', 0, '', @expireAt)";
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
var manager = CreateManager(connection);
// Act
manager.Execute(_token);
// Assert
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.[Set]").Single());
}
}
[Fact, CleanDatabase]
public void Execute_Processes_HashTable()
{
using (var connection = CreateConnection())
{
// Arrange
const string createSql = @"
insert into HangFire.Hash ([Key], [Field], [Value], ExpireAt)
values ('key', 'field', '', @expireAt)";
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
var manager = CreateManager(connection);
// Act
manager.Execute(_token);
// Assert
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Hash").Single());
}
}
private static int CreateExpirationEntry(SqlConnection connection, DateTime? expireAt)
{
const string insertSql = @"
insert into HangFire.AggregatedCounter ([Key], [Value], [ExpireAt])
values ('key', 1, @expireAt)
select scope_identity() as Id";
var id = connection.Query(insertSql, new { @expireAt = expireAt }).Single();
var recordId = (int) id.Id;
return recordId;
}
private static bool IsEntryExpired(SqlConnection connection, int entryId)
{
var count = connection.Query<int>(
"select count(*) from HangFire.AggregatedCounter where Id = @id", new { id = entryId }).Single();
return count == 0;
}
private SqlConnection CreateConnection()
{
return ConnectionUtils.CreateConnection();
}
private ExpirationManager CreateManager(SqlConnection connection)
{
var storage = new SqlServerStorage(connection);
return new ExpirationManager(storage, TimeSpan.Zero);
}
}
}

Some files were not shown because too many files have changed in this diff Show More