Closed jkonecki closed 1 year ago
This was the breaking change which I mentioned with the Grain Context. Although there is a an Method for that : (But I share your opinion that it needs to be fixed within Orleans it self.)
You can see it in the Reminder Tests how to use the Testkit with this restriction
If you are trying to test a method that goes to a background thread, the method above doesn't help you since the context gets reset when you transition between threads. I ended up creating these classes (terrible names!), and injecting them into the grains:
internal interface IReminderExtensions
{
Task<IGrainReminder> RegisterOrUpdateReminder(Grain grain, string reminderName, TimeSpan dueTime, TimeSpan period);
Task UnregisterReminder(Grain grain, IGrainReminder reminder);
}
internal class ReminderExtensions : IReminderExtensions
{
public Task<IGrainReminder> RegisterOrUpdateReminder(Grain grain, string reminderName, TimeSpan dueTime, TimeSpan period)
=> grain.RegisterOrUpdateReminder(reminderName, dueTime, period);
public Task UnregisterReminder(Grain grain, IGrainReminder reminder)
=> grain.UnregisterReminder(reminder);
}
internal class TestReminderExtensions : IReminderExtensions
{
private readonly TestKitSilo silo;
public TestReminderExtensions(TestKitSilo silo)
{
this.silo = silo;
}
public async Task<IGrainReminder> RegisterOrUpdateReminder(Grain grain, string reminderName, TimeSpan dueTime, TimeSpan period)
{
using (await this.silo.GetReminderActivationContext(grain))
{
return await grain.RegisterOrUpdateReminder(reminderName, dueTime, period);
}
}
public async Task UnregisterReminder(Grain grain, IGrainReminder reminder)
{
using (await this.silo.GetReminderActivationContext(grain))
{
await grain.UnregisterReminder(reminder);
}
}
}
If you are trying to test a method that goes to a background thread, the method above doesn't help you since the context gets reset when you transition between threads. I ended up creating these classes (terrible names!), and injecting them into the grains:
This was really useful, thanks! I did run into one further problem: It deadlocks if you use the helpers during grain activation. I came up with this hack, as I couldn't see any way of knowing if a grain has completed activation in TestKitSilo
/// <summary>
/// This detects if we're in the onactivate where we don't need to swap context (which deadlocks)
/// </summary>
private async Task<IDisposable?> CheckIfInCreateGrain(Grain grain)
{
if (new StackTrace().GetFrames().Any(frame => frame.GetMethod()?.Name?.Contains("CreateGrainAsync") == true))
{
return null;
}
return await this.silo.GetReminderActivationContext(grain);
}
public async Task<IGrainReminder> RegisterOrUpdateReminder(Grain grain, string reminderName, TimeSpan dueTime, TimeSpan period)
{
using (await CheckIfInCreateGrain(grain))
{
return await grain.RegisterOrUpdateReminder(reminderName, dueTime, period);
}
}
For future readers: You might also need to shim GetReminder(s)
https://github.com/OrleansContrib/OrleansTestKit/pull/135 will address this -- setting the RuntimeContext via reflection instead of setting backing fields afterwards
I'm running into the following issue when trying to test reminders after upgrading to Orleans 7.x. I suspect a change would be needed in Orleans itself.
GrainReminderExtensions
class has the following check forRuntimeContext.Current
which fails inside unit test:I came up with the below workaround - reflection to the rescue ;-)