# How Holehe Achieves Concurrent Execution of Module Checks

> Discover how Holehe achieves concurrent execution of module checks using Trio's async framework and a shared httpx.AsyncClient for efficient, non-blocking I/O. Learn more!

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-29

---

**Holehe achieves concurrent execution of module checks by leveraging the Trio asynchronous framework to spawn lightweight tasks within a nursery, sharing a single `httpx.AsyncClient` instance across all modules for non-blocking HTTP I/O.**

Holehe is an open-source email reconnaissance tool maintained by megadose that verifies account existence across hundreds of websites. To minimize execution time when performing these checks, the tool implements **concurrent execution of module checks** using Python's Trio library rather than traditional threading or multiprocessing. This design enables dozens of network requests to run simultaneously within a single asynchronous event loop, dramatically reducing total scan duration compared to sequential execution.

## Dynamic Module Discovery

Before any network requests occur, Holehe dynamically discovers available check modules. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the `import_submodules` function (lines 37-47) walks the `holehe.modules` package tree and imports every submodule containing platform-specific checks.

Once loaded, the `get_functions` function (lines 50-63) extracts the actual coroutine objects from these modules. These functions are standard Python async functions that accept an email address, an HTTP client, and an output list as parameters.

## Shared Async HTTP Client Setup

To maximize efficiency and avoid connection pool exhaustion, Holehe creates a single `httpx.AsyncClient` instance in `maincore` ([`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 13-14). This client is configured with a timeout parameter and shared across all concurrent checks.

Using a shared client allows connection reuse and ensures that all modules perform non-blocking HTTP requests through the same underlying transport. This is critical for achieving true concurrency without blocking the event loop or spawning excessive OS threads.

## Concurrent Execution Using Trio Nurseries

The core concurrency mechanism resides in the `maincore` async function ([`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 66-71). Holehe utilizes Trio's structured concurrency model through a nursery pattern:

```python
async with trio.open_nursery() as nursery:
    for website in websites:
        nursery.start_soon(launch_module, website, email, client, out)

```

Each discovered website function becomes a lightweight async task spawned via `nursery.start_soon()`. The `launch_module` wrapper function handles the actual invocation. Because Trio schedules all tasks cooperatively in a single thread, the program can manage hundreds of pending network operations without the overhead of thread context switching.

The `launch_module` function ([`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 66-78) executes the module-specific coroutine:

```python
async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)
    except Exception:
        out.append({
            "name": module.__name__,
            "domain": "unknown",
            "rateLimit": False,
            "error": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

## Error Isolation and Progress Tracking

Holehe ensures that a single failing module does not abort the entire reconnaissance operation. The `launch_module` function wraps each module call in a try/except block ([`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 66-78), capturing exceptions and recording failure states to the output list without propagating errors to the nursery.

For user feedback during execution, Holehe implements a custom Trio instrument. The `TrioProgress` class in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) (lines 4-10) hooks into Trio's task lifecycle:

```python
class TrioProgress(trio.abc.Instrument):
    def __init__(self, total):
        self.tqdm = tqdm(total=total)

    def task_exited(self, task):
        if task.name.split(".")[-1] == "launch_module":
            self.tqdm.update(1)

```

This instrument is registered in `maincore` via `trio.lowlevel.add_instrument()` before the nursery opens and removed after completion. It monitors task exits and increments a `tqdm` progress bar each time a `launch_module` task finishes, providing real-time visibility into completion rates across the concurrent workload.

## Summary

Holehe's concurrent architecture delivers high-performance email reconnaissance through several key design choices:

- **Dynamic loading** via `import_submodules` and `get_functions` enables automatic discovery of check modules without hardcoded lists.
- **Shared state** using a single `httpx.AsyncClient` reduces connection overhead and enables non-blocking I/O across all checks.
- **Structured concurrency** through Trio's nurseries ensures that all spawned tasks complete before the program exits, preventing resource leaks.
- **Fault isolation** via try/except wrappers in `launch_module` guarantees that individual module failures do not crash the entire scan.
- **Real-time progress** tracking through `TrioProgress` provides accurate completion statistics without polling or shared mutable counters.

## Frequently Asked Questions

### Why does Holehe use Trio instead of Python's standard asyncio library?

Holehe uses Trio because it enforces structured concurrency through nurseries, which makes it impossible to spawn "fire-and-forget" tasks that could outlive their parent context. Unlike asyncio, Trio requires that all tasks be properly nested within a nursery block, ensuring that `maincore` cannot exit until every `launch_module` task has completed or been cancelled. This prevents resource leaks and makes error handling more predictable when running hundreds of concurrent network checks.

### How does Holehe prevent one failing module from crashing the entire scan?

The `launch_module` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 66-78) wraps each module invocation in a comprehensive try/except block. When a module raises an exception—whether from a network timeout, parsing error, or unexpected API response—the wrapper catches the error, records a failure entry to the output list, and returns normally. Because the error never propagates to the nursery level, Trio continues scheduling remaining tasks uninterrupted.

### How does the progress bar accurately track completion during concurrent execution?

Holehe registers a custom `TrioProgress` instrument with Trio's low-level instrumentation API. This class implements the `task_exited` hook, which Trio calls whenever any task terminates. The instrument checks if the exiting task's name ends with `launch_module` and increments the `tqdm` progress bar accordingly. Since Trio invokes this callback synchronously during task cleanup, the progress count remains accurate even when multiple modules complete simultaneously.

### Do module developers need to write special code to support concurrent execution?

No. Module developers implement standard async functions that accept `(email, client, out)` parameters and use the provided `httpx.AsyncClient` for HTTP requests. The concurrency management happens entirely in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) through the Trio nursery and `launch_module` wrapper. As long as modules use the shared `client` parameter rather than creating their own blocking HTTP clients, they automatically benefit from Holehe's concurrent execution model without additional boilerplate.