Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,18 @@ Use `UsePostgres(postgres => postgres.DataSource = dataSource)` when an applicat

The operational store keeps active jobs plus a bounded searchable terminal window. By default, background retention cleanup keeps completed jobs for 24 hours and failed or canceled jobs for 7 days, then deletes those terminal job rows and their tags, concurrency groups, and events. Configure `ShedduellerOptions.JobRetention` to change the windows, set a state retention to `null` to keep that state indefinitely, or set `Enabled = false` to disable cleanup.

Concurrency group limits use a persisted override over a code-defined default over the built-in default of `1`. Use `IConcurrencyGroupManager.SetDefaultLimitAsync(...)` from startup or deployment seeding code so dashboard edits survive restarts. Use `SetLimitAsync(...)` for an explicit live override and `ClearLimitOverrideAsync(...)` to fall back to the code default.
Concurrency groups independently enforce active-job capacity and an optional smooth job-start rate across the cluster. Capacity limits use a persisted override over a code-defined default over the built-in default of `1`. Rate limits use a persisted override over a code-defined default over a built-in unlimited rate.

Use `IConcurrencyGroupManager.SetDefaultLimitAsync(...)` and `SetDefaultRateLimitAsync(...)` from startup or deployment seeding code so dashboard edits survive restarts. Rate permits are evenly spaced: a rate of two starts per second admits one claim every 500 milliseconds and does not accumulate burst credit while idle. Every successful claim consumes a rate permit, including retries and reclaims.

```csharp
await concurrencyGroups.SetDefaultLimitAsync("provider:happy-holiday-homes", 1);
await concurrencyGroups.SetDefaultRateLimitAsync(
"provider:happy-holiday-homes",
new ConcurrencyGroupRateLimit(2, TimeSpan.FromSeconds(1)));
```

Use `SetLimitAsync(...)` and `SetRateLimitAsync(...)` for limited live overrides. `SetUnlimitedRateLimitAsync(...)` explicitly disables a code-defined rate, while `ClearRateLimitOverrideAsync(...)` returns to the code default. `ClearLimitOverrideAsync(...)` performs the equivalent reset for capacity.

## Enqueue Jobs

Expand Down
1 change: 1 addition & 0 deletions samples/Sheddueller.SampleHost/LauncherPageRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public static string Render(string? statusMessage)
AppendActionCard(builder, "/launch/delayed", "Delayed job", "Queues a short delayed job to exercise delayed state and not-before time.", "Enqueue job");
AppendActionCard(builder, "/launch/many-tags", "Many tags", "Queues a tagged job with informational tags first and ceremonial tags later.", "Enqueue job");
AppendActionCard(builder, "/launch/blocking-batch", "Concurrency batch", "Sets a shared group limit to 1 and enqueues several long jobs.", "Enqueue batch");
AppendActionCard(builder, "/launch/rate-limited-batch", "Rate-limited batch", "Queues six jobs with concurrency 3 and a smooth rate of two starts per five seconds.", "Enqueue batch");
AppendActionCard(builder, "/launch/idempotent", "Idempotent reprice", "Queues one reprice-listing-3 job behind a held group slot; click twice quickly to reuse the queued job.", "Enqueue job");
AppendActionCard(builder, "/launch/cancelable", "Cancelable delayed job", "Creates a delayed queued job that can be canceled from the dashboard.", "Enqueue job");
builder.AppendLine(" </div>");
Expand Down
26 changes: 26 additions & 0 deletions samples/Sheddueller.SampleHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,32 @@
return RedirectWithMessage($"Queued {jobIds.Count} concurrency-demo jobs in group '{GroupKey}' with limit 1.");
});

app.MapPost("/launch/rate-limited-batch", async (
IConcurrencyGroupManager concurrencyGroupManager,
IJobEnqueuer enqueuer,
CancellationToken cancellationToken) =>
{
const string GroupKey = "demo:rate-limited";
await concurrencyGroupManager.SetDefaultLimitAsync(GroupKey, 3, cancellationToken).ConfigureAwait(false);
await concurrencyGroupManager.SetDefaultRateLimitAsync(
GroupKey,
new ConcurrencyGroupRateLimit(2, TimeSpan.FromSeconds(5)),
cancellationToken).ConfigureAwait(false);

var jobIds = new List<Guid>();
for (var index = 1; index <= 6; index++)
{
var jobId = await enqueuer.EnqueueAsync<DemoJobService>(
(service, ct) => service.RunQuickAsync($"rate-limited-{index}", ct),
new JobSubmission(Priority: 20, ConcurrencyGroupKeys: [GroupKey]),
cancellationToken).ConfigureAwait(false);
jobIds.Add(jobId);
}

return RedirectWithMessage(
$"Queued {jobIds.Count} jobs in group '{GroupKey}' with concurrency 3 and a smooth rate of 2 starts per 5 seconds.");
});

app.MapPost("/launch/idempotent", async (
IConcurrencyGroupManager concurrencyGroupManager,
IJobEnqueuer enqueuer,
Expand Down
1 change: 1 addition & 0 deletions samples/Sheddueller.SampleHost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ The sample applies PostgreSQL schema migrations automatically on startup and reg
- `Permanent failure`: terminal failure without retries
- `Delayed job`: waits 30 seconds before becoming claimable
- `Concurrency batch`: sets a shared limit of 1 and queues several long-running jobs
- `Rate-limited batch`: queues six jobs with concurrency 3 and a smooth rate of two starts every five seconds
- `Idempotent reprice`: queues a 10-second reprice job with generated idempotency behind a group limit of 1; click twice quickly to see the same queued job reused
- `Recurring demo`: creates or updates a recurring schedule that fires each minute
- `Cancelable delayed job`: creates a queued delayed job that can be canceled from the dashboard job detail page
Expand Down
Loading