KAFKA-4901: Make ProduceRequest thread-safe#2689
Conversation
If request logging is enabled, it can be accessed concurrently from a network thread (which calls `toString`) and the request handler thread (which calls `clearPartitionRecords()`).
More consistent line length usage.
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Looks like trunk is broken due to headers that don't have the required format, PR to fix it: #2690 |
|
Note that the PR doesn't include tests yet. I wanted to get some feedback on the approach before doing that. |
|
retest this please |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
| .append(", acks=").append(acks) | ||
| .append(", timeout=").append(timeout); | ||
|
|
||
| if (partitionRecords == null) |
There was a problem hiding this comment.
nit: missing open parenthesis?
|
|
||
| if (partitionRecords == null) | ||
| bld.append(", partitions=").append(Utils.mkString(partitions, ",")); | ||
| else |
There was a problem hiding this comment.
Does this print out the record contents as well? If so, that seems very excessive.
I think the old ProduceRequest had it right: print out the various fields and on initialization of ProduceRequest, store a size-per-partition mapping so that even after clearing records, we can log the size-per-partition.
There was a problem hiding this comment.
Yeah, actually I agree with this. These messages have started to pollute some test logs.
There was a problem hiding this comment.
In other words, what I'm trying to say is maybe we can simply avoid the race by not attempting to print the records at all.
There was a problem hiding this comment.
Thanks for the feedback. I'll try to address the comments:
-
Formatting differs from Struct's. That's right, I used the same format as ProduceRequest.Builder as that was easier, but I can use Struct's formatting if people prefer that (there's some inconsistency, either way).
-
It prints a summary of the record contents (i.e.
Record(magic = %d, attributes = %d, compression = %s, crc = %d, %s = %d, key = %d bytes, value = %d bytes)since that's the current behaviour in trunk. Note that the old Scala class only printed the partitions and sizes if TRACE logging was enabled. For DEBUG logging, the partitions information wasn't printed at all. We lost that distinction with the Java classes. I can fix that for Fetch and Produce requests in this PR and we can tackle the rest later. It seems to me that printing the record summary if TRACE logging is enabled would make sense. However, since the information won't be available in many/most cases, it makes sense to keep it simple and never print more than the size.
| return struct; | ||
| } | ||
|
|
||
| @Override |
There was a problem hiding this comment.
It seems that the toString() method would generate different format of output depending on whether the request has been cleared or not? An alternative is to store the toString() result on construction, this introduces some overhead (hopefully not too much) but would maintain the original information of the requests.
There was a problem hiding this comment.
If we match the old ProducerRecord behavior and don't include the actual record data in the toString (which I think we should change), then your proposal ends up essentially matching what I said.
There was a problem hiding this comment.
Thanks for the feedback @becketqin. Generating the string is not cheap and we don't need it unless trace logging is enabled. What do you think of the approach suggested here:
There was a problem hiding this comment.
That makes sense. I was not thinking about printing the entire message content out, just the record summary.
|
I updated the PR as discussed although I left FetchRequest/Response alone for now since it's better for this PR to focus on ProduceRequest. I also started a system tests build for the Streams tests as they were failing due to this issue: https://jenkins.confluent.io/job/system-test-kafka-branch-builder/770/console |
|
Thanks @ijuma. Can you provide a snippet of what the actual output looks like now with verbose/nonverbose logging? |
| @Override | ||
| public Struct toStruct() { | ||
| // Store it in a local variable to protect against concurrent updates | ||
| Map<TopicPartition, MemoryRecords> partitionRecords = partitionRecordsOrFail(); |
There was a problem hiding this comment.
It seems that we don't need this line and partitionRecordsOrFail() any more?
There was a problem hiding this comment.
@junrao, I added that to make the code fail-fast in case assumptions are broken in the future. Calling toStruct after clearPartitionRecords is a bug and we should fail with an appropriate error message if that happens.
|
Regarding the system tests, there was a transient failure that did not happen on a subsequent re-run: https://jenkins.confluent.io/job/system-test-kafka-branch-builder/771/console @onurkaraman, the output would look like: |
|
Refer to this link for build results (access rights to CI server needed): |
| bld.append("{acks=").append(acks) | ||
| .append(",timeout=").append(timeout); | ||
|
|
||
| if (verbose) |
There was a problem hiding this comment.
In the non-verbose case, probably print out # of topic partitions?
There was a problem hiding this comment.
Good suggestion. I added that.
|
A couple more successful Streams system test builds: https://jenkins.confluent.io/job/system-test-kafka-branch-builder/772/console |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
Refer to this link for build results (access rights to CI server needed): |
|
@ijuma : Thanks for the patch. LGTM. |
If request logging is enabled,
ProduceRequestcan be accessedand mutated concurrently from a network thread (which calls
toString) and a request handler thread (which callsclearPartitionRecords()).That can lead to a
ConcurrentModificationExceptionwhen iteratingthe
partitionRecordsmap.The underlying thread-safety issue has existed since the server
started using the Java implementation of ProduceRequest in 0.10.0.
However, we were incorrectly not clearing the underlying struct until
0.10.2, so
toStringitself was thread-safe until that change. In 0.10.2,toStringis no longer thread-safe and we could potentially see aNullPointerExceptiongiven the right set of interleavings betweentoStringandclearPartitionRecordsalthough we haven't seen thathappen yet.
In trunk, we changed the requests to have a
toStructmethodinstead of creating a struct in the constructor and
toStringwasno longer printing the contents of the
Struct. This accidentallyfixed the race condition, but it meant that request logging was less
useful.
A couple of days ago,
AbstractRequest.toStringwas changed toprint the contents of the request by calling
toStruct().toString()and reintroduced the race condition. The impact is more visible
because we iterate over a
HashMap, which proactivelychecks for concurrent modification (unlike arrays).
We will need a separate PR for 0.10.2.