How Holehe Uses TrioProgress for Asynchronous Progress Tracking
TrioProgress is a custom Trio instrument in Holehe that monitors coroutine lifecycle events to drive a real-time tqdm progress bar, giving users live visual feedback during email OSINT scans.
Holehe is an asynchronous email reconnaissance tool that checks username and email availability across hundreds of platforms. Because it leverages the Trio async library to run concurrent checks, it needs a way to visualize progress without blocking the event loop. The solution is a custom instrument called TrioProgress, implemented in holehe/instruments.py, that hooks into Trio's task scheduling system to track completion status in real time.
What is TrioProgress?
TrioProgress is a specialized class that inherits from trio.abc.Instrument, allowing it to receive low-level callbacks whenever tasks are spawned or completed. According to the source code in holehe/instruments.py, this instrument bridges Trio's internal task management with the tqdm library to produce a command-line progress bar.
The class maintains three critical state attributes:
_total: The total number of modules (check coroutines) to execute_bar: Atqdm.tqdminstance configured with the module count_running: A counter tracking currently active tasks
How TrioProgress Tracks Async Tasks
The instrument works by implementing two key callback methods that Trio automatically invokes during the nursery lifecycle.
Task Spawn Monitoring
When the main scanning loop starts a new check coroutine, Trio calls task_spawned(). This method increments the internal _running counter and advances the progress bar by one unit:
# holehe/instruments.py
def task_spawned(self, task):
self._running += 1
self._bar.update(1)
This callback fires immediately when nursery.start_soon() is invoked in holehe/core.py, ensuring the progress bar updates the instant a new platform check begins.
Task Exit Handling
When a coroutine completes—whether successfully or with an exception—Trio invokes task_exited(). The instrument decrements the running counter and closes the progress bar when all tasks finish:
# holehe/instruments.py
def task_exited(self, task):
self._running -= 1
if self._running == 0:
self._bar.close()
This ensures the visual indicator accurately reflects the actual state of the async worker pool, preventing the bar from stalling on failed or cancelled checks.
tqdm Integration
The constructor initializes the visual component using tqdm's standard API:
# holehe/instruments.py
def __init__(self, total):
self._total = total
self._bar = tqdm(total=total, desc="Scanning", unit="module")
self._running = 0
By setting unit="module", the progress bar displays "module" as the unit of work, making it clear to users that each increment represents one platform check completing.
Integration in the Core Scanning Loop
In holehe/core.py, the main async runner orchestrates the instrument's lifecycle around the Trio nursery. Before spawning any check coroutines, the code registers the instrument using Trio's low-level API. After all tasks complete, it cleans up by removing the instrument.
# holehe/core.py (conceptual excerpt)
import trio
from .instruments import TrioProgress
async def maincore(module_coroutines):
# Initialize progress tracking with total module count
instrument = TrioProgress(total=len(module_coroutines))
# Register the instrument to receive task callbacks
trio.lowlevel.add_instrument(instrument)
try:
async with trio.open_nursery() as nursery:
for coro in module_coroutines:
nursery.start_soon(coro)
finally:
# Ensure cleanup happens even if errors occur
trio.lowlevel.remove_instrument(instrument)
This pattern ensures the progress bar exists only during active scanning and does not interfere with other Trio operations or subsequent runs.
Practical Usage Examples
Command Line Execution
When running Holehe normally, the TrioProgress bar appears automatically as modules execute:
holehe -u user@example.com
Output shows real-time progress:
Scanning: 23/45 [===============>................] 51%
Custom Script Integration
You can reuse the same progress tracking in custom async scripts that leverage Holehe's checking modules:
import trio
from holehe.instruments import TrioProgress
from holehe.modules import my_custom_check
async def run_custom_scan(email):
# Build list of coroutines
coroutines = [
my_custom_check(email),
# ... additional checks
]
# Setup progress tracking
progress = TrioProgress(total=len(coroutines))
trio.lowlevel.add_instrument(progress)
try:
async with trio.open_nursery() as nursery:
for coro in coroutines:
nursery.start_soon(coro)
finally:
trio.lowlevel.remove_instrument(progress)
if __name__ == "__main__":
trio.run(run_custom_scan, "target@example.com")
Programmatic Progress Monitoring
If you need to extract progress data for a GUI or logging system instead of stdout, subclass TrioProgress to expose the counters:
from holehe.instruments import TrioProgress
class LoggingProgress(TrioProgress):
def task_spawned(self, task):
super().task_spawned(task)
logger.info(f"Started {task.name}, {self._running} active")
def task_exited(self, task):
super().task_exited(task)
logger.info(f"Finished {task.name}, {self._running} remaining")
Summary
- TrioProgress is defined in
holehe/instruments.pyand inherits fromtrio.abc.Instrumentto receive async task callbacks. - It tracks coroutine lifecycle through
task_spawned()andtask_exited()methods, maintaining an internal counter of active checks. - The instrument wraps a
tqdmprogress bar that updates in real-time as each platform check starts, providing "Scanning: X/total" visual feedback. - In
holehe/core.py, the instrument is registered withtrio.lowlevel.add_instrument()before opening the nursery and removed withremove_instrument()after completion. - This architecture separates progress visualization from business logic while leveraging Trio's native instrumentation API for zero-overhead monitoring.
Frequently Asked Questions
How does TrioProgress handle failed or crashing modules?
When a check coroutine raises an exception, Trio's nursery management still invokes task_exited() on the instrument. The method decrements the _running counter regardless of exit status, ensuring the progress bar advances even when modules crash. The exception propagation is handled separately by the nursery's error policy, but the visual indicator remains accurate.
Can I use TrioProgress with asyncio instead of Trio?
No. TrioProgress specifically implements trio.abc.Instrument, which is part of Trio's low-level instrumentation API. This protocol has no equivalent in asyncio's architecture. To use Holehe's progress tracking, you must run the checks within Trio's event loop using trio.run() or trio.lowlevel.start_guest_run().
Why does the progress bar show "module" as the unit instead of "site" or "check"?
The __init__ method in holehe/instruments.py explicitly sets unit="module" when constructing the tqdm instance. This terminology reflects the codebase architecture where each platform checker is implemented as a separate module. You can customize this by subclassing and overriding the constructor, but the default aligns with Holehe's internal naming conventions.
Is the progress bar thread-safe when running multiple scans concurrently?
Yes, because Trio instruments are bound to a specific Trio thread and event loop. Each call to trio.lowlevel.add_instrument() registers the instance only for that particular task tree. If you run multiple scans in parallel using separate Trio low-level calls or guest runs, each maintains its own instrument state, preventing race conditions on the tqdm output.
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 →