Release the whole broker when thread creation fails - #5563
Open
BNCMK wants to merge 1 commit into
Open
Conversation
rd_free(rkb) frees the outer struct only. Everything rd_kafka_broker_destroy_final() would release is abandoned with it: rkb_origname, rkb_ApiVersions, the ops queue, six rd_avg structures, the locks and condition variables, and the two references taken above. Unwind through the destructor instead. It asserts it runs on the broker's own thread, which was never created here, so the assertion fires on an uninitialised handle. At this point the broker is not on rk_brokers and nothing else can reach it, so the calling thread is its only owner; recording that makes the assertion true rather than bypassed. Both references drop, the count reaches zero, and the destructor releases everything including the pipe, so the explicit closes are no longer needed and are removed. Refs confluentinc#4456
|
Please sign the Contributor License Agreement here before this PR can be approved. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The broker is abandoned, not only its descriptors, when thread creation fails
Follows #4456, which reports two pipe descriptors leaking when
thrd_create()fails in
rd_kafka_broker_add(). That report is correct and its fix closes thedescriptors. The same line abandons everything else the broker owns, and this is
a separate defect on the same path.
Applies on top of the #4456 fix.
src/rdkafka_broker.c, verified against 2.15.0.What else is abandoned
rd_kafka_broker_add()allocates the broker, its wake-up pipe, its ops queue,its averaging structures, its name, its locks and condition variables, and takes
two references on it, all before calling
thrd_create(). On failure the functionreaches:
rd_free()frees the outer struct and nothing else. Everythingrd_kafka_broker_destroy_final()would release is left behind:rkb_origname,rkb_ApiVersionsrkb_opsqueuerd_kafka_q_destroy_ownerrd_avgstructuresThe descriptors #4456 reports are the visible part, because they accumulate
somewhere a person can look. The rest is invisible until a process that retries
broker creation runs long enough to notice its heap.
Why the obvious fix aborts
Calling the destructor twice, once per reference, is the natural repair and it
fails:
rd_kafka_broker_destroy_final()opens with that assertion because in everyother case it is reached from the broker's own thread. Here that thread was
never created, so the handle is uninitialised and the assertion fires on
whatever it contains.
The fix
At this point the broker is not yet on
rk_brokers,rd_kafka_sasl_broker_inithas not run, and nothing else holds a pointer to it, so the calling thread is the
only thread that owns it. Recording that before tearing down makes the assertion
true rather than bypassed:
Both references drop, the count reaches zero, and the destructor releases
everything including the descriptors, so the explicit closes added for #4456 are
no longer needed and this patch removes them.
The alternative is to keep closing resources inline as each is noticed. That is
how the path arrived here: the next resource added to a broker would have to be
remembered in two places, and the second place is the one nobody looks at.
If the assignment to
rkb_threadis unwelcome, the same result comes fromrelaxing the assertion for a broker whose thread was never started. That is a
maintainer's call about which invariant to state, and either way the teardown
belongs in one function.
Measured
Same reproduction as #4456, which counts
/proc/self/fdbefore and after.MALLOC_CHECK_=3Twelve rounds under
MALLOC_CHECK_=3report no double free, and the normal pathwith thread creation succeeding is unchanged.
The diff
Tooling
The descriptor half was confirmed at the syscall boundary with
ExecVis, which records what every process on
a machine did without the program being instrumented, recompiled or aware. The
heap half was found by reading what the destructor releases and comparing it with
what the failure path does, which is the kind of comparison a leak checker only
reaches once the path has actually run, and this one runs only when thread
creation fails.