diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java index b860698a78..615a98d950 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java @@ -29,6 +29,10 @@ public class DevToolsProperties { private static final String DEFAULT_RESTART_EXCLUDES = "META-INF/resources/**,resource/**,static/**,public/**,templates/**"; + private static final long DEFAULT_RESTART_POLL_INTERVAL = 1000; + + private static final long DEFAULT_RESTART_QUIET_PERIOD = 400; + private Restart restart = new Restart(); private Livereload livereload = new Livereload(); @@ -62,6 +66,17 @@ public class DevToolsProperties { */ private String exclude = DEFAULT_RESTART_EXCLUDES; + /** + * Amount of time (in milliseconds) to wait between polling for classpath changes. + */ + private long pollInterval = DEFAULT_RESTART_POLL_INTERVAL; + + /** + * Amount of quiet time (in milliseconds) requited without any classpath changes + * before a restart is triggered. + */ + private long quietPeriod = DEFAULT_RESTART_QUIET_PERIOD; + /** * The name of specific that that when changed will will trigger the restart. If * not specified any classpath file change will trigger the restart. @@ -84,6 +99,22 @@ public class DevToolsProperties { this.exclude = exclude; } + public long getPollInterval() { + return this.pollInterval; + } + + public void setPollInterval(long pollInterval) { + this.pollInterval = pollInterval; + } + + public long getQuietPeriod() { + return this.quietPeriod; + } + + public void setQuietPeriod(long quietPeriod) { + this.quietPeriod = quietPeriod; + } + public String getTriggerFile() { return this.triggerFile; } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java index 41b1423bbc..add8581c9c 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.devtools.autoconfigure.DevToolsProperties.Restart; import org.springframework.boot.devtools.classpath.ClassPathChangedEvent; import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher; import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy; @@ -130,8 +131,11 @@ public class LocalDevToolsAutoConfiguration { @Bean public FileSystemWatcher getFileSystemWatcher() { - FileSystemWatcher watcher = new FileSystemWatcher(); - String triggerFile = this.properties.getRestart().getTriggerFile(); + Restart restartProperties = this.properties.getRestart(); + FileSystemWatcher watcher = new FileSystemWatcher(true, + restartProperties.getPollInterval(), + restartProperties.getQuietPeriod()); + String triggerFile = restartProperties.getTriggerFile(); if (StringUtils.hasLength(triggerFile)) { watcher.setTriggerFilter(new TriggerFileFilter(triggerFile)); } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java index 81e3c992ea..a8b4e80dca 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java @@ -40,17 +40,17 @@ import org.springframework.util.Assert; */ public class FileSystemWatcher { - private static final long DEFAULT_IDLE_TIME = 400; + private static final long DEFAULT_POLL_INTERVAL = 1000; - private static final long DEFAULT_QUIET_TIME = 200; + private static final long DEFAULT_QUIET_PERIOD = 400; private List listeners = new ArrayList(); private final boolean daemon; - private final long idleTime; + private final long pollInterval; - private final long quietTime; + private final long quietPeriod; private Thread watchThread; @@ -64,20 +64,24 @@ public class FileSystemWatcher { * Create a new {@link FileSystemWatcher} instance. */ public FileSystemWatcher() { - this(true, DEFAULT_IDLE_TIME, DEFAULT_QUIET_TIME); + this(true, DEFAULT_POLL_INTERVAL, DEFAULT_QUIET_PERIOD); } /** * Create a new {@link FileSystemWatcher} instance. * @param daemon if a daemon thread used to monitor changes - * @param idleTime the amount of time to wait between checking for changes - * @param quietTime the amount of time required after a change has been detected to + * @param pollInterval the amount of time to wait between checking for changes + * @param quietPeriod the amount of time required after a change has been detected to * ensure that updates have completed */ - public FileSystemWatcher(boolean daemon, long idleTime, long quietTime) { + public FileSystemWatcher(boolean daemon, long pollInterval, long quietPeriod) { + Assert.isTrue(pollInterval > 0, "PollInterval must be positive"); + Assert.isTrue(quietPeriod > 0, "QuietPeriod must be positive"); + Assert.isTrue(pollInterval > quietPeriod, + "PollInterval must be greater than QuietPeriod"); this.daemon = daemon; - this.idleTime = idleTime; - this.quietTime = quietTime; + this.pollInterval = pollInterval; + this.quietPeriod = quietPeriod; } /** @@ -131,10 +135,10 @@ public class FileSystemWatcher { FileSystemWatcher.this.remainingScans.decrementAndGet(); } scan(); - remainingScans = FileSystemWatcher.this.remainingScans.get(); } catch (InterruptedException ex) { } + remainingScans = FileSystemWatcher.this.remainingScans.get(); } }; }; @@ -152,13 +156,13 @@ public class FileSystemWatcher { } private void scan() throws InterruptedException { - Thread.sleep(this.idleTime - this.quietTime); + Thread.sleep(this.pollInterval - this.quietPeriod); Map previous; Map current = this.folders; do { previous = current; current = getCurrentSnapshots(); - Thread.sleep(this.quietTime); + Thread.sleep(this.quietPeriod); } while (isDifferent(previous, current)); if (isDifferent(this.folders, current)) { @@ -228,6 +232,7 @@ public class FileSystemWatcher { Thread thread = this.watchThread; if (thread != null) { this.remainingScans.set(remainingScans); + thread.interrupt(); if (Thread.currentThread() != thread) { try { thread.join(); diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/RemoteClientConfiguration.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/RemoteClientConfiguration.java index d23fd03e15..2a44f46eb2 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/RemoteClientConfiguration.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/remote/client/RemoteClientConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.devtools.autoconfigure.DevToolsProperties; +import org.springframework.boot.devtools.autoconfigure.DevToolsProperties.Restart; import org.springframework.boot.devtools.autoconfigure.OptionalLiveReloadServer; import org.springframework.boot.devtools.autoconfigure.RemoteDevToolsProperties; import org.springframework.boot.devtools.autoconfigure.TriggerFileFilter; @@ -187,8 +188,11 @@ public class RemoteClientConfiguration { @Bean public FileSystemWatcher getFileSystemWather() { - FileSystemWatcher watcher = new FileSystemWatcher(); - String triggerFile = this.properties.getRestart().getTriggerFile(); + Restart restartProperties = this.properties.getRestart(); + FileSystemWatcher watcher = new FileSystemWatcher(true, + restartProperties.getPollInterval(), + restartProperties.getQuietPeriod()); + String triggerFile = restartProperties.getTriggerFile(); if (StringUtils.hasLength(triggerFile)) { watcher.setTriggerFilter(new TriggerFileFilter(triggerFile)); } diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java index a7d884f50c..9e9cdee1bd 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/filewatch/FileSystemWatcherTests.java @@ -64,6 +64,27 @@ public class FileSystemWatcherTests { setupWatcher(20, 10); } + @Test + public void pollIntervalMustBePositive() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("PollInterval must be positive"); + new FileSystemWatcher(true, 0, 1); + } + + @Test + public void quietPeriodMustBePositive() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("QuietPeriod must be positive"); + new FileSystemWatcher(true, 1, 0); + } + + @Test + public void pollIntervalMustBeGreaterThanQuietPeriod() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("PollInterval must be greater than QuietPeriod"); + new FileSystemWatcher(true, 1, 1); + } + @Test public void listenerMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); @@ -117,7 +138,7 @@ public class FileSystemWatcherTests { @Test public void waitsForIdleTime() throws Exception { this.changes.clear(); - setupWatcher(100, 0); + setupWatcher(100, 1); File folder = startWithNewFolder(); touch(new File(folder, "test1.txt")); Thread.sleep(200); diff --git a/spring-boot-samples/spring-boot-sample-devtools/src/main/java/demo/SampleDevToolsApplication.java b/spring-boot-samples/spring-boot-sample-devtools/src/main/java/demo/SampleDevToolsApplication.java index 9a9cca90d6..61d0bd9a62 100644 --- a/spring-boot-samples/spring-boot-sample-devtools/src/main/java/demo/SampleDevToolsApplication.java +++ b/spring-boot-samples/spring-boot-sample-devtools/src/main/java/demo/SampleDevToolsApplication.java @@ -26,5 +26,4 @@ public class SampleDevToolsApplication extends WebMvcAutoConfigurationAdapter { public static void main(String[] args) { SpringApplication.run(SampleDevToolsApplication.class, args); } - } diff --git a/spring-boot-samples/spring-boot-sample-devtools/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-devtools/src/main/resources/application.properties index e82dc814f8..9223aae320 100644 --- a/spring-boot-samples/spring-boot-sample-devtools/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-devtools/src/main/resources/application.properties @@ -1,2 +1,3 @@ spring.devtools.remote.secret=secret +# spring.devtools.restart.poll-interval=10000 # spring.devtools.restart.trigger-file=.reloadtrigger