# How TrioProgress Integrates with Holehe's Task Lifecycle: Deep Dive into Async Instrumentation

> Discover how TrioProgress integrates with Holehe's task lifecycle using async instrumentation. Gain real-time progress visibility without blocking the event loop.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: deep-dive
- Published: 2026-08-29

---

**TrioProgress hooks into Trio's low-level instrumentation API to track every spawned and exited task during Holehe's concurrent email-checking operations, providing real-time progress visibility without blocking the async event loop.**

Holehe leverages **Trio**, a structured concurrency library for Python, to execute multiple OSINT module checks simultaneously. To monitor these concurrent operations, the tool implements a custom `trio.abc.Instrument` subclass called `TrioProgress` that intercepts task lifecycle events during the entire execution flow.

## The Integration Architecture

Holehe's integration of TrioProgress follows a precise lifecycle: registration, execution monitoring, and cleanup. This pattern ensures that every async task spawned to check an email against a service is counted and reported without interfering with the actual workload.

### Instrument Initialization and Registration

When `maincore()` begins execution in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), it instantiates `TrioProgress` with the user's verbosity preferences. The instrument is immediately registered with Trio's low-level system using `trio.lowlevel.add_instrument()`.

```python

# holehe/core.py

instrument = TrioProgress(verbose=verbose, debug=debug)
trio.lowlevel.add_instrument(instrument)

```

This registration occurs before any async work begins, ensuring the instrument captures the complete task lifecycle. The `TrioProgress` constructor initializes counters (`started` and `finished`) and optionally prints an initialization message when verbose mode is enabled.

### Task Spawning in the Nursery

Holehe uses Trio's **nursery** pattern to manage concurrent execution. Inside the `runner()` async function, the code opens a nursery and spawns each module check as a separate task:

```python

# holehe/core.py

async def runner():
    async with trio.open_nursery() as nursery:
        for module in modules:
            nursery.start_soon(module.run, email, verbose, debug, sleep, output, mode, json_out)

```

Each call to `nursery.start_soon()` triggers Trio's internal task spawning mechanism, which automatically invokes the `task_spawned()` callback on any registered instrument, including our `TrioProgress` instance.

### Lifecycle Callbacks and Progress Tracking

The `TrioProgress` class in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) implements three critical methods from `trio.abc.Instrument`:

- **`task_spawned(task)`**: Increments the `started` counter and optionally logs debug output showing the task object representation
- **`task_exited(task)`**: Increments the `finished` counter when a task completes, whether successfully or with an exception
- **`after_run()`**: Prints a final completion summary showing the ratio of finished to started tasks

```python

# holehe/instruments.py

class TrioProgress(trio.abc.Instrument):
    def task_spawned(self, task):
        self.started += 1
        if self.debug:
            print(f"[TrioProgress] Task spawned: {task!r}")
    
    def task_exited(self, task):
        self.finished += 1
        if self.debug:
            print(f"[TrioProgress] Task exited: {task!r}")
    
    def after_run(self):
        if self.verbose:
            print(f"[TrioProgress] Completed {self.finished}/{self.started} tasks")

```

These callbacks execute synchronously within Trio's event loop, allowing accurate counting without introducing async overhead or blocking delays.

### Cleanup and Instrument Removal

After `trio.run(runner)` completes and the nursery closes, `maincore()` explicitly removes the instrument from Trio's system:

```python

# holehe/core.py

trio.run(runner)
trio.lowlevel.remove_instrument(instrument)

```

This cleanup step is essential to prevent memory leaks and ensure that subsequent calls to `maincore()` (if any) start with fresh instrumentation rather than accumulating stale callbacks.

## Code Implementation Details

### The TrioProgress Class

Located in [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py), the `TrioProgress` class inherits from `trio.abc.Instrument` and tracks state across the entire run:

```python

# holehe/instruments.py

import trio

class TrioProgress(trio.abc.Instrument):
    def __init__(self, verbose=False, debug=False):
        self.verbose = verbose
        self.debug = debug
        self.started = 0
        self.finished = 0
        if self.verbose:
            print("[TrioProgress] Initialized")

```

The class maintains simple integer counters that are incremented thread-safely by Trio's internal machinery, making it suitable for high-concurrency scenarios where dozens of modules may run simultaneously.

### Core Orchestration Logic

The `maincore()` function serves as the synchronous entry point that bridges the CLI interface with the async Trio world. It handles the full instrument lifecycle:

```python

# holehe/core.py

def maincore(email, verbose, debug, sleep, output, mode, json_out, modules):
    instrument = TrioProgress(verbose=verbose, debug=debug)
    trio.lowlevel.add_instrument(instrument)
    
    async def runner():
        async with trio.open_nursery() as nursery:
            for module in modules:
                nursery.start_soon(module.run, email, verbose, debug, sleep, output, mode, json_out)
    
    trio.run(runner)
    trio.lowlevel.remove_instrument(instrument)

```

This design separates the synchronous setup/teardown from the async execution logic, keeping the instrumentation plumbing isolated from the actual OSINT work performed by individual modules.

## Practical Usage Example

To see TrioProgress in action during a Holehe scan, enable verbose output:

```bash
holehe user@example.com --verbose

```

With verbose mode enabled, you'll see output similar to:

```

[TrioProgress] Initialized
[TrioProgress] Completed 15/15 tasks

```

For debugging concurrent behavior, use the debug flag to see individual task spawn/exit events:

```bash
holehe user@example.com --debug

```

This outputs detailed task objects as they enter and exit the nursery:

```

[TrioProgress] Task spawned: <Task '__main__.runner.<locals>._run_wrapper' at 0x...>
[TrioProgress] Task exited: <Task '__main__.runner.<locals>._run_wrapper' at 0x...>

```

## Summary

- **TrioProgress** is a custom `trio.abc.Instrument` implementation that tracks task lifecycle events in Holehe's async execution engine.
- The instrument is registered via `trio.lowlevel.add_instrument()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) before the nursery opens and removed via `trio.lowlevel.remove_instrument()` after completion.
- It counts tasks via `task_spawned()` and `task_spawned()` callbacks, reporting progress through `after_run()` when verbose mode is active.
- This integration allows Holehe to monitor dozens of concurrent email verification checks without modifying the individual module code or blocking the event loop.

## Frequently Asked Questions

### What is the purpose of TrioProgress in Holehe?

**TrioProgress provides transparent monitoring of concurrent task execution.** It allows users to see how many OSINT checks have started and finished when running Holehe against an email address, giving visibility into the progress of potentially dozens of simultaneous network requests without interfering with the actual verification logic.

### How does TrioProgress differ from a standard progress bar library?

**Unlike traditional progress bars that require manual updates within business logic, TrioProgress leverages Trio's instrumentation API to intercept task events automatically.** Standard libraries like `tqdm` require explicit `update()` calls in user code, whereas `TrioProgress` receives callbacks directly from the Trio runtime whenever any task spawns or exits, making it decoupled from the individual module implementations in `holehe/modules/`.

### Can TrioProgress be used outside of Holehe?

**Yes, the pattern is reusable for any Trio-based application.** The `TrioProgress` class implements the standard `trio.abc.Instrument` interface, meaning you can instantiate it and add it to any Trio application using `trio.lowlevel.add_instrument()` to track task metrics. However, the specific output formatting and counter logic in Holehe's implementation are tailored for tracking module execution counts.

### Why does Holehe remove the instrument after `trio.run()` completes?

**Removal prevents state leakage and callback accumulation.** According to the implementation in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), calling `trio.lowlevel.remove_instrument(instrument)` ensures that the instrument's references are released and its callbacks are unregistered. This is critical for long-running processes or repeated invocations where stale instruments could cause memory leaks or double-counting in subsequent runs.