/* * Copyright (c) 2013-2017 Cinchapi 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.cinchapi.concourse.server.plugin.io; import java.io.File; import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileLock; import java.nio.file.StandardOpenOption; import javax.annotation.concurrent.Immutable; import com.cinchapi.concourse.server.plugin.concurrent.FileLocks; import com.google.common.base.Throwables; /** * A 32-bit integer value that is maintained within a {@link MappedByteBuffer * mappable} {@link FileChannel location}. * <p> * A {@link MappedAtomicInteger} is useful for cases when an int is constantly * read * and updated from a disjointed file. For example, the file might contain * discreet messages and an int that occupies the first 4 bytes of the file is * maintained to specify how many messages are in the file. In such a case, * using a {@link MappedAtomicInteger} is useful because the value that * maintains the * number of messages can be managed in a more natural way while ensuring atomic * safety. * </p> * * @author Jeff Nelson */ @Immutable public final class MappedAtomicInteger { /** * The number of bytes in the backing {@link #storage} needed for the * {@link MappedAtomicInteger}. */ private final static int SIZE = 4; /** * The backing {@link FileChannel} from which the {@link #storage} buffer is * created; used to facilitate locking. */ private final FileChannel channel; /** * The position in the backing {@link #channel} where this * {@link MappedAtomicInteger} begins; used to facilitate locking. */ private final long position; /** * A memory-mapped segment of the backing {@link #channel} that contains the * represented value in stored form. */ private final MappedByteBuffer storage; /** * Construct a new instance. * * @param channel the {@link FileChannel} where the value is stored */ public MappedAtomicInteger(FileChannel channel) { this(channel, 0); } /** * Construct a new instance. * * @param channel the {@link FileChannel} where the value is stored * @param position the position in the {@code channel} where the value * begins */ public MappedAtomicInteger(FileChannel channel, int position) { try { this.channel = channel; this.position = position; this.storage = channel.map(MapMode.READ_WRITE, position, SIZE); } catch (IOException e) { throw Throwables.propagate(e); } } /** * Construct a new instance. * * @param address the path to the location where the value is stored */ public MappedAtomicInteger(String address) { this(address, 0); } /** * Construct a new instance. * * @param address the path to the location where the value is stored * @param position the position in the {@code address} where the value * begins */ public MappedAtomicInteger(String address, int position) { File file = new File(address); try { this.channel = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); this.position = position; this.storage = channel.map(MapMode.READ_WRITE, position, SIZE); } catch (IOException e) { throw Throwables.propagate(e); } } /** * Add {@code amount} to the current value and return the sum. * * @param amount the number to add to the current value * @return the sum of the current value and the added {@code amount}, which * becomes the new value */ public int addAndGet(int amount) { FileLock lock = lock(); try { int value = getUnsafe(); value = value + amount; return setUnsafe(value); } finally { FileLocks.release(lock); } } @Override public void finalize() { try { channel.close(); } catch (IOException e) { throw Throwables.propagate(e); } } /** * Return the current value. * * @return the current value */ public int get() { FileLock lock = lock(); try { return getUnsafe(); } finally { FileLocks.release(lock); } } /** * Set the value equal to {@code value} * * @param value the new value */ public void set(int value) { FileLock lock = lock(); try { setUnsafe(value); } finally { FileLocks.release(lock); } } /** * Atomically set the value equal to {@code value} and {@link #sync()} the * change to disk. * * @param value the new value */ public void setAndSync(int value) { FileLock lock = lock(); try { setUnsafe(value); sync(); } finally { FileLocks.release(lock); } } /** * Force sync any changes made to disk. */ public void sync() { storage.force(); } /** * Return the current value without grabbing any locks. * <p> * ONLY USE THIS METHOD INTERNALLY!!! * </p> * * @return the current value */ private int getUnsafe() { int value = storage.getInt(); storage.rewind(); return value; } /** * Grab a lock to perform atomic reads and/or writes. * * @return the lock */ private FileLock lock() { return FileLocks.lock(channel, position, SIZE, false); } /** * Set {@code value} without grabbing any locks. * <p> * ONLY USE THIS METHOD INTERNALLY!!! * </p> * * @param value the value to set * @return {@code value} for chaining */ private int setUnsafe(int value) { storage.putInt(value); storage.rewind(); return value; } }