shashank created CAMEL-26088:
--------------------------------

             Summary: camel-wal - a state update after a roll-over writes at a 
stale file position (corrupt log) or marks a newer record, and a shorter record 
leaves the end of the old content in the file
                 Key: CAMEL-26088
                 URL: https://issues.apache.org/jira/browse/CAMEL-26088
             Project: Camel
          Issue Type: Bug
            Reporter: shashank
            Assignee: shashank


{{LogWriter}} keeps the last {{maxRecordCount}} entries in {{TransactionLog}}, 
a circular buffer with a layer per loop (main 6a2f66ddec3e). After a roll-over 
each {{append}} writes the new entry and then the entries of the previous layer 
again after it. {{WriteAheadResumeStrategy}} marks each entry 
{{PROCESSED}}/{{FAILED}} from the callback of the wrapped strategy, so updates 
of the entries appended just before a roll-over arrive after it.

# {{updateState(CachedEntryInfo, ...)}} writes at {{entryInfo.getPosition()}}, 
the position of the first append, which holds another record once the roll-over 
wrote the entries again with records of different sizes: {{LogReader}} then 
fails with {{BufferTooSmallException ... 1667457792 bytes needed}} or reads a 
wrong record.
# {{TransactionLog.canUpdate}} accepts an update from any older layer when its 
index is above the current index: an update two or more layers late marks the 
newer record in that slot. It also rejects the entry at the current index (not 
replaced yet), so that update is dropped and the record stays {{NEW}} in the 
log although it was processed.
# When the new entry is shorter than the one it replaces, the end of the old 
content stays after the last entry and {{LogReader}} fails 
({{BufferTooSmallException ... 814950 bytes needed}}), without any update.

So the log holds wrong states or cannot be read. (On main a restart does not 
read it yet: {{WriteAheadResumeStrategy.start()}} truncates the file before 
{{loadCache()}} reads it, a separate defect, see below.)

*Fix*: {{canUpdate}} accepts the same layer, or the previous layer at or after 
the current index, never older layers. {{TransactionLog.EntryContainer}} keeps 
the current file position of the entry, set by every write, and {{updateState}} 
writes there. {{append}} truncates the file after the rewritten entries. 
{{append}}, {{updateState}} and {{reset}} take the writer's lock (the one flush 
takes), as the update reads the position the append moves and they can run on 
different threads.

*Tests*: new {{LogWriterUpdateAfterRollOverPositionTest}}, 4 tests, all fail on 
main (the two {{BufferTooSmallException}}s, {{expected: <{dd=NEW, bb=PROCESSED, 
cc=NEW}> but was: <{dd=NEW, bb=NEW, cc=NEW}>}}, {{expected: <{g=NEW, e=NEW, 
f=NEW}> but was: <{g=NEW, e=NEW, f=PROCESSED}>}}); two {{TransactionLogTest}} 
assertions that encoded the off-by-one are corrected. Unit tests of camel-wal 
with the fix: 27 run, 0 failures.

*Related*: {{WriteAheadResumeStrategy.start()}} creates the {{LogWriter}}, 
which opens the file with {{TRUNCATE_EXISTING}}, before {{loadCache()}} reads 
it, so the records of the previous run are never replayed (checked with a test: 
135 bytes before {{start()}}, 12 after, nothing replayed). Separate JIRA.

*Not changed*: {{LogReader}} gives a {{PersistedLogEntry}} the channel position 
after its buffer was filled, not the entry's position, so 
{{updateState(PersistedLogEntry, ...)}} during the replay looks wrong too 
(read, not run); follow-up.

*How it was found*: spec mining of {{TransactionLogTest.canUpdate}} over all 
layer/index pairs plus roll-overs with records of different sizes.

*Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x 
(same code). Read, not run.

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to