Exemple #1
0
        public static void SuspendResumeTrialFinal()
        {
            var scheduler = new TestScheduler();

            int      suspendOnCount       = 3;
            TimeSpan suspendCountDuration = TimeSpan.FromSeconds(5);
            TimeSpan suspendDuration      = TimeSpan.FromSeconds(15);

            var subject = new Subject <int>();
            var source  = subject.Timestamp(scheduler).Select(i => new MetaValue <int>(i.Value, i.Timestamp, Emit.Value));

            var head = source.Take(2);
            var tail = source
                       .Buffer(3, 1)
                       .Where(b => b.Count == 3)
                       .Select(b =>
            {
                var lastInBuffer  = b.Last();
                var firstInBuffer = b.First();

                var diff = lastInBuffer.Timestamp.Subtract(firstInBuffer.Timestamp);
                return(new MetaValue <int>(lastInBuffer.Value, lastInBuffer.Timestamp, diff <= suspendCountDuration ? Emit.Suspend : Emit.Value));
            });

            var rectified = tail.Scan(
                ScannedValueWithRectification <int> .Empty,
                (enrichedAccumulatedValue, currentMetaValue) =>
            {
                if (enrichedAccumulatedValue.StartOfSuspendWindow != DateTimeOffset.MinValue)
                {
                    var endOfSuspend = enrichedAccumulatedValue.StartOfSuspendWindow.Add(suspendDuration);
                    if (currentMetaValue.Timestamp < endOfSuspend)
                    {
                        return(new ScannedValueWithRectification <int>(
                                   null,
                                   enrichedAccumulatedValue.StartOfSuspendWindow,
                                   enrichedAccumulatedValue.RemainingNumberOfValuesToRectify));
                    }
                }

                if (enrichedAccumulatedValue.RemainingNumberOfValuesToRectify > 0)
                {
                    // Rectify the first N-1 values after a resume to Emit.Value
                    var rectifiedCurrent = new MetaValue <int>(currentMetaValue.Value, currentMetaValue.Timestamp, Emit.Value);
                    // Preserve the information of the suspend window until all necessary values have been rectified
                    return(new ScannedValueWithRectification <int>(
                               rectifiedCurrent,
                               enrichedAccumulatedValue.StartOfSuspendWindow,
                               enrichedAccumulatedValue.RemainingNumberOfValuesToRectify - 1));
                }

                return(currentMetaValue.Kind == Emit.Suspend
                        ? new ScannedValueWithRectification <int>(null, currentMetaValue.Timestamp, 2)
                        : new ScannedValueWithRectification <int>(currentMetaValue, DateTimeOffset.MinValue, 0));

                //Emit value
            })
                            .Where(ev => ev.MetaValue != null)
                            .Select(ev => ev.MetaValue);

            var prefinal = head.Merge(rectified);

            prefinal.Subscribe(Console.WriteLine);

            var final = prefinal.Select(m => m.Value);

            /**********************************************************************
            *
            * source --1---------2----------3-4-5-6-7--------8---------9------
            *
            **********************************************************************/

            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(1);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10000).Ticks);
            subject.OnNext(2);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(11000).Ticks);
            subject.OnNext(3);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(4);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1500).Ticks);
            subject.OnNext(5);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1250).Ticks);
            subject.OnNext(6);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2250).Ticks);
            subject.OnNext(7);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(9000).Ticks);
            subject.OnNext(8);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10000).Ticks);
            subject.OnNext(9);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(22000).Ticks);
            subject.OnNext(10);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(11);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(12);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(12000).Ticks);
            subject.OnNext(13);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1000).Ticks);
            subject.OnNext(14);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(15); //should be emitted outside of suspend
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1000).Ticks);
            subject.OnNext(16);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(2000).Ticks);
            subject.OnNext(17);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(10000).Ticks);
            subject.OnNext(18);
            scheduler.AdvanceBy(TimeSpan.FromMilliseconds(1).Ticks);
            subject.OnCompleted();
        }
Exemple #2
0
 public ScannedValue(MetaValue <T> metaValue, DateTimeOffset startOfSuspendWindow)
 {
     MetaValue            = metaValue;
     StartOfSuspendWindow = startOfSuspendWindow;
 }
        public static IObservable <T> SuspendDuringFloodWithRectification <T>(
            this IObservable <T> source,
            int maxElementsPerWindow,
            TimeSpan windowDuration,
            TimeSpan suspendDuration,
            IScheduler scheduler)
        {
            scheduler = scheduler ?? new TestScheduler();

            var internalSource = source.Timestamp(scheduler).Select(i => new MetaValue <T>(i.Value, i.Timestamp, Emit.Value));

            // The first max-1 elements will always be emitted (no need to enrich them)
            var head = source.Take(maxElementsPerWindow - 1);

            // Enrich the rest of the elements with information about the absolute time they were emitted (see ... below)
            var tail = internalSource
                       // Apply a buffer in order to determine if the last element in the buffer (the element of importance) is supposed to introduce a suspension
                       .Buffer(maxElementsPerWindow, 1)
                       // The last max-1 buffers are incomplete and can be safely ignored, because the last buffer of size max contains that last element in the observable
                       .Where(b => b.Count == maxElementsPerWindow)
                       .Select(b =>
            {
                var lastInBuffer  = b.Last();
                var firstInBuffer = b.First();

                var diff = lastInBuffer.Timestamp.Subtract(firstInBuffer.Timestamp);
                //... also enrich the value with information about whether or not it is supposed to introduce a suspension
                // (based on the time difference to the first element in the buffer)
                return(new MetaValue <T>(lastInBuffer.Value, lastInBuffer.Timestamp, diff <= windowDuration ? Emit.Suspend : Emit.Value));
            });

            // Run through the meta values produced and ignore the ones emitted during a suspend,
            // while rectifing (emitting) the ones wrongly identified as suspended (caused by them being in buffers with values that should have been ignored)
            var rectifiedTail = tail.Scan(
                ScannedValueWithRectification <T> .Empty,
                (accumulator, metaValue) =>
            {
                if (accumulator.StartOfSuspendWindow != DateTimeOffset.MinValue)
                {
                    var endOfSuspend = accumulator.StartOfSuspendWindow.Add(suspendDuration);
                    if (metaValue.Timestamp < endOfSuspend)         // Ignore (return null) values emitted during an ongoing suspend window
                    {
                        return(new ScannedValueWithRectification <T>(
                                   null,
                                   accumulator.StartOfSuspendWindow,
                                   accumulator.RemainingNumberOfValuesToRectify));
                    }
                }

                if (accumulator.RemainingNumberOfValuesToRectify > 0)
                {
                    // Rectify the first max-1 values after a resume to Emit.Value. There is a philosofical reason for this: if one value
                    // (for which there is always a buffer in which it is the last value) is deemed as introducing a suspension due
                    // to values from the original source that ended up being ignored because they were emitted during an active suspension,
                    // I don't find it logical for this value to still be considered as introducing a suspension. It should be considered as
                    // the 1st value emitted after a suspend, and because suspendDuration >> windowDuration it should always emit a value, same goes
                    // for all max-1 values that follow an end of a suspension interval.
                    var rectifiedCurrent = new MetaValue <T>(metaValue.Value, metaValue.Timestamp, Emit.Value);
                    // Preserve the information of the suspend window until all necessary values have been rectified
                    return(new ScannedValueWithRectification <T>(
                               rectifiedCurrent,
                               accumulator.StartOfSuspendWindow,
                               accumulator.RemainingNumberOfValuesToRectify - 1));
                }

                return(metaValue.Kind == Emit.Suspend
                            ? new ScannedValueWithRectification <T>(null, metaValue.Timestamp, maxElementsPerWindow - 1) // Begin a suspend window without emitting the value that triggered it
                            : new ScannedValueWithRectification <T>(metaValue, DateTimeOffset.MinValue, 0));             // Emit a value
            })
                                .Where(sv => sv.MetaValue != null)                                                       // This is the part that effectively actually ignores the values under a suspend window
                                .Select(ev => ev.MetaValue.Value);

            return(head.Merge(rectifiedTail));
        }
Exemple #4
0
 public ScannedValueWithRectification(MetaValue <T> metaValue, DateTimeOffset startOfSuspendWindow, int remainingNumberOfValuesToRectify)
     : base(metaValue, startOfSuspendWindow)
 {
     RemainingNumberOfValuesToRectify = remainingNumberOfValuesToRectify;
 }