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