# 12 · Add Bounded Parallelism

English narration · HHY 1.7.0

Check several local endpoints with two workers and keep failures in the report.

## 00:00:00.000 — Three checks, one report

We already know how to call one endpoint. Now we will check three endpoints and produce one report, including a deliberate failure. A sequential loop would wait for each request before starting the next. Parallel lets several independent operations make progress at once, while an explicit worker limit keeps the workload bounded. We will use two workers, keep each result associated with its URL, and treat one failed request as a report entry instead of losing the entire batch.

```
users/health   true
missing        false
orders/health  true
```

## 00:00:35.130 — Describe the work as values

The input is an ordinary list of three local URLs. Start the included fixture exactly as in the previous lesson. These small values describe the work without opening any connection yet. That separation is useful: you can inspect the batch before sending it, add another URL, or remove a route without changing the worker logic. A more realistic service record could also contain a display name, but the URL alone is enough for this first reproducible example.

```
let urls = [
    "http://127.0.0.1:9311/users/health",
    "http://127.0.0.1:9311/missing",
    "http://127.0.0.1:9311/orders/health"
]
```

## 00:01:10.650 — Set the concurrency limit

Convert the list to a Stream, then pass it through parallel with a limit of two. Each worker receives one URL and returns one result. The documentation describes isolated workers and bounded buffers, so the concurrency setting is a resource decision, not a promise of a particular speedup. A larger number can increase pressure on your machine or the server. Begin with a modest limit and choose it according to the work and the service you are allowed to call.

```
urls |> stream |> parallel(2) { url ->
    # one independent request per input
}
```

## 00:01:43.800 — Turn expected failure into a value

Inside the worker, attempt wraps the request and JSON decoding. It returns a result whose okay field tells us whether that operation succeeded. We then return a plain object containing the original URL and this Boolean. This placement matters. The recovery boundary belongs around each independent operation, so the missing route becomes one false value. If we let that error escape instead, the documented fail fast behavior would cancel the remaining work rather than produce a complete service report.

```
let result = attempt {
    http.get(url) |> timeout(2s) |> send
        |> response_body |> parse_json
}
return { url: url, ok: result.ok }
```

## 00:02:21.710 — Collect in input order

After the worker block, collect consumes the finite batch. JSON encoding produces a report that other programs can read. Parallel preserves input order even if requests finish in a different order. That makes the output predictable, but it also means a slow early request can delay seeing later results. Do not interpret the order of report rows as the order of network completion. Each row still includes its URL, so it remains meaningful even if you later sort or filter the report.

```
} |> collect |> encode_json |> print
```

## 00:02:58.570 — Avoid a shared counter

A common mistake is trying to accumulate a shared mutable total inside the workers. Run the supplied counter demonstration and inspect its result. On the validated local build, it returns one and two, rather than a running shared total. This demonstrates isolated behavior on the validated build, rather than a shared counter. Capture diagnostics can differ across engines and versions, so we will not rely on this mistake as an API contract. Return a value from each worker, then aggregate those returned values in the parent pipeline where the ownership is clear.

```
let mut total = 0
[1, 2] |> stream |> parallel(2) { item ->
    total = total + item
    return total
} |> collect |> print
```

## 00:03:40.680 — Run and inspect the failure

Run the health checker while the fixture is available. The first and third routes succeed, and the intentionally missing route is false. Now stop the fixture and run it again. Requests should fail within their configured limits, and the report should still contain entries because attempt handles each request failure. This is a useful distinction between an operational problem and a broken script: the script can complete its reporting job while honestly recording that the services could not be reached.

```
hhy run health-check.hhy
```

## 00:04:15.640 — Practice a richer report

Extend each input from a URL to an object with a service name and URL. Update the worker to request the URL field and retain the name in its returned report. Then compare worker limits of one and two using the same input. Focus first on identical results rather than timing claims, because this tiny fixture is not a performance benchmark. You now have a repeatable concurrency pattern: finite input, bounded workers, local recovery, returned values, and one terminal collection.

```
Input → parallel(2) → result objects → collect
```

