Esempio n. 1
0
        /// <summary>
        /// Solve the Problem locally in the WebRole/Hub asynchronously using Rx Observable sequences to capture and signal updates to the Client(s)
        /// </summary>
        /// <param name="problemToSolve"><see cref="WaterBucket.Domain.Problem"/> to be solved and calculate the results</param>
        /// <param name="work">The <see cref="WaterBucketWeb.Models.WorkOrder"/> passed from the Client determining how to perform the work of solving the <see cref="WaterBucket.Domain.Problem"/></param>
        /// <returns>Task resulting in <see cref="WaterBucket.Domain.SolutionResult"/>s for the problem for each <see cref="WaterBucket.Domain.ISolutionStrategy"/> employed</returns>
        protected async Task<SolutionResult[]> SolveAProblemAsync(Problem problemToSolve, WorkOrder work)//, int delayStart, int workDelay)
        {
            if (work.StartDelay > 0)
                Thread.Sleep(work.StartDelay);

            ISolutionStrategy smallToBig, bigToSmall;
            smallToBig = new SmallToBigSingleBucketSolutionStrategy(problemToSolve);
            bigToSmall = new BigToSmallSingleBucketSolutionStrategy(problemToSolve);
            var startSmall = from step in problemToSolve.Solve(smallToBig)
                             select step;
            //startSmall.ToObservable().Subscribe(ProblemActionStep, ProblemActionError, ProblemSolutionCompleted);
            // Example of doing all of the code on a single line
            //problemToSolve.Solve(new SmallToBigSingleBucketSolutionStrategy(problemToSolve)).ToObservable().Subscribe(ProblemActionStep, ProblemActionError, () => ProblemSolutionCompleted("Small to Big"));
            // Capture as primitive value type rather than rely on reference type for use in Tasks - was burned on this with the PublisherAddresses list
            bool yieldOnAction = work.YieldWeb;
            // Set up a Task to run the Observable in but don't wait on it yet
            var smallToBigTask = Task.Run(() => (work.WorkDelay > 0 ? startSmall.ToObservable().Do(_ => Thread.Sleep(work.WorkDelay)) : startSmall.ToObservable())
                .Subscribe(step => ProblemActionStep(step, yieldOnAction), ProblemActionError, () => ProblemSolutionCompleted("Small to Big")))
                .ContinueWith(t => 
                    {
                        if (t.IsCanceled)
                        {
                        }
                        else if (t.IsFaulted)
                        {
                        }
                        else if (t.IsCompleted)
                        {
                            t.Result.Dispose();
                            SignalSolution(smallToBig.StrategyName, smallToBig.Result);
                            return smallToBig.Result;
                        }
                        return null;
                    });
            var startBig = from step in problemToSolve.Solve(bigToSmall)
                            select step;
            //var startBig = (from step in problemToSolve.Solve(bigToSmall)
            //               select step).ToObservable();
            //if (workDelay > 0)
            //{
            //    var timer = Observable.Interval(TimeSpan.FromMilliseconds(workDelay));
            //    startBig = startBig.Zip(timer, (bs, _) => bs);
            //}
            // Set up a Task to run the Observable in but don't wait on it yet
            var bigToSmallTask = Task.Run(() => (work.WorkDelay > 0 ? startBig.ToObservable().Do(_ => Thread.Sleep(work.WorkDelay)) : startBig.ToObservable())
                .Subscribe(step => ProblemActionStep(step, yieldOnAction), ProblemActionError, () => ProblemSolutionCompleted("Big to Small")))
                .ContinueWith(t =>
                    {
                        if (t.IsCanceled)
                        {
                        }
                        else if (t.IsFaulted)
                        {
                        }
                        else if (t.IsCompleted)
                        {
                            t.Result.Dispose();
                            SignalSolution(bigToSmall.StrategyName, bigToSmall.Result);
                            return smallToBig.Result;
                        }
                        return null;
                    });
            // Wait for the Observable execution tasks to complete before finishing
            return await Task.WhenAll(smallToBigTask, bigToSmallTask);
        }
Esempio n. 2
0
        // Deprecated message for SignedUpdates with the theory of having a single ZmqSocket subscribe to all
        //  messages for a problem and then separate/parse them by which strategy the message is an update for 
        //  and pass that appropriately to the client(s)
        //protected IEnumerable<SignedProblemUpdate> GetProblemUpdates(ZmqSocket subscriber, string signature)
        //{
        //    if (subscriber == null)
        //        throw new ArgumentNullException("subscriber");

        //    SignedProblemUpdate update;
        //    do
        //    {
        //        update = null;
        //        // This is the blocking version of Receiving from a ZMQ Socket - this is okay since we will wrap this call in Rx
        //        ZmqMessage msg = subscriber.ReceiveMessage();
        //        // To decouple ProblemUpdate from the Transport Medium, only binary data is passed using byte[] instead of a ZeroMQ.Frame
        //        update = new SignedProblemUpdate(msg.Select(f => f.Buffer).ToArray());
        //        if (!signature.Equals(update.Signature))
        //            continue;

        //        if (update.IsAction || update.IsInitial)
        //            yield return update;
        //    } while ((update != null) && !(update.IsCompletion || update.IsError));

        //    if (update != null)
        //    {
        //        if (update.IsCompletion)
        //        {
        //            yield return update;
        //        }
        //        else if (update.IsError)
        //        {
        //            throw update.GetException<Exception>();
        //        }
        //    }

        //    yield break;
        //}

        /// <summary>
        /// Solve the Problem using a WorkerRole hosted in Azure PaaS Cloud asynchronously by queuing the problem in an Azure Storage CloudQueue and 
        /// listening on ZmqSockets for update messages from the Worker doing the work as the work is being done.
        /// <para>Based on a setting by the Client in the WorkOrder optionally use an Rx Observable sequences over the message stream to signal 
        /// updates to the Client(s)</para>
        /// </summary>
        /// <param name="problemToSolve"><see cref="WaterBucket.Domain.Problem"/> to be solved and calculate the results</param>
        /// <param name="work">The <see cref="WaterBucketWeb.Models.WorkOrder"/> passed from the Client determining how to perform the work of solving the <see cref="WaterBucket.Domain.Problem"/></param>
        /// <returns>Task that can be awaited on for work being completed by the Worker</returns>
        /// <seealso cref="Microsoft.WindowsAzure.Storage.Queue.CloudQueue"/>
        /// <seealso cref="ZeroMQ"/>
        protected async Task SolveProblemWithWorkerAsync(Problem problemToSolve, WorkOrder work)//, int delayStart, int workDelay)
        {
            // Calling this here will ensure that it is trying to find workers anytime it needs them
            // rather than prefetching PublisherAddresses, finding out they're not initialized and 
            // never being able to use workers after that
            // - fortunately or unfortunately, it doesn't matter in SignalR since Hubs are reconstructed 
            //   for every call to the Hub so the result if called in the ctor wouldn't be cached accross calls
            //InitPublisherAddresses();
            //if (!CanUseWorker)
            //{
            //    Clients.All.addMsg("Cannot use Worker for calculations, reason: " + NoWorkerReason);
            //    return;
            //}

            // Check whether the CloudQueue is for the staging or production environment, which use separate Queues in order to provide fully isolated environments
            bool isStaging = RoleEnvironmentExt.GetRoleConfigSetting("UseStaging", false);
            // Get the CloudQueue on which we want to put messages for the WorkerRoles to get their work
            string storageAccountConStr = !isStaging ? RoleEnvironment.GetConfigurationSettingValue("StorageAccount") : RoleEnvironment.GetConfigurationSettingValue("StagingStorageAccount");
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageAccountConStr);
            CloudQueueClient qClient = storageAccount.CreateCloudQueueClient();
            CloudQueue queue = qClient.GetQueueReference(CLOUD_Q_FOR_WORK);
            queue.CreateIfNotExists();

            try
            {
                ZmqContext ctx = ZmqContext.Create();
                //ZmqSocket socket = ctx.CreateSocket(SocketType.SUB);
                //socket.Connect(publishAddress);
                //List<ISolutionStrategy> strategies = new List<ISolutionStrategy>();
                //strategies.Add(new BigToSmallSingleBucketSolutionStrategy(problemToSolve));
                //strategies.Add(new SmallToBigSingleBucketSolutionStrategy(problemToSolve));
                //IObservable<SignedProblemUpdate> combinedUpdates = null;
                //foreach (var s in strategies)
                //{
                //    socket.Subscribe(Encoding.UTF8.GetBytes(s.Signature));
                //    if (combinedUpdates == null)
                //    {
                //        combinedUpdates = GetProblemUpdates(socket, s.Signature).ToObservable();
                //    }
                //    else
                //    {
                //        combinedUpdates = combinedUpdates.Union(GetProblemUpdates(socket, s.Signature).ToObservable());
                //    }
                //}
                ISolutionStrategy bigToSmall = new BigToSmallSingleBucketSolutionStrategy(problemToSolve);
                ISolutionStrategy smallToBig = new SmallToBigSingleBucketSolutionStrategy(problemToSolve);
                //socket.Subscribe(Encoding.UTF8.GetBytes(bigToSmall.Signature));
                //socket.Subscribe(Encoding.UTF8.GetBytes(smallToBig.Signature));

                //var combinedUpdates = (from u in GetProblemUpdates(socket, bigToSmall.Signature)
                //                       select u)
                //                       .Union(
                //                       from u in GetProblemUpdates(socket, smallToBig.Signature)
                //                       select u);
                //bool done = false;

                //using (combinedUpdates.ToObservable().Subscribe(
                //    update =>
                //    {
                //        if (update.IsAction || update.IsInitial)
                //        {
                //            ProblemActionStep(update.IntoType<BucketActionStep>().UpdateState);
                //        }
                //        else if (update.IsCompletion)
                //        {
                //            ISolutionStrategy signedStrategy = strategies.FirstOrDefault(s => update.Signature.Equals(s.Signature));
                //            if (signedStrategy != null)
                //            {
                //                var solnResult = update.IntoType<SolutionResult>().UpdateState;
                //                ProblemSolutionCompleted(signedStrategy.StrategyName);
                //                // Don't need this with the bool done
                //                signedStrategy.RemoteResult(solnResult);
                //                SignalSolution(signedStrategy.StrategyName, solnResult);
                //            }
                //            //if (bigToSmall.Signature.Equals(update.Signature))
                //            //{
                //            //    SignalSolution(bigToSmall.StrategyName, update.IntoType<SolutionResult>().UpdateState);
                //            //}
                //            //else if (smallToBig.Signature.Equals(update.Signature))
                //            //{
                //            //    SignalSolution(smallToBig.StrategyName, update.IntoType<SolutionResult>().UpdateState);
                //            //}
                //        }
                //        else if (update.IsError)
                //        {
                //            ProblemActionError(update.GetException<Exception>());
                //        }
                //    },
                //    ProblemActionError,
                //    () => { done = true; }
                //    ))
                //{
                //    byte[] qMsgBytes = new byte[12];
                //    Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.FirstBucket.Capacity), 0, qMsgBytes, 0, 4);
                //    Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.SecondBucket.Capacity), 0, qMsgBytes, 4, 4);
                //    Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.GoalWaterVolume), 0, qMsgBytes, 8, 4);
                //    CloudQueueMessage qMsg = new CloudQueueMessage(qMsgBytes);
                //    queue.AddMessage(qMsg);
                //    while (!done)
                //    {
                //        Thread.Sleep(500);
                //    }
                //}
                Task b2sWork, s2bWork;
                // Put the work.YieldWeb value in a local value type which is easier to pass to child Tasks
                bool yieldOnAction = work.YieldWeb;
                // The Client does NOT want to use a Rx Observable to wrap the message stream from the ZmqSocket
                if (!work.ObserverOnZmq)
                {
                    // Start tasks to listen for Worker updates on their own ZmqSockets per each SolutionStrategy that will be employed to solve the problem and signal Client(s) as they come in
                    b2sWork = Task.Run(() =>
                        {
                            try
                            {
                                foreach (var step in GetWorkerUpdates(ctx, bigToSmall, result => SignalSolution(bigToSmall.StrategyName, result)))
                                {
                                    ProblemActionStep(step, yieldOnAction);
                                }
                                ProblemSolutionCompleted("Big to Small");
                            }
                            catch (Exception ex)
                            {
                                ProblemActionError(ex);
                            }
                        });
                    s2bWork = Task.Run(() =>
                        {
                            try
                            {
                                foreach (var step in GetWorkerUpdates(ctx, smallToBig, result => SignalSolution(smallToBig.StrategyName, result)))
                                {
                                    ProblemActionStep(step, yieldOnAction);
                                }
                                ProblemSolutionCompleted("Small to Big");
                            }
                            catch (Exception ex)
                            {
                                ProblemActionError(ex);
                            }
                        });
                }
                else // The Client wants to use a Rx Observable to wrap the message stream from the ZmqSocket
                {
                    // First create a lazy evaluated Linq Sequence around getting ZmqSocket subscription messages for a specific SolutionStrategy
                    var b2sUpdates = from u in GetWorkerUpdates(ctx, bigToSmall, result => SignalSolution(bigToSmall.StrategyName, result))
                                     select u;
                    // Start a task in which a Rx Observable will wrap the message stream and signal the Client(s) when messages are received
                    b2sWork = Task.Run(() => b2sUpdates.ToObservable()
                        .Subscribe(step => ProblemActionStep(step, yieldOnAction), ProblemActionError, () => ProblemSolutionCompleted("Big to Small")))
                        .ContinueWith(t =>
                            {
                                if (t.IsCanceled)
                                {
                                    // TODO: Log this
                                }
                                else if (t.IsFaulted)
                                {
                                    // TODO: Log the Exception
                                }
                                else if (t.IsCompleted)
                                {
                                    t.Result.Dispose();
                                }
                            });
                    // Repeat the above steps for the other SolutionStrategy
                    var s2bUpdates = from u in GetWorkerUpdates(ctx, smallToBig, result => SignalSolution(smallToBig.StrategyName, result))
                                     select u;
                    s2bWork = Task.Run(() => s2bUpdates.ToObservable()
                        .Subscribe(step => ProblemActionStep(step, yieldOnAction), ProblemActionError, () => ProblemSolutionCompleted("Small to Big")))
                        .ContinueWith(t =>
                            {
                                if (t.IsFaulted)
                                {
                                    // TODO: Log the Exception
                                }
                                else if (t.IsCanceled)
                                {
                                    // TODO: Log this
                                }
                                else if (t.IsCompleted)
                                {
                                    t.Result.Dispose();
                                }
                            });
                }
                // To ensure the Subscriptions occur before any messages are published by the Worker, we could call Thread.Yield here
                // and we will get back control once those Tasks block on attempting to receive a message
                //Thread.Yield();
                // BUT - we want to see the effects of delays on publishing and the Zmq PUB-SUB messaging so we're not doing that now
                // Create the message to be put into the Azure Storage CloudQueue that is picked up by the Workers
                byte[] qMsgBytes = new byte[20];
                Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.FirstBucket.Capacity), 0, qMsgBytes, 0, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.SecondBucket.Capacity), 0, qMsgBytes, 4, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(problemToSolve.GoalWaterVolume), 0, qMsgBytes, 8, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(work.StartDelay), 0, qMsgBytes, 12, 4);
                // be able to signal to the worker the WorkDelay or whether it should Yield on each Action (workDelay == 0) or Not to Yield (workDelay < 0)
                int workDelay = work.WorkDelay > 0 ? work.WorkDelay : work.YieldWorker ? 0 : -1;
                Buffer.BlockCopy(BitConverter.GetBytes(workDelay), 0, qMsgBytes, 16, 4);
                CloudQueueMessage qMsg = new CloudQueueMessage(qMsgBytes);
                queue.AddMessage(qMsg);
                // Now wait for the Subscription tasks to complete
                await Task.WhenAll(b2sWork, s2bWork);
            }
            catch (Exception ex)
            {
                // TODO: Log any uncaught exception
                throw new Exception("Encountered Exception during WorkerAsync", ex);
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Get a message from the Client to Solve a given problem using the given work order
        /// </summary>
        /// <param name="problem"><see cref="WaterBucket.Domain.Problem"/> defining the Bucket and Goal Parameters to use in the calculation</param>
        /// <param name="work">WorkOrder parameters used during execution of the solution</param>
        public void Solve(ProblemVM problem, WorkOrder work)//, bool useWorker = false, int delayStart = -1, int workDelay = -1)
        {
            if (BroadcastAll)
            {
                Clients.All.problemSubmitted(ObservableOnJavaScript);
                Clients.Others.submission(problem, work.UseWorker);
            }
            else
            {
                Clients.Caller.problemSubmitted(ObservableOnJavaScript);
            }
            // Test Bucket Ranges here instead of at the browser so that updates made on the back end for thresholds are checked without requiring browser refresh
            bool end = false;
            int bigBucket = Math.Max(problem.FirstBucketCapacity, problem.SecondBucketCapacity);
            if (bigBucket > BucketMax)
            {
                if (BroadcastAll)
                {
                    Clients.All.outOfRange(bigBucket, true);
                }
                else
                {
                    Clients.Caller.outOfRange(bigBucket, true);
                }
                end = true;
            }
            int smallBucket = Math.Min(problem.FirstBucketCapacity, problem.SecondBucketCapacity);
            if (smallBucket < BucketMin)
            {
                if (BroadcastAll)
                {
                    Clients.All.outOfRange(smallBucket, false);
                }
                else
                {
                    Clients.Caller.outOfRange(smallBucket, false);
                }
                end = true;
            }
            if (end)
                return;
            // Check to see if the Problem exists in the Cache
            Problem problemo = HttpContext.Current.Cache[problem.CacheKey] as Problem;
            // If not, create a new Problem based on the parameters to solve and add it to the Cache
            if (problemo == null)
            {
                problemo = new Problem(problem.FirstBucketCapacity, problem.SecondBucketCapacity, problem.GoalWaterVolume);
                HttpContext.Current.Cache.Insert(problem.CacheKey, problemo);
            }
            // Notify Client(s) that the Problem is being started
            // ActionBindingThreshold tells the client browser whether to use knockout.js observableArray binding for displaying action step updates or
            // plain old DOM manipulation - ko.observableArray creates a big performance hit
            bool bindActions = bigBucket <= ActionBindingThreshold;
            if (BroadcastAll)
            {
                Clients.All.startedProblem(problem, bindActions && !work.UseWorker);
            }
            else
            {
                Clients.Caller.startedProblem(problem, bindActions && !work.UseWorker);
            }

            // Check to see if it is a solvable Problem
            if (!problemo.IsSolvable)
            {
                // If not a solvable problem then notify the clients and do not try to solve it
                if (BroadcastAll)
                {
                    Clients.All.notSolvable(problem);
                }
                else
                {
                    Clients.Caller.notSolvable(problem);
                }
            }
            else
            {
                // Try-Catch didn't work here to solve the problem I was having
                //try
                //{
                    var problemTasks = work.UseWorker ? SolveProblemWithWorkerAsync(problemo, work) : SolveAProblemAsync(problemo, work);
                //    problemTasks.ContinueWith(t =>
                //        {
                //            if (t.IsFaulted)
                //                Clients.All.errorInSolve(t.Exception);
                //        });
                //}
                //catch (Exception ex)
                //{
                //    Clients.All.errorInSolve(ex);
                //}
            }
        }