Skip to content

Commit 815dcb0

Browse files
v.karamyshevLost-Fly
authored andcommitted
Fixes issue #912: Executes liftF effect twice in a product composition
When combining a Done fetch with a Blocked fetch in product/productR/map2, the previous implementation would call product(fa, c) or map2(fa, c)(f), causing the already-completed effect `fa` to be re-run. The fix uses the already-computed result value instead of re-running the entire fetch: - product: use c.map((a, _)) instead of product(fa, c) - productR: use c or c.as(b) instead of productR(fa)(c) - map2: use c.map(b => f(a, b)) instead of map2(fa, c)(f) Added unit tests to verify that side effects are executed exactly once.
1 parent 5a65c95 commit 815dcb0

2 files changed

Lines changed: 53 additions & 6 deletions

File tree

fetch/src/main/scala/fetch.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ object `package` {
301301
case (Done(a), Done(b)) =>
302302
Done[F, Z](f(a, b))
303303
case (Done(a), Blocked(br, c)) =>
304-
Blocked[F, Z](br, map2(fa, c)(f))
304+
Blocked[F, Z](br, c.map(b => f(a, b)))
305305
case (Blocked(br, c), Done(b)) =>
306-
Blocked[F, Z](br, map2(c, fb)(f))
306+
Blocked[F, Z](br, c.map(a => f(a, b)))
307307
case (Blocked(br, c), Blocked(br2, c2)) =>
308308
Blocked[F, Z](combineRequestMaps(br, br2), map2(c, c2)(f))
309309
case (_, Throw(e)) =>
@@ -324,9 +324,9 @@ object `package` {
324324
case (Done(a), Done(b)) =>
325325
Done[F, (A, B)]((a, b))
326326
case (Done(a), Blocked(br, c)) =>
327-
Blocked[F, (A, B)](br, product(fa, c))
327+
Blocked[F, (A, B)](br, c.map((a, _)))
328328
case (Blocked(br, c), Done(b)) =>
329-
Blocked[F, (A, B)](br, product(c, fb))
329+
Blocked[F, (A, B)](br, c.map((_, b)))
330330
case (Blocked(br, c), Blocked(br2, c2)) =>
331331
Blocked[F, (A, B)](combineRequestMaps(br, br2), product(c, c2))
332332
case (_, Throw(e)) =>
@@ -343,9 +343,9 @@ object `package` {
343343
case (Done(a), Done(b)) =>
344344
Done[F, B](b)
345345
case (Done(a), Blocked(br, c)) =>
346-
Blocked[F, B](br, productR(fa)(c))
346+
Blocked[F, B](br, c)
347347
case (Blocked(br, c), Done(b)) =>
348-
Blocked[F, B](br, productR(c)(fb))
348+
Blocked[F, B](br, c.as(b))
349349
case (Blocked(br, c), Blocked(br2, c2)) =>
350350
Blocked[F, B](combineRequestMaps(br, br2), productR(c)(c2))
351351
case (_, Throw(e)) =>

fetch/src/test/scala/FetchTests.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,4 +942,51 @@ class FetchTests extends FetchSpec {
942942

943943
io.map(_ shouldEqual List(0, 1, 2, 42)).unsafeToFuture()
944944
}
945+
946+
// Side effect re-execution tests (Issue #912)
947+
948+
"Product should not re-execute side effects from completed fetches" in {
949+
Ref[IO]
950+
.of(0)
951+
.flatMap { counter =>
952+
val fetchWithSideEffect: Fetch[IO, Unit] = Fetch.liftF(counter.update(_ + 1))
953+
954+
val program = (
955+
List.range(1, 10).map(one[IO]).reduce(_ >> _),
956+
fetchWithSideEffect
957+
).tupled
958+
959+
Fetch.run[IO](program) >> counter.get.map(_ shouldEqual 1)
960+
}
961+
.unsafeToFuture()
962+
}
963+
964+
"ProductR should not re-execute side effects from completed fetches" in {
965+
Ref[IO]
966+
.of(0)
967+
.flatMap { counter =>
968+
val fetchWithSideEffect: Fetch[IO, Unit] = Fetch.liftF(counter.update(_ + 1))
969+
970+
val program = List.range(1, 10).map(one[IO]).reduce(_ >> _) *> fetchWithSideEffect
971+
972+
Fetch.run[IO](program) >> counter.get.map(_ shouldEqual 1)
973+
}
974+
.unsafeToFuture()
975+
}
976+
977+
"mapN should not re-execute side effects from completed fetches" in {
978+
Ref[IO]
979+
.of(0)
980+
.flatMap { counter =>
981+
val fetchWithSideEffect: Fetch[IO, Int] = Fetch.liftF(counter.updateAndGet(_ + 1))
982+
983+
val program = (
984+
List.range(1, 10).map(one[IO]).reduce(_ >> _),
985+
fetchWithSideEffect
986+
).mapN((_, n) => n)
987+
988+
Fetch.run[IO](program) >> counter.get.map(_ shouldEqual 1)
989+
}
990+
.unsafeToFuture()
991+
}
945992
}

0 commit comments

Comments
 (0)