diff options
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java b/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java new file mode 100644 index 0000000..f15a5ff --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/FileSystemState.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013 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.checkNotNull; + +import com.google.common.base.Throwables; +import com.google.common.collect.Sets; +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.ClosedFileSystemException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Object that manages the open/closed state of a file system, ensuring that all open resources are + * closed when the file system is closed and that file system methods throw an exception when the + * file system has been closed. + * + * @author Colin Decker + */ +final class FileSystemState implements Closeable { + + private final Set<Closeable> resources = Sets.newConcurrentHashSet(); + private final Runnable onClose; + + private final AtomicBoolean open = new AtomicBoolean(true); + + /** Count of resources currently in the process of being registered. */ + private final AtomicInteger registering = new AtomicInteger(); + + FileSystemState(Runnable onClose) { + this.onClose = checkNotNull(onClose); + } + + /** Returns whether or not the file system is open. */ + public boolean isOpen() { + return open.get(); + } + + /** + * Checks that the file system is open, throwing {@link ClosedFileSystemException} if it is not. + */ + public void checkOpen() { + if (!open.get()) { + throw new ClosedFileSystemException(); + } + } + + /** + * Registers the given resource to be closed when the file system is closed. Should be called when + * the resource is opened. + */ + public <C extends Closeable> C register(C resource) { + // Initial open check to avoid incrementing registering if we already know it's closed. + // This is to prevent any possibility of a weird pathalogical situation where the do/while + // loop in close() keeps looping as register() is called repeatedly from multiple threads. + checkOpen(); + + registering.incrementAndGet(); + try { + // Need to check again after marking registration in progress to avoid a potential race. + // (close() could have run completely between the first checkOpen() and + // registering.incrementAndGet().) + checkOpen(); + resources.add(resource); + return resource; + } finally { + registering.decrementAndGet(); + } + } + + /** Unregisters the given resource. Should be called when the resource is closed. */ + public void unregister(Closeable resource) { + resources.remove(resource); + } + + /** + * Closes the file system, runs the {@code onClose} callback and closes all registered resources. + */ + @Override + public void close() throws IOException { + if (open.compareAndSet(true, false)) { + onClose.run(); + + Throwable thrown = null; + do { + for (Closeable resource : resources) { + try { + resource.close(); + } catch (Throwable e) { + if (thrown == null) { + thrown = e; + } else { + thrown.addSuppressed(e); + } + } finally { + // ensure the resource is removed even if it doesn't remove itself when closed + resources.remove(resource); + } + } + + // It's possible for a thread registering a resource to register that resource after open + // has been set to false and even after we've looped through and closed all the resources. + // Since registering must be incremented *before* checking the state of open, however, + // when we reach this point in that situation either the register call is still in progress + // (registering > 0) or the new resource has been successfully added (resources not empty). + // In either case, we just need to repeat the loop until there are no more register calls + // in progress (no new calls can start and no resources left to close. + } while (registering.get() > 0 || !resources.isEmpty()); + Throwables.propagateIfPossible(thrown, IOException.class); + } + } +} |