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