-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-10029; Don't update completedReceives when channels are closed to avoid ConcurrentModificationException #8705
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
Changes from 5 commits
74107fa
09589be
64f84b3
8cbe30e
a44db94
ceace47
d572d8e
e605e29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1597,6 +1597,8 @@ class SocketServerTest { | |
| testableSelector.waitForOperations(SelectorOperation.Poll, 1) | ||
|
|
||
| testableSelector.waitForOperations(SelectorOperation.CloseSelector, 1) | ||
| assertEquals(1, testableServer.uncaughtExceptions) | ||
| testableServer.uncaughtExceptions = 0 | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -1675,6 +1677,7 @@ class SocketServerTest { | |
| testWithServer(testableServer) | ||
| } finally { | ||
| shutdownServerAndMetrics(testableServer) | ||
| assertEquals(0, testableServer.uncaughtExceptions) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1730,6 +1733,7 @@ class SocketServerTest { | |
| new Metrics, time, credentialProvider) { | ||
|
|
||
| @volatile var selector: Option[TestableSelector] = None | ||
| @volatile var uncaughtExceptions = 0 | ||
|
|
||
| override def newProcessor(id: Int, requestChannel: RequestChannel, connectionQuotas: ConnectionQuotas, listenerName: ListenerName, | ||
| protocol: SecurityProtocol, memoryPool: MemoryPool): Processor = { | ||
|
|
@@ -1742,6 +1746,12 @@ class SocketServerTest { | |
| selector = Some(testableSelector) | ||
| testableSelector | ||
| } | ||
|
|
||
| override private[network] def processException(errorMessage: String, throwable: Throwable): Unit = { | ||
| if (errorMessage.contains("uncaught exception")) | ||
| uncaughtExceptions += 1 | ||
| super.processException(errorMessage, throwable) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1797,19 +1807,20 @@ class SocketServerTest { | |
| class PollData[T] { | ||
| var minPerPoll = 1 | ||
| val deferredValues = mutable.Buffer[T]() | ||
| val currentPollValues = mutable.Buffer[T]() | ||
| def update(newValues: mutable.Buffer[T]): Unit = { | ||
| if (currentPollValues.nonEmpty || deferredValues.size + newValues.size >= minPerPoll) { | ||
| def update(newValues: mutable.Buffer[T], addToCurrentResult: T => Unit): Unit = { | ||
| val currentPollValues = mutable.Buffer[T]() | ||
| if (deferredValues.size + newValues.size >= minPerPoll) { | ||
| if (deferredValues.nonEmpty) { | ||
| currentPollValues ++= deferredValues | ||
| deferredValues.clear() | ||
| } | ||
| currentPollValues ++= newValues | ||
| } else | ||
| deferredValues ++= newValues | ||
| } | ||
| def reset(): Unit = { | ||
| currentPollValues.clear() | ||
|
|
||
| // Update results for the current poll | ||
| newValues.clear() | ||
| currentPollValues.foreach(addToCurrentResult) | ||
| } | ||
| } | ||
| val cachedCompletedReceives = new PollData[NetworkReceive]() | ||
|
|
@@ -1861,20 +1872,33 @@ class SocketServerTest { | |
|
|
||
| override def poll(timeout: Long): Unit = { | ||
| try { | ||
| assertEquals(0, super.completedReceives().size) | ||
| assertEquals(0, super.completedSends().size) | ||
|
|
||
| pollCallback.apply() | ||
| while (!pendingClosingChannels.isEmpty) { | ||
| makeClosing(pendingClosingChannels.poll()) | ||
| } | ||
| allCachedPollData.foreach(_.reset) | ||
| runOp(SelectorOperation.Poll, None) { | ||
| super.poll(pollTimeoutOverride.getOrElse(timeout)) | ||
| } | ||
| } finally { | ||
| super.channels.forEach(allChannels += _.id) | ||
| allDisconnectedChannels ++= super.disconnected.asScala.keys | ||
| cachedCompletedReceives.update(super.completedReceives.asScala.toBuffer) | ||
| cachedCompletedSends.update(super.completedSends.asScala) | ||
| cachedDisconnected.update(super.disconnected.asScala.toBuffer) | ||
|
|
||
| val map: util.Map[String, NetworkReceive] = JTestUtils.fieldValue(this, classOf[Selector], "completedReceives") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we call this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ijuma Thanks for the review, updated. |
||
| def addToCompletedReceives(receive: NetworkReceive): Unit = { | ||
| val channelOpt = Option(super.channel(receive.source)).orElse(Option(super.closingChannel(receive.source))) | ||
| channelOpt.foreach { channel => map.put(channel.id, receive) } | ||
| } | ||
|
|
||
| // For each result type (completedReceives/completedSends/disconnected), defer the result to a subsequent poll() | ||
| // if `minPerPoll` results are not yet available. When sufficient results are available, all available results | ||
| // including previously deferred results are returned. This allows tests to process `minPerPoll` elements as the | ||
| // results of a single poll iteration. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think your refactoring has added the comments to other places. Maybe we can add "This allows tests to process |
||
| cachedCompletedReceives.update(super.completedReceives.asScala.toBuffer, addToCompletedReceives) | ||
| cachedCompletedSends.update(super.completedSends.asScala, s => super.completedSends.add(s)) | ||
| cachedDisconnected.update(super.disconnected.asScala.toBuffer, d => super.disconnected.put(d._1, d._2)) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1899,12 +1923,6 @@ class SocketServerTest { | |
| } | ||
| } | ||
|
|
||
| override def disconnected: java.util.Map[String, ChannelState] = cachedDisconnected.currentPollValues.toMap.asJava | ||
|
|
||
| override def completedSends: java.util.List[Send] = cachedCompletedSends.currentPollValues.asJava | ||
|
|
||
| override def completedReceives: java.util.List[NetworkReceive] = cachedCompletedReceives.currentPollValues.asJava | ||
|
|
||
| override def close(id: String): Unit = { | ||
| runOp(SelectorOperation.Close, Some(id)) { | ||
| super.close(id) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the goal of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We return
minPerPollresults together, so the current values are cleared and then populated as necessary. The code is now in the same place, so hopefully that is clearer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I was asking is that we call
toBufferbefore calling this method for a couple of cases. That will basically create a copy, so then thisclearwon't do anything useful?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I refactored a bit to clear the original buffer in each case.