
io.aeron.archive.ArchiveMarkFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aeron-all Show documentation
Show all versions of aeron-all Show documentation
Efficient reliable UDP unicast, UDP multicast, and IPC transport protocol.
/*
* Copyright 2014-2024 Real Logic Limited.
*
* 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
*
* https://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 io.aeron.archive;
import io.aeron.CommonContext;
import io.aeron.archive.client.ArchiveException;
import io.aeron.archive.codecs.mark.MarkFileHeaderDecoder;
import io.aeron.archive.codecs.mark.MarkFileHeaderEncoder;
import io.aeron.archive.codecs.mark.VarAsciiEncodingEncoder;
import org.agrona.CloseHelper;
import org.agrona.MarkFile;
import org.agrona.SemanticVersion;
import org.agrona.SystemUtil;
import org.agrona.concurrent.AtomicBuffer;
import org.agrona.concurrent.EpochClock;
import org.agrona.concurrent.UnsafeBuffer;
import java.io.File;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import static io.aeron.archive.Archive.Configuration.LIVENESS_TIMEOUT_MS;
/**
* Used to mark the presence of a running {@link Archive} in a directory to guard it.
*/
public class ArchiveMarkFile implements AutoCloseable
{
/**
* Major version for the archive files stored on disk. A change to this requires migration.
*/
public static final int MAJOR_VERSION = 3;
/**
* Minor version for the archive files stored on disk. A change to this indicates new features.
*/
public static final int MINOR_VERSION = 1;
/**
* Patch version for the archive files stored on disk. A change to this indicates feature parity bug fixes.
*/
public static final int PATCH_VERSION = 0;
/**
* Combined semantic version for the stored files.
*
* @see SemanticVersion
*/
public static final int SEMANTIC_VERSION = SemanticVersion.compose(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
/**
* Header length for the {@link MarkFile} containing the metadata.
*/
public static final int HEADER_LENGTH = 8 * 1024;
/**
* Name for the archive {@link MarkFile} stored in the {@link Archive.Configuration#ARCHIVE_DIR_PROP_NAME}.
*/
public static final String FILENAME = "archive-mark.dat";
/**
* Name for a file contain a link to the directory containing the {@link MarkFile}.
*/
public static final String LINK_FILENAME = "archive-mark.lnk";
private final MarkFileHeaderDecoder headerDecoder = new MarkFileHeaderDecoder();
private final MarkFileHeaderEncoder headerEncoder = new MarkFileHeaderEncoder();
private final MarkFile markFile;
private final UnsafeBuffer buffer;
private final UnsafeBuffer errorBuffer;
ArchiveMarkFile(final Archive.Context ctx)
{
this(
new File(ctx.markFileDir(), FILENAME),
alignedTotalFileLength(ctx),
ctx.errorBufferLength(),
ctx.epochClock(),
LIVENESS_TIMEOUT_MS);
encode(ctx);
}
ArchiveMarkFile(
final File file,
final int totalFileLength,
final int errorBufferLength,
final EpochClock epochClock,
final long timeoutMs)
{
final boolean markFileExists = file.exists();
markFile = new MarkFile(
file,
markFileExists,
MarkFileHeaderDecoder.versionEncodingOffset(),
MarkFileHeaderDecoder.activityTimestampEncodingOffset(),
totalFileLength,
timeoutMs,
epochClock,
(version) -> validateVersion(file, version),
null);
buffer = markFile.buffer();
errorBuffer = errorBufferLength > 0 ?
new UnsafeBuffer(buffer, HEADER_LENGTH, errorBufferLength) : new UnsafeBuffer(buffer, 0, 0);
headerEncoder.wrap(buffer, 0);
headerDecoder.wrap(buffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);
if (markFileExists)
{
if (buffer.capacity() != totalFileLength)
{
throw new ArchiveException(
"ArchiveMarkFile capacity=" + buffer.capacity() + " < expectedCapacity=" + totalFileLength);
}
final int existingErrorBufferLength = headerDecoder.errorBufferLength();
if (existingErrorBufferLength > 0)
{
final UnsafeBuffer existingErrorBuffer = new UnsafeBuffer(
buffer, headerDecoder.headerLength(), existingErrorBufferLength);
saveExistingErrors(file, existingErrorBuffer, CommonContext.fallbackLogger());
existingErrorBuffer.setMemory(0, existingErrorBufferLength, (byte)0);
}
}
headerEncoder.pid(SystemUtil.getPid());
}
/**
* Construct the {@link MarkFile} based on an existing directory containing a mark file of a running archive.
*
* @param directory containing the archive files.
* @param filename for the mark file.
* @param epochClock to be used for checking liveness.
* @param timeoutMs after which the opening will be aborted if no archive starts.
* @param logger to detail any discoveries.
*/
public ArchiveMarkFile(
final File directory,
final String filename,
final EpochClock epochClock,
final long timeoutMs,
final Consumer logger)
{
this(
directory,
filename,
epochClock,
timeoutMs,
(version) -> ArchiveMarkFile.validateVersion(new File(directory, filename), version),
logger);
}
/**
* Open an existing {@link MarkFile} or create a new one to be used in migration.
*
* @param directory containing the archive files.
* @param filename for the mark file.
* @param epochClock to be used for checking liveness.
* @param timeoutMs after which the opening will be aborted if no archive starts.
* @param versionCheck for confirming the correct version.
* @param logger to detail any discoveries.
*/
public ArchiveMarkFile(
final File directory,
final String filename,
final EpochClock epochClock,
final long timeoutMs,
final IntConsumer versionCheck,
final Consumer logger)
{
this(new MarkFile(
directory,
filename,
MarkFileHeaderDecoder.versionEncodingOffset(),
MarkFileHeaderDecoder.activityTimestampEncodingOffset(),
timeoutMs,
epochClock,
versionCheck,
logger));
}
ArchiveMarkFile(final MarkFile markFile)
{
this.markFile = markFile;
buffer = markFile.buffer();
headerEncoder.wrap(buffer, 0);
headerDecoder.wrap(buffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);
errorBuffer = headerDecoder.errorBufferLength() > 0 ?
new UnsafeBuffer(buffer, headerDecoder.headerLength(), headerDecoder.errorBufferLength()) :
new UnsafeBuffer(buffer, 0, 0);
}
/**
* {@inheritDoc}
*/
public void close()
{
CloseHelper.close(markFile);
}
/**
* Signal the archive has concluded successfully and ready to start.
*/
public void signalReady()
{
markFile.signalReady(SEMANTIC_VERSION);
}
/**
* Update the activity timestamp as a proof of life.
*
* @param nowMs activity timestamp as a proof of life.
*/
public void updateActivityTimestamp(final long nowMs)
{
if (!markFile.isClosed())
{
markFile.timestampOrdered(nowMs);
}
}
/**
* Read the activity timestamp of the archive with volatile semantics.
*
* @return the activity timestamp of the archive with volatile semantics.
*/
public long activityTimestampVolatile()
{
return markFile.timestampVolatile();
}
/**
* The encoder for writing the {@link MarkFile} header.
*
* @return the encoder for writing the {@link MarkFile} header.
*/
public MarkFileHeaderEncoder encoder()
{
return headerEncoder;
}
/**
* The decoder for reading the {@link MarkFile} header.
*
* @return the decoder for reading the {@link MarkFile} header.
*/
public MarkFileHeaderDecoder decoder()
{
return headerDecoder;
}
/**
* The direct buffer which wraps the region of the {@link MarkFile} which contains the error log.
*
* @return the direct buffer which wraps the region of the {@link MarkFile} which contains the error log.
*/
public AtomicBuffer errorBuffer()
{
return errorBuffer;
}
/**
* Save the existing errors from a {@link MarkFile} to a {@link PrintStream} for logging.
*
* @param markFile which contains the error buffer.
* @param errorBuffer which wraps the error log.
* @param logger to which the existing errors will be printed.
*/
public static void saveExistingErrors(final File markFile, final AtomicBuffer errorBuffer, final PrintStream logger)
{
CommonContext.saveExistingErrors(markFile, errorBuffer, logger, "archive");
}
/**
* Determine if the path matches the archive mark file name.
*
* @param path to match.
* @param attributes ignored, only needed for BiPredicate signature matching.
* @return true if the filename matches.
*/
public static boolean isArchiveMarkFile(final Path path, final BasicFileAttributes attributes)
{
return FILENAME.equals(path.getFileName().toString());
}
/**
* Get the parent directory containing the mark file.
*
* @return parent directory of the mark file.
* @see MarkFile#parentDirectory()
*/
public File parentDirectory()
{
return markFile.parentDirectory();
}
/**
* Forces any changes made to the mark file's content to be written to the storage device containing the mapped
* file.
* @since 1.44.0
*/
public void force()
{
if (!markFile.isClosed())
{
markFile.mappedByteBuffer().force();
}
}
private static int alignedTotalFileLength(final Archive.Context ctx)
{
final int headerLength =
MarkFileHeaderEncoder.BLOCK_LENGTH +
(4 * VarAsciiEncodingEncoder.lengthEncodingLength()) +
(null != ctx.controlChannel() ? ctx.controlChannel().length() : 0) +
ctx.localControlChannel().length() +
(null != ctx.recordingEventsChannel() ? ctx.recordingEventsChannel().length() : 0) +
ctx.aeronDirectoryName().length();
if (headerLength > HEADER_LENGTH)
{
throw new ArchiveException(
"ArchiveMarkFile headerLength=" + headerLength + " > headerLengthCapacity=" + HEADER_LENGTH);
}
return HEADER_LENGTH + ctx.errorBufferLength();
}
String aeronDirectory()
{
headerDecoder.sbeRewind();
headerDecoder.skipControlChannel();
headerDecoder.skipLocalControlChannel();
headerDecoder.skipEventsChannel();
return headerDecoder.aeronDirectory();
}
private void encode(final Archive.Context ctx)
{
headerEncoder
.startTimestamp(ctx.epochClock().time())
.controlStreamId(ctx.controlStreamId())
.localControlStreamId(ctx.localControlStreamId())
.eventsStreamId(ctx.recordingEventsStreamId())
.headerLength(HEADER_LENGTH)
.errorBufferLength(ctx.errorBufferLength())
.controlChannel(ctx.controlChannel())
.localControlChannel(ctx.localControlChannel())
.eventsChannel(ctx.recordingEventsChannel())
.aeronDirectory(ctx.aeronDirectoryName());
}
private static void validateVersion(final File markFile, final int version)
{
if (SemanticVersion.major(version) != MAJOR_VERSION)
{
throw new IllegalArgumentException(
"mark file (" + markFile.getAbsolutePath() + ") major version " + SemanticVersion.major(version) +
" does not match software: " + MAJOR_VERSION);
}
}
/**
* {@inheritDoc}
*/
public String toString()
{
return "ArchiveMarkFile{" +
"markFile=" + markFile +
'}';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy