-
Notifications
You must be signed in to change notification settings - Fork 560
Rewrote parTraverseN
and parTraverseN_
for better performance
#4451
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
base: series/3.6.x
Are you sure you want to change the base?
Changes from all commits
9ca5eec
d609d3e
9f0b328
607ab9a
569ce54
0109cc3
a3f8fe8
6a2588c
599b790
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -139,7 +139,52 @@ trait GenConcurrent[F[_], E] extends GenSpawn[F, E] { | |
|
||
implicit val F: GenConcurrent[F, E] = this | ||
|
||
MiniSemaphore[F](n).flatMap { sem => ta.parTraverse { a => sem.withPermit(f(a)) } } | ||
F.deferred[Option[E]] flatMap { preempt => | ||
F.ref[Set[Fiber[F, ?, ?]]](Set()) flatMap { supervision => | ||
MiniSemaphore[F](n) flatMap { sem => | ||
val results = ta traverse { a => | ||
preempt.tryGet flatMap { | ||
case Some(_) => | ||
// it's okay to produce never here because the early abort preceeds us | ||
// this effect won't get sequenced, so it can be anything really | ||
F.pure(F.never[B]) | ||
|
||
case None => | ||
F.uncancelable { poll => | ||
F.deferred[Outcome[F, E, B]] flatMap { result => | ||
val action = poll(sem.acquire) >> f(a) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this intentionally There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's intentional. I should probably comment it as such. I think most users probably believe that even the pure part of the function is parallelized (and subject to the semaphore). |
||
.guaranteeCase { oc => | ||
result.complete(oc) *> oc.fold( | ||
preempt.complete(None).void, | ||
e => preempt.complete(Some(e)).void, | ||
_ => F.unit) *> sem.release | ||
} | ||
.void | ||
.voidError | ||
.start | ||
|
||
action flatMap { fiber => | ||
supervision.update(_ + fiber) map { _ => | ||
result | ||
.get | ||
.flatMap(_.embed(F.canceled *> F.never)) | ||
.onCancel(fiber.cancel) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's not necessary. I've been building this up a bit incrementally so there's some overlapping logic I need to deduplicate due to the number of corner cases this function has. |
||
.guarantee(supervision.update(_ - fiber)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
results.flatMap(_.sequence) guaranteeCase { | ||
case Outcome.Succeeded(_) => F.unit | ||
// has to be done in parallel to avoid head of line issues | ||
case _ => supervision.get.flatMap(_.toList.parTraverse_(_.cancel)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -152,7 +197,52 @@ trait GenConcurrent[F[_], E] extends GenSpawn[F, E] { | |
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scaladoc above. |
||
implicit val F: GenConcurrent[F, E] = this | ||
|
||
MiniSemaphore[F](n).flatMap { sem => ta.parTraverse_ { a => sem.withPermit(f(a)) } } | ||
// TODO we need to write a test for error cancelation | ||
F.deferred[Option[E]] flatMap { preempt => | ||
F.ref[List[Fiber[F, ?, ?]]](Nil) flatMap { supervision => | ||
MiniSemaphore[F](n) flatMap { sem => | ||
val startAll = ta traverse_ { a => | ||
// first check to see if any of the effects have errored out | ||
// don't bother starting new things if that happens | ||
preempt.tryGet flatMap { | ||
case Some(_) => | ||
F.unit // allow the error to be resurfaced later | ||
|
||
case None => | ||
F.uncancelable { poll => | ||
// if the effect produces an error, race to kill all the rest | ||
val wrapped = f(a) guaranteeCase { oc => | ||
djspiewak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sem.release *> oc.fold( | ||
preempt.complete(None).void, | ||
e => preempt.complete(Some(e)).void, | ||
_ => F.unit) | ||
} | ||
|
||
val suppressed = wrapped.void.voidError | ||
|
||
poll(sem.acquire) >> suppressed.start flatMap { fiber => | ||
// supervision is handled very differently here: we never remove from the set | ||
supervision.update(fiber :: _) | ||
} | ||
} | ||
} | ||
} | ||
|
||
val cancelAll = supervision.get.flatMap(_.parTraverse_(_.cancel)) | ||
|
||
startAll.onCancel(cancelAll) *> | ||
// we block until it's all done by acquiring all the permits | ||
F.race(preempt.get *> cancelAll, sem.acquire.replicateA_(n)) *> | ||
// if we hit an error or self-cancelation in any effect, resurface it here | ||
// note that we can't lose errors here because of the permits: we know the fibers are done | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there may be a race here:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good point. I do this in both implementations. My thinking was that it increases parallelism somewhat (releasing the permit asap), but it does general this race condition. I'll fix it in both. |
||
preempt.tryGet flatMap { | ||
case Some(Some(e)) => F.raiseError(e) | ||
case Some(None) => F.canceled | ||
case None => F.unit | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
override def racePair[A, B](fa: F[A], fb: F[B]) | ||
|
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.
Scaladoc needs an update above.