Skip to content

Commit

Permalink
Add long-requested ability to configure the polling rate for the Watc…
Browse files Browse the repository at this point in the history
…hService that Jimfs creates for watching for directory changes.

This is done in such a way that it doesn't make it awkward to create a new WatchService implementation in the future.

Fixes #14.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=111885937
  • Loading branch information
cgdecker committed Jan 12, 2016
1 parent a68159d commit c60af2a
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 19 deletions.
20 changes: 20 additions & 0 deletions jimfs/src/main/java/com/google/common/jimfs/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.SecureDirectoryStream;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributeView;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -209,6 +210,9 @@ public static Builder builder(PathType pathType) {
final ImmutableSet<AttributeProvider> attributeProviders;
final ImmutableMap<String, Object> defaultAttributeValues;

// Watch service
final WatchServiceConfiguration watchServiceConfig;

// Other
final ImmutableSet<String> roots;
final String workingDirectory;
Expand All @@ -234,6 +238,7 @@ private Configuration(Builder builder) {
builder.defaultAttributeValues == null
? ImmutableMap.<String, Object>of()
: ImmutableMap.copyOf(builder.defaultAttributeValues);
this.watchServiceConfig = builder.watchServiceConfig;
this.roots = builder.roots;
this.workingDirectory = builder.workingDirectory;
this.supportedFeatures = builder.supportedFeatures;
Expand Down Expand Up @@ -276,6 +281,9 @@ public static final class Builder {
private Set<AttributeProvider> attributeProviders = null;
private Map<String, Object> defaultAttributeValues;

// Watch service
private WatchServiceConfiguration watchServiceConfig = WatchServiceConfiguration.DEFAULT;

// Other
private ImmutableSet<String> roots = ImmutableSet.of();
private String workingDirectory;
Expand All @@ -302,6 +310,7 @@ private Builder(Configuration configuration) {
configuration.defaultAttributeValues.isEmpty()
? null
: new HashMap<>(configuration.defaultAttributeValues);
this.watchServiceConfig = configuration.watchServiceConfig;
this.roots = configuration.roots;
this.workingDirectory = configuration.workingDirectory;
this.supportedFeatures = configuration.supportedFeatures;
Expand Down Expand Up @@ -607,6 +616,17 @@ public Builder setSupportedFeatures(Feature... features) {
return this;
}

/**
* Sets the configuration that {@link WatchService} instances created by the file system
* should use. The default configuration polls watched directories for changes every 5 seconds.
*
* @since 1.1
*/
public Builder setWatchServiceConfiguration(WatchServiceConfiguration config) {
this.watchServiceConfig = checkNotNull(config);
return this;
}

/**
* Creates a new immutable configuration object from this builder.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,17 +180,21 @@ final class JimfsFileSystem extends FileSystem {

private final FileSystemView defaultView;

private final WatchServiceConfiguration watchServiceConfig;

JimfsFileSystem(
JimfsFileSystemProvider provider,
URI uri,
JimfsFileStore fileStore,
PathService pathService,
FileSystemView defaultView) {
FileSystemView defaultView,
WatchServiceConfiguration watchServiceConfig) {
this.provider = checkNotNull(provider);
this.uri = checkNotNull(uri);
this.fileStore = checkNotNull(fileStore);
this.pathService = checkNotNull(pathService);
this.defaultView = checkNotNull(defaultView);
this.watchServiceConfig = checkNotNull(watchServiceConfig);
}

@Override
Expand Down Expand Up @@ -296,7 +300,7 @@ public UserPrincipalLookupService getUserPrincipalLookupService() {

@Override
public WatchService newWatchService() throws IOException {
return new PollingWatchService(defaultView, pathService, fileStore.state());
return watchServiceConfig.newWatchService(defaultView, pathService);
}

@Nullable private ExecutorService defaultThreadPool;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ public static JimfsFileSystem newFileSystem(

JimfsFileStore fileStore = createFileStore(config, pathService, state);
FileSystemView defaultView = createDefaultView(config, fileStore, pathService);
WatchServiceConfiguration watchServiceConfig = config.watchServiceConfig;

JimfsFileSystem fileSystem =
new JimfsFileSystem(provider, uri, fileStore, pathService, defaultView);
new JimfsFileSystem(provider, uri, fileStore, pathService, defaultView, watchServiceConfig);

pathService.setFileSystem(fileSystem);
return fileSystem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,13 @@
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.io.IOException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchService;
Expand Down Expand Up @@ -73,30 +71,25 @@ final class PollingWatchService extends AbstractWatchService {
private final PathService pathService;
private final FileSystemState fileSystemState;

private final long pollingTime;
private final TimeUnit timeUnit;
@VisibleForTesting
final long interval;
@VisibleForTesting
final TimeUnit timeUnit;

private ScheduledFuture<?> pollingFuture;

public PollingWatchService(
FileSystemView view, PathService pathService, FileSystemState fileSystemState) {
this(view, pathService, fileSystemState, 5, SECONDS);
}

// TODO(cgdecker): make user configurable somehow? meh
@VisibleForTesting
PollingWatchService(
FileSystemView view,
PathService pathService,
FileSystemState fileSystemState,
long pollingTime,
long interval,
TimeUnit timeUnit) {
this.view = checkNotNull(view);
this.pathService = checkNotNull(pathService);
this.fileSystemState = checkNotNull(fileSystemState);

checkArgument(pollingTime >= 0, "polling time (%s) may not be negative", pollingTime);
this.pollingTime = pollingTime;
checkArgument(interval >= 0, "interval (%s) may not be negative", interval);
this.interval = interval;
this.timeUnit = checkNotNull(timeUnit);

fileSystemState.register(this);
Expand Down Expand Up @@ -168,7 +161,7 @@ public void close() {

private void startPolling() {
pollingFuture =
pollingService.scheduleAtFixedRate(pollingTask, pollingTime, pollingTime, timeUnit);
pollingService.scheduleAtFixedRate(pollingTask, interval, interval, timeUnit);
}

private void stopPolling() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2016 Google Inc.
*
* 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 com.google.common.jimfs;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.nio.file.WatchService;
import java.util.concurrent.TimeUnit;

/**
* Configuration for the {@link WatchService} implementation used by a file system.
*
* @author Colin Decker
* @since 1.1
*/
public abstract class WatchServiceConfiguration {

/**
* The default configuration that's used if the user doesn't provide anything more specific.
*/
static final WatchServiceConfiguration DEFAULT = polling(5, SECONDS);

/**
* Returns a configuration for a {@link WatchService} that polls watched directories for changes
* every {@code interval} of the given {@code timeUnit} (e.g. every 5
* {@link TimeUnit#SECONDS seconds}).
*/
public static WatchServiceConfiguration polling(long interval, TimeUnit timeUnit) {
return new PollingConfig(interval, timeUnit);
}

WatchServiceConfiguration() {}

/**
* Creates a new {@link AbstractWatchService} implementation.
*/
// return type and parameters of this method subject to change if needed for any future
// implementations
abstract AbstractWatchService newWatchService(FileSystemView view, PathService pathService);

/**
* Implementation for {@link #polling}.
*/
private static final class PollingConfig extends WatchServiceConfiguration {

private final long interval;
private final TimeUnit timeUnit;

private PollingConfig(long interval, TimeUnit timeUnit) {
checkArgument(interval > 0, "interval (%s) must be positive", interval);
this.interval = interval;
this.timeUnit = checkNotNull(timeUnit);
}

@Override
AbstractWatchService newWatchService(FileSystemView view, PathService pathService) {
return new PollingWatchService(view, pathService, view.state(), interval, timeUnit);
}

@Override
public String toString() {
return "WatchServiceConfiguration.polling(" + interval + ", " + timeUnit + ")";
}
}
}
31 changes: 31 additions & 0 deletions jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import static com.google.common.jimfs.PathNormalization.NFC;
import static com.google.common.jimfs.PathNormalization.NFD;
import static com.google.common.jimfs.PathSubject.paths;
import static com.google.common.jimfs.WatchServiceConfiguration.polling;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.fail;

import com.google.common.collect.ImmutableList;
Expand All @@ -38,7 +41,9 @@
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchService;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.concurrent.TimeUnit;

/**
* Tests for {@link Configuration}, {@link Configuration.Builder} and file systems created from
Expand Down Expand Up @@ -337,6 +342,32 @@ public void testCreateFileSystemFromConfigurationWithWorkingDirectoryNotUnderCon
}
}

@Test
public void testFileSystemWithDefaultWatchService() throws IOException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());

WatchService watchService = fs.newWatchService();
assertThat(watchService).isInstanceOf(PollingWatchService.class);

PollingWatchService pollingWatchService = (PollingWatchService) watchService;
assertThat(pollingWatchService.interval).isEqualTo(5);
assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS);
}

@Test
public void testFileSystemWithCustomWatchServicePollingInterval() throws IOException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix().toBuilder()
.setWatchServiceConfiguration(polling(10, MILLISECONDS))
.build());

WatchService watchService = fs.newWatchService();
assertThat(watchService).isInstanceOf(PollingWatchService.class);

PollingWatchService pollingWatchService = (PollingWatchService) watchService;
assertThat(pollingWatchService.interval).isEqualTo(10);
assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS);
}

@Test
public void testTurkishNormalization() {
Configuration config =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ public void setUp() {
}

@After
public void tearDown() {
public void tearDown() throws IOException {
watcher.close();
fs.close();
watcher = null;
fs = null;
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2016 Google Inc.
*
* 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 com.google.common.jimfs;

import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.truth.Truth;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.IOException;
import java.nio.file.WatchService;
import java.util.concurrent.TimeUnit;

/**
* Tests for {@link WatchServiceConfiguration}.
*
* @author Colin Decker
*/
@RunWith(JUnit4.class)
public class WatchServiceConfigurationTest {

private JimfsFileSystem fs;

@Before
public void setUp() {
// kind of putting the cart before the horse maybe, but it's the easiest way to get valid
// instances of both a FileSystemView and a PathService
fs = (JimfsFileSystem) Jimfs.newFileSystem();
}

@After
public void tearDown() throws IOException {
fs.close();
fs = null;
}

@Test
public void testPollingConfig() {
WatchServiceConfiguration polling = WatchServiceConfiguration.polling(50, MILLISECONDS);
WatchService watchService = polling.newWatchService(fs.getDefaultView(), fs.getPathService());
assertThat(watchService).isInstanceOf(PollingWatchService.class);

PollingWatchService pollingWatchService = (PollingWatchService) watchService;
assertThat(pollingWatchService.interval).isEqualTo(50);
assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS);
}

@Test
public void testDefaultConfig() {
WatchService watchService = WatchServiceConfiguration.DEFAULT
.newWatchService(fs.getDefaultView(), fs.getPathService());
assertThat(watchService).isInstanceOf(PollingWatchService.class);

PollingWatchService pollingWatchService = (PollingWatchService) watchService;
assertThat(pollingWatchService.interval).isEqualTo(5);
assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS);
}
}

0 comments on commit c60af2a

Please sign in to comment.