Skip to content

Commit

Permalink
Removed the need to be inside a 'structured' block when calling the '…
Browse files Browse the repository at this point in the history
…race' function
  • Loading branch information
rcardin committed Jun 14, 2024
1 parent 9bbc5e0 commit 30c7b2a
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 87 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ The library provides the `race` method to race two jobs. The `race` function ret

```scala 3
val results = new ConcurrentLinkedQueue[String]()
val actual: Int | String = structured {
val actual: Int | String =
race[Int, String](
{
delay(1.second)
Expand All @@ -190,7 +190,6 @@ val actual: Int | String = structured {
"42"
}
)
}
actual should be("42")
results.toArray should contain theSameElementsInOrderAs List("job2")
```
Expand Down
5 changes: 2 additions & 3 deletions core/src/main/scala/in/rcard/sus4s/sus4s.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ object sus4s {
* <h2>Example</h2>
* {{{
* val results = new ConcurrentLinkedQueue[String]()
* val actual: Int | String = structured {
* val actual: Int | String =
* race[Int, String](
* {
* delay(1.second)
Expand All @@ -244,7 +244,6 @@ object sus4s {
* "42"
* }
* )
* }
* actual should be("42")
* results.toArray should contain theSameElementsInOrderAs List("job2")
* }}}
Expand All @@ -260,7 +259,7 @@ object sus4s {
* @return
* The result of the first block that completes
*/
def race[A, B](firstBlock: Suspend ?=> A, secondBlock: Suspend ?=> B): Suspend ?=> A | B = {
def race[A, B](firstBlock: Suspend ?=> A, secondBlock: Suspend ?=> B): A | B = {
val loomScope = new ShutdownOnSuccess[A | B]()
given suspended: Suspend = SuspendScope(loomScope.asInstanceOf[StructuredTaskScope[Any]])
try {
Expand Down
152 changes: 70 additions & 82 deletions core/src/test/scala/RaceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,126 +10,114 @@ import scala.util.Try
class RaceSpec extends AnyFlatSpec with Matchers {
"Racing two functions" should "return the result of the first one that completes and cancel the execution of the other" in {
val results = new ConcurrentLinkedQueue[String]()
val actual: Int | String = structured {
race[Int, String](
{
delay(1.second)
results.add("job1")
throw new RuntimeException("Error")
}, {
delay(500.millis)
results.add("job2")
"42"
}
)
}
val actual: Int | String = race[Int, String](
{
delay(1.second)
results.add("job1")
throw new RuntimeException("Error")
}, {
delay(500.millis)
results.add("job2")
"42"
}
)

actual should be("42")
results.toArray should contain theSameElementsInOrderAs List("job2")
}

it should "return the result of the second one if the first one throws an exception" in {
val results = new ConcurrentLinkedQueue[String]()
val actual: Int | String = structured {
race(
{
delay(1.second)
results.add("job1")
42
}, {
delay(500.millis)
results.add("job2")
throw new RuntimeException("Error")
}
)
}
val actual: Int | String = race(
{
delay(1.second)
results.add("job1")
42
}, {
delay(500.millis)
results.add("job2")
throw new RuntimeException("Error")
}
)

actual should be(42)
results.toArray should contain theSameElementsInOrderAs List("job2", "job1")
}

it should "honor the structural concurrency and wait for all the jobs to complete" in {
val results = new ConcurrentLinkedQueue[String]()
val actual: Int | String = structured {
race(
{
val job1 = fork {
fork {
delay(2.second)
results.add("job3")
}
delay(1.second)
results.add("job1")
val actual: Int | String = race(
{
val job1 = fork {
fork {
delay(2.second)
results.add("job3")
}
42
}, {
delay(500.millis)
throw new RuntimeException("Error")
delay(1.second)
results.add("job1")
}
)
}
42
}, {
delay(500.millis)
throw new RuntimeException("Error")
}
)

actual should be(42)
results.toArray should contain theSameElementsInOrderAs List("job1", "job3")
}

it should "throw the exception thrown by the first function both throw an exception" in {
val expectedResult = Try {
structured {
race(
{
delay(1.second)
throw new RuntimeException("Error in job1")
}, {
delay(500.millis)
throw new RuntimeException("Error in job2")
}
)
}
race(
{
delay(1.second)
throw new RuntimeException("Error in job1")
}, {
delay(500.millis)
throw new RuntimeException("Error in job2")
}
)
}

expectedResult.failure.exception shouldBe a[RuntimeException]
expectedResult.failure.exception.getMessage shouldBe "Error in job2"
}

it should "honor the structural concurrency and return the value of the second function if the first threw an exception" in {
val actual: Int | String = structured {
race(
{
val job1 = fork {
delay(500.millis)
println("job1")
throw new RuntimeException("Error in job1")
}
42
}, {
delay(1.second)
println("job2")
"42"
val actual: Int | String = race(
{
val job1 = fork {
delay(500.millis)
println("job1")
throw new RuntimeException("Error in job1")
}
)
}
42
}, {
delay(1.second)
println("job2")
"42"
}
)

actual should be("42")
}

it should "honor the structured concurrency and cancel all the children jobs" in {
val results = new ConcurrentLinkedQueue[String]()
val actual: Int | String = structured {
race(
{
val job1 = fork {
delay(1.seconds)
results.add("job1")
}
42
}, {
delay(500.millis)
results.add("job2")
"42"
val actual: Int | String = race(
{
val job1 = fork {
delay(1.seconds)
results.add("job1")
}
)
}
42
}, {
delay(500.millis)
results.add("job2")
"42"
}
)

Thread.sleep(2000)

Expand Down

0 comments on commit 30c7b2a

Please sign in to comment.