[
https://issues.apache.org/jira/browse/CAMEL-26107?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Work on CAMEL-26107 started by shashank.
----------------------------------------
> camel-wal - WriteAheadResumeStrategy never replays the unconfirmed offsets
> after a restart: start() truncates the log before loadCache() reads it
> -------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-26107
> URL: https://issues.apache.org/jira/browse/CAMEL-26107
> Project: Camel
> Issue Type: Bug
> Reporter: shashank
> Assignee: shashank
> Priority: Major
>
> {{WriteAheadResumeStrategy}} keeps the offsets that the wrapped resume
> strategy has not confirmed yet in a log file, and is meant to replay them
> (the entries in state {{NEW}} or {{FAILED}}) on the next start. After a
> restart nothing is replayed (main 7b58ce0cdaf6):
> * {{start()}} creates {{new LogWriter(logFile, flushPolicy)}}; the
> {{LogWriter}} constructor opens the file with {{CREATE, WRITE,
> TRUNCATE_EXISTING}} and writes a new header.
> * {{loadCache()}} reads the file with a {{LogReader}} and replays the
> {{NEW}}/{{FAILED}} entries.
> * Camel calls {{start()}} first: {{ResumableProcessor.doStart}} starts the
> strategy, and the consumers (cassandraql, couchbase, couchdb, mongodb change
> streams, through {{ResumeStrategyHelper.resume}}; file and aws2-kinesis call
> {{loadCache()}} directly) call {{loadCache()}} in their own {{doStart}}. The
> {{Splitter}} also starts the strategy before {{loadCache()}}.
> So {{loadCache()}} reads a file with only the 12-byte header. Checked with a
> test: three {{NEW}} records, 135 bytes before {{start()}}, 12 bytes after,
> nothing replayed; with the bytes written back after {{start()}}, all three
> are replayed. The offsets that were in flight when the JVM stopped are lost
> silently, which is what the component is for.
> Once the log is read, two {{LogReader}} cases become reachable: a record cut
> short at the end of the file (a crash while writing it) throws
> {{BufferUnderflowException}} when the record spans two reads of the reader's
> 512 KiB buffer, and a negative slot length throws
> {{NegativeArraySizeException}}; a file shorter than the header throws
> {{BufferUnderflowException}} from the constructor.
> *Fix*: {{start()}} reads the unconfirmed entries of the existing log before
> it creates the writer, and appends them to the new log, so they survive
> another crash before the replay. As the wrapped strategy keeps the last
> offset of each key, only the last entry of each key in the log counts: it is
> replayed if it is {{NEW}} or {{FAILED}}, and an earlier entry of the same key
> is not (an old {{FAILED}} offset must not replace a later offset of the key
> that was confirmed, and the entries of a wrapped strategy that never calls
> back, such as the mongodb one, must not pile up across restarts).
> {{loadCache()}} replays them through the same path as a new offset, so the
> callback of the wrapped strategy marks each one {{PROCESSED}} or {{FAILED}}
> at its position in the new log (the replay no longer writes through
> {{updateState(PersistedLogEntry, ...)}}). A replay that fails, or an entry
> the adapter cannot deserialize, is logged with a WARN and does not stop the
> other entries nor the start of the route; the entry stays in the log.
> {{LogReader}} throws {{InvalidRecordException}} for an incomplete record or
> header (also across two buffer reads) and for a negative slot length;
> {{start()}} logs a WARN and replays the entries before such a record, and a
> file of another format is replaced by a new log with a WARN, as before. The
> route start does not fail on a damaged log.
> *Tests*: new {{WriteAheadResumeStrategyRestartTest}}, 23 tests through
> {{start()}} + {{loadCache()}}: NEW and FAILED replayed, PROCESSED not; only
> the last offset of each key; no pile-up with a wrapped strategy that never
> confirms; an entry that cannot be replayed does not stop the others nor the
> start; a failed replay is replayed on the next start; entries stay in the log
> until replayed; a last record truncated at 5 places, and across the reader
> buffer; a negative, huge or too long slot length and an invalid state; no
> file, empty, header only, shorter than a header, other format, other version.
> 17 fail on main ({{expected: <[key-0=0, key-2=2, key-3=3]> but was: <[]>}}),
> the 6 'nothing to replay' cases pass on main. With the strategy change but
> the old {{LogReader}}, 4 fail with {{NegativeArraySizeException}} /
> {{BufferUnderflowException}}. camel-wal: 46 tests, 0 failures; merged with
> the CAMEL-26088 branch: 50, 0 failures.
> *Upgrade guide*: 4.24 entry, as users now get the replay after a restart (the
> documented intent).
> *Not changed*: after a roll-over the log holds the newer entries before the
> older ones, so the last entry of a key and the replay order are those of the
> file, not the order of the offsets (no layer information in the file).
> {{LogReader}} still gives a {{PersistedLogEntry}} the channel position after
> the buffer fill; no caller writes at it any more.
> *Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x
> (same camel-wal Java sources, and {{start()}} before {{loadCache()}} in
> {{ResumableProcessor}}/{{ResumeStrategyHelper}}). Read, not run on the
> branches.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)