Problem statement: In Windows Workflow Foundation we have a long running state machine. We had an issue with SQL server deadlocks putting workflows into a suspended state. Since Workflow instances are independent actors, we cannot easily coordinate these calls. As part of our workflow error handling strategy, if the offending activity receives a sql exception, we want to delay and try again and do this in a loop up to a certain number of retries.
We need a (semi) random int. Now the proper thing to do is generate one random list and use the next(min,max), but each workflow instance is an independent actor and I don't want to make a central service just for this.
My strategy, is by using the leftmost digits of DateTime->Ticks as the random seed, this will generate a random enough number for a delay activity. The plan is that eventually all workflows will advance to their next state. Performance is not a concern. I know random is an expensive class to spin up, but I can live with it.
Example: There are 100 workflow instances that are scheduled to change state because a new workday has started. They all want to call sql at the same time. Workflow Instances 90,91,and 92 make calls at the same time. 90 goes through just fine, but causes a deadlock for 91 and 92.
So 91 and 92 go into a delay activity, but before we go into the delay, we need to set a somewhat random number of seconds to wait.
All Workflow Instances have a minSeconds = 10 and maxSeconds = 600
Workflow Instance 1 : never calls our random generator
Workflow Instance 2 : never calls our random generator
Workflow Instance 90: never calls our random generator
Workflow Instance 91 : hits a deadlock calls generator which returns 63
Workflow Instance 92 : hits a deadlock calls generator which returns 123
Since 91 and 92 have to wait a different number of seconds, 91 waits less time, executes, then 60 seconds later 92 wakes up from the delay and calls our large transactional stored procedure and ... no more deadlocks.
public sealed class RandomSecondsGenerator : CodeActivity<int>
{
[RequiredArgument]
public InArgument<int> MinSeconds { get; set; }
[RequiredArgument]
public InArgument<int> MaxSeconds { get; set; }
/// <summary>
/// Usage,
/// 1. Create a workflow variable, as an int, to hold the number of seconds.
/// 2. Create a workflow variable, as a timespan, to hold the conversion from int to timespan.
/// Call this Activity, and set the result to the in variable
/// Use the assignment activity to convert the int to a timespan like below
/// timespanToWait = TimeSpan.FromSeconds(secondsToWait);
/// Add the delay activity to the workflow ...
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected override int Execute(CodeActivityContext context)
{
var ticks = DateTime.Now.Ticks;
var intArray = ticks.ToString().Reverse().Take(8);
var backTogether = intArray.Aggregate("", (current, item) => current + item);
var seed = Convert.ToInt32(backTogether);
var rand = new Random(seed);
var min = context.GetValue(MinSeconds);
var max = context.GetValue(MaxSeconds);
var secondsToWait = rand.Next(min, max);
return secondsToWait;
}
}
}