Refactor JournalSegmentDescriptor 79/110779/15
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 5 May 2024 18:50:00 +0000 (20:50 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 5 May 2024 19:04:43 +0000 (21:04 +0200)
Converted JournalSegmentDescriptor to record, unused fields
removed. Also eliminated extra read of descriptor on initial
segment load.

JIRA: CONTROLLER-2107
Change-Id: I5ef45cd2bebfafe0a9dcc9838e056d9ed0bf7958
Signed-off-by: Oleksandr Zharov <oleksandr.zharov@pantheon.tech>
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
atomix-storage/src/main/java/io/atomix/storage/journal/JournalSegmentDescriptor.java
atomix-storage/src/main/java/io/atomix/storage/journal/SegmentedJournal.java
atomix-storage/src/test/java/io/atomix/storage/journal/JournalSegmentDescriptorTest.java

index 587271a0889c9ff110f52f75d36c45ec8b0d15d3..24652f003b99f4d15e7effec033dfe8134e1d7d4 100644 (file)
  */
 package io.atomix.storage.journal;
 
-import static java.util.Objects.requireNonNull;
-
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
 
 /**
  * Stores information about a {@link JournalSegment} of the log.
@@ -32,280 +30,271 @@ import org.eclipse.jdt.annotation.NonNull;
  * The segment descriptor manages metadata related to a single segment of the log. Descriptors are stored within the
  * first {@code 64} bytes of each segment in the following order:
  * <ul>
- * <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number within
- * each log. Segments with in-sequence identifiers should contain in-sequence indexes.</li>
- * <li>{@code index} (64-bit signed integer) - The effective first index of the segment. This indicates the index at which
- * the first entry should be written to the segment. Indexes are monotonically increasing thereafter.</li>
- * <li>{@code version} (64-bit signed integer) - The version of the segment. Versions are monotonically increasing
- * starting at {@code 1}. Versions will only be incremented whenever the segment is rewritten to another memory/disk
- * space, e.g. after log compaction.</li>
- * <li>{@code maxSegmentSize} (32-bit unsigned integer) - The maximum number of bytes allowed in the segment.</li>
- * <li>{@code maxEntries} (32-bit signed integer) - The total number of expected entries in the segment. This is the final
- * number of entries allowed within the segment both before and after compaction. This entry count is used to determine
- * the count of internal indexing and deduplication facilities.</li>
- * <li>{@code updated} (64-bit signed integer) - The last update to the segment in terms of milliseconds since the epoch.
- * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
- * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
- * change to {@code updated}.</li>
- * <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked once
- * all entries have been committed to the segment. The lock state of each segment is used to determine log compaction
- * and recovery behavior.</li>
+ *   <li>{@code id} (64-bit signed integer) - A unique segment identifier. This is a monotonically increasing number
+ *       within each log. Segments with in-sequence identifiers should contain in-sequence indexes.</li>
+ *   <li>{@code index} (64-bit signed integer) - The effective first index of the segment. This indicates the index at
+ *       which the first entry should be written to the segment. Indexes are monotonically increasing thereafter.</li>
+ *   <li>{@code version} (64-bit signed integer) - The version of the segment. Versions are monotonically increasing
+ *       starting at {@code 1}. Versions will only be incremented whenever the segment is rewritten to another
+ *       memory/disk space, e.g. after log compaction.</li>
+ *   <li>{@code maxSegmentSize} (32-bit unsigned integer) - The maximum number of bytes allowed in the segment.</li>
+ *   <li>{@code maxEntries} (32-bit signed integer) - The total number of expected entries in the segment. This is the
+ *       final number of entries allowed within the segment both before and after compaction. This entry count is used
+ *       to determine the count of internal indexing and deduplication facilities.</li>
+ *   <li>{@code updated} (64-bit signed integer) - The last update to the segment in terms of milliseconds since the
+ *       epoch.
+ *       When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment
+ *       have been committed, the {@code updated} time should be set to the current time. Log compaction should not
+ *       result in a change to {@code updated}.</li>
+ *   <li>{@code locked} (8-bit boolean) - A boolean indicating whether the segment is locked. Segments will be locked
+ *       once all entries have been committed to the segment. The lock state of each segment is used to determine log
+ *       compaction and recovery behavior.</li>
  * </ul>
  * The remainder of the 64 segment header bytes are reserved for future metadata.
  *
  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
  */
-public final class JournalSegmentDescriptor {
-  public static final int BYTES = 64;
-
-  // Current segment version.
-  @VisibleForTesting
-  static final int VERSION = 1;
-
-  // The lengths of each field in the header.
-  private static final int VERSION_LENGTH = Integer.BYTES;     // 32-bit signed integer
-  private static final int ID_LENGTH = Long.BYTES;             // 64-bit signed integer
-  private static final int INDEX_LENGTH = Long.BYTES;          // 64-bit signed integer
-  private static final int MAX_SIZE_LENGTH = Integer.BYTES;    // 32-bit signed integer
-  private static final int MAX_ENTRIES_LENGTH = Integer.BYTES; // 32-bit signed integer
-  private static final int UPDATED_LENGTH = Long.BYTES;        // 64-bit signed integer
-
-  // The positions of each field in the header.
-  private static final int VERSION_POSITION = 0;                                         // 0
-  private static final int ID_POSITION = VERSION_POSITION + VERSION_LENGTH;              // 4
-  private static final int INDEX_POSITION = ID_POSITION + ID_LENGTH;                     // 12
-  private static final int MAX_SIZE_POSITION = INDEX_POSITION + INDEX_LENGTH;            // 20
-  private static final int MAX_ENTRIES_POSITION = MAX_SIZE_POSITION + MAX_SIZE_LENGTH;   // 24
-  private static final int UPDATED_POSITION = MAX_ENTRIES_POSITION + MAX_ENTRIES_LENGTH; // 28
-
-  /**
-   * Returns a descriptor builder.
-   * <p>
-   * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
-   *
-   * @return The descriptor builder.
-   */
-  public static Builder builder() {
-    return new Builder(ByteBuffer.allocate(BYTES));
-  }
-
-  /**
-   * Returns a descriptor builder for the given descriptor buffer.
-   *
-   * @param buffer The descriptor buffer.
-   * @return The descriptor builder.
-   * @throws NullPointerException if {@code buffer} is null
-   */
-  public static Builder builder(ByteBuffer buffer) {
-    return new Builder(buffer);
-  }
-
-  private final ByteBuffer buffer;
-  private final int version;
-  private final long id;
-  private final long index;
-  private final int maxSegmentSize;
-  private final int maxEntries;
-  private volatile long updated;
-  private volatile boolean locked;
-
-  /**
-   * @throws NullPointerException if {@code buffer} is null
-   */
-  public JournalSegmentDescriptor(ByteBuffer buffer) {
-    this.buffer = buffer;
-    this.version = buffer.getInt();
-    this.id = buffer.getLong();
-    this.index = buffer.getLong();
-    this.maxSegmentSize = buffer.getInt();
-    this.maxEntries = buffer.getInt();
-    this.updated = buffer.getLong();
-    this.locked = buffer.get() == 1;
-  }
-
-  /**
-   * Read a JournalSegmentDescriptor from a {@link Path}.
-   *
-   * @param path path to read from
-   * @return A {@link JournalSegmentDescriptor}
-   * @throws IOException if an I/O error occurs or there is not enough data
-   */
-  public static @NonNull JournalSegmentDescriptor readFrom(final Path path) throws IOException {
-      try (var channel = FileChannel.open(path, StandardOpenOption.READ)) {
-          final var buffer = ByteBuffer.allocate(BYTES);
-          final var readBytes = channel.read(buffer);
-          if (readBytes != BYTES) {
-              throw new IOException("Need " + BYTES + " bytes, only " + readBytes + " available");
-          }
-          return new JournalSegmentDescriptor(buffer.flip());
-      }
-  }
-
-  /**
-   * Returns the segment version.
-   * <p>
-   * Versions are monotonically increasing starting at {@code 1}.
-   *
-   * @return The segment version.
-   */
-  public int version() {
-    return version;
-  }
-
-  /**
-   * Returns the segment identifier.
-   * <p>
-   * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
-   * contain in-sequence indexes.
-   *
-   * @return The segment identifier.
-   */
-  public long id() {
-    return id;
-  }
+public record JournalSegmentDescriptor(
+        int version,
+        long id,
+        long index,
+        int maxSegmentSize,
+        int maxEntries,
+        long updated,
+        boolean locked) {
+    public static final int BYTES = 64;
 
-  /**
-   * Returns the segment index.
-   * <p>
-   * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
-   * increasing thereafter.
-   *
-   * @return The segment index.
-   */
-  public long index() {
-    return index;
-  }
+    // Current segment version.
+    @VisibleForTesting
+    static final int VERSION = 1;
 
-  /**
-   * Returns the maximum count of the segment.
-   *
-   * @return The maximum allowed count of the segment.
-   */
-  public int maxSegmentSize() {
-    return maxSegmentSize;
-  }
-
-  /**
-   * Returns the maximum number of entries allowed in the segment.
-   *
-   * @return The maximum number of entries allowed in the segment.
-   */
-  public int maxEntries() {
-    return maxEntries;
-  }
+    /**
+     * Read a JournalSegmentDescriptor from a {@link Path}.
+     *
+     * @param path path to read from
+     * @return A {@link JournalSegmentDescriptor}
+     * @throws IOException if an I/O error occurs or there is not enough data
+     */
+    public static @NonNull JournalSegmentDescriptor readFrom(final Path path) throws IOException {
+        final byte[] bytes;
+        try (var is = Files.newInputStream(path, StandardOpenOption.READ)) {
+            bytes = is.readNBytes(BYTES);
+        }
 
-  /**
-   * Returns last time the segment was updated.
-   * <p>
-   * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
-   * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
-   * change to {@code updated}.
-   *
-   * @return The last time the segment was updated in terms of milliseconds since the epoch.
-   */
-  public long updated() {
-    return updated;
-  }
+        if (bytes.length != BYTES) {
+            throw new IOException("Need " + BYTES + " bytes, only " + bytes.length + " available");
+        }
 
-  /**
-   * Writes an update to the descriptor.
-   */
-  public void update(long timestamp) {
-    if (!locked) {
-      buffer.putLong(UPDATED_POSITION, timestamp);
-      this.updated = timestamp;
+        final var buffer = ByteBuffer.wrap(bytes);
+        return new JournalSegmentDescriptor(
+            buffer.getInt(),
+            buffer.getLong(),
+            buffer.getLong(),
+            buffer.getInt(),
+            buffer.getInt(),
+            buffer.getLong(),
+            buffer.get() == 1);
     }
-  }
 
-  /**
-   * Copies the segment to a new buffer.
-   */
-  JournalSegmentDescriptor copyTo(ByteBuffer buffer) {
-    buffer.putInt(version);
-    buffer.putLong(id);
-    buffer.putLong(index);
-    buffer.putInt(maxSegmentSize);
-    buffer.putInt(maxEntries);
-    buffer.putLong(updated);
-    buffer.put(locked ? (byte) 1 : (byte) 0);
-    return this;
-  }
+    /**
+     * Returns the segment version.
+     * <p>
+     * Versions are monotonically increasing starting at {@code 1}.
+     *
+     * @return The segment version.
+     */
+    public int version() {
+        return version;
+    }
 
-  @Override
-  public String toString() {
-    return MoreObjects.toStringHelper(this)
-        .add("version", version)
-        .add("id", id)
-        .add("index", index)
-        .add("updated", updated)
-        .toString();
-  }
+    /**
+     * Returns the segment identifier.
+     * <p>
+     * The segment ID is a monotonically increasing number within each log. Segments with in-sequence identifiers should
+     * contain in-sequence indexes.
+     *
+     * @return The segment identifier.
+     */
+    public long id() {
+        return id;
+    }
 
-  /**
-   * Segment descriptor builder.
-   */
-  public static class Builder {
-    private final ByteBuffer buffer;
+    /**
+     * Returns the segment index.
+     * <p>
+     * The index indicates the index at which the first entry should be written to the segment. Indexes are monotonically
+     * increasing thereafter.
+     *
+     * @return The segment index.
+     */
+    public long index() {
+        return index;
+    }
 
-    private Builder(ByteBuffer buffer) {
-      this.buffer = requireNonNull(buffer, "buffer cannot be null");
-      buffer.putInt(VERSION_POSITION, VERSION);
+    /**
+     * Returns the maximum count of the segment.
+     *
+     * @return The maximum allowed count of the segment.
+     */
+    public int maxSegmentSize() {
+        return maxSegmentSize;
     }
 
     /**
-     * Sets the segment identifier.
+     * Returns the maximum number of entries allowed in the segment.
      *
-     * @param id The segment identifier.
-     * @return The segment descriptor builder.
+     * @return The maximum number of entries allowed in the segment.
      */
-    public Builder withId(long id) {
-      buffer.putLong(ID_POSITION, id);
-      return this;
+    public int maxEntries() {
+        return maxEntries;
     }
 
     /**
-     * Sets the segment index.
+     * Returns last time the segment was updated.
+     * <p>
+     * When the segment is first constructed, the {@code updated} time is {@code 0}. Once all entries in the segment have
+     * been committed, the {@code updated} time should be set to the current time. Log compaction should not result in a
+     * change to {@code updated}.
      *
-     * @param index The segment starting index.
-     * @return The segment descriptor builder.
+     * @return The last time the segment was updated in terms of milliseconds since the epoch.
      */
-    public Builder withIndex(long index) {
-      buffer.putLong(INDEX_POSITION, index);
-      return this;
+    public long updated() {
+        return updated;
     }
 
     /**
-     * Sets maximum count of the segment.
+     * Returns this segment as an array of bytes
      *
-     * @param maxSegmentSize The maximum count of the segment.
-     * @return The segment descriptor builder.
+     * @return bytes
      */
-    public Builder withMaxSegmentSize(int maxSegmentSize) {
-      buffer.putInt(MAX_SIZE_POSITION, maxSegmentSize);
-      return this;
+    byte @NonNull [] toArray() {
+        final var bytes = new byte[BYTES];
+        ByteBuffer.wrap(bytes)
+            .putInt(version)
+            .putLong(id)
+            .putLong(index)
+            .putInt(maxSegmentSize)
+            .putInt(maxEntries)
+            .putLong(updated)
+            .put(locked ? (byte) 1 : (byte) 0);
+        return bytes;
     }
 
     /**
-     * Sets the maximum number of entries in the segment.
+     * Returns a descriptor builder.
+     * <p>
+     * The descriptor builder will write segment metadata to a {@code 48} byte in-memory buffer.
      *
-     * @param maxEntries The maximum number of entries in the segment.
-     * @return The segment descriptor builder.
-     * @deprecated since 3.0.2
+     * @return The descriptor builder.
      */
-    @Deprecated
-    public Builder withMaxEntries(int maxEntries) {
-      buffer.putInt(MAX_ENTRIES_POSITION, maxEntries);
-      return this;
+    public static Builder builder() {
+        return builder(VERSION);
     }
 
     /**
-     * Builds the segment descriptor.
+     * Returns a descriptor builder for the given descriptor buffer.
      *
-     * @return The built segment descriptor.
+     * @param version version to build
+     * @return The descriptor builder.
+     * @throws NullPointerException if {@code buffer} is null
      */
-    public JournalSegmentDescriptor build() {
-      buffer.rewind();
-      return new JournalSegmentDescriptor(buffer);
+    public static Builder builder(final int version) {
+        return new Builder(version);
+    }
+
+    /**
+     * Segment descriptor builder.
+     */
+    public static final class Builder {
+        private final int version;
+
+        private Long id;
+        private Long index;
+        private Integer maxSegmentSize;
+        private Integer maxEntries;
+        private Long updated;
+
+        Builder(final int version) {
+            this.version = version;
+        }
+
+        /**
+         * Sets the segment identifier.
+         *
+         * @param id The segment identifier.
+         * @return The segment descriptor builder.
+         */
+        public Builder withId(final long id) {
+            this.id = id;
+            return this;
+        }
+
+        /**
+         * Sets the segment index.
+         *
+         * @param index The segment starting index.
+         * @return The segment descriptor builder.
+         */
+        public Builder withIndex(final long index) {
+            this.index = index;
+            return this;
+        }
+
+        /**
+         * Sets maximum count of the segment.
+         *
+         * @param maxSegmentSize The maximum count of the segment.
+         * @return The segment descriptor builder.
+         */
+        public Builder withMaxSegmentSize(final int maxSegmentSize) {
+            this.maxSegmentSize = maxSegmentSize;
+            return this;
+        }
+
+        /**
+         * Sets the maximum number of entries in the segment.
+         *
+         * @param maxEntries The maximum number of entries in the segment.
+         * @return The segment descriptor builder.
+         * @deprecated since 3.0.2
+         */
+        @Deprecated
+        public Builder withMaxEntries(final int maxEntries) {
+            this.maxEntries = maxEntries;
+            return this;
+        }
+
+        /**
+         * Sets updated timestamp;
+         *
+         * @param updated Epoch milliseconds
+         * @return The segment descriptor builder.
+         */
+        public Builder withUpdated(final long updated) {
+            this.updated = updated;
+            return this;
+        }
+
+        /**
+         * Builds the segment descriptor.
+         *
+         * @return The built segment descriptor.
+         */
+        public JournalSegmentDescriptor build() {
+            return new JournalSegmentDescriptor(version,
+                checkSet(id, "id"),
+                checkSet(index, "index"),
+                checkSet(maxSegmentSize, "maxSegmentSize"),
+                checkSet(maxEntries, "maxEntries"),
+                checkSet(updated, "updated"),
+                false);
+        }
+
+        private static <T> @NonNull T checkSet(final @Nullable T obj, final String name) {
+            if (obj != null) {
+                return obj;
+            }
+            throw new IllegalArgumentException(name + " not set");
+        }
     }
-  }
 }
index 5f8519865b816541a5e70cd1d3bda1c0bc9c960d..507b0776e95b446fc8c67874704ccb2a43583a2b 100644 (file)
@@ -22,9 +22,6 @@ import static java.util.Objects.requireNonNull;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.StandardOpenOption;
 import java.util.Collection;
 import java.util.Map;
 import java.util.TreeMap;
@@ -63,7 +60,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
   private volatile long commitIndex;
 
   private final ConcurrentNavigableMap<Long, JournalSegment> segments = new ConcurrentSkipListMap<>();
-  private final Collection<SegmentedJournalReader> readers = ConcurrentHashMap.newKeySet();
+  private final Collection<SegmentedJournalReader<?>> readers = ConcurrentHashMap.newKeySet();
   private JournalSegment currentSegment;
 
   private volatile boolean open = true;
@@ -195,7 +192,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
    */
   public long size() {
     return segments.values().stream()
-        .mapToLong(segment -> segment.size())
+        .mapToLong(JournalSegment::size)
         .sum();
   }
 
@@ -216,6 +213,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
    * @param mode The mode in which to read entries.
    * @return The Raft log reader.
    */
+  @Override
   public JournalReader<E> openReader(long index, JournalReader.Mode mode) {
     final var segment = getSegment(index);
     final var reader = switch (mode) {
@@ -246,16 +244,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
     if (!segments.isEmpty()) {
       currentSegment = segments.lastEntry().getValue();
     } else {
-      JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
-          .withId(1)
-          .withIndex(1)
-          .withMaxSegmentSize(maxSegmentSize)
-          .withMaxEntries(maxEntriesPerSegment)
-          .build();
-
-      currentSegment = createSegment(descriptor);
-      currentSegment.descriptor().update(System.currentTimeMillis());
-
+      currentSegment = createSegment(1, 1);
       segments.put(1L, currentSegment);
     }
   }
@@ -282,20 +271,12 @@ public final class SegmentedJournal<E> implements Journal<E> {
    * Resets the current segment, creating a new segment if necessary.
    */
   private synchronized void resetCurrentSegment() {
-    JournalSegment lastSegment = getLastSegment();
-    if (lastSegment != null) {
-      currentSegment = lastSegment;
-    } else {
-      JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
-          .withId(1)
-          .withIndex(1)
-          .withMaxSegmentSize(maxSegmentSize)
-          .withMaxEntries(maxEntriesPerSegment)
-          .build();
-
-      currentSegment = createSegment(descriptor);
-
+    final var lastSegment = getLastSegment();
+    if (lastSegment == null) {
+      currentSegment = createSegment(1, 1);
       segments.put(1L, currentSegment);
+    } else {
+      currentSegment = lastSegment;
     }
   }
 
@@ -320,13 +301,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
     }
     segments.clear();
 
-    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
-        .withId(1)
-        .withIndex(index)
-        .withMaxSegmentSize(maxSegmentSize)
-        .withMaxEntries(maxEntriesPerSegment)
-        .build();
-    currentSegment = createSegment(descriptor);
+    currentSegment = createSegment(1, index);
     segments.put(index, currentSegment);
     return currentSegment;
   }
@@ -363,17 +338,10 @@ public final class SegmentedJournal<E> implements Journal<E> {
     assertOpen();
     assertDiskSpace();
 
-    JournalSegment lastSegment = getLastSegment();
-    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
-        .withId(lastSegment != null ? lastSegment.descriptor().id() + 1 : 1)
-        .withIndex(currentSegment.lastIndex() + 1)
-        .withMaxSegmentSize(maxSegmentSize)
-        .withMaxEntries(maxEntriesPerSegment)
-        .build();
-
-    currentSegment = createSegment(descriptor);
-
-    segments.put(descriptor.index(), currentSegment);
+    final var index = currentSegment.lastIndex() + 1;
+    final var lastSegment = getLastSegment();
+    currentSegment = createSegment(lastSegment != null ? lastSegment.descriptor().id() + 1 : 1, index);
+    segments.put(index, currentSegment);
     return currentSegment;
   }
 
@@ -424,34 +392,24 @@ public final class SegmentedJournal<E> implements Journal<E> {
   /**
    * Creates a new segment.
    */
-  JournalSegment createSegment(JournalSegmentDescriptor descriptor) {
-    File segmentFile = JournalSegmentFile.createSegmentFile(name, directory, descriptor.id());
+  JournalSegment createSegment(long id, long index) {
+    final var segmentFile = JournalSegmentFile.createSegmentFile(name, directory, id);
+    final var descriptor = JournalSegmentDescriptor.builder()
+        .withId(id)
+        .withIndex(index)
+        .withMaxSegmentSize(maxSegmentSize)
+        .withMaxEntries(maxEntriesPerSegment)
+        .withUpdated(System.currentTimeMillis())
+        .build();
 
-    RandomAccessFile raf;
-    FileChannel channel;
-    try {
-      raf = new RandomAccessFile(segmentFile, "rw");
-      raf.setLength(descriptor.maxSegmentSize());
-      channel =  raf.getChannel();
+    try (var raf = new RandomAccessFile(segmentFile, "rw")) {
+      raf.setLength(maxSegmentSize);
+      raf.write(descriptor.toArray());
     } catch (IOException e) {
       throw new StorageException(e);
     }
 
-    ByteBuffer buffer = ByteBuffer.allocate(JournalSegmentDescriptor.BYTES);
-    descriptor.copyTo(buffer);
-    buffer.flip();
-    try {
-      channel.write(buffer);
-    } catch (IOException e) {
-      throw new StorageException(e);
-    } finally {
-      try {
-        channel.close();
-        raf.close();
-      } catch (IOException e) {
-      }
-    }
-    JournalSegment segment = newSegment(new JournalSegmentFile(segmentFile), descriptor);
+    final var segment = newSegment(new JournalSegmentFile(segmentFile), descriptor);
     LOG.debug("Created segment: {}", segment);
     return segment;
   }
@@ -529,7 +487,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
    * @param index The index at which to reset readers.
    */
   void resetHead(long index) {
-    for (SegmentedJournalReader<E> reader : readers) {
+    for (var reader : readers) {
       if (reader.getNextIndex() < index) {
         reader.reset(index);
       }
@@ -542,7 +500,7 @@ public final class SegmentedJournal<E> implements Journal<E> {
    * @param index The index at which to reset readers.
    */
   void resetTail(long index) {
-    for (SegmentedJournalReader<E> reader : readers) {
+    for (var reader : readers) {
       if (reader.getNextIndex() >= index) {
         reader.reset(index);
       }
@@ -663,7 +621,8 @@ public final class SegmentedJournal<E> implements Journal<E> {
     private double indexDensity = DEFAULT_INDEX_DENSITY;
     private boolean flushOnCommit = DEFAULT_FLUSH_ON_COMMIT;
 
-    protected Builder() {
+    Builder() {
+      // Hidden on purpose
     }
 
     /**
@@ -742,7 +701,8 @@ public final class SegmentedJournal<E> implements Journal<E> {
      * @throws IllegalArgumentException If the {@code maxSegmentSize} is not positive
      */
     public Builder<E> withMaxSegmentSize(int maxSegmentSize) {
-      checkArgument(maxSegmentSize > JournalSegmentDescriptor.BYTES, "maxSegmentSize must be greater than " + JournalSegmentDescriptor.BYTES);
+      checkArgument(maxSegmentSize > JournalSegmentDescriptor.BYTES,
+          "maxSegmentSize must be greater than " + JournalSegmentDescriptor.BYTES);
       this.maxSegmentSize = maxSegmentSize;
       return this;
     }
index 6db959dc3fa3cebf9b1db5152d752d18f47fcfa0..5de345e48157528563d1a78d7eed7e0c97611b82 100644 (file)
  */
 package io.atomix.storage.journal;
 
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.nio.ByteBuffer;
-
-import static org.junit.Assert.assertEquals;
+import org.junit.jupiter.api.Test;
 
 /**
  * Segment descriptor test.
  *
  * @author <a href="http://github.com/kuujo">Jordan Halterman</a>
  */
-public class JournalSegmentDescriptorTest {
-
-  /**
-   * Tests the segment descriptor builder.
-   */
-  @Test
-  public void testDescriptorBuilder() {
-    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder(ByteBuffer.allocate(JournalSegmentDescriptor.BYTES))
-        .withId(2)
-        .withIndex(1025)
-        .withMaxSegmentSize(1024 * 1024)
-        .withMaxEntries(2048)
-        .build();
-
-    assertEquals(2, descriptor.id());
-    assertEquals(JournalSegmentDescriptor.VERSION, descriptor.version());
-    assertEquals(1025, descriptor.index());
-    assertEquals(1024 * 1024, descriptor.maxSegmentSize());
-    assertEquals(2048, descriptor.maxEntries());
-
-    assertEquals(0, descriptor.updated());
-    long time = System.currentTimeMillis();
-    descriptor.update(time);
-    assertEquals(time, descriptor.updated());
-  }
-
-  /**
-   * Tests copying the segment descriptor.
-   */
-  @Test
-  public void testDescriptorCopy() {
-    JournalSegmentDescriptor descriptor = JournalSegmentDescriptor.builder()
-        .withId(2)
-        .withIndex(1025)
-        .withMaxSegmentSize(1024 * 1024)
-        .withMaxEntries(2048)
-        .build();
-
-    long time = System.currentTimeMillis();
-    descriptor.update(time);
+class JournalSegmentDescriptorTest {
+    /**
+     * Tests the segment descriptor builder.
+     */
+    @Test
+    void testDescriptorBuilder() {
+        final var descriptor = JournalSegmentDescriptor.builder()
+            .withId(2)
+            .withIndex(1025)
+            .withMaxSegmentSize(1024 * 1024)
+            .withMaxEntries(2048)
+            .withUpdated(0)
+            .build();
 
-    descriptor = descriptor.copyTo(ByteBuffer.allocate(JournalSegmentDescriptor.BYTES));
+        assertEquals(2, descriptor.id());
+        assertEquals(JournalSegmentDescriptor.VERSION, descriptor.version());
+        assertEquals(1025, descriptor.index());
+        assertEquals(1024 * 1024, descriptor.maxSegmentSize());
+        assertEquals(2048, descriptor.maxEntries());
+        assertEquals(0, descriptor.updated());
+    }
 
-    assertEquals(2, descriptor.id());
-    assertEquals(JournalSegmentDescriptor.VERSION, descriptor.version());
-    assertEquals(1025, descriptor.index());
-    assertEquals(1024 * 1024, descriptor.maxSegmentSize());
-    assertEquals(2048, descriptor.maxEntries());
-    assertEquals(time, descriptor.updated());
-  }
+    /**
+     * Tests copying the segment descriptor.
+     */
+    @Test
+    void testToArray() {
+        assertArrayEquals(new byte[] {
+            0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 4, 1, 0, 16, 0, 0, 0, 0, 8, 0, 8, 7, 6, 5,
+            4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        }, JournalSegmentDescriptor.builder()
+            .withId(2)
+            .withIndex(1025)
+            .withMaxSegmentSize(1024 * 1024)
+            .withMaxEntries(2048)
+            .withUpdated(0x0807060504030201L)
+            .build()
+            .toArray());
+    }
 }