public void BasicSchedulerTest( [Values( 0, 1, 3, 5, 6, 7, 8, 9, 10, 150 )] int sameFeedHitIntervalSeconds ) { ForeignAccessCentral.LocationRequestFactories["test"] = new MockForeignFactory( ) { MaxCallsInPackCount = 2, MinTimeFromPrevStartMs = 2000, MinCallsGapMs = 0, SameFeedHitIntervalSeconds = sameFeedHitIntervalSeconds }; Scheduler scheduler = new Scheduler( ); /* * time: 09876543210 * A: @.....[]... * B: ...@O...... * * Legend: * '[' call start * ']' succ call end * 'O' call started and succ.finished on the same second */ { TrackerStateHolder holder = GetHolder( "A", Now( -10 ) ); holder.RequestStartTime = Now( -4 ); holder.RefreshTime = Now( -3 ); scheduler.Trackers.Add( holder.ForeignId, holder ); } { TrackerStateHolder holder = GetHolder( "B", Now( -7 ) ); holder.RequestStartTime = Now( -6 ); holder.RefreshTime = Now( -6 ); scheduler.Trackers.Add( holder.ForeignId, holder ); } int maxSchedulerSleep = ( int ) ( Scheduler.MaxSleepTimeSpan.TotalSeconds ); int realTimeToWait = Math.Max( 0, sameFeedHitIntervalSeconds - 6 ); int expectedSecondsWait = Math.Min( maxSchedulerSleep, realTimeToWait ); var mockWaitHandle = new MockWaitHandle( expectedSecondsWait * 1000, false ); TimeService.DebugReplacement = ( ) => this.now; IEnumerable<TrackerStateHolder> trackersToRequest = scheduler.ScheduleCleanupWait( mockWaitHandle ); Assert.IsTrue( mockWaitHandle.IsWaitSucceeded ); if ( realTimeToWait <= maxSchedulerSleep ) { Assert.AreEqual( 1, trackersToRequest.Count( ) ); Assert.AreEqual( "B", trackersToRequest.First( ).ForeignId.Id ); } else { Assert.AreEqual( 0, trackersToRequest.Count( ) ); } }
/// <summary> /// <para>@ add</para> /// <para>[ start</para> /// <para>] end</para> /// <para># add + start</para> /// <para>$ add + start + end</para> /// <para>% start + end</para> /// </summary> private void ParseAndTest( int realTimeToWait, params string[] lines) { int maxSchedulerSleep = ( int ) ( Scheduler.MaxSleepTimeSpan.TotalSeconds ); TimeService.DebugReplacement = ( ) => this.now; Scheduler scheduler = new Scheduler( ); if ( this.timeoutSpanInSeconds > 0 ) scheduler.TimeoutSpanInSeconds = this.timeoutSpanInSeconds; int? iTimeLineLength = null; // for a parameter i, returns time corresponding to the point in the timeline // e.g. '@' in "...@." corresponds to (this.now - 1 sec). Func<int, DateTime> getTimeFunc = i => // ReSharper disable once AccessToModifiedClosure // ReSharper disable once PossibleInvalidOperationException Now( i - iTimeLineLength.Value + 1 ); string winningName = null; foreach ( string line in lines ) { if ( line.Trim( ).ToLower( ).StartsWith( "time:" ) ) continue; // line started from "time:" is kind of a comment, ignoring that int colIndex = line.IndexOf( ':' ); if ( colIndex <= 0 ) throw new Exception( "Cannot find name for: " + line ); string name = line .Remove( colIndex ) .Replace( '*', ' ' ) .Trim( ); string timeLine = line.Substring( colIndex + 1 ).ToLower( ); ForeignId id = GetForeignId( name ); TrackerStateHolder holder; // might be tracker was added by the prev.line: scheduler.Trackers.TryGetValue( id, out holder ); if ( iTimeLineLength == null ) iTimeLineLength = timeLine.Length; else if ( iTimeLineLength != timeLine.Length ) throw new Exception( "Timelines have different lengths" ); for ( int i = 0; i < timeLine.Length; i++ ) { char c = timeLine[i]; // @ add // [ call start // ] call end // s scheduled start if ( c == '@' ) { if ( holder != null ) throw new Exception( "Only one '@' per tracker can be found, and it should be first for the tracker: " + id.Id ); holder = GetHolder( id, getTimeFunc( i ) ); scheduler.Trackers.Add( id, holder ); } if ( c == '[' ) // holder should be created by now by '@' encountered earlier, otherwise it's NullRefException holder.RequestStartTime = getTimeFunc( i ); if ( c == ']' ) { holder.RefreshTime = getTimeFunc( i ); holder.Snapshot = CreateMockupSnapshot( holder.RefreshTime.Value ); } if ( c == 's' ) holder.ScheduledTime = getTimeFunc( i ); } if ( line.Contains( "*" ) ) { if ( winningName != null ) throw new Exception( "'*' sign (for a winning tracker) specified more than once" ); winningName = name; } if ( holder.CurrentRequest == null && holder.RequestStartTime.HasValue && ( holder.RefreshTime == null || holder.RequestStartTime.Value > holder.RefreshTime.Value ) ) { // just any request to make the field not-null: var originalReplacement = TimeService.DebugReplacement; try { TimeService.DebugReplacement = ( ) => holder.RequestStartTime.Value; RequestParams requestParams = default( RequestParams ); requestParams.Id = id.Id; holder.CurrentRequest = new FakeLocationRequest( requestParams ); } finally { TimeService.DebugReplacement = originalReplacement; } } else { // need else because it could be set earlier by one of the prev. lines. holder.CurrentRequest = null; } Assert.AreEqual( null, holder.CheckTimesConsistency( ) ); } int expectedSecondsWait = Math.Min( maxSchedulerSleep, realTimeToWait ); var mockWaitHandle = new MockWaitHandle( expectedSecondsWait * 1000, false ); IEnumerable<TrackerStateHolder> trackersToRequest = scheduler.ScheduleCleanupWait( mockWaitHandle ); Assert.IsTrue( mockWaitHandle.IsWaitSucceeded ); if ( realTimeToWait <= maxSchedulerSleep ) { Assert.AreEqual( 1, trackersToRequest.Count( ) ); Assert.AreEqual( winningName, trackersToRequest.First( ).ForeignId.Id ); } else { Assert.AreEqual( 0, trackersToRequest.Count( ) ); } }
/// <summary>Constructor is private to make the instance accessible only via the <see cref="Singleton"/> field.</summary> private ForeignRequestsManager( string revisionFilePath ) { try { InitRevisionPersister( revisionFilePath ); AdminAlerts["Scheduler"] = "New"; this.scheduler = new RequestsSchedule.Scheduler( ); this.statistics = this.scheduler.Statistics; this.refreshThread = new Thread( RefreshThreadWorker ) { Name = "LocWorker", IsBackground = true }; this.refreshThread.Start( ); // it waits until refreshThreadEvent set. this.refreshThreadEvent.Set( ); } catch ( Exception exc ) { Log.Error( "Error on starting-up", exc ); throw; } }