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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: CI

on:
push:
branches:
- "**"
pull_request:
branches:
- "**"
Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="[10.0.0, 11.0.0)" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="[10.0.0, 11.0.0)" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.5.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="[10.0.0, 11.0.0)" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.6.2" />
<PackageVersion Include="Microsoft.Testing.Platform" Version="2.2.1" />
<PackageVersion Include="Cronos" Version="0.12.0" />
Expand All @@ -19,4 +19,4 @@
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.11.0" />
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
</ItemGroup>
</Project>
</Project>
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,37 @@ dotnet add package Sheddueller.Testing

Register `AddSheddueller(...)` in processes that only submit work or manage schedules. Register `AddShedduellerWorker(...)` in processes that should also execute jobs.

`WorkerOptions` below is an application options type; the callback can read any service registered with DI.

```csharp
using Npgsql;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Sheddueller;
using Sheddueller.Postgres;

var dataSource = NpgsqlDataSource.Create(
builder.Configuration.GetConnectionString("Sheddueller")
?? throw new InvalidOperationException("Missing Sheddueller connection string."));

builder.Services.AddSingleton(dataSource);
builder.Services.Configure<WorkerOptions>(
builder.Configuration.GetSection("Worker"));
builder.Services.AddTransient<EmailJobs>();

builder.Services.AddShedduellerWorker(sheddueller => sheddueller
.UsePostgres(postgres =>
{
postgres.DataSource = dataSource;
postgres.SchemaName = "sheddueller";
})
.ConfigureOptions(options =>
.UsePostgres(
serviceProvider =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
return configuration.GetConnectionString("Sheddueller")
?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required.");
},
(serviceProvider, postgres) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
postgres.SchemaName = configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller";
})
.ConfigureOptions((serviceProvider, options) =>
{
var worker = serviceProvider.GetRequiredService<IOptions<WorkerOptions>>().Value;
options.NodeId = Environment.MachineName;
options.MaxConcurrentExecutionsPerNode = 8;
options.MaxConcurrentExecutionsPerNode = worker.MaxConcurrentExecutions;
options.DefaultRetryPolicy = new RetryPolicy(
MaxAttempts: 3,
BackoffKind: RetryBackoffKind.Exponential,
Expand All @@ -102,11 +111,13 @@ builder.Services.AddShedduellerWorker(sheddueller => sheddueller
Schema migrations are explicit:

```csharp
await app.Services.GetRequiredService<IPostgresMigrator>().ApplyAsync();
await app.ApplyShedduellerPostgresMigrationsAsync();
```

Run migrations during deployment or before starting workers against a new schema. Normal startup validates the configured provider; it does not silently create the schema.

Use `UsePostgres(postgres => postgres.DataSource = dataSource)` when an application needs to share or own a prebuilt `NpgsqlDataSource`; in that mode, the application also owns disposal.

## Enqueue Jobs

Job methods return `Task` or `ValueTask` and receive the scheduler-owned `CancellationToken`. Use `Job.Context` when a handler needs durable logs, progress events, the job id, or the attempt number.
Expand Down Expand Up @@ -164,7 +175,7 @@ builder.Services.AddShedduellerDashboard(options =>
});

app.UseAntiforgery();
((IApplicationBuilder)app).MapShedduellerDashboard("/sheddueller");
app.MapShedduellerDashboard("/sheddueller");
```

The dashboard uses the configured Sheddueller provider and can be hosted by a worker process or a client-only web process.
Expand Down
Empty file added docs/.gitkeep
Empty file.
29 changes: 14 additions & 15 deletions samples/Sheddueller.SampleHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Npgsql;

using Sheddueller;
using Sheddueller.Inspection.Jobs;
using Sheddueller.Postgres;
Expand All @@ -9,21 +7,22 @@

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("Sheddueller")
?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required.");
var schemaName = builder.Configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller";
await using var dataSource = NpgsqlDataSource.Create(connectionString);

builder.Services.AddSingleton(dataSource);
builder.Services.AddSingleton<DemoJobState>();
builder.Services.AddTransient<DemoJobService>();

builder.Services.AddShedduellerWorker(sheddueller => sheddueller
.UsePostgres(options =>
{
options.DataSource = dataSource;
options.SchemaName = schemaName;
})
.UsePostgres(
serviceProvider =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
return configuration.GetConnectionString("Sheddueller")
?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required.");
},
(serviceProvider, options) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
options.SchemaName = configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller";
})
.ConfigureOptions(options =>
{
options.NodeId = "sample-host";
Expand All @@ -37,7 +36,7 @@

var app = builder.Build();

await app.Services.GetRequiredService<IPostgresMigrator>().ApplyAsync();
await app.ApplyShedduellerPostgresMigrationsAsync();

app.UseAntiforgery();

Expand Down Expand Up @@ -170,7 +169,7 @@
return RedirectWithMessage($"Queued cancelable delayed job {jobId:D} for {notBeforeUtc:O}.");
});

((IApplicationBuilder)app).MapShedduellerDashboard("/sheddueller");
app.MapShedduellerDashboard("/sheddueller");

await app.RunAsync();

Expand Down
4 changes: 0 additions & 4 deletions samples/Sheddueller.SampleHost/Sheddueller.SampleHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,4 @@
<ProjectReference Include="..\..\src\Sheddueller.Postgres\Sheddueller.Postgres.csproj" />
<ProjectReference Include="..\..\src\Sheddueller.Worker\Sheddueller.Worker.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Npgsql" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Debug",
Expand Down
10 changes: 9 additions & 1 deletion src/Sheddueller.Dashboard/Components/DashboardApp.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@inject NavigationManager Navigation
@inject Microsoft.Extensions.Options.IOptions<ShedduellerDashboardOptions> DashboardOptions

<!DOCTYPE html>
<html lang="en">
Expand All @@ -13,7 +14,14 @@
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<Routes @rendermode="DashboardRenderMode" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>

@code {
private IComponentRenderMode DashboardRenderMode
=> DashboardOptions.Value.Prerender
? InteractiveServer
: new InteractiveServerRenderMode(prerender: false);
}
Loading