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

Expose isNewCoroutine from CopyableThreadContextElement #3902

Open
wanyingd1996 opened this issue Oct 4, 2023 · 5 comments
Open

Expose isNewCoroutine from CopyableThreadContextElement #3902

wanyingd1996 opened this issue Oct 4, 2023 · 5 comments

Comments

@wanyingd1996
Copy link
Contributor

Use case

Hi, all. Our tracing system is based on ThreadLocal. To not lose changes to our ThreadLocal, we store the object in CopyableThreadContextElement.

ThreadLocal.set(trace)
withContext(context) { // copyForChild()
  // updateThreadContext()
  assertThat(ThreadLocal.get()).isEqualTo(trace)
  ...
}

class TraceThreadContextElement(val trace: Trace) {
  fun copyForChild() {
    return TraceThreadContextElement(ThreadLocal.get())
  }

  fun updateThreadContext() {
    ThreadLocal.set(trace)
  }
}

The above solution works fine for us, but we only want to propagate our object when used with withContext, we don't want to propagate the state into launch, because we as a tracing system, only consider withContext as part of the execution of the enclosing code block, since it is blocking, while launch is not.

Therefore, we hope there is a way to tell whether the CopyableThreadContextElement is created for withContext or launch, and I noticed coroutine implementation already has a boolean isNewCoroutine indicating the difference, and I wonder if is possible to expose this flag from copyForChild, thanks!

The Shape of the API

interface CopyableThreadContextElement {
  fun copyForChild(isNewCoroutine: Boolean)
  fun mergeForChild(isNewCoroutine: Boolean)
}
@dovchinnikov
Copy link
Contributor

Related #3414

@lowasser
Copy link
Contributor

Do you have an approach in mind to retain backwards compatibility?

@wanyingd1996
Copy link
Contributor Author

Deprecate the old API, and make the new API delegate to the old API by default. Then users has to at least override one of them. If both are overriden, then only the override for the new API will be used.

interface CopyableThreadContextElement<T> {
  @Deprecated
  fun copyForChild(): CopyableThreadContextElement<T> {
    throw NotImplementedError(
      "copyForChild(isNewCoroutine) not implemented")
  }
  fun copyForChild(
      isNewCoroutine: Boolean): CopyableThreadContextElement<T> {
    return copyForChild()
  }

  @Deprecated
  fun mergeForChild(
      element: CoroutineContext.Element): CoroutineContext {
    throw NotImplementedError(
      "mergeForChild(element, isNewCoroutine) not implemented")
  }
  fun mergeForChild(
      element: CoroutineContext.Element, isNewCoroutine: Boolean) {
    return mergeForChild(element)
  }
}

@wanyingd1996
Copy link
Contributor Author

Hi, any thoughts on this proposal? An alternative way with regard to compatibility issue:

interface CopyableThreadContextElement<T> {
  fun copyForChild(): CopyableThreadContextElement<T>
  fun copyForChildOnNewCoroutine(): CopyableThreadContextElement<T> = copyForChild()
}

Make copyForChild being used when isNewCoroutine is false, and copyForChildOnNewCoroutine for the other case, by default copyForChildOnNewCoroutine uses implementation of copyForChild. This might be easier than my earlier proposal. Thanks!

@wanyingd1996
Copy link
Contributor Author

Hi, any updates for the issue? having distinction between new coroutine and switching context will make our tracing clients life easier, so that they don't need to manually set ThreadLocal everywhere, only for launches. Thanks!

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

3 participants