Module

orchestration.render

Rendering orchestration for Bengal SSG.

Handles page rendering in both sequential and parallel modes. Supports free-threaded Python for true parallelism and falls back to sequential rendering on standard Python. Integrates with dependency tracking for incremental builds.

Key Concepts:

  • Parallel rendering: ThreadPoolExecutor for concurrent page rendering
  • Free-threaded detection: Automatic detection of GIL-disabled Python
  • Dependency tracking: Template dependency tracking for incremental builds
  • Error handling: Graceful error handling with page-level isolation

Related Modules:

  • bengal.rendering.template_engine: Template rendering implementation
  • bengal.rendering.renderer: Individual page rendering logic
  • bengal.cache.dependency_tracker: Dependency graph construction

See Also:

  • bengal/orchestration/render.py: render_pages() for rendering entry point
  • plan/active/rfc-template-performance-optimization.md: Performance RFC

Classes

RenderOrchestrator
Orchestrates page rendering in sequential or parallel modes. Handles page rendering with support f…
9

Orchestrates page rendering in sequential or parallel modes.

Handles page rendering with support for free-threaded Python for true parallelism. Manages thread-local rendering pipelines and integrates with dependency tracking for incremental builds.

Creation:

Direct instantiation: RenderOrchestrator(site)
  • Created by BuildOrchestrator during build
  • Requires Site instance with pages populated

Attributes

Name Type Description
site

Site instance containing pages and configuration

_free_threaded

Whether running on free-threaded Python (GIL disabled)

Relationships
  • Uses: RenderingPipeline for individual page rendering - Uses: DependencyTracker for dependency tracking - Uses: BuildStats for build statistics collection - Used by: BuildOrchestrator for rendering phase Thread Safety: Thread-safe for parallel rendering. Uses thread-local pipelines to avoid contention. Detects free-threaded Python automatically.

Methods 1

process
Render pages (parallel or sequential).
9 None
def process(self, pages: list[Page], parallel: bool = True, quiet: bool = False, tracker: DependencyTracker | None = None, stats: BuildStats | None = None, progress_manager: Any | None = None, reporter: Any | None = None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Render pages (parallel or sequential).

Parameters 9
pages list[Page]

List of pages to render

parallel bool

Whether to use parallel rendering

quiet bool

Whether to suppress progress output (minimal output mode)

tracker DependencyTracker | None

Dependency tracker for incremental builds

stats BuildStats | None

Build statistics tracker

progress_manager Any | None

Live progress manager (optional)

reporter Any | None
build_context Any | None
changed_sources set[Path] | None
Internal Methods 8
__init__
Initialize render orchestrator.
1 None
def __init__(self, site: Site)

Initialize render orchestrator.

Parameters 1
site Site

Site instance containing pages and configuration

_render_sequential
Build pages sequentially.
7 None
def _render_sequential(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, progress_manager: Any | None = None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Build pages sequentially.

Parameters 7
pages list[Page]

Pages to render

tracker DependencyTracker | None

Dependency tracker

quiet bool

Whether to suppress verbose output

stats BuildStats | None

Build statistics tracker

progress_manager Any | None

Live progress manager (optional)

build_context Any | None
changed_sources set[Path] | None
_render_parallel
Build pages in parallel for better performance. Threading Model: - Creates…
7 None
def _render_parallel(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, progress_manager: Any | None = None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Build pages in parallel for better performance.

Threading Model:

  • Creates ThreadPoolExecutor with max_workers threads
  • max_workers comes from config (default: 4)
  • Each thread gets its own RenderingPipeline instance (cached)
  • Each pipeline gets its own MarkdownParser instance (cached)

Free-Threaded Python Support (PEP 703):

  • Automatically detects Python 3.13t+ with GIL disabled
  • ThreadPoolExecutor gets true parallelism (no GIL contention)
  • ~1.5-2x faster rendering on multi-core machines
  • No code changes needed - works automatically

Caching Strategy:

Thread-local caching at two levels:
1. RenderingPipeline: One per thread (Jinja2 environment is expensive)
2. MarkdownParser: One per thread (parser setup is expensive)

This means with max_workers=N:
  • N RenderingPipeline instances created
  • N MarkdownParser instances created
  • Both are reused for all pages processed by that thread

Performance Example:

With 200 pages and max_workers=10:
  • 10 threads created

  • 10 pipelines created (one-time cost: ~50ms)

  • 10 parsers created (one-time cost: ~100ms)

  • Each thread processes ~20 pages

  • Per-page savings: ~5ms (pipeline) + ~10ms (parser) = ~15ms

  • Total savings: ~3 seconds vs creating fresh for each page

    On free-threaded Python (3.14t):

  • Same setup but ~1.78x faster due to true parallelism

  • 1000 pages in 1.94s vs 3.46s with GIL (515 vs 289 pages/sec)

Parameters 7
pages list[Page]

Pages to render

tracker DependencyTracker | None

Dependency tracker for incremental builds

quiet bool

Whether to suppress verbose output

stats BuildStats | None

Build statistics tracker

progress_manager Any | None

Live progress manager (optional)

build_context Any | None
changed_sources set[Path] | None
_render_parallel_simple
Parallel rendering without progress (traditional).
6 None
def _render_parallel_simple(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Parallel rendering without progress (traditional).

Parameters 6
pages list[Page]
tracker DependencyTracker | None
quiet bool
stats BuildStats | None
build_context Any | None
changed_sources set[Path] | None
_render_sequential_with_progress
Render pages sequentially with rich progress bar.
6 None
def _render_sequential_with_progress(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Render pages sequentially with rich progress bar.

Parameters 6
pages list[Page]
tracker DependencyTracker | None
quiet bool
stats BuildStats | None
build_context Any | None
changed_sources set[Path] | None
_render_parallel_with_live_progress
Render pages in parallel with live progress manager.
7 None
def _render_parallel_with_live_progress(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, progress_manager: Any, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Render pages in parallel with live progress manager.

Parameters 7
pages list[Page]
tracker DependencyTracker | None
quiet bool
stats BuildStats | None
progress_manager Any
build_context Any | None
changed_sources set[Path] | None
_render_parallel_with_progress
Render pages in parallel with rich progress bar.
6 None
def _render_parallel_with_progress(self, pages: list[Page], tracker: DependencyTracker | None, quiet: bool, stats: BuildStats | None, build_context: Any | None = None, changed_sources: set[Path] | None = None) -> None

Render pages in parallel with rich progress bar.

Parameters 6
pages list[Page]
tracker DependencyTracker | None
quiet bool
stats BuildStats | None
build_context Any | None
changed_sources set[Path] | None
_set_output_paths_for_pages
Pre-set output paths for specific pages before rendering. Only processes pages…
1 None
def _set_output_paths_for_pages(self, pages: list[Page]) -> None

Pre-set output paths for specific pages before rendering.

Only processes pages that are being rendered, not all pages in the site. This is an optimization for incremental builds where we only render a subset.

Parameters 1
pages list[Page]

Functions

_is_free_threaded
Detect if running on free-threaded Python (PEP 703). Free-threaded Python (python3.13t+) has the G…
0 bool
def _is_free_threaded() -> bool

Detect if running on free-threaded Python (PEP 703).

Free-threaded Python (python3.13t+) has the GIL disabled, allowing true parallel execution with ThreadPoolExecutor.

Returns

bool

True if running on free-threaded Python, False otherwise