Refactor Journal{Reader,Writer} class hierarchy

Description

Navigating through the implementation details is rather daunting, as there are four implementations of JournalWriter:

  1. SegmentedJournalWriter, given out via SegmentedJournalWriter.openWriter()

  2. MappableJournalSegmentWriter, used by JournalSegment, which forwards requests to one of

  3. FileChannelJournalSegmentWriter, used for buffered access

  4. MappedJournalSegmentWriter, used for memory-mapped access

The same holds true for JournalReader and makes navigating the codebase rather strange, as it would seem those four are equivalent, but they are not.

First, SegmentedJournalWriter is the only real implementation, hence it should be hosting the interface definition and be a final class.

Second, we should have an abstract class named JournalSegmentWriter, which holds the API contract and code shared between FileChannelJournalSegmentWriter and MappedJournalSegmentWriter.

Third, MappableJournalSegmentWriter seems to only be a lifecycle helper to deal with closing the channel, indexing and switching (potentially) mapping the file into memory. I think that class should just get integrated into JournalSegment.

Activity

Show:

Robert Varga March 11, 2024 at 11:18 AM
Edited

We'll retain the interfaces and hide the implementations.

Robert Varga March 10, 2024 at 9:44 PM

So MappedJournalSegment{Reader,Writer} are really just delegators, forwarding to the current implementation and used by SegmentedJournal{Reader,Writer}. Access to these is protected through reference counting – i.e. we always acquire/release the underlying segment before getting the writer or creating a reader.

The lifecycle is not really what we want, as reader's implementation seems to be subservient to the reference count and hence we need to track readers only to be able to switch their implementation – essentially to cater to a non-existing edge case where the refcount drops to zero while there is a reader open.

This is not what we want to do: we really want the mapping to remain intact for as long as there are references to it. As such, we really need to expand the internal interface to have an acquireWriter() and acquireNewReader() – which will internalize refcounting and just give out the requested object, which will then remain alive until it is relinquished.

Done

Details

Assignee

Reporter

Labels

Components

Fix versions

Priority

Created March 9, 2024 at 4:48 PM
Updated March 11, 2024 at 5:35 PM
Resolved March 11, 2024 at 5:35 PM