Skip to content

KAFKA-9156: fix LazyTimeIndex & LazyOffsetIndex concurrency#7760

Merged
ijuma merged 7 commits into
apache:trunkfrom
bookingcom:booking/fix-lazy-indexes-concurrency
Dec 2, 2019
Merged

KAFKA-9156: fix LazyTimeIndex & LazyOffsetIndex concurrency#7760
ijuma merged 7 commits into
apache:trunkfrom
bookingcom:booking/fix-lazy-indexes-concurrency

Conversation

@alexandrfox

@alexandrfox alexandrfox commented Nov 28, 2019

Copy link
Copy Markdown
Contributor

Fix of https://issues.apache.org/jira/browse/KAFKA-9156

Race condition in concurrent get method invocation of lazy indexes might lead to multiple OffsetIndex/TimeIndex objects being concurrently created. When that happens position of MappedByteBuffer in AbstractIndex is advanced to the last entry which in turn leads to a critical BufferOverflowException being thrown whenever leader or replica tries to append a record to the segment.

Moreover, file_= setter is seemingly also vulnerable to the race, since multiple threads can attempt to set a new file reference as well as create new Time/OffsetIndex objects at the same time. This might lead to the discrepant File references being held by e.g. LazyTimeIndex and its TimeIndex counterpart.

This patch attempts to fix the issue by making sure that index objects are atomically constructed when loaded lazily via get method. Additionally, file reference modifications are also made atomic and thread safe.

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

Comment thread core/src/main/scala/kafka/log/TimeIndex.scala Outdated
Comment thread core/src/main/scala/kafka/log/TimeIndex.scala Outdated
@ijuma ijuma requested review from hachikuji and junrao November 30, 2019 22:31
Comment thread core/src/main/scala/kafka/log/OffsetIndex.scala Outdated
Moved common logic of LazyOffsetIndex and LazyTimeIndex into a common
abstract class. Moved file reference modification into a sealed trait to
simplify "file" getters and setters.
Comment thread core/src/main/scala/kafka/log/AbstractLazyIndex.scala Outdated
@alexandrfox alexandrfox requested a review from ijuma December 1, 2019 19:41
Comment thread core/src/main/scala/kafka/log/AbstractLazyIndex.scala Outdated
Comment thread core/src/main/scala/kafka/log/AbstractLazyIndex.scala Outdated
@ijuma

ijuma commented Dec 1, 2019

Copy link
Copy Markdown
Member

Thanks for the update, looking better. I left a couple more comments.

Comment thread core/src/main/scala/kafka/log/OffsetIndex.scala Outdated
Comment thread core/src/main/scala/kafka/log/AbstractLazyIndex.scala Outdated
Comment thread core/src/main/scala/kafka/log/AbstractLazyIndex.scala Outdated
@ijuma

ijuma commented Dec 2, 2019

Copy link
Copy Markdown
Member

For some reason, I cannot push to your branch, so I submitted a PR: https://github.com/bookingcom/kafka/pull/1/files

@alexandrfox

alexandrfox commented Dec 2, 2019

Copy link
Copy Markdown
Contributor Author

For some reason, I cannot push to your branch, so I submitted a PR: https://github.com/bookingcom/kafka/pull/1/files

Probably due to org. settings. LGTM, so I merged it, thank you! Also, added a few missing type annotations to _maxEntries and _entries of AbsrtactIndex

@ijuma ijuma left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, just a minor documentation suggestion below. It would be good to get a second opinion from @junrao or @hachikuji before we merge this to 2.3, 2.4 and trunk.

Comment thread core/src/main/scala/kafka/log/LazyIndex.scala
@ijuma

ijuma commented Dec 2, 2019

Copy link
Copy Markdown
Member

@alexandrfox In the PR description, it's worth clarifying that mutation operations are done under a lock in the caller classes. I think the gap is that get can be called during Log.read which does not acquire any lock.

@alexandrfox

alexandrfox commented Dec 2, 2019

Copy link
Copy Markdown
Contributor Author

@alexandrfox In the PR description, it's worth clarifying that mutation operations are done under a lock in the caller classes. I think the gap is that get can be called during Log.read which does not acquire any lock.

Makes sense, I think LogCleaner also may call get while performing doClean (which can naturally happen at the same time as read).

@junrao junrao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@alexandrfox : Thanks for the PR. LGTM. Just a minor comment below.

def file_=(f: File)
}

private class IndexFile(@volatile var file: File) extends IndexWrapper

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does file need to be var? It seems that it's never updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hey @junrao, yes, this reference is updated when LazyIndex.file_= setter is invoked before LazyIndex.get

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@alexandrfox : Thanks. That makes sense. So, we can keep the code as it is.

@hachikuji hachikuji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Nice fix and thanks for the additional cleanup!

@alexandrfox

alexandrfox commented Dec 2, 2019

Copy link
Copy Markdown
Contributor Author

@ijuma looks like we have approvals, thanks to @junrao and @hachikuji. And thank you for taking your time guiding me through my first PR!

Also, thanks to @lushilin for pin-pointing the problem!

@ijuma

ijuma commented Dec 2, 2019

Copy link
Copy Markdown
Member

One job passed, two jobs failed with known flaky tests:

kafka.admin.ReassignPartitionsClusterTest.shouldListMovingPartitionsThroughApi
kafka.api.ConsumerBounceTest.testClose
kafka.api.ConsumerBounceTest.testRollingBrokerRestartsWithSmallerMaxGroupSizeConfigDisruptsBigGroup

@ijuma ijuma merged commit 075f583 into apache:trunk Dec 2, 2019
ijuma pushed a commit that referenced this pull request Dec 2, 2019
Race condition in concurrent  `get` method invocation of lazy indexes might lead
to multiple `OffsetIndex`/`TimeIndex` objects being concurrently created. When
that happens position of `MappedByteBuffer` in `AbstractIndex` is advanced to
the last entry which in turn leads to a critical `BufferOverflowException` being
thrown whenever leader or replica tries to append a record to the segment.

Moreover, `file_=` setter is seemingly also vulnerable to the race, since multiple
threads can attempt to set a new file reference as well as create new
Time/OffsetIndex objects at the same time. This might lead to the discrepant
File references being held by e.g. LazyTimeIndex and its TimeIndex counterpart.

This patch attempts to fix the issue by making sure that index objects are
atomically constructed when loaded lazily via `get` method. Additionally, `file`
reference modifications are also made atomic and thread safe.

Note that the `Lazy*Index` mutation operations are executed with a lock held by
the callers, but `get` can be called without a lock (e.g. from `Log.read`).

Reviewers: Jun Rao <junrao@gmail.com>, Jason Gustafson <jason@confluent.io>, Shilin Lu, Ismael Juma <ismael@juma.me.uk>
ijuma pushed a commit that referenced this pull request Dec 2, 2019
Race condition in concurrent  `get` method invocation of lazy indexes might lead
to multiple `OffsetIndex`/`TimeIndex` objects being concurrently created. When
that happens position of `MappedByteBuffer` in `AbstractIndex` is advanced to
the last entry which in turn leads to a critical `BufferOverflowException` being
thrown whenever leader or replica tries to append a record to the segment.

Moreover, `file_=` setter is seemingly also vulnerable to the race, since multiple
threads can attempt to set a new file reference as well as create new
Time/OffsetIndex objects at the same time. This might lead to the discrepant
File references being held by e.g. LazyTimeIndex and its TimeIndex counterpart.

This patch attempts to fix the issue by making sure that index objects are
atomically constructed when loaded lazily via `get` method. Additionally, `file`
reference modifications are also made atomic and thread safe.

Note that the `Lazy*Index` mutation operations are executed with a lock held by
the callers, but `get` can be called without a lock (e.g. from `Log.read`).

Reviewers: Jun Rao <junrao@gmail.com>, Jason Gustafson <jason@confluent.io>, Shilin Lu, Ismael Juma <ismael@juma.me.uk>
@ijuma

ijuma commented Dec 2, 2019

Copy link
Copy Markdown
Member

Merged to trunk, 2.4 and 2.3.

@alexandrfox alexandrfox deleted the booking/fix-lazy-indexes-concurrency branch December 3, 2019 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants