How Holehe Modules Are Launched Concurrently: Trio Async Architecture Explained

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, 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) 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) 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):

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). Holehe uses Trio rather than asyncio for structured concurrency.

Opening the Nursery

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) wraps each service call:

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

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:

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

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 →