Skip to content

Commit da8d095

Browse files
committed
Add KeyedMutexSpec
1 parent 9c8c23b commit da8d095

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2020-2024 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats
18+
package effect
19+
package std
20+
21+
import cats.arrow.FunctionK
22+
import cats.syntax.all._
23+
24+
import org.specs2.specification.core.Fragments
25+
26+
import scala.concurrent.duration._
27+
28+
final class KeyedMutexSpec extends BaseSpec with DetectPlatform {
29+
final override def executionTimeout = 2.minutes
30+
31+
"ConcurrentKeyedMutex" should {
32+
tests(KeyedMutex.apply[IO, Int])
33+
}
34+
35+
"KeyedMutex with dual constructors" should {
36+
tests(KeyedMutex.in[IO, IO, Int])
37+
}
38+
39+
"MapK'd KeyedMutex" should {
40+
tests(KeyedMutex[IO, Int].map(_.mapK[IO](FunctionK.id)))
41+
}
42+
43+
def tests(keyedMutex: IO[KeyedMutex[IO, Int]]): Fragments = {
44+
"execute action if free in the given key" in real {
45+
keyedMutex.flatMap { m => m.lock(key = 0).surround(IO.unit).mustEqual(()) }
46+
}
47+
48+
"be reusable in the same key" in real {
49+
keyedMutex.flatMap { m =>
50+
val p = m.lock(key = 0).surround(IO.unit)
51+
52+
(p, p).tupled.mustEqual(((), ()))
53+
}
54+
}
55+
56+
"free key on error" in real {
57+
keyedMutex.flatMap { m =>
58+
val p =
59+
m.lock(key = 0).surround(IO.raiseError(new Exception)).attempt >>
60+
m.lock(key = 0).surround(IO.unit)
61+
62+
p.mustEqual(())
63+
}
64+
}
65+
66+
"free key on cancellation" in ticked { implicit ticker =>
67+
val p = for {
68+
m <- keyedMutex
69+
f <- m.lock(key = 0).surround(IO.never).start
70+
_ <- IO.sleep(1.second)
71+
_ <- f.cancel
72+
_ <- m.lock(key = 0).surround(IO.unit)
73+
} yield ()
74+
75+
p must completeAs(())
76+
}
77+
78+
"block action if not free in the given key" in ticked { implicit ticker =>
79+
keyedMutex.flatMap { m =>
80+
m.lock(key = 0).surround(IO.never) >>
81+
m.lock(key = 0).surround(IO.unit)
82+
} must nonTerminate
83+
}
84+
85+
"not block action if using a different key" in ticked { implicit ticker =>
86+
keyedMutex.flatMap { m =>
87+
IO.race(
88+
m.lock(key = 0).surround(IO.never),
89+
IO.sleep(1.second) >> m.lock(key = 1).surround(IO.unit)
90+
).void
91+
} must completeAs(())
92+
}
93+
94+
"used concurrently in the same key" in ticked { implicit ticker =>
95+
keyedMutex.flatMap { m =>
96+
val p =
97+
IO.sleep(1.second) >>
98+
m.lock(key = 0).surround(IO.unit)
99+
100+
(p, p).parTupled
101+
} must completeAs(((), ()))
102+
}
103+
104+
"used concurrently with multiple keys" in ticked { implicit ticker =>
105+
keyedMutex.flatMap { m =>
106+
def p(key: Int): IO[Unit] =
107+
IO.sleep(1.second) >> m.lock(key).surround(IO.unit)
108+
109+
List.range(start = 0, end = 10).parTraverse_(p)
110+
} must completeAs(())
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)