# How Holehe Modules Are Launched Concurrently: Trio Async Architecture Explained

> Discover how Holehe modules launch concurrently using Trio's nursery feature for parallel website probes. Learn about the Trio async architecture and optimize your service checks.

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

---

**Holehe runs service-checking modules in parallel using Trio's nursery feature to spawn concurrent async tasks for each website probe.**

Holehe is an OSINT tool that checks email and username availability across hundreds of platforms. The key to its speed lies in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), where the tool discovers every module in `holehe.modules` and executes them simultaneously rather than sequentially.

## Dynamic Module Discovery

Holehe does not hardcode which services to probe. Instead, it dynamically discovers modules at runtime.

### Walking the Package Tree

The `import_submodules` function (lines 37-47 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)) imports every submodule under `holehe.modules`:

- It uses `pkgutil.walk_packages` to traverse the package tree
- Returns a dictionary mapping fully-qualified names to imported module objects
- Enables adding new services without modifying core orchestration code

### Extracting Service Functions

The `get_functions` function (lines 50-63 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)) transforms imported modules into callable probes:

1. Iterates over the module dictionary from `import_submodules`
2. For each module, extracts the callable matching the final component of its dotted path
3. Builds a list of async functions (`websites`) ready for execution

For example, `holehe.modules.social_media.twitter` yields the `twitter` function.

## Shared Async HTTP Client

Before launching modules, Holehe creates a single `httpx.AsyncClient` (lines 12-14 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)):

```python
client = httpx.AsyncClient(timeout=10)

```

Sharing one client across all modules provides critical advantages:

- **Connection pooling** — Reuses TCP connections to the same hosts
- **Reduced overhead** — Avoids creating hundreds of separate client instances
- **Consistent configuration** — Uniform timeouts and headers across all probes

Each module receives this client instance as its second argument.

## Concurrent Execution with Trio Nurseries

The parallel launch mechanism resides in `maincore` (lines 15-21 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)). Holehe uses **Trio** rather than `asyncio` for structured concurrency.

### Opening the Nursery

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

```

**Key behaviors:**

- `trio.open_nursery()` creates a context manager that tracks all spawned tasks
- `nursery.start_soon` schedules `launch_module` to run immediately
- The nursery exits only after **every** scheduled task completes
- No explicit `await` needed — Trio handles synchronization automatically

### Individual Module Launching

The `launch_module` coroutine (lines 66-78 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)) wraps each service call:

```python
async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)
    except Exception:
        out.append({"name": module.__name__, "rateLimit": True, "error": True})

```

This wrapper ensures **fault isolation** — an exception in one probe cannot crash the entire scan.

## Complete Programmatic Example

```python
import httpx
import trio
from holehe.core import import_submodules, get_functions
from holehe.instruments import TrioProgress

async def run_holehe(email: str):
    """Run Holehe concurrently against all discovered services."""
    # Discover and extract module functions

    modules = import_submodules("holehe.modules")
    funcs = get_functions(modules)
    
    # Shared HTTP client with timeout

    client = httpx.AsyncClient(timeout=10)
    results = []
    
    # Optional progress tracking

    instrument = TrioProgress(len(funcs))
    trio.lowlevel.add_instrument(instrument)
    
    # Concurrent execution via nursery

    async with trio.open_nursery() as nursery:
        for fn in funcs:
            nursery.start_soon(fn, email, client, results)
    
    trio.lowlevel.remove_instrument(instrument)
    await client.aclose()
    return results

# Execute with Trio's runner

if __name__ == "__main__":
    email = "target@example.com"
    print(trio.run(run_holehe, email))

```

## Error Handling and Resilience

Holehe's concurrency model prioritizes **completion over perfection**:

| Scenario | Handling |
|----------|----------|
| Module raises exception | Caught in `launch_module`; placeholder result appended with `error=True` |
| Network timeout | Handled by `httpx` timeout; module returns control to nursery |
| Rate limiting detected | Module sets `rateLimit=True` in result dict |
| Partial results acceptable | Main scan continues regardless of individual failures |

This design means a single slow or broken service cannot block the entire investigation.

## Module Interface Contract

Every module in `holehe.modules` must implement this async signature:

```python
async def service_name(email: str, client: httpx.AsyncClient, out: list) -> None:
    # Probe the service

    # Append result dictionary to out list

    # No return value

```

The `out` parameter acts as a shared results collector. Modules append dictionaries containing keys like `name`, `domain`, `exists`, `emailrecovery`, `phoneNumber`, and `others`.

## Summary

- **Discovery**: `import_submodules` and `get_functions` dynamically load service checks from `holehe.modules`
- **Resource sharing**: One `httpx.AsyncClient` serves all concurrent probes
- **Concurrency**: Trio's `open_nursery` with `start_soon` launches all modules simultaneously
- **Resilience**: `launch_module` wrappers catch exceptions to prevent cascade failures
- **Completion**: The nursery ensures all tasks finish before returning results

## Frequently Asked Questions

### Why does Holehe use Trio instead of asyncio?

Trio provides **structured concurrency** through nurseries, making it impossible to accidentally lose track of spawned tasks. The `async with` nursery pattern guarantees all tasks complete or properly propagate exceptions, eliminating a class of concurrency bugs common in raw `asyncio` code. According to the Holehe source, this choice simplifies the `maincore` implementation while maintaining rigorous execution control.

### How many modules can run concurrently without performance degradation?

There's no hardcoded limit in Holehe. Practical throughput depends on the `httpx.AsyncClient` connection pool limits and the target system's file descriptor capacity. The shared client naturally throttles through connection reuse, and Trio's scheduling remains efficient even with hundreds of tasks. For very large scans, users may adjust `httpx` limits or run multiple Holehe invocations with filtered module subsets.

### What happens if a single module hangs indefinitely?

The `httpx.AsyncClient(timeout=10)` setting in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) enforces a 10-second timeout on all HTTP operations. If a module's network request exceeds this, `httpx` raises an exception that `launch_module` catches, recording a failure result and allowing the nursery to continue. The timeout prevents any single slow service from stalling the entire scan.

### Can I run specific modules instead of all discovered services?

The public CLI does not expose fine-grained module selection, but programmatic usage permits this. After calling `get_functions`, filter the returned list before passing to the nursery. For example, keep only functions whose `__name__` matches desired services, or exclude known rate-limited modules. The dynamic discovery system makes custom filtering straightforward without modifying core code.