-
Notifications
You must be signed in to change notification settings - Fork 11
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
[MaybeBug/MaybeDoc] Unclear expectations about dependent jobs #108
Comments
That is an interesting case and for sure a bug! Your first given option should be what is happening. That is what the user configured. I understand the test-setup is rather complex. The issue at the moment is, that we don't have any means of looking inside the state (when in a test). Therefore, the channel writes something, and the tests listen. So that we know for sure, something was triggered. |
I guess I do have an idea for fix: Services.AddNCronJob(s => s.AddJob<...>(p => p.WithCronExpression(...).WithCronExpression(...)).ExecuteWhen() Here I want to execute the job for both cron expressions |
Plus: Servuces.AddNCronJob(s => s.AddJob<..>().ExecuteWhen(); It should also work. So if the user doesn't define any parameters, it should work either way (think of Instant Jobs), |
I was also toying with something on my side. We currently link by Job type. Now that all the interesting pieces are more or less in |
I currently have a working version that does exactly that. But that leads to the following failing test: [Fact]
public async Task WhenJobWasSuccessful_DependentAnonymousJobShouldRun()
{
Func<ChannelWriter<object>, JobExecutionContext, Task> execution = async (writer, context) => await writer.WriteAsync($"Parent: {context.ParentOutput}");
ServiceCollection.AddNCronJob(n => n.AddJob<PrincipalJob>()
.ExecuteWhen(success: s => s.RunJob(execution)));
var provider = CreateServiceProvider();
await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);
provider.GetRequiredService<IInstantJobRegistry>().ForceRunInstantJob<PrincipalJob>(true);
var result = await CommunicationChannel.Reader.ReadAsync(CancellationToken) as string;
result.ShouldBe("Parent: Success");
} Basically: A job that does get triggered directly. Still, at least that is my assumption, dependent jobs, should be executed. |
By the way, a very simple test in regards to your original message might be: [Fact]
public async Task ConfiguringDifferentDependentJobsForSchedulesShouldResultInIndependentRuns()
{
ServiceCollection.AddNCronJob(n =>
{
n.AddJob<PrincipalJob>(s => s.WithCronExpression("1 0 1 * *").WithParameter(true))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("1").AsTask()));
n.AddJob<PrincipalJob>(s => s.WithCronExpression("1 0 2 * *").WithParameter(true))
.ExecuteWhen(s => s.RunJob((ChannelWriter<object> writer) => writer.WriteAsync("2").AsTask()));
});
var provider = CreateServiceProvider();
await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);
using var timeoutToken = new CancellationTokenSource(3000);
using var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, CancellationToken);
FakeTimer.Advance(TimeSpan.FromMinutes(1));
var content = await CommunicationChannel.Reader.ReadAsync(linkedToken.Token);
content.ShouldBe("1");
FakeTimer.Advance(TimeSpan.FromDays(1));
content = await CommunicationChannel.Reader.ReadAsync(linkedToken.Token);
content.ShouldBe("2");
} |
Down to one failing test |
This one -.- [Fact]
public async Task CanBuildAChainOfDependentJobs()
{
ServiceCollection.AddNCronJob(n =>
{
n.AddJob<PrincipalJob>().ExecuteWhen(success: s => s.RunJob<DependentJob>());
n.AddJob<DependentJob>().ExecuteWhen(success: s => s.RunJob<DependentDependentJob>());
});
var provider = CreateServiceProvider();
await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);
provider.GetRequiredService<IInstantJobRegistry>().ForceRunInstantJob<PrincipalJob>(true);
using var timeoutToken = new CancellationTokenSource(2000);
using var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, CancellationToken);
var result = await CommunicationChannel.Reader.ReadAsync(linkedToken.Token) as string;
result.ShouldBe("Me: Parent: Success");
FakeTimer.Advance(TimeSpan.FromMinutes(1));
result = await CommunicationChannel.Reader.ReadAsync(linkedToken.Token) as string;
result.ShouldBe("Dependent job did run");
} But I could move some stuff out of the |
I'm starting to wonder whether
In the example above, we've got one Job and two schedules. When triggering Should we blindly trigger one of them, all of them? 😉 |
Yes - I am running down the same rabbit hole! There are plenty of similar questions in my head (mainly all of them very exceptional / edge cases): ServiceCollection.AddNCronJob(n =>
{
n.AddJob<PrincipalJob>().ExecuteWhen(success: s => s.RunJob<DependentJob>("PARAM"));
n.AddJob<DependentJob>(p => p.WithCronExpression("*/2 * * * *")).ExecuteWhen(success: s => s.RunJob<DependentDependentJob>());
n.AddJob<DependentJob>(p => p.WithCronExpression("* * * * *")).ExecuteWhen(success: s => s.RunJob<AnotherJob>());
}); If I run If I just add a The approach via |
The simplest way to cover almost all, is to allow a standalone |
Considering most of those are edge cases and as of now everything lives in the JobRegistry, there could be a path where the lib would analyze the trees of potential executions and issue some warnings when ambiguities are identified. With regards to instant jobs, as their definition live outside of AddNCronJob, there's little the analyzer could do about them. However, a breaking change in the API may potentially restrict what a InstantJob execution could accept and (maybe) help resolve this.
This whole thing somehow reminds me of the constraints of a DI container...
When one register two services through their interfaces ("A job with different cron expressions or twice the same job with different ExecuteWhen"), how does a container react when it's supposed to instantiate a type that only accepts an interface as a constructor parameter? I believe this somehow to answer that question that dotnet came around with the keyed services (hence the "named convoy" thingie above). (Please keep in mind that my knowledge of the lib and all of its use cases is very superficial. So please consider everything I write with a grain of salt) |
Good points overall - I am still struggling what is the most intuitive way for people. What are they really expecting? But maybe keyed services might help here. Thanks for raising that issue. It kept me thinking alot |
FWIW, it's not a "production" issue on my side. Just one thing I've stumbled upon while working on #106. Once JobRegistry was assembled, this typed based approach downside somehow raised its head and drove me in "whatifs" scenarios. Knowing a bit more of the inner workings, would I need to achieve this kind of model, I'd know how to temporarily walk around this current dark corner. So, no pressure 😉 |
I love that you make your thoughts for yourself and basically gift us your precious time! Really appreciated. I really have to think about the issue and/or if I just document this current behavior at the very least. |
Reopened as only partially addressed through #124 |
A follow up on #128 (comment) which belongs more to this issue: The context of dependent jobs should not be considered in the scope of |
Describe the bug
Running the same job on different schedules, each schedule having different sub dependent jobs doesn't seem to work.
I've tried to expose this through a test that seems to put the "issue/expectation" under the light.
I've tried to model something that may exist in real life: Regular internal reporting vs longer delayed external reporting.
As a side note, I've haven't been able to model a test simulating a daily vs a monthly execution and would be interested to know more about how to achieve this.
Current behavior
ReportToProductOwnerJob
is invoked every minute.ReportToStakeholdersJob
is invoked every minute.Expected behavior
I can think of 3 options (from "best" to "worse" from the user standpoint):
Make it work
ReportToProductOwnerJob
is invoked every minute.ReportToStakeholdersJob
is invoked every month.Prevent it from happening
Document that it's not currently supported
Although "Make it work" would obviously be the preferred option, my current endeavor in #106 leads me to think that this might not be a one-liner fix.
Version information
The text was updated successfully, but these errors were encountered: