[ 
https://issues.apache.org/jira/browse/ZOOKEEPER-5073?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Wenhai Wang updated ZOOKEEPER-5073:
-----------------------------------
    Description: 
The ZooKeeper multi-threaded C client may hang indefinitely when either worker 
thread fails to start during client initialization.

h3. Affected code

The issue is located in:

{{zookeeper-client/zookeeper-client-c/src/mt_adaptor.c}}

The {{start_threads()}} function initializes the startup barrier with:

{code:c}
adaptor->threadsToWait = 2;
{code}

It then creates the IO thread and completion thread:

{code:c}
rc = pthread_create(&adaptor->io, 0, do_io, zh);
assert("pthread_create() failed for the IO thread" && !rc);

rc = pthread_create(&adaptor->completion, 0, do_completion, zh);
assert("pthread_create() failed for the completion thread" && !rc);

wait_for_others(zh);
{code}

h3. Problem

When assertions are disabled, for example in a release build compiled with 
{{NDEBUG}}, the return values of {{pthread_create()}} are effectively ignored.

If either {{pthread_create()}} call fails, the corresponding worker thread is 
not created. However, {{threadsToWait}} remains initialized to {{2}}.

Each successfully created worker thread calls {{notify_thread_ready()}} and 
decrements {{threadsToWait}} once. The initialization thread calls 
{{wait_for_others()}} and waits until {{threadsToWait}} becomes zero.

For example, if the IO thread is created successfully but the completion thread 
fails to start:

# {{threadsToWait}} is initialized to {{2}}.

# The IO thread decrements it to {{1}}.

# The completion thread does not exist and therefore cannot perform the second 
decrement.

# The IO thread waits in {{notify_thread_ready()}}.

# The initialization thread waits in {{wait_for_others()}}.

# {{threadsToWait}} can never become zero.

As a result, {{zookeeper_init()}} does not return, and the client 
initialization hangs indefinitely.

When assertions are enabled, the process aborts instead of returning an 
initialization error. Therefore, the runtime resource failure is not handled 
gracefully in either build mode.

h3. Expected behavior

The C client should detect a non-zero return value from {{pthread_create()}}, 
stop and join any worker thread that was already created, clean up the 
initialized resources, and return an initialization error to the caller.

h3. Actual behavior

When assertions are disabled, the client waits indefinitely on the startup 
condition variable.

When assertions are enabled, the process terminates through {{assert()}}.

h3. Reproduction

The issue can be reproduced deterministically by:

# Building the ZooKeeper C client with {{NDEBUG}} enabled.

# Injecting an {{EAGAIN}} failure into either the first or second 
{{pthread_create()}} call.

# Calling {{zookeeper_init()}}.

# Observing that {{zookeeper_init()}} does not return.

A typical blocked thread state is:

{noformat}
Initialization thread:
pthread_cond_wait
wait_for_others
start_threads
adaptor_init
zookeeper_init

Successfully created worker thread:
pthread_cond_wait
notify_thread_ready
do_io or do_completion
{noformat}

h3. Root cause

{{start_threads()}} assumes that both {{pthread_create()}} calls always succeed 
and uses assertions instead of normal runtime error handling.

The startup barrier count is not adjusted when thread creation fails. In 
addition, the failure cannot be propagated because {{start_threads()}} 
currently returns {{void}}.


  was:
The ZooKeeper multi-threaded C client may hang indefinitely when either of the 
worker threads fails to start during client initialization.
h3. Affected code

The issue is located in:

{{zookeeper-client/zookeeper-client-c/src/mt_adaptor.c}}

The {{start_threads()}} function initializes the startup barrier with:

{{adaptor->threadsToWait = 2;}}

It then creates the IO thread and completion thread:

 

{{rc = pthread_create(&adaptor->io, 0, do_io, zh);
assert("pthread_create() failed for the IO thread" && !rc);

rc = pthread_create(&adaptor->completion, 0, do_completion, zh);
assert("pthread_create() failed for the completion thread" && !rc);

wait_for_others(zh);}}
h3. Problem

When assertions are disabled, for example in a release build compiled with 
{{{}NDEBUG{}}}, the return values of {{pthread_create()}} are effectively 
ignored.

If either {{pthread_create()}} call fails, the corresponding worker thread is 
not created. However, {{threadsToWait}} remains initialized to {{{}2{}}}.

Each successfully created worker thread calls {{notify_thread_ready()}} and 
decrements {{threadsToWait}} once. The initialization thread calls 
{{wait_for_others()}} and waits until {{threadsToWait}} becomes zero.

For example, if the IO thread is created successfully but the completion thread 
fails to start:
 # {{threadsToWait}} is initialized to {{{}2{}}}.

 # The IO thread decrements it to {{{}1{}}}.

 # The completion thread does not exist and therefore cannot perform the second 
decrement.

 # The IO thread waits in {{{}notify_thread_ready(){}}}.

 # The initialization thread waits in {{{}wait_for_others(){}}}.

 # {{threadsToWait}} can never become zero.

As a result, {{zookeeper_init()}} does not return and the client initialization 
hangs indefinitely.

When assertions are enabled, the process aborts instead of returning an 
initialization error, which also means the runtime resource failure is not 
handled gracefully.
h3. Expected behavior

The C client should detect a non-zero return value from 
{{{}pthread_create(){}}}, stop and join any worker thread that was already 
created, clean up the initialized resources, and return an initialization error 
to the caller.
h3. Actual behavior

When assertions are disabled, the client waits indefinitely on the startup 
condition variable.

When assertions are enabled, the process terminates through {{{}assert(){}}}.
h3. Reproduction

The issue can be reproduced deterministically by:
 # Building the ZooKeeper C client with {{NDEBUG}} enabled.

 # Injecting an {{EAGAIN}} failure into either the first or second 
{{pthread_create()}} call.

 # Calling {{{}zookeeper_init(){}}}.

 # Observing that {{zookeeper_init()}} does not return.

A typical blocked thread state is:

 

{{Initialization thread:
pthread_cond_wait
wait_for_others
start_threads
adaptor_init
zookeeper_init

Successfully created worker thread:
pthread_cond_wait
notify_thread_ready
do_io or do_completion}}
h3. Root cause

{{start_threads()}} assumes that both {{pthread_create()}} calls always succeed 
and uses assertions instead of normal runtime error handling.

The startup barrier count is not adjusted when thread creation fails, and the 
failure cannot be propagated because {{start_threads()}} currently returns 
{{{}void{}}}.


> C client hangs indefinitely when pthread_create fails during initialization
> ---------------------------------------------------------------------------
>
>                 Key: ZOOKEEPER-5073
>                 URL: https://issues.apache.org/jira/browse/ZOOKEEPER-5073
>             Project: ZooKeeper
>          Issue Type: Bug
>    Affects Versions: 3.5.9, 3.7.0, 3.10.0
>         Environment: OS: Debian10
> Architecture: x86_64
> Compiler: GCC 8.3.0
> ZooKeeper version: 3.10.0
> Client mode: multi-threaded
>            Reporter: Wenhai Wang
>            Priority: Major
>              Labels: hang
>         Attachments: README.md, build.sh, error.log, pthread_create_fail.c, 
> run_fault_test.sh, zk_init_probe.c
>
>
> The ZooKeeper multi-threaded C client may hang indefinitely when either 
> worker thread fails to start during client initialization.
> h3. Affected code
> The issue is located in:
> {{zookeeper-client/zookeeper-client-c/src/mt_adaptor.c}}
> The {{start_threads()}} function initializes the startup barrier with:
> {code:c}
> adaptor->threadsToWait = 2;
> {code}
> It then creates the IO thread and completion thread:
> {code:c}
> rc = pthread_create(&adaptor->io, 0, do_io, zh);
> assert("pthread_create() failed for the IO thread" && !rc);
> rc = pthread_create(&adaptor->completion, 0, do_completion, zh);
> assert("pthread_create() failed for the completion thread" && !rc);
> wait_for_others(zh);
> {code}
> h3. Problem
> When assertions are disabled, for example in a release build compiled with 
> {{NDEBUG}}, the return values of {{pthread_create()}} are effectively ignored.
> If either {{pthread_create()}} call fails, the corresponding worker thread is 
> not created. However, {{threadsToWait}} remains initialized to {{2}}.
> Each successfully created worker thread calls {{notify_thread_ready()}} and 
> decrements {{threadsToWait}} once. The initialization thread calls 
> {{wait_for_others()}} and waits until {{threadsToWait}} becomes zero.
> For example, if the IO thread is created successfully but the completion 
> thread fails to start:
> # {{threadsToWait}} is initialized to {{2}}.
> # The IO thread decrements it to {{1}}.
> # The completion thread does not exist and therefore cannot perform the 
> second decrement.
> # The IO thread waits in {{notify_thread_ready()}}.
> # The initialization thread waits in {{wait_for_others()}}.
> # {{threadsToWait}} can never become zero.
> As a result, {{zookeeper_init()}} does not return, and the client 
> initialization hangs indefinitely.
> When assertions are enabled, the process aborts instead of returning an 
> initialization error. Therefore, the runtime resource failure is not handled 
> gracefully in either build mode.
> h3. Expected behavior
> The C client should detect a non-zero return value from {{pthread_create()}}, 
> stop and join any worker thread that was already created, clean up the 
> initialized resources, and return an initialization error to the caller.
> h3. Actual behavior
> When assertions are disabled, the client waits indefinitely on the startup 
> condition variable.
> When assertions are enabled, the process terminates through {{assert()}}.
> h3. Reproduction
> The issue can be reproduced deterministically by:
> # Building the ZooKeeper C client with {{NDEBUG}} enabled.
> # Injecting an {{EAGAIN}} failure into either the first or second 
> {{pthread_create()}} call.
> # Calling {{zookeeper_init()}}.
> # Observing that {{zookeeper_init()}} does not return.
> A typical blocked thread state is:
> {noformat}
> Initialization thread:
> pthread_cond_wait
> wait_for_others
> start_threads
> adaptor_init
> zookeeper_init
> Successfully created worker thread:
> pthread_cond_wait
> notify_thread_ready
> do_io or do_completion
> {noformat}
> h3. Root cause
> {{start_threads()}} assumes that both {{pthread_create()}} calls always 
> succeed and uses assertions instead of normal runtime error handling.
> The startup barrier count is not adjusted when thread creation fails. In 
> addition, the failure cannot be propagated because {{start_threads()}} 
> currently returns {{void}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to