Skip to content

Commit cb79861

Browse files
YunchuWangjviaunytian
authored
feat: Schedule Support (#368)
* update sln * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * add * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * SAVE * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * save * fix * cleanup * demo1 * take orch inst id from creationoptions if provided * save * remove unsupported for now * save * save * save * comment * save * style * update eventids * startimmediatelyifalte * save * refactor * save * before waiter refactor * logging rule scheduledemo added * sample fb * fb * param * creatopt update * update scheduleconfig * save * ischeduletaskclient interface * save * listschedule update * remove create * createschedule * save * createsche * create step 2 * create step 3 * list * save * poll free part 1 * poll free part 2 * poll free working * clean up * more cleanup * clean * rename scheduleconsoleapp * remove * save * schedule webapp sample * add http client file for sample * update * save * finalize schedule webapi sample * partial fb addressed * more fb address * minimal * config * fb * cleanup * save * log * throw * fb * fb * save * Update src/ScheduledTasks/Client/ScheduledTaskClient.cs Co-authored-by: Jacob Viau <[email protected]> * pagefix * fix * fix * createorupdate * fb * REFACTOR * clean * updatre nextrunat computation * save * fix * excep * rename schedulehandle and ch to abstract * renmae to scheduleclientimpl * SAVE * rename scheduledtaskclient and abstract * fix * fix * fix * fix * test p1 * test p2 * remove ScheduleAlreadyExistsException * add createasync in scheduleclient * fix unit tests * fix * fb * fb * fb * f * fix * up * fixed scheduletests * fix tests * fix * more tests * cleanup * save * unit test fix * cleanup comments * more tests * cleanup * cleanup * link orch with schedule * fb * todo * fix test * Update src/ScheduledTasks/Client/ScheduledTaskClient.cs Co-authored-by: Naiyuan Tian <[email protected]> * Update src/ScheduledTasks/Client/ScheduledTaskClient.cs Co-authored-by: Naiyuan Tian <[email protected]> * Update src/ScheduledTasks/Client/ScheduleClient.cs Co-authored-by: Naiyuan Tian <[email protected]> * refresh only active * Revert "refresh only active" This reverts commit 65db0e2. * fb * Update ScheduleClientImpl.cs * rename schedule clients to default prefix * fb --------- Co-authored-by: Jacob Viau <[email protected]> Co-authored-by: Naiyuan Tian <[email protected]>
1 parent 760847e commit cb79861

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+5520
-1
lines changed

Microsoft.DurableTask.sln

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged.Tests",
8585
EndProject
8686
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppMinimal", "samples\ConsoleAppMinimal\ConsoleAppMinimal.csproj", "{B48FACA9-A328-452A-BFAE-C4F60F9C7024}"
8787
EndProject
88+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScheduledTasks", "src\ScheduledTasks\ScheduledTasks.csproj", "{69ED743C-D616-4530-87E2-391D249D7368}"
89+
EndProject
90+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScheduleConsoleApp", "samples\ScheduleConsoleApp\ScheduleConsoleApp.csproj", "{A89B766C-987F-4C9F-8937-D0AB9FE640C8}"
91+
EndProject
92+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScheduleWebApp", "samples\ScheduleWebApp\ScheduleWebApp.csproj", "{100348B5-4D97-4A3F-B777-AB14F276F8FE}"
93+
EndProject
94+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScheduledTasks.Tests", "test\ScheduledTasks.Tests\ScheduledTasks.Tests.csproj", "{D2779F32-A548-44F8-B60A-6AC018966C79}"
95+
EndProject
8896
Global
8997
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9098
Debug|Any CPU = Debug|Any CPU
@@ -223,6 +231,22 @@ Global
223231
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Debug|Any CPU.Build.0 = Debug|Any CPU
224232
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Release|Any CPU.ActiveCfg = Release|Any CPU
225233
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Release|Any CPU.Build.0 = Release|Any CPU
234+
{69ED743C-D616-4530-87E2-391D249D7368}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
235+
{69ED743C-D616-4530-87E2-391D249D7368}.Debug|Any CPU.Build.0 = Debug|Any CPU
236+
{69ED743C-D616-4530-87E2-391D249D7368}.Release|Any CPU.ActiveCfg = Release|Any CPU
237+
{69ED743C-D616-4530-87E2-391D249D7368}.Release|Any CPU.Build.0 = Release|Any CPU
238+
{A89B766C-987F-4C9F-8937-D0AB9FE640C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
239+
{A89B766C-987F-4C9F-8937-D0AB9FE640C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
240+
{A89B766C-987F-4C9F-8937-D0AB9FE640C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
241+
{A89B766C-987F-4C9F-8937-D0AB9FE640C8}.Release|Any CPU.Build.0 = Release|Any CPU
242+
{100348B5-4D97-4A3F-B777-AB14F276F8FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
243+
{100348B5-4D97-4A3F-B777-AB14F276F8FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
244+
{100348B5-4D97-4A3F-B777-AB14F276F8FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
245+
{100348B5-4D97-4A3F-B777-AB14F276F8FE}.Release|Any CPU.Build.0 = Release|Any CPU
246+
{D2779F32-A548-44F8-B60A-6AC018966C79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
247+
{D2779F32-A548-44F8-B60A-6AC018966C79}.Debug|Any CPU.Build.0 = Debug|Any CPU
248+
{D2779F32-A548-44F8-B60A-6AC018966C79}.Release|Any CPU.ActiveCfg = Release|Any CPU
249+
{D2779F32-A548-44F8-B60A-6AC018966C79}.Release|Any CPU.Build.0 = Release|Any CPU
226250
EndGlobalSection
227251
GlobalSection(SolutionProperties) = preSolution
228252
HideSolutionNode = FALSE
@@ -265,6 +289,10 @@ Global
265289
{CECADDB5-E30A-4CE2-8604-9AC596D4A2DC} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
266290
{3272C041-F81D-4C85-A4FB-2A700B5A7A9D} = {CECADDB5-E30A-4CE2-8604-9AC596D4A2DC}
267291
{B48FACA9-A328-452A-BFAE-C4F60F9C7024} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
292+
{69ED743C-D616-4530-87E2-391D249D7368} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
293+
{A89B766C-987F-4C9F-8937-D0AB9FE640C8} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
294+
{100348B5-4D97-4A3F-B777-AB14F276F8FE} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
295+
{D2779F32-A548-44F8-B60A-6AC018966C79} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
268296
EndGlobalSection
269297
GlobalSection(ExtensibilityGlobals) = postSolution
270298
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.DurableTask;
5+
6+
namespace ScheduleConsoleApp.Activities;
7+
8+
[DurableTask]
9+
public class GetStockPrice : TaskActivity<string, decimal>
10+
{
11+
public override Task<decimal> RunAsync(TaskActivityContext context, string symbol)
12+
{
13+
// Mock implementation - would normally call stock API
14+
return Task.FromResult(100.00m);
15+
}
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.DurableTask;
5+
using Microsoft.Extensions.Logging;
6+
7+
[DurableTask]
8+
public class StockPriceOrchestrator : TaskOrchestrator<string, string>
9+
{
10+
public override async Task<string> RunAsync(TaskOrchestrationContext context, string symbol)
11+
{
12+
var logger = context.CreateReplaySafeLogger("DemoOrchestration");
13+
logger.LogInformation("Getting stock price for: {symbol}", symbol);
14+
try
15+
{
16+
// Get current stock price
17+
decimal currentPrice = await context.CallGetStockPriceAsync(symbol);
18+
19+
logger.LogInformation("Current price for {symbol} is ${price:F2}", symbol, currentPrice);
20+
21+
return $"Stock {symbol} price: ${currentPrice:F2} at {DateTime.UtcNow}";
22+
}
23+
catch (Exception ex)
24+
{
25+
logger.LogError(ex, "Error processing stock price for {symbol}", symbol);
26+
throw;
27+
}
28+
}
29+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
using Microsoft.DurableTask;
4+
using Microsoft.DurableTask.Client;
5+
using Microsoft.DurableTask.Client.AzureManaged;
6+
using Microsoft.DurableTask.ScheduledTasks;
7+
using Microsoft.DurableTask.Worker;
8+
using Microsoft.DurableTask.Worker.AzureManaged;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.Extensions.Hosting;
12+
using Microsoft.Extensions.Logging;
13+
using ScheduleConsoleApp;
14+
15+
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
16+
17+
// Get configuration
18+
string connectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
19+
?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_CONNECTION_STRING'");
20+
21+
// Configure the worker
22+
builder.Services.AddDurableTaskWorker(builder =>
23+
{
24+
// Add the Schedule entity and demo orchestration
25+
builder.AddTasks(r => r.AddAllGeneratedTasks());
26+
27+
// Enable scheduled tasks support
28+
builder.UseDurableTaskScheduler(connectionString);
29+
builder.UseScheduledTasks();
30+
});
31+
32+
// Configure the client
33+
builder.Services.AddDurableTaskClient(builder =>
34+
{
35+
builder.UseDurableTaskScheduler(connectionString);
36+
builder.UseScheduledTasks();
37+
});
38+
39+
// Configure console logging
40+
builder.Services.AddLogging(logging =>
41+
{
42+
logging.AddSimpleConsole(options =>
43+
{
44+
options.SingleLine = true;
45+
options.UseUtcTimestamp = true;
46+
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
47+
});
48+
});
49+
50+
IHost host = builder.Build();
51+
await host.StartAsync();
52+
53+
// Run the schedule operations
54+
ScheduledTaskClient scheduledTaskClient = host.Services.GetRequiredService<ScheduledTaskClient>();
55+
await ScheduleDemo.RunDemoAsync(scheduledTaskClient);
56+
57+
await host.StopAsync();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting"/>
11+
<PackageReference Include="Azure.Identity" />
12+
<PackageReference Include="Grpc.Net.Client" />
13+
<PackageReference Include="Microsoft.DurableTask.Generators" OutputItemType="Analyzer" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\Client\AzureManaged\Client.AzureManaged.csproj" />
18+
<ProjectReference Include="..\..\src\Worker\AzureManaged\Worker.AzureManaged.csproj" />
19+
<ProjectReference Include="..\..\src\ScheduledTasks\ScheduledTasks.csproj" />
20+
</ItemGroup>
21+
</Project>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.DurableTask;
5+
using Microsoft.DurableTask.ScheduledTasks;
6+
7+
namespace ScheduleConsoleApp;
8+
9+
/// <summary>
10+
/// Demonstrates various schedule operations in a sample application.
11+
/// </summary>
12+
static class ScheduleDemo
13+
{
14+
public static async Task RunDemoAsync(ScheduledTaskClient scheduledTaskClient)
15+
{
16+
ArgumentNullException.ThrowIfNull(scheduledTaskClient);
17+
18+
try
19+
{
20+
await DeleteExistingSchedulesAsync(scheduledTaskClient);
21+
await CreateAndManageScheduleAsync(scheduledTaskClient);
22+
}
23+
catch (Exception ex)
24+
{
25+
Console.WriteLine($"One of your schedule operations failed, please fix and rerun: {ex.Message}");
26+
}
27+
}
28+
29+
static async Task DeleteExistingSchedulesAsync(ScheduledTaskClient scheduledTaskClient)
30+
{
31+
// Define the initial query with the desired page size
32+
ScheduleQuery query = new ScheduleQuery { PageSize = 100 };
33+
34+
// Retrieve the pageable collection of schedule IDs
35+
AsyncPageable<ScheduleDescription> schedules = scheduledTaskClient.ListSchedulesAsync(query);
36+
37+
// Delete each existing schedule
38+
await foreach (ScheduleDescription schedule in schedules)
39+
{
40+
ScheduleClient scheduleClient = scheduledTaskClient.GetScheduleClient(schedule.ScheduleId);
41+
await scheduleClient.DeleteAsync();
42+
Console.WriteLine($"Deleted schedule {schedule.ScheduleId}");
43+
}
44+
}
45+
46+
static async Task CreateAndManageScheduleAsync(ScheduledTaskClient scheduledTaskClient)
47+
{
48+
// Create schedule options that runs every 4 seconds
49+
ScheduleCreationOptions scheduleOptions = new ScheduleCreationOptions(
50+
"demo-schedule101",
51+
nameof(StockPriceOrchestrator),
52+
TimeSpan.FromSeconds(4))
53+
{
54+
StartAt = DateTimeOffset.UtcNow,
55+
OrchestrationInput = "MSFT"
56+
};
57+
58+
// Create the schedule and get a handle to it
59+
ScheduleClient scheduleClient = await scheduledTaskClient.CreateScheduleAsync(scheduleOptions);
60+
61+
// Get and print the initial schedule description
62+
await PrintScheduleDescriptionAsync(scheduleClient);
63+
64+
// Pause the schedule
65+
Console.WriteLine("\nPausing schedule...");
66+
await scheduleClient.PauseAsync();
67+
await PrintScheduleDescriptionAsync(scheduleClient);
68+
69+
// Resume the schedule
70+
Console.WriteLine("\nResuming schedule...");
71+
await scheduleClient.ResumeAsync();
72+
await PrintScheduleDescriptionAsync(scheduleClient);
73+
74+
// Wait for a while to let the schedule run
75+
await Task.Delay(TimeSpan.FromMinutes(30));
76+
}
77+
78+
static async Task PrintScheduleDescriptionAsync(ScheduleClient scheduleClient)
79+
{
80+
ScheduleDescription scheduleDescription = await scheduleClient.DescribeAsync();
81+
Console.WriteLine(scheduleDescription);
82+
Console.WriteLine("\n\n");
83+
}
84+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Debug",
5+
"Microsoft": "Warning",
6+
"ScheduleConsoleApp": "Debug",
7+
"DemoOrchestration": "Debug"
8+
}
9+
}
10+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace ScheduleWebApp.Models;
5+
6+
/// <summary>
7+
/// Represents a request to create a new schedule.
8+
/// </summary>
9+
public class CreateScheduleRequest
10+
{
11+
/// <summary>
12+
/// Gets or sets the unique identifier for the schedule.
13+
/// </summary>
14+
public string Id { get; set; } = default!;
15+
16+
/// <summary>
17+
/// Gets or sets the name of the orchestration to be scheduled.
18+
/// </summary>
19+
public string OrchestrationName { get; set; } = default!;
20+
21+
/// <summary>
22+
/// Gets or sets the input data for the orchestration.
23+
/// </summary>
24+
public string? Input { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the time interval between schedule executions.
28+
/// </summary>
29+
public TimeSpan Interval { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the time when the schedule should start.
33+
/// </summary>
34+
public DateTimeOffset? StartAt { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the time when the schedule should end.
38+
/// </summary>
39+
public DateTimeOffset? EndAt { get; set; }
40+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace ScheduleWebApp.Models;
5+
6+
/// <summary>
7+
/// Represents a request to update an existing schedule.
8+
/// </summary>
9+
public class UpdateScheduleRequest
10+
{
11+
/// <summary>
12+
/// Gets or sets the name of the orchestration to be scheduled.
13+
/// </summary>
14+
public string OrchestrationName { get; set; } = default!;
15+
16+
/// <summary>
17+
/// Gets or sets the input data for the orchestration.
18+
/// </summary>
19+
public string? Input { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the time interval between schedule executions.
23+
/// </summary>
24+
public TimeSpan Interval { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the time when the schedule should start.
28+
/// </summary>
29+
public DateTimeOffset? StartAt { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the time when the schedule should end.
33+
/// </summary>
34+
public DateTimeOffset? EndAt { get; set; }
35+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.DurableTask;
5+
6+
namespace ScheduleWebApp.Orchestrations;
7+
8+
public class CacheClearingOrchestrator : TaskOrchestrator<string, string>
9+
{
10+
public override async Task<string> RunAsync(TaskOrchestrationContext context, string scheduleId)
11+
{
12+
ILogger logger = context.CreateReplaySafeLogger(nameof(CacheClearingOrchestrator));
13+
try
14+
{
15+
logger.LogInformation("Starting CacheClearingOrchestration for schedule ID: {ScheduleId}", scheduleId);
16+
17+
// Simulate cache clearing
18+
await Task.Delay(TimeSpan.FromSeconds(5));
19+
20+
logger.LogInformation("CacheClearingOrchestration completed for schedule ID: {ScheduleId}", scheduleId);
21+
22+
return "ok";
23+
}
24+
catch (Exception ex)
25+
{
26+
logger.LogError(ex, "Error in CacheClearingOrchestration for schedule ID: {ScheduleId}", scheduleId);
27+
throw;
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)