Skip to content

Commit 7ab9828

Browse files
committed
Add job duration. Add job update only method.
1 parent 1251601 commit 7ab9828

File tree

5 files changed

+88
-27
lines changed

5 files changed

+88
-27
lines changed

samples/Foundatio.AppHost/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
.WithUrls(u =>
2222
{
2323
u.Urls.Clear();
24-
u.Urls.Add(new ResourceUrlAnnotation { Url = "/jobstatus", DisplayText = "Job Status", Endpoint = u.GetEndpoint("http") });
25-
u.Urls.Add(new ResourceUrlAnnotation { Url = "/runjob", DisplayText = "Run Job", Endpoint = u.GetEndpoint("http") });
24+
u.Urls.Add(new ResourceUrlAnnotation { Url = "/jobs/status", DisplayText = "Job Status", Endpoint = u.GetEndpoint("http") });
25+
u.Urls.Add(new ResourceUrlAnnotation { Url = "/jobs/run", DisplayText = "Run Job", Endpoint = u.GetEndpoint("http") });
2626
});
2727

2828
builder.Build().Run();

samples/Foundatio.HostingSample/Program.cs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -109,31 +109,47 @@
109109

110110
app.MapGet("/", () => "Foundatio!");
111111

112-
app.MapGet("/jobstatus", httpContext =>
113-
{
114-
var jobManager = httpContext.RequestServices.GetRequiredService<JobManager>();
115-
var status = jobManager.GetJobStatus();
112+
app.MapGet("/jobs/status", (IJobManager jobManager, HttpRequest req) =>
113+
{
114+
var jobName = req.Query["name"];
115+
if (!String.IsNullOrEmpty(jobName))
116+
return Results.Ok(jobManager.GetJobStatus(jobName));
116117

117-
return httpContext.Response.WriteAsJsonAsync(status);
118-
});
118+
return Results.Ok(jobManager.GetJobStatus());
119+
});
119120

120-
app.MapGet("/runjob", async httpContext =>
121-
{
122-
var jobManager = httpContext.RequestServices.GetRequiredService<IJobManager>();
123-
await jobManager.RunJobAsync("EvenMinutes");
124-
await jobManager.RunJobAsync<EveryMinuteJob>();
121+
app.MapGet("/jobs/run", async (IJobManager jobManager, HttpRequest req) =>
122+
{
123+
var jobName = req.Query["name"];
124+
if (string.IsNullOrWhiteSpace(jobName))
125+
return Results.BadRequest("Job name is required.");
125126

126-
await httpContext.Response.WriteAsync("Job manually triggered");
127-
});
127+
await jobManager.RunJobAsync(jobName);
128128

129-
app.MapGet("/togglejob", httpContext =>
130-
{
131-
var jobManager = httpContext.RequestServices.GetRequiredService<IJobManager>();
132-
jobManager.AddOrUpdate("EvenMinutes", j => j.Enabled(!j.Target.IsEnabled));
133-
jobManager.AddOrUpdate<EveryMinuteJob>(j => j.Enabled(!j.Target.IsEnabled));
129+
return Results.Accepted();
130+
});
134131

135-
return httpContext.Response.WriteAsync("Job toggled");
136-
});
132+
app.MapGet("/jobs/enable", (IJobManager jobManager, HttpRequest req) =>
133+
{
134+
var jobName = req.Query["name"];
135+
if (string.IsNullOrWhiteSpace(jobName))
136+
return Results.BadRequest("Job name is required.");
137+
138+
jobManager.Update(jobName, c => c.Enabled());
139+
140+
return Results.Accepted();
141+
});
142+
143+
app.MapGet("/jobs/disable", (IJobManager jobManager, HttpRequest req) =>
144+
{
145+
var jobName = req.Query["name"];
146+
if (string.IsNullOrWhiteSpace(jobName))
147+
return Results.BadRequest("Job name is required.");
148+
149+
jobManager.Update(jobName, c => c.Disabled());
150+
151+
return Results.Accepted();
152+
});
137153

138154
app.UseHealthChecks("/health");
139155
app.UseReadyHealthChecks("Critical");

src/Foundatio.Extensions.Hosting/Jobs/JobManager.cs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ public interface IJobManager
1515
{
1616
void AddOrUpdate<TJob>(Action<ScheduledJobOptionsBuilder> configure = null) where TJob : class, IJob;
1717
void AddOrUpdate(string jobName, Action<ScheduledJobOptionsBuilder> configure = null);
18+
void Update<TJob>(Action<ScheduledJobOptionsBuilder> configure = null);
19+
void Update(string jobName, Action<ScheduledJobOptionsBuilder> configure = null);
1820
void Remove<TJob>() where TJob : class, IJob;
1921
void Remove(string jobName);
2022
JobStatus[] GetJobStatus();
23+
JobStatus GetJobStatus(string jobName);
2124
Task RunJobAsync<TJob>(CancellationToken cancellationToken = default) where TJob : class, IJob;
2225
Task RunJobAsync(string jobName, CancellationToken cancellationToken = default);
2326
}
@@ -49,7 +52,7 @@ public void AddOrUpdate<TJob>(Action<ScheduledJobOptionsBuilder> configure = nul
4952
string jobName = JobOptions.GetDefaultJobName(typeof(TJob));
5053
lock (_lock)
5154
{
52-
var job = Jobs.FirstOrDefault(j => j.Options.Name == jobName);
55+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
5356
if (job == null)
5457
{
5558
var options = new ScheduledJobOptions
@@ -74,7 +77,7 @@ public void AddOrUpdate(string jobName, Action<ScheduledJobOptionsBuilder> confi
7477
{
7578
lock (_lock)
7679
{
77-
var job = Jobs.FirstOrDefault(j => j.Options.Name == jobName);
80+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
7881
if (job == null)
7982
{
8083
var options = new ScheduledJobOptions
@@ -94,12 +97,39 @@ public void AddOrUpdate(string jobName, Action<ScheduledJobOptionsBuilder> confi
9497
}
9598
}
9699

100+
public void Update<TJob>(Action<ScheduledJobOptionsBuilder> configure = null)
101+
{
102+
string jobName = JobOptions.GetDefaultJobName(typeof(TJob));
103+
lock (_lock)
104+
{
105+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
106+
if (job == null)
107+
throw new ArgumentException("Job not found.", nameof(jobName));
108+
109+
var builder = new ScheduledJobOptionsBuilder(job.Options);
110+
configure?.Invoke(builder);
111+
}
112+
}
113+
114+
public void Update(string jobName, Action<ScheduledJobOptionsBuilder> configure = null)
115+
{
116+
lock (_lock)
117+
{
118+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
119+
if (job == null)
120+
throw new ArgumentException("Job not found.", nameof(jobName));
121+
122+
var builder = new ScheduledJobOptionsBuilder(job.Options);
123+
configure?.Invoke(builder);
124+
}
125+
}
126+
97127
public void Remove<TJob>() where TJob : class, IJob
98128
{
99129
string jobName = JobOptions.GetDefaultJobName(typeof(TJob));
100130
lock (_lock)
101131
{
102-
var job = Jobs.FirstOrDefault(j => j.Options.Name == jobName);
132+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
103133
if (job == null)
104134
return;
105135

@@ -112,7 +142,7 @@ public void Remove(string jobName)
112142
{
113143
lock (_lock)
114144
{
115-
var job = _jobs.FirstOrDefault(j => j.Options.Name == jobName);
145+
var job = _jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
116146
if (job == null)
117147
return;
118148

@@ -129,13 +159,17 @@ public JobStatus[] GetJobStatus()
129159
Schedule = j.Options.CronSchedule,
130160
LastRun = j.LastRun,
131161
LastSuccess = j.LastSuccess,
162+
LastDuration = j.LastDuration,
132163
LastErrorMessage = j.LastErrorMessage,
133164
NextRun = j.NextRun,
134165
IsRunning = j.IsRunning,
135166
IsEnabled = j.Options.IsEnabled
136167
}).ToArray();
137168
}
138169

170+
public JobStatus GetJobStatus(string jobName) => GetJobStatus().FirstOrDefault(j => j.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase))
171+
?? throw new ArgumentException("Job not found.", nameof(jobName));
172+
139173
public async Task RunJobAsync<TJob>(CancellationToken cancellationToken = default) where TJob : class, IJob
140174
{
141175
string jobName = JobOptions.GetDefaultJobName(typeof(TJob));
@@ -144,7 +178,7 @@ public async Task RunJobAsync<TJob>(CancellationToken cancellationToken = defaul
144178

145179
public async Task RunJobAsync(string jobName, CancellationToken cancellationToken = default)
146180
{
147-
var job = Jobs.FirstOrDefault(j => j.Options.Name == jobName);
181+
var job = Jobs.FirstOrDefault(j => j.Options.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase));
148182
if (job == null)
149183
throw new ArgumentException("Job not found.", nameof(jobName));
150184

@@ -161,6 +195,7 @@ public class JobStatus
161195
public string Schedule { get; set; }
162196
public DateTime? LastRun { get; set; }
163197
public DateTime? LastSuccess { get; set; }
198+
public TimeSpan? LastDuration { get; set; }
164199
public string LastErrorMessage { get; set; }
165200
public DateTime? NextRun { get; set; }
166201
public bool IsRunning { get; set; }

src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobInstance.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using Foundatio.Caching;
@@ -90,6 +91,7 @@ private void UpdateCronExpression()
9091
public DateTime? NextRun { get; internal set; }
9192
public DateTime? LastRun { get; internal set; }
9293
public DateTime? LastSuccess { get; internal set; }
94+
public TimeSpan? LastDuration { get; internal set; }
9395
public string LastErrorMessage { get; internal set; }
9496

9597
public Task RunTask { get; private set; }
@@ -210,7 +212,10 @@ public async Task StartAsync(bool isManual, CancellationToken cancellationToken
210212

211213
await UpdateDistributedStateAsync(true).AnyContext();
212214

215+
var sw = Stopwatch.StartNew();
213216
var result = await job.TryRunAsync(cancellationToken).AnyContext();
217+
sw.Stop();
218+
LastDuration = sw.Elapsed;
214219

215220
_logger.LogJobResult(result, Options.Name);
216221
if (result.IsSuccess)
@@ -280,6 +285,7 @@ await _cacheClient.SetAsync(CacheKey + ":state",
280285
IsRunning = IsRunning,
281286
LastRun = LastRun,
282287
LastSuccess = LastSuccess,
288+
LastDuration = LastDuration,
283289
LastErrorMessage = LastErrorMessage
284290
}).AnyContext();
285291

@@ -293,6 +299,7 @@ await _messageBus.PublishAsync(new JobStateChangedMessage
293299
IsRunning = IsRunning,
294300
LastRun = LastRun,
295301
LastSuccess = LastSuccess,
302+
LastDuration = LastDuration,
296303
LastErrorMessage = LastErrorMessage,
297304
Reason = reason
298305
}).AnyContext();
@@ -328,6 +335,7 @@ internal async Task ApplyDistributedStateAsync(JobInstanceState state = null)
328335
IsRunning = state.IsRunning;
329336
LastRun = state.LastRun;
330337
LastSuccess = state.LastSuccess;
338+
LastDuration = state.LastDuration;
331339
LastErrorMessage = state.LastErrorMessage;
332340
NextRun = GetNextScheduledRun();
333341
}
@@ -352,6 +360,7 @@ public class JobInstanceState
352360
public bool IsRunning { get; set; }
353361
public DateTime? LastRun { get; set; }
354362
public DateTime? LastSuccess { get; set; }
363+
public TimeSpan? LastDuration { get; set; }
355364
public string LastErrorMessage { get; set; }
356365
}
357366

src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ await _messageBus.SubscribeAsync<JobStateChangedMessage>(s =>
6262
job.IsRunning = s.IsRunning;
6363
job.LastRun = s.LastRun;
6464
job.LastSuccess = s.LastSuccess;
65+
job.LastDuration = s.LastDuration;
6566
job.LastErrorMessage = s.LastErrorMessage;
6667
job.LastStateSync = _timeProvider.GetUtcNow().UtcDateTime;
6768
job.NextRun = job.GetNextScheduledRun();

0 commit comments

Comments
 (0)