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_packagesto 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:
- Iterates over the module dictionary from
import_submodules - For each module, extracts the callable matching the final component of its dotted path
- 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 tasksnursery.start_soonscheduleslaunch_moduleto run immediately- The nursery exits only after every scheduled task completes
- No explicit
awaitneeded — 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_submodulesandget_functionsdynamically load service checks fromholehe.modules - Resource sharing: One
httpx.AsyncClientserves all concurrent probes - Concurrency: Trio's
open_nurserywithstart_soonlaunches all modules simultaneously - Resilience:
launch_modulewrappers 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →