From c2bb83ce739a96c3e5421c672ba362e5c4e9a797 Mon Sep 17 00:00:00 2001 From: dsyer Date: Tue, 1 Apr 2008 09:49:11 +0000 Subject: [PATCH] RESOLVED - issue BATCH-198: Make backoff policy tests less sensitive to virtualisation --- .../backoff/ExponentialBackOffPolicy.java | 17 ++++-- .../retry/backoff/FixedBackOffPolicy.java | 20 +++++-- .../retry/backoff/ObjectWaitSleeper.java | 37 ++++++++++++ .../batch/retry/backoff/Sleeper.java | 33 +++++++++++ .../retry/backoff/StatelessBackOffPolicy.java | 1 - .../batch/retry/backoff/DummySleeper.java | 58 +++++++++++++++++++ .../ExponentialBackOffPolicyTests.java | 29 ++++------ .../backoff/FixedBackOffPolicyTests.java | 26 ++++----- .../retry/backoff/ObjectWaitSleeperTests.java | 42 ++++++++++++++ 9 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ObjectWaitSleeper.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/Sleeper.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/DummySleeper.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ObjectWaitSleeperTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicy.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicy.java index e32acd755..12e73d69b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicy.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicy.java @@ -31,7 +31,6 @@ import org.springframework.util.ClassUtils; * * @author Rob Harrop * @author Dave Syer - * @since 2.1 */ public class ExponentialBackOffPolicy implements BackOffPolicy { @@ -67,6 +66,16 @@ public class ExponentialBackOffPolicy implements BackOffPolicy { */ private volatile double multiplier = DEFAULT_MULTIPLIER; + private Sleeper sleeper = new ObjectWaitSleeper(); + + /** + * Public setter for the {@link Sleeper} strategy. + * @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}. + */ + public void setSleeper(Sleeper sleeper) { + this.sleeper = sleeper; + } + /** * Set the initial sleep interval value. Default is 1 * millisecond. Cannot be set to a value less than one. @@ -107,9 +116,7 @@ public class ExponentialBackOffPolicy implements BackOffPolicy { public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException { ExponentialBackOffContext context = (ExponentialBackOffContext) backOffContext; try { - synchronized (context) { - context.wait(context.getSleepAndIncrement()); - } + sleeper.sleep(context.getSleepAndIncrement()); } catch (InterruptedException e) { throw new BackOffInterruptedException("Thread interrupted while sleeping", e); @@ -130,7 +137,7 @@ public class ExponentialBackOffPolicy implements BackOffPolicy { this.maxInterval = maxInterval; } - public long getSleepAndIncrement() { + public synchronized long getSleepAndIncrement() { long sleep = this.interval; if (sleep > maxInterval) { sleep = (long) maxInterval; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/FixedBackOffPolicy.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/FixedBackOffPolicy.java index 8f2cb2295..efc313c0a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/FixedBackOffPolicy.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/FixedBackOffPolicy.java @@ -25,7 +25,7 @@ package org.springframework.batch.retry.backoff; * this may cause a single retry operation to have pauses of different * intervals. * @author Rob Harrop - * @since 2.1 + * @author Dave Syer */ public class FixedBackOffPolicy extends StatelessBackOffPolicy { @@ -39,6 +39,17 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy { */ private volatile long backOffPeriod = DEFAULT_BACK_OFF_PERIOD; + + private Sleeper sleeper = new ObjectWaitSleeper(); + + /** + * Public setter for the {@link Sleeper} strategy. + * @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}. + */ + public void setSleeper(Sleeper sleeper) { + this.sleeper = sleeper; + } + /** * Set the back off period in milliseconds. Cannot be < 1. Default value * is 1000ms. @@ -48,15 +59,12 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy { } /** - * Pause for the {@link #backOffPeriod} using {@link Thread#sleep}. + * Pause for the {@link #backOffPeriod}. * @throws BackOffInterruptedException if interrupted during sleep. */ protected void doBackOff() throws BackOffInterruptedException { try { - Object mutex = new Object(); - synchronized (mutex) { - mutex.wait(this.backOffPeriod); - } + sleeper.sleep(backOffPeriod); } catch (InterruptedException e) { throw new BackOffInterruptedException("Thread interrupted while sleeping", e); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ObjectWaitSleeper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ObjectWaitSleeper.java new file mode 100644 index 000000000..254b43a67 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/ObjectWaitSleeper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.retry.backoff; + +/** + * Simple {@link Sleeper} implementation that just waits on a local Object. + * + * @author Dave Syer + * + */ +public class ObjectWaitSleeper implements Sleeper { + + /* + * (non-Javadoc) + * @see org.springframework.batch.retry.backoff.Sleeper#sleep(long) + */ + public void sleep(long backOffPeriod) throws InterruptedException { + Object mutex = new Object(); + synchronized (mutex) { + mutex.wait(backOffPeriod); + } + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/Sleeper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/Sleeper.java new file mode 100644 index 000000000..d547b4163 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/Sleeper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.retry.backoff; + +/** + * Strategy interface for backoff policies to delegate the pausing of execution. + * + * @author Dave Syer + * + */ +public interface Sleeper { + + /** + * Pause for the specified period using whatever means available. + * + * @param backOffPeriod + */ + void sleep(long backOffPeriod) throws InterruptedException; + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/StatelessBackOffPolicy.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/StatelessBackOffPolicy.java index bf5f610fc..836459e74 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/StatelessBackOffPolicy.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/backoff/StatelessBackOffPolicy.java @@ -24,7 +24,6 @@ import org.springframework.batch.retry.RetryContext; * * @author Rob Harrop * @author Dave Syer - * @since 2.1 */ public abstract class StatelessBackOffPolicy implements BackOffPolicy { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/DummySleeper.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/DummySleeper.java new file mode 100644 index 000000000..cf0991079 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/DummySleeper.java @@ -0,0 +1,58 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.retry.backoff; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Simple {@link Sleeper} implementation that just waits on a local Object. + * + * @author Dave Syer + * + */ +public class DummySleeper implements Sleeper { + + private List backOffs = new ArrayList(); + + /** + * Public getter for the long. + * @return the lastBackOff + */ + public long getLastBackOff() { + return ((Long) backOffs.get(backOffs.size()-1)).longValue(); + } + + public long[] getBackOffs() { + long[] result = new long[backOffs.size()]; + int i = 0; + for (Iterator iterator = backOffs.iterator(); iterator.hasNext();) { + Long value = (Long) iterator.next(); + result[i++] =value.longValue(); + } + return result ; + } + + /* + * (non-Javadoc) + * @see org.springframework.batch.retry.backoff.Sleeper#sleep(long) + */ + public void sleep(long backOffPeriod) throws InterruptedException { + this.backOffs.add(new Long(backOffPeriod)); + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicyTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicyTests.java index f28330ca4..558d2bea5 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicyTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ExponentialBackOffPolicyTests.java @@ -21,10 +21,11 @@ import junit.framework.TestCase; /** * @author Rob Harrop * @author Dave Syer - * @since 2.1 */ public class ExponentialBackOffPolicyTests extends TestCase { + private DummySleeper sleeper = new DummySleeper(); + public void testSetMaxInterval() throws Exception { ExponentialBackOffPolicy strategy = new ExponentialBackOffPolicy(); strategy.setMaxInterval(1000); @@ -52,44 +53,34 @@ public class ExponentialBackOffPolicyTests extends TestCase { public void testSingleBackOff() throws Exception { ExponentialBackOffPolicy strategy = new ExponentialBackOffPolicy(); + strategy.setSleeper(sleeper); BackOffContext context = strategy.start(null); - long before = System.currentTimeMillis(); strategy.backOff(context); - long after = System.currentTimeMillis(); - assertEqualsApprox(ExponentialBackOffPolicy.DEFAULT_INITIAL_INTERVAL, after - before, 30); + assertEquals(ExponentialBackOffPolicy.DEFAULT_INITIAL_INTERVAL, sleeper.getLastBackOff()); } public void testMaximumBackOff() throws Exception { ExponentialBackOffPolicy strategy = new ExponentialBackOffPolicy(); strategy.setMaxInterval(50); + strategy.setSleeper(sleeper); BackOffContext context = strategy.start(null); - long before = System.currentTimeMillis(); strategy.backOff(context); - long after = System.currentTimeMillis(); - assertEqualsApprox(50L, after - before, 15); + assertEquals(50, sleeper.getLastBackOff()); } public void testMultiBackOff() throws Exception { ExponentialBackOffPolicy strategy = new ExponentialBackOffPolicy(); - long seed = 40; // not too small or Windoze won't resolve the difference - double multiplier = 1.2; // not too large or the test takes ages! + long seed = 40; + double multiplier = 1.2; strategy.setInitialInterval(seed); strategy.setMultiplier(multiplier); + strategy.setSleeper(sleeper); BackOffContext context = strategy.start(null); for (int x = 0; x < 5; x++) { - long before = System.currentTimeMillis(); strategy.backOff(context); - long after = System.currentTimeMillis(); - assertFalse(after == before); - assertEqualsApprox(seed, after - before, 20); + assertEquals(seed, sleeper.getLastBackOff()); seed *= multiplier; } } - private void assertEqualsApprox(long desired, long actual, long variance) { - long lower = desired - variance; - long upper = desired + 5 * variance / 2; - assertTrue("Expected value to be between '" + lower + "' and '" + upper + "' but was '" + actual + "'", - lower <= actual); - } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/FixedBackOffPolicyTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/FixedBackOffPolicyTests.java index b72a270c8..dd84843bb 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/FixedBackOffPolicyTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/FixedBackOffPolicyTests.java @@ -25,42 +25,38 @@ import junit.framework.TestCase; */ public class FixedBackOffPolicyTests extends TestCase { + private DummySleeper sleeper = new DummySleeper(); + public void testSetBackoffPeriodNegative() throws Exception { FixedBackOffPolicy strategy = new FixedBackOffPolicy(); strategy.setBackOffPeriod(-1000L); - long before = System.currentTimeMillis(); + strategy.setSleeper(sleeper); strategy.backOff(null); - long after = System.currentTimeMillis(); // We should see a zero backoff if we try to set it negative - assertEqualsApprox(0, after - before, 25); + assertEquals(1, sleeper.getBackOffs().length); + assertEquals(1, sleeper.getLastBackOff()); } public void testSingleBackOff() throws Exception { int backOffPeriod = 50; FixedBackOffPolicy strategy = new FixedBackOffPolicy(); strategy.setBackOffPeriod(backOffPeriod); - long before = System.currentTimeMillis(); + strategy.setSleeper(sleeper); strategy.backOff(null); - long after = System.currentTimeMillis(); - assertEqualsApprox(backOffPeriod, after - before, 25); + assertEquals(1, sleeper.getBackOffs().length); + assertEquals(backOffPeriod, sleeper.getLastBackOff()); } public void testManyBackOffCalls() throws Exception { int backOffPeriod = 50; FixedBackOffPolicy strategy = new FixedBackOffPolicy(); strategy.setBackOffPeriod(backOffPeriod); + strategy.setSleeper(sleeper); for (int x = 0; x < 10; x++) { - long before = System.currentTimeMillis(); strategy.backOff(null); - long after = System.currentTimeMillis(); - assertEqualsApprox(backOffPeriod, after - before, 25); + assertEquals(backOffPeriod, sleeper.getLastBackOff()); } + assertEquals(10, sleeper.getBackOffs().length); } - private void assertEqualsApprox(long desired, long actual, long variance) { - long lower = desired - variance; - long upper = desired + 2 * variance; - assertTrue("Expected value to be between '" + lower + "' and '" + upper + "' but was '" + actual + "'", - lower <= actual); - } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ObjectWaitSleeperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ObjectWaitSleeperTests.java new file mode 100644 index 000000000..6c26511a9 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/backoff/ObjectWaitSleeperTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.retry.backoff; + +import junit.framework.TestCase; + +/** + * @author Dave Syer + */ +public class ObjectWaitSleeperTests extends TestCase { + + public void testSingleBackOff() throws Exception { + long backOffPeriod = 50; + ObjectWaitSleeper strategy = new ObjectWaitSleeper(); + long before = System.currentTimeMillis(); + strategy.sleep(backOffPeriod); + long after = System.currentTimeMillis(); + assertEqualsApprox(backOffPeriod, after - before, 25); + } + + private void assertEqualsApprox(long desired, long actual, long variance) { + long lower = desired - variance; + long upper = desired + 2 * variance; + assertTrue("Expected value to be between '" + lower + "' and '" + upper + "' but was '" + actual + "'", + lower <= actual); + } + +}