jamesward/easyracer

A series of obstacle courses as a way to compare how different languages and frameworks handle structured concurrency

Shell

279

3,254 commits

updated Sep 22, 2026

See the code

README

Easy Racer

A series of obstacle courses as a way to compare how different languages and frameworks handle structured concurrency, including:

  • loser cancellation
  • resource management
  • efficient thread utilization (i.e. reactive, non-blocking)
  • explicit timeouts
  • errors causing a race loss

A scenario server validates the implementations of 11 scenarios:

  1. Race 2 concurrent requests

    GET /1
    

    The winner returns a 200 response with a body containing right

  2. Race 2 concurrent requests, where one produces a connection error

    GET /2
    

    The winner returns a 200 response with a body containing right

  3. Race 10,000 concurrent requests

    GET /3
    

    The winner returns a 200 response with a body containing right

  4. Race 2 concurrent requests but 1 of them should have a 1 second timeout

    GET /4
    

    The winner returns a 200 response with a body containing right

  5. Race 2 concurrent requests where a non-200 response is a loser

    GET /5
    

    The winner returns a 200 response with a body containing right

  6. Race 3 concurrent requests where a non-200 response is a loser

    GET /6
    

    The winner returns a 200 response with a body containing right

  7. Start a request, wait at least 3 seconds then start a second request (hedging)

    GET /7
    

    The winner returns a 200 response with a body containing right

  8. Race 2 concurrent requests that "use" a resource which is obtained and released through other requests. The "use" request can return a non-20x request, in which case it is not a winner.

    GET /8?open
    GET /8?use=<id obtained from open request>
    GET /8?close=<id obtained from open request>
    

    The winner returns a 200 response with a body containing right

  9. Make 10 concurrent requests where 5 return a 200 response with a letter

    GET /9
    

    When assembled in order of when they responded, form the "right" answer

  10. This scenario validates that a computationally heavy task can be run in parallel to another task, and then cancelled.

    Part 1) Make a request and while the connection is open, perform something computationally heavy (e.g. repeated SHA calculation), then cancel the task when the connection closes

    GET /10?{some_id}
    

    Part 2) In parallel to Part 1, every 1 second, make a request with the current process load (0 to 1)

    GET /10?{same_id_as_part_1}={load}
    

    The request in Part 2 will respond with a 20x response if it looks like Part 1 was done correctly (in which case you can stop sending load values), otherwise it will respond with a 30x response if you should continue sending values, or with a 40x response if something has gone wrong.

  11. This scenario validates that a race where all racers fail, is handled correctly. Race a request with another race of 2 requests.

    GET /11
    

    The winner returns a 200 response with a body containing right

The scenario server has a public container ghcr.io/jamesward/easyracer and if you contribute your client to this repo, use Testcontainers and include automated integration tests.

For local dev you can spin up the server via Docker:

docker run -it -p8080:8080 ghcr.io/jamesward/easyracer --debug

Clients

SourceScenario CoverageAuthor(s)Notes
Scala 3 + ZIO11/11 testsJames Ward
Scala 3 + Ox11/11 testsAdam Warski
Scala 3 + Kyo11/11 testsJames Ward
Scala 3 + Gears11/11 testsJack Leow
Scala 3 + Pekko11/11 testsJack Leow
Scala + Cats Effects 311/11 testsPaul Snively Kamil Kloch
Scala 3 + Soundness11/11 testsJack Leow
Kotlin + Coroutines11/11 testsJack Leow
Kotlin + Flow11/11 testsJack Leow
Kotlin + Splitties11/11 testsJames Ward
Kotlin + Arrow11/11 testsJames Ward
Java + Loom11/11 testsJames Ward
Java + Jox11/11 testsJames Ward
Java + CompletableFuture11/11 testsJuan Antonio Breña Moral
Java + Reactor11/11 testsJames Ward
Java + RxJava11/11 testsJuan Antonio Breña Moral
Clojure + core.async11/11 testsJuan Antonio Breña Moral
Elixir + Task (async)11/11 testsJuan Antonio Breña Moral
JRuby + concurrent11/11 testsJuan Antonio Breña Moral
Rust + Tokio10/11 testsJames Ward and Rust Developer Retreat ParticipantsNeeds Scenario 11
C#9/11 testsJason De LormeScenario 10: CPU Not Pegged, Needs 11
F# + Reactive Extensions11/11 testsJack Leow
OCaml + Lwt + Cohttp10/11 testsPuneeth ChagantiNeeds Scenario 11
OCaml + Eio + Cohttp10/11 testsPuneeth ChagantiNeeds Scenario 11
Python + AIOHTTP + TaskGroup9/11 testsJames Ward Bruce EckelNeeds Scenario 10, 11
Python + RxPy11/11 testsJack Leow
Go11/11 testsJack Leow
Go conc11/11 testsJack Leow
Swift + Grand Central Dispatch11/11 testsJack Leow
Swift + async/await11/11 testsJack Leow
Swift + Combine11/11 testsJack Leow
Elm11/11 testsJack Leow
JavaScript9/11 testsJames WardNeeds Scenario 10, 11
TypeScript + Effect11/11 testsJames Ward Michael Arnaldi Sebastian Lorenz
Python + HTTPX + Trio8/11 testsJames WardNeeds Scenarios 3, 10, 11
Python + AIOHTTP6/11 testsJames WardNeeds Scenarios 3, 4, 8, 10, 11
Python + HTTPX + asyncio2/11 testsJames WardNeeds Scenarios 3-11
Haskell + async11/11 testsBenjamin Maurer

Contributors

jamesward

1,368 commits

dependabot[bot]

1,007 commits

jackgene

457 commits

scala-steward

278 commits

jamesward/easyracer

A series of obstacle courses as a way to compare how different languages and frameworks handle structured concurrency

Shell

279

3,254 commits

updated Sep 22, 2026

See the code

README

Easy Racer

A series of obstacle courses as a way to compare how different languages and frameworks handle structured concurrency, including:

  • loser cancellation
  • resource management
  • efficient thread utilization (i.e. reactive, non-blocking)
  • explicit timeouts
  • errors causing a race loss

A scenario server validates the implementations of 11 scenarios:

  1. Race 2 concurrent requests

    GET /1
    

    The winner returns a 200 response with a body containing right

  2. Race 2 concurrent requests, where one produces a connection error

    GET /2
    

    The winner returns a 200 response with a body containing right

  3. Race 10,000 concurrent requests

    GET /3
    

    The winner returns a 200 response with a body containing right

  4. Race 2 concurrent requests but 1 of them should have a 1 second timeout

    GET /4
    

    The winner returns a 200 response with a body containing right

  5. Race 2 concurrent requests where a non-200 response is a loser

    GET /5
    

    The winner returns a 200 response with a body containing right

  6. Race 3 concurrent requests where a non-200 response is a loser

    GET /6
    

    The winner returns a 200 response with a body containing right

  7. Start a request, wait at least 3 seconds then start a second request (hedging)

    GET /7
    

    The winner returns a 200 response with a body containing right

  8. Race 2 concurrent requests that "use" a resource which is obtained and released through other requests. The "use" request can return a non-20x request, in which case it is not a winner.

    GET /8?open
    GET /8?use=<id obtained from open request>
    GET /8?close=<id obtained from open request>
    

    The winner returns a 200 response with a body containing right

  9. Make 10 concurrent requests where 5 return a 200 response with a letter

    GET /9
    

    When assembled in order of when they responded, form the "right" answer

  10. This scenario validates that a computationally heavy task can be run in parallel to another task, and then cancelled.

    Part 1) Make a request and while the connection is open, perform something computationally heavy (e.g. repeated SHA calculation), then cancel the task when the connection closes

    GET /10?{some_id}
    

    Part 2) In parallel to Part 1, every 1 second, make a request with the current process load (0 to 1)

    GET /10?{same_id_as_part_1}={load}
    

    The request in Part 2 will respond with a 20x response if it looks like Part 1 was done correctly (in which case you can stop sending load values), otherwise it will respond with a 30x response if you should continue sending values, or with a 40x response if something has gone wrong.

  11. This scenario validates that a race where all racers fail, is handled correctly. Race a request with another race of 2 requests.

    GET /11
    

    The winner returns a 200 response with a body containing right

The scenario server has a public container ghcr.io/jamesward/easyracer and if you contribute your client to this repo, use Testcontainers and include automated integration tests.

For local dev you can spin up the server via Docker:

docker run -it -p8080:8080 ghcr.io/jamesward/easyracer --debug

Clients

SourceScenario CoverageAuthor(s)Notes
Scala 3 + ZIO11/11 testsJames Ward
Scala 3 + Ox11/11 testsAdam Warski
Scala 3 + Kyo11/11 testsJames Ward
Scala 3 + Gears11/11 testsJack Leow
Scala 3 + Pekko11/11 testsJack Leow
Scala + Cats Effects 311/11 testsPaul Snively Kamil Kloch
Scala 3 + Soundness11/11 testsJack Leow
Kotlin + Coroutines11/11 testsJack Leow
Kotlin + Flow11/11 testsJack Leow
Kotlin + Splitties11/11 testsJames Ward
Kotlin + Arrow11/11 testsJames Ward
Java + Loom11/11 testsJames Ward
Java + Jox11/11 testsJames Ward
Java + CompletableFuture11/11 testsJuan Antonio Breña Moral
Java + Reactor11/11 testsJames Ward
Java + RxJava11/11 testsJuan Antonio Breña Moral
Clojure + core.async11/11 testsJuan Antonio Breña Moral
Elixir + Task (async)11/11 testsJuan Antonio Breña Moral
JRuby + concurrent11/11 testsJuan Antonio Breña Moral
Rust + Tokio10/11 testsJames Ward and Rust Developer Retreat ParticipantsNeeds Scenario 11
C#9/11 testsJason De LormeScenario 10: CPU Not Pegged, Needs 11
F# + Reactive Extensions11/11 testsJack Leow
OCaml + Lwt + Cohttp10/11 testsPuneeth ChagantiNeeds Scenario 11
OCaml + Eio + Cohttp10/11 testsPuneeth ChagantiNeeds Scenario 11
Python + AIOHTTP + TaskGroup9/11 testsJames Ward Bruce EckelNeeds Scenario 10, 11
Python + RxPy11/11 testsJack Leow
Go11/11 testsJack Leow
Go conc11/11 testsJack Leow
Swift + Grand Central Dispatch11/11 testsJack Leow
Swift + async/await11/11 testsJack Leow
Swift + Combine11/11 testsJack Leow
Elm11/11 testsJack Leow
JavaScript9/11 testsJames WardNeeds Scenario 10, 11
TypeScript + Effect11/11 testsJames Ward Michael Arnaldi Sebastian Lorenz
Python + HTTPX + Trio8/11 testsJames WardNeeds Scenarios 3, 10, 11
Python + AIOHTTP6/11 testsJames WardNeeds Scenarios 3, 4, 8, 10, 11
Python + HTTPX + asyncio2/11 testsJames WardNeeds Scenarios 3-11
Haskell + async11/11 testsBenjamin Maurer

Contributors

jamesward

1,368 commits

dependabot[bot]

1,007 commits

jackgene

457 commits

scala-steward

278 commits

Languages

Shell

26.2%

Batchfile

25.6%

Scala

7.7%

Java

6.8%

Swift

6.5%

Python

4.0%

Elm

3.0%

Elixir

3.0%

Kotlin

2.7%

OCaml

2.2%

Go

1.9%

Ruby

1.7%

Clojure

1.5%

C#

1.4%

JavaScript

1.4%

TypeScript

1.2%

Rust

1.1%