All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.cosmos.implementation.uuid.ext.LockedFile Maven / Gradle / Ivy

/* JUG Java Uuid Generator
 *
 * Copyright (c) 2002- Tatu Saloranta, [email protected]
 *
 * Licensed under the License specified in the file LICENSE which is
 * included with the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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.
 */

/*
 * Portions Copyright (c) Microsoft Corporation
 */

package com.azure.cosmos.implementation.uuid.ext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

/**
 * Utility class used by {@link FileBasedTimestampSynchronizer} to do
 * actual file access and locking.
 *

* Class stores simple timestamp values based on system time accessed * using System.currentTimeMillis(). A single timestamp * is stored into a file using {@link RandomAccessFile} in fully * synchronized mode. Value is written in ISO-Latin (ISO-8859-1) * encoding (superset of Ascii, 1 byte per char) as 16-digit hexadecimal * number, surrounded by brackets. As such, file produced should * always have exact size of 18 bytes. For extra robustness, slight * variations in number of digits are accepeted, as are white space * chars before and after bracketed value. */ /* * Portions Copyright (c) Microsoft Corporation */ class LockedFile { private static final Logger logger = LoggerFactory.getLogger(LockedFile.class); /** * Expected file length comes from hex-timestamp (16 digits), * preamble "[0x",(3 chars) and trailer "]\r\n" (2 chars, linefeed * to help debugging -- in some environments, missing trailing linefeed * causes problems: also, 2-char linefeed to be compatible with all * standard linefeeds on MacOS, Unix and Windows). */ final static int DEFAULT_LENGTH = 22; final static long READ_ERROR = 0L; // // // Configuration: final File mFile; // // // File state RandomAccessFile mRAFile; FileChannel mChannel; FileLock mLock; ByteBuffer mWriteBuffer = null; /** * Flag set if the original file (created before this instance was * created) had size other than default size and needs to be * truncated */ boolean mWeirdSize; /** * Marker used to ensure that the timestamps stored are monotonously * increasing. Shouldn't really be needed, since caller should take * care of it, but let's be bit paranoid here. */ long mLastTimestamp = 0L; LockedFile(File f) throws IOException { mFile = f; RandomAccessFile raf = null; FileChannel channel = null; FileLock lock = null; boolean ok = false; try { // let's just use a single block to share cleanup code raf = new RandomAccessFile(f, "rwd"); // Then lock them, if possible; if not, let's err out channel = raf.getChannel(); if (channel == null) { throw new IOException("Failed to access channel for '"+f+"'"); } lock = channel.tryLock(); if (lock == null) { throw new IOException("Failed to lock '"+f+"' (another JVM running UUIDGenerator?)"); } ok = true; } finally { if (!ok) { doDeactivate(f, raf, lock); } } mRAFile = raf; mChannel = channel; mLock = lock; } public void deactivate() { RandomAccessFile raf = mRAFile; mRAFile = null; FileLock lock = mLock; mLock = null; doDeactivate(mFile, raf, lock); } public long readStamp() { int size; try { size = (int) mChannel.size(); } catch (IOException ioe) { logger.error("Failed to read file size", ioe); return READ_ERROR; } mWeirdSize = (size != DEFAULT_LENGTH); // Let's check specifically empty files though if (size == 0) { logger.warn("Missing or empty file, can not read timestamp value"); return READ_ERROR; } // Let's also allow some slack... but just a bit if (size > 100) { size = 100; } byte[] data = new byte[size]; try { mRAFile.readFully(data); } catch (IOException ie) { logger.error("(file '{}') Failed to read {} bytes", mFile, size, ie); return READ_ERROR; } /* Ok, got data. Now, we could just directly parse the bytes (since * it is single-byte encoding)... but for convenience, let's create * the String (this is only called once per JVM session) */ char[] cdata = new char[size]; for (int i = 0; i < size; ++i) { cdata[i] = (char) (data[i] & 0xFF); } String dataStr = new String(cdata); // And let's trim leading (and trailing, who cares) dataStr = dataStr.trim(); long result = -1; String err = null; if (!dataStr.startsWith("[0") || dataStr.length() < 3 || Character.toLowerCase(dataStr.charAt(2)) != 'x') { err = "does not start with '[0x' prefix"; } else { int ix = dataStr.indexOf(']', 3); if (ix <= 0) { err = "does not end with ']' marker"; } else { String hex = dataStr.substring(3, ix); if (hex.length() > 16) { err = "length of the (hex) timestamp too long; expected 16, had "+hex.length()+" ('"+hex+"')"; } else { try { result = Long.parseLong(hex, 16); } catch (NumberFormatException nex) { err = "does not contain a valid hex timestamp; got '" +hex+"' (parse error: "+nex+")"; } } } } // Unsuccesful? if (result < 0L) { logger.error("(file '{}') Malformed timestamp file contents: {}", mFile, err); return READ_ERROR; } mLastTimestamp = result; return result; } final static String HEX_DIGITS = "0123456789abcdef"; public void writeStamp(long stamp) throws IOException { // Let's do sanity check first: if (stamp <= mLastTimestamp) { /* same stamp is not dangerous, but pointless... so warning, * not an error: */ if (stamp == mLastTimestamp) { logger.warn("(file '{}') Trying to re-write existing timestamp ({})", mFile, stamp); return; } throw new IOException(""+mFile+" trying to overwrite existing value ("+mLastTimestamp+") with an earlier timestamp ("+stamp+")"); } //System.err.println("!!!! Syncing ["+mFile+"] with "+stamp+" !!!"); // Need to initialize the buffer? if (mWriteBuffer == null) { mWriteBuffer = ByteBuffer.allocate(DEFAULT_LENGTH); mWriteBuffer.put(0, (byte) '['); mWriteBuffer.put(1, (byte) '0'); mWriteBuffer.put(2, (byte) 'x'); mWriteBuffer.put(19, (byte) ']'); mWriteBuffer.put(20, (byte) '\r'); mWriteBuffer.put(21, (byte) '\n'); } // Converting to hex is simple for (int i = 18; i >= 3; --i) { int val = (((int) stamp) & 0x0F); mWriteBuffer.put(i, (byte) HEX_DIGITS.charAt(val)); stamp = (stamp >> 4); } // and off we go: mWriteBuffer.position(0); // to make sure we always write it all mChannel.write(mWriteBuffer, 0L); if (mWeirdSize) { mRAFile.setLength(DEFAULT_LENGTH); mWeirdSize = false; } // This is probably not needed (as the random access file is supposedly synced)... but let's be safe: mChannel.force(false); // And that's it! } /* ////////////////////////////////////////////////////////////// // Internal methods ////////////////////////////////////////////////////////////// */ protected static void doDeactivate(File f, RandomAccessFile raf, FileLock lock) { if (lock != null) { try { lock.release(); } catch (Throwable t) { logger.error("Failed to release lock (for file '{}')", f, t); } } if (raf != null) { try { raf.close(); } catch (Throwable t) { logger.error("Failed to close file '{}'", f, t); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy