Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CQ: Fix entry missing from cache leading to crash on read #11288

Merged
merged 1 commit into from
May 21, 2024

Conversation

lhoguin
Copy link
Contributor

@lhoguin lhoguin commented May 21, 2024

The issue comes from a mechanic that allows us to avoid writing to disk when a message has already been consumed. It works fine in normal circumstances, but fan-out makes things trickier.

When multiple queues write and read the same message, we could get a crash. Let's say queues A and B both handle message Msg.

  • Queue A asks store to write Msg
  • Queue B asks store to write Msg
  • Queue B asks store to delete Msg (message was immediately consumed)
  • Store processes Msg write from queue A
    • Store writes Msg to current file
  • Store processes Msg write from queue B
    • Store notices queue B doesn't need Msg anymore; doesn't write
    • Store clears Msg from the cache
  • Queue A tries to read Msg
    • Msg is missing from the cache
    • Queue A tries to read from disk
    • Msg is in the current write file and may not be on disk yet
    • Crash

The problem is that the store clears Msg from the cache. We need all messages written to the current file to remain in the cache as we can't guarantee the data is on disk when comes the time to read. That is, until we roll over to the next file.

The issue was that a match was wrong, instead of matching a single location from the index, the code was matching against a list. The error was present in the code for almost 13 years since commit 2ef30dc.

Types of Changes

The issue comes from a mechanic that allows us to avoid writing
to disk when a message has already been consumed. It works fine
in normal circumstances, but fan-out makes things trickier.

When multiple queues write and read the same message, we could
get a crash. Let's say queues A and B both handle message Msg.

* Queue A asks store to write Msg
* Queue B asks store to write Msg
* Queue B asks store to delete Msg (message was immediately consumed)
* Store processes Msg write from queue A
  * Store writes Msg to current file
* Store processes Msg write from queue B
  * Store notices queue B doesn't need Msg anymore; doesn't write
  * Store clears Msg from the cache
* Queue A tries to read Msg
  * Msg is missing from the cache
  * Queue A tries to read from disk
  * Msg is in the current write file and may not be on disk yet
  * Crash

The problem is that the store clears Msg from the cache. We need
all messages written to the current file to remain in the cache
as we can't guarantee the data is on disk when comes the time
to read. That is, until we roll over to the next file.

The issue was that a match was wrong, instead of matching a single
location from the index, the code was matching against a list. The
error was present in the code for almost 13 years since commit
2ef30dc.
@lhoguin
Copy link
Contributor Author

lhoguin commented May 21, 2024

The following command should trigger the bug before this patch, and not trigger the bug after this patch:

while true; do perf-test -e amq.fanout -t fanout -y 49 -c 1 -qp q-%d -qpf 1 -qpt 50 \
-qa x-queue-version=2 -ad false -f persistent -csd 2 -s 5000 -z 10; done

It might need converting to non-zsh shell.

The bug can be seen when looking at the logs at the same time (tail -F /path/to/log).

@lhoguin
Copy link
Contributor Author

lhoguin commented May 21, 2024

While the bug does occur in v3.12.x and earlier, it doesn't result in a crash on read, because those versions would defer the read to the message store process when they couldn't read directly. But we have removed that from v3.13.x by ensuring queues can always read from cache or from disk. Except we didn't expect this bug to pop up.

@michaelklishin
Copy link
Member

I had to run PerfTest from the binary distribution like so (note the different -e argument to avoid a precondition_failed due to different built-in exchange properties):

while true; do ./bin/runjava com.rabbitmq.perf.PerfTest -f persistent -e server.11288.fanout -t fanout -y 49 -c 1 -qp q-%d -qpf 1 -qpt 50 -qa x-queue-version=2 -ad false -f persistent -csd 2 -s 5000 -z 10; done

Copy link
Member

@michaelklishin michaelklishin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could reproduce the exception quickly against a 3.13.2 node but not this PR.

This is a type of issues that Dialyzer could have caught but in index_lookup/2 in this module we delegate to an unknown (to Dialyzer) other module.

Copy link
Contributor

@mkuratczyk mkuratczyk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Can't reproduce anymore

@lhoguin lhoguin marked this pull request as ready for review May 21, 2024 16:08
@michaelklishin michaelklishin merged commit 6047583 into main May 21, 2024
17 of 18 checks passed
@michaelklishin michaelklishin deleted the loic-fix-fan-out-pread-crash-main branch May 21, 2024 17:25
michaelklishin added a commit that referenced this pull request May 21, 2024
CQ: Fix entry missing from cache leading to crash on read (backport #11288)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants