-
Notifications
You must be signed in to change notification settings - Fork 530
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
Quick and dirty unsafe queue API #3975
Quick and dirty unsafe queue API #3975
Conversation
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.
I like this idea. I've left a small comment about the scaladoc.
Another fun thing we could do is a Deferred
with a def unsafeComplete(a: A): Boolean
. (I'm not saying to do it in this PR, it's just a similar idea.)
There have been multiple attempts by you and me at similar ideas 😅 I think unlike |
Yep 😄
Yeah, ok, that's fair. (Although apparently I have trouble letting go of this idea...) |
cdc2129
to
0572831
Compare
Starting point for an unsafe queue API as part of std. Opening WIP PR to start the discussion.
For background… @armanbilge and I have been discussing this at length for a few months, prompted by some work that @kamilkloch was doing on high performance websocket handling. Specifically, we were looking at how to reduce the overhead associated with
Dispatcher.sequential
down to a minimum. In Kamil's case, there is a need for something much more bespoke than what could go into Cats Effect itself, but the line of reasoning was interesting nonetheless.The point was made that most practical use of
Dispatcher
– particularlyDispatcher.sequential
– is composed withQueue#offer
. In other words, most practical code isn't actually running general effects, it's just putting stuff onto an asynchronous queue. While in theory the general variant of that problem still requiresDispatcher
, there are special cases which can be handled with less overhead. An example of one is actually the work queue inside ofDispatcher
itself, which allows for enqueue from impure code and dequeue from pure code. This is precisely the sort of machinery that most people are looking for, and they useDispatcher
+Queue
to get it. We can special-case this to remove a whole layer of overhead in what is generally a fairly hot path.The way to special case here is to make the observation that our existing async queues already allow for impure
offer
-ish implementations. Or more specifically, our unbounded queue allows for an impureoffer
(since it will never block), and our bounded queue allows for an impuretryOffer
(since the queue might be full). This PR reifies this observation in a pair of traits:cats.effect.std.unsafe.BoundedQueueSink
andcats.effect.std.unsafe.UnboundedQueueSink
, and then implements those traits (trivially) in the existing implementations, exposing them in a pair of new constructors:Queue.unsafeBounded
andQueue.unsafeUnbounded
. These constructors return realQueue
s which can be used as normal, but with the added ability to use the appropriate offer variant impurely.This btw is a fun theoretical aside, since there's something modestly profound here. Cats Effect safely abstracts over bounded and unbounded queues because fiber blocking is cheap, so we're allowed to have a single
offer
signature (A => F[Unit]
) which works for both, despite the fact that it might block in the bounded case. We got this idea from Java's standard library, which plays the same trick but with threads, but blocking threads is expensive and so arguably Java's abstraction is a bit leaky. Rather than repeat that mistake with our own impure queues, we simply don't allow forunsafeOffer: A => Unit
on a bounded queue, which means we differentiate bounded and unbounded queues at the type level in this API (note that, unintuitively, an unbounded queue is a special case of a bounded queue in this paradigm).Anyway, I'm not super thrilled with the
Queue[F, A] with unsafe.BoundedQueueSink[F, A]
(and similar with unbounded) paradigm, so it might be saner to build anunsafe.BoundedQueue
andunsafe.UnboundedQueue
trait which simply names this type, particularly sinceF
is polyvariant. I am fond of theunsafe
package prefix though, and I think it works reasonably well.TODO