-
Notifications
You must be signed in to change notification settings - Fork 36
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
Stateful tracing, revisted #108
Conversation
Huh, does this actually work? I never understood how it's supposed to work with trace.span("outer") {
IO.both(
trace.span("inner-left")(...),
trace.span("inner-right")(...)
)
} |
A mess happens. An absolute mess. |
Yeah, my implementation always calls |
This old FiberRef is not far off what I did here, but it adds a |
typelevel/cats-effect#3100 (comment) suggests that this " So maybe my example would be: trace.span("outer") {
IO.both(
trace.fork *> trace.span("inner-left")(...),
trace.fork *> trace.span("inner-right")(...)
)
} Still, I'd worry this is brittle in practice—to fork or not to fork? Non-trace-aware libraries wouldn't be able to do this, so it would be responsibility of traced callers to apply forking at all the right places. |
5a7a771 was a mad dash to compilation before I go to my next appointment, but it does the right thing on @armanbilge's challenge. |
Ah, so the idea is we should |
private def createScope(scope: Scope): Resource[F, Unit] = | ||
Resource( | ||
local.modify(oldRef => | ||
(Ref.unsafe[F, Scope](scope), () -> local.set(oldRef).to[F])).to[F] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, this one's cheating. It fails the interruptScope test. It's pretty, though.
_ <- Resource | ||
.make(local.set(newRef).to[F])(_ => local.set(oldRef).to[F]) | ||
ref <- Resource.eval(local.get.to[F]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref
is not guaranteed to be newRef
!
As discussed in typelevel/cats-effect#3405, there are things that can go wrong with for {
a <- createScope(...).allocated
b <- createScope(...).allocated
_ <- a._2 // sets back to original scope
_ <- b._2 // sets back to b's parent's scope, a
} yield () The stateful scopes are always going to inhabit a minefield that the local scopes do not. As much as I prefer this API, I'm about ready to concede. |
Is there an updated overview anywhere of what you mean by local vs stateful scopes? I remember a table from some old Natchez issues but it would be helpful to me to have something more recent (and that incorporates everything you've learned since then). |
I'll riff on an old table.
Footnotes
|
I bet we can get @bpholt asked about this in typelevel/natchez#706 as well. Can someone point me at something that relies on Basically I imagine you want to transform |
|
I mean, if |
I'm not sure what we're disagreeing about. You can make an implementation of |
I think, my claim is that you can make an implementation, even with the thread-hopping :) but this is better discussed/prototyped with a concrete foreign lib. |
I don't have a library handy but I have a stupid effect. It's a lawful local in that it won't be observed outside the implicit def threadLocalLocal[E](threadLocal: ThreadLocal[E]) = new Local[IO, E] {
val applicative = Applicative[IO]
def ask[E2 >: E] = IO(threadLocal.get())
def local[A](fa: IO[A])(f: E => E): IO[A] = {
IO {
val p = threadLocal.get()
threadLocal.set(f(p))
p
}.bracket(_ => fa)(p => IO(threadLocal.set(p)))
}
}
IO {
val tl = new ThreadLocal[Int]()
tl.set(0)
tl
}.map(threadLocalLocal).flatMap { implicit local =>
(
local.scope(local.ask[Int])(1),
local.scope(local.ask[Int].evalOn(scala.concurrent.ExecutionContext.global))(2)
).tupled
}
|
Yeah, ok, you can't make that work with vanilla But I think that's the wrong approach. I think what we should do is use an ordinary |
The ThreadLocal is interesting, this branch no longer is. |
Inspired by @djspiewak's toot, here's
IOLocal[Ref[IO, Scope]]
. This structure passes the following:In so doing, it puts @iRevive's original stateful design back on the table, at the expense of Kleisli et al.
Performance is probably hot trash, and there are probably dragons with
Resource#allocated
and releasing in an order abominable unto the acquisitions. But it passes the interruptedScope test!