View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase;
19  
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.util.concurrent.TimeUnit;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.CountingChore;
30  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.DoNothingChore;
31  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.FailInitialChore;
32  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.SampleStopper;
33  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.SleepingChore;
34  import org.apache.hadoop.hbase.TestChoreService.ScheduledChoreSamples.SlowChore;
35  import org.apache.hadoop.hbase.testclassification.SmallTests;
36  import org.junit.Test;
37  import org.junit.experimental.categories.Category;
38  
39  @Category(SmallTests.class)
40  public class TestChoreService {
41    public static final Log log = LogFactory.getLog(TestChoreService.class);
42  
43    /**
44     * A few ScheduledChore samples that are useful for testing with ChoreService
45     */
46    public static class ScheduledChoreSamples {
47      /**
48       * Straight forward stopper implementation that is used by default when one is not provided
49       */
50      public static class SampleStopper implements Stoppable {
51        private boolean stopped = false;
52  
53        @Override
54        public void stop(String why) {
55          stopped = true;
56        }
57  
58        @Override
59        public boolean isStopped() {
60          return stopped;
61        }
62      }
63  
64      /**
65       * Sleeps for longer than the scheduled period. This chore always misses its scheduled periodic
66       * executions
67       */
68      public static class SlowChore extends ScheduledChore {
69        public SlowChore(String name, int period) {
70          this(name, new SampleStopper(), period);
71        }
72  
73        public SlowChore(String name, Stoppable stopper, int period) {
74          super(name, stopper, period);
75        }
76  
77        @Override
78        protected boolean initialChore() {
79          try {
80            Thread.sleep(getPeriod() * 2);
81          } catch (InterruptedException e) {
82            log.warn("", e);
83          }
84          return true;
85        }
86  
87        @Override
88        protected void chore() {
89          try {
90            Thread.sleep(getPeriod() * 2);
91          } catch (InterruptedException e) {
92            log.warn("", e);
93          }
94        }
95      }
96  
97      /**
98       * Lightweight ScheduledChore used primarily to fill the scheduling queue in tests
99       */
100     public static class DoNothingChore extends ScheduledChore {
101       public DoNothingChore(String name, int period) {
102         super(name, new SampleStopper(), period);
103       }
104 
105       public DoNothingChore(String name, Stoppable stopper, int period) {
106         super(name, stopper, period);
107       }
108 
109       @Override
110       protected void chore() {
111         // DO NOTHING
112       }
113 
114     }
115 
116     public static class SleepingChore extends ScheduledChore {
117       private int sleepTime;
118 
119       public SleepingChore(String name, int chorePeriod, int sleepTime) {
120         this(name, new SampleStopper(), chorePeriod, sleepTime);
121       }
122 
123       public SleepingChore(String name, Stoppable stopper, int period, int sleepTime) {
124         super(name, stopper, period);
125         this.sleepTime = sleepTime;
126       }
127 
128       @Override
129       protected boolean initialChore() {
130         try {
131           Thread.sleep(sleepTime);
132         } catch (InterruptedException e) {
133           log.warn("", e);
134         }
135         return true;
136       }
137 
138       @Override
139       protected void chore() {
140         try {
141           Thread.sleep(sleepTime);
142         } catch (Exception e) {
143           log.warn("", e);
144         }
145       }
146     }
147 
148     public static class CountingChore extends ScheduledChore {
149       private int countOfChoreCalls;
150       private boolean outputOnTicks = false;
151 
152       public CountingChore(String name, int period) {
153         this(name, new SampleStopper(), period);
154       }
155 
156       public CountingChore(String name, Stoppable stopper, int period) {
157         this(name, stopper, period, false);
158       }
159 
160       public CountingChore(String name, Stoppable stopper, int period,
161           final boolean outputOnTicks) {
162         super(name, stopper, period);
163         this.countOfChoreCalls = 0;
164         this.outputOnTicks = outputOnTicks;
165       }
166 
167       @Override
168       protected boolean initialChore() {
169         countOfChoreCalls++;
170         if (outputOnTicks) outputTickCount();
171         return true;
172       }
173 
174       @Override
175       protected void chore() {
176         countOfChoreCalls++;
177         if (outputOnTicks) outputTickCount();
178       }
179 
180       private void outputTickCount() {
181         log.info("Chore: " + getName() + ". Count of chore calls: " + countOfChoreCalls);
182       }
183 
184       public int getCountOfChoreCalls() {
185         return countOfChoreCalls;
186       }
187 
188       public boolean isOutputtingOnTicks() {
189         return outputOnTicks;
190       }
191 
192       public void setOutputOnTicks(boolean o) {
193         outputOnTicks = o;
194       }
195     }
196 
197     /**
198      * A Chore that will try to execute the initial chore a few times before succeeding. Once the
199      * initial chore is complete the chore cancels itself
200      */
201     public static class FailInitialChore extends ScheduledChore {
202       private int numberOfFailures;
203       private int failureThreshold;
204 
205       /**
206        * @param failThreshold Number of times the Chore fails when trying to execute initialChore
207        *          before succeeding.
208        */
209       public FailInitialChore(String name, int period, int failThreshold) {
210         this(name, new SampleStopper(), period, failThreshold);
211       }
212 
213       public FailInitialChore(String name, Stoppable stopper, int period, int failThreshold) {
214         super(name, stopper, period);
215         numberOfFailures = 0;
216         failureThreshold = failThreshold;
217       }
218 
219       @Override
220       protected boolean initialChore() {
221         if (numberOfFailures < failureThreshold) {
222           numberOfFailures++;
223           return false;
224         } else {
225           return true;
226         }
227       }
228 
229       @Override
230       protected void chore() {
231         assertTrue(numberOfFailures == failureThreshold);
232         cancel(false);
233       }
234 
235     }
236   }
237 
238   @Test (timeout=20000)
239   public void testInitialChorePrecedence() throws InterruptedException {
240     ChoreService service = new ChoreService("testInitialChorePrecedence");
241 
242     final int period = 100;
243     final int failureThreshold = 5;
244 
245     try {
246       ScheduledChore chore = new FailInitialChore("chore", period, failureThreshold);
247       service.scheduleChore(chore);
248 
249       int loopCount = 0;
250       boolean brokeOutOfLoop = false;
251 
252      while (!chore.isInitialChoreComplete() && chore.isScheduled()) {
253        Thread.sleep(failureThreshold * period);
254        loopCount++;
255        if (loopCount > 3) {
256          brokeOutOfLoop = true;
257          break;
258        }
259     }
260 
261     assertFalse(brokeOutOfLoop);
262     } finally {
263       shutdownService(service);
264     }
265   }
266 
267   @Test (timeout=20000)
268   public void testCancelChore() throws InterruptedException {
269     final int period = 100;
270     ScheduledChore chore1 = new DoNothingChore("chore1", period);
271     ChoreService service = new ChoreService("testCancelChore");
272     try {
273       service.scheduleChore(chore1);
274       assertTrue(chore1.isScheduled());
275 
276       chore1.cancel(true);
277       assertFalse(chore1.isScheduled());
278       assertTrue(service.getNumberOfScheduledChores() == 0);
279     } finally {
280       shutdownService(service);
281     }
282   }
283 
284   @Test (timeout=20000)
285   public void testScheduledChoreConstruction() {
286     final String NAME = "chore";
287     final int PERIOD = 100;
288     final long VALID_DELAY = 0;
289     final long INVALID_DELAY = -100;
290     final TimeUnit UNIT = TimeUnit.NANOSECONDS;
291 
292     ScheduledChore chore1 =
293         new ScheduledChore(NAME, new SampleStopper(), PERIOD, VALID_DELAY, UNIT) {
294       @Override
295       protected void chore() {
296         // DO NOTHING
297       }
298     };
299 
300     assertEquals("Name construction failed", NAME, chore1.getName());
301     assertEquals("Period construction failed", PERIOD, chore1.getPeriod());
302     assertEquals("Initial Delay construction failed", VALID_DELAY, chore1.getInitialDelay());
303     assertEquals("TimeUnit construction failed", UNIT, chore1.getTimeUnit());
304 
305     ScheduledChore invalidDelayChore =
306         new ScheduledChore(NAME, new SampleStopper(), PERIOD, INVALID_DELAY, UNIT) {
307       @Override
308       protected void chore() {
309         // DO NOTHING
310       }
311     };
312 
313     assertEquals("Initial Delay should be set to 0 when invalid", 0,
314       invalidDelayChore.getInitialDelay());
315   }
316 
317   @Test (timeout=20000)
318   public void testChoreServiceConstruction() throws InterruptedException {
319     final int corePoolSize = 10;
320     final int defaultCorePoolSize = ChoreService.MIN_CORE_POOL_SIZE;
321 
322     ChoreService customInit = new ChoreService("testChoreServiceConstruction_custom", corePoolSize, false);
323     try {
324       assertEquals(corePoolSize, customInit.getCorePoolSize());
325     } finally {
326       shutdownService(customInit);
327     }
328 
329     ChoreService defaultInit = new ChoreService("testChoreServiceConstruction_default");
330     try {
331       assertEquals(defaultCorePoolSize, defaultInit.getCorePoolSize());
332     } finally {
333       shutdownService(defaultInit);
334     }
335 
336     ChoreService invalidInit = new ChoreService("testChoreServiceConstruction_invalid", -10, false);
337     try {
338       assertEquals(defaultCorePoolSize, invalidInit.getCorePoolSize());
339     } finally {
340       shutdownService(invalidInit);
341     }
342   }
343 
344   @Test (timeout=20000)
345   public void testFrequencyOfChores() throws InterruptedException {
346     final int period = 100;
347     // Small delta that acts as time buffer (allowing chores to complete if running slowly)
348     final int delta = period/5;
349     ChoreService service = new ChoreService("testFrequencyOfChores");
350     CountingChore chore = new CountingChore("countingChore", period);
351     try {
352       service.scheduleChore(chore);
353 
354       Thread.sleep(10 * period + delta);
355       assertEquals("10 periods have elapsed.", 11, chore.getCountOfChoreCalls());
356 
357       Thread.sleep(10 * period + delta);
358       assertEquals("20 periods have elapsed.", 21, chore.getCountOfChoreCalls());
359     } finally {
360       shutdownService(service);
361     }
362   }
363 
364   public void shutdownService(ChoreService service) throws InterruptedException {
365     service.shutdown();
366     while (!service.isTerminated()) {
367       Thread.sleep(100);
368     }
369   }
370 
371   @Test (timeout=20000)
372   public void testForceTrigger() throws InterruptedException {
373     final int period = 100;
374     final int delta = period/10;
375     ChoreService service = new ChoreService("testForceTrigger");
376     final CountingChore chore = new CountingChore("countingChore", period);
377     try {
378       service.scheduleChore(chore);
379       Thread.sleep(10 * period + delta);
380 
381       assertEquals("10 periods have elapsed.", 11, chore.getCountOfChoreCalls());
382 
383       // Force five runs of the chore to occur, sleeping between triggers to ensure the
384       // chore has time to run
385       chore.triggerNow();
386       Thread.sleep(delta);
387       chore.triggerNow();
388       Thread.sleep(delta);
389       chore.triggerNow();
390       Thread.sleep(delta);
391       chore.triggerNow();
392       Thread.sleep(delta);
393       chore.triggerNow();
394       Thread.sleep(delta);
395 
396       assertEquals("Trigger was called 5 times after 10 periods.", 16,
397           chore.getCountOfChoreCalls());
398 
399       Thread.sleep(10 * period + delta);
400 
401       // Be loosey-goosey. It used to be '26' but it was a big flakey relying on timing.
402       assertTrue("Expected at least 16 invocations, instead got " + chore.getCountOfChoreCalls(),
403           chore.getCountOfChoreCalls() > 16);
404     } finally {
405       shutdownService(service);
406     }
407   }
408 
409   @Test (timeout=20000)
410   public void testCorePoolIncrease() throws InterruptedException {
411     final int initialCorePoolSize = 3;
412     ChoreService service = new ChoreService("testCorePoolIncrease", initialCorePoolSize, false);
413 
414     try {
415       assertEquals("Setting core pool size gave unexpected results.", initialCorePoolSize,
416         service.getCorePoolSize());
417 
418       final int slowChorePeriod = 100;
419       SlowChore slowChore1 = new SlowChore("slowChore1", slowChorePeriod);
420       SlowChore slowChore2 = new SlowChore("slowChore2", slowChorePeriod);
421       SlowChore slowChore3 = new SlowChore("slowChore3", slowChorePeriod);
422 
423       service.scheduleChore(slowChore1);
424       service.scheduleChore(slowChore2);
425       service.scheduleChore(slowChore3);
426 
427       Thread.sleep(slowChorePeriod * 10);
428       assertEquals("Should not create more pools than scheduled chores", 3,
429         service.getCorePoolSize());
430 
431       SlowChore slowChore4 = new SlowChore("slowChore4", slowChorePeriod);
432       service.scheduleChore(slowChore4);
433 
434       Thread.sleep(slowChorePeriod * 10);
435       assertEquals("Chores are missing their start time. Should expand core pool size", 4,
436         service.getCorePoolSize());
437 
438       SlowChore slowChore5 = new SlowChore("slowChore5", slowChorePeriod);
439       service.scheduleChore(slowChore5);
440 
441       Thread.sleep(slowChorePeriod * 10);
442       assertEquals("Chores are missing their start time. Should expand core pool size", 5,
443         service.getCorePoolSize());
444     } finally {
445       shutdownService(service);
446     }
447   }
448 
449   @Test(timeout = 30000)
450   public void testCorePoolDecrease() throws InterruptedException {
451     final int initialCorePoolSize = 3;
452     ChoreService service = new ChoreService("testCorePoolDecrease", initialCorePoolSize, false);
453     final int chorePeriod = 100;
454     try {
455       // Slow chores always miss their start time and thus the core pool size should be at least as
456       // large as the number of running slow chores
457       SlowChore slowChore1 = new SlowChore("slowChore1", chorePeriod);
458       SlowChore slowChore2 = new SlowChore("slowChore2", chorePeriod);
459       SlowChore slowChore3 = new SlowChore("slowChore3", chorePeriod);
460 
461       service.scheduleChore(slowChore1);
462       service.scheduleChore(slowChore2);
463       service.scheduleChore(slowChore3);
464 
465       Thread.sleep(chorePeriod * 10);
466       assertEquals("Should not create more pools than scheduled chores",
467         service.getNumberOfScheduledChores(), service.getCorePoolSize());
468 
469       SlowChore slowChore4 = new SlowChore("slowChore4", chorePeriod);
470       service.scheduleChore(slowChore4);
471       Thread.sleep(chorePeriod * 10);
472       assertEquals("Chores are missing their start time. Should expand core pool size",
473         service.getNumberOfScheduledChores(), service.getCorePoolSize());
474 
475       SlowChore slowChore5 = new SlowChore("slowChore5", chorePeriod);
476       service.scheduleChore(slowChore5);
477       Thread.sleep(chorePeriod * 10);
478       assertEquals("Chores are missing their start time. Should expand core pool size",
479         service.getNumberOfScheduledChores(), service.getCorePoolSize());
480       assertEquals(5, service.getNumberOfChoresMissingStartTime());
481 
482       // Now we begin to cancel the chores that caused an increase in the core thread pool of the
483       // ChoreService. These cancellations should cause a decrease in the core thread pool.
484       slowChore5.cancel();
485       Thread.sleep(chorePeriod * 10);
486       assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()),
487         service.getCorePoolSize());
488       assertEquals(4, service.getNumberOfChoresMissingStartTime());
489 
490       slowChore4.cancel();
491       Thread.sleep(chorePeriod * 10);
492       assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()),
493         service.getCorePoolSize());
494       assertEquals(3, service.getNumberOfChoresMissingStartTime());
495 
496       slowChore3.cancel();
497       Thread.sleep(chorePeriod * 10);
498       assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()),
499         service.getCorePoolSize());
500       assertEquals(2, service.getNumberOfChoresMissingStartTime());
501 
502       slowChore2.cancel();
503       Thread.sleep(chorePeriod * 10);
504       assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()),
505         service.getCorePoolSize());
506       assertEquals(1, service.getNumberOfChoresMissingStartTime());
507 
508       slowChore1.cancel();
509       Thread.sleep(chorePeriod * 10);
510       assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()),
511         service.getCorePoolSize());
512       assertEquals(0, service.getNumberOfChoresMissingStartTime());
513     } finally {
514       shutdownService(service);
515     }
516   }
517 
518   @Test (timeout=20000)
519   public void testNumberOfRunningChores() throws InterruptedException {
520     ChoreService service = new ChoreService("testNumberOfRunningChores");
521 
522     final int period = 100;
523     final int sleepTime = 5;
524 
525     try {
526       DoNothingChore dn1 = new DoNothingChore("dn1", period);
527       DoNothingChore dn2 = new DoNothingChore("dn2", period);
528       DoNothingChore dn3 = new DoNothingChore("dn3", period);
529       DoNothingChore dn4 = new DoNothingChore("dn4", period);
530       DoNothingChore dn5 = new DoNothingChore("dn5", period);
531 
532       service.scheduleChore(dn1);
533       service.scheduleChore(dn2);
534       service.scheduleChore(dn3);
535       service.scheduleChore(dn4);
536       service.scheduleChore(dn5);
537 
538       Thread.sleep(sleepTime);
539       assertEquals("Scheduled chore mismatch", 5, service.getNumberOfScheduledChores());
540 
541       dn1.cancel();
542       Thread.sleep(sleepTime);
543       assertEquals("Scheduled chore mismatch", 4, service.getNumberOfScheduledChores());
544 
545       dn2.cancel();
546       dn3.cancel();
547       dn4.cancel();
548       Thread.sleep(sleepTime);
549       assertEquals("Scheduled chore mismatch", 1, service.getNumberOfScheduledChores());
550 
551       dn5.cancel();
552       Thread.sleep(sleepTime);
553       assertEquals("Scheduled chore mismatch", 0, service.getNumberOfScheduledChores());
554     } finally {
555       shutdownService(service);
556     }
557   }
558 
559   @Test (timeout=20000)
560   public void testNumberOfChoresMissingStartTime() throws InterruptedException {
561     ChoreService service = new ChoreService("testNumberOfChoresMissingStartTime");
562 
563     final int period = 100;
564     final int sleepTime = 5 * period;
565 
566     try {
567       // Slow chores sleep for a length of time LONGER than their period. Thus, SlowChores
568       // ALWAYS miss their start time since their execution takes longer than their period
569       SlowChore sc1 = new SlowChore("sc1", period);
570       SlowChore sc2 = new SlowChore("sc2", period);
571       SlowChore sc3 = new SlowChore("sc3", period);
572       SlowChore sc4 = new SlowChore("sc4", period);
573       SlowChore sc5 = new SlowChore("sc5", period);
574 
575       service.scheduleChore(sc1);
576       service.scheduleChore(sc2);
577       service.scheduleChore(sc3);
578       service.scheduleChore(sc4);
579       service.scheduleChore(sc5);
580 
581       Thread.sleep(sleepTime);
582       assertEquals(5, service.getNumberOfChoresMissingStartTime());
583 
584       sc1.cancel();
585       Thread.sleep(sleepTime);
586       assertEquals(4, service.getNumberOfChoresMissingStartTime());
587 
588       sc2.cancel();
589       sc3.cancel();
590       sc4.cancel();
591       Thread.sleep(sleepTime);
592       assertEquals(1, service.getNumberOfChoresMissingStartTime());
593 
594       sc5.cancel();
595       Thread.sleep(sleepTime);
596       assertEquals(0, service.getNumberOfChoresMissingStartTime());
597     } finally {
598       shutdownService(service);
599     }
600   }
601 
602   /**
603    * ChoreServices should never have a core pool size that exceeds the number of chores that have
604    * been scheduled with the service. For example, if 4 ScheduledChores are scheduled with a
605    * ChoreService, the number of threads in the ChoreService's core pool should never exceed 4
606    */
607   @Test (timeout=20000)
608   public void testMaximumChoreServiceThreads() throws InterruptedException {
609     ChoreService service = new ChoreService("testMaximumChoreServiceThreads");
610 
611     final int period = 100;
612     final int sleepTime = 5 * period;
613 
614     try {
615       // Slow chores sleep for a length of time LONGER than their period. Thus, SlowChores
616       // ALWAYS miss their start time since their execution takes longer than their period.
617       // Chores that miss their start time will trigger the onChoreMissedStartTime callback
618       // in the ChoreService. This callback will try to increase the number of core pool
619       // threads.
620       SlowChore sc1 = new SlowChore("sc1", period);
621       SlowChore sc2 = new SlowChore("sc2", period);
622       SlowChore sc3 = new SlowChore("sc3", period);
623       SlowChore sc4 = new SlowChore("sc4", period);
624       SlowChore sc5 = new SlowChore("sc5", period);
625 
626       service.scheduleChore(sc1);
627       service.scheduleChore(sc2);
628       service.scheduleChore(sc3);
629       service.scheduleChore(sc4);
630       service.scheduleChore(sc5);
631 
632       Thread.sleep(sleepTime);
633       assertTrue(service.getCorePoolSize() <= service.getNumberOfScheduledChores());
634 
635       SlowChore sc6 = new SlowChore("sc6", period);
636       SlowChore sc7 = new SlowChore("sc7", period);
637       SlowChore sc8 = new SlowChore("sc8", period);
638       SlowChore sc9 = new SlowChore("sc9", period);
639       SlowChore sc10 = new SlowChore("sc10", period);
640 
641       service.scheduleChore(sc6);
642       service.scheduleChore(sc7);
643       service.scheduleChore(sc8);
644       service.scheduleChore(sc9);
645       service.scheduleChore(sc10);
646 
647       Thread.sleep(sleepTime);
648       assertTrue(service.getCorePoolSize() <= service.getNumberOfScheduledChores());
649     } finally {
650       shutdownService(service);
651     }
652   }
653 
654   @Test (timeout=20000)
655   public void testChangingChoreServices() throws InterruptedException {
656     final int period = 100;
657     final int sleepTime = 10;
658     ChoreService service1 = new ChoreService("testChangingChoreServices_1");
659     ChoreService service2 = new ChoreService("testChangingChoreServices_2");
660     ScheduledChore chore = new DoNothingChore("sample", period);
661 
662     try {
663       assertFalse(chore.isScheduled());
664       assertFalse(service1.isChoreScheduled(chore));
665       assertFalse(service2.isChoreScheduled(chore));
666       assertTrue(chore.getChoreServicer() == null);
667 
668       service1.scheduleChore(chore);
669       Thread.sleep(sleepTime);
670       assertTrue(chore.isScheduled());
671       assertTrue(service1.isChoreScheduled(chore));
672       assertFalse(service2.isChoreScheduled(chore));
673       assertFalse(chore.getChoreServicer() == null);
674 
675       service2.scheduleChore(chore);
676       Thread.sleep(sleepTime);
677       assertTrue(chore.isScheduled());
678       assertFalse(service1.isChoreScheduled(chore));
679       assertTrue(service2.isChoreScheduled(chore));
680       assertFalse(chore.getChoreServicer() == null);
681 
682       chore.cancel();
683       assertFalse(chore.isScheduled());
684       assertFalse(service1.isChoreScheduled(chore));
685       assertFalse(service2.isChoreScheduled(chore));
686       assertTrue(chore.getChoreServicer() == null);
687     } finally {
688       shutdownService(service1);
689       shutdownService(service2);
690     }
691   }
692 
693   @Test (timeout=20000)
694   public void testStopperForScheduledChores() throws InterruptedException {
695     ChoreService service = new ChoreService("testStopperForScheduledChores");
696     Stoppable stopperForGroup1 = new SampleStopper();
697     Stoppable stopperForGroup2 = new SampleStopper();
698     final int period = 100;
699     final int delta = period/10;
700 
701     try {
702       ScheduledChore chore1_group1 = new DoNothingChore("c1g1", stopperForGroup1, period);
703       ScheduledChore chore2_group1 = new DoNothingChore("c2g1", stopperForGroup1, period);
704       ScheduledChore chore3_group1 = new DoNothingChore("c3g1", stopperForGroup1, period);
705 
706       ScheduledChore chore1_group2 = new DoNothingChore("c1g2", stopperForGroup2, period);
707       ScheduledChore chore2_group2 = new DoNothingChore("c2g2", stopperForGroup2, period);
708       ScheduledChore chore3_group2 = new DoNothingChore("c3g2", stopperForGroup2, period);
709 
710       service.scheduleChore(chore1_group1);
711       service.scheduleChore(chore2_group1);
712       service.scheduleChore(chore3_group1);
713       service.scheduleChore(chore1_group2);
714       service.scheduleChore(chore2_group2);
715       service.scheduleChore(chore3_group2);
716 
717       Thread.sleep(delta);
718       Thread.sleep(10 * period);
719       assertTrue(chore1_group1.isScheduled());
720       assertTrue(chore2_group1.isScheduled());
721       assertTrue(chore3_group1.isScheduled());
722       assertTrue(chore1_group2.isScheduled());
723       assertTrue(chore2_group2.isScheduled());
724       assertTrue(chore3_group2.isScheduled());
725 
726       stopperForGroup1.stop("test stopping group 1");
727       Thread.sleep(period);
728       assertFalse(chore1_group1.isScheduled());
729       assertFalse(chore2_group1.isScheduled());
730       assertFalse(chore3_group1.isScheduled());
731       assertTrue(chore1_group2.isScheduled());
732       assertTrue(chore2_group2.isScheduled());
733       assertTrue(chore3_group2.isScheduled());
734 
735       stopperForGroup2.stop("test stopping group 2");
736       Thread.sleep(period);
737       assertFalse(chore1_group1.isScheduled());
738       assertFalse(chore2_group1.isScheduled());
739       assertFalse(chore3_group1.isScheduled());
740       assertFalse(chore1_group2.isScheduled());
741       assertFalse(chore2_group2.isScheduled());
742       assertFalse(chore3_group2.isScheduled());
743     } finally {
744       shutdownService(service);
745     }
746   }
747 
748   @Test (timeout=20000)
749   public void testShutdownCancelsScheduledChores() throws InterruptedException {
750     final int period = 100;
751     ChoreService service = new ChoreService("testShutdownCancelsScheduledChores");
752     ScheduledChore successChore1 = new DoNothingChore("sc1", period);
753     ScheduledChore successChore2 = new DoNothingChore("sc2", period);
754     ScheduledChore successChore3 = new DoNothingChore("sc3", period);
755 
756     try {
757       assertTrue(service.scheduleChore(successChore1));
758       assertTrue(successChore1.isScheduled());
759       assertTrue(service.scheduleChore(successChore2));
760       assertTrue(successChore2.isScheduled());
761       assertTrue(service.scheduleChore(successChore3));
762       assertTrue(successChore3.isScheduled());
763     } finally {
764       shutdownService(service);
765     }
766 
767     assertFalse(successChore1.isScheduled());
768     assertFalse(successChore2.isScheduled());
769     assertFalse(successChore3.isScheduled());
770   }
771 
772   @Test (timeout=20000)
773   public void testShutdownWorksWhileChoresAreExecuting() throws InterruptedException {
774     final int period = 100;
775     final int sleep = 5 * period;
776     ChoreService service = new ChoreService("testShutdownWorksWhileChoresAreExecuting");
777     ScheduledChore slowChore1 = new SleepingChore("sc1", period, sleep);
778     ScheduledChore slowChore2 = new SleepingChore("sc2", period, sleep);
779     ScheduledChore slowChore3 = new SleepingChore("sc3", period, sleep);
780     try {
781       assertTrue(service.scheduleChore(slowChore1));
782       assertTrue(service.scheduleChore(slowChore2));
783       assertTrue(service.scheduleChore(slowChore3));
784 
785       Thread.sleep(sleep / 2);
786       shutdownService(service);
787 
788       assertFalse(slowChore1.isScheduled());
789       assertFalse(slowChore2.isScheduled());
790       assertFalse(slowChore3.isScheduled());
791       assertTrue(service.isShutdown());
792 
793       Thread.sleep(5);
794       assertTrue(service.isTerminated());
795     } finally {
796       shutdownService(service);
797     }
798   }
799 
800   @Test (timeout=20000)
801   public void testShutdownRejectsNewSchedules() throws InterruptedException {
802     final int period = 100;
803     ChoreService service = new ChoreService("testShutdownRejectsNewSchedules");
804     ScheduledChore successChore1 = new DoNothingChore("sc1", period);
805     ScheduledChore successChore2 = new DoNothingChore("sc2", period);
806     ScheduledChore successChore3 = new DoNothingChore("sc3", period);
807     ScheduledChore failChore1 = new DoNothingChore("fc1", period);
808     ScheduledChore failChore2 = new DoNothingChore("fc2", period);
809     ScheduledChore failChore3 = new DoNothingChore("fc3", period);
810 
811     try {
812       assertTrue(service.scheduleChore(successChore1));
813       assertTrue(successChore1.isScheduled());
814       assertTrue(service.scheduleChore(successChore2));
815       assertTrue(successChore2.isScheduled());
816       assertTrue(service.scheduleChore(successChore3));
817       assertTrue(successChore3.isScheduled());
818     } finally {
819       shutdownService(service);
820     }
821 
822     assertFalse(service.scheduleChore(failChore1));
823     assertFalse(failChore1.isScheduled());
824     assertFalse(service.scheduleChore(failChore2));
825     assertFalse(failChore2.isScheduled());
826     assertFalse(service.scheduleChore(failChore3));
827     assertFalse(failChore3.isScheduled());
828   }
829 }