|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package jdk.jfr.internal; |
|
|
|
import java.io.IOException; |
|
import java.io.RandomAccessFile; |
|
import java.nio.channels.ReadableByteChannel; |
|
import java.nio.file.Path; |
|
import java.time.Instant; |
|
import java.time.LocalDateTime; |
|
import java.time.ZonedDateTime; |
|
import java.util.Comparator; |
|
import java.util.Objects; |
|
|
|
import jdk.jfr.internal.SecuritySupport.SafePath; |
|
|
|
final class RepositoryChunk { |
|
private static final int MAX_CHUNK_NAMES = 100; |
|
|
|
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() { |
|
@Override |
|
public int compare(RepositoryChunk c1, RepositoryChunk c2) { |
|
return c1.endTime.compareTo(c2.endTime); |
|
} |
|
}; |
|
|
|
private final SafePath repositoryPath; |
|
private final SafePath unFinishedFile; |
|
private final SafePath file; |
|
private final Instant startTime; |
|
private final RandomAccessFile unFinishedRAF; |
|
|
|
private Instant endTime = null; |
|
private int refCount = 0; |
|
private long size; |
|
|
|
RepositoryChunk(SafePath path, Instant startTime) throws Exception { |
|
ZonedDateTime z = ZonedDateTime.now(); |
|
String fileName = Repository.REPO_DATE_FORMAT.format( |
|
LocalDateTime.ofInstant(startTime, z.getZone())); |
|
this.startTime = startTime; |
|
this.repositoryPath = path; |
|
this.unFinishedFile = findFileName(repositoryPath, fileName, ".part"); |
|
this.file = findFileName(repositoryPath, fileName, ".jfr"); |
|
this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile); |
|
SecuritySupport.touch(file); |
|
} |
|
|
|
private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception { |
|
Path p = directory.toPath().resolve(name + extension); |
|
for (int i = 1; i < MAX_CHUNK_NAMES; i++) { |
|
SafePath s = new SafePath(p); |
|
if (!SecuritySupport.exists(s)) { |
|
return s; |
|
} |
|
String extendedName = String.format("%s_%02d%s", name, i, extension); |
|
p = directory.toPath().resolve(extendedName); |
|
} |
|
p = directory.toPath().resolve(name + "_" + System.currentTimeMillis() + extension); |
|
return SecuritySupport.toRealPath(new SafePath(p)); |
|
} |
|
|
|
public SafePath getUnfishedFile() { |
|
return unFinishedFile; |
|
} |
|
|
|
void finish(Instant endTime) { |
|
try { |
|
finishWithException(endTime); |
|
} catch (IOException e) { |
|
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not finish chunk. " + e.getMessage()); |
|
} |
|
} |
|
|
|
private void finishWithException(Instant endTime) throws IOException { |
|
unFinishedRAF.close(); |
|
this.size = finish(unFinishedFile, file); |
|
this.endTime = endTime; |
|
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Chunk finished: " + file); |
|
} |
|
|
|
private static long finish(SafePath unFinishedFile, SafePath file) throws IOException { |
|
Objects.requireNonNull(unFinishedFile); |
|
Objects.requireNonNull(file); |
|
SecuritySupport.delete(file); |
|
SecuritySupport.moveReplace(unFinishedFile, file); |
|
return SecuritySupport.getFileSize(file); |
|
} |
|
|
|
public Instant getStartTime() { |
|
return startTime; |
|
} |
|
|
|
public Instant getEndTime() { |
|
return endTime; |
|
} |
|
|
|
private void delete(SafePath f) { |
|
try { |
|
SecuritySupport.delete(f); |
|
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted"); |
|
} catch (IOException e) { |
|
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Repository chunk " + f + " could not be deleted: " + e.getMessage()); |
|
if (f != null) { |
|
SecuritySupport.deleteOnExit(f); |
|
} |
|
} |
|
} |
|
|
|
private void destroy() { |
|
if (!isFinished()) { |
|
finish(Instant.MIN); |
|
} |
|
if (file != null) { |
|
delete(file); |
|
} |
|
try { |
|
unFinishedRAF.close(); |
|
} catch (IOException e) { |
|
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Could not close random access file: " + unFinishedFile.toString() + ". File will not be deleted due to: " + e.getMessage()); |
|
} |
|
} |
|
|
|
public synchronized void use() { |
|
++refCount; |
|
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Use chunk " + toString() + " ref count now " + refCount); |
|
} |
|
|
|
public synchronized void release() { |
|
--refCount; |
|
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Release chunk " + toString() + " ref count now " + refCount); |
|
if (refCount == 0) { |
|
destroy(); |
|
} |
|
} |
|
|
|
@Override |
|
@SuppressWarnings("deprecation") |
|
protected void finalize() { |
|
boolean destroy = false; |
|
synchronized (this) { |
|
if (refCount > 0) { |
|
destroy = true; |
|
} |
|
} |
|
if (destroy) { |
|
destroy(); |
|
} |
|
} |
|
|
|
public long getSize() { |
|
return size; |
|
} |
|
|
|
public boolean isFinished() { |
|
return endTime != null; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
if (isFinished()) { |
|
return file.toString(); |
|
} |
|
return unFinishedFile.toString(); |
|
} |
|
|
|
ReadableByteChannel newChannel() throws IOException { |
|
if (!isFinished()) { |
|
throw new IOException("Chunk not finished"); |
|
} |
|
return ((SecuritySupport.newFileChannelToRead(file))); |
|
} |
|
|
|
public boolean inInterval(Instant startTime, Instant endTime) { |
|
if (startTime != null && getEndTime().isBefore(startTime)) { |
|
return false; |
|
} |
|
if (endTime != null && getStartTime().isAfter(endTime)) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
public SafePath getFile() { |
|
return file; |
|
} |
|
} |