Skip to content

Conversation

@jatcwang
Copy link

@jatcwang jatcwang commented Mar 23, 2024

Rough draft for now. Any feedback/nitpick welcome :)

Async[F].delay {
val jContext: JContext = ctx.underlying
val _ = jContext.makeCurrent()
}.flatMap(_ => k(cb))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is problematic because there's no guarantee that delay and the subsequent flatMap are executed together. Any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can use evalOn with a custom ExecutonContext:

def tracedContext(ctx: JContext): ExecutionContext =
  new ExecutionContext {
    def execute(runnable: Runnable): Unit = {
      val scope = ctx.makeCurrent()
      try {
        runnable.run()
      } finally {
        scope.close()
      }
    }
    def reportFailure(cause: Throwable): Unit =
      cause.printStackTrace()
  }
  
def asyncWithContext[F[_]: Async, A](k: (Either[Throwable, A] => Unit) => F[Option[F[Unit]]])(implicit L: Local[F, Context]): F[A] =
  Local[F, Context].ask.flatMap { ctx =>
    Async[F].evalOn(
      Async[F].async[A](cb => k(cb)), 
      tracedContext(ctx.underlying)
    )
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But implications on performance are unknown :(

Copy link
Contributor

@iRevive iRevive Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evalOn is applicable for all scenarios, actually. But again, it may (and highly likely will) affect the performance.

def evalWithJContext[F[_]: Async, A](fa: F[A])(implicit L: Local[F, Context]): F[A] = 
  Local[F, Context].ask.flatMap { ctx =>
    Async[F].evalOn(fa, tracedContext(ctx.underlying))
  }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea :) Why do you think it'll affect performance (assuming fa pretty much immediately calls the async API instead of doing a bunch of CPU work)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK context shifts may be costly.

The current implementation of the tracedContext is basically a same-thread execution.

Here is the logic for the evalOn in the CE: https://github.com/typelevel/cats-effect/blob/3cf97ae4fd643f333febb4554af2fb603ed2c9d2/core/shared/src/main/scala/cats/effect/IOFiber.scala#L976-L988.

If I get it right, fa will be executed right within the fiber's loop.

@armanbilge perhaps you have some insides?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added your suggestion for now. I'm thinking that a typical async API involves using another threadpool so context shift isn't avoidable in that case. (But yeah will be good if an additional context shift can be avoided)

createOtel4s[IO].flatMap(otel4s => program(otel4s))
def run: IO[Unit] =
OtelJava.autoConfigured[IO]() { otel4s =>
implicit val local: Local[IO, Context] = otel4s.localContext
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
implicit val local: Local[IO, Context] = otel4s.localContext
import otel4s.localContext

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@jatcwang jatcwang marked this pull request as ready for review May 20, 2024 17:26
@jatcwang
Copy link
Author

Seems like one of the builds had an OOM. If someone can help me retrigger it it should pass 🤞

Comment on lines +204 to +216
def tracedContext(ctx: JContext): ExecutionContext =
new ExecutionContext {
def execute(runnable: Runnable): Unit = {
val scope = ctx.makeCurrent()
try {
runnable.run()
} finally {
scope.close()
}
}
def reportFailure(cause: Throwable): Unit =
cause.printStackTrace()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just getting caught up ...

Hmm, I'm concerned by this. If I'm reading correctly you have effectively implemented a ParasiticExecutionContext. If you run arbitrary effects on this you may stackoverflow and/or dead-lock.

@mergify mergify bot added the documentation Improvements or additions to documentation label Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants