How Trio Tasks Are Launched Concurrently in Holehe

Holehe executes hundreds of website-checking modules simultaneously by opening a Trio nursery in maincore() and scheduling each module via nursery.start_soon(), wrapping individual HTTP requests in launch_module() to ensure deterministic concurrency and isolated error handling.

Holehe is a Python-based open-source intelligence (OSINT) tool designed to verify email addresses across hundreds of online services. To maximize throughput without blocking the event loop, the project adopts the Trio library for structured concurrency. Analyzing how Trio tasks are launched concurrently within the megadose/holehe repository reveals a robust architectural pattern using nurseries, thin task wrappers, and low-level instrumentation.

Entry Point and Async Runtime Initialization

The concurrent execution begins in holehe/core.py, where the synchronous main() function bootstraps the Trio event loop.


# holehe/core.py

import trio

def main():
    # ... argument parsing ...

    trio.run(maincore, args)

Calling trio.run(maincore) creates a single-threaded async environment and executes the maincore() coroutine until completion. This entry point ensures that all subsequent async operations—including HTTP requests and task scheduling—run within Trio's deterministic concurrency model.

Structured Concurrency with Trio Nurseries

Inside maincore(), Holehe implements structured concurrency by opening a nursery, which acts as a parent container for all concurrent tasks.

Creating the Task Nursery

After parsing command-line arguments and discovering available modules via get_functions(), maincore() instantiates an httpx.AsyncClient and prepares the nursery context:


# holehe/core.py

async def maincore(args):
    modules = import_submodules("holehe.modules")
    websites = get_functions(modules, args)
    client = httpx.AsyncClient()
    
    async with trio.open_nursery() as nursery:
        # Task scheduling occurs here

The async with trio.open_nursery() statement creates a scope where tasks can be launched concurrently. The nursery guarantees that execution proceeds only after every scheduled task has completed or failed, providing deterministic cleanup.

Scheduling Concurrent Checks

Holehe iterates over the collected website modules and schedules each one using nursery.start_soon(). This method accepts a coroutine function and its arguments, creating a new child task that runs concurrently with all others:


# holehe/core.py

out = []  # Shared result list

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

By scheduling launch_module rather than the raw module functions directly, Holehe inserts a lightweight wrapper that standardizes error handling and result collection across all services.

Task Wrapping and Error Isolation

The launch_module() function in holehe/core.py serves as a thin adapter between the nursery and the individual website modules located in holehe/modules/. This isolation prevents a single failing service from crashing the entire concurrent batch.


# holehe/core.py

async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)
    except Exception:
        name = str(module).split('<function ')[1].split(' ')[0]
        out.append({
            "name": name,
            "domain": module.__name__,
            "rateLimit": False,
            "error": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

Each launch_module task runs independently, appending its result dictionary to the shared out list upon completion. Because Trio nurseries propagate exceptions only after all tasks finish, the explicit try/except block ensures that individual module failures are captured locally while the remaining tasks continue execution.

Real-Time Progress Monitoring with Custom Instruments

To provide feedback during concurrent execution, Holehe leverages Trio's low-level instrumentation API. Before opening the nursery, maincore() registers a custom TrioProgress instrument:


# holehe/core.py

from holehe.instruments import TrioProgress

instrument = TrioProgress(len(websites))
trio.lowlevel.add_instrument(instrument)

async with trio.open_nursery() as nursery:
    # ... task scheduling ...

# After nursery exits:

trio.lowlevel.remove_instrument(instrument)

The TrioProgress class, defined in holehe/instruments.py, hooks into Trio's task lifecycle events to track how many modules have completed. This allows the tool to render a real-time progress bar without blocking the event loop or requiring explicit callbacks within each module.

Summary

  • Nursery-based concurrency: Holehe launches Trio tasks concurrently via nursery.start_soon() within an async with trio.open_nursery() block in holehe/core.py, ensuring all modules run simultaneously.
  • Deterministic cleanup: The nursery scope guarantees that the program proceeds to result sorting and CSV export only after every scheduled task has finished.
  • Isolated error handling: The launch_module() wrapper catches exceptions individually, preventing one failed HTTP request from terminating the entire batch.
  • Instrumentation: Custom TrioProgress instruments are added via trio.lowlevel.add_instrument() to monitor task completion across the concurrent workload.

Frequently Asked Questions

How does Holehe ensure all Trio tasks complete before exiting?

The async with trio.open_nursery() context manager in holehe/core.py acts as a synchronization barrier. Execution blocks at the end of this block until every task scheduled via nursery.start_soon() has finished, allowing safe cleanup of the httpx.AsyncClient and removal of the TrioProgress instrument.

What happens if one module crashes during concurrent execution?

The launch_module() wrapper in holehe/core.py catches exceptions individually within a try/except block. Instead of propagating the error to the nursery—which would mark the task as failed but not stop others—it appends an error state to the shared results list, ensuring that checks against other websites continue uninterrupted.

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

According to the megadose/holehe source code, Trio provides structured concurrency primitives—specifically nurseries and cancellation scopes—that make it easier to reason about concurrent task lifecycles. The nursery pattern ensures that no child task outlives its parent scope, preventing resource leaks common in traditional asyncio patterns.

Where is the concurrent task logic implemented?

The orchestration logic resides in holehe/core.py within the maincore() function, which opens the nursery and schedules tasks. The individual website-checking implementations are located in holehe/modules/ as separate async functions, each adhering to the signature async def module_name(email, client, out).

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 →