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

Andy Huang updated YUNIKORN-3404:
---------------------------------
    Description: 
The Kubernetes shim PlaceholderManager lifecycle implementation has race 
conditions and shutdown gaps around its running state and stop channel.

PlaceholderManager.Start() currently checks the running atomic and updates it 
in separate operations:
{code:go}
if mgr.isRunning() {
    return
}
mgr.setRunning(true)
go func() {
    // ...
}()
{code}
Concurrent Start() calls can both observe running=false before either call 
stores true, starting multiple cleanup goroutines for the same 
PlaceholderManager.

PlaceholderManager.Stop() has a similar check-then-act pattern:
{code:go}
if !mgr.isRunning() {
    return
}
mgr.stopChan <- struct{}{}
{code}
Concurrent Stop() calls can both observe running=true. Only one send can be 
received before the cleanup goroutine exits, potentially leaving another caller 
blocked indefinitely on the unbuffered stop channel.

There is an additional shutdown problem. The cleanup goroutine alternates 
between receiving from stopChan and cleanOrphanPlaceholders(). The latter holds 
the manager lock and can issue Delete calls without a deadline. If shutdown 
occurs while an orphan sweep is blocked on a slow API server, Stop() blocks on 
its unbuffered send until the complete sweep returns.

The running state is changed asynchronously by the cleanup goroutine after 
receiving the stop signal. Stop() therefore does not guarantee that the manager 
has stopped when it returns, and Start() and Stop() can leave lifecycle state 
inconsistent when invoked concurrently.

The lifecycle operations should be atomic, idempotent, and shutdown-safe. A 
likely approach is to use CompareAndSwap or sync.Once for lifecycle 
transitions, close a stop channel exactly once instead of sending on it, and 
provide a reliable way to wait for the cleanup goroutine to exit.

Expected behavior:
 * At most one PlaceholderManager cleanup goroutine can run.
 * Concurrent Start() calls do not start duplicate goroutines.
 * Stop() is safe and idempotent.
 * Concurrent Stop() calls do not block or panic.
 * Shutdown does not wait indefinitely for an orphan cleanup blocked on an API 
call.
 * When Stop() returns, the manager has either fully stopped or exposes a 
reliable way to wait for shutdown completion.
 * Start() and Stop() do not leave the running state inconsistent.

This was identified while reviewing KubernetesShim shutdown fixes:
 * https://issues.apache.org/jira/browse/YUNIKORN-3367
 * [https://github.com/apache/yunikorn-k8shim/pull/1074]

  was:
The Kubernetes shim PlaceholderManager lifecycle implementation has race 
conditions and shutdown gaps around its running state and stop channel.

PlaceholderManager.Start() currently checks the running atomic and updates it 
in separate operations:

{code:go}
if mgr.isRunning() {
    return
}
mgr.setRunning(true)
go func() {
    // ...
}()
{code}

Concurrent Start() calls can both observe running=false before either call 
stores true, starting multiple cleanup goroutines for the same 
PlaceholderManager.

PlaceholderManager.Stop() has a similar check-then-act pattern:

{code:go}
if !mgr.isRunning() {
    return
}
mgr.stopChan <- struct{}{}
{code}

Concurrent Stop() calls can both observe running=true. Only one send can be 
received before the cleanup goroutine exits, potentially leaving another caller 
blocked indefinitely on the unbuffered stop channel.

There is an additional shutdown problem. The cleanup goroutine alternates 
between receiving from stopChan and cleanOrphanPlaceholders(). The latter holds 
the manager lock and can issue Delete calls without a deadline. If shutdown 
occurs while an orphan sweep is blocked on a slow API server, Stop() blocks on 
its unbuffered send until the complete sweep returns.

The running state is changed asynchronously by the cleanup goroutine after 
receiving the stop signal. Stop() therefore does not guarantee that the manager 
has stopped when it returns, and Start() and Stop() can leave lifecycle state 
inconsistent when invoked concurrently.

The lifecycle operations should be atomic, idempotent, and shutdown-safe. A 
likely approach is to use CompareAndSwap or sync.Once for lifecycle 
transitions, close a stop channel exactly once instead of sending on it, and 
provide a reliable way to wait for the cleanup goroutine to exit.

Expected behavior:

* At most one PlaceholderManager cleanup goroutine can run.
* Concurrent Start() calls do not start duplicate goroutines.
* Stop() is safe and idempotent.
* Concurrent Stop() calls do not block or panic.
* Shutdown does not wait indefinitely for an orphan cleanup blocked on an API 
call.
* When Stop() returns, the manager has either fully stopped or exposes a 
reliable way to wait for shutdown completion.
* Start() and Stop() do not leave the running state inconsistent.

This was identified while reviewing KubernetesShim shutdown fixes:

* https://issues.apache.org/jira/browse/YUNIKORN-3367
* https://issues.apache.org/jira/browse/YUNIKORN-3368
* https://github.com/apache/yunikorn-k8shim/pull/1074


> PlaceholderManager Start and Stop operations have race conditions
> -----------------------------------------------------------------
>
>                 Key: YUNIKORN-3404
>                 URL: https://issues.apache.org/jira/browse/YUNIKORN-3404
>             Project: Apache YuniKorn
>          Issue Type: Bug
>          Components: shim - kubernetes
>            Reporter: Andy Huang
>            Priority: Major
>
> The Kubernetes shim PlaceholderManager lifecycle implementation has race 
> conditions and shutdown gaps around its running state and stop channel.
> PlaceholderManager.Start() currently checks the running atomic and updates it 
> in separate operations:
> {code:go}
> if mgr.isRunning() {
>     return
> }
> mgr.setRunning(true)
> go func() {
>     // ...
> }()
> {code}
> Concurrent Start() calls can both observe running=false before either call 
> stores true, starting multiple cleanup goroutines for the same 
> PlaceholderManager.
> PlaceholderManager.Stop() has a similar check-then-act pattern:
> {code:go}
> if !mgr.isRunning() {
>     return
> }
> mgr.stopChan <- struct{}{}
> {code}
> Concurrent Stop() calls can both observe running=true. Only one send can be 
> received before the cleanup goroutine exits, potentially leaving another 
> caller blocked indefinitely on the unbuffered stop channel.
> There is an additional shutdown problem. The cleanup goroutine alternates 
> between receiving from stopChan and cleanOrphanPlaceholders(). The latter 
> holds the manager lock and can issue Delete calls without a deadline. If 
> shutdown occurs while an orphan sweep is blocked on a slow API server, Stop() 
> blocks on its unbuffered send until the complete sweep returns.
> The running state is changed asynchronously by the cleanup goroutine after 
> receiving the stop signal. Stop() therefore does not guarantee that the 
> manager has stopped when it returns, and Start() and Stop() can leave 
> lifecycle state inconsistent when invoked concurrently.
> The lifecycle operations should be atomic, idempotent, and shutdown-safe. A 
> likely approach is to use CompareAndSwap or sync.Once for lifecycle 
> transitions, close a stop channel exactly once instead of sending on it, and 
> provide a reliable way to wait for the cleanup goroutine to exit.
> Expected behavior:
>  * At most one PlaceholderManager cleanup goroutine can run.
>  * Concurrent Start() calls do not start duplicate goroutines.
>  * Stop() is safe and idempotent.
>  * Concurrent Stop() calls do not block or panic.
>  * Shutdown does not wait indefinitely for an orphan cleanup blocked on an 
> API call.
>  * When Stop() returns, the manager has either fully stopped or exposes a 
> reliable way to wait for shutdown completion.
>  * Start() and Stop() do not leave the running state inconsistent.
> This was identified while reviewing KubernetesShim shutdown fixes:
>  * https://issues.apache.org/jira/browse/YUNIKORN-3367
>  * [https://github.com/apache/yunikorn-k8shim/pull/1074]



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

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to