Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add suspend and resume functionality #1673

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
eea6db1
Allow to pass custom data to ApplyStateContext instances
odinserj Apr 7, 2020
d2be67e
Add missing null check to the ApplyStateContext class
odinserj Apr 7, 2020
b49eec5
Add CustomData property to the ElectStateContext class
odinserj Apr 7, 2020
6faf8de
Ensure custom data is preserved between apply/elect context conversions
odinserj Apr 7, 2020
4b073a9
Add missing unit tests for the StateChangeContext class
odinserj Apr 7, 2020
58b17e8
Add custom data property to the StateChangeContext class
odinserj Apr 7, 2020
21451ef
Pass custom data between contexts in state changer
odinserj Apr 7, 2020
4de10a6
Pass custom data between contexts in workers
odinserj Apr 7, 2020
5c28ac0
Update Hangfire.sln.DotSettings
odinserj Apr 8, 2020
0b0ec7f
Make context mock classes public
odinserj Apr 8, 2020
87094a2
Create Hangfire.Core.Tests.csproj.DotSettings
odinserj Apr 8, 2020
6848c91
Use new lang features for recently touched context classes
odinserj Apr 8, 2020
03a7f5b
Add suspend and resume functionality
May 15, 2020
fc8d879
Fix debug code
May 15, 2020
312e404
Merge branch 'master' into dev
williambuchanan2 May 15, 2020
be14ca2
Fix minor bug with string format
May 18, 2020
8243051
Merge branch 'dev' of https://github.com/williambuchanan2/Hangfire in…
May 18, 2020
a10d0bc
Merge branch 'master' into dev
odinserj Oct 12, 2020
cedc8b3
Update StateChangeContextFacts.cs
odinserj Oct 12, 2020
48bf829
Merge branch 'master' into dev
odinserj Jan 20, 2021
e56c88f
Ignore some members when serializing JobFilterAttribute to decrease size
odinserj Jan 20, 2021
9d691b4
Fetch "Retries" metric with other statistics when supported by storage
odinserj Jan 20, 2021
fc67870
Display deleted jobs in the realtime graph
odinserj Jan 20, 2021
05375b0
Merge branch 'dev' into dev
williambuchanan2 Feb 26, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Hangfire.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@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>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hangfire/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hangfire/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Odinokov/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sergey/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>
6 changes: 3 additions & 3 deletions src/Hangfire.AspNetCore/Hangfire.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net451;net461;netstandard1.3;netstandard2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>net48;net451;net461;netstandard1.3;netstandard2.0;netcoreapp3.0</TargetFrameworks>
<DebugType>portable</DebugType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<RootNamespace>Hangfire</RootNamespace>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net45' or '$(TargetFramework)'=='net461'">
<PropertyGroup Condition="'$(TargetFramework)'=='net45' or '$(TargetFramework)'=='net461' or '$(TargetFramework)'=='net48'">
<DebugType>full</DebugType>
</PropertyGroup>

Expand All @@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netstandard2.0'">
<ItemGroup Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net48'">
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.0.0" />
Expand Down
10 changes: 10 additions & 0 deletions src/Hangfire.Core/Common/JobFilterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;

namespace Hangfire.Common
{
Expand All @@ -30,8 +32,11 @@ public abstract class JobFilterAttribute : Attribute, IJobFilter
private static readonly ConcurrentDictionary<Type, bool> MultiuseAttributeCache = new ConcurrentDictionary<Type, bool>();
private int _order = JobFilter.DefaultOrder;

[JsonIgnore]
public bool AllowMultiple => AllowsMultiple(GetType());

[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
[DefaultValue(JobFilter.DefaultOrder)]
public int Order
{
get { return _order; }
Expand All @@ -45,6 +50,11 @@ public int Order
}
}

#if !NETSTANDARD1_3
[JsonIgnore]
public override object TypeId => base.TypeId;
#endif

private static bool AllowsMultiple(Type attributeType)
{
return MultiuseAttributeCache.GetOrAdd(
Expand Down
18 changes: 13 additions & 5 deletions src/Hangfire.Core/Dashboard/Content/js/hangfire.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@
})();

hangfire.RealtimeGraph = (function() {
function RealtimeGraph(element, succeeded, failed, succeededStr, failedStr, pollInterval) {
function RealtimeGraph(element, succeeded, failed, deleted, succeededStr, failedStr, deletedStr, pollInterval) {
this._succeeded = succeeded;
this._failed = failed;
this._deleted = deleted;
this._last = Date.now();
this._pollInterval = pollInterval;

Expand All @@ -72,7 +73,8 @@
data: {
datasets: [
{ label: succeededStr, borderColor: '#62B35F', backgroundColor: '#6FCD6D' },
{ label: failedStr, borderColor: '#BB4847', backgroundColor: '#D55251' }
{ label: failedStr, borderColor: '#BB4847', backgroundColor: '#D55251' },
{ label: deletedStr, borderColor: '#777777', backgroundColor: '#919191' }
]
},
options: {
Expand All @@ -98,20 +100,24 @@
RealtimeGraph.prototype.appendHistory = function (statistics) {
var newSucceeded = parseInt(statistics["succeeded:count"].intValue);
var newFailed = parseInt(statistics["failed:count"].intValue);
var newDeleted = parseInt(statistics["deleted:count"].intValue);
var now = Date.now();

if (this._succeeded !== null && this._failed !== null && (now - this._last < this._pollInterval * 2)) {
var succeeded = Math.max(newSucceeded - this._succeeded, 0);
var failed = Math.max(newFailed - this._failed, 0);
var deleted = Math.max(newDeleted - this._deleted, 0);

this._chart.data.datasets[0].data.push({ x: new Date(), y: succeeded });
this._chart.data.datasets[1].data.push({ x: new Date(), y: failed });
this._chart.data.datasets[0].data.push({ x: now, y: succeeded });
this._chart.data.datasets[1].data.push({ x: now, y: failed });
this._chart.data.datasets[2].data.push({ x: now, y: deleted });

this._chart.update();
}

this._succeeded = newSucceeded;
this._failed = newFailed;
this._deleted = newDeleted;
this._last = now;
};

Expand Down Expand Up @@ -232,10 +238,12 @@
if (realtimeElement) {
var succeeded = parseInt($(realtimeElement).data('succeeded'));
var failed = parseInt($(realtimeElement).data('failed'));
var deleted = parseInt($(realtimeElement).data('deleted'));

var succeededStr = $(realtimeElement).data('succeeded-string');
var failedStr = $(realtimeElement).data('failed-string');
var realtimeGraph = new Hangfire.RealtimeGraph(realtimeElement, succeeded, failed, succeededStr, failedStr, pollInterval);
var deletedStr = $(realtimeElement).data('deleted-string');
var realtimeGraph = new Hangfire.RealtimeGraph(realtimeElement, succeeded, failed, deleted, succeededStr, failedStr, deletedStr, pollInterval);

this._poller.addListener(function (data) {
realtimeGraph.appendHistory(data);
Expand Down
58 changes: 55 additions & 3 deletions src/Hangfire.Core/Dashboard/Content/resx/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/Hangfire.Core/Dashboard/Content/resx/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,33 @@
<data name="Common_Error" xml:space="preserve">
<value>Error</value>
</data>

<data name="JobsSidebarMenu_Suspended" xml:space="preserve">
<value>Suspended</value>
</data>
<data name="Metrics_SuspendedCount" xml:space="preserve">
<value>Suspended</value>
</data>
<data name="SuspendedPage_Warning_Html" xml:space="preserve">
<value>&lt;h4&gt;This page can't be displayed&lt;/h4&gt;
&lt;p&gt;
Don't worry, suspend is working as expected. Your current job storage does not support
some queries required to show this page. Please try to update your storage or wait until
the full command set is implemented.
&lt;/p&gt;
&lt;p&gt;
Please go to the &lt;a href="{0}"&gt;Scheduled jobs&lt;/a&gt; page to see all the
scheduled jobs including retries.
&lt;/p&gt;</value>
</data>
<data name="SuspendedJobsPage_NoJobs" xml:space="preserve">
<value>All is OK – you have no suspended.</value>
</data>
<data name="SuspendedJobsPage_Title" xml:space="preserve">
<value>Suspended Jobs</value>
</data>
<data name="JobDetailsPage_Parameters" xml:space="preserve">
<value>Parameters</value>

</data>
</root>
43 changes: 36 additions & 7 deletions src/Hangfire.Core/Dashboard/DashboardMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static IEnumerable<DashboardMetric> GetMetrics()
}

public static readonly DashboardMetric ServerCount = new DashboardMetric(
"servers:count",
"servers:count",
"Metrics_Servers",
page => new Metric(page.Statistics.Servers)
{
Expand All @@ -84,15 +84,23 @@ public static IEnumerable<DashboardMetric> GetMetrics()
page =>
{
long retryCount;
using (var connection = page.Storage.GetConnection())

if (page.Statistics.Retries.HasValue)
{
var storageConnection = connection as JobStorageConnection;
if (storageConnection == null)
retryCount = page.Statistics.Retries.Value;
}
else
{
using (var connection = page.Storage.GetConnection())
{
return null;
}
var storageConnection = connection as JobStorageConnection;
if (storageConnection == null)
{
return null;
}

retryCount = storageConnection.GetSetCount("retries");
retryCount = storageConnection.GetSetCount("retries");
}
}

return new Metric(retryCount)
Expand Down Expand Up @@ -190,5 +198,26 @@ public static IEnumerable<DashboardMetric> GetMetrics()
Style = awaitingCount > 0 ? MetricStyle.Info : MetricStyle.Default
};
});
public static readonly DashboardMetric SuspendedCount = new DashboardMetric(
"suspended:count",
"Metrics_SuspendedCount",
page =>
{
long suspendedCount = -1;

using (var connection = page.Storage.GetConnection())
{
var storageConnection = connection as JobStorageConnection;
if (storageConnection != null)
{
suspendedCount = storageConnection.GetSetCount("paused-jobs");
}
}

return new Metric(suspendedCount)
{
Style = suspendedCount > 0 ? MetricStyle.Info : MetricStyle.Default
};
});
}
}
10 changes: 10 additions & 0 deletions src/Hangfire.Core/Dashboard/DashboardRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ static DashboardRoutes()
Routes.AddRazorPage("/servers", x => new ServersPage());
Routes.AddRazorPage("/retries", x => new RetriesPage());

Routes.AddRazorPage("/jobs/suspended", x => new SuspendedJobsPage());
Routes.AddRecurringBatchCommand(
"/recurring/suspend",
(manager, jobId) => manager.SuspendJob(jobId));

Routes.AddRecurringBatchCommand(
"/recurring/resume",
(manager, jobId) => manager.ResumeJob(jobId));


#endregion
}

Expand Down
6 changes: 6 additions & 0 deletions src/Hangfire.Core/Dashboard/JobsSidebarMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ static JobsSidebarMenu()
Active = page.RequestPath.StartsWith("/jobs/awaiting"),
Metric = DashboardMetrics.AwaitingCount
});
Items.Add(page => new MenuItem(Strings.JobsSidebarMenu_Suspended, page.Url.To("/jobs/suspended"))
{
Active = page.RequestPath.StartsWith("/jobs/suspended"),
Metric = DashboardMetrics.SuspendedCount
});

}
}
}
6 changes: 4 additions & 2 deletions src/Hangfire.Core/Dashboard/Pages/HomePage.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
</div>
}
<h3>@Strings.HomePage_RealtimeGraph</h3>
<canvas width="1140" height="250" id="realtimeGraph" data-succeeded="@Statistics.Succeeded" data-failed="@Statistics.Failed"
<canvas width="1140" height="250" id="realtimeGraph" data-succeeded="@Statistics.Succeeded" data-failed="@Statistics.Failed" data-deleted="@Statistics.Deleted"
data-succeeded-string="@Strings.HomePage_GraphHover_Succeeded"
data-failed-string="@Strings.HomePage_GraphHover_Failed"></canvas>
data-failed-string="@Strings.HomePage_GraphHover_Failed"
data-deleted-string="@Strings.JobsSidebarMenu_Deleted"></canvas>
<div style="display: none;">
<span data-metric="succeeded:count"></span>
<span data-metric="failed:count"></span>
<span data-metric="deleted:count"></span>
</div>

<h3>
Expand Down
Loading