Skip to content
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

Coroutines debugger may overwrite the actual state of coroutine during asynchronous resume #3193

Closed
qwwdfsad opened this issue Feb 17, 2022 · 1 comment
Assignees

Comments

@qwwdfsad
Copy link
Contributor

qwwdfsad commented Feb 17, 2022

Consider the following situation when the dispatcher in the context is multithreaded

suspend fun foo() = yield()

It basically can be boiled down to the following pseudocode:

fun foo(...) {
    uCont.intercepted().dispatchUsingDispatcher()
    // Notify the debugger the coroutine is suspended 
    probeCoroutineSuspended()
    return COROUTINE_SUSPENDED // Unroll the stack
}

where dispatch concurrently invokes probeCoroutineResumed to notify the debugger the coroutine is actually executing right now.

The problem is straightforward -- if timings are unlucky enough, probeCoroutineResumed can be invoked before probeCoroutineSuspended, with probeCoroutineSuspended overwriting the state and leaving it as SUSPENDED

@qwwdfsad qwwdfsad added the bug label Feb 17, 2022
@qwwdfsad
Copy link
Contributor Author

Steps to reproduce:

  • add while (true) to RunningThreadStackMergeTest
  • Re-run it once every 5 secs, the bug reproducer only before C2 JIT kicks in

Failing build https://teamcity.jetbrains.com/buildConfiguration/KotlinTools_KotlinxCoroutines_Build/3793970?showRootCauses=false&expandBuildChangesSection=true&expandBuildProblemsSection=true&expandBuildTestsSection=true

@qwwdfsad qwwdfsad added the debug label Aug 8, 2022
@qwwdfsad qwwdfsad self-assigned this Oct 31, 2022
qwwdfsad added a commit that referenced this issue Oct 31, 2022
### Reproducing scenario

For the following code:
```
suspend fun foo() = yield()
```

the following bytecode is produced:

```
fun foo(...) {
    uCont.intercepted().dispatchUsingDispatcher() // 1
    probeCoroutineSuspended() // 2
    return COROUTINE_SUSPENDED // Unroll the stack
}
```

And it is possible to observe the next 'probeCoroutineSuspended' **prior** to receiving 'probeCoroutineSuspended'.

To address this, a dedicated 'unmatchedResumes' field is introduced to coroutine state,
which keeps track of consecutive 'probeCoroutineResumed' without matching 'probeCoroutineSuspended'
and attributes lately arrived probes to it.

Unfortunately, it introduces a much more unlikely race when **two** 'probeCoroutineSuspended' are reordered, then we misattribute 'lastObservedFrame', but it is still better than misattribute actual coroutine state.

@volatile and @synchronized are also introduced to DebugCoroutineInfoImpl as previously they have been a subject to data-races as well

Fixes #3193
qwwdfsad added a commit that referenced this issue Oct 31, 2022
For the following code:
```
suspend fun foo() = yield()
```

the following bytecode is produced:

```
fun foo(...) {
    uCont.intercepted().dispatchUsingDispatcher() // 1
    probeCoroutineSuspended() // 2
    return COROUTINE_SUSPENDED // Unroll the stack
}
```

And it is possible to observe the next 'probeCoroutineSuspended' **prior** to receiving 'probeCoroutineSuspended'.

### Steps taken

To address this, a dedicated 'unmatchedResumes' field is introduced to the coroutine state, which keeps track of consecutive 'probeCoroutineResumed' without matching 'probeCoroutineSuspended' and attributes lately arrived probes to it.

Unfortunately, it introduces a much more unlikely race when **two** 'probeCoroutineSuspended' are reordered, then we misattribute 'lastObservedFrame', but it is still better than misattributing the actual coroutine state.

@volatile and @synchronized are also introduced to DebugCoroutineInfoImpl as previously they have been subject to data races as well

Fixes #3193
qwwdfsad added a commit that referenced this issue Feb 2, 2023
### Reproducing scenario

For the following code:
```
suspend fun foo() = yield()
```

the following bytecode is produced:

```
fun foo(...) {
    uCont.intercepted().dispatchUsingDispatcher() // 1
    probeCoroutineSuspended() // 2
    return COROUTINE_SUSPENDED // Unroll the stack
}
```

And it is possible to observe the next 'probeCoroutineSuspended' **prior** to receiving 'probeCoroutineSuspended'.

### Steps taken

To address this, a dedicated 'unmatchedResumes' field is introduced to the coroutine state, which keeps track of consecutive 'probeCoroutineResumed' without matching 'probeCoroutineSuspended' and attributes lately arrived probes to it.

Unfortunately, it introduces a much more unlikely race when **two** 'probeCoroutineSuspended' are reordered, then we misattribute 'lastObservedFrame', but it is still better than misattributing the actual coroutine state.

@volatile and @synchronized are also introduced to DebugCoroutineInfoImpl as previously they have been subject to data races as well

Fixes #3193
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant