How Holehe Achieves Concurrent Execution of Module Checks

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, 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, 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, lines 66-71). Holehe utilizes Trio's structured concurrency model through a nursery pattern:

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, lines 66-78) executes the module-specific coroutine:

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, 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 (lines 4-10) hooks into Trio's task lifecycle:

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 (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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →