2
\$\begingroup\$

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;
        }
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Hi. Welcome to Code Review! This would be a stronger question if it included an input/output usage example. I.e. a running example. \$\endgroup\$ – mdfst13 Oct 20 '16 at 14:33
1
\$\begingroup\$

don't new it every time
it is expensive and is not as random as just keep using random
new Random() is based on a time seed

    private Random rand = new Random();
    protected override int Execute(CodeActivityContext context)
    {
        var min = context.GetValue(MinSeconds);
        var max = context.GetValue(MaxSeconds);
        var secondsToWait = rand.Next(min, max);
        return secondsToWait;
    }

OP said the many of the class are generated at once
Just create random once and pass it in the constructor

public class RandomSecondsGenerator 
{
    public int MinSeconds { get; set; }
    public int MaxSeconds { get; set; }
    private Random rand;
    protected int Execute(DateTime context)
    {
        var secondsToWait = rand.Next(MinSeconds, MaxSeconds);
        return secondsToWait;
    }
    public RandomSecondsGenerator(Random random)
    {
        rand = random;
    }
}
\$\endgroup\$
  • \$\begingroup\$ Moving the random out of the execute would not prevent it from being newed up each time since each workflow is an independent actor. Also since hundreds of workflow instances (that are independent) are essentially kicking off at the same time, the straight random call will produce the same number. stackoverflow.com/questions/1785744/… \$\endgroup\$ – Eric Rohlfs Oct 20 '16 at 16:13
  • \$\begingroup\$ I also thought about just getting a guid, then pulling the first three integers out of the guid. \$\endgroup\$ – Eric Rohlfs Oct 20 '16 at 16:14
  • \$\begingroup\$ Then why not just have a shared static method or pass Random in the constructor? \$\endgroup\$ – paparazzo Oct 20 '16 at 16:50
  • \$\begingroup\$ Workflows in Workflow Foundation do not share anything. Making something static would just make it a singleton for one of the 100 instances of that workflow. A service outside of the workflow would need to be called for that to work, then that service would need to be maintained. Also workflows are instantiated via a WCF call. First serializing random is not practical. I suppose we could pass in a list of like 50 randomish numbers each workflow could independently work on, but that is more work too, I'd just rather the WF work it out on its own. \$\endgroup\$ – Eric Rohlfs Oct 20 '16 at 17:19
  • 1
    \$\begingroup\$ OK but it is tricky you get a good random seed. But since no one is trying to hack you it does not need to be perfectly random. Still think is a bad plan to generate the Random in the method call. \$\endgroup\$ – paparazzo Oct 20 '16 at 17:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.