Free cookie consent management tool by TermsFeed Policy Generator

source: branches/3.3-Hive/sources/HeuristicLab.Hive/HeuristicLab.Hive.Experiment/3.3/HiveJob.cs @ 4423

Last change on this file since 4423 was 4423, checked in by cneumuel, 14 years ago
  • Refactored HL.Hive.Experiment. JobItems are not called HiveJobs and OptimizerJobs do not contain a hierarchy anymore.
  • Dynamic generation of jobs on a slave are not reflected on the client user interface.
  • Optimizer-Trees are now strictly synchronized with the HiveJob-Trees (also the ComputeInParallel property is taken into account when the Child HiveJobs are created)
  • Improved the way a class can report progress and lock the UI (IProgressReporter, IProgress, Progress, ProgressView)
  • Changes were made to the config-files, so that server and clients work with blade12.hpc.fh-hagenberg.at
  • Lots of small changes and bugfixes
File size: 21.6 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using HeuristicLab.Core;
6using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
7using HeuristicLab.Hive.Contracts.BusinessObjects;
8using HeuristicLab.Hive.Contracts;
9using System.Drawing;
10using HeuristicLab.Common;
11using System.Diagnostics;
12using HeuristicLab.Optimization;
13using HeuristicLab.Hive.Contracts.ResponseObjects;
14using HeuristicLab.Hive.JobBase;
15using System.IO;
16using HeuristicLab.Persistence.Default.Xml;
17using HeuristicLab.PluginInfrastructure;
18using System.Reflection;
19using HeuristicLab.Hive.Experiment.Jobs;
20using HeuristicLab.Collections;
21
22namespace HeuristicLab.Hive.Experiment {
23  [StorableClass]
24  public class HiveJob : Item {
25    private static object locker = new object();
26
27    public override Image ItemImage {
28      get {
29        if (jobDto.Id == Guid.Empty) { // not yet uploaded
30          return HeuristicLab.Common.Resources.VS2008ImageLibrary.Event;
31        } else {
32          if (jobDto.State == JobState.Offline) return HeuristicLab.Common.Resources.VS2008ImageLibrary.ExecutablePrepared;
33          else if (jobDto.State == JobState.WaitForChildJobs) return HeuristicLab.Common.Resources.VS2008ImageLibrary.ExecutablePrepared;
34          else if (jobDto.State == JobState.Calculating) return HeuristicLab.Common.Resources.VS2008ImageLibrary.ExecutableStarted;
35          else if (jobDto.State == JobState.Aborted) return HeuristicLab.Common.Resources.VS2008ImageLibrary.ExecutableStopped;
36          else if (jobDto.State == JobState.Failed) return HeuristicLab.Common.Resources.VS2008ImageLibrary.Error;
37          else if (jobDto.State == JobState.Finished) return HeuristicLab.Common.Resources.VS2008ImageLibrary.ExecutableStopped;
38          else return HeuristicLab.Common.Resources.VS2008ImageLibrary.Event;
39        }
40      }
41    }
42
43    [Storable]
44    private JobDto jobDto;
45    public JobDto JobDto {
46      get { return jobDto; }
47      set {
48        if (jobDto != value) {
49          jobDto = value;
50          OnJobDtoChanged();
51          OnToStringChanged();
52          OnItemImageChanged();
53        }
54      }
55    }
56
57    [Storable]
58    private OptimizerJob job;
59    public OptimizerJob Job {
60      get { return job; }
61      private set {
62        if (job != null && syncJobsWithOptimizers) {
63          this.childHiveJobs.Clear();
64        }
65        if (job != value) {
66          DergisterOptimizerEvents();
67          job = value;
68          if (job.ExecutionState == ExecutionState.Stopped) {
69            IsFinishedOptimizerDownloaded = true;
70          }
71          RegisterOptimizerEvents();
72          OnJobChanged();
73        }
74      }
75    }
76
77    [Storable]
78    private HiveJobList childHiveJobs;
79    public ReadOnlyItemList<HiveJob> ChildHiveJobs {
80      get { return childHiveJobs.AsReadOnly(); }
81    }
82
83    [Storable]
84    private bool isFinishedOptimizerDownloaded;
85    public bool IsFinishedOptimizerDownloaded {
86      get { return isFinishedOptimizerDownloaded; }
87      set {
88        if (isFinishedOptimizerDownloaded != value) {
89          isFinishedOptimizerDownloaded = value;
90          OnIsFinishedOptimizerDownloadedChanged();
91        }
92      }
93    }
94
95    [Storable]
96    private bool syncJobsWithOptimizers = true;
97
98    public HiveJob() {
99      this.JobDto = new JobDto() {
100        State = JobState.Offline,
101        DateCreated = DateTime.Now,
102        CoresNeeded = 1,
103        MemoryNeeded = 0
104      };
105      this.childHiveJobs = new HiveJobList();
106      syncJobsWithOptimizers = true;
107    }
108
109    public HiveJob(JobDto jobDto)
110      : this() {
111      this.JobDto = jobDto;
112    }
113
114    public HiveJob(JobResult jobResult)
115      : this() {
116      UpdateFromJobResult(jobResult);
117    }
118
119    public HiveJob(OptimizerJob optimizerJob, bool autoCreateChildHiveJobs)
120      : this() {
121      this.syncJobsWithOptimizers = autoCreateChildHiveJobs;
122      this.Job = optimizerJob;
123      this.syncJobsWithOptimizers = true;
124    }
125
126    public HiveJob(IOptimizer optimizer)
127      : this() {
128      this.Job = new OptimizerJob(optimizer);
129    }
130
131    public HiveJob(SerializedJob serializedJob, bool autoCreateChildHiveJobs)
132      : this() {
133      this.syncJobsWithOptimizers = autoCreateChildHiveJobs;
134      this.JobDto = serializedJob.JobInfo;
135      this.Job = SerializedJob.Deserialize<OptimizerJob>(serializedJob.SerializedJobData);
136      this.syncJobsWithOptimizers = true;
137    }
138
139    /// <summary>
140    /// if this.Optimizer is an experiment
141    ///   Uses the child-optimizers of this.HiveJob and creates HiveJob-childs
142    /// if this.Optimizer is a batchrun
143    ///   Creates a number of child-jobs according to repetitions
144    /// </summary>
145    private void UpdateChildHiveJobs() {
146      if (Job != null && syncJobsWithOptimizers) {
147        if (Job.Optimizer is Optimization.Experiment) {
148          Optimization.Experiment experiment = (Optimization.Experiment)Job.Optimizer;
149          foreach (IOptimizer childOpt in experiment.Optimizers) {
150            this.childHiveJobs.Add(new HiveJob(childOpt));
151          }
152        } else if (Job.Optimizer is Optimization.BatchRun) {
153          Optimization.BatchRun batchRun = Job.OptimizerAsBatchRun;
154          if (batchRun.Algorithm != null) {
155            while (this.childHiveJobs.Count < batchRun.Repetitions) {
156              this.childHiveJobs.Add(new HiveJob(batchRun.Algorithm));
157            }
158            while (this.childHiveJobs.Count > batchRun.Repetitions) {
159              this.childHiveJobs.Remove(this.childHiveJobs.Last());
160            }
161          }
162        }
163      }
164    }
165
166    private void RegisterOptimizerEvents() {
167      if (Job != null) {
168        if (Job.Optimizer is Optimization.Experiment) {
169          Optimization.Experiment experiment = Job.OptimizerAsExperiment;
170          experiment.Optimizers.ItemsAdded += new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsAdded);
171          experiment.Optimizers.ItemsReplaced += new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsReplaced);
172          experiment.Optimizers.ItemsRemoved += new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsRemoved);
173          experiment.Optimizers.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_CollectionReset);
174        } else if (Job.Optimizer is Optimization.BatchRun) {
175          Optimization.BatchRun batchRun = Job.OptimizerAsBatchRun;
176          batchRun.RepetitionsChanged += new EventHandler(batchRun_RepetitionsChanged);
177          batchRun.AlgorithmChanged += new EventHandler(batchRun_AlgorithmChanged);
178        }
179
180        Job.ComputeInParallelChanged += new EventHandler(Job_ComputeInParallelChanged);
181      }
182    }
183
184    void batchRun_AlgorithmChanged(object sender, EventArgs e) {
185      if (syncJobsWithOptimizers) {
186        this.childHiveJobs.Clear();
187        UpdateChildHiveJobs();
188      }
189    }
190
191    void batchRun_RepetitionsChanged(object sender, EventArgs e) {
192      if (syncJobsWithOptimizers) {
193        UpdateChildHiveJobs();
194      }
195    }
196
197    private void DergisterOptimizerEvents() {
198      if (Job != null && Job.Optimizer is Optimization.Experiment) {
199        if (Job.Optimizer is Optimization.Experiment) {
200          Optimization.Experiment experiment = Job.OptimizerAsExperiment;
201          experiment.Optimizers.ItemsAdded -= new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsAdded);
202          experiment.Optimizers.ItemsReplaced -= new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsReplaced);
203          experiment.Optimizers.ItemsRemoved -= new Collections.CollectionItemsChangedEventHandler<Collections.IndexedItem<IOptimizer>>(Optimizers_ItemsRemoved);
204          experiment.Optimizers.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_CollectionReset);
205        } else if (Job.Optimizer is Optimization.BatchRun) {
206          Optimization.BatchRun batchRun = Job.OptimizerAsBatchRun;
207          batchRun.RepetitionsChanged -= new EventHandler(batchRun_RepetitionsChanged);
208          batchRun.AlgorithmChanged -= new EventHandler(batchRun_AlgorithmChanged);
209        }
210
211        Job.ComputeInParallelChanged -= new EventHandler(Job_ComputeInParallelChanged);
212      }
213    }
214
215    private void Optimizers_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
216      if (syncJobsWithOptimizers && this.Job.ComputeInParallel) {
217        foreach (var item in e.Items) {
218          if (GetChildByOptimizer(item.Value) == null && item.Value.Name != "Placeholder") {
219            this.childHiveJobs.Add(new HiveJob(item.Value));
220          }
221        }
222      }
223    }
224    private void Optimizers_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
225      if (syncJobsWithOptimizers && this.Job.ComputeInParallel) {
226        foreach (var item in e.OldItems) {
227          this.childHiveJobs.Remove(this.GetChildByOptimizer(item.Value));
228        }
229        foreach (var item in e.Items) {
230          if (GetChildByOptimizer(item.Value) == null && item.Value.Name != "Placeholder") {
231            this.childHiveJobs.Add(new HiveJob(item.Value));
232          }
233        }
234      }
235    }
236    private void Optimizers_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
237      if (syncJobsWithOptimizers && this.Job.ComputeInParallel) {
238        foreach (var item in e.Items) {
239          this.childHiveJobs.Remove(this.GetChildByOptimizer(item.Value));
240        }
241      }
242    }
243    void Optimizers_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
244      if (syncJobsWithOptimizers && this.Job.ComputeInParallel) {
245        foreach (var item in e.Items) {
246          this.childHiveJobs.Remove(this.GetChildByOptimizer(item.Value));
247        }
248      }
249    }
250
251    void Job_ComputeInParallelChanged(object sender, EventArgs e) {
252      if (Job != null && syncJobsWithOptimizers) {
253        if (Job.ComputeInParallel) {
254          // child-hive jobs are not yet created, so create them according to the child-optimizers
255          this.UpdateChildHiveJobs();
256        } else {
257          // child-hive jobs need to be deleted
258          this.childHiveJobs.Clear();
259        }
260      }
261    }
262
263    public void AddChildHiveJob(HiveJob hiveJob) {
264      this.childHiveJobs.Add(hiveJob);
265      syncJobsWithOptimizers = false;
266      if (this.Job.Optimizer is Optimization.Experiment) {
267        if (!this.Job.OptimizerAsExperiment.Optimizers.Contains(hiveJob.Job.Optimizer)) {
268          UpdateOptimizerInExperiment(this.Job.OptimizerAsExperiment, hiveJob.Job);
269        }
270      } else if (this.Job.Optimizer is Optimization.BatchRun) {
271        UpdateOptimizerInBatchRun(this.Job.OptimizerAsBatchRun, hiveJob.Job);
272      }
273      syncJobsWithOptimizers = true;
274    }
275
276    /// <summary>
277    /// if this.Optimizer is Experiment
278    ///   replace the child-optimizer in the experiment
279    /// if this.Optimizer is BatchRun
280    ///   add the runs from the optimizerJob to the batchrun and replace the algorithm
281    /// </summary>
282    public void UpdateChildOptimizer(OptimizerJob optimizerJob, Guid childJobId) {
283      syncJobsWithOptimizers = false; // don't sync with optimizers during this method
284      ExecutionState executionState = optimizerJob.Optimizer.ExecutionState; // has to be stored before adding the optimizers, because of the sideeffects of updating the optimizers in experiment/batchrun
285
286      if (this.Job.Optimizer is Optimization.Experiment) {
287        UpdateOptimizerInExperiment(this.Job.OptimizerAsExperiment, optimizerJob);
288        executionState = ExecutionState.Stopped;
289      } else if (this.Job.Optimizer is Optimization.BatchRun) {
290        UpdateOptimizerInBatchRun(this.Job.OptimizerAsBatchRun, optimizerJob);
291        executionState = ExecutionState.Stopped;
292      }
293      HiveJob child = this.ChildHiveJobs.Single(j => j.JobDto.Id == childJobId);
294      if (!optimizerJob.ComputeInParallel) {
295        child.syncJobsWithOptimizers = false;
296        child.Job = optimizerJob;
297        child.syncJobsWithOptimizers = true;
298      }
299      if (executionState == ExecutionState.Stopped) {
300        child.IsFinishedOptimizerDownloaded = true;
301      }
302      syncJobsWithOptimizers = true;
303    }
304
305    /// <summary>
306    /// Adds the runs from the optimizerJob to the batchrun and replaces the algorithm
307    /// Sideeffect: the optimizerJob.Optimizer will be prepared (scopes are deleted and executionstate will be reset)
308    /// </summary>
309    private void UpdateOptimizerInBatchRun(BatchRun batchRun, OptimizerJob optimizerJob) {
310      if (batchRun.Algorithm == null) {
311        batchRun.Algorithm = (IAlgorithm)optimizerJob.Optimizer; // only set the first optimizer as algorithm. if every time the Algorithm would be set, the runs would be cleared each time
312      }
313      foreach (IRun run in optimizerJob.Optimizer.Runs) {
314        if (!batchRun.Runs.Contains(run))
315          batchRun.Runs.Add(run);
316      }
317    }
318
319    /// <summary>
320    /// replace the child-optimizer in the experiment
321    /// Sideeffect: the optimizerJob.Optimizer will be prepared (scopes are deleted and executionstate will be reset)
322    /// </summary>
323    private void UpdateOptimizerInExperiment(Optimization.Experiment experiment, OptimizerJob optimizerJob) {
324      if (optimizerJob.IndexInParentOptimizerList < 0)
325        throw new IndexOutOfRangeException("IndexInParentOptimizerList must be equal or greater than zero! The Job is invalid and the optimizer-tree cannot be reassembled.");
326
327      while (experiment.Optimizers.Count < optimizerJob.IndexInParentOptimizerList) {
328        experiment.Optimizers.Add(new UserDefinedAlgorithm("Placeholder")); // add dummy-entries to Optimizers so that its possible to insert the optimizerJob at the correct position
329      }
330      if (experiment.Optimizers.Count < optimizerJob.IndexInParentOptimizerList + 1) {
331        experiment.Optimizers.Add(optimizerJob.Optimizer);
332      } else {
333        // if ComputeInParallel==true, don't replace the optimizer (except it is still a Placeholder)
334        // this is because Jobs with ComputeInParallel get submitted to hive with their child-optimizers deleted
335        if (!optimizerJob.ComputeInParallel || experiment.Optimizers[optimizerJob.IndexInParentOptimizerList].Name == "Placeholder") {
336          experiment.Optimizers[optimizerJob.IndexInParentOptimizerList] = optimizerJob.Optimizer;
337        }
338      }
339    }
340
341    /// <summary>
342    /// Sets the IndexInParentOptimizerList property of the OptimizerJob
343    /// according to the position in the OptimizerList of the parentHiveJob.Job
344    /// Recursively updates all the child-jobs as well
345    /// </summary>
346    internal void SetIndexInParentOptimizerList(HiveJob parentHiveJob) {
347      if (parentHiveJob != null) {
348        if (parentHiveJob.Job.Optimizer is Optimization.Experiment) {
349          this.Job.IndexInParentOptimizerList = parentHiveJob.Job.OptimizerAsExperiment.Optimizers.IndexOf(this.Job.Optimizer);
350        } else if (parentHiveJob.Job.Optimizer is Optimization.BatchRun) {
351          this.Job.IndexInParentOptimizerList = 0;
352        } else {
353          throw new NotSupportedException("Only Experiment and BatchRuns are supported");
354        }
355      }
356      foreach (HiveJob child in childHiveJobs) {
357        child.SetIndexInParentOptimizerList(this);
358      }
359    }
360
361    public override string ToString() {
362      if (jobDto != null) {
363        return job.Name;
364      } else {
365        return base.ToString();
366      }
367    }
368
369    public void UpdateFromJobResult(JobResult jobResult) {
370      if (jobResult != null) {
371        jobDto.Id = jobResult.Id;
372        jobDto.DateCreated = jobResult.DateCreated;
373        jobDto.DateCalculated = jobResult.DateCalculated;
374        jobDto.DateFinished = jobResult.DateFinished;
375        jobDto.Exception = jobResult.Exception;
376        jobDto.Id = jobResult.Id;
377        jobDto.ExecutionTime = jobResult.ExecutionTime;
378        jobDto.State = jobResult.State;
379        // what about parentJob
380        OnJobStateChanged();
381        OnToStringChanged();
382        OnItemImageChanged();
383      }
384    }
385
386    /// <summary>
387    /// Creates a SerializedJob object containing the JobDto and the IJob-Object as byte[]
388    /// </summary>
389    /// <param name="withoutChildOptimizers">
390    ///   if true the Child-Optimizers will not be serialized (if the job contains an Experiment)
391    /// </param>
392    public SerializedJob GetAsSerializedJob(bool withoutChildOptimizers) {
393      byte[] jobByteArray;
394      if (withoutChildOptimizers && this.Job.Optimizer is Optimization.Experiment) {
395        OptimizerJob clonedJob = (OptimizerJob)this.Job.Clone(); // use a cloned job, so that the childHiveJob don't get confused
396        clonedJob.OptimizerAsExperiment.Optimizers.Clear();
397        jobByteArray = SerializedJob.Serialize(clonedJob);
398      } else if (withoutChildOptimizers && this.Job.Optimizer is Optimization.BatchRun) {
399        OptimizerJob clonedJob = (OptimizerJob)this.Job.Clone();
400        clonedJob.OptimizerAsBatchRun.Algorithm = null;
401        jobByteArray = SerializedJob.Serialize(clonedJob);
402      } else if (this.Job.Optimizer is IAlgorithm) {
403        ((IAlgorithm)this.Job.Optimizer).StoreAlgorithmInEachRun = false; // avoid storing the algorithm in runs to reduce size
404        jobByteArray = SerializedJob.Serialize(this.Job);
405      } else {
406        jobByteArray = SerializedJob.Serialize(this.Job);
407      }
408
409      UpdateRequiredPlugins();
410
411      SerializedJob serializedJob = new SerializedJob() {
412        JobInfo = jobDto,
413        SerializedJobData = jobByteArray
414      };
415
416      return serializedJob;
417    }
418
419    /// <summary>
420    /// find out which which plugins are needed for the given object
421    /// </summary>
422    private void UpdateRequiredPlugins() {
423      this.JobDto.PluginsNeeded = HivePluginInfoDto.FindPluginsNeeded(job.GetType());
424    }
425
426    #region Events
427    public event EventHandler JobDtoChanged;
428    private void OnJobDtoChanged() {
429      LogMessage("JobDtoChanged");
430      EventHandler handler = JobDtoChanged;
431      if (handler != null) handler(this, EventArgs.Empty);
432    }
433
434    public event EventHandler JobStateChanged;
435    private void OnJobStateChanged() {
436      LogMessage("JobStateChanged (State: " + this.JobDto.State + ", ExecutionTime: " + this.JobDto.ExecutionTime.ToString() + ")");
437      EventHandler handler = JobStateChanged;
438      if (handler != null) handler(this, EventArgs.Empty);
439    }
440
441    public event EventHandler JobChanged;
442    private void OnJobChanged() {
443      Job_ComputeInParallelChanged(this, EventArgs.Empty);
444      var handler = JobChanged;
445      if (handler != null) handler(this, EventArgs.Empty);
446    }
447
448    public event EventHandler IsFinishedOptimizerDownloadedChanged;
449    private void OnIsFinishedOptimizerDownloadedChanged() {
450      var handler = IsFinishedOptimizerDownloadedChanged;
451      if (handler != null) handler(this, EventArgs.Empty);
452    }
453    #endregion
454
455    public void LogMessage(string message) {
456      lock (locker) {
457        if (job != null) {
458          job.Log.LogMessage(message);
459        }
460      }
461    }
462
463    public override IDeepCloneable Clone(Cloner cloner) {
464      LogMessage("I am beeing cloned");
465      HiveJob clone = (HiveJob)base.Clone(cloner);
466      clone.jobDto = (JobDto)cloner.Clone(this.jobDto);
467      clone.job = (OptimizerJob)cloner.Clone(this.job);
468      return clone;
469    }
470
471    /// <summary>
472    /// Returns a list of HiveJobs including this and all its child-jobs recursively
473    /// </summary>
474    public IEnumerable<HiveJob> GetAllHiveJobs() {
475      List<HiveJob> jobs = new List<HiveJob>();
476      jobs.Add(this);
477      foreach (HiveJob child in this.ChildHiveJobs) {
478        jobs.AddRange(child.GetAllHiveJobs());
479      }
480      return jobs;
481    }
482
483    public HiveJob GetParentByJobId(Guid jobId) {
484      if (this.ChildHiveJobs.SingleOrDefault(j => j.jobDto.Id == jobId) != null)
485        return this;
486      foreach (HiveJob child in this.childHiveJobs) {
487        HiveJob result = child.GetParentByJobId(jobId);
488        if (result != null)
489          return result;
490      }
491      return null;
492    }
493
494
495    public HiveJob GetChildByOptimizerJob(OptimizerJob optimizerJob) {
496      foreach (var child in ChildHiveJobs) {
497        if (child.Job == optimizerJob)
498          return child;
499      }
500      return null;
501    }
502
503
504    public HiveJob GetChildByOptimizer(IOptimizer optimizer) {
505      foreach (var child in ChildHiveJobs) {
506        if (child.Job.Optimizer == optimizer)
507          return child;
508      }
509      return null;
510    }
511
512    /// <summary>
513    /// Searches for an HiveJob object with the correct jobId recursively
514    /// </summary>
515    public HiveJob GetHiveJobByJobId(Guid jobId) {
516      if (this.JobDto.Id == jobId) {
517        return this;
518      } else {
519        foreach (HiveJob child in this.ChildHiveJobs) {
520          HiveJob result = child.GetHiveJobByJobId(jobId);
521          if (result != null)
522            return result;
523        }
524      }
525      return null;
526    }
527
528
529    public void RemoveByJobId(Guid jobId) {
530      IEnumerable<HiveJob> jobs = ChildHiveJobs.Where(j => j.JobDto.Id == jobId).ToList(); // if Guid.Empty needs to be removed, there could be more than one with this jobId
531      foreach (HiveJob j in jobs) {
532        this.childHiveJobs.Remove(j);
533      }
534      foreach (HiveJob child in ChildHiveJobs) {
535        child.RemoveByJobId(jobId);
536      }
537    }
538
539  }
540}
Note: See TracBrowser for help on using the repository browser.