source: trunk/eraser/Eraser.Manager/DirectExecutor.cs @ 2129

Revision 2129, 13.3 KB checked in by lowjoel, 5 years ago (diff)
  • Define the behaviour of Run immediately (run now tasks) and Run manually: after a run now task is complete, it is reset to run manually; if a run immediately task is saved to the task list, upon load, it is automatically queued for running.
  • As a result, we don't need to worry about having blank "task status" entries when the task is set to run immediately upon creation. This would revert r1969
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net> @17/10/2008
6 * Modified By:
7 *
8 * This file is part of Eraser.
9 *
10 * Eraser is free software: you can redistribute it and/or modify it under the
11 * terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version.
14 *
15 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
16 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 *
19 * A copy of the GNU General Public License can be found at
20 * <http://www.gnu.org/licenses/>.
21 */
22
23using System;
24using System.Collections.Generic;
25using System.Collections.Specialized;
26using System.Text;
27using System.Threading;
28using System.IO;
29using System.Runtime.Serialization;
30using System.Runtime.Serialization.Formatters.Binary;
31
32using Eraser.Util;
33using Eraser.Util.ExtensionMethods;
34
35namespace Eraser.Manager
36{
37    /// <summary>
38    /// The DirectExecutor class is used by the Eraser GUI directly when the program
39    /// is run without the help of a Service.
40    /// </summary>
41    public class DirectExecutor : Executor
42    {
43        public DirectExecutor()
44        {
45            TaskAdded += OnTaskAdded;
46            TaskDeleted += OnTaskDeleted;
47            tasks = new DirectExecutorTasksCollection(this);
48            thread = new Thread(Main);
49        }
50
51        protected override void Dispose(bool disposing)
52        {
53            if (thread == null || schedulerInterrupt == null)
54                return;
55
56            if (disposing)
57            {
58                thread.Abort();
59                schedulerInterrupt.Set();
60
61                //Wait for the executor thread to exit -- we call some event functions
62                //and these events may need invocation on the main thread. So,
63                //pump messages from the main thread until the thread exits.
64                if (System.Windows.Forms.Application.MessageLoop)
65                {
66                    if (!thread.Join(new TimeSpan(0, 0, 0, 0, 100)))
67                        System.Windows.Forms.Application.DoEvents();
68                }
69
70                //If we are disposing on a secondary thread, or a thread without
71                //a message loop, just wait for the thread to exit indefinitely
72                else
73                    thread.Join();
74
75                schedulerInterrupt.Close();
76            }
77
78            thread = null;
79            schedulerInterrupt = null;
80            base.Dispose(disposing);
81        }
82
83        public override void Run()
84        {
85            thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
86            thread.Start();
87        }
88
89        public override void QueueTask(Task task)
90        {
91            lock (tasksLock)
92            {
93                //Queue the task to be run immediately.
94                DateTime executionTime = DateTime.Now;
95                if (!scheduledTasks.ContainsKey(executionTime))
96                    scheduledTasks.Add(executionTime, new List<Task>());
97                scheduledTasks[executionTime].Add(task);
98                schedulerInterrupt.Set();
99            }
100        }
101
102        public override void ScheduleTask(Task task)
103        {
104            RecurringSchedule schedule = task.Schedule as RecurringSchedule;
105            if (schedule == null)
106                return;
107
108            DateTime executionTime = (schedule.MissedPreviousSchedule &&
109                ManagerLibrary.Settings.ExecuteMissedTasksImmediately) ?
110                    DateTime.Now : schedule.NextRun;
111
112            lock (tasksLock)
113            {
114                if (!scheduledTasks.ContainsKey(executionTime))
115                    scheduledTasks.Add(executionTime, new List<Task>());
116                scheduledTasks[executionTime].Add(task);
117            }
118        }
119
120        public override void QueueRestartTasks()
121        {
122            lock (tasksLock)
123            {
124                foreach (Task task in Tasks)
125                    if (task.Schedule == Schedule.RunOnRestart)
126                        QueueTask(task);
127            }
128        }
129
130        public override void UnqueueTask(Task task)
131        {
132            lock (tasksLock)
133                for (int i = 0; i != scheduledTasks.Count; ++i)
134                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
135                    {
136                        Task currentTask = scheduledTasks.Values[i][j];
137                        if (currentTask == task &&
138                            (!(currentTask.Schedule is RecurringSchedule) ||
139                                ((RecurringSchedule)currentTask.Schedule).NextRun != scheduledTasks.Keys[i]))
140                        {
141                            scheduledTasks.Values[i].RemoveAt(j);
142                        }
143                        else
144                        {
145                            ++j;
146                        }
147                    }
148        }
149
150        internal override bool IsTaskQueued(Task task)
151        {
152            lock (tasksLock)
153                foreach (KeyValuePair<DateTime, List<Task>> tasks in scheduledTasks)
154                    foreach (Task i in tasks.Value)
155                        if (task == i)
156                            if (task.Schedule is RecurringSchedule)
157                            {
158                                if (((RecurringSchedule)task.Schedule).NextRun != tasks.Key)
159                                    return true;
160                            }
161                            else
162                                return true;
163
164            return false;
165        }
166
167        private void OnTaskAdded(object sender, TaskEventArgs e)
168        {
169            e.Task.TaskEdited += OnTaskEdited;
170        }
171
172        private void OnTaskEdited(object sender, EventArgs e)
173        {
174            //Find all schedule entries containing the task - since the user cannot make
175            //edits to the task when it is queued (only if it is scheduled) remove
176            //all task references and add them back
177            Task task = (Task)sender;
178            lock (tasksLock)
179                for (int i = 0; i != scheduledTasks.Count; ++i)
180                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
181                    {
182                        Task currentTask = scheduledTasks.Values[i][j];
183                        if (currentTask == task)
184                            scheduledTasks.Values[i].RemoveAt(j);
185                        else
186                            j++;
187                    }
188
189            //Then reschedule the task
190            if (task.Schedule is RecurringSchedule)
191                ScheduleTask(task);
192        }
193
194        private void OnTaskDeleted(object sender, TaskEventArgs e)
195        {
196            e.Task.TaskEdited -= OnTaskEdited;
197        }
198
199        public override ExecutorTasksCollection Tasks
200        {
201            get
202            {
203                return tasks;
204            }
205        }
206
207        /// <summary>
208        /// The thread entry point for this object. This object operates on a queue
209        /// and hence the thread will sequentially execute tasks.
210        /// </summary>
211        private void Main()
212        {
213            //The waiting thread will utilize a polling loop to check for new
214            //scheduled tasks. This will be checked every 30 seconds. However,
215            //when the thread is waiting for a new task, it can be interrupted.
216            while (thread.ThreadState != ThreadState.AbortRequested)
217            {
218                //Check for a new task
219                Task task = null;
220                lock (tasksLock)
221                {
222                    while (scheduledTasks.Count != 0)
223                        if (scheduledTasks.Values[0].Count == 0)
224                        {
225                            //Clean all all time slots at the start of the queue which are
226                            //empty
227                            scheduledTasks.RemoveAt(0);
228                        }
229                        else
230                        {
231                            if (scheduledTasks.Keys[0] <= DateTime.Now)
232                            {
233                                List<Task> tasks = scheduledTasks.Values[0];
234                                task = tasks[0];
235                                tasks.RemoveAt(0);
236                            }
237
238                            //Do schedule queue maintenance: clean up all empty timeslots
239                            if (task == null)
240                            {
241                                for (int i = 0; i < scheduledTasks.Count; )
242                                    if (scheduledTasks.Values[i].Count == 0)
243                                        scheduledTasks.RemoveAt(i);
244                                    else
245                                        ++i;
246                            }
247
248                            break;
249                        }
250                }
251
252                if (task != null)
253                {
254                    //Start a new log session to separate this session's events
255                    //from previous ones.
256                    LogSink sessionLog = new LogSink();
257                    task.Log.Add(sessionLog);
258                    using (new LogSession(sessionLog))
259                    {
260                        ExecuteTask(task);
261                    }
262                }
263
264                //Wait for half a minute to check for the next scheduled task.
265                schedulerInterrupt.WaitOne(30000, false);
266            }
267        }
268
269        /// <summary>
270        /// Executes the given task.
271        /// </summary>
272        /// <param name="task">The task to execute.</param>
273        private void ExecuteTask(Task task)
274        {
275            //Set the currently executing task.
276            currentTask = task;
277
278            //Prevent the system from sleeping.
279            Power.ExecutionState = ExecutionState.Continuous | ExecutionState.SystemRequired;
280
281            try
282            {
283                //Broadcast the task started event.
284                task.Canceled = false;
285                task.OnTaskStarted();
286
287                //Run the task
288                foreach (ErasureTarget target in task.Targets)
289                    try
290                    {
291                        target.Execute();
292                    }
293                    catch (FatalException)
294                    {
295                        throw;
296                    }
297                    catch (OperationCanceledException)
298                    {
299                        throw;
300                    }
301                    catch (SharingViolationException)
302                    {
303                    }
304            }
305            catch (FatalException e)
306            {
307                Logger.Log(e.Message, LogLevel.Fatal);
308            }
309            catch (OperationCanceledException e)
310            {
311                Logger.Log(e.Message, LogLevel.Fatal);
312            }
313            catch (SharingViolationException)
314            {
315            }
316            finally
317            {
318                //Allow the system to sleep again.
319                Power.ExecutionState = ExecutionState.Continuous;
320
321                //If the task is a recurring task, reschedule it since we are done.
322                if (task.Schedule is RecurringSchedule)
323                    ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
324
325                //If the task is an execute on restart task or run immediately task, it is
326                //only run once and can now be restored to a manually run task
327                if (task.Schedule == Schedule.RunOnRestart || task.Schedule == Schedule.RunNow)
328                    task.Schedule = Schedule.RunManually;
329
330                //And the task finished event.
331                task.OnTaskFinished();
332
333                //Remove the actively executing task from our instance variable
334                currentTask = null;
335            }
336        }
337
338        /// <summary>
339        /// The thread object.
340        /// </summary>
341        private Thread thread;
342
343        /// <summary>
344        /// The lock preventing concurrent access for the tasks list and the
345        /// tasks queue.
346        /// </summary>
347        private object tasksLock = new object();
348
349        /// <summary>
350        /// The queue of tasks. This queue is executed when the first element's
351        /// timestamp (the key) has been past. This list assumes that all tasks
352        /// are sorted by timestamp, smallest one first.
353        /// </summary>
354        private SortedList<DateTime, List<Task>> scheduledTasks =
355            new SortedList<DateTime, List<Task>>();
356
357        /// <summary>
358        /// The task list associated with this executor instance.
359        /// </summary>
360        private DirectExecutorTasksCollection tasks;
361
362        /// <summary>
363        /// The currently executing task.
364        /// </summary>
365        Task currentTask;
366
367        /// <summary>
368        /// An automatically reset event allowing the addition of new tasks to
369        /// interrupt the thread's sleeping state waiting for the next recurring
370        /// task to be due.
371        /// </summary>
372        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
373
374        private class DirectExecutorTasksCollection : ExecutorTasksCollection
375        {
376            /// <summary>
377            /// Constructor.
378            /// </summary>
379            /// <param name="executor">The <see cref="DirectExecutor"/> object owning
380            /// this list.</param>
381            public DirectExecutorTasksCollection(DirectExecutor executor)
382                : base(executor)
383            {
384            }
385
386            #region IList<Task> Members
387            public override int IndexOf(Task item)
388            {
389                return list.IndexOf(item);
390            }
391
392            public override void Insert(int index, Task item)
393            {
394                item.Executor = Owner;
395                lock (list)
396                    list.Insert(index, item);
397
398                //Call all the event handlers who registered to be notified of tasks
399                //being added.
400                Owner.OnTaskAdded(new TaskEventArgs(item));
401
402                //If the task is scheduled to run now, break the waiting thread and
403                //run it immediately
404                if (item.Schedule == Schedule.RunNow)
405                {
406                    Owner.QueueTask(item);
407                }
408                //If the task is scheduled, add the next execution time to the list
409                //of schduled tasks.
410                else if (item.Schedule != Schedule.RunOnRestart)
411                {
412                    Owner.ScheduleTask(item);
413                }
414            }
415
416            public override void RemoveAt(int index)
417            {
418                lock (list)
419                {
420                    Task task = list[index];
421                    task.Cancel();
422                    task.Executor = null;
423                    list.RemoveAt(index);
424
425                    //Call all event handlers registered to be notified of task deletions.
426                    Owner.OnTaskDeleted(new TaskEventArgs(task));
427                }
428            }
429
430            public override Task this[int index]
431            {
432                get
433                {
434                    lock (list)
435                        return list[index];
436                }
437                set
438                {
439                    lock (list)
440                        list[index] = value;
441                }
442            }
443            #endregion
444
445            #region ICollection<Task> Members
446            public override void Add(Task item)
447            {
448                Insert(Count, item);
449            }
450
451            public override void Clear()
452            {
453                foreach (Task task in list)
454                    Remove(task);
455            }
456
457            public override bool Contains(Task item)
458            {
459                lock (list)
460                    return list.Contains(item);
461            }
462
463            public override void CopyTo(Task[] array, int arrayIndex)
464            {
465                lock (list)
466                    list.CopyTo(array, arrayIndex);
467            }
468
469            public override int Count
470            {
471                get
472                {
473                    lock (list)
474                        return list.Count;
475                }
476            }
477
478            public override bool Remove(Task item)
479            {
480                lock (list)
481                {
482                    int index = list.IndexOf(item);
483                    if (index < 0)
484                        return false;
485
486                    RemoveAt(index);
487                }
488
489                return true;
490            }
491            #endregion
492
493            #region IEnumerable<Task> Members
494            public override IEnumerator<Task> GetEnumerator()
495            {
496                return list.GetEnumerator();
497            }
498            #endregion
499
500            public override void SaveToStream(Stream stream)
501            {
502                lock (list)
503                    new BinaryFormatter().Serialize(stream, list);
504            }
505
506            public override void LoadFromStream(Stream stream)
507            {
508                //Load the list into the dictionary
509                StreamingContext context = new StreamingContext(
510                    StreamingContextStates.All, Owner);
511                BinaryFormatter formatter = new BinaryFormatter(null, context);
512
513                try
514                {
515                    List<Task> deserialised = (List<Task>)formatter.Deserialize(stream);
516                    list.AddRange(deserialised);
517
518                    foreach (Task task in deserialised)
519                    {
520                        Owner.OnTaskAdded(new TaskEventArgs(task));
521                        if (task.Schedule == Schedule.RunNow)
522                            Owner.QueueTask(task);
523                        else if (task.Schedule is RecurringSchedule)
524                            Owner.ScheduleTask(task);
525                    }
526                }
527                catch (FileLoadException e)
528                {
529                    throw new InvalidDataException(e.Message, e);
530                }
531                catch (SerializationException e)
532                {
533                    throw new InvalidDataException(e.Message, e);
534                }
535            }
536
537            /// <summary>
538            /// The data store for this object.
539            /// </summary>
540            private List<Task> list = new List<Task>();
541        }
542    }
543}
Note: See TracBrowser for help on using the repository browser.