Beispiel #1
0
        private void ScheduleScriptRuns()
        {
            if (_scheduleTrigger.WindowCount == 0 || SensusServiceHelper.Get() == null || Probe == null || !Probe.Protocol.Running || !_enabled)
            {
                return;
            }

            // get trigger times with respect to the current time occurring after the maximum previously scheduled trigger time.
            foreach (ScriptTriggerTime triggerTime in _scheduleTrigger.GetTriggerTimes(DateTime.Now, _maxScheduledDate.Max(DateTime.Now), _maxAge))
            {
                // don't schedule scripts past the end of the protocol if there's a scheduled end date.
                if (!Probe.Protocol.ContinueIndefinitely && triggerTime.Trigger > Probe.Protocol.EndDate)
                {
                    break;
                }

                // we should always allow at least one future script to be scheduled. this is why the _scheduledCallbackIds collection
                // is a member of the current instance and not global within the script probe. beyond this single scheduled script,
                // only allow a maximum of 32 script-run callbacks to be scheduled. android's limit is 500, and ios 9 has a limit of 64.
                // not sure about ios 10+. as long as we have just a few script runners, each one will be able to schedule a few future
                // script runs. this will help mitigate the problem of users ignoring surveys and losing touch with the study.
                lock (_scriptRunCallbacks)
                {
                    if (_scriptRunCallbacks.Count > 32 / Probe.ScriptRunners.Count)
                    {
                        break;
                    }
                }

                ScheduleScriptRun(triggerTime);
            }
        }
Beispiel #2
0
        private void ScheduleCallbacks()
        {
            if (_scheduleTrigger.WindowCount == 0 || SensusServiceHelper.Get() == null || Probe == null || !Probe.Protocol.Running || !_enabled)
            {
                return;
            }

            foreach (var triggerTime in _scheduleTrigger.GetTriggerTimes(DateTime.Now, _maxScheduledDate.Max(DateTime.Now), _maxAge))
            {
                if (!Probe.Protocol.ContinueIndefinitely && triggerTime.Trigger > Probe.Protocol.EndDate)
                {
                    break;
                }

                if (_scheduledCallbackIds.Count > 32 / Probe.ScriptRunners.Count)
                {
                    break;
                }

                ScheduleScriptRun(triggerTime);
            }
        }
Beispiel #3
0
        public async Task ScheduleScriptRunsAsync()
        {
            if (_scheduleTrigger.WindowCount == 0 ||               // there are no windows to schedule
                SensusServiceHelper.Get() == null ||               // the service helper hasn't loaded
                Probe == null ||                                   // there is no probe
                Probe.Protocol.State == ProtocolState.Stopped ||   // protocol has stopped
                Probe.Protocol.State == ProtocolState.Stopping ||  // protocol is about to stop
                !_enabled)                                         // script is disabled
            {
                return;
            }

            // clean up scheduled callback times
            List <ScriptTriggerTime> callbackTimesToReschedule = new List <ScriptTriggerTime>();

            lock (_scheduledCallbackTimes)
            {
                // remove any callbacks whose times have passed. be sure to use the callback's next execution time, as this may differ
                // from the script trigger time when callback batching is enabled. for example, if the callback permit delay tolerance
                // prior to the trigger time, then the scheduled callback next execution time may precede the trigger time.
                _scheduledCallbackTimes.RemoveAll(scriptRunCallback => scriptRunCallback.Item1.NextExecution.GetValueOrDefault(DateTime.MinValue) < DateTime.Now);

                // get future callbacks that need to be rescheduled. it can happen that the app crashes or is killed
                // and then resumes (e.g., on ios push notification), and it's important for the times of the scheduled
                // callbacks to be maintained. this is particularly important for scheduled times that are very far out
                // in the future. if the app is restarted more often than the script is run, then the script will likely
                // never be run.
                foreach (Tuple <ScheduledCallback, ScriptTriggerTime> callbackTime in _scheduledCallbackTimes.ToList())
                {
                    if (!SensusContext.Current.CallbackScheduler.ContainsCallback(callbackTime.Item1))
                    {
                        // it's correct to reference the trigger time rather than the callback time, as the former
                        // is what should be used in callback batching.
                        callbackTimesToReschedule.Add(callbackTime.Item2);

                        // we're about to reschedule the callback. remove the old one so we don't have duplicates.
                        _scheduledCallbackTimes.Remove(callbackTime);
                    }
                }
            }

            // reschedule script runs as needed
            foreach (ScriptTriggerTime callbackTimeToReschedule in callbackTimesToReschedule)
            {
                await ScheduleScriptRunAsync(callbackTimeToReschedule);
            }

            // abort if we already have enough runs scheduled in the future. only allow a maximum of 32 script-run callbacks
            // to be scheduled app-wide leaving room for other callbacks (e.g., the storage and polling systems). android's
            // app-level limit is 500, and ios 9 has a limit of 64. not sure about ios 10+.
            int scriptRunCallbacksForThisRunner = (32 / Probe.ScriptRunners.Count);

            scriptRunCallbacksForThisRunner = Math.Max(scriptRunCallbacksForThisRunner, 1);  // schedule at least 1 run, regardless of the os cap.
            scriptRunCallbacksForThisRunner = Math.Min(scriptRunCallbacksForThisRunner, 3);  // cap the runs, as each one takes some time to schedule.
            lock (_scheduledCallbackTimes)
            {
                if (_scheduledCallbackTimes.Count >= scriptRunCallbacksForThisRunner)
                {
                    return;
                }
            }

            // get all trigger times starting from today
            foreach (ScriptTriggerTime triggerTime in _scheduleTrigger.GetTriggerTimes(DateTime.Now, _maxAge))
            {
                // schedule all future runs, except those beyond the protocol's end date (if there is one). need to check
                // that they're in the future because GetTriggerTimes will return all times starting from 0:00 of the current
                // day.
                if (triggerTime.Trigger > DateTime.Now && (Probe.Protocol.ContinueIndefinitely || triggerTime.Trigger < Probe.Protocol.EndDate))
                {
                    await ScheduleScriptRunAsync(triggerTime);

                    // stop when we have scheduled enough runs
                    lock (_scheduledCallbackTimes)
                    {
                        if (_scheduledCallbackTimes.Count >= scriptRunCallbacksForThisRunner)
                        {
                            break;
                        }
                    }
                }
            }

            // save the app state to retain the callback schedule in case the app terminates
            await SensusServiceHelper.Get().SaveAsync();
        }