-
Notifications
You must be signed in to change notification settings - Fork 515
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
Fix #380: Deferred, Semaphore and MVar #424
Conversation
This reverts commit dc59865.
09ce12d
to
cccd000
Compare
I broke |
Codecov Report
@@ Coverage Diff @@
## master #424 +/- ##
==========================================
+ Coverage 87.03% 89.66% +2.63%
==========================================
Files 69 70 +1
Lines 1905 2003 +98
Branches 199 199
==========================================
+ Hits 1658 1796 +138
+ Misses 247 207 -40 |
val cursor = r.iterator | ||
while (cursor.hasNext) { | ||
val next = cursor.next() | ||
val task = F.map(F.start(F.delay(next(a))))(mapUnit) |
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.
F.void
instead of F.map(..)(mapUnit)
?
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.
F.void
is more wasteful, because it translates to F.map(task)(_ => ())
and the Scala compiler won't reuse the created _ => ()
.
When doing performance-sensitive work, in general I care about these issues in about this order:
- avoiding extraneous async or lazy boundaries
- avoiding extraneous
compareAndSet
operations - avoiding extraneous memory allocations
Caching dummy functions like _ => ()
falls into point 3, and sometimes I screw up due to the code becoming unclear, but it doesn't happen that often 🙂
@@ -388,6 +388,80 @@ object Concurrent { | |||
def timeout[F[_], A](fa: F[A], duration: FiniteDuration)(implicit F: Concurrent[F], timer: Timer[F]): F[A] = | |||
timeoutTo(fa, duration, F.raiseError(new TimeoutException(duration.toString))) | |||
|
|||
/** | |||
* Function that creates an async and cancelable `F[A]`, similar with |
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.
What guarantees are provided w.r.t. cancelation of the returned action?
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.
This is a very good question.
I realized that the task generated by the provided generator function should be evaluated uninterruptedly, otherwise it can leak.
I fixed it and added a ScalaDoc comment about it.
It's now using bracketCase
, which tbh isn't ideal for performance, but it does so only after the first put
or take
attempt failed, so it's not on the happy path.
Fixes #380.
TL;DR about the issue:
acquire
andrelease
pair, like we have inDeferred
(get / complete),Semaphore
(acquire / release) andMVar
(take / put), all of them being similar, therelease
part can block indefinitely in case the bind continuation ofacquire
is a never ending loopacquire
to always fork after an await, however we do not haveshift
available because we don't have aContextShift
restriction specifiedF.unit.start.flatMap(_.join)
is NOT a replacement forshift
The solution is, when notifying consumers that are waiting for a value, to
start
and then forget about the result, something like this:This is the only reasonable solution for the
Concurrent
-enabled implementations. Some notes:release
(this is related to a remark by @SystemFw thatSemaphore
relies on listeners waking up in order and I really hope you only rely on the priority of the listeners and not on how the actual callbacks are being called, because that would be very wrong)startAndForget
in the type class hierarchy (Cats-Effect 2.0) ... unfortunately we cannot add it for Scala 2.11 without breaking binary compatibility, but maybe we can find alternative ways until thensuspendWithContext[A](f: ExecutionContext => F[A]): F[A]
inContextShift
; this too breaks binary compatibility, plus it involves some work in IO's run-loop, but that part is I believe doable without binary compat changesUnfortunately in fixing
MVar
I needed this function and it made sense to expose it: