Remove debug statements from KCal backends.
[beagle.git] / Util / Scheduler.cs
blob6b0424af727feffc1fb453691c57c10d5d9887a7
1 //
2 // Scheduler.cs
3 //
4 // Copyright (C) 2004-2005 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.Text;
30 using System.Threading;
31 using System.Xml;
32 using System.Xml.Serialization;
34 namespace Beagle.Util {
36 public class Scheduler {
38 // Fire an event if there are no tasks left to execute.
39 public delegate void EmptyQueueDelegate ();
40 public event EmptyQueueDelegate EmptyQueueEvent;
42 public static bool Debug = false;
44 public enum Priority {
46 Shutdown = 0, // Do it on shutdown
48 Idle = 1, // Execute only when the whole machine is idle
49 // Probably should be reserved for computationally-expensive stuff
50 // FIXME: These are not properly scheduled right now
52 Maintenance = 2, // Only execute when there are no lower-priority
53 // tasks from the same source to execute instead
55 Delayed = 3, // Do it soon
57 Immediate = 4, // Do it right now
60 public delegate void Hook ();
61 public delegate void TaskHook (Task task);
63 //////////////////////////////////////////////////////////////////////////////
65 public abstract class Task : IComparable {
67 private string tag = null;
68 private Priority priority = Priority.Delayed;
69 private int sub_priority = 0;
70 private DateTime trigger_time = DateTime.MinValue;
71 private DateTime timestamp; // when added to the scheduler
73 // Some metadata
74 public string Creator;
75 public string Description;
77 public object Source = null; // this is just an opaque identifier
79 public ITaskCollector Collector = null;
80 public double Weight = 1.0;
82 public bool Reschedule = false;
84 private ArrayList task_groups = null;
85 private TaskGroupPrivate child_task_group = null;
87 ///////////////////////////////
90 // The tag is the task's unique identifier
91 public string Tag {
93 get { return tag; }
95 set {
96 // Don't allow us to change the tag of a scheduled task
97 if (tag == null || scheduler == null)
98 tag = value;
99 else
100 throw new Exception ("Can't change tag of " + tag + "!");
104 public Priority Priority {
106 get { return priority; }
108 set {
109 if (priority != value) {
110 priority = value;
111 Recompute ();
116 public int SubPriority {
118 get { return sub_priority; }
120 set {
121 if (sub_priority != value) {
122 sub_priority = value;
123 Recompute ();
128 public DateTime TriggerTime {
130 get { return trigger_time; }
132 set {
133 if (trigger_time != value) {
134 trigger_time = value;
135 Recompute ();
140 public DateTime Timestamp {
141 get { return timestamp; }
144 ///////////////////////////////
146 public void AddTaskGroup (TaskGroup group)
148 if (task_groups == null)
149 task_groups = new ArrayList ();
150 task_groups.Add (group);
153 private void IncrementAllTaskGroups ()
155 if (task_groups != null) {
156 foreach (TaskGroupPrivate group in task_groups) {
157 if (! group.Finished)
158 group.Increment ();
163 private void DecrementAllTaskGroups ()
165 if (task_groups != null) {
166 foreach (TaskGroupPrivate group in task_groups) {
167 if (! group.Finished)
168 group.Decrement ();
173 private void TouchAllTaskGroups ()
175 if (task_groups != null) {
176 foreach (TaskGroupPrivate group in task_groups) {
177 if (! group.Finished)
178 group.Touch ();
183 ///////////////////////////////
185 private Scheduler scheduler = null;
187 public void Schedule (Scheduler scheduler)
189 // Increment the task groups the first
190 // time a task is scheduled.
191 if (this.scheduler == null)
192 IncrementAllTaskGroups ();
193 this.timestamp = DateTime.Now;
194 this.scheduler = scheduler;
195 this.cancelled = false;
198 private void Recompute ()
200 if (scheduler != null)
201 scheduler.Recompute ();
204 ///////////////////////////////
206 // A blocked task will not execute.
207 public bool Blocked {
208 get {
209 // Block the task if we have unexecuted children
210 return child_task_group != null && ! child_task_group.Finished;
214 ///////////////////////////////
216 private bool cancelled = false;
218 public bool Cancelled {
219 get { return cancelled; }
222 public void Cancel ()
224 if (! cancelled) {
225 DecrementAllTaskGroups ();
226 Cleanup (); // clean up after cancelled tasks
228 cancelled = true;
231 ///////////////////////////////
233 // The Task's count keeps track of how many
234 // times it has been executed.
236 private int count = 0;
238 public int Count {
239 get { return count; }
242 ///////////////////////////////
244 public void SpawnChild (Task child_task)
246 if (child_task_group == null)
247 child_task_group = new TaskGroupPrivate ("Children of " + Tag, null, null);
248 child_task.AddTaskGroup (child_task_group);
249 child_task.Source = this.Source;
250 scheduler.Add (child_task);
253 ///////////////////////////////
255 public void DoTask ()
257 if (! cancelled) {
258 if (Debug)
259 Logger.Log.Debug ("Starting task {0}", Tag);
260 child_task_group = null;
261 Reschedule = false;
262 TouchAllTaskGroups ();
264 Stopwatch sw = new Stopwatch ();
265 sw.Start ();
267 try {
268 DoTaskReal ();
269 } catch (Exception ex) {
270 Logger.Log.Warn (ex,
271 "Caught exception in DoTaskReal\n" +
272 " Tag: {0}\n" +
273 " Creator: {0}\n" +
274 "Description: {0}\n" +
275 " Priority: {0} ({1})",
276 Tag, Creator, Description, Priority, SubPriority);
278 sw.Stop ();
279 if (Debug)
280 Logger.Log.Debug ("Finished task {0} in {1}", Tag, sw);
282 if (Reschedule) {
283 ++count;
284 scheduler.Add (this); // re-add ourselves
285 } else {
286 DecrementAllTaskGroups ();
287 scheduler = null;
292 protected abstract void DoTaskReal ();
294 ///////////////////////////////
296 // Clean-up is called whenever we know that a task will never
297 // be executed. It is never called on tasks for who DoTaskReal
298 // has been called (except when rescheduled). Cleanup is also
299 // called when a task is cancelled.
301 public void Cleanup ()
303 try {
304 DoCleanup ();
305 } catch (Exception ex) {
306 Logger.Log.Warn (ex, "Caught exception cleaning up task '{0}'", Tag);
310 protected virtual void DoCleanup ()
312 // Do nothing by default
315 ///////////////////////////////
317 // Sort from lowest to highest priority
318 // FIXME: This does not define a total ordering
319 // on the set of all tasks, so use it with care.
320 public int CompareTo (object obj)
322 Task other = obj as Task;
323 if (other == null)
324 return 1;
326 Priority this_priority;
327 Priority other_priority;
329 this_priority = this.Priority;
330 other_priority = other.Priority;
332 // To other sources, Maintenance tasks looks like
333 // Delayed tasks.
334 if (this.Source != other.Source) {
335 if (this_priority == Priority.Maintenance)
336 this_priority = Priority.Delayed;
337 if (other_priority == Priority.Maintenance)
338 other_priority = Priority.Delayed;
341 int cmp;
342 cmp = (int)this_priority - (int)other_priority;
343 if (cmp != 0)
344 return cmp;
346 cmp = this.SubPriority - other.SubPriority;
347 if (cmp != 0)
348 return cmp;
350 // Tasks that were added to the scheduler earlier take
351 // precedence over those that were added later.
352 cmp = DateTime.Compare (other.Timestamp, this.Timestamp);
353 if (cmp != 0)
354 return cmp;
356 // Try to break any ties
357 return this.GetHashCode () - other.GetHashCode ();
360 public void AppendToStringBuilder (StringBuilder sb)
362 sb.Append (Priority).Append (' ').Append (SubPriority);
363 sb.Append (" (").Append (Timestamp).Append (")\n");
365 sb.Append (Tag).Append ('\n');
367 double t = (TriggerTime - DateTime.Now).TotalSeconds;
368 if (t > 0) {
369 if (t < 120)
370 sb.AppendFormat ("Hold for {0:0.00} seconds\n", t);
371 else {
372 sb.Append ("Hold until ").Append (TriggerTime);
373 sb.Append ('\n');
377 if (Creator != null)
378 sb.Append ("Creator: ").Append (Creator).Append ('\n');
380 if (Description != null)
381 sb.Append (Description).Append ('\n');
385 private class TaskHookWrapper : Task {
387 TaskHook hook;
389 public TaskHookWrapper (TaskHook hook)
391 this.hook = hook;
394 protected override void DoTaskReal ()
396 if (hook != null)
397 hook (this);
401 public static Task TaskFromHook (TaskHook hook)
403 return new TaskHookWrapper (hook);
406 //////////////////////////////////////////////////////////////////////////////
409 // Task Groups
412 public static TaskGroup NewTaskGroup (string name, Hook pre_hook, Hook post_hook)
414 return new TaskGroupPrivate (name, pre_hook, post_hook);
417 // The TaskGroup we hand back to the user is an interface that
418 // exposes minimal functionality.
419 public interface TaskGroup {
420 string Name { get; }
421 bool Finished { get; }
424 private class TaskGroupPrivate : TaskGroup {
425 private string name;
426 private int task_count = 0;
427 private bool touched = false;
428 private bool finished = false;
429 private Hook pre_hook;
430 private Hook post_hook;
432 public TaskGroupPrivate (string name,
433 Hook pre_hook,
434 Hook post_hook)
436 this.name = name;
437 this.pre_hook = pre_hook;
438 this.post_hook = post_hook;
441 public string Name {
442 get { return name; }
445 public bool Finished {
446 get { return finished; }
449 // Call this when a task is added to the task group.
450 public void Increment ()
452 if (finished)
453 throw new Exception ("Tried to increment a finished TaskGroup");
454 ++task_count;
457 // Call this when we execute a task in the task group.
458 public void Touch ()
460 if (finished)
461 throw new Exception ("Tried to touch a finished TaskGroup");
463 if (! touched) {
464 if (pre_hook != null) {
465 try {
466 pre_hook ();
467 } catch (Exception ex) {
468 Logger.Log.Warn (ex, "Caught exception in pre_hook of task group '{0}'", Name);
471 touched = true;
475 // Call this after a task in the task group is complete.
476 public void Decrement ()
478 if (finished)
479 throw new Exception ("Tried to decrement a finished TaskGroup");
481 --task_count;
482 // Only fire our post-hook if the pre-hook fired
483 // (or would have fired, had it been non-null)
484 if (task_count == 0 && touched) {
485 if (post_hook != null) {
486 try {
487 post_hook ();
488 } catch (Exception ex) {
489 Logger.Log.Warn (ex, "Caught exception in post_hook of task group '{0}'", Name);
492 finished = true;
497 //////////////////////////////////////////////////////////////////////////////
500 // Task Collector
502 // This is a mechanism for executing tasks in sets, possibly outside of
503 // priority order.
506 public interface ITaskCollector {
508 double GetMaximumWeight ();
510 void PreTaskHook ();
511 void PostTaskHook ();
514 //////////////////////////////////////////////////////////////////////////////
516 private static double global_delay = -1.0;
518 static Scheduler ()
520 string exercise;
521 exercise = Environment.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG");
523 if (exercise != null) {
524 if (exercise.Length > 2 && exercise [0] == 't')
525 global_delay = Double.Parse (exercise.Substring (1));
526 else
527 global_delay = 0.0;
531 //////////////////////////////////////////////////////////////////////////////
533 private static Scheduler global = new Scheduler ();
535 public static Scheduler Global {
536 get { return global; }
539 //////////////////////////////////////////////////////////////////////////////
541 private object big_lock = new object ();
543 // FIXME: shutdown tasks should probably be ordered by something
544 private Queue shutdown_task_queue = new Queue ();
546 private Hashtable tasks_by_tag = new Hashtable ();
547 private int total_executed_task_count = 0;
549 public void Add (Task task)
551 if (task == null)
552 return;
554 if (task.Source == null)
555 throw new Exception ("Attempting to add Task with no source!");
557 Task old_task = null;
559 lock (big_lock) {
561 // Keep track of when immediate priority tasks are
562 // added so that we can throttle if the scheduler
563 // is being slammed with them.
564 if (task.Priority == Priority.Immediate) {
565 // Shift our times down by one
566 Array.Copy (last_immediate_times, 1, last_immediate_times, 0, 4);
567 last_immediate_times [4] = DateTime.Now;
570 old_task = tasks_by_tag [task.Tag] as Task;
572 task.Schedule (this);
574 // Re-adding the same task is basically a no-op --- we
575 // just update the timestamp and return.
576 if (old_task == task)
577 return;
579 if (Debug) {
580 Logger.Log.Debug ("Adding task");
581 Logger.Log.Debug ("Tag: {0}", task.Tag);
582 if (task.Description != null)
583 Logger.Log.Debug ("Desc: {0}", task.Description);
586 if (task.Priority == Priority.Shutdown)
587 shutdown_task_queue.Enqueue (task);
588 else
589 tasks_by_tag [task.Tag] = task;
591 Monitor.Pulse (big_lock);
594 // If we clobbered another task, call cancel on it.
595 // This happens after we release the lock, since
596 // cancellation could result in a task group post-hook
597 // being run.
598 if (old_task != null)
599 old_task.Cancel ();
602 public Task GetByTag (string tag)
604 lock (big_lock)
605 return tasks_by_tag [tag] as Task;
608 public bool ContainsByTag (string tag)
610 Task task = GetByTag (tag);
611 return task != null && !task.Cancelled;
614 public void Recompute ()
616 lock (big_lock)
617 Monitor.Pulse (big_lock);
620 //////////////////////////////////////////////////////////////////////////////
622 private Thread thread = null;
623 public bool running = false;
624 private static bool shutdown_requested = false;
626 public void Start ()
628 lock (this) {
629 if (shutdown_requested || thread != null)
630 return;
631 running = true;
632 thread = ExceptionHandlingThread.Start (new ThreadStart (Worker));
636 public void Stop (bool to_shutdown)
638 lock (big_lock) {
639 shutdown_requested = to_shutdown;
641 if (running) {
642 running = false;
643 thread = null;
644 status_str = "Stopped";
645 Monitor.Pulse (big_lock);
650 public void Stop ()
652 Stop (false);
656 // Delay Computations
658 // This code controls how we space out tasks
661 // FIXME: random magic constants
662 const double idle_threshold = 5.314159 * 60; // probably should be longer
663 const double idle_ramp_up_time = 5.271828 * 60; // probably should be longer
664 const double default_delayed_rate_factor = 9.03; // work about 1/10th of the time
665 const double default_idle_rate_factor = 2.097; // work about 1/3rd of the time
666 const double maximum_delay = 20; // never wait for more than 20s
667 const double min_throttled_delay = 1.5; // never wait less than this when throttled
668 const double min_overloaded_delay = 2.2; // never wait less than this when there are many tasks
669 const int task_overload_threshold = 60; // number of tasks to process before delaying
671 DateTime[] last_immediate_times = new DateTime [5];
673 // The return value and duration_of_previous_task are both measured in seconds.
674 private double ComputeDelay (Priority priority_of_next_task,
675 double duration_of_previous_task,
676 int executed_task_count)
678 if (global_delay >= 0.0)
679 return global_delay;
681 double rate_factor;
683 rate_factor = 2.0;
685 // Do everything faster the longer we are idle.
686 double idle_time = SystemInformation.InputIdleTime;
687 double idle_scale = 1.0;
688 bool is_idle = false;
689 bool need_throttle = false;
691 // Never speed up if we are using the battery.
692 if (idle_time > idle_threshold && ! SystemInformation.UsingBattery) {
693 is_idle = true;
694 double t = (idle_time - idle_threshold) / idle_ramp_up_time;
695 idle_scale = (1 - Math.Min (t, 1.0));
698 switch (priority_of_next_task) {
700 case Priority.Immediate:
701 rate_factor = 0;
703 if (last_immediate_times [0] != DateTime.MinValue) {
704 TimeSpan last_add_delta = DateTime.Now.Subtract (last_immediate_times [4]);
706 // If less than a second has gone by since the
707 // last immediate task was added, there is
708 // still a torrent of events coming in, and we
709 // may need to throttle.
710 if (last_add_delta.TotalSeconds <= 1) {
711 TimeSpan between_add_delta = last_immediate_times [4].Subtract (last_immediate_times [0]);
713 // At least 5 immediate tasks have been
714 // added in the last second. We
715 // definitely need to throttle.
716 if (between_add_delta.TotalSeconds <= 1) {
717 need_throttle = true;
718 rate_factor = idle_scale * default_idle_rate_factor;
723 // If we've processed many tasks since the last
724 // time we took a break, ignore the priority and set a
725 // delay equivalent to Priority.Delayed.
726 if (!is_idle && executed_task_count >= task_overload_threshold)
727 rate_factor = idle_scale * default_delayed_rate_factor;
729 break;
731 case Priority.Delayed:
732 rate_factor = idle_scale * default_delayed_rate_factor;
733 break;
735 case Priority.Idle:
736 rate_factor = idle_scale * default_idle_rate_factor;
737 break;
741 // FIXME: we should do something more sophisticated than this
742 // with the load average.
743 // Random numbers galore!
744 double load_average = SystemInformation.LoadAverageOneMinute;
745 if (load_average > 3.001)
746 rate_factor *= 5.002;
747 else if (load_average > 1.5003)
748 rate_factor *= 2.004;
750 double delay = rate_factor * duration_of_previous_task;
752 // space out delayed tasks a bit when we aren't idle
753 if (! is_idle
754 && priority_of_next_task == Priority.Delayed
755 && delay < 0.5)
756 delay = 0.5;
758 if (delay > maximum_delay)
759 delay = maximum_delay;
761 // If we need to throttle, make sure we don't delay less than
762 // a second and some.
763 if (need_throttle && delay < min_throttled_delay)
764 delay = min_throttled_delay;
766 // If we're not idle and we've just processed more
767 // than a certain number of events, take a break.
768 if (! is_idle
769 && executed_task_count >= task_overload_threshold
770 && delay < min_overloaded_delay)
771 delay = min_overloaded_delay;
773 return delay;
777 // The main loop
780 // A convenience function. There should be a
781 // constructor to TimeSpan that does this.
782 private static TimeSpan TimeSpanFromSeconds (double t)
784 // Wait barfs if you hand it a negative TimeSpan,
785 // so we are paranoid;
786 if (t < 0.001)
787 t = 0;
789 // 1 tick = 100 nanoseconds
790 long ticks = (long) (t * 1.0e+7);
791 return new TimeSpan (ticks);
794 private string status_str = null;
796 private void Worker ()
798 DateTime end_time_of_previous_task = DateTime.MinValue;
799 double duration_of_previous_task = 0.0;
801 Hook pre_hook = null;
802 Hook post_hook = null;
803 ArrayList to_be_executed = new ArrayList ();
804 Hashtable max_priority_by_source = new Hashtable ();
805 int executed_task_count = 0;
806 StringBuilder status_builder = new StringBuilder ();
808 while (running) {
810 status_str = "Finding next task to execute";
812 lock (big_lock) {
814 // If there are no pending tasks, wait
815 // on our lock and then re-start our
816 // while loop
817 if (tasks_by_tag.Count == 0) {
818 if (EmptyQueueEvent != null)
819 EmptyQueueEvent ();
820 status_str = "Waiting on empty queue";
821 Monitor.Wait (big_lock);
822 executed_task_count = 0;
823 continue;
826 // Walk across our list of tasks and find
827 // the next one to execute.
828 DateTime now = DateTime.Now;
829 DateTime next_trigger_time = DateTime.MaxValue;
831 // Make a first pass over our tasks, finding the
832 // highest-priority item per source.
833 max_priority_by_source.Clear ();
834 foreach (Task task in tasks_by_tag.Values) {
835 if (task.Blocked || task.TriggerTime >= now)
836 continue;
837 if (max_priority_by_source.Contains (task.Source)) {
838 Priority p = (Priority) max_priority_by_source [task.Source];
839 if (p < task.Priority)
840 max_priority_by_source [task.Source] = task.Priority;
841 } else {
842 max_priority_by_source [task.Source] = task.Priority;
846 // Now make a second pass over the tasks and find
847 // the highest-priority item. We use the information
848 // from the first pass to correctly prioritize maintenance tasks.
849 Task next_task = null;
850 foreach (Task task in tasks_by_tag.Values) {
851 if (task.Blocked)
852 continue;
853 if (task.TriggerTime >= now) {
854 if (task.TriggerTime < next_trigger_time)
855 next_trigger_time = task.TriggerTime;
856 continue;
859 // If this is a maintenance task and there is a high-priority
860 // task from the same source, skip it.
861 if (task.Priority == Priority.Maintenance) {
862 Priority p = (Priority) max_priority_by_source [task.Source];
863 if (p > task.Priority)
864 continue;
867 if (task.TriggerTime < now) {
868 if (next_task == null || next_task.CompareTo (task) < 0)
869 next_task = task;
873 // If we didn't find a task, wait for the next trigger-time
874 // and then re-start our while loop.
875 if (next_task == null) {
876 if (next_trigger_time == DateTime.MaxValue) {
877 status_str = "Waiting for an unblocked task";
878 Monitor.Wait (big_lock);
879 } else {
880 status_str = "Waiting for the next trigger time";
881 Monitor.Wait (big_lock, next_trigger_time - now);
883 executed_task_count = 0;
884 continue;
887 // If we did find a task, do we want to execute it right now?
888 // Or should we wait a bit?
890 // How should we space things out?
891 double delay = 0;
892 delay = ComputeDelay (next_task.Priority, duration_of_previous_task, executed_task_count);
893 delay = Math.Min (delay, (next_trigger_time - now).TotalSeconds);
895 // Adjust by the time that has actually elapsed since the
896 // last task.
897 delay -= (now - end_time_of_previous_task).TotalSeconds;
899 // If we still need to wait a bit longer, wait for the appropriate
900 // amount of time and then re-start our while loop.
901 if (delay > 0.001) {
902 status_str = "Waiting for next task.";
903 Monitor.Wait (big_lock, TimeSpanFromSeconds (delay));
904 executed_task_count = 0;
905 continue;
909 // If we've made it to this point, it is time to start
910 // executing our selected task.
913 to_be_executed.Clear ();
915 if (next_task.Collector == null) {
917 to_be_executed.Add (next_task);
919 } else {
921 pre_hook = new Hook (next_task.Collector.PreTaskHook);
922 post_hook = new Hook (next_task.Collector.PostTaskHook);
924 // Find all eligible tasks with the same collector,
925 // and add them to the collection list.
926 now = DateTime.Now;
927 foreach (Task task in tasks_by_tag.Values)
928 if (task != next_task
929 && task.Collector == next_task.Collector
930 && !task.Blocked
931 && task.TriggerTime < now)
932 to_be_executed.Add (task);
934 // Order the tasks from highest to lowest priority.
935 // Our original task will always be the first item
936 // in the resulting array.
937 to_be_executed.Sort ();
938 to_be_executed.Add (next_task);
939 to_be_executed.Reverse ();
941 // Now find how many tasks can be executed before we
942 // exceed the collector's maximum weight. If necessary,
943 // prune the list of tasks.
944 double remaining_weight;
945 remaining_weight = next_task.Collector.GetMaximumWeight ();
946 int i = 0;
947 while (i < to_be_executed.Count && remaining_weight > 0) {
948 Task task;
949 task = to_be_executed [i] as Task;
950 remaining_weight -= task.Weight;
951 ++i;
953 if (i < to_be_executed.Count)
954 to_be_executed.RemoveRange (i, to_be_executed.Count - i);
957 // Remove the tasks we are about to execute from our
958 // master list.
959 foreach (Task task in to_be_executed)
960 tasks_by_tag.Remove (task.Tag);
962 // Pulse our lock, in case anyone is waiting for it.
963 Monitor.Pulse (big_lock);
966 // Now actually execute the set of tasks we found.
968 status_builder.Length = 0;
969 status_builder.Append ("Executing task");
970 if (to_be_executed.Count > 1)
971 status_builder.Append ('s');
972 status_builder.Append ('\n');
973 foreach (Task task in to_be_executed) {
974 task.AppendToStringBuilder (status_builder);
975 status_builder.Append ('\n');
977 status_str = status_builder.ToString ();
979 DateTime start_time = DateTime.Now;
980 if (pre_hook != null) {
981 try {
982 pre_hook ();
983 } catch (Exception ex) {
984 Logger.Log.Error (ex, "Caught exception in pre_hook '{0}'", pre_hook);
987 foreach (Task task in to_be_executed) {
988 task.DoTask ();
989 ++total_executed_task_count;
990 ++executed_task_count;
992 if (post_hook != null) {
993 try {
994 post_hook ();
995 } catch (Exception ex) {
996 Logger.Log.Error (ex, "Caught exception in post_hook '{0}'", post_hook);
1000 end_time_of_previous_task = DateTime.Now;
1001 duration_of_previous_task = (end_time_of_previous_task - start_time).TotalSeconds;
1004 // Execute all shutdown tasks
1005 foreach (Task task in shutdown_task_queue)
1006 if (! task.Cancelled && ! task.Blocked)
1007 task.DoTask ();
1009 // Call Cleanup on all of our unexecuted tasks
1010 foreach (Task task in tasks_by_tag.Values)
1011 task.Cleanup ();
1013 if (Debug)
1014 Logger.Log.Debug ("Scheduler.Worker finished");
1017 //////////////////////////////////////////////////////////////////////////////
1019 private static StringBuilder cached_sb = new StringBuilder ();
1021 public SchedulerInformation GetCurrentStatus ()
1023 SchedulerInformation current_status = new SchedulerInformation ();
1025 lock (big_lock) {
1027 ArrayList blocked_tasks = new ArrayList ();
1028 ArrayList future_tasks = new ArrayList ();
1029 ArrayList pending_tasks = new ArrayList ();
1031 DateTime now = DateTime.Now;
1032 foreach (Task task in tasks_by_tag.Values) {
1033 if (task.Blocked)
1034 blocked_tasks.Add (task);
1035 else if (task.TriggerTime > now)
1036 future_tasks.Add (task);
1037 else
1038 pending_tasks.Add (task);
1041 blocked_tasks.Sort ();
1042 blocked_tasks.Reverse ();
1044 future_tasks.Sort ();
1045 future_tasks.Reverse ();
1047 pending_tasks.Sort ();
1048 pending_tasks.Reverse ();
1050 foreach (Task task in pending_tasks) {
1051 cached_sb.Length = 0;
1052 task.AppendToStringBuilder (cached_sb);
1053 current_status.PendingTasks.Add (cached_sb.ToString ());
1056 foreach (Task task in future_tasks) {
1057 cached_sb.Length = 0;
1058 task.AppendToStringBuilder (cached_sb);
1059 current_status.FutureTasks.Add (cached_sb.ToString ());
1062 foreach (Task task in blocked_tasks) {
1063 cached_sb.Length = 0;
1064 task.AppendToStringBuilder (cached_sb);
1065 current_status.BlockedTasks.Add (cached_sb.ToString ());
1068 current_status.TotalTaskCount = total_executed_task_count;
1069 current_status.StatusString = status_str;
1073 return current_status;
1078 public class SchedulerInformation {
1079 [XmlAttribute]
1080 public int TotalTaskCount = -1;
1082 [XmlAttribute]
1083 public string StatusString;
1085 [XmlArray]
1086 [XmlArrayItem (ElementName="PendingTask", Type=typeof (string))]
1087 public ArrayList PendingTasks = new ArrayList ();
1089 [XmlArray]
1090 [XmlArrayItem (ElementName="FutureTask", Type=typeof (string))]
1091 public ArrayList FutureTasks = new ArrayList ();
1093 [XmlArray]
1094 [XmlArrayItem (ElementName="BlockedTask", Type=typeof (string))]
1095 public ArrayList BlockedTasks = new ArrayList ();
1097 private static StringBuilder sb = new StringBuilder ();
1099 public string ToHumanReadableString ()
1101 sb.Length = 0;
1103 sb.Append ("Scheduler:\n");
1104 sb.Append ("Count: ").Append (TotalTaskCount);
1105 sb.Append ('\n');
1107 if (StatusString != null)
1108 sb.Append ("Status: ").Append (StatusString).Append ('\n');
1110 int pos = 1;
1111 sb.Append ("\nPending Tasks:\n");
1112 if (PendingTasks != null && PendingTasks.Count > 0) {
1113 foreach (string task in PendingTasks) {
1114 sb.Append (pos).Append (' ').Append (task).Append ('\n');
1115 ++pos;
1117 } else
1118 sb.Append ("Scheduler queue is empty.\n");
1121 if (FutureTasks != null && FutureTasks.Count > 0) {
1122 sb.Append ("\nFuture Tasks:\n");
1123 foreach (string task in FutureTasks)
1124 sb.Append (task).Append ('\n');
1127 if (BlockedTasks != null && BlockedTasks.Count > 0) {
1128 sb.Append ("\nBlocked Tasks:\n");
1129 foreach (string task in BlockedTasks)
1130 sb.Append (task).Append ('\n');
1133 return sb.ToString ();
1137 #if false
1138 class TestTask : Scheduler.Task {
1140 private class TestCollector : Scheduler.ITaskCollector {
1142 public double GetMinimumWeight ()
1144 return 0;
1147 public double GetMaximumWeight ()
1149 return 5;
1152 public void PreTaskHook ()
1154 Console.WriteLine ("+++ Pre-Task Hook");
1157 public void PostTaskHook ()
1159 Console.WriteLine ("+++ Post-Task Hook");
1163 protected override void DoTaskReal ()
1165 Console.WriteLine ("Doing task '{0}' at {1}", Tag, DateTime.Now);
1166 Thread.Sleep (200);
1167 if (Tag == "Bar")
1168 Reschedule = true;
1171 private static void BeginTaskGroup ()
1173 Console.WriteLine ("--- Begin Task Group!");
1176 private static void EndTaskGroup ()
1178 Console.WriteLine ("--- End Task Group!");
1181 public static void Main ()
1183 Scheduler sched = Scheduler.Global;
1185 Scheduler.TaskGroup tg = Scheduler.NewTaskGroup ("foo",
1186 new Scheduler.Hook (BeginTaskGroup),
1187 new Scheduler.Hook (EndTaskGroup));
1189 sched.Start ();
1191 Scheduler.Task task;
1193 task = new TestTask ();
1194 task.Tag = "Foo";
1195 task.AddTaskGroup (tg);
1196 task.Priority = Scheduler.Priority.Delayed;
1197 task.TriggerTime = DateTime.Now.AddSeconds (7);
1198 sched.Add (task);
1200 task = new TestTask ();
1201 task.Tag = "Bar";
1202 task.AddTaskGroup (tg);
1203 task.Priority = Scheduler.Priority.Delayed;
1204 sched.Add (task);
1206 Scheduler.ITaskCollector collector = null;
1207 for (int i = 0; i < 20; ++i) {
1208 if ((i % 10) == 0)
1209 collector = new TestCollector ();
1210 task = new TestTask ();
1211 task.Tag = String.Format ("Baboon {0}", i);
1212 task.Collector = collector;
1213 task.Priority = Scheduler.Priority.Delayed;
1214 sched.Add (task);
1217 while (true) {
1218 Thread.Sleep (1000);
1222 #endif